Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow orderly_shared_resource to be used with unnamed arguments. #136

Merged
merged 3 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 12 additions & 9 deletions R/metadata.R
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,9 @@ static_orderly_dependency <- function(args) {
##'
##' @title Copy shared resources into a packet directory
##'
##' @param ... Named arguments corresponding to shared resources to
##' copy. The name will be the destination filename, while the value
##' is the filename within the shared resource directory.
##' @param ... The shared resources to copy. If arguments are named, the name
##' will be the destination file while the value is the filename within the
##' shared resource directory.
##'
##' @return Invisibly, a data.frame with columns `here` (the fileames
##' as as copied into the running packet) and `there` (the filenames
Expand All @@ -351,21 +351,23 @@ orderly_shared_resource <- function(...) {
invisible(files)
}


validate_shared_resource <- function(args, call) {
if (length(args) == 0) {
cli::cli_abort("'orderly_shared_resource' requires at least one argument",
call = call)
}
assert_named(args, unique = TRUE, call = environment())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you see if you can harmonise this through validate_file_from_to (in outpack_misc.R) which contains most of this logic? I think it should just drop in directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha yes. I hadn't noticed that function. Switched to using it.

We now get string interpolation on the filenames "for free". I'm not sure whether it makes much sense to support it. I could disable it easily by setting envir = rlang::empty_env() in the call to validate_file_from_to.

I'm even sure the interpolation is that useful in orderly_dependency / orderly_copy_files. As far as I can tell, it only expands the names, not the values. This means orderly_dependency(..., files = "${foo}") wouldn't work, because it would use the literal ${foo} as the source file name... We could fix that behaviour, or we could remove that feature entirely (is there any benefit in us doing the interpolation? Why can't the caller use sprintf?)

is_invalid <- !vlapply(args, function(x) is.character(x) && length(x) == 1)
if (any(is_invalid)) {
cli::cli_abort(
sprintf(
"Invalid shared resource %s: entries must be strings",
paste(squote(names(args)[is_invalid]), collapse = ", ")),
"Arguments to 'orderly_shared_resource' must be strings",
call = call)
}

args <- fill_missing_names(args)
assert_unique_names(args, call = call,
name = "Arguments to 'orderly_shared_resource'")

list_to_character(args)
}

Expand Down Expand Up @@ -405,7 +407,8 @@ copy_shared_resource <- function(path_root, path_dest, config, files, call) {


static_orderly_shared_resource <- function(args) {
unlist(lapply(args, static_character_vector, TRUE), FALSE, TRUE)
list_to_character(
fill_missing_names(lapply(args, static_character_vector, TRUE)))
}


Expand Down
13 changes: 13 additions & 0 deletions R/util.R
Original file line number Diff line number Diff line change
Expand Up @@ -644,3 +644,16 @@ is_testing <- function() {
# https://github.com/r-lib/testthat/blob/fe50a22/R/test-env.R#L20
identical(Sys.getenv("TESTTHAT"), "true")
}

#' Given a character vector or list, missing names are filled using the value.
fill_missing_names <- function(x) {
if (!is.null(x)) {
if (is.null(names(x))) {
names(x) <- list_to_character(x)
} else {
missing <- is.na(names(x)) | names(x) == ""
names(x)[missing] <- x[missing]
}
}
x
}
19 changes: 13 additions & 6 deletions R/util_assert.R
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,25 @@ 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 && 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)
if (unique) {
assert_unique_names(x, name = name, arg = arg, call = call)
}
}

Expand Down
7 changes: 7 additions & 0 deletions tests/testthat/examples/shared-shorthand/shared-shorthand.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
orderly2::orderly_shared_resource("data.csv")
orderly2::orderly_artefact("A graph of things", "mygraph.png")

data <- read.csv("data.csv", stringsAsFactors = FALSE)
png("mygraph.png")
plot(data)
dev.off()
4 changes: 2 additions & 2 deletions tests/testthat/helper-orderly.R
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ copy_examples <- function(examples, path_src) {
}

fs::dir_create(path_src)
if (any(c("shared", "shared-dir") %in% examples)) {
if (any(c("shared", "shared-shorthand", "shared-dir") %in% examples)) {
fs::dir_create(file.path(path_src, "shared"))
if ("shared" %in% examples) {
if (any(c("shared", "shared-shorthand") %in% examples)) {
fs::file_copy(test_path("examples/explicit/data.csv"),
file.path(path_src, "shared"))
}
Expand Down
23 changes: 23 additions & 0 deletions tests/testthat/test-cleanup.R
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,29 @@ test_that("can clean up shared resources", {
expect_equal(status$delete, "shared_data.csv")
})

test_that("can clean up shared resources with shorthand syntax", {
path <- test_prepare_orderly_example("shared-shorthand")
path_src <- file.path(path, "src", "shared-shorthand")
file.create(file.path(path_src, "data.csv"))
status <- orderly_cleanup_status("shared-shorthand", root = path)

files <- c("data.csv", "shared-shorthand.R")
expect_setequal(rownames(status$role), files)
expect_equal(
status$role,
cbind(orderly = set_names(c(FALSE, TRUE), files),
resource = FALSE,
shared_resource = c(TRUE, FALSE),
dependency = FALSE,
artefact = FALSE))
expect_equal(
status$status,
cbind(source = set_names(c(FALSE, TRUE), files),
derived = c(TRUE, FALSE),
ignored = NA))
expect_equal(status$delete, "data.csv")
})


test_that("can clean up dependencies", {
path <- test_prepare_orderly_example(c("data", "depends"))
Expand Down
74 changes: 72 additions & 2 deletions tests/testthat/test-run.R
Original file line number Diff line number Diff line change
Expand Up @@ -261,19 +261,64 @@ test_that("can run manually with shared resources", {
})


test_that("can run with shared resources using shorthand arguments", {
path <- test_prepare_orderly_example("shared-shorthand")
envir <- new.env()
id <- orderly_run_quietly("shared-shorthand", root = path, envir = envir)
expect_setequal(
dir(file.path(path, "archive", "shared-shorthand", id)),
c("data.csv", "mygraph.png", "shared-shorthand.R"))
meta <- orderly_metadata(id, root = path)
expect_equal(nrow(meta$custom$orderly$shared), 1)
expect_equal(meta$custom$orderly$shared,
data_frame(here = "data.csv", there = "data.csv"))
expect_equal(
meta$custom$orderly$role,
data_frame(path = c("shared-shorthand.R", "data.csv"),
role = c("orderly", "shared")))
})


test_that("can validate shared resource arguments", {
expect_error(
validate_shared_resource(list(), NULL),
"'orderly_shared_resource' requires at least one argument")

expect_error(
validate_shared_resource(list(input = c("a", "b")), NULL),
"Invalid shared resource 'input': entries must be strings")
"Arguments to 'orderly_shared_resource' must be strings")
expect_error(
validate_shared_resource(list(a = 1, b = TRUE, c = "str"), NULL),
"Invalid shared resource 'a', 'b': entries must be strings")
"Arguments to 'orderly_shared_resource' must be strings")
expect_error(
validate_shared_resource(list(1, TRUE, "str"), NULL),
"Arguments to 'orderly_shared_resource' must be strings")
expect_error(
validate_shared_resource(list(a = 1, TRUE, "str"), NULL),
"Arguments to 'orderly_shared_resource' must be strings")

expect_error(
validate_shared_resource(list(a = "A", a = "B"), NULL),
"'Arguments to 'orderly_shared_resource'' must have unique names")
expect_error(
validate_shared_resource(list("a", "a"), NULL),
"'Arguments to 'orderly_shared_resource'' must have unique names")
expect_error(
validate_shared_resource(list("a", a = "B"), NULL),
"'Arguments to 'orderly_shared_resource'' must have unique names")

expect_equal(
validate_shared_resource(list(a = "A", b = "B"), NULL),
c(a = "A", b = "B"))
expect_equal(
validate_shared_resource(list(a = "A", b = "A"), NULL),
c(a = "A", b = "A"))
expect_equal(
validate_shared_resource(list("a", "b"), NULL),
c(a = "a", b = "b"))
expect_equal(
validate_shared_resource(list("a", b = "B"), NULL),
c(a = "a", b = "B"))
})


Expand Down Expand Up @@ -1243,6 +1288,31 @@ test_that("can read about assigned shared resources", {
expect_equal(res$shared_resource, c(shared_data = "data"))
})

test_that("can read about shared resources with shorthand syntax", {
path <- test_prepare_orderly_example("shared-shorthand")
path_src <- file.path(path, "src", "shared-shorthand")
code <- readLines(file.path(path_src, "shared-shorthand.R"))
code <- sub("orderly2::orderly_shared_resource",
"r <- orderly2::orderly_shared_resource",
code)
code <- c(code, 'saveRDS(r, "resources.rds")')
writeLines(code, file.path(path_src, "shared-shorthand.R"))

id <- orderly_run_quietly("shared-shorthand", root = path, envir = new.env())
r <- readRDS(file.path(path, "archive", "shared-shorthand",
id, "resources.rds"))
expect_equal(r, data_frame(here = "data.csv", there = "data.csv"))

res <- withr::with_dir(
path_src,
withVisible(orderly_shared_resource("data.csv")))
expect_equal(res$visible, FALSE)
expect_equal(res$value, r)

res <- orderly_read(path_src)
expect_equal(res$shared_resource, c(data.csv = "data.csv"))
})


test_that("can read about dependencies", {
path <- test_prepare_orderly_example(c("data", "depends"))
Expand Down
8 changes: 8 additions & 0 deletions tests/testthat/test-util-assert.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ test_that("assert_simple_scalar_atomic", {
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")
Expand Down
15 changes: 15 additions & 0 deletions tests/testthat/test-util.R
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,18 @@ test_that("read_string strips newlines", {
result <- expect_silent(read_string(path))
expect_equal(result, "12345678")
})

test_that("fill_missing_names works", {
expect_equal(fill_missing_names(NULL), NULL)

expect_equal(fill_missing_names(c("a", "b")), c(a = "a", b = "b"))
expect_equal(fill_missing_names(c("a", "a")), c(a = "a", a = "a"))
expect_equal(fill_missing_names(c(x = "a", "a")), c(x = "a", a = "a"))

expect_equal(fill_missing_names(list("a", "a")),
list(a = "a", a = "a"))
expect_equal(fill_missing_names(list(x = "a", "a")),
list(x = "a", a = "a"))
expect_equal(fill_missing_names(list(x = "a", y = "a")),
list(x = "a", y = "a"))
})
2 changes: 1 addition & 1 deletion vignettes/introduction.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ We can then write an orderly report `use_shared` that uses this shared file, wit
```{r, echo = FALSE, results = "asis"}
fs::dir_create(file.path(path, "src", "use_shared"))
writeLines(c(
'orderly2::orderly_shared_resource(data.csv = "data.csv")',
'orderly2::orderly_shared_resource("data.csv")',
'orderly2::orderly_artefact("analysis", "analysis.png")',
"",
'd <- read.csv("data.csv")',
Expand Down
Loading