diff --git a/.covrignore b/.covrignore new file mode 100644 index 00000000..d90e23c9 --- /dev/null +++ b/.covrignore @@ -0,0 +1 @@ +R/import-*.R diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..39c9638d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +R/import-*.R linguist-generated=true diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 3f51c5c7..9a20f2a5 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -44,3 +44,4 @@ jobs: clean: false branch: gh-pages folder: docs + single-commit: true diff --git a/.lintr b/.lintr index 0c778269..b008c93b 100644 --- a/.lintr +++ b/.lintr @@ -4,4 +4,4 @@ linters: linters_with_defaults( object_usage_linter = NULL, cyclocomp_linter = NULL ) -exclusions: list("tests/testthat.R", "R/cpp11.R") +exclusions: list("tests/testthat.R", "R/cpp11.R", "R/import-standalone-utils-assert.R", "R/import-standalone-utils-assert-path.R") diff --git a/DESCRIPTION b/DESCRIPTION index 73d63ee6..935e8154 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: orderly2 Title: Orderly Next Generation -Version: 1.99.34 +Version: 1.99.36 Authors@R: c(person("Rich", "FitzJohn", role = c("aut", "cre"), email = "rich.fitzjohn@gmail.com"), person("Robert", "Ashton", role = "aut"), diff --git a/R/import-standalone-utils-assert-path.R b/R/import-standalone-utils-assert-path.R new file mode 100644 index 00000000..cfbe9db1 --- /dev/null +++ b/R/import-standalone-utils-assert-path.R @@ -0,0 +1,151 @@ +# Standalone file: do not edit by hand +# Source: https://github.com/reside-ic/reside.utils/blob/HEAD/R/standalone-utils-assert-path.R +# Generated by: usethis::use_standalone("reside-ic/reside.utils", "utils-assert-path") +# ---------------------------------------------------------------------- +# +# --- +# repo: reside/reside.utils +# file: standalone-utils-assert-path.R +# dependencies: standalone-utils-assert.R +# imports: [cli, fs] +# --- +assert_file_exists <- function(files, name = "File", call = parent.frame(), + arg = NULL) { + err <- !file.exists(files) + ## TODO: throughout this file it would be nice to use cli's '.file' + ## class and ector contraction, *but* it renders poorly on default + ## black backgfrounds (dark blue) and makes testing a bit harder + ## because the rendering depends on cli options. + ## + ## TODO: add a canonical case check, as for the relative path bit. + if (any(err)) { + ## Because we interpolate both 'name' and the file list, we need + ## to disambiguate the quantity. + n <- cli::qty(sum(err)) + cli::cli_abort( + "{name}{n}{?s} {?does/do} not exist: {format_file_list(files[err])}", + call = call, arg = arg) + } +} + + +assert_file_exists_relative <- function(files, workdir, name, + call = parent.frame(), + arg = NULL) { + assert_relative_path(files, name, workdir, call) + + assert_character(files, name, call = call) + err <- !file_exists(files, workdir = workdir) + if (any(err)) { + n <- cli::qty(sum(err)) + cli::cli_abort( + c("{name}{n}{?s} {?does/do} not exist: {format_file_list(files[err])}", + i = "Looked within directory '{workdir}'"), + call = call) + } + + files_canonical <- file_canonical_case(files, workdir) + err <- is.na(files_canonical) | fs::path(files) != files_canonical + if (any(err)) { + i <- err & !is.na(files_canonical) + hint_case <- sprintf("For '%s', did you mean '%s'?", + files[i], files_canonical[i]) + names(hint_case) <- rep("i", length(hint_case)) + n <- cli::qty(sum(err)) + cli::cli_abort( + c("{name}{n}{?s} {?does/do} not exist: {format_file_list(files[err])}", + hint_case, + i = paste("If you don't use the canonical case for a file, your code", + "is not portable across different platforms"), + i = "Looked within directory '{workdir}'"), + call = call) + } +} + + +assert_is_directory <- function(path, name = "Directory", call = parent.frame(), + arg = NULL) { + assert_scalar_character(path, arg = arg, call = call) + assert_file_exists(path, name = name, arg = arg, call = call) + if (!fs::is_dir(path)) { + cli::cli_abort("Path exists but is not a directory: {path}", + call = call, arg = arg) + } +} + + +assert_relative_path <- function(files, name, workdir, call = parent.frame(), + arg = NULL) { + err <- fs::is_absolute_path(files) + if (any(err)) { + n <- cli::qty(sum(err)) + files_err <- files[err] + names(files_err) <- rep("x", length(files_err)) + cli::cli_abort( + c("{name}{n}{?s} must be {?a/} relative path{?s}", + files_err, + i = "Path was relative to directory '{workdir}'"), + call = call, arg = arg) + } + + err <- vapply(fs::path_split(files), function(x) any(x == ".."), TRUE) + if (any(err)) { + n <- cli::qty(sum(err)) + files_err <- files[err] + names(files_err) <- rep("x", length(files_err)) + cli::cli_abort( + c("{name}{n}{?s} must not contain '..' (parent directory) components", + files_err, + i = "Path was relative to directory '{workdir}'"), + call = call, arg = arg) + } +} + + +assert_directory_does_not_exist <- function(x, name = "Directory", arg = NULL, + call = parent.frame()) { + ok <- !fs::dir_exists(x) + if (!all(ok)) { + cli::cli_abort("{name}{?s} already exists: {format_file_list(x[!ok])}", + call = call, arg = arg) + } + invisible(x) +} + + +file_canonical_case <- function(path, workdir) { + if (length(path) != 1) { + return(vapply(path, file_canonical_case, "", workdir, USE.NAMES = FALSE)) + } + stopifnot(!fs::is_absolute_path(path)) + path_split <- tolower(fs::path_split(path)[[1]]) + base <- workdir + ret <- character(length(path_split)) + for (i in seq_along(path_split)) { + pos <- dir(base) + j <- which(path_split[[i]] == tolower(pos)) + if (length(j) != 1) { + return(NA_character_) + } + ret[[i]] <- pos[[j]] + base <- file.path(base, pos[[j]]) + } + paste(ret, collapse = "/") +} + + +file_exists <- function(..., workdir = NULL) { + files <- c(...) + if (!is.null(workdir)) { + assert_scalar_character(workdir) + owd <- setwd(workdir) # nolint + on.exit(setwd(owd)) # nolint + } + fs::file_exists(files) +} + + +format_file_list <- function(x) { + cli::cli_vec(sprintf("'%s'", x), + style = list("vec-sep2" = ", ", "vec-last" = ", ")) +} diff --git a/R/import-standalone-utils-assert.R b/R/import-standalone-utils-assert.R new file mode 100644 index 00000000..441503d8 --- /dev/null +++ b/R/import-standalone-utils-assert.R @@ -0,0 +1,251 @@ +# Standalone file: do not edit by hand +# Source: https://github.com/reside-ic/reside.utils/blob/HEAD/R/standalone-utils-assert.R +# Generated by: usethis::use_standalone("reside-ic/reside.utils", "utils-assert") +# ---------------------------------------------------------------------- +# +# --- +# repo: reside/reside.utils +# file: standalone-utils-assert.R +# imports: cli +# --- +assert_scalar <- function(x, name = deparse(substitute(x)), arg = name, + call = parent.frame()) { + if (length(x) != 1) { + cli::cli_abort( + c("'{name}' must be a scalar", + i = "{name} has length {length(x)}"), + call = call, arg = arg) + } + invisible(x) +} + + +assert_character <- function(x, name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + if (!is.character(x)) { + cli::cli_abort("Expected '{name}' to be character", call = call, arg = arg) + } + invisible(x) +} + + +assert_numeric <- function(x, name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + if (!is.numeric(x)) { + cli::cli_abort("Expected '{name}' to be numeric", call = call, arg = arg) + } + invisible(x) +} + + +assert_integer <- function(x, name = deparse(substitute(x)), + tolerance = NULL, arg = name, + call = parent.frame()) { + if (is.numeric(x)) { + rx <- round(x) + if (is.null(tolerance)) { + tolerance <- sqrt(.Machine$double.eps) + } + if (!isTRUE(all.equal(x, rx, tolerance = tolerance))) { + cli::cli_abort( + c("Expected '{name}' to be integer", + i = paste("{cli::qty(length(x))}The provided", + "{?value was/values were} numeric, but not very close", + "to integer values")), + arg = arg, call = call) + } + x <- as.integer(rx) + } else { + cli::cli_abort("Expected '{name}' to be integer", call = call, arg = arg) + } + invisible(x) +} + + +assert_logical <- function(x, name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + if (!is.logical(x)) { + cli::cli_abort("Expected '{name}' to be logical", arg = arg, call = call) + } + invisible(x) +} + + +assert_nonmissing <- function(x, name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + if (anyNA(x)) { + cli::cli_abort("Expected '{name}' to be non-NA", arg = arg, call = call) + } + invisible(x) +} + + +assert_scalar_character <- function(x, name = deparse(substitute(x)), + allow_null = FALSE, + arg = name, call = parent.frame()) { + if (allow_null && is.null(x)) { + return(invisible(x)) + } + assert_scalar(x, name, arg = arg, call = call) + assert_character(x, name, arg = arg, call = call) + assert_nonmissing(x, name, arg = arg, call = call) +} + + +assert_scalar_numeric <- function(x, name = deparse(substitute(x)), + allow_null = FALSE, + arg = name, call = parent.frame()) { + if (allow_null && is.null(x)) { + return(invisible(x)) + } + assert_scalar(x, name, arg = arg, call = call) + assert_numeric(x, name, arg = arg, call = call) + assert_nonmissing(x, name, arg = arg, call = call) +} + + +assert_scalar_integer <- function(x, name = deparse(substitute(x)), + tolerance = NULL, allow_null = FALSE, + arg = name, call = parent.frame()) { + if (allow_null && is.null(x)) { + return(invisible(x)) + } + assert_scalar(x, name, arg = arg, call = call) + assert_integer(x, name, tolerance = tolerance, arg = arg, call = call) + assert_nonmissing(x, name, arg = arg, call = call) +} + + +assert_scalar_logical <- function(x, name = deparse(substitute(x)), + allow_null = FALSE, + arg = name, call = parent.frame()) { + if (allow_null && is.null(x)) { + return(invisible(x)) + } + assert_scalar(x, name, arg = arg, call = call) + assert_logical(x, name, arg = arg, call = call) + assert_nonmissing(x, name, arg = arg, call = call) +} + + +assert_scalar_size <- function(x, allow_zero = TRUE, allow_null = FALSE, + name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + if (allow_null && is.null(x)) { + return(invisible(x)) + } + assert_scalar_integer(x, name = name, arg = arg, call = call) + assert_nonmissing(x, name, arg = arg, call = call) + min <- if (allow_zero) 0 else 1 + if (x < min) { + cli::cli_abort("'{name}' must be at least {min}", arg = arg, call = call) + } + invisible(x) +} + + +assert_length <- function(x, len, name = deparse(substitute(x)), arg = name, + call = parent.frame()) { + if (length(x) != len) { + cli::cli_abort( + "Expected '{name}' to have length {len}, but was length {length(x)}", + arg = arg, call = call) + } + invisible(x) +} + + +assert_is <- function(x, what, name = deparse(substitute(x)), arg = name, + call = parent.frame()) { + if (!inherits(x, what)) { + cli::cli_abort("Expected '{name}' to be a '{what}' object", + arg = arg, call = call) + } + invisible(x) +} + + +assert_list <- function(x, name = deparse(substitute(x)), arg = name, + call = parent.frame()) { + if (!is.list(x)) { + cli::cli_abort("Expected '{name}' to be a list", + arg = arg, call = call) + } + invisible(x) +} + + +assert_scalar_positive_numeric <- function(x, allow_zero = TRUE, + name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + assert_scalar_numeric(x, name = name, call = call) + if (allow_zero) { + if (x < 0) { + cli::cli_abort("'{name}' must be at least 0", arg = arg, call = call) + } + } else { + if (x <= 0) { + cli::cli_abort("'{name}' must be greater than 0", arg = arg, call = call) + } + } + invisible(x) +} + + +assert_scalar_positive_integer <- function(x, allow_zero = TRUE, + name = deparse(substitute(x)), + tolerance = NULL, arg = name, + call = parent.frame()) { + assert_scalar_integer(x, name, tolerance = tolerance, arg = arg, call = call) + min <- if (allow_zero) 0 else 1 + if (x < min) { + cli::cli_abort("'{name}' must be at least {min}", arg = arg, call = call) + } + invisible(x) +} + + +assert_raw <- function(x, len = NULL, name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + if (!is.raw(x)) { + cli::cli_abort("'{name}' must be a raw vector", arg = arg, call = call) + } + if (!is.null(len)) { + assert_length(x, len, name = name, call = call) + } + invisible(x) +} + + +assert_named <- function(x, unique = FALSE, name = deparse(substitute(x)), + arg = name, call = parent.frame()) { + nms <- names(x) + if (is.null(nms)) { + cli::cli_abort("'{name}' must be named", call = call, arg = arg) + } + if (anyNA(nms) || any(nms == "")) { + cli::cli_abort("All elements of '{name}' must be named", + call = call, arg = arg) + } + if (unique && anyDuplicated(names(x))) { + dups <- sprintf("'%s'", unique(names(x)[duplicated(names(x))])) + cli::cli_abort( + c("'{name}' must have unique names", + i = "Found {length(dups)} duplicate{?s}: {dups}"), + call = call, arg = arg) + } + invisible(x) +} + + +match_value <- function(x, choices, name = deparse(substitute(x)), arg = name, + call = parent.frame()) { + assert_scalar_character(x, call = call, name = name, arg = arg) + if (!(x %in% choices)) { + choices_str <- paste(sprintf("'%s'", choices), collapse = ", ") + cli::cli_abort(c("'{name}' must be one of {choices_str}", + i = "Instead we were given '{x}'"), call = call, + arg = arg) + } + x +} diff --git a/R/util.R b/R/util.R index ee25909b..d7c0a34f 100644 --- a/R/util.R +++ b/R/util.R @@ -598,27 +598,6 @@ pretty_bytes <- function(n) { } -file_canonical_case <- function(path, workdir) { - if (length(path) != 1) { - return(vcapply(path, file_canonical_case, workdir, USE.NAMES = FALSE)) - } - stopifnot(!fs::is_absolute_path(path)) - path_split <- tolower(fs::path_split(path)[[1]]) - base <- workdir - ret <- character(length(path_split)) - for (i in seq_along(path_split)) { - pos <- dir(base) - j <- which(path_split[[i]] == tolower(pos)) - if (length(j) != 1) { - return(NA_character_) - } - ret[[i]] <- pos[[j]] - base <- file.path(base, pos[[j]]) - } - paste(ret, collapse = "/") -} - - saverds_atomic <- function(data, path, allow_fail = FALSE) { tmp <- tempfile(pattern = sub("\\.rds", "", basename(path)), tmpdir = dirname(path), diff --git a/R/util_assert.R b/R/util_assert.R index 8d5c2f98..13342633 100644 --- a/R/util_assert.R +++ b/R/util_assert.R @@ -1,46 +1,6 @@ -assert_scalar <- function(x, name = deparse(substitute(x)), arg = name, - call = NULL) { - if (length(x) != 1) { - cli::cli_abort(c("'{name}' must be a scalar", - i = "{name} has length {length(x)}"), - call = call, arg = arg) - } -} - -assert_character <- function(x, name = deparse(substitute(x)), - arg = name, call = NULL) { - if (!is.character(x)) { - cli::cli_abort("'{name}' must be character", call = call, arg = arg) - } -} - -assert_logical <- function(x, name = deparse(substitute(x)), arg = name, - call = NULL) { - if (!is.logical(x)) { - cli::cli_abort("'{name}' must be logical", call = call, arg = arg) - } - invisible(x) -} - -assert_scalar_character <- function(x, name = deparse(substitute(x)), - arg = name, call = NULL, - allow_null = FALSE) { - if (is.null(x) && allow_null) { - return(invisible(x)) - } - assert_scalar(x, name, arg = arg, call = call) - assert_character(x, name, arg = arg, call = call) -} - -assert_scalar_logical <- function(x, name = deparse(substitute(x)), - arg = name, call = NULL) { - assert_scalar(x, name, arg = arg, call = call) - assert_logical(x, name, arg = arg, call = call) -} - assert_simple_scalar_atomic <- function(x, name = deparse(substitute(x)), - arg = name, call = NULL) { - assert_scalar(x, name) + arg = name, call = parent.frame()) { + assert_scalar(x, name, call = call) if (!is_simple_atomic(x)) { cli::cli_abort("'{name}' must be atomic (string, numeric, logical)", call = call, arg = arg) @@ -48,48 +8,6 @@ assert_simple_scalar_atomic <- function(x, name = deparse(substitute(x)), invisible(x) } -assert_unique_names <- function(x, name = deparse(substitute(x)), - arg = name, call = NULL) { - if (any(duplicated(names(x)))) { - dups <- unique(names(x)[duplicated(names(x))]) - cli::cli_abort( - c("'{name}' must have unique names", - i = "Found {length(dups)} duplicate{?s}: {collapseq(dups)}"), - call = call, arg = arg) - } -} - -assert_named <- function(x, unique = FALSE, name = deparse(substitute(x)), - arg = name, call = NULL) { - ## TODO: we get bad quotes here from static_orderly_parameters - if (is.null(names(x))) { - cli::cli_abort("'{name}' must be named", call = call, arg = arg) - } - if (unique) { - assert_unique_names(x, name = name, arg = arg, call = call) - } -} - -assert_is <- function(x, what, name = deparse(substitute(x)), - arg = name, call = NULL) { - if (!inherits(x, what)) { - cli::cli_abort( - c("'{name}' must be a {paste(what, collapse = ' / ')}", - "{name} was a {paste(class(x), collapse = ' / ')}"), - call = call, arg = arg) - } -} - -assert_file_exists <- function(files, name = "File", call = NULL, arg = NULL) { - err <- !file.exists(files) - if (any(err)) { - n <- cli::qty(sum(err)) - cli::cli_abort( - "{name}{n}{?s} {?does/do} not exist: {collapseq(files[err])}", - call = call, arg = arg) - } -} - find_entrypoint_filename <- function(src, suppress_zero_files = FALSE, suppress_multiple_files = FALSE) { @@ -119,91 +37,3 @@ find_entrypoint_filename <- function(src, suppress_zero_files = FALSE, } if (n_found == 1) names[files_exist] else NA_character_ } - - -assert_file_exists_relative <- function(files, workdir, name, call = NULL) { - assert_relative_path(files, name, workdir, call) - - assert_character(files, call = call) - err <- !file_exists(files, workdir = workdir) - if (any(err)) { - n <- cli::qty(sum(err)) - cli::cli_abort( - c("{name}{n}{?s} {?does/do} not exist: {collapseq(files[err])}", - i = "Looked within directory '{workdir}'"), - call = call) - } - - files_canonical <- file_canonical_case(files, workdir) - err <- is.na(files_canonical) | fs::path(files) != files_canonical - if (any(err)) { - i <- err & !is.na(files_canonical) - hint_case <- sprintf("For '%s', did you mean '%s'?", - files[i], files_canonical[i]) - n <- cli::qty(sum(err)) - cli::cli_abort( - c("{name}{n}{?s} {?does/do} not exist: {collapseq(files[err])}", - set_names(hint_case, "i"), - i = paste("If you don't use the canonical case for a file, your code", - "is not portable across different platforms"), - i = "Looked within directory '{workdir}'"), - call = call) - } -} - -assert_is_directory <- function(path, name = "Directory", call = NULL, - arg = NULL) { - assert_scalar_character(path, arg = arg, call = call) - assert_file_exists(path, name = name, arg = arg, call = call) - if (!is_directory(path)) { - cli::cli_abort("Path exists but is not a directory: {path}", - call = call, arg = arg) - } -} - -assert_relative_path <- function(files, name, workdir, call = NULL, - arg = NULL) { - err <- fs::is_absolute_path(files) - if (any(err)) { - n <- cli::qty(sum(err)) - cli::cli_abort( - c("{name}{n}{?s} must be {?a/} relative path{?s}", - set_names(files[err], "x"), - i = "Path was relative to directory '{workdir}'"), - call = call, arg = arg) - } - - err <- vlapply(fs::path_split(files), function(x) any(x == "..")) - if (any(err)) { - n <- cli::qty(sum(err)) - cli::cli_abort( - c("{name}{n}{?s} must not contain '..' (parent directory) components", - set_names(files[err], "x"), - i = "Path was relative to directory '{workdir}'"), - call = call, arg = arg) - } -} - - -assert_directory_does_not_exist <- function(x, name = "Directory", arg = NULL, - call = NULL) { - ok <- !fs::dir_exists(x) - if (!all(ok)) { - cli::cli_abort("{name} already exists: {collapseq(x[!ok])}", - call = call, arg = arg) - } - invisible(x) -} - - -match_value <- function(x, choices, name = deparse(substitute(x)), - arg = name, call = NULL) { - assert_scalar_character(x, name = name, arg = arg, call = call) - if (!(x %in% choices)) { - cli::cli_abort( - c("'{name}' must be one of {collapseq(choices)}", - i = "Instead we were given '{x}'"), - call = call, arg = arg) - } - x -} diff --git a/tests/testthat/test-outpack-metadata.R b/tests/testthat/test-outpack-metadata.R index a68606d9..23ae995b 100644 --- a/tests/testthat/test-outpack-metadata.R +++ b/tests/testthat/test-outpack-metadata.R @@ -111,7 +111,7 @@ test_that("Sensible error if metadata file not found", { root <- create_temporary_root(use_file_store = TRUE) expect_error( orderly_metadata(1, root), - "'id' must be character") + "Expected 'id' to be character") expect_error( orderly_metadata(letters, root), "'id' must be a scalar") diff --git a/tests/testthat/test-query.R b/tests/testthat/test-query.R index b9cf3be1..a32402e9 100644 --- a/tests/testthat/test-query.R +++ b/tests/testthat/test-query.R @@ -258,7 +258,7 @@ test_that("include parameters from subqueries too", { test_that("validate inputs to orderly_query", { expect_error( orderly_query("latest", list(a = 1)), - "'name' must be character") + "Expected 'name' to be character") }) diff --git a/tests/testthat/test-util-assert.R b/tests/testthat/test-util-assert.R index dc30bca6..0975dd9e 100644 --- a/tests/testthat/test-util-assert.R +++ b/tests/testthat/test-util-assert.R @@ -1,146 +1,6 @@ -test_that("assert_scalar", { - expect_error(assert_scalar(NULL), "must be a scalar") - expect_error(assert_scalar(numeric(0)), "must be a scalar") - expect_error(assert_scalar(1:2), "must be a scalar") -}) - - -test_that("assert_character", { - expect_silent(assert_character("a")) - expect_error(assert_character(1), "must be character") - expect_error(assert_character(TRUE), "must be character") -}) - - -test_that("assert_logical", { - expect_silent(assert_logical(TRUE)) - expect_error(assert_logical(1), "must be logical") - expect_error(assert_logical("true"), "must be logical") -}) - - test_that("assert_simple_scalar_atomic", { expect_silent(assert_simple_scalar_atomic(TRUE)) expect_silent(assert_simple_scalar_atomic(1)) expect_silent(assert_simple_scalar_atomic("a")) expect_error(assert_simple_scalar_atomic(list(1)), "must be atomic") }) - -test_that("assert_unique_names", { - expect_error(assert_unique_names(setNames(1:3, c("a", "a", "c"))), - "must have unique names") - expect_error(assert_unique_names(setNames(1:3, c("a", "c", "a"))), - "must have unique names") - expect_silent(assert_unique_names(setNames(1:3, c("a", "b", "c")))) -}) - - -test_that("assert_named", { - expect_error(assert_named(1), "must be named") - expect_error(assert_named(setNames(1:2, c("a", "a")), TRUE), - "must have unique names") - expect_silent(assert_named(setNames(1:2, c("a", "a")), FALSE)) -}) - - -test_that("assert_is", { - expect_error(assert_is("x", "foo"), "must be a foo") - expect_silent(assert_is(structure("x", class = "foo"), "foo")) -}) - - -test_that("assert_file_exists", { - tmp <- normalise_path(tempdir()) - path <- tempfile(tmpdir = tmp) - expect_error(assert_file_exists(path, "File"), "File does not exist") - file.create(path) - expect_silent(assert_file_exists(path, "File")) -}) - - -test_that("assert_file_exists_relative works checks if files exist", { - tmp <- withr::local_tempdir() - file.create(file.path(tmp, "c")) - expect_error(assert_file_exists_relative("a", tmp, "File"), - "File does not exist: 'a'") - expect_error(assert_file_exists_relative(c("a", "b"), tmp, "File"), - "Files do not exist: 'a', 'b'") - expect_error(assert_file_exists_relative(c("a", "b", "c", "d"), tmp, "File"), - "Files do not exist: 'a', 'b', 'd'") - expect_silent(assert_file_exists_relative("c", tmp, "File")) -}) - - -test_that("assert_file_exists_relative informs about case mismatch", { - testthat::skip_if_not_installed("mockery") - mock_file_exists <- mockery::mock(TRUE, cycle = TRUE) - mockery::stub(assert_file_exists_relative, "file_exists", mock_file_exists) - - tmp <- withr::local_tempdir() - file.create(file.path(tmp, "a")) - fs::dir_create(file.path(tmp, "b/c")) - file.create(file.path(tmp, "b/c/d")) - - err <- expect_error( - assert_file_exists_relative("A", tmp, "File"), - "File does not exist: 'A'") - expect_length(err$body, 3) - expect_equal(names(err$body), c("i", "i", "i")) - expect_equal(err$body[[1]], "For 'A', did you mean 'a'?") - expect_match(err$body[[2]], "If you don't use the canonical case for a file") - expect_match(err$body[[3]], "Looked within directory '.+'") - - err <- expect_error( - assert_file_exists_relative(c("A", "b/C/d"), tmp, "File"), - "Files do not exist: 'A', 'b/C/d'") - expect_length(err$body, 4) - expect_equal(names(err$body), c("i", "i", "i", "i")) - expect_equal(err$body[[1]], "For 'A', did you mean 'a'?") - expect_equal(err$body[[2]], "For 'b/C/d', did you mean 'b/c/d'?") - expect_match(err$body[[3]], "If you don't use the canonical case for a file") - expect_match(err$body[[4]], "Looked within directory '.+'") - - err <- expect_error( - assert_file_exists_relative(c("A", "b/X/d"), tmp, "File"), - "Files do not exist: 'A', 'b/X/d'") - expect_length(err$body, 3) - expect_equal(names(err$body), c("i", "i", "i")) - expect_equal(err$body[[1]], "For 'A', did you mean 'a'?") - expect_match(err$body[[2]], "If you don't use the canonical case for a file") - expect_match(err$body[[3]], "Looked within directory '.+'") -}) - - -test_that("assert_is_directory", { - path <- tempfile(tmpdir = normalise_path(tempdir())) - expect_error(assert_is_directory(path), "Directory does not exist") - file.create(path) - expect_error(assert_is_directory(path), - "Path exists but is not a directory") - expect_silent(assert_is_directory(".")) -}) - - -test_that("assert_relative_path", { - workdir <- getwd() - expect_error(assert_relative_path(getwd(), "File", workdir), - "File must be a relative path", - fixed = TRUE) - expect_silent(assert_relative_path("relpath", "File", workdir)) - expect_silent(assert_relative_path("a/b/c", "File", workdir)) - - expect_error( - assert_relative_path("../my/path", "File", workdir), - "must not contain '..' (parent directory) components", - fixed = TRUE) - expect_error( - assert_relative_path("my/../../path", "File", workdir), - "must not contain '..' (parent directory) components", - fixed = TRUE) -}) - - -test_that("match_value", { - expect_error(match_value("foo", letters), "must be one of") - expect_silent(match_value("a", letters)) -}) diff --git a/tests/testthat/test-util.R b/tests/testthat/test-util.R index 4b5975fe..c86ce449 100644 --- a/tests/testthat/test-util.R +++ b/tests/testthat/test-util.R @@ -294,20 +294,6 @@ test_that("can collapse with special last case", { }) -test_that("can convert files to canonical case", { - tmp <- withr::local_tempdir() - p <- file.path(tmp, "a", "b", "c") - fs::dir_create(dirname(p)) - file.create(p) - expect_equal(file_canonical_case("a/b/c", tmp), "a/b/c") - expect_equal(file_canonical_case("a//b//c", tmp), "a/b/c") - expect_equal(file_canonical_case("a/B/c", tmp), "a/b/c") - expect_equal(file_canonical_case("A/B/C", tmp), "a/b/c") - expect_equal(file_canonical_case("A/win~1/C", tmp), NA_character_) - expect_equal(file_canonical_case(c("a/b/c", "a/b/d"), tmp), c("a/b/c", NA)) -}) - - test_that("can gracefully cope with rds save failure", { mock_move <- mockery::mock(stop("some error"), cycle = TRUE) mockery::stub(saverds_atomic, "fs::file_move", mock_move) diff --git a/vignettes/collaboration.Rmd b/vignettes/collaboration.Rmd index 50f7cf2d..e5d9c1a4 100644 --- a/vignettes/collaboration.Rmd +++ b/vignettes/collaboration.Rmd @@ -233,7 +233,7 @@ This multi-step process means that we avoid copying data that is already known a It is important that the `.outpack` directory is *not* shared via git; we warn about this now, and you can use `orderly2::ordery_gitignore_update()` to automatically create a suitable `.gitignore` file that will prevent it being accidentally committed. -However, +However, If Alice and Bob were starting on a new machine they would: @@ -253,20 +253,20 @@ One of the simplest ways to share packets with a collaborator is through a share 1. Initialise an orderly location on the shared file system ```{r as="alice"} orderly2::orderly_init( - root = path_sharepoint_alice, - path_archive = NULL, - use_file_store = TRUE, + root = path_sharepoint_alice, + path_archive = NULL, + use_file_store = TRUE, require_complete_tree = TRUE ) ``` - + Create an orderly store with a file store and a complete tree. See `orderly2::orderly_init()` for more details. 1. Add this as a location ```{r, as="alice", inwd = path_alice} orderly2::orderly_location_add( "sharepoint", "path", list(path = path_sharepoint_alice)) ``` - + 1. Push any packets you want to share ```{r, as="alice", inwd = path_alice} orderly2::orderly_location_push(id, "sharepoint") @@ -280,11 +280,10 @@ Then these will be available for your collaborator to pull. Note that the data i orderly2::orderly_location_add( "alices_orderly", "path", list(path = path_sharepoint_bob)) ``` - + 1. Pull the metadata and use the packets as desired ```{r, as="bob", inwd = path_bob} orderly2::orderly_location_pull_metadata( "alices_orderly" ) ``` - diff --git a/vignettes/introduction.Rmd b/vignettes/introduction.Rmd index 5b8634cd..6902440e 100644 --- a/vignettes/introduction.Rmd +++ b/vignettes/introduction.Rmd @@ -187,7 +187,8 @@ In addition, there is also a function `orderly::orderly_run_info()` that can be Let's add some additional annotations to the previous reports: ```{r, include = FALSE} -code_data <- readLines(file.path(path, "src", "incoming_data", "incoming_data.R")) +code_data <- readLines( + file.path(path, "src", "incoming_data", "incoming_data.R")) writeLines(c( "orderly2::orderly_strict_mode()", 'orderly2::orderly_resource("data.csv")',