Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added .stamp/catalog.lock.lock
Empty file.
Binary file added .stamp/catalog.qs2
Binary file not shown.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export(log_reset)
export(log_save)
export(log_show)
export(log_summary)
export(log_versions)
export(log_warn)
export(merge_branch_into)
export(new_pip_release)
Expand Down
201 changes: 113 additions & 88 deletions R/log.R
Original file line number Diff line number Diff line change
Expand Up @@ -184,132 +184,157 @@ log_init <- function(name = getOption("pipfun.log.default"),

#' Save a log to disk
#'
#' Saves a log stored in `.piplogenv` to a `.qs` file for persistence.
#' Saves a log stored in `.piplogenv` to disk using {stamp}, with metadata
#' and versioning support.
#'
#' @param name Name of the log in memory (default:
#' `getOption("pipfun.log.default")`).
#' @param path `r lifecycle::badge("deprecated")` `path` is no longer supported.
#' Use `board` argument now. If value passed to `path` is not a pins board, it
#' will through an error.
#' @param board pins board
#' @param pin_name name of pin that will be used to load the log. By default it is the same as `name`.
#' @inheritDotParams pins::pin_write title description metadata tags
#' @param dir Directory where the log should be saved.
#' @param id File identifier (without extension). Defaults to `name`.
#' @param format File format (default: "qs2").
#' @param metadata Optional named list of metadata to attach.
#' @param code Optional code object whose hash will be stored.
#' @param ... Forwarded to `stamp::st_save()`.
#'
#'
#' @return Invisible `TRUE` if successful.
#' @return Invisibly, the result returned by `stamp::st_save()`.
#' @export
log_save <- function(name = getOption("pipfun.log.default", "default"),
board = NULL,
pin_name = name,
path = deprecated(),
...) {

if (lifecycle::is_present(path)) {
lifecycle::deprecate_warn(
when = "0.3.7",
what = "log_save(path)",
with = "log_save(board)",
details = "all the logs will be saved as pins, so you need to use a pins board rather than a directory path"
)
board <- path
log_save <- function(
name = getOption("pipfun.log.default", "default"),
dir,
id = name,
format = "qs2",
metadata = list(),
code = NULL,
...
) {

# ---- Validate directory ----
if (missing(dir) || !fs::dir_exists(dir)) {
cli::cli_abort("Provided directory path does not exist: {.path {dir}}")
}

if (!inherits(board, "pins_board")) {
cli::cli_abort("{.arg board} must be a pins_board class object")
# ---- Validate log ----
if (!rlang::env_has(.piplogenv, name)) {
cli::cli_abort("Log {.field {name}} does not exist in memory.")
}

log <- rlang::env_get(.piplogenv, name)

if (!exists(name, envir = .piplogenv)) {
cli::cli_abort("Log {.field {name}} does not exist in memory.")
# Restore class if dropped by serialization
if (is.data.table(log)) {
setattr(log, "class", unique(c("piplog", class(log))))
}

log <- get(name, envir = .piplogenv)

# Final validation
if (!inherits(log, "piplog")) {
cli::cli_abort("Object {.field {name}} is not a valid piplog.")
cli::cli_abort("File does not contain a valid {.cls piplog} object.")
}

pins::pin_write(board = board,
x = log,
name = pin_name,
type = "qs",
versioned = TRUE,
...)
# ---- Build stamp path ----
file <- fs::path(dir, id, ext = format)
sp <- stamp::st_path(file, format = format)

# ---- Save with stamp ----
out <- stamp::st_save(
x = log,
file = sp,
metadata = c(
list(
class = "piplog",
log_name = name,
saved_at = Sys.time()
),
metadata
),
code = code,
format = format,
...
)

invisible(TRUE)
invisible(out)
}


#' Load a log from a .qs file
#'
#' Loads a previously saved log into `.piplogenv`, optionally under a different
#' name.
#' Load a log from disk
#'
#' @param board pins board
#' @param pin_name name of pin that will be used to load the log. By default it
#' is the same as `name`.
#' @inheritParams pins::pin_read
#' @param name `r lifecycle::badge("deprecated")` `name` has been superseded by
#' `pin_name`. It is nor inferred from filename any more.
#' @param path `r lifecycle::badge("deprecated")` `path` is no longer supported.
#' Use `board` argument now. If value passed to `path` is not a pins board, it
#' will through an error.
#' @inheritDotParams pins::pin_read
#' Loads a previously saved piplog from disk using {stamp}, optionally under a
#' different name.
#'
#' @param overwrite logical: whether to override the log in `.piplogenv` with
#' the same `pin_name`. Default is FALSE.
#' @param dir Directory where the log is stored.
#' @param id File identifier (without extension). Defaults to `name`.
#' @param name Name to assign to the log in memory (default: `id`).
#' @param version Optional version identifier passed to `stamp::st_load()`.
#' Use `"available"` to list available versions.
#' @param format File format (default: "qs2").
#' @param overwrite Logical: whether to overwrite an existing log in
#' `.piplogenv`. Default is FALSE.
#' @param verbose Logical: whether to announce loading progress.
#'
#' @return Invisibly returns the name of the loaded log.
#' @export
log_load <- function(board,
pin_name = name,
version = NULL,
hash = NULL,
path = deprecated(),
name = deprecated(),
overwrite = FALSE,
...) {

if (lifecycle::is_present(path)) {
lifecycle::deprecate_warn(
when = "0.3.7",
what = "log_save(path)",
with = "log_save(board)",
details = "all the logs will be saved as pins, so you need to use a pins board rather than a directory path"
)
board <- path
log_load <- function(
dir,
id,
name = id,
version = NULL,
format = "qs2",
overwrite = FALSE,
verbose = TRUE
) {

# ---- Validate directory ----
if (missing(dir) || !fs::dir_exists(dir)) {
cli::cli_abort("Artifact folder {.path {dir}} does not exist.")
}
if (lifecycle::is_present(name)) {
lifecycle::deprecate_warn(
when = "0.3.7",
what = "log_save(name)",
with = "log_save(pin_name)"
)
pin_name <- name

# ---- Build path ----
file <- fs::path(dir, id, ext = format)

# ---- List available versions ----
if (identical(version, "available")) {
vr <- stamp::st_versions(file)
if (nrow(vr) == 0) {
cli::cli_abort("No versions found in {.path {file}}.")
}
vr[, vintage := (.I - 1) * -1]
return(vr[])
}

if (!inherits(board, "pins_board")) {
cli::cli_abort("{.arg board} must be a pins_board class object")
# ---- Load log ----
ver <- if (is.null(version)) "latest" else version

if (verbose) {
cli::cli_alert_info(
"Loading {.path {file}} (version = {.strong {ver}})"
)
}

log <- pins::pin_read(board = board,
name = pin_name,
version = version,
hash = hash,
...)

log <- stamp::st_load(file, version = version)

# ---- Validate object ----
# Restore class if dropped by serialization
if (is.data.table(log)) {
setattr(log, "class", unique(c("piplog", class(log))))
}

# Final validation
if (!inherits(log, "piplog")) {
cli::cli_abort("File does not contain a valid {.cls piplog} object.")
}


if (rlang::env_has(.piplogenv, pin_name) && !overwrite) {
cli::cli_abort("A log named {.field {pin_name}} already exists in memory. Use {.code overwrite = TRUE} to replace it.")
# ---- Handle overwrite ----
if (rlang::env_has(.piplogenv, name) && !isTRUE(overwrite)) {
cli::cli_abort(
"A log named {.field {name}} already exists in memory.
Use {.code overwrite = TRUE} to replace it."
)
}

rlang::env_poke(.piplogenv, pin_name, log)
rlang::env_poke(.piplogenv, name, log)

invisible(pin_name)
invisible(name)
}

#' Reset or delete a log from memory
Expand Down
37 changes: 37 additions & 0 deletions R/log_helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,40 @@ inspect_args <- function(.env = parent.frame()) {
structure(resolved, class = "inspect_args")
}

#' List available versions of a saved log
#'
#' Lists all saved versions of a log stored on disk using {stamp}.
#'
#' @param dir Directory where the log is stored.
#' @param id File identifier (without extension).
#' @param format File format (default: "qs2").
#'
#' @return A data.table of available versions.
#' @export
log_versions <- function(
dir,
id,
format = "qs2"
) {

# ---- Validate directory ----
if (missing(dir) || !fs::dir_exists(dir)) {
cli::cli_abort("Artifact folder {.path {dir}} does not exist.")
}

# ---- Build file path ----
file <- fs::path(dir, id, ext = format)

# ---- Get versions ----
vr <- stamp::st_versions(file)

if (nrow(vr) == 0) {
cli::cli_abort("No versions found for {.path {file}}.")
}

# ---- Add convenience ordering (latest = 0) ----
vr[, vintage := (.I - 1) * -1]

vr[]
}

47 changes: 17 additions & 30 deletions man/log_load.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading