From 5c6898fa00dd64a2b09d2c5fd43f041cc56f31a8 Mon Sep 17 00:00:00 2001 From: Ross Farrugia <82581364+rossfarrugia@users.noreply.github.com> Date: Wed, 7 Sep 2022 10:39:01 +0100 Subject: [PATCH 001/179] #63 #65 #66 #67 Clarify documentation and add dependency version requirements Close #63 Close #65 Close #66 Close #67 --- DESCRIPTION | 25 +++++++++++++------------ _pkgdown.yml | 2 +- man/admiraldev-package.Rd | 3 ++- staged_dependencies.yaml | 2 +- vignettes/admiraldev.Rmd | 10 ++-------- 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 93bc010e..862b3bf7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,12 +9,13 @@ Authors@R: c( person("Samia", "Kabi", role = "aut"), person("Pooja", "Kumari", role = "aut"), person("Syed", "Mubasheer", role = "aut"), + person("Ross", "Farrugia", role = "aut"), person("Ondrej", "Slama", role = "ctb"), person("F. Hoffmann-La Roche AG", role = c("cph", "fnd")), person("GlaxoSmithKline LLC", role = c("cph", "fnd")) ) Description: Utility functions to check data, variables and conditions for functions used in - 'admiral' and 'admiral' extension packages. Additional utility helper functions to to assist developers + 'admiral' and 'admiral' extension packages. Additional utility helper functions to assist developers with maintaining documentation, testing and general upkeep of 'admiral' and 'admiral' extension packages. License: Apache License (>= 2) URL: https://pharmaverse.github.io/admiraldev/main/, https://github.com/pharmaverse/admiraldev/ @@ -25,17 +26,17 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.2.0 Depends: R (>= 3.5) Imports: - assertthat, - dplyr, - lubridate, - magrittr, - purrr, - rlang, - stringr, - hms, - tidyr, - tidyselect, - lifecycle + assertthat (>= 0.2.1), + dplyr (>= 0.8.4), + hms (>= 0.5.3), + lifecycle (>= 0.1.0), + lubridate (>= 1.7.4), + magrittr (>= 1.5), + purrr (>= 0.3.3), + rlang (>= 0.4.4), + stringr (>= 1.4.0), + tidyr (>= 1.0.2), + tidyselect (>= 1.0.0) Suggests: admiral.test, devtools, diff --git a/_pkgdown.yml b/_pkgdown.yml index d8e9b8e9..a46d9496 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -79,7 +79,7 @@ reference: navbar: components: reference: - text: Functions + text: Reference href: reference/index.html articles: text: Developer Guides diff --git a/man/admiraldev-package.Rd b/man/admiraldev-package.Rd index f8c998cf..921f9eb2 100644 --- a/man/admiraldev-package.Rd +++ b/man/admiraldev-package.Rd @@ -8,7 +8,7 @@ \description{ \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} -Utility functions to check data, variables and conditions for functions used in 'admiral' and 'admiral' extension packages. Additional utility helper functions to to assist developers with maintaining documentation, testing and general upkeep of 'admiral' and 'admiral' extension packages. +Utility functions to check data, variables and conditions for functions used in 'admiral' and 'admiral' extension packages. Additional utility helper functions to assist developers with maintaining documentation, testing and general upkeep of 'admiral' and 'admiral' extension packages. } \seealso{ Useful links: @@ -28,6 +28,7 @@ Authors: \item Samia Kabi \item Pooja Kumari \item Syed Mubasheer + \item Ross Farrugia } Other contributors: diff --git a/staged_dependencies.yaml b/staged_dependencies.yaml index 475c40e9..1756e9f8 100644 --- a/staged_dependencies.yaml +++ b/staged_dependencies.yaml @@ -3,6 +3,6 @@ current_repo: repo: pharmaverse/admiraltemplate host: https://github.com upstream_repos: - - repo: pharmaverse/admiral + - repo: pharmaverse/admiral.test host: https://github.com downstream_repos: diff --git a/vignettes/admiraldev.Rmd b/vignettes/admiraldev.Rmd index f6a24000..a2962768 100644 --- a/vignettes/admiraldev.Rmd +++ b/vignettes/admiraldev.Rmd @@ -56,11 +56,5 @@ A developer working on `{admiralonco}` implements a new type of derivation funct Loose guidelines: -1. The derivation function should be closely looked at to see if it can be generalized to other ADaM datsets. If that is the case, then it should be moved to `{admiral}` core. If the function is very specific to onoclogy needs, then it should remain in `{admiralonco}`. -1. The `assert` custom checking functions should always live within `{admiraldev}` to stay with the family of `assertion` functions. - - - - - - +1. The derivation function should be closely looked at to see if it can be generalized to other ADaM datsets. If that is the case, then it should be moved to `{admiral}` core. If the function is very specific to oncology needs, then it should remain in `{admiralonco}`. +1. The `assert` custom checking functions follow a similar principle - if they can be generalized to other therapeutic areas then move to `{admiraldev}`, whereas if very specific to oncology needs, then they should remain in `{admiralonco}`. From 392d0cbf2623c14dfb4e6513465f3a26d948462c Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Mon, 12 Sep 2022 18:35:19 +0200 Subject: [PATCH 002/179] 73_format_testthat: improve formatting tests and update tests --- R/addin_format_testthat.R | 22 ++++++++++++++------- tests/testthat/test-addin_format_testthat.R | 4 ++-- tests/testthat/test-compat_friendly_type.R | 10 ++++++---- tests/testthat/test-dataset_vignette.R | 12 +++++------ tests/testthat/test-dev_utilities.R | 11 ++++++++--- tests/testthat/test-get.R | 3 +++ tests/testthat/test-quo.R | 7 +++++-- tests/testthat/test-quote.R | 12 ++++++++--- tests/testthat/test-test_helpers.R | 4 +++- tests/testthat/test-tmp_vars.R | 2 +- tests/testthat/test-warnings.R | 16 +++++++++++---- tests/testthat/test-what.R | 16 ++++++++++----- 12 files changed, 81 insertions(+), 38 deletions(-) diff --git a/R/addin_format_testthat.R b/R/addin_format_testthat.R index 7fd34971..c59d7303 100644 --- a/R/addin_format_testthat.R +++ b/R/addin_format_testthat.R @@ -19,7 +19,7 @@ prepare_test_that_file <- function(path) { } # parse the name of the testing function - testing_fun <- sub("^test-", "", sub(".R$", "", basename(path))) + testing_file <- sub("^test-", "", sub(".R$", "", basename(path))) # get file content file_content <- readLines(path) @@ -47,13 +47,21 @@ prepare_test_that_file <- function(path) { ) test_that_desc_cleaned <- stringr::str_remove( string = test_that_desc_parsed, - pattern = paste0(testing_fun, ", ", "test \\d{1,}: ") + pattern = paste0("(\\w+,? )?[Tt]est \\d{1,} ?: ") ) + # determine name of function which is tested + # the function name can be specified by # function_name ---- comments + function_name <- str_match(file_content, "# (\\w+) ----")[,2] + if (is.na(function_name[1])) { + function_name[1] <- testing_file + } + function_name <- tidyr::fill(data.frame(name = function_name), name)$name + function_name <- function_name[test_that_loc] + # formulate new test descriptions (update only those that don't include test_title) new_desc <- paste0( - testing_fun, ", ", - "test ", seq_along(test_that_loc), ": ", + "Test ", seq_along(test_that_loc), ": ", test_that_desc_cleaned ) @@ -61,7 +69,7 @@ prepare_test_that_file <- function(path) { test_that_lines_updated <- stringr::str_replace( string = test_that_lines, pattern = '(?<=test_that\\(").*"', - replacement = paste0(new_desc, '"') + replacement = paste0(function_name, " ", new_desc, '"') ) # modify the file content @@ -72,10 +80,10 @@ prepare_test_that_file <- function(path) { #### # formulate headers according to RStudio editor functionality - headers <- paste0("# ---- ", new_desc, " ----") + headers <- paste0("## ", new_desc, " ----") # get locations of headers created by this function - header_loc_lgl <- grepl(paste0("^# ---- ", testing_fun, ", ", "test \\d{1,}: "), file_content) + header_loc_lgl <- grepl(paste0("^##?( ----)?( \\w+)?,? [tT]est \\d{1,} ?: "), file_content) # remove those headers file_content <- file_content[!header_loc_lgl] diff --git a/tests/testthat/test-addin_format_testthat.R b/tests/testthat/test-addin_format_testthat.R index 5bb21390..071c830f 100644 --- a/tests/testthat/test-addin_format_testthat.R +++ b/tests/testthat/test-addin_format_testthat.R @@ -1,5 +1,5 @@ -# ---- addin_format_testthat, test 1: works as expected ---- -test_that("addin_format_testthat, test 1: works as expected", { +## Test 1: works as expected ---- +test_that("addin_format_testthat Test 1: works as expected", { # test: file exists expect_error( diff --git a/tests/testthat/test-compat_friendly_type.R b/tests/testthat/test-compat_friendly_type.R index 5c775ce2..3ac65e51 100644 --- a/tests/testthat/test-compat_friendly_type.R +++ b/tests/testthat/test-compat_friendly_type.R @@ -1,9 +1,11 @@ -test_that("friendly_type_of() supports objects", { +## Test 1: friendly_type_of() supports objects ---- +test_that("compat_friendly_type Test 1: friendly_type_of() supports objects", { expect_equal(friendly_type_of(mtcars), "a object") expect_equal(friendly_type_of(quo(1)), "a object") }) -test_that("friendly_type_of() supports matrices and arrays (#141)", { +## Test 2: friendly_type_of() supports matrices and arrays (#141) ---- +test_that("compat_friendly_type Test 2: friendly_type_of() supports matrices and arrays (#141)", { expect_equal(friendly_type_of(list()), "an empty list") expect_equal(friendly_type_of(matrix(list(1, 2))), "a list matrix") expect_equal(friendly_type_of(array(list(1, 2, 3), dim = 1:3)), "a list array") @@ -15,8 +17,8 @@ test_that("friendly_type_of() supports matrices and arrays (#141)", { expect_equal(friendly_type_of(array(letters[1:3], dim = 1:3)), "a character array") }) - -test_that("friendly_type_of() handles scalars", { +## Test 3: friendly_type_of() handles scalars ---- +test_that("compat_friendly_type Test 3: friendly_type_of() handles scalars", { expect_equal(friendly_type_of(NA), "`NA`") expect_equal(friendly_type_of(TRUE), "`TRUE`") diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index 26c082c1..a0426cbd 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -1,19 +1,19 @@ library(admiral.test) -# ---- dataset_vignette, test 1: a 'knitr_kable' object is output when run outside pkgdown ---- -test_that("dataset_vignette, test 1: a 'knitr_kable' object is output when run outside pkgdown", { +## Test 1: a 'knitr_kable' object is output when run outside pkgdown ---- +test_that("dataset_vignette Test 1: a 'knitr_kable' object is output when run outside pkgdown", { expect_s3_class(dataset_vignette(head(admiral_dm)), "knitr_kable") }) -# ---- dataset_vignette, test 2: a 'datatables' object is output when run inside pkgdown ---- -test_that("dataset_vignette, test 2: a 'datatables' object is output when run inside pkgdown", { +## Test 2: a 'datatables' object is output when run inside pkgdown ---- +test_that("dataset_vignette Test 2: a 'datatables' object is output when run inside pkgdown", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class(dataset_vignette(head(admiral_dm)), "datatables") }) -# ---- dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown ---- -test_that("dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown", { +## Test 3: a 'knitr_kable' object is output when run outside pkgdown ---- +test_that("dataset_vignette Test 3: a 'knitr_kable' object is output when run outside pkgdown", { Sys.setenv(IN_PKGDOWN = "false") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class( diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 3d9d5ead..a69133d7 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -1,17 +1,22 @@ -test_that("arg_name works", { +# arg_name ---- +## Test 1: arg_name works ---- +test_that("arg_name Test 1: arg_name works", { expect_equal(arg_name(sym("a")), "a") expect_equal(arg_name(call("enquo", sym("a"))), "a") expect_error(arg_name("a"), "Could not extract argument name from") }) -test_that("`convert_dtm_to_dtc` is in correct format", { +# convert_dtm_to_dtc ---- +## Test 2: `convert_dtm_to_dtc` is in correct format ---- +test_that("convert_dtm_to_dtc Test 2: `convert_dtm_to_dtc` is in correct format", { expect_equal( convert_dtm_to_dtc(as.POSIXct("2022-04-05 15:34:07 UTC")), "2022-04-05T15:34:07" ) }) -test_that("`convert_dtm_to_dtc` Error is thrown if dtm is not in correct format", { +## Test 3: `convert_dtm_to_dtc` Error is thrown if dtm is not in correct format ---- +test_that("convert_dtm_to_dtc Test 3: `convert_dtm_to_dtc` Error is thrown if dtm is not in correct format", { expect_error( convert_dtm_to_dtc("2022-04-05T15:26:14"), "lubridate::is.instant(dtm) is not TRUE", diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index ae1515ce..552d4de6 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -1,3 +1,5 @@ +# get_constant_vars ---- +## Test 1: without ignore_vars ---- test_that("get_constant_vars Test 1: without ignore_vars", { data <- tibble::tribble( ~USUBJID, ~AGE, ~AVISIT, @@ -13,6 +15,7 @@ test_that("get_constant_vars Test 1: without ignore_vars", { ) }) +## Test 2: with ignore_vars ---- test_that("get_constant_vars Test 2: with ignore_vars", { data <- tibble::tribble( ~USUBJID, ~AGE, ~WGTBL, ~HGTBL, ~AVISIT, diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index 88171777..8e05d295 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -1,4 +1,6 @@ -test_that("Test 10 : `quo_not_missing` returns TRUE if no missing argument", { +# quo_not_missing ---- +## Test 1: `quo_not_missing` returns TRUE if no missing argument ---- +test_that("quo_not_missing Test 1: `quo_not_missing` returns TRUE if no missing argument", { test_fun <- function(x) { x <- rlang::enquo(x) assertthat::assert_that(quo_not_missing(x)) @@ -6,7 +8,8 @@ test_that("Test 10 : `quo_not_missing` returns TRUE if no missing argument", { expect_true(test_fun(my_variable)) }) -test_that("Test 11 : `quo_not_missing` throws and Error if missing argument", { +## Test 2: `quo_not_missing` throws and Error if missing argument ---- +test_that("quo_not_missing Test 2: `quo_not_missing` throws and Error if missing argument", { test_fun <- function(x) { x <- rlang::enquo(x) assertthat::assert_that(quo_not_missing(x)) diff --git a/tests/testthat/test-quote.R b/tests/testthat/test-quote.R index fabc6dd5..58922aa3 100644 --- a/tests/testthat/test-quote.R +++ b/tests/testthat/test-quote.R @@ -1,14 +1,20 @@ -test_that("Test 1: enumerate works", { +# enumerate ---- +## Test 1: enumerate works ---- +test_that("enumerate Test 1: enumerate works", { expect_equal(enumerate(letters[1]), "`a`") expect_equal(enumerate(letters[1:3]), "`a`, `b` and `c`") }) -test_that("Test 2: squote works", { +# squote ---- +## Test 2: squote works ---- +test_that("squote Test 2: squote works", { expect_equal(squote(letters[1]), "'a'") expect_equal(squote(letters[1:3]), c("'a'", "'b'", "'c'")) }) -test_that("Test 3: squote works", { +# dquote ---- +## Test 3: dquote works ---- +test_that("dquote Test 3: dquote works", { expect_equal(dquote(letters[1]), "\"a\"") expect_equal(dquote(letters[1:3]), c("\"a\"", "\"b\"", "\"c\"")) x <- NULL diff --git a/tests/testthat/test-test_helpers.R b/tests/testthat/test-test_helpers.R index c8b8a193..07ff21f1 100644 --- a/tests/testthat/test-test_helpers.R +++ b/tests/testthat/test-test_helpers.R @@ -1,4 +1,6 @@ -test_that("expect_dfs_equal works", { +# expect_dfs_equal ---- +## Test 1: expect_dfs_equal works ---- +test_that("expect_dfs_equal Test 1: expect_dfs_equal works", { a <- data.frame(x = 1:3, y = 4:6) b <- data.frame(x = 1:3, y = 5:7) diff --git a/tests/testthat/test-tmp_vars.R b/tests/testthat/test-tmp_vars.R index 7acd2423..1ee57e63 100644 --- a/tests/testthat/test-tmp_vars.R +++ b/tests/testthat/test-tmp_vars.R @@ -24,7 +24,7 @@ test_that("get_new_tmp_var Test 3: the temporary variable counter is increased c }) # remove_tmp_vars ---- -## Test 4: no variables are removed when no tmp vars are present ---- +## Test 4: no variables are removed when no tmp vars are present ---- test_that("remove_tmp_vars Test 4: no variables are removed when no tmp vars are present", { expect_identical(dm, remove_tmp_vars(dm)) }) diff --git a/tests/testthat/test-warnings.R b/tests/testthat/test-warnings.R index d786325c..1dba0089 100644 --- a/tests/testthat/test-warnings.R +++ b/tests/testthat/test-warnings.R @@ -1,6 +1,8 @@ library(admiral.test) -test_that("A warning is issued when a variable to be derived already exists in the input dataset", { +# warn_if_vars_exist ---- +## Test 1: A warning is issued when a variable to be derived already exists in the input dataset ---- +test_that("warn_if_vars_exist Test 1: A warning is issued when a variable to be derived already exists in the input dataset", { data(admiral_dm) expect_warning( @@ -21,19 +23,25 @@ test_that("A warning is issued when a variable to be derived already exists in t ) }) -test_that("A warning is issued when a vector contain unknown datetime format", { +# warn_if_invalud_dtc ---- +## Test 2: A warning is issued when a vector contain unknown datetime format ---- +test_that("warn_if_invalud_dtc Test 2: A warning is issued when a vector contain unknown datetime format", { expect_warning( warn_if_invalid_dtc(dtc = "20210406T12:30:30") ) }) -test_that("A warning is issued when a vector contain an incomplete dtc", { +# warn_if_inclomplete_dtc ---- +## Test 3: A warning is issued when a vector contain an incomplete dtc ---- +test_that("warn_if_inclomplete_dtc Test 3: A warning is issued when a vector contain an incomplete dtc", { expect_warning( warn_if_incomplete_dtc("2021-04-06", n = 19) ) }) -test_that("A warning is issued when two lists are inconsistent", { +# warn_if_inconsistent_list ---- +## Test 4: A warning is issued when two lists are inconsistent ---- +test_that("warn_if_inconsistent_list Test 4: A warning is issued when two lists are inconsistent", { expect_warning( warn_if_inconsistent_list( base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ, DTHVAR = "text"), diff --git a/tests/testthat/test-what.R b/tests/testthat/test-what.R index 40255acd..496034c4 100644 --- a/tests/testthat/test-what.R +++ b/tests/testthat/test-what.R @@ -1,4 +1,6 @@ -test_that("atomic vectors of length 1", { +# what_is_it ---- +## Test 1: atomic vectors of length 1 ---- +test_that("what_is_it Test 1: atomic vectors of length 1", { expect_identical(what_is_it(NULL), "`NULL`") expect_identical(what_is_it(TRUE), "`TRUE`") expect_identical(what_is_it(NA), "`NA`") @@ -8,7 +10,8 @@ test_that("atomic vectors of length 1", { expect_identical(what_is_it(2.42), "`2.42`") }) -test_that("vectors", { +## Test 2: vectors ---- +test_that("what_is_it Test 2: vectors", { expect_identical(what_is_it(letters), "a character vector") expect_identical(what_is_it(1:10), "an integer vector") expect_identical(what_is_it(c(1.2, 3)), "a double vector") @@ -16,7 +19,8 @@ test_that("vectors", { expect_identical(what_is_it(list(1, letters, TRUE)), "a list") }) -test_that("S3 objects", { +## Test 3: S3 objects ---- +test_that("what_is_it Test 3: S3 objects", { expect_identical(what_is_it(mtcars), "a data frame") expect_identical(what_is_it(factor(letters)), "a factor") expect_identical(what_is_it(lm(hp ~ mpg, data = mtcars)), "an object of class 'lm'") @@ -24,10 +28,12 @@ test_that("S3 objects", { }) -test_that("S4 objects", { +## Test 4: S4 objects ---- +test_that("what_is_it Test 4: S4 objects", { expect_identical(what_is_it(lubridate::days(1)), "a S4 object of class 'Period'") }) -test_that("symbols", { +## Test 5: symbols ---- +test_that("what_is_it Test 5: symbols", { expect_identical(what_is_it(quote(USUBJID)), "a symbol") }) From 89b2d08f6b7fe300f375f44ba86c2f50ef9524e6 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Tue, 13 Sep 2022 16:00:31 +0200 Subject: [PATCH 003/179] 73_format_testthat: add documentation --- NAMESPACE | 1 + R/admiraldev-package.R | 6 +- docs/pkgdown.yml | 2 +- tests/testthat/test-dev_utilities.R | 8 +-- vignettes/unit_test_format_tests.png | Bin 0 -> 54985 bytes vignettes/unit_test_guidance.Rmd | 92 +++++++++++++++++++++++++++ vignettes/unit_test_toc.png | Bin 0 -> 263153 bytes 7 files changed, 101 insertions(+), 8 deletions(-) create mode 100755 vignettes/unit_test_format_tests.png create mode 100755 vignettes/unit_test_toc.png diff --git a/NAMESPACE b/NAMESPACE index b50534d2..ed64924c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -195,6 +195,7 @@ importFrom(stringr,str_c) importFrom(stringr,str_detect) importFrom(stringr,str_extract) importFrom(stringr,str_glue) +importFrom(stringr,str_match) importFrom(stringr,str_remove) importFrom(stringr,str_remove_all) importFrom(stringr,str_replace) diff --git a/R/admiraldev-package.R b/R/admiraldev-package.R index e4b2e9eb..c450e2d8 100644 --- a/R/admiraldev-package.R +++ b/R/admiraldev-package.R @@ -15,9 +15,9 @@ #' @importFrom utils capture.output str #' @importFrom purrr map map2 map_chr map_lgl reduce walk keep map_if transpose #' flatten every modify_at modify_if reduce compose -#' @importFrom stringr str_c str_detect str_extract str_remove str_remove_all -#' str_replace str_trim str_to_lower str_subset str_to_title str_to_upper -#' str_glue +#' @importFrom stringr str_c str_detect str_extract str_glue str_match +#' str_remove str_remove_all str_replace str_trim str_to_lower str_subset +#' str_to_title str_to_upper #' @importFrom assertthat assert_that is.number on_failure<- #' @importFrom lubridate as_datetime ceiling_date date days duration floor_date is.Date is.instant #' time_length %--% ymd ymd_hms weeks years hours minutes diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 62e3a507..c0809ee5 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -9,7 +9,7 @@ articles: programming_strategy: programming_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-08-17T11:24Z +last_built: 2022-09-13T12:30Z urls: reference: https://pharmaverse.github.io/admiraldev/reference article: https://pharmaverse.github.io/admiraldev/articles diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index a69133d7..818ece22 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -7,16 +7,16 @@ test_that("arg_name Test 1: arg_name works", { }) # convert_dtm_to_dtc ---- -## Test 2: `convert_dtm_to_dtc` is in correct format ---- -test_that("convert_dtm_to_dtc Test 2: `convert_dtm_to_dtc` is in correct format", { +## Test 2: works if dtm is in correct format ---- +test_that("convert_dtm_to_dtc Test 2: works if dtm is in correct format", { expect_equal( convert_dtm_to_dtc(as.POSIXct("2022-04-05 15:34:07 UTC")), "2022-04-05T15:34:07" ) }) -## Test 3: `convert_dtm_to_dtc` Error is thrown if dtm is not in correct format ---- -test_that("convert_dtm_to_dtc Test 3: `convert_dtm_to_dtc` Error is thrown if dtm is not in correct format", { +## Test 3: Error is thrown if dtm is not in correct format ---- +test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct format", { expect_error( convert_dtm_to_dtc("2022-04-05T15:26:14"), "lubridate::is.instant(dtm) is not TRUE", diff --git a/vignettes/unit_test_format_tests.png b/vignettes/unit_test_format_tests.png new file mode 100755 index 0000000000000000000000000000000000000000..8d2c4336495a627c495c1c5fab7b2b503e88656d GIT binary patch literal 54985 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%0AMQ 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 f^|0a5") the testing framework used is testthat and has the following format : ``` +## Test 1: ---- test_that(" Test 1: ", { library(tibble) @@ -159,6 +160,8 @@ usethis::use_test("all_funcs") Open the newly created file "test-all_funcs.R" and use the following format: ``` +# my_new_func ---- +## Test 1: ---- test_that("my_new_func Test 1: ", { library(tibble) @@ -188,6 +191,89 @@ Ensure you give a meaningful explanation of the test in the testthat call, as these will be compiled in the package validation report. Having the name of the function and test ID included in title will also help with traceability. +The comments ending with `----` create entries in the TOC in RStudio. + +```{r echo=FALSE, out.width='120%'} +knitr::include_graphics("./unit_test_toc.png") +``` + +For adding and updating these comments and (re)numbering the tests the "Format +test_that test file" addin can be called. + +```{r echo=FALSE, out.width='120%'} +knitr::include_graphics("./unit_test_format_tests.png") +``` + +The addin + +- updates or adds the number of the tests in the comments and in the +`test_that()` call, +- updates the comments based on the description provided in the `test_that()` +call, +- updates the function name in the `test_that()` call. The function name is +extracted from the last `# ----` comment before the +`test_that()` call. If a test file tests more than one function, such comments +should be added before the first test of each function. If a test files tests a +single function only, the comments can be omitted. In this case the addin +determines the function name from the file name by stripping of the "test-" +prefix and the ".R" suffix. + +When writing new unit tests, just provide a description in the `test_that()` +call and if necessary the function name in a `# ----` comment: +``` +# arg_name ---- +test_that("arg_name works", { + expect_equal(arg_name(sym("a")), "a") + expect_equal(arg_name(call("enquo", sym("a"))), "a") + expect_error(arg_name("a"), "Could not extract argument name from") +}) + +# convert_dtm_to_dtc ---- +test_that("works if dtm is in correct format", { + expect_equal( + convert_dtm_to_dtc(as.POSIXct("2022-04-05 15:34:07 UTC")), + "2022-04-05T15:34:07" + ) +}) + +test_that("Error is thrown if dtm is not in correct format", { + expect_error( + convert_dtm_to_dtc("2022-04-05T15:26:14"), + "lubridate::is.instant(dtm) is not TRUE", + fixed = TRUE + ) +}) +``` + +Call the addin and get: +``` +# arg_name ---- +## Test 1: arg_name works ---- +test_that("arg_name Test 1: arg_name works", { + expect_equal(arg_name(sym("a")), "a") + expect_equal(arg_name(call("enquo", sym("a"))), "a") + expect_error(arg_name("a"), "Could not extract argument name from") +}) + +# convert_dtm_to_dtc ---- +## Test 2: works if dtm is in correct format ---- +test_that("convert_dtm_to_dtc Test 2: works if dtm is in correct format", { + expect_equal( + convert_dtm_to_dtc(as.POSIXct("2022-04-05 15:34:07 UTC")), + "2022-04-05T15:34:07" + ) +}) + +## Test 3: Error is thrown if dtm is not in correct format ---- +test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct format", { + expect_error( + convert_dtm_to_dtc("2022-04-05T15:26:14"), + "lubridate::is.instant(dtm) is not TRUE", + fixed = TRUE + ) +}) +``` + Once you have tested your unit test program, you can run all unit tests from the console, as follows. @@ -195,6 +281,12 @@ the console, as follows. devtools::test() ``` +For running just the tests of the current file call + +``` +devtools::test_file() +``` + ## Automation of Unit Tests When a user actions a pull request in {admiral} GitHub repo, the unit tests are diff --git a/vignettes/unit_test_toc.png b/vignettes/unit_test_toc.png new file mode 100755 index 0000000000000000000000000000000000000000..03e62be1363a09a4db81e9ac6dd0a741ad767127 GIT binary patch literal 263153 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

$mkflLxbJK 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 Q^BCw*Rn$_bmA4N6FU)6>Gynhq literal 0 HcmV?d00001 From 97bb1ccd3956fddb492a921bbe4e929a15c2f92e Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Tue, 13 Sep 2022 16:16:59 +0200 Subject: [PATCH 004/179] 73_format_testthat: fix links and lintr --- .github/pull_request_template.md | 8 ++++---- R/addin_format_testthat.R | 2 +- tests/testthat/test-warnings.R | 16 ++++++++-------- vignettes/programming_strategy.Rmd | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c871b5c9..cf0b190a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,16 +1,16 @@ -Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiral/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. +Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. Please check off each taskbox as an acknowledgment that you completed the task or check off that it is not relevant to your Pull Request. This checklist is part of the Github Action workflows and the Pull Request will not be merged into the `devel` branch until you have checked off each task. - [ ] Place Closes # into the beginning of your Pull Request Title (Use Edit button in top-right if you need to update) - [ ] Code is formatted according to the [tidyverse style guide](https://style.tidyverse.org/). Run `styler::style_file()` to style R and Rmd files -- [ ] Updated relevant unit tests or have written new unit tests - See [Unit Test Guide](https://pharmaverse.github.io/admiral/articles/unit_test_guidance.html) -- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiral/articles/programming_strategy.html#deprecation)? +- [ ] Updated relevant unit tests or have written new unit tests - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/articles/unit_test_guidance.html) +- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/articles/programming_strategy.html#deprecation)? - [ ] Update to all relevant roxygen headers and examples. - [ ] Run `devtools::document()` so all `.Rd` files in the `man` folder and the `NAMESPACE` file in the project root are updated appropriately - [ ] Address any updates needed for vignettes and/or templates - [ ] Update `NEWS.md` if the changes pertain to a user-facing function (i.e. it has an `@export` tag) or documentation aimed at users (rather than developers) -- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiral/reference/index.html)" page. +- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiraldev/reference/index.html)" page. - [ ] Address or fix all lintr warnings and errors - `lintr::lint_package()` - [ ] Run `R CMD check` locally and address all errors and warnings - `devtools::check()` - [ ] Link the issue so that it closes after successful merging. diff --git a/R/addin_format_testthat.R b/R/addin_format_testthat.R index c59d7303..7bffc337 100644 --- a/R/addin_format_testthat.R +++ b/R/addin_format_testthat.R @@ -52,7 +52,7 @@ prepare_test_that_file <- function(path) { # determine name of function which is tested # the function name can be specified by # function_name ---- comments - function_name <- str_match(file_content, "# (\\w+) ----")[,2] + function_name <- str_match(file_content, "# (\\w+) ----")[, 2] if (is.na(function_name[1])) { function_name[1] <- testing_file } diff --git a/tests/testthat/test-warnings.R b/tests/testthat/test-warnings.R index 1dba0089..050cf6b6 100644 --- a/tests/testthat/test-warnings.R +++ b/tests/testthat/test-warnings.R @@ -1,8 +1,8 @@ library(admiral.test) # warn_if_vars_exist ---- -## Test 1: A warning is issued when a variable to be derived already exists in the input dataset ---- -test_that("warn_if_vars_exist Test 1: A warning is issued when a variable to be derived already exists in the input dataset", { +## Test 1: warning if a variable already exists in the input dataset ---- +test_that("warn_if_vars_exist Test 1: warning if a variable already exists in the input dataset", { data(admiral_dm) expect_warning( @@ -24,24 +24,24 @@ test_that("warn_if_vars_exist Test 1: A warning is issued when a variable to be }) # warn_if_invalud_dtc ---- -## Test 2: A warning is issued when a vector contain unknown datetime format ---- -test_that("warn_if_invalud_dtc Test 2: A warning is issued when a vector contain unknown datetime format", { +## Test 2: Warning if vector contains unknown datetime format ---- +test_that("warn_if_invalud_dtc Test 2: Warning if vector contains unknown datetime format", { expect_warning( warn_if_invalid_dtc(dtc = "20210406T12:30:30") ) }) # warn_if_inclomplete_dtc ---- -## Test 3: A warning is issued when a vector contain an incomplete dtc ---- -test_that("warn_if_inclomplete_dtc Test 3: A warning is issued when a vector contain an incomplete dtc", { +## Test 3: Warning if vector contains an incomplete dtc ---- +test_that("warn_if_inclomplete_dtc Test 3: Warning if vector contains an incomplete dtc", { expect_warning( warn_if_incomplete_dtc("2021-04-06", n = 19) ) }) # warn_if_inconsistent_list ---- -## Test 4: A warning is issued when two lists are inconsistent ---- -test_that("warn_if_inconsistent_list Test 4: A warning is issued when two lists are inconsistent", { +## Test 4: Warning if two lists are inconsistent ---- +test_that("warn_if_inconsistent_list Test 4: Warning if two lists are inconsistent", { expect_warning( warn_if_inconsistent_list( base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ, DTHVAR = "text"), diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 2b12307a..05089083 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -398,7 +398,7 @@ add an issue in GitHub for discussion. | `der_tte` | Function used only for creating a Time to Event (TTE) Dataset | | `der_occds` | OCCDS specific derivation of helper Functions | | `der_prm_tte` | TTE Functions for adding Parameters to TTE Dataset | -| `deprecated` | Function which will be removed from admiral after next release. See [Deprecation Guidance](https://pharmaverse.github.io/admiral/articles/programming_strategy.html#deprecation). | +| `deprecated` | Function which will be removed from admiral after next release. See [Deprecation Guidance](#deprecation). | | `utils_ds_chk` | Utilities for Dataset Checking | | `utils_fil` | Utilities for Filtering Observations | | `utils_fmt` | Utilities for Formatting Observations | From 84461310302e79f588ac05710061d29db18e95eb Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Tue, 13 Sep 2022 16:23:43 +0200 Subject: [PATCH 005/179] 73_format_testthat: fix unit test --- tests/testthat/test-addin_format_testthat.R | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-addin_format_testthat.R b/tests/testthat/test-addin_format_testthat.R index 071c830f..fa474611 100644 --- a/tests/testthat/test-addin_format_testthat.R +++ b/tests/testthat/test-addin_format_testthat.R @@ -36,12 +36,11 @@ test_that("addin_format_testthat Test 1: works as expected", { file_content = c( "# some stuff in comment", paste0( - "# ---- ", sub("test-", "", basename(tf)), ", ", - "test 1: my description ----" + "## Test 1: my description ----" ), paste0( - 'test_that("', sub("test-", "", basename(tf)), ", ", - 'test 1: my description", {' + 'test_that("', sub("test-", "", basename(tf)), " ", + 'Test 1: my description", {' ), " expect_true(TRUE)", "}" From 15aee243310f98ae0af0c1ae47e8a476763fd511 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Tue, 13 Sep 2022 16:27:38 +0200 Subject: [PATCH 006/179] 73_format_testthat: fix links --- .github/pull_request_template.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cf0b190a..87294b8f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,16 +1,16 @@ -Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. +Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/devel/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. Please check off each taskbox as an acknowledgment that you completed the task or check off that it is not relevant to your Pull Request. This checklist is part of the Github Action workflows and the Pull Request will not be merged into the `devel` branch until you have checked off each task. - [ ] Place Closes # into the beginning of your Pull Request Title (Use Edit button in top-right if you need to update) - [ ] Code is formatted according to the [tidyverse style guide](https://style.tidyverse.org/). Run `styler::style_file()` to style R and Rmd files -- [ ] Updated relevant unit tests or have written new unit tests - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/articles/unit_test_guidance.html) -- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/articles/programming_strategy.html#deprecation)? +- [ ] Updated relevant unit tests or have written new unit tests - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html) +- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#deprecation)? - [ ] Update to all relevant roxygen headers and examples. - [ ] Run `devtools::document()` so all `.Rd` files in the `man` folder and the `NAMESPACE` file in the project root are updated appropriately - [ ] Address any updates needed for vignettes and/or templates - [ ] Update `NEWS.md` if the changes pertain to a user-facing function (i.e. it has an `@export` tag) or documentation aimed at users (rather than developers) -- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiraldev/reference/index.html)" page. +- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiraldev/devel/reference/index.html)" page. - [ ] Address or fix all lintr warnings and errors - `lintr::lint_package()` - [ ] Run `R CMD check` locally and address all errors and warnings - `devtools::check()` - [ ] Link the issue so that it closes after successful merging. From d9c55ebdbed0e76a21dd1f734c978906df8e1057 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Tue, 13 Sep 2022 16:40:37 +0200 Subject: [PATCH 007/179] 73_format_testthat: fix R-CMD checks --- R/global.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/global.R b/R/global.R index 56d5fae8..7e73db53 100644 --- a/R/global.R +++ b/R/global.R @@ -4,5 +4,6 @@ globalVariables(c( "_unit", "auto", + "name", "PARAMCD" )) From ef7dcbd4c75a8f95c5f5802b8c0f3794a50c248e Mon Sep 17 00:00:00 2001 From: Dinakar <26552821+cicdguy@users.noreply.github.com> Date: Tue, 13 Sep 2022 14:58:44 -0500 Subject: [PATCH 008/179] Use devel for default landing (#84) --- .github/workflows/common.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index 584a828e..9517d697 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -78,6 +78,8 @@ jobs: # Note that if you have multiple versions of docs, # your URL links are likely to break due to path changes skip-multiversion-docs: false + # Ref to use for the multiversion docs landing page + multiversion-docs-landing-page: devel linter: name: Lint uses: pharmaverse/admiralci/.github/workflows/lintr.yml@main From f0b060748fb38670c707aeac2d7410b67aedc7dd Mon Sep 17 00:00:00 2001 From: Dinakar <26552821+cicdguy@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:13:49 -0500 Subject: [PATCH 009/179] Use BS5 to render the dropdown --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index a46d9496..3934f61d 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,6 +1,7 @@ url: https://pharmaverse.github.io/admiraldev template: + bootstrap: 5 params: bootswatch: flatly docsearch: From 8c572078f6d979ca5f3f2820ecc63fdfd963ab3c Mon Sep 17 00:00:00 2001 From: Dinakar <26552821+cicdguy@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:33:36 -0500 Subject: [PATCH 010/179] No docsearch, rely on standard BS5 search --- _pkgdown.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 3934f61d..d7fc2e29 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -4,9 +4,6 @@ template: bootstrap: 5 params: bootswatch: flatly - docsearch: - api_key: 2d4895759f79e89d6eb1c3b990bca6d8 - index_name: pharmaverse repo: url: home: https://github.com/pharmaverse/admiraldev From d75e7583874f67f7292a584546fed18943ee5ce4 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Thu, 15 Sep 2022 14:04:14 +0200 Subject: [PATCH 011/179] 73_format_testthat: allow dot in function name --- R/addin_format_testthat.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/addin_format_testthat.R b/R/addin_format_testthat.R index 7bffc337..6de0674c 100644 --- a/R/addin_format_testthat.R +++ b/R/addin_format_testthat.R @@ -47,12 +47,12 @@ prepare_test_that_file <- function(path) { ) test_that_desc_cleaned <- stringr::str_remove( string = test_that_desc_parsed, - pattern = paste0("(\\w+,? )?[Tt]est \\d{1,} ?: ") + pattern = paste0("([\\w\\.]+,? )?[Tt]est \\d{1,} ?: ") ) # determine name of function which is tested # the function name can be specified by # function_name ---- comments - function_name <- str_match(file_content, "# (\\w+) ----")[, 2] + function_name <- str_match(file_content, "# ([\\w\\.]+) ----")[, 2] if (is.na(function_name[1])) { function_name[1] <- testing_file } From a0448cb0e713e9433446a9d529855ed7c8dc713d Mon Sep 17 00:00:00 2001 From: PoojaKumari05 Date: Tue, 20 Sep 2022 15:21:09 +0000 Subject: [PATCH 012/179] #62 Upadted filter_if to current version in admiral and added test case --- R/dev_utilities.R | 3 +-- tests/testthat/test-dev_utilities.R | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/R/dev_utilities.R b/R/dev_utilities.R index e34fc0bc..555db8ec 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -248,9 +248,8 @@ negate_vars <- function(vars = NULL) { #' @family dev_utility #' filter_if <- function(dataset, filter) { - assert_data_frame(dataset) + assert_data_frame(dataset, check_is_grouped = FALSE) assert_filter_cond(filter, optional = TRUE) - if (quo_is_null(filter)) { dataset } else { diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 3d9d5ead..b78d1df8 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -18,3 +18,31 @@ test_that("`convert_dtm_to_dtc` Error is thrown if dtm is not in correct format" fixed = TRUE ) }) + +test_that("input is returned as is if filter is NULL", { + input <- tibble::tribble( + ~USUBJID, ~VSTESTCD, ~VSSTRESN, + "P01", "WEIGHT", 80.9, + "P01", "HEIGHT", 189.2 + ) + + expect_dfs_equal( + input, + filter_if(input, quo(NULL)), + keys = c("USUBJID", "VSTESTCD") + ) +}) + +test_that("input is filtered if filter is not NULL", { + input <- tibble::tribble( + ~USUBJID, ~VSTESTCD, ~VSSTRESN, + "P01", "WEIGHT", 80.9, + "P01", "HEIGHT", 189.2 + ) + + expect_dfs_equal( + input[1L, ], + filter_if(input, quo(VSTESTCD == "WEIGHT")), + keys = c("USUBJID", "VSTESTCD") + ) +}) From 5e4efc82ffa30e58afcfee49412dcabeebc0794b Mon Sep 17 00:00:00 2001 From: PoojaKumari05 Date: Thu, 22 Sep 2022 17:35:50 +0000 Subject: [PATCH 013/179] #62 Updated test cases of fiter_if() as per the testing framwork --- tests/testthat/test-dev_utilities.R | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index b78d1df8..128f9c98 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -19,29 +19,37 @@ test_that("`convert_dtm_to_dtc` Error is thrown if dtm is not in correct format" ) }) -test_that("input is returned as is if filter is NULL", { - input <- tibble::tribble( +test_that("filter_if Test 1 : Input is returned as is if filter is NULL", { + library(tibble) + + input <- tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, "P01", "HEIGHT", 189.2 ) + expected_output = input + expect_dfs_equal( - input, + expected_output, filter_if(input, quo(NULL)), keys = c("USUBJID", "VSTESTCD") ) }) -test_that("input is filtered if filter is not NULL", { - input <- tibble::tribble( +test_that("filter_if Test 2 : Input is filtered if filter is not NULL", { + library(tibble) + + input <- tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, "P01", "HEIGHT", 189.2 ) + expected_output = input[1L, ] + expect_dfs_equal( - input[1L, ], + expected_output, filter_if(input, quo(VSTESTCD == "WEIGHT")), keys = c("USUBJID", "VSTESTCD") ) From 49cdda0e8043cc4db86c795632b95b0ae776e97c Mon Sep 17 00:00:00 2001 From: bms63 Date: Thu, 22 Sep 2022 18:16:49 +0000 Subject: [PATCH 014/179] #73 Upversion desc and new. Added in verbiage for using addin. Updated guidance to use tibble::tribble() --- DESCRIPTION | 2 +- NEWS.md | 22 ++++++++++++------ R/addin_format_testthat.R | 2 +- vignettes/unit_test_guidance.Rmd | 39 ++++++++++++++++---------------- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 862b3bf7..bde64344 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: admiraldev Type: Package Title: Development Tools for the Admiral Package Family -Version: 0.1.0 +Version: 0.2.0 Authors@R: c( person("Ben", "Straub", email = "ben.x.straub@gsk.com", role = c("aut", "cre")), person("Stefan", "Bundfuss", role = "aut"), diff --git a/NEWS.md b/NEWS.md index 6a65d04c..2308aff9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,18 @@ +# admiraldev 0.2.0 + +## New Features + + - Developer addin for formatting tests to admiral programming standards (#73) + +## Updates of Existing Functions + - NA +## Breaking Changes + - NA +## Documentation + - NA +## Various + - NA + # admiraldev 0.1.0 ## New Features @@ -7,18 +22,11 @@ - New `{admiraldev}` website created ## Updates of Existing Functions - - NA - ## Breaking Changes - - NA - ## Documentation - - NA - ## Various - - NA diff --git a/R/addin_format_testthat.R b/R/addin_format_testthat.R index 6de0674c..d0aafe9d 100644 --- a/R/addin_format_testthat.R +++ b/R/addin_format_testthat.R @@ -97,7 +97,7 @@ prepare_test_that_file <- function(path) { list(file_content = file_content_new) } -# Function for the RStudio Addin, see inst/rstudio/addins.dcf. +# Function for the RStudio Addin, see inst/rstudio/format_tests.dcf. # Wrapper of prepare_test_that_file. format_test_that_file <- function() { file_info <- rstudioapi::getActiveDocumentContext() diff --git a/vignettes/unit_test_guidance.Rmd b/vignettes/unit_test_guidance.Rmd index 483c48f7..0de5a484 100644 --- a/vignettes/unit_test_guidance.Rmd +++ b/vignettes/unit_test_guidance.Rmd @@ -123,8 +123,8 @@ Don’t forget to add a unit test for each exported function. ## Set up the Test Script -Within {admiral} folder https://github.com/pharmaverse/admiral/tree/main/tests/testthat, -add a script with the naming convention “test-\.R”., +Within the {admiral} folder https://github.com/pharmaverse/admiral/tree/main/tests/testthat, +add a script with the naming convention `test-\.R`., the unit test script can be created from the console also, as follows: ``` @@ -135,9 +135,8 @@ the testing framework used is testthat and has the following format : ``` ## Test 1: ---- test_that(" Test 1: ", { - library(tibble) - input <- tribble( + input <- tibble::tribble( ~inputvar1, ~inputvar2, ... ... @@ -150,22 +149,21 @@ test_that(" Test 1: ", { }) ``` -For example, if you are testing a function called my_new_func that is contained -in script all_funcs.R then from console use: +For example, if you are testing a function called `my_new_func` that is contained +in script `all_funcs.R` then from console use: ``` usethis::use_test("all_funcs") ``` -Open the newly created file "test-all_funcs.R" and use the following format: +Open the newly created file `test-all_funcs.R` and use the following format: ``` # my_new_func ---- ## Test 1: ---- test_that("my_new_func Test 1: ", { - library(tibble) - input <- tribble( + input <- tibble::tribble( ~inputvar1, ~inputvar2, ... ... @@ -176,7 +174,7 @@ test_that("my_new_func Test 1: ", { expect_dfs_equal((input), expected_output) }) ``` -**Note**: When comparing datasets in {admiral} we use function `expect_dfs_equal()`. +**Note**: When comparing datasets in `{admiral}` we use function `expect_dfs_equal()`. The input and expected output for the unit tests must follow the following rules: @@ -184,7 +182,7 @@ The input and expected output for the unit tests must follow the following rules * Values should be hard-coded whenever possible. * If values need to be derived, only unit tested functions can be used. -If a dataset needs to be created for testing purpose, it should be done so using the function `tribble()` (specify `library(tibble)` before calling this function). +If a dataset needs to be created for testing purpose, it should be done so using the function `tribble()` from the `tibble` package with the following command `tibble::tribble()`. Make sure to align columns as well. This ensures quick code readability. Ensure you give a meaningful explanation of the test in the testthat call, as @@ -197,20 +195,23 @@ The comments ending with `----` create entries in the TOC in RStudio. knitr::include_graphics("./unit_test_toc.png") ``` -For adding and updating these comments and (re)numbering the tests the "Format -test_that test file" addin can be called. + +## Addin admiraldev::format_test_that_file() + +To ease the burden on developers for writing and adding tests we have developed an Addin for formatting test_that test files according to admiral programming standards. The Addin will add and update comments as well as number or re-numbers the tests. Just use the Addin button and select the "Format +test_that test file" as seen in the image. Be sure to have the test-file open and selected when calling the Addin. ```{r echo=FALSE, out.width='120%'} knitr::include_graphics("./unit_test_format_tests.png") ``` -The addin +The Addin will perform the following: -- updates or adds the number of the tests in the comments and in the -`test_that()` call, -- updates the comments based on the description provided in the `test_that()` -call, -- updates the function name in the `test_that()` call. The function name is +- Updates or adds the number of the tests in the comments and in the +`test_that()` call +- Updates the comments based on the description provided in the `test_that()` +call +- Updates the function name in the `test_that()` call. The function name is extracted from the last `# ----` comment before the `test_that()` call. If a test file tests more than one function, such comments should be added before the first test of each function. If a test files tests a From a5e29eef75abd14a4725f2bf38f9e27b6c3e0ee7 Mon Sep 17 00:00:00 2001 From: bms63 Date: Thu, 22 Sep 2022 18:18:32 +0000 Subject: [PATCH 015/179] #73 Fixing comment --- R/addin_format_testthat.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/addin_format_testthat.R b/R/addin_format_testthat.R index d0aafe9d..6de0674c 100644 --- a/R/addin_format_testthat.R +++ b/R/addin_format_testthat.R @@ -97,7 +97,7 @@ prepare_test_that_file <- function(path) { list(file_content = file_content_new) } -# Function for the RStudio Addin, see inst/rstudio/format_tests.dcf. +# Function for the RStudio Addin, see inst/rstudio/addins.dcf. # Wrapper of prepare_test_that_file. format_test_that_file <- function() { file_info <- rstudioapi::getActiveDocumentContext() From 807a230455539396927c2a5751d5bd79b47a09d7 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Fri, 23 Sep 2022 12:31:07 +0200 Subject: [PATCH 016/179] 73_format_testthat: fix spelling and links --- _pkgdown.yml | 2 +- docs/pkgdown.yml | 6 +++--- inst/WORDLIST | 1 + vignettes/unit_test_guidance.Rmd | 8 ++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index d7fc2e29..507da630 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,4 +1,4 @@ -url: https://pharmaverse.github.io/admiraldev +url: https://pharmaverse.github.io/admiraldev/devel template: bootstrap: 5 diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index c0809ee5..1a3360ae 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -9,8 +9,8 @@ articles: programming_strategy: programming_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-09-13T12:30Z +last_built: 2022-09-23T10:26Z urls: - reference: https://pharmaverse.github.io/admiraldev/reference - article: https://pharmaverse.github.io/admiraldev/articles + reference: https://pharmaverse.github.io/admiraldev/devel/reference + article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/inst/WORDLIST b/inst/WORDLIST index 109227fb..3ac0472e 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -17,6 +17,7 @@ Template’ USUBJIDs adam adamig +addin admiraltemplate admiralxxx admiraldev diff --git a/vignettes/unit_test_guidance.Rmd b/vignettes/unit_test_guidance.Rmd index 0de5a484..ecbaf945 100644 --- a/vignettes/unit_test_guidance.Rmd +++ b/vignettes/unit_test_guidance.Rmd @@ -123,9 +123,9 @@ Don’t forget to add a unit test for each exported function. ## Set up the Test Script -Within the {admiral} folder https://github.com/pharmaverse/admiral/tree/main/tests/testthat, -add a script with the naming convention `test-\.R`., -the unit test script can be created from the console also, as follows: +Within the `tests/testthat` folder of the project, add a script with the naming +convention `test-.R`., the unit test script can be +created from the console also, as follows: ``` usethis::use_test("") @@ -196,7 +196,7 @@ knitr::include_graphics("./unit_test_toc.png") ``` -## Addin admiraldev::format_test_that_file() +## Addin `admiraldev::format_test_that_file()` To ease the burden on developers for writing and adding tests we have developed an Addin for formatting test_that test files according to admiral programming standards. The Addin will add and update comments as well as number or re-numbers the tests. Just use the Addin button and select the "Format test_that test file" as seen in the image. Be sure to have the test-file open and selected when calling the Addin. From bc3c9be6a32d7ce5354dcf05dcfc24eb61e63da6 Mon Sep 17 00:00:00 2001 From: Sophie Shapcott Date: Mon, 26 Sep 2022 08:54:17 +0100 Subject: [PATCH 017/179] #89-updated programming strategy vignette as indicated. --- vignettes/programming_strategy.Rmd | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 05089083..5a787c26 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -488,7 +488,7 @@ The documentation will be updated at: + the description level for a function, + the keywords will be replaced with `deprecated` -+ the @family roxygen tag will be removed ++ the @family roxygen tag will become `deprecated` ```{r, eval=FALSE} #' Title of the function @@ -498,6 +498,8 @@ The documentation will be updated at: #' #' This function is *deprecated*, please use `new_fun()` instead. #' . +#' @family deprecated +#' #' @keywords deprecated #' . ``` From 9592ed09b215a021f58b578549d745cf664b14bc Mon Sep 17 00:00:00 2001 From: Sophie Shapcott Date: Mon, 26 Sep 2022 09:01:13 +0100 Subject: [PATCH 018/179] 89-run `styler::style_file()` to ensure vignette follows the tidyverse style guide --- vignettes/programming_strategy.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 5a787c26..c06ef774 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -499,7 +499,7 @@ The documentation will be updated at: #' This function is *deprecated*, please use `new_fun()` instead. #' . #' @family deprecated -#' +#' #' @keywords deprecated #' . ``` From de9ffd6f884f10fa9b77c0dd706111c21900fe87 Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Tue, 27 Sep 2022 13:14:06 +0000 Subject: [PATCH 019/179] 106_add_suffix_to_vars: implement add_suffix_to_vars() --- NAMESPACE | 2 + NEWS.md | 2 + R/dev_utilities.R | 94 +++++++++++++++++++++++++++++ docs/pkgdown.yml | 4 +- man/add_suffix_to_vars.Rd | 43 +++++++++++++ man/quo_c.Rd | 4 +- man/quo_not_missing.Rd | 4 +- man/replace_symbol_in_quo.Rd | 38 ++++++++++++ tests/testthat/test-dev_utilities.R | 48 +++++++++++++++ 9 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 man/add_suffix_to_vars.Rd create mode 100644 man/replace_symbol_in_quo.Rd diff --git a/NAMESPACE b/NAMESPACE index ed64924c..7678668e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,7 @@ export("%notin%") export("%or%") +export(add_suffix_to_vars) export(anti_join) export(arg_name) export(as_name) @@ -60,6 +61,7 @@ export(negate_vars) export(quo_c) export(quo_not_missing) export(remove_tmp_vars) +export(replace_symbol_in_quo) export(replace_values_by_names) export(set_dataset) export(squote) diff --git a/NEWS.md b/NEWS.md index 2308aff9..eeee3f72 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,8 @@ ## New Features - Developer addin for formatting tests to admiral programming standards (#73) + + - New functions `replace_symbol_in_quo()` and `add_suffix_to_vars()` (#106) ## Updates of Existing Functions - NA diff --git a/R/dev_utilities.R b/R/dev_utilities.R index e34fc0bc..829c084f 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -142,6 +142,100 @@ replace_values_by_names <- function(quosures) { } +#' Replace Symbols in a Quosure +#' +#' Replace symbols in a quosure +#' +#' @param quosure Quosure +#' +#' @param target Target symbol +#' +#' @param replace Replacing symbol +#' +#' @author Stefan Bundfuss +#' +#' @return The quosure where every occurence of the symbol `target` is replaced +#' by `replace` +#' +#' @keywords quo +#' @family quo +#' +#' @export +#' +#' @examples +#' replace_symbol_in_quo(quo(AVAL), target = AVAL, replace = AVAL.join) +#' replace_symbol_in_quo(quo(AVALC), target = AVAL, replace = AVAL.join) +#' replace_symbol_in_quo(quo(desc(AVAL)), target = AVAL, replace = AVAL.join) +replace_symbol_in_quo <- function(quosure, + target, + replace) { + assert_expr(quosure) + target <- quo_get_expr(assert_symbol(enquo(target))) + replace <- quo_get_expr(assert_symbol(enquo(replace))) + expr <- quo_get_expr(quosure) + if (is.symbol(expr)) { + if (expr == target) { + expr = replace + } + } else { + for (i in seq_along(quosure)) { + if (expr[[i]] == target) { + expr[[i]] <- replace + } + } + } + rlang::quo_set_expr(quosure, expr) +} + +#' Add a Suffix to Variables in a List of Quosures +#' +#' Add a suffix to variables in a list of quosures +#' +#' @param order List of quosures +#' +#' *Permitted Values*: list of variables or `desc()` function calls +#' created by `vars()`, e.g., `vars(ADT, desc(AVAL))` +#' +#' @param vars Variables to change +#' +#' *Permitted Values*: list of variables created by `vars()` +#' +#' @param suffix Suffix +#' +#' *Permitted Values*: A character scalar +#' +#' @author Stefan Bundfuss +#' +#' @return The list of quosures where for each element the suffix (`suffix`) is +#' added to every symbol specified for `vars` +#' +#' @keywords quo +#' @family quo +#' +#' @export +#' +#' @examples +#' add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") +add_suffix_to_vars <- function(order, + vars, + suffix +) { + assert_order_vars(order) + assert_vars(vars) + assert_character_scalar(suffix) + for (i in seq_along(vars)) { + order <- lapply( + order, + replace_symbol_in_quo, + target = !!quo_get_expr(vars[[i]]), + replace = !!sym(paste0(as_label( + quo_get_expr(vars[[i]]) + ), suffix))) + } + class(order) <- c("quosures", "list") + order +} + #' Turn a Quosure into a String #' #' @details diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 1a3360ae..8ad0cfa8 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,4 +1,4 @@ -pandoc: 2.11.4 +pandoc: 2.17.1.1 pkgdown: 2.0.3 pkgdown_sha: ~ articles: @@ -9,7 +9,7 @@ articles: programming_strategy: programming_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-09-23T10:26Z +last_built: 2022-09-27T11:33Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/man/add_suffix_to_vars.Rd b/man/add_suffix_to_vars.Rd new file mode 100644 index 00000000..14d3eff6 --- /dev/null +++ b/man/add_suffix_to_vars.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dev_utilities.R +\name{add_suffix_to_vars} +\alias{add_suffix_to_vars} +\title{Add a Suffix to Variables in a List of Quosures} +\usage{ +add_suffix_to_vars(order, vars, suffix) +} +\arguments{ +\item{order}{List of quosures + +\emph{Permitted Values}: list of variables or \verb{desc()} function calls +created by \code{vars()}, e.g., \code{vars(ADT, desc(AVAL))}} + +\item{vars}{Variables to change + +\emph{Permitted Values}: list of variables created by \code{vars()}} + +\item{suffix}{Suffix + +\emph{Permitted Values}: A character scalar} +} +\value{ +The list of quosures where for each element the suffix (\code{suffix}) is +added to every symbol specified for \code{vars} +} +\description{ +Add a suffix to variables in a list of quosures +} +\examples{ +add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") +} +\seealso{ +Helpers for working with Quosures: +\code{\link{quo_c}()}, +\code{\link{quo_not_missing}()}, +\code{\link{replace_symbol_in_quo}()} +} +\author{ +Stefan Bundfuss +} +\concept{quo} +\keyword{quo} diff --git a/man/quo_c.Rd b/man/quo_c.Rd index 5e85af04..c094decf 100644 --- a/man/quo_c.Rd +++ b/man/quo_c.Rd @@ -17,7 +17,9 @@ Concatenate One or More Quosure(s) } \seealso{ Helpers for working with Quosures: -\code{\link{quo_not_missing}()} +\code{\link{add_suffix_to_vars}()}, +\code{\link{quo_not_missing}()}, +\code{\link{replace_symbol_in_quo}()} } \author{ Thomas Neitmann diff --git a/man/quo_not_missing.Rd b/man/quo_not_missing.Rd index 109146da..0032a144 100644 --- a/man/quo_not_missing.Rd +++ b/man/quo_not_missing.Rd @@ -17,7 +17,9 @@ Check Whether an Argument Is Not a Quosure of a Missing Argument } \seealso{ Helpers for working with Quosures: -\code{\link{quo_c}()} +\code{\link{add_suffix_to_vars}()}, +\code{\link{quo_c}()}, +\code{\link{replace_symbol_in_quo}()} } \author{ Thomas Neitmann, Ondrej Slama diff --git a/man/replace_symbol_in_quo.Rd b/man/replace_symbol_in_quo.Rd new file mode 100644 index 00000000..d286cdab --- /dev/null +++ b/man/replace_symbol_in_quo.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dev_utilities.R +\name{replace_symbol_in_quo} +\alias{replace_symbol_in_quo} +\title{Replace Symbols in a Quosure} +\usage{ +replace_symbol_in_quo(quosure, target, replace) +} +\arguments{ +\item{quosure}{Quosure} + +\item{target}{Target symbol} + +\item{replace}{Replacing symbol} +} +\value{ +The quosure where every occurence of the symbol \code{target} is replaced +by \code{replace} +} +\description{ +Replace symbols in a quosure +} +\examples{ +replace_symbol_in_quo(quo(AVAL), target = AVAL, replace = AVAL.join) +replace_symbol_in_quo(quo(AVALC), target = AVAL, replace = AVAL.join) +replace_symbol_in_quo(quo(desc(AVAL)), target = AVAL, replace = AVAL.join) +} +\seealso{ +Helpers for working with Quosures: +\code{\link{add_suffix_to_vars}()}, +\code{\link{quo_c}()}, +\code{\link{quo_not_missing}()} +} +\author{ +Stefan Bundfuss +} +\concept{quo} +\keyword{quo} diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 818ece22..a8ab557a 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -23,3 +23,51 @@ test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct f fixed = TRUE ) }) + +# replace_symbol_in_quo ---- +## Test 4: symbol is replaced ---- +test_that("replace_symbol_in_quo Test 4: symbol is replaced", { + expect_equal( + expected = quo(AVAL.join), + object = replace_symbol_in_quo( + quo(AVAL), + target = AVAL, + replace = AVAL.join)) +}) + +## Test 5: partial match is not replaced ---- +test_that("replace_symbol_in_quo Test 5: partial match is not replaced", { + expect_equal( + expected = quo(AVALC), + object = replace_symbol_in_quo( + quo(AVALC), + target = AVAL, + replace = AVAL.join)) +}) + +## Test 6: symbol in expression is replaced ---- +test_that("replace_symbol_in_quo Test 6: symbol in expression is replaced", { + expect_equal( + expected = quo(desc(AVAL.join)), + object = replace_symbol_in_quo( + quo(desc(AVAL)), + target = AVAL, + replace = AVAL.join)) +}) + +# add_suffix_to_vars ---- +## Test 7: with single variable ---- +test_that("add_suffix_to_vars Test 7: with single variable", { + expect_equal( + expected = vars(ADT, desc(AVAL.join), AVALC), + object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") + ) +}) + +## Test 8: with more than one variable ---- +test_that("add_suffix_to_vars Test 8: with more than one variable", { + expect_equal( + expected = vars(ADT, desc(AVAL.join), AVALC.join), + object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL, AVALC), suffix = ".join") + ) +}) From bf8b63df8e0b2f65da4ae71111af314337fe8446 Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Tue, 27 Sep 2022 13:21:26 +0000 Subject: [PATCH 020/179] 106_add_suffix_to_vars: fix styler check --- R/dev_utilities.R | 8 ++++---- tests/testthat/test-dev_utilities.R | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/R/dev_utilities.R b/R/dev_utilities.R index 829c084f..ff25458b 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -175,7 +175,7 @@ replace_symbol_in_quo <- function(quosure, expr <- quo_get_expr(quosure) if (is.symbol(expr)) { if (expr == target) { - expr = replace + expr <- replace } } else { for (i in seq_along(quosure)) { @@ -218,8 +218,7 @@ replace_symbol_in_quo <- function(quosure, #' add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") add_suffix_to_vars <- function(order, vars, - suffix -) { + suffix) { assert_order_vars(order) assert_vars(vars) assert_character_scalar(suffix) @@ -230,7 +229,8 @@ add_suffix_to_vars <- function(order, target = !!quo_get_expr(vars[[i]]), replace = !!sym(paste0(as_label( quo_get_expr(vars[[i]]) - ), suffix))) + ), suffix)) + ) } class(order) <- c("quosures", "list") order diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index a8ab557a..9cb0a03e 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -32,7 +32,9 @@ test_that("replace_symbol_in_quo Test 4: symbol is replaced", { object = replace_symbol_in_quo( quo(AVAL), target = AVAL, - replace = AVAL.join)) + replace = AVAL.join + ) + ) }) ## Test 5: partial match is not replaced ---- @@ -42,7 +44,9 @@ test_that("replace_symbol_in_quo Test 5: partial match is not replaced", { object = replace_symbol_in_quo( quo(AVALC), target = AVAL, - replace = AVAL.join)) + replace = AVAL.join + ) + ) }) ## Test 6: symbol in expression is replaced ---- @@ -52,7 +56,9 @@ test_that("replace_symbol_in_quo Test 6: symbol in expression is replaced", { object = replace_symbol_in_quo( quo(desc(AVAL)), target = AVAL, - replace = AVAL.join)) + replace = AVAL.join + ) + ) }) # add_suffix_to_vars ---- From 416270b478627f16144831561d0afd9b02d121a1 Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Tue, 27 Sep 2022 13:23:44 +0000 Subject: [PATCH 021/179] 106_add_suffix_to_vars: fix lintr check --- tests/testthat/test-dev_utilities.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 9cb0a03e..7fcacf05 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -74,6 +74,10 @@ test_that("add_suffix_to_vars Test 7: with single variable", { test_that("add_suffix_to_vars Test 8: with more than one variable", { expect_equal( expected = vars(ADT, desc(AVAL.join), AVALC.join), - object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL, AVALC), suffix = ".join") + object = add_suffix_to_vars( + vars(ADT, desc(AVAL), AVALC), + vars = vars(AVAL, AVALC), + suffix = ".join" + ) ) }) From 04d9f859967b65c1f953aef935068f47b1705473 Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Tue, 27 Sep 2022 13:38:56 +0000 Subject: [PATCH 022/179] 106_add_suffix_to_vars: fix R-CMD checks --- R/dev_utilities.R | 3 +++ man/replace_symbol_in_quo.Rd | 3 +++ 2 files changed, 6 insertions(+) diff --git a/R/dev_utilities.R b/R/dev_utilities.R index ff25458b..aeecf455 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -163,6 +163,9 @@ replace_values_by_names <- function(quosures) { #' @export #' #' @examples +#' +#' library(rlang) +#' #' replace_symbol_in_quo(quo(AVAL), target = AVAL, replace = AVAL.join) #' replace_symbol_in_quo(quo(AVALC), target = AVAL, replace = AVAL.join) #' replace_symbol_in_quo(quo(desc(AVAL)), target = AVAL, replace = AVAL.join) diff --git a/man/replace_symbol_in_quo.Rd b/man/replace_symbol_in_quo.Rd index d286cdab..4d909a40 100644 --- a/man/replace_symbol_in_quo.Rd +++ b/man/replace_symbol_in_quo.Rd @@ -21,6 +21,9 @@ by \code{replace} Replace symbols in a quosure } \examples{ + +library(rlang) + replace_symbol_in_quo(quo(AVAL), target = AVAL, replace = AVAL.join) replace_symbol_in_quo(quo(AVALC), target = AVAL, replace = AVAL.join) replace_symbol_in_quo(quo(desc(AVAL)), target = AVAL, replace = AVAL.join) From e3ad1e3564d756c346fb337f5519505698f81e3d Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Wed, 28 Sep 2022 19:10:08 +0200 Subject: [PATCH 023/179] 106_add_suffix_to_vars: rearrange functions --- R/dev_utilities.R | 122 ---------------------------- R/quo.R | 122 ++++++++++++++++++++++++++++ man/add_suffix_to_vars.Rd | 5 +- man/arg_name.Rd | 1 - man/as_name.Rd | 1 - man/convert_dtm_to_dtc.Rd | 1 - man/extract_vars.Rd | 1 - man/filter_if.Rd | 1 - man/grapes-notin-grapes.Rd | 1 - man/grapes-or-grapes.Rd | 1 - man/negate_vars.Rd | 1 - man/quo_c.Rd | 3 +- man/quo_not_missing.Rd | 3 +- man/replace_symbol_in_quo.Rd | 5 +- man/replace_values_by_names.Rd | 22 ++--- man/valid_time_units.Rd | 1 - man/vars2chr.Rd | 1 - tests/testthat/test-dev_utilities.R | 58 ------------- tests/testthat/test-quo.R | 58 +++++++++++++ 19 files changed, 198 insertions(+), 210 deletions(-) diff --git a/R/dev_utilities.R b/R/dev_utilities.R index aeecf455..05704f75 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -116,128 +116,6 @@ extract_vars <- function(x, side = "lhs") { tryCatch(lhs, error = function(e) rhs) } -#' Replace Quosure Value with Name -#' -#' @param quosures A list of quosures -#' -#' @author Thomas Neitmann -#' -#' @keywords dev_utility -#' @family dev_utility -#' -#' -#' @return A list of quosures -#' @export -replace_values_by_names <- function(quosures) { - vars <- map2(quosures, names(quosures), function(q, n) { - if (n == "") { - return(q) - } - quo_set_env( - quo(!!as.symbol(n)), - quo_get_env(q) - ) - }) - structure(vars, class = "quosures", names = NULL) -} - - -#' Replace Symbols in a Quosure -#' -#' Replace symbols in a quosure -#' -#' @param quosure Quosure -#' -#' @param target Target symbol -#' -#' @param replace Replacing symbol -#' -#' @author Stefan Bundfuss -#' -#' @return The quosure where every occurence of the symbol `target` is replaced -#' by `replace` -#' -#' @keywords quo -#' @family quo -#' -#' @export -#' -#' @examples -#' -#' library(rlang) -#' -#' replace_symbol_in_quo(quo(AVAL), target = AVAL, replace = AVAL.join) -#' replace_symbol_in_quo(quo(AVALC), target = AVAL, replace = AVAL.join) -#' replace_symbol_in_quo(quo(desc(AVAL)), target = AVAL, replace = AVAL.join) -replace_symbol_in_quo <- function(quosure, - target, - replace) { - assert_expr(quosure) - target <- quo_get_expr(assert_symbol(enquo(target))) - replace <- quo_get_expr(assert_symbol(enquo(replace))) - expr <- quo_get_expr(quosure) - if (is.symbol(expr)) { - if (expr == target) { - expr <- replace - } - } else { - for (i in seq_along(quosure)) { - if (expr[[i]] == target) { - expr[[i]] <- replace - } - } - } - rlang::quo_set_expr(quosure, expr) -} - -#' Add a Suffix to Variables in a List of Quosures -#' -#' Add a suffix to variables in a list of quosures -#' -#' @param order List of quosures -#' -#' *Permitted Values*: list of variables or `desc()` function calls -#' created by `vars()`, e.g., `vars(ADT, desc(AVAL))` -#' -#' @param vars Variables to change -#' -#' *Permitted Values*: list of variables created by `vars()` -#' -#' @param suffix Suffix -#' -#' *Permitted Values*: A character scalar -#' -#' @author Stefan Bundfuss -#' -#' @return The list of quosures where for each element the suffix (`suffix`) is -#' added to every symbol specified for `vars` -#' -#' @keywords quo -#' @family quo -#' -#' @export -#' -#' @examples -#' add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") -add_suffix_to_vars <- function(order, - vars, - suffix) { - assert_order_vars(order) - assert_vars(vars) - assert_character_scalar(suffix) - for (i in seq_along(vars)) { - order <- lapply( - order, - replace_symbol_in_quo, - target = !!quo_get_expr(vars[[i]]), - replace = !!sym(paste0(as_label( - quo_get_expr(vars[[i]]) - ), suffix)) - ) - } - class(order) <- c("quosures", "list") - order -} #' Turn a Quosure into a String #' diff --git a/R/quo.R b/R/quo.R index 0c12510c..7bc14796 100644 --- a/R/quo.R +++ b/R/quo.R @@ -35,3 +35,125 @@ quo_not_missing <- function(x) { on_failure(quo_not_missing) <- function(call, env) { paste0("Argument `", deparse(call$x), "` is missing, with no default") } + +#' Replace Quosure Value with Name +#' +#' @param quosures A list of quosures +#' +#' @author Thomas Neitmann +#' +#' @keywords quo +#' @family quo +#' +#' +#' @return A list of quosures +#' @export +replace_values_by_names <- function(quosures) { + vars <- map2(quosures, names(quosures), function(q, n) { + if (n == "") { + return(q) + } + quo_set_env( + quo(!!as.symbol(n)), + quo_get_env(q) + ) + }) + structure(vars, class = "quosures", names = NULL) +} + +#' Replace Symbols in a Quosure +#' +#' Replace symbols in a quosure +#' +#' @param quosure Quosure +#' +#' @param target Target symbol +#' +#' @param replace Replacing symbol +#' +#' @author Stefan Bundfuss +#' +#' @return The quosure where every occurence of the symbol `target` is replaced +#' by `replace` +#' +#' @keywords quo +#' @family quo +#' +#' @export +#' +#' @examples +#' +#' library(rlang) +#' +#' replace_symbol_in_quo(quo(AVAL), target = AVAL, replace = AVAL.join) +#' replace_symbol_in_quo(quo(AVALC), target = AVAL, replace = AVAL.join) +#' replace_symbol_in_quo(quo(desc(AVAL)), target = AVAL, replace = AVAL.join) +replace_symbol_in_quo <- function(quosure, + target, + replace) { + assert_expr(quosure) + target <- quo_get_expr(assert_symbol(enquo(target))) + replace <- quo_get_expr(assert_symbol(enquo(replace))) + expr <- quo_get_expr(quosure) + if (is.symbol(expr)) { + if (expr == target) { + expr <- replace + } + } else { + for (i in seq_along(quosure)) { + if (expr[[i]] == target) { + expr[[i]] <- replace + } + } + } + rlang::quo_set_expr(quosure, expr) +} + +#' Add a Suffix to Variables in a List of Quosures +#' +#' Add a suffix to variables in a list of quosures +#' +#' @param order List of quosures +#' +#' *Permitted Values*: list of variables or `desc()` function calls +#' created by `vars()`, e.g., `vars(ADT, desc(AVAL))` +#' +#' @param vars Variables to change +#' +#' *Permitted Values*: list of variables created by `vars()` +#' +#' @param suffix Suffix +#' +#' *Permitted Values*: A character scalar +#' +#' @author Stefan Bundfuss +#' +#' @return The list of quosures where for each element the suffix (`suffix`) is +#' added to every symbol specified for `vars` +#' +#' @keywords quo +#' @family quo +#' +#' @export +#' +#' @examples +#' add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") +add_suffix_to_vars <- function(order, + vars, + suffix) { + assert_order_vars(order) + assert_vars(vars) + assert_character_scalar(suffix) + for (i in seq_along(vars)) { + order <- lapply( + order, + replace_symbol_in_quo, + target = !!quo_get_expr(vars[[i]]), + replace = !!sym(paste0(as_label( + quo_get_expr(vars[[i]]) + ), suffix)) + ) + } + class(order) <- c("quosures", "list") + order +} diff --git a/man/add_suffix_to_vars.Rd b/man/add_suffix_to_vars.Rd index 14d3eff6..098f11a3 100644 --- a/man/add_suffix_to_vars.Rd +++ b/man/add_suffix_to_vars.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dev_utilities.R +% Please edit documentation in R/quo.R \name{add_suffix_to_vars} \alias{add_suffix_to_vars} \title{Add a Suffix to Variables in a List of Quosures} @@ -34,7 +34,8 @@ add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".j Helpers for working with Quosures: \code{\link{quo_c}()}, \code{\link{quo_not_missing}()}, -\code{\link{replace_symbol_in_quo}()} +\code{\link{replace_symbol_in_quo}()}, +\code{\link{replace_values_by_names}()} } \author{ Stefan Bundfuss diff --git a/man/arg_name.Rd b/man/arg_name.Rd index 981ce05d..699734cf 100644 --- a/man/arg_name.Rd +++ b/man/arg_name.Rd @@ -24,7 +24,6 @@ Developer Utility Functions: \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/as_name.Rd b/man/as_name.Rd index 0560c69a..70bd65ee 100644 --- a/man/as_name.Rd +++ b/man/as_name.Rd @@ -28,7 +28,6 @@ Developer Utility Functions: \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/convert_dtm_to_dtc.Rd b/man/convert_dtm_to_dtc.Rd index b1a807a6..59598efb 100644 --- a/man/convert_dtm_to_dtc.Rd +++ b/man/convert_dtm_to_dtc.Rd @@ -26,7 +26,6 @@ Developer Utility Functions: \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/extract_vars.Rd b/man/extract_vars.Rd index 755d0b09..064f44a5 100644 --- a/man/extract_vars.Rd +++ b/man/extract_vars.Rd @@ -26,7 +26,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/filter_if.Rd b/man/filter_if.Rd index d807f855..01d766da 100644 --- a/man/filter_if.Rd +++ b/man/filter_if.Rd @@ -27,7 +27,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/grapes-notin-grapes.Rd b/man/grapes-notin-grapes.Rd index 47fc55ee..5df54a5a 100644 --- a/man/grapes-notin-grapes.Rd +++ b/man/grapes-notin-grapes.Rd @@ -27,7 +27,6 @@ Developer Utility Functions: \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/grapes-or-grapes.Rd b/man/grapes-or-grapes.Rd index 56bd7e42..2c3badb5 100644 --- a/man/grapes-or-grapes.Rd +++ b/man/grapes-or-grapes.Rd @@ -31,7 +31,6 @@ Developer Utility Functions: \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/negate_vars.Rd b/man/negate_vars.Rd index 57bde4bf..dfd0c25b 100644 --- a/man/negate_vars.Rd +++ b/man/negate_vars.Rd @@ -31,7 +31,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/quo_c.Rd b/man/quo_c.Rd index c094decf..fd5342f1 100644 --- a/man/quo_c.Rd +++ b/man/quo_c.Rd @@ -19,7 +19,8 @@ Concatenate One or More Quosure(s) Helpers for working with Quosures: \code{\link{add_suffix_to_vars}()}, \code{\link{quo_not_missing}()}, -\code{\link{replace_symbol_in_quo}()} +\code{\link{replace_symbol_in_quo}()}, +\code{\link{replace_values_by_names}()} } \author{ Thomas Neitmann diff --git a/man/quo_not_missing.Rd b/man/quo_not_missing.Rd index 0032a144..fd2185d4 100644 --- a/man/quo_not_missing.Rd +++ b/man/quo_not_missing.Rd @@ -19,7 +19,8 @@ Check Whether an Argument Is Not a Quosure of a Missing Argument Helpers for working with Quosures: \code{\link{add_suffix_to_vars}()}, \code{\link{quo_c}()}, -\code{\link{replace_symbol_in_quo}()} +\code{\link{replace_symbol_in_quo}()}, +\code{\link{replace_values_by_names}()} } \author{ Thomas Neitmann, Ondrej Slama diff --git a/man/replace_symbol_in_quo.Rd b/man/replace_symbol_in_quo.Rd index 4d909a40..432fccec 100644 --- a/man/replace_symbol_in_quo.Rd +++ b/man/replace_symbol_in_quo.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dev_utilities.R +% Please edit documentation in R/quo.R \name{replace_symbol_in_quo} \alias{replace_symbol_in_quo} \title{Replace Symbols in a Quosure} @@ -32,7 +32,8 @@ replace_symbol_in_quo(quo(desc(AVAL)), target = AVAL, replace = AVAL.join) Helpers for working with Quosures: \code{\link{add_suffix_to_vars}()}, \code{\link{quo_c}()}, -\code{\link{quo_not_missing}()} +\code{\link{quo_not_missing}()}, +\code{\link{replace_values_by_names}()} } \author{ Stefan Bundfuss diff --git a/man/replace_values_by_names.Rd b/man/replace_values_by_names.Rd index 132ef303..12093baa 100644 --- a/man/replace_values_by_names.Rd +++ b/man/replace_values_by_names.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dev_utilities.R +% Please edit documentation in R/quo.R \name{replace_values_by_names} \alias{replace_values_by_names} \title{Replace Quosure Value with Name} @@ -16,20 +16,14 @@ A list of quosures Replace Quosure Value with Name } \seealso{ -Developer Utility Functions: -\code{\link{\%notin\%}()}, -\code{\link{\%or\%}()}, -\code{\link{arg_name}()}, -\code{\link{as_name}()}, -\code{\link{convert_dtm_to_dtc}()}, -\code{\link{extract_vars}()}, -\code{\link{filter_if}()}, -\code{\link{negate_vars}()}, -\code{\link{valid_time_units}()}, -\code{\link{vars2chr}()} +Helpers for working with Quosures: +\code{\link{add_suffix_to_vars}()}, +\code{\link{quo_c}()}, +\code{\link{quo_not_missing}()}, +\code{\link{replace_symbol_in_quo}()} } \author{ Thomas Neitmann } -\concept{dev_utility} -\keyword{dev_utility} +\concept{quo} +\keyword{quo} diff --git a/man/valid_time_units.Rd b/man/valid_time_units.Rd index 9020bd91..7e1fc640 100644 --- a/man/valid_time_units.Rd +++ b/man/valid_time_units.Rd @@ -22,7 +22,6 @@ Developer Utility Functions: \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{vars2chr}()} } \concept{dev_utility} diff --git a/man/vars2chr.Rd b/man/vars2chr.Rd index c2f89143..654bc8f2 100644 --- a/man/vars2chr.Rd +++ b/man/vars2chr.Rd @@ -28,7 +28,6 @@ Developer Utility Functions: \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, -\code{\link{replace_values_by_names}()}, \code{\link{valid_time_units}()} } \author{ diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 7fcacf05..818ece22 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -23,61 +23,3 @@ test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct f fixed = TRUE ) }) - -# replace_symbol_in_quo ---- -## Test 4: symbol is replaced ---- -test_that("replace_symbol_in_quo Test 4: symbol is replaced", { - expect_equal( - expected = quo(AVAL.join), - object = replace_symbol_in_quo( - quo(AVAL), - target = AVAL, - replace = AVAL.join - ) - ) -}) - -## Test 5: partial match is not replaced ---- -test_that("replace_symbol_in_quo Test 5: partial match is not replaced", { - expect_equal( - expected = quo(AVALC), - object = replace_symbol_in_quo( - quo(AVALC), - target = AVAL, - replace = AVAL.join - ) - ) -}) - -## Test 6: symbol in expression is replaced ---- -test_that("replace_symbol_in_quo Test 6: symbol in expression is replaced", { - expect_equal( - expected = quo(desc(AVAL.join)), - object = replace_symbol_in_quo( - quo(desc(AVAL)), - target = AVAL, - replace = AVAL.join - ) - ) -}) - -# add_suffix_to_vars ---- -## Test 7: with single variable ---- -test_that("add_suffix_to_vars Test 7: with single variable", { - expect_equal( - expected = vars(ADT, desc(AVAL.join), AVALC), - object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") - ) -}) - -## Test 8: with more than one variable ---- -test_that("add_suffix_to_vars Test 8: with more than one variable", { - expect_equal( - expected = vars(ADT, desc(AVAL.join), AVALC.join), - object = add_suffix_to_vars( - vars(ADT, desc(AVAL), AVALC), - vars = vars(AVAL, AVALC), - suffix = ".join" - ) - ) -}) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index 8e05d295..a67ef5d4 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -16,3 +16,61 @@ test_that("quo_not_missing Test 2: `quo_not_missing` throws and Error if missing } expect_error(test_fun()) # missing argument -> throws error }) + +# replace_symbol_in_quo ---- +## Test 3: symbol is replaced ---- +test_that("replace_symbol_in_quo Test 3: symbol is replaced", { + expect_equal( + expected = quo(AVAL.join), + object = replace_symbol_in_quo( + quo(AVAL), + target = AVAL, + replace = AVAL.join + ) + ) +}) + +## Test 4: partial match is not replaced ---- +test_that("replace_symbol_in_quo Test 4: partial match is not replaced", { + expect_equal( + expected = quo(AVALC), + object = replace_symbol_in_quo( + quo(AVALC), + target = AVAL, + replace = AVAL.join + ) + ) +}) + +## Test 5: symbol in expression is replaced ---- +test_that("replace_symbol_in_quo Test 5: symbol in expression is replaced", { + expect_equal( + expected = quo(desc(AVAL.join)), + object = replace_symbol_in_quo( + quo(desc(AVAL)), + target = AVAL, + replace = AVAL.join + ) + ) +}) + +# add_suffix_to_vars ---- +## Test 6: with single variable ---- +test_that("add_suffix_to_vars Test 6: with single variable", { + expect_equal( + expected = vars(ADT, desc(AVAL.join), AVALC), + object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") + ) +}) + +## Test 7: with more than one variable ---- +test_that("add_suffix_to_vars Test 7: with more than one variable", { + expect_equal( + expected = vars(ADT, desc(AVAL.join), AVALC.join), + object = add_suffix_to_vars( + vars(ADT, desc(AVAL), AVALC), + vars = vars(AVAL, AVALC), + suffix = ".join" + ) + ) +}) From 5faf839e130a96fd6682cc5d312950af3a739d27 Mon Sep 17 00:00:00 2001 From: bms63 Date: Thu, 29 Sep 2022 18:37:52 +0000 Subject: [PATCH 024/179] #106 Trying out SO solution --- NAMESPACE | 1 + R/expect_dfs_equal.R | 38 ++++++++++++++++++++++++++++++++++++++ man/expect_dfs_equal.Rd | 4 ++++ man/expect_equal_tbl.Rd | 28 ++++++++++++++++++++++++++++ tests/testthat/test-quo.R | 4 ++-- 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 man/expect_equal_tbl.Rd diff --git a/NAMESPACE b/NAMESPACE index 7678668e..88e6a806 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -36,6 +36,7 @@ export(desc) export(dquote) export(enumerate) export(expect_dfs_equal) +export(expect_equal_tbl) export(extract_vars) export(filter_if) export(get_constant_vars) diff --git a/R/expect_dfs_equal.R b/R/expect_dfs_equal.R index 191f2407..216beeb7 100644 --- a/R/expect_dfs_equal.R +++ b/R/expect_dfs_equal.R @@ -26,3 +26,41 @@ expect_dfs_equal <- function(base, compare, keys, ...) { invisible() } } + +#' Expectation: Are Table Lists are Equal? +#' +#' Uses [diffdf::diffdf()] to compares 2 datasets for any differences +#' +#' @param base Input dataset +#' @param compare Comparison dataset + +#' @return +#' An error if `base` and `compare` do not match or `NULL` invisibly if they do +#' +#' @author Ben Straub +#' @keywords test_helper +#' @family test_helper +#' +#' @export +expect_equal_tbl <- function(object, expected, ..., info = NULL) { + act <- testthat::quasi_label(rlang::enquo(object), arg = "object") + exp <- testthat::quasi_label(rlang::enquo(expected), arg = "expected") + + # all.equal.list is slightly problematic: it returns TRUE for match, and + # returns a character vector when differences are observed. We extract + # both a match-indicator and a failure message + + diffs <- all.equal.list(object, expected, ...) + has_diff <- if (is.logical(diffs)) diffs else FALSE + diff_msg <- paste(diffs, collapse = "\n") + + testthat::expect( + has_diff, + failure_message = sprintf( + "%s not equal to %s.\n%s", act$lab, exp$lab, diff_msg + ), + info = info + ) + + invisible(act$val) +} diff --git a/man/expect_dfs_equal.Rd b/man/expect_dfs_equal.Rd index 17d8d261..37ecd736 100644 --- a/man/expect_dfs_equal.Rd +++ b/man/expect_dfs_equal.Rd @@ -22,6 +22,10 @@ An error if \code{base} and \code{compare} do not match or \code{NULL} invisibly \description{ Uses \code{\link[diffdf:diffdf]{diffdf::diffdf()}} to compares 2 datasets for any differences } +\seealso{ +Helps with Testing: +\code{\link{expect_equal_tbl}()} +} \author{ Thomas Neitmann } diff --git a/man/expect_equal_tbl.Rd b/man/expect_equal_tbl.Rd new file mode 100644 index 00000000..c9e0265c --- /dev/null +++ b/man/expect_equal_tbl.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/expect_dfs_equal.R +\name{expect_equal_tbl} +\alias{expect_equal_tbl} +\title{Expectation: Are Table Lists are Equal?} +\usage{ +expect_equal_tbl(object, expected, ..., info = NULL) +} +\arguments{ +\item{base}{Input dataset} + +\item{compare}{Comparison dataset} +} +\value{ +An error if \code{base} and \code{compare} do not match or \code{NULL} invisibly if they do +} +\description{ +Uses \code{\link[diffdf:diffdf]{diffdf::diffdf()}} to compares 2 datasets for any differences +} +\seealso{ +Helps with Testing: +\code{\link{expect_dfs_equal}()} +} +\author{ +Ben Straub +} +\concept{test_helper} +\keyword{test_helper} diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index a67ef5d4..b69ffd03 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -57,7 +57,7 @@ test_that("replace_symbol_in_quo Test 5: symbol in expression is replaced", { # add_suffix_to_vars ---- ## Test 6: with single variable ---- test_that("add_suffix_to_vars Test 6: with single variable", { - expect_equal( + expect_equal_tbl( expected = vars(ADT, desc(AVAL.join), AVALC), object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") ) @@ -65,7 +65,7 @@ test_that("add_suffix_to_vars Test 6: with single variable", { ## Test 7: with more than one variable ---- test_that("add_suffix_to_vars Test 7: with more than one variable", { - expect_equal( + expect_equal_tbl( expected = vars(ADT, desc(AVAL.join), AVALC.join), object = add_suffix_to_vars( vars(ADT, desc(AVAL), AVALC), From 5c3bba21c2c076aaa5df431be0b4a0adadf00b10 Mon Sep 17 00:00:00 2001 From: bms63 Date: Fri, 30 Sep 2022 00:43:39 +0000 Subject: [PATCH 025/179] #106 reverting SO solution --- NAMESPACE | 1 - R/expect_dfs_equal.R | 37 ------------------------------------- man/expect_dfs_equal.Rd | 4 ---- man/expect_equal_tbl.Rd | 28 ---------------------------- tests/testthat/test-quo.R | 4 ++-- 5 files changed, 2 insertions(+), 72 deletions(-) delete mode 100644 man/expect_equal_tbl.Rd diff --git a/NAMESPACE b/NAMESPACE index 88e6a806..7678668e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -36,7 +36,6 @@ export(desc) export(dquote) export(enumerate) export(expect_dfs_equal) -export(expect_equal_tbl) export(extract_vars) export(filter_if) export(get_constant_vars) diff --git a/R/expect_dfs_equal.R b/R/expect_dfs_equal.R index 216beeb7..5069ae70 100644 --- a/R/expect_dfs_equal.R +++ b/R/expect_dfs_equal.R @@ -27,40 +27,3 @@ expect_dfs_equal <- function(base, compare, keys, ...) { } } -#' Expectation: Are Table Lists are Equal? -#' -#' Uses [diffdf::diffdf()] to compares 2 datasets for any differences -#' -#' @param base Input dataset -#' @param compare Comparison dataset - -#' @return -#' An error if `base` and `compare` do not match or `NULL` invisibly if they do -#' -#' @author Ben Straub -#' @keywords test_helper -#' @family test_helper -#' -#' @export -expect_equal_tbl <- function(object, expected, ..., info = NULL) { - act <- testthat::quasi_label(rlang::enquo(object), arg = "object") - exp <- testthat::quasi_label(rlang::enquo(expected), arg = "expected") - - # all.equal.list is slightly problematic: it returns TRUE for match, and - # returns a character vector when differences are observed. We extract - # both a match-indicator and a failure message - - diffs <- all.equal.list(object, expected, ...) - has_diff <- if (is.logical(diffs)) diffs else FALSE - diff_msg <- paste(diffs, collapse = "\n") - - testthat::expect( - has_diff, - failure_message = sprintf( - "%s not equal to %s.\n%s", act$lab, exp$lab, diff_msg - ), - info = info - ) - - invisible(act$val) -} diff --git a/man/expect_dfs_equal.Rd b/man/expect_dfs_equal.Rd index 37ecd736..17d8d261 100644 --- a/man/expect_dfs_equal.Rd +++ b/man/expect_dfs_equal.Rd @@ -22,10 +22,6 @@ An error if \code{base} and \code{compare} do not match or \code{NULL} invisibly \description{ Uses \code{\link[diffdf:diffdf]{diffdf::diffdf()}} to compares 2 datasets for any differences } -\seealso{ -Helps with Testing: -\code{\link{expect_equal_tbl}()} -} \author{ Thomas Neitmann } diff --git a/man/expect_equal_tbl.Rd b/man/expect_equal_tbl.Rd deleted file mode 100644 index c9e0265c..00000000 --- a/man/expect_equal_tbl.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/expect_dfs_equal.R -\name{expect_equal_tbl} -\alias{expect_equal_tbl} -\title{Expectation: Are Table Lists are Equal?} -\usage{ -expect_equal_tbl(object, expected, ..., info = NULL) -} -\arguments{ -\item{base}{Input dataset} - -\item{compare}{Comparison dataset} -} -\value{ -An error if \code{base} and \code{compare} do not match or \code{NULL} invisibly if they do -} -\description{ -Uses \code{\link[diffdf:diffdf]{diffdf::diffdf()}} to compares 2 datasets for any differences -} -\seealso{ -Helps with Testing: -\code{\link{expect_dfs_equal}()} -} -\author{ -Ben Straub -} -\concept{test_helper} -\keyword{test_helper} diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index b69ffd03..a67ef5d4 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -57,7 +57,7 @@ test_that("replace_symbol_in_quo Test 5: symbol in expression is replaced", { # add_suffix_to_vars ---- ## Test 6: with single variable ---- test_that("add_suffix_to_vars Test 6: with single variable", { - expect_equal_tbl( + expect_equal( expected = vars(ADT, desc(AVAL.join), AVALC), object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") ) @@ -65,7 +65,7 @@ test_that("add_suffix_to_vars Test 6: with single variable", { ## Test 7: with more than one variable ---- test_that("add_suffix_to_vars Test 7: with more than one variable", { - expect_equal_tbl( + expect_equal( expected = vars(ADT, desc(AVAL.join), AVALC.join), object = add_suffix_to_vars( vars(ADT, desc(AVAL), AVALC), From dfb80621347f6eb8c48e0c80fe18b726eeb39704 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Fri, 30 Sep 2022 10:43:58 +0200 Subject: [PATCH 026/179] 106_add_suffix_to_vars: fix styler and lintr checks --- R/expect_dfs_equal.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/expect_dfs_equal.R b/R/expect_dfs_equal.R index 5069ae70..191f2407 100644 --- a/R/expect_dfs_equal.R +++ b/R/expect_dfs_equal.R @@ -26,4 +26,3 @@ expect_dfs_equal <- function(base, compare, keys, ...) { invisible() } } - From ee93b3480ec2124dff36cabafaf9ee5d1c83a576 Mon Sep 17 00:00:00 2001 From: PoojaKumari05 Date: Mon, 3 Oct 2022 07:22:03 +0000 Subject: [PATCH 027/179] 62_resolve_style_issues --- tests/testthat/test-dev_utilities.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index f8bbef58..470bedf8 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -33,7 +33,7 @@ test_that("filter_if Test 1 : Input is returned as is if filter is NULL", { "P01", "HEIGHT", 189.2 ) - expected_output = input + expected_output <- input expect_dfs_equal( expected_output, @@ -51,7 +51,7 @@ test_that("filter_if Test 2 : Input is filtered if filter is not NULL", { "P01", "HEIGHT", 189.2 ) - expected_output = input[1L, ] + expected_output <- input[1L, ] expect_dfs_equal( expected_output, From 6f0018e5bfe133de4218d22a41c88d2a77a1b17b Mon Sep 17 00:00:00 2001 From: "Farrugia, Ross {MDBB~Welwyn}" Date: Wed, 5 Oct 2022 16:29:35 +0200 Subject: [PATCH 028/179] #79 new release strategy vignette --- _pkgdown.yml | 2 + docs/pkgdown.yml | 3 +- vignettes/git_usage.Rmd | 35 ----------------- vignettes/release_strategy.Rmd | 69 ++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 vignettes/release_strategy.Rmd diff --git a/_pkgdown.yml b/_pkgdown.yml index 507da630..e9ea6ae9 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -94,3 +94,5 @@ navbar: href: articles/git_usage.html - text: Pull Request Review Guidance href: articles/pr_review_guidance.html + - text: Release Strategy + href: articles/release_strategy.html diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 8ad0cfa8..c93d3744 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -7,9 +7,10 @@ articles: git_usage: git_usage.html pr_review_guidance: pr_review_guidance.html programming_strategy: programming_strategy.html + release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-09-27T11:33Z +last_built: 2022-10-05T14:22Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/git_usage.Rmd b/vignettes/git_usage.Rmd index 55405536..35056258 100644 --- a/vignettes/git_usage.Rmd +++ b/vignettes/git_usage.Rmd @@ -179,38 +179,3 @@ knitr::include_graphics("github_conflicts.png", dpi = 144) * [GitHub and RStudio](https://resources.github.com/whitepapers/github-and-rstudio/) * [Happy Git and GitHub for the useR](https://happygitwithr.com/) - -# Package Release - -## Quarterly Release - -A package release is done in five parts: - -1) Create a Pull Request from `devel` into the `pre-release` branch. Issues identified in this Pull Request should have work done in separate branches and merged into the `pre-release` branch and **NOT** `devel`. -1) Verify that all CI/CD checks are passing for the `devel` into the `pre-release` Pull Request, merge and then bundle up and send off to CRAN. -1) Once the package is available on CRAN, another Pull Request is created for merging the `pre-release` branch into the `main` branch. This will trigger the GitHub action to rebuild the `{admiral}` website with all the updates for this release. -1) Use the release button on GitHub to "release" the package onto GitHub. This release onto Github archives the version of code within the `main` branch, attaches the News/Changelog file, bundles the code into a `tar.gz` file and makes a validation report via the GitHub action `validation` from [insightsengineering/validatoR](https://github.com/insightsengineering/thevalidatoR). Please see past [admiral releases](https://github.com/pharmaverse/admiral/releases) for reference. -1) Any issues fixed in the `pre-release/main` branches should be merged back into `devel`. - -**Quarterly Release:** `devel >> pre-release >> main` - -## Hot Fix Release - -Occasionally we will need to release a hot fix to address a package breaking bug. A hot fix release is done in 6 parts: - -1) Identify all the bugs that need to be fixed for this hot fix release and label with hot fix label. -1) Branches addressing the bugs should have Pull Requests merged into the `patch` branch **NOT** the `devel` branch. -1) Create a Pull Request from `patch` into the `pre-release` branch. Verify that all CI/CD checks are passing, merge and bundle up and send off to CRAN. -1) Once package is approved and available on CRAN, another Pull Request is created for merging the `pre-release` branch into the `main` branch. This will trigger the action to rebuild the `{admiral}` website with all the updates for this hot fix release. -1) Use the release button on GitHub to "release" the package onto GitHub. This release onto Github archives the version of code within the `main` branch, attaches the News/Changelog file, bundles the code into a `tar.gz` file and makes a validation report via the GitHub action `validation` from [insightsengineering/validatoR](https://github.com/insightsengineering/thevalidatoR). Please see past [admiral releases](https://github.com/pharmaverse/admiral/releases) for reference. -1) These hot fixes should then be merged into the `devel` branch through an additional Pull Request. - -**Hot Fix Release**: `patch >> pre-release >> main >> devel` - -## Release Schedule - -A release schedule is maintained on the [homepage](../index.html#release-schedule). - - - - diff --git a/vignettes/release_strategy.Rmd b/vignettes/release_strategy.Rmd new file mode 100644 index 00000000..19b9e038 --- /dev/null +++ b/vignettes/release_strategy.Rmd @@ -0,0 +1,69 @@ +--- +title: "Release Strategy" +output: + rmarkdown::html_vignette: + toc: true + toc_depth: 6 +vignette: > + %\VignetteIndexEntry{Release Strategy} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +# Introduction + +This article explains how we do package releases for `{admiral}` and across all our +package extensions. This includes details around planned/scheduled releases, as well +as hotfixes. + +# Release Schedule + +A release schedule is maintained on the [homepage](../index.html#release-schedule). + +# Planning Releases + +Whenever we start looking towards a future release, we create a new release issue +label on GitHub of the form `release Q4-2022` for example. This then can be added +to all the issues we plan to cover in the release and these can be moved to the +Priority column of our project board. + +We should share in advance with our users a high level summary of expected package +updates via the community meetings, especially any anticipated breaking changes. + +# Package Release Process + +## Quarterly Release + +A package release is done in five parts: + +1) Create a Pull Request from `devel` into the `pre-release` branch. Issues identified in this Pull Request should have work done in separate branches and merged into the `pre-release` branch and **NOT** `devel`. +1) Verify that all CI/CD checks are passing for the `devel` into the `pre-release` Pull Request, merge and then bundle up and send off to CRAN. +1) Once the package is available on CRAN, another Pull Request is created for merging the `pre-release` branch into the `main` branch. This will trigger the GitHub action to rebuild the `{admiral}` website with all the updates for this release. +1) Use the release button on GitHub to "release" the package onto GitHub. This release onto Github archives the version of code within the `main` branch, attaches the News/Changelog file, bundles the code into a `tar.gz` file and makes a validation report via the GitHub action `validation` from [insightsengineering/validatoR](https://github.com/insightsengineering/thevalidatoR). Please see past [admiral releases](https://github.com/pharmaverse/admiral/releases) for reference. +1) Any issues fixed in the `pre-release/main` branches should be merged back into `devel`. + +**Quarterly Release:** `devel >> pre-release >> main` + +## Hot Fix Release + +Occasionally we will need to release a hot fix to address a package breaking bug. A hot fix release is done in 6 parts: + +1) Identify all the bugs that need to be fixed for this hot fix release and label with hot fix label. +1) Branches addressing the bugs should have Pull Requests merged into the `patch` branch **NOT** the `devel` branch. +1) Create a Pull Request from `patch` into the `pre-release` branch. Verify that all CI/CD checks are passing, merge and bundle up and send off to CRAN. +1) Once package is approved and available on CRAN, another Pull Request is created for merging the `pre-release` branch into the `main` branch. This will trigger the action to rebuild the `{admiral}` website with all the updates for this hot fix release. +1) Use the release button on GitHub to "release" the package onto GitHub. This release onto Github archives the version of code within the `main` branch, attaches the News/Changelog file, bundles the code into a `tar.gz` file and makes a validation report via the GitHub action `validation` from [insightsengineering/validatoR](https://github.com/insightsengineering/thevalidatoR). Please see past [admiral releases](https://github.com/pharmaverse/admiral/releases) for reference. +1) These hot fixes should then be merged into the `devel` branch through an additional Pull Request. + +**Hot Fix Release**: `patch >> pre-release >> main >> devel` + +# Communications + +After the release, we raise awareness via our Slack channel and LinkedIn. From e090ffb1028c383f670f3386ca5bf491b0ab94d2 Mon Sep 17 00:00:00 2001 From: Ross Farrugia <82581364+rossfarrugia@users.noreply.github.com> Date: Wed, 5 Oct 2022 15:32:07 +0100 Subject: [PATCH 029/179] #79 package release vignette --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index eeee3f72..1a4110dd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,7 +11,7 @@ ## Breaking Changes - NA ## Documentation - - NA + - New vignette for our package release strategy (#79) ## Various - NA From 72ea8510fed7f13ee85acde0a19a566ddf28c59b Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Wed, 5 Oct 2022 14:06:34 -0400 Subject: [PATCH 030/179] Update _pkgdown.yml --- _pkgdown.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 507da630..c3848e6f 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,4 +1,4 @@ -url: https://pharmaverse.github.io/admiraldev/devel +url: https://pharmaverse.github.io/admiraldev/ template: bootstrap: 5 From dc6866d0a9920bfab4736e7bfa6fad79c9c35323 Mon Sep 17 00:00:00 2001 From: Ross Farrugia <82581364+rossfarrugia@users.noreply.github.com> Date: Thu, 6 Oct 2022 08:21:34 +0100 Subject: [PATCH 031/179] #79 update release schedule link --- vignettes/release_strategy.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/release_strategy.Rmd b/vignettes/release_strategy.Rmd index 19b9e038..62d309f0 100644 --- a/vignettes/release_strategy.Rmd +++ b/vignettes/release_strategy.Rmd @@ -25,7 +25,7 @@ as hotfixes. # Release Schedule -A release schedule is maintained on the [homepage](../index.html#release-schedule). +A release schedule is maintained on the [homepage](https://pharmaverse.github.io/admiral/#release-schedule). # Planning Releases From 65748257e0c248f6efc98a936b9e7bdca128194b Mon Sep 17 00:00:00 2001 From: Ross Farrugia <82581364+rossfarrugia@users.noreply.github.com> Date: Thu, 6 Oct 2022 08:21:38 +0100 Subject: [PATCH 032/179] #79 update release schedule link --- README.Rmd | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.Rmd b/README.Rmd index 42559d0b..c85563c9 100644 --- a/README.Rmd +++ b/README.Rmd @@ -61,10 +61,4 @@ remotes::install_github("pharmaverse/admiraldev", ref = "devel") ## Release Schedule -`{admiraldev}` is to be officially released to CRAN one week before an official release of `{admiral}`. You can find the release schedule for `{admiral}` packages [here](https://github.com/pharmaverse/admiral/tree/devel#release-schedule). - - - - - - +`{admiraldev}` is to be officially released to CRAN one week before an official release of `{admiral}`. You can find the release schedule for `{admiral}` packages [here](https://pharmaverse.github.io/admiral/#release-schedule). From 5da326fa1f01e69eaf7964f6f8fabd63f3718aab Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Tue, 11 Oct 2022 17:45:18 +0000 Subject: [PATCH 033/179] 117_expect_names: add expect_names argument to assert_vars() --- NEWS.md | 5 ++- R/assertions.R | 28 ++++++++++++++- docs/pkgdown.yml | 6 ++-- man/assert_vars.Rd | 16 ++++++++- tests/testthat/test-assertions.R | 59 +++++++++++++++++++++++++------- 5 files changed, 95 insertions(+), 19 deletions(-) diff --git a/NEWS.md b/NEWS.md index 1a4110dd..81e25219 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,7 +7,10 @@ - New functions `replace_symbol_in_quo()` and `add_suffix_to_vars()` (#106) ## Updates of Existing Functions - - NA + + - `expect_names` argument added to `assert_vars()` to check if all variables + are named (#117) + ## Breaking Changes - NA ## Documentation diff --git a/R/assertions.R b/R/assertions.R index 550c4fb2..1fa545b0 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -437,9 +437,15 @@ assert_filter_cond <- function(arg, optional = FALSE) { #' Checks if an argument is a valid list of variables created using `vars()` #' #' @param arg A function argument to be checked +#' #' @param optional Is the checked parameter optional? If set to `FALSE` and `arg` #' is `NULL` then an error is thrown #' +#' @param expect_names Expect Names? +#' +#' If the argument is set to `TRUE`, it is checked if all variables are named, +#' e.g., `vars(APERSDT = APxxSDT, APEREDT = APxxEDT)`. +#' #' @author Samia Kabi #' #' @return @@ -462,7 +468,16 @@ assert_filter_cond <- function(arg, optional = FALSE) { #' try(example_fun(c("USUBJID", "PARAMCD", "VISIT"))) #' #' try(example_fun(vars(USUBJID, toupper(PARAMCD), desc(AVAL)))) -assert_vars <- function(arg, optional = FALSE) { +#' +#' example_fun_name <- function(by_vars) { +#' assert_vars(by_vars, expect_names = TRUE) +#' } +#' +#' example_fun_name(vars(APERSDT = APxxSDT, APEREDT = APxxEDT)) +#' +#' try(example_fun_name(vars(APERSDT = APxxSDT, APxxEDT))) +#' +assert_vars <- function(arg, optional = FALSE, expect_names = FALSE) { assert_logical_scalar(optional) default_err_msg <- sprintf( @@ -493,6 +508,17 @@ assert_vars <- function(arg, optional = FALSE) { abort(err_msg) } + if (expect_names) { + if (any(names(arg) == "")) { + abort(sprintf( + paste( + "`%s` must be a named list of unquoted variable names,", + "e.g. `vars(APERSDT = APxxSDT, APEREDT = APxxEDT)`"), + arg_name(substitute(arg)) + )) + } + } + invisible(arg) } diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index c93d3744..f0614510 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,8 +10,8 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-05T14:22Z +last_built: 2022-10-11T17:39Z urls: - reference: https://pharmaverse.github.io/admiraldev/devel/reference - article: https://pharmaverse.github.io/admiraldev/devel/articles + reference: https://pharmaverse.github.io/admiraldev/reference + article: https://pharmaverse.github.io/admiraldev/articles diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index 66b852a5..7e9059fc 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -4,13 +4,18 @@ \alias{assert_vars} \title{Is an Argument a List of Variables?} \usage{ -assert_vars(arg, optional = FALSE) +assert_vars(arg, optional = FALSE, expect_names = FALSE) } \arguments{ \item{arg}{A function argument to be checked} \item{optional}{Is the checked parameter optional? If set to \code{FALSE} and \code{arg} is \code{NULL} then an error is thrown} + +\item{expect_names}{Expect Names? + +If the argument is set to \code{TRUE}, it is checked if all variables are named, +e.g., \code{vars(APERSDT = APxxSDT, APEREDT = APxxEDT)}.} } \value{ The function throws an error if \code{arg} is not a list of variables created using \code{vars()} @@ -31,6 +36,15 @@ try(example_fun(rlang::exprs(USUBJID, PARAMCD))) try(example_fun(c("USUBJID", "PARAMCD", "VISIT"))) try(example_fun(vars(USUBJID, toupper(PARAMCD), desc(AVAL)))) + +example_fun_name <- function(by_vars) { + assert_vars(by_vars, expect_names = TRUE) +} + +example_fun_name(vars(APERSDT = APxxSDT, APEREDT = APxxEDT)) + +try(example_fun_name(vars(APERSDT = APxxSDT, APxxEDT))) + } \seealso{ Checks for valid input and returns warning or errors messages: diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index ea5bfe8e..348ef732 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -1,7 +1,8 @@ library(admiral.test) -test_that("Test 1 : `assert_has_variables` an error is thrown if a required - variable is missing", { +# assert_has_variables ---- +## Test 1: error if a required variable is missing ---- +test_that("assert_has_variables Test 1: error if a required variable is missing", { data(admiral_dm) expect_error( @@ -10,14 +11,16 @@ test_that("Test 1 : `assert_has_variables` an error is thrown if a required ) }) -test_that("Test 2 : `assert_has_variables` no error is thrown if a required - variable exists", { +## Test 2: no error if a required variable exists ---- +test_that("assert_has_variables Test 2: no error if a required variable exists", { data(admiral_dm) expect_error(assert_has_variables(admiral_dm, "USUBJID"), NA) }) -test_that("Test 3 : `assert_filter_cond` works as expected", { +# assert_filter_cond ---- +## Test 3: `assert_filter_cond` works as expected ---- +test_that("assert_filter_cond Test 3: `assert_filter_cond` works as expected", { fc <- quo(AGE == 64) expect_identical( assert_filter_cond(fc), @@ -42,15 +45,21 @@ test_that("Test 3 : `assert_filter_cond` works as expected", { ) }) -test_that("Test 4 : is_valid_sec_min works as expected", { +# is_valid_sec_min ---- +## Test 4: is_valid_sec_min works as expected ---- +test_that("is_valid_sec_min Test 4: is_valid_sec_min works as expected", { expect_true(is_valid_sec_min(59)) }) -test_that("Test 5 : is_valid_hour works as expected", { +# is_valid_hour ---- +## Test 5: is_valid_hour works as expected ---- +test_that("is_valid_hour Test 5: is_valid_hour works as expected", { expect_true(is_valid_hour(23)) }) -test_that("Test 6 : `assert_data_frame` throws an error if not a dataframe", { +# assert_data_fram ---- +## Test 6: error if not a dataframe ---- +test_that("assert_data_fram Test 6: error if not a dataframe", { example_fun <- function(dataset) { assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } @@ -59,7 +68,8 @@ test_that("Test 6 : `assert_data_frame` throws an error if not a dataframe", { ) }) -test_that("Test 7 : `assert_data_frame` throws an error if dataframe is grouped", { +## Test 7: error if dataframe is grouped ---- +test_that("assert_data_fram Test 7: error if dataframe is grouped", { example_fun <- function(dataset) { assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } @@ -71,7 +81,9 @@ test_that("Test 7 : `assert_data_frame` throws an error if dataframe is grouped" ) }) -test_that("Test 8 : `assert_character_scalar` throws an error if not a character scaler string", { +# assert_character_scalar ---- +## Test 8: error if not a character scaler string ---- +test_that("assert_character_scalar Test 8: error if not a character scaler string", { example_fun2 <- function(msg_type) { msg_type <- assert_character_scalar(msg_type, values = c("warning", "error"), case_sensitive = FALSE @@ -84,7 +96,8 @@ test_that("Test 8 : `assert_character_scalar` throws an error if not a character expect_error(example_fun2(2)) }) -test_that("Test 9 : `assert_character_scalar` throws an error if input is a vector", { +## Test 9: error if input is a vector ---- +test_that("assert_character_scalar Test 9: error if input is a vector", { example_fun2 <- function(msg_type) { msg_type <- assert_character_scalar(msg_type, values = c("warning", "error"), case_sensitive = FALSE @@ -97,11 +110,31 @@ test_that("Test 9 : `assert_character_scalar` throws an error if input is a vect expect_error(example_fun2(c("admiral", "admiralonco"))) }) -test_that("Test 10 : `assert_order_vars` returns invisible if used correctly", { +# assert_vars ---- +test_that("no error if expected input", { + expect_invisible(assert_vars(vars(USUBJID, PARAMCD))) + expect_invisible(assert_vars( + vars(APERSDT = APxxSDT, APEREDT = APxxEDT), + expect_names = TRUE)) +}) + +test_that("error if unexpected input", { + expect_error(assert_vars(AVAL + 1)) + expect_error(assert_vars(rlang::exprs(USUBJID, PARAMCD))) + expect_error(assert_vars(c("USUBJID", "PARAMCD", "VISIT"))) + expect_error(assert_vars(vars(USUBJID, AVAL + 2))) + expect_error(assert_vars(vars(APERSDT = APxxSDT, APxxEDT), expect_names = TRUE)) +} +) + +# assert_order_vars ---- +## Test 10: returns invisible if used correctly ---- +test_that("assert_order_vars Test 10: returns invisible if used correctly", { expect_invisible(assert_order_vars(vars(USUBJID, PARAMCD, desc(AVISITN)))) }) -test_that("Test 11 : `assert_order_vars` returns errors if used incorrectly", { +## Test 11: returns errors if used incorrectly ---- +test_that("assert_order_vars Test 11: returns errors if used incorrectly", { expect_error(assert_order_vars(rlang::exprs(USUBJID, PARAMCD))) expect_error(assert_order_vars(c("USUBJID", "PARAMCD", "VISIT"))) expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) From e0f46c0df110b7e0600ef17593a5c7eb0c531ffb Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Tue, 11 Oct 2022 17:49:51 +0000 Subject: [PATCH 034/179] 117_expect_names: style files --- R/assertions.R | 6 +++--- tests/testthat/test-assertions.R | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 1fa545b0..6bb76379 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -476,7 +476,6 @@ assert_filter_cond <- function(arg, optional = FALSE) { #' example_fun_name(vars(APERSDT = APxxSDT, APEREDT = APxxEDT)) #' #' try(example_fun_name(vars(APERSDT = APxxSDT, APxxEDT))) -#' assert_vars <- function(arg, optional = FALSE, expect_names = FALSE) { assert_logical_scalar(optional) @@ -512,8 +511,9 @@ assert_vars <- function(arg, optional = FALSE, expect_names = FALSE) { if (any(names(arg) == "")) { abort(sprintf( paste( - "`%s` must be a named list of unquoted variable names,", - "e.g. `vars(APERSDT = APxxSDT, APEREDT = APxxEDT)`"), + "`%s` must be a named list of unquoted variable names,", + "e.g. `vars(APERSDT = APxxSDT, APEREDT = APxxEDT)`" + ), arg_name(substitute(arg)) )) } diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 348ef732..1e79af6e 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -111,30 +111,32 @@ test_that("assert_character_scalar Test 9: error if input is a vector", { }) # assert_vars ---- -test_that("no error if expected input", { +## Test 10: no error if expected input ---- +test_that("assert_vars Test 10: no error if expected input", { expect_invisible(assert_vars(vars(USUBJID, PARAMCD))) expect_invisible(assert_vars( vars(APERSDT = APxxSDT, APEREDT = APxxEDT), - expect_names = TRUE)) + expect_names = TRUE + )) }) -test_that("error if unexpected input", { +## Test 11: error if unexpected input ---- +test_that("assert_vars Test 11: error if unexpected input", { expect_error(assert_vars(AVAL + 1)) expect_error(assert_vars(rlang::exprs(USUBJID, PARAMCD))) expect_error(assert_vars(c("USUBJID", "PARAMCD", "VISIT"))) expect_error(assert_vars(vars(USUBJID, AVAL + 2))) expect_error(assert_vars(vars(APERSDT = APxxSDT, APxxEDT), expect_names = TRUE)) -} -) +}) # assert_order_vars ---- -## Test 10: returns invisible if used correctly ---- -test_that("assert_order_vars Test 10: returns invisible if used correctly", { +## Test 12: returns invisible if used correctly ---- +test_that("assert_order_vars Test 12: returns invisible if used correctly", { expect_invisible(assert_order_vars(vars(USUBJID, PARAMCD, desc(AVISITN)))) }) -## Test 11: returns errors if used incorrectly ---- -test_that("assert_order_vars Test 11: returns errors if used incorrectly", { +## Test 13: returns errors if used incorrectly ---- +test_that("assert_order_vars Test 13: returns errors if used incorrectly", { expect_error(assert_order_vars(rlang::exprs(USUBJID, PARAMCD))) expect_error(assert_order_vars(c("USUBJID", "PARAMCD", "VISIT"))) expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) From 2b250871a482f913c5f41531fb8b3e749d1e6f20 Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Tue, 11 Oct 2022 17:59:55 +0000 Subject: [PATCH 035/179] 117_expect_names: update man --- man/assert_vars.Rd | 1 - 1 file changed, 1 deletion(-) diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index 7e9059fc..3ec28810 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -44,7 +44,6 @@ example_fun_name <- function(by_vars) { example_fun_name(vars(APERSDT = APxxSDT, APEREDT = APxxEDT)) try(example_fun_name(vars(APERSDT = APxxSDT, APxxEDT))) - } \seealso{ Checks for valid input and returns warning or errors messages: From 2cce08f8937a725cb51dedf6e7dba915e8e5aad1 Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Wed, 12 Oct 2022 09:09:08 +0000 Subject: [PATCH 036/179] 117_expect_names: remove admiral.test dependency form test-assertions.R and fix links --- _pkgdown.yml | 2 +- docs/pkgdown.yml | 6 +++--- tests/testthat/test-assertions.R | 23 ++++++++++++++++------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/_pkgdown.yml b/_pkgdown.yml index 62051a2d..fb594647 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -1,4 +1,4 @@ -url: https://pharmaverse.github.io/admiraldev/ +url: https://pharmaverse.github.io/admiraldev/devel/ template: bootstrap: 5 diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index f0614510..f88c2205 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,8 +10,8 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-11T17:39Z +last_built: 2022-10-12T09:02Z urls: - reference: https://pharmaverse.github.io/admiraldev/reference - article: https://pharmaverse.github.io/admiraldev/articles + reference: https://pharmaverse.github.io/admiraldev/devel/reference + article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 1e79af6e..c0c60904 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -1,21 +1,26 @@ -library(admiral.test) # assert_has_variables ---- ## Test 1: error if a required variable is missing ---- test_that("assert_has_variables Test 1: error if a required variable is missing", { - data(admiral_dm) + data <- tibble::tribble( + ~USUBJID, + "1" + ) expect_error( - assert_has_variables(admiral_dm, "TRT01P"), + assert_has_variables(data, "TRT01P"), "Required variable `TRT01P` is missing." ) }) ## Test 2: no error if a required variable exists ---- test_that("assert_has_variables Test 2: no error if a required variable exists", { - data(admiral_dm) + data <- tibble::tribble( + ~USUBJID, + "1" + ) - expect_error(assert_has_variables(admiral_dm, "USUBJID"), NA) + expect_error(assert_has_variables(data, "USUBJID"), NA) }) # assert_filter_cond ---- @@ -74,10 +79,14 @@ test_that("assert_data_fram Test 7: error if dataframe is grouped", { assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } - admiral_dm <- admiral_dm %>% group_by(ARMCD) + data <- tibble::tribble( + ~STUDYID, ~USUBJID, ~ARMCD, + "xyz", "1", "PLACEBO", + "xyz", "2", "ACTIVE" + ) %>% group_by(ARMCD) expect_error( - example_fun(admiral_dm) + example_fun(data) ) }) From f1fd57920e0b5438784449e881150a9eae8b46c8 Mon Sep 17 00:00:00 2001 From: Stefan Bundfuss Date: Wed, 12 Oct 2022 09:12:48 +0000 Subject: [PATCH 037/179] 117_expect_names: remove short description of expect_names --- R/assertions.R | 6 ++---- man/assert_vars.Rd | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 6bb76379..f9ec1cb7 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -441,10 +441,8 @@ assert_filter_cond <- function(arg, optional = FALSE) { #' @param optional Is the checked parameter optional? If set to `FALSE` and `arg` #' is `NULL` then an error is thrown #' -#' @param expect_names Expect Names? -#' -#' If the argument is set to `TRUE`, it is checked if all variables are named, -#' e.g., `vars(APERSDT = APxxSDT, APEREDT = APxxEDT)`. +#' @param expect_names If the argument is set to `TRUE`, it is checked if all +#' variables are named, e.g., `vars(APERSDT = APxxSDT, APEREDT = APxxEDT)`. #' #' @author Samia Kabi #' diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index 3ec28810..b9fb012c 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -12,10 +12,8 @@ assert_vars(arg, optional = FALSE, expect_names = FALSE) \item{optional}{Is the checked parameter optional? If set to \code{FALSE} and \code{arg} is \code{NULL} then an error is thrown} -\item{expect_names}{Expect Names? - -If the argument is set to \code{TRUE}, it is checked if all variables are named, -e.g., \code{vars(APERSDT = APxxSDT, APEREDT = APxxEDT)}.} +\item{expect_names}{If the argument is set to \code{TRUE}, it is checked if all +variables are named, e.g., \code{vars(APERSDT = APxxSDT, APEREDT = APxxEDT)}.} } \value{ The function throws an error if \code{arg} is not a list of variables created using \code{vars()} From 71771737a426be7e8f09a43ede0d4a9475294922 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 19:57:07 +0200 Subject: [PATCH 038/179] added tests in get.R and tested in test-get.R --- R/get.R | 8 ++++++++ tests/testthat/test-get.R | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/R/get.R b/R/get.R index 1438b8f9..ac92a7a1 100644 --- a/R/get.R +++ b/R/get.R @@ -19,6 +19,10 @@ #' @return Variable vector. #' @export get_constant_vars <- function(dataset, by_vars, ignore_vars = NULL) { + assert_data_frame(dataset, optional = FALSE) + assert_vars(by_vars, optional = FALSE) + assert_vars(ignore_vars, optional = TRUE) + non_by_vars <- setdiff(names(dataset), vars2chr(by_vars)) if (!is.null(ignore_vars)) { @@ -63,6 +67,8 @@ get_constant_vars <- function(dataset, by_vars, ignore_vars = NULL) { #' #' get_duplicates(c("a", "a", "b", "c", "d", "d")) get_duplicates <- function(x) { + assert_that(is.atomic(x), msg="x must be an atomic vector") + unique(x[duplicated(x)]) } @@ -78,5 +84,7 @@ get_duplicates <- function(x) { #' @return A list of quosures #' @export get_source_vars <- function(quosures) { + assert_varval_list(quosures) + quo_c(quosures)[lapply(quo_c(quosures), quo_is_symbol) == TRUE] } diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index 552d4de6..faf2933e 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -30,3 +30,27 @@ test_that("get_constant_vars Test 2: with ignore_vars", { vars(USUBJID, AGE) ) }) + +# get_duplicates ---- +## Test 1: x atomic vector ---- +test_that("get_duplicates Test 1: x atomic vector", { + x <- c("a", "a", "b", "c", "d", "d",1,1,4) + + expect_equal( + get_duplicates(x), + c("a","d",1) + ) +}) + +## Test 2: x not atomic vector ---- +test_that("get_constant_vars Test 2: x not atomic vector", { + x <- list("a", "a", "b", "c", "d", "d",1,1,4) + + expect_error(get_duplicates(x) + ) +}) + + + + + From abe67fcace8be50a1d4e0a7cde9037418abdebea Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 20:17:48 +0200 Subject: [PATCH 039/179] removed empty space --- tests/testthat/test-get.R | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index faf2933e..3fe89e64 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -49,8 +49,3 @@ test_that("get_constant_vars Test 2: x not atomic vector", { expect_error(get_duplicates(x) ) }) - - - - - From 7a2ba0b52ebef5d3e3eff4ee983b8c5b65bfd4f2 Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Wed, 12 Oct 2022 14:24:14 -0400 Subject: [PATCH 040/179] Add test to get to full coverage when running inside pkgdown using display_vars input --- tests/testthat/test-dataset_vignette.R | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index 26c082c1..e3c9c357 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -12,8 +12,8 @@ test_that("dataset_vignette, test 2: a 'datatables' object is output when run in expect_s3_class(dataset_vignette(head(admiral_dm)), "datatables") }) -# ---- dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown ---- -test_that("dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown", { +# ---- dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown using display_vars input ---- +test_that("dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown using display_vars input", { Sys.setenv(IN_PKGDOWN = "false") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class( @@ -21,3 +21,12 @@ test_that("dataset_vignette, test 3: a 'knitr_kable' object is output when run o "knitr_kable" ) }) + +# ---- dataset_vignette, test 4: a 'datatables' object is output when run inside pkgdown using display_vars input ---- +test_that("dataset_vignette, test 4: a 'datatables' object is output when run inside pkgdown using display_vars input", { + Sys.setenv(IN_PKGDOWN = "true") + on.exit(Sys.setenv(IN_PKGDOWN = "")) + expect_s3_class(dataset_vignette(head(admiral_dm), display_vars = vars(STUDYID, USUBJID)), + "datatables" + ) +}) From 5e590138951e4bf7ee06e81df573242a9a57fcdf Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 20:28:51 +0200 Subject: [PATCH 041/179] added style changes --- R/get.R | 2 +- tests/testthat/test-get.R | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/get.R b/R/get.R index ac92a7a1..5f9793f2 100644 --- a/R/get.R +++ b/R/get.R @@ -67,7 +67,7 @@ get_constant_vars <- function(dataset, by_vars, ignore_vars = NULL) { #' #' get_duplicates(c("a", "a", "b", "c", "d", "d")) get_duplicates <- function(x) { - assert_that(is.atomic(x), msg="x must be an atomic vector") + assert_that(is.atomic(x), msg = "x must be an atomic vector") unique(x[duplicated(x)]) } diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index 3fe89e64..57b3ae21 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -34,17 +34,17 @@ test_that("get_constant_vars Test 2: with ignore_vars", { # get_duplicates ---- ## Test 1: x atomic vector ---- test_that("get_duplicates Test 1: x atomic vector", { - x <- c("a", "a", "b", "c", "d", "d",1,1,4) + x <- c("a", "a", "b", "c", "d", "d", 1, 1, 4) expect_equal( get_duplicates(x), - c("a","d",1) + c("a", "d", 1) ) }) ## Test 2: x not atomic vector ---- test_that("get_constant_vars Test 2: x not atomic vector", { - x <- list("a", "a", "b", "c", "d", "d",1,1,4) + x <- list("a", "a", "b", "c", "d", "d", 1, 1, 4) expect_error(get_duplicates(x) ) From e5cf8b4b7f2dc570864b01e0b4e0184f18615b73 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 20:34:37 +0200 Subject: [PATCH 042/179] styler::style_file("tests/testthat/test-get.R") --- tests/testthat/test-get.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index 57b3ae21..95cda75e 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -46,6 +46,5 @@ test_that("get_duplicates Test 1: x atomic vector", { test_that("get_constant_vars Test 2: x not atomic vector", { x <- list("a", "a", "b", "c", "d", "d", 1, 1, 4) - expect_error(get_duplicates(x) - ) + expect_error(get_duplicates(x)) }) From fe832acc0b2d25cf5d4138832ab49b7b2014ebba Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Wed, 12 Oct 2022 14:38:12 -0400 Subject: [PATCH 043/179] Reformat using admiraldev::format_test_that_file() based on Unit Test Guidance developer guide --- tests/testthat/test-dataset_vignette.R | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index e3c9c357..7b06fafc 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -1,19 +1,20 @@ library(admiral.test) -# ---- dataset_vignette, test 1: a 'knitr_kable' object is output when run outside pkgdown ---- -test_that("dataset_vignette, test 1: a 'knitr_kable' object is output when run outside pkgdown", { +#dataset_vignette +# ---- dataset_vignette, test 1: A 'knitr_kable' object is output when run outside pkgdown ---- +test_that("dataset_vignette, test 1: A 'knitr_kable' object is output when run outside pkgdown", { expect_s3_class(dataset_vignette(head(admiral_dm)), "knitr_kable") }) -# ---- dataset_vignette, test 2: a 'datatables' object is output when run inside pkgdown ---- -test_that("dataset_vignette, test 2: a 'datatables' object is output when run inside pkgdown", { +# ---- dataset_vignette, test 2: A 'datatables' object is output when run inside pkgdown ---- +test_that("dataset_vignette, test 2: A 'datatables' object is output when run inside pkgdown", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class(dataset_vignette(head(admiral_dm)), "datatables") }) -# ---- dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown using display_vars input ---- -test_that("dataset_vignette, test 3: a 'knitr_kable' object is output when run outside pkgdown using display_vars input", { +# ---- dataset_vignette, test 3: A 'knitr_kable' object is output when run outside pkgdown using display_vars input ---- +test_that("dataset_vignette, test 3: A 'knitr_kable' object is output when run outside pkgdown using display_vars input", { Sys.setenv(IN_PKGDOWN = "false") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class( @@ -22,11 +23,11 @@ test_that("dataset_vignette, test 3: a 'knitr_kable' object is output when run o ) }) -# ---- dataset_vignette, test 4: a 'datatables' object is output when run inside pkgdown using display_vars input ---- -test_that("dataset_vignette, test 4: a 'datatables' object is output when run inside pkgdown using display_vars input", { +# ---- dataset_vignette, test 4: A 'datatables' object is output when run inside pkgdown using display_vars input ---- +test_that("dataset_vignette, test 4: A 'datatables' object is output when run inside pkgdown using display_vars input", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class(dataset_vignette(head(admiral_dm), display_vars = vars(STUDYID, USUBJID)), - "datatables" + "datatables" ) }) From 60a4dbfed4073c3568f57061f77b7fec6acff5c1 Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Wed, 12 Oct 2022 14:59:33 -0400 Subject: [PATCH 044/179] Match formating from previous test 3 --- tests/testthat/test-dataset_vignette.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index 7b06fafc..911e5331 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -27,7 +27,8 @@ test_that("dataset_vignette, test 3: A 'knitr_kable' object is output when run o test_that("dataset_vignette, test 4: A 'datatables' object is output when run inside pkgdown using display_vars input", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) - expect_s3_class(dataset_vignette(head(admiral_dm), display_vars = vars(STUDYID, USUBJID)), - "datatables" + expect_s3_class( + dataset_vignette(head(admiral_dm), display_vars = vars(STUDYID, USUBJID)), + "datatables" ) }) From 71f205e85f0674bc3fcdddf58982feda1408aab2 Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Wed, 12 Oct 2022 15:09:12 -0400 Subject: [PATCH 045/179] run styler::style_file() --- tests/testthat/test-dataset_vignette.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index 911e5331..521e203c 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -1,6 +1,6 @@ library(admiral.test) -#dataset_vignette +# dataset_vignette # ---- dataset_vignette, test 1: A 'knitr_kable' object is output when run outside pkgdown ---- test_that("dataset_vignette, test 1: A 'knitr_kable' object is output when run outside pkgdown", { expect_s3_class(dataset_vignette(head(admiral_dm)), "knitr_kable") From 803bf2ad1b2dc592a98d48d608f4d1d8546e485f Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Wed, 12 Oct 2022 16:04:57 -0400 Subject: [PATCH 046/179] Address lintr::lint_package() warning/error --- tests/testthat/test-dataset_vignette.R | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index 521e203c..33ccfdc2 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -1,20 +1,20 @@ library(admiral.test) # dataset_vignette -# ---- dataset_vignette, test 1: A 'knitr_kable' object is output when run outside pkgdown ---- -test_that("dataset_vignette, test 1: A 'knitr_kable' object is output when run outside pkgdown", { +## Test 1: A 'knitr_kable' object is output when run outside pkgdown ---- +test_that("Test 1: A 'knitr_kable' object is output when run outside pkgdown", { expect_s3_class(dataset_vignette(head(admiral_dm)), "knitr_kable") }) -# ---- dataset_vignette, test 2: A 'datatables' object is output when run inside pkgdown ---- -test_that("dataset_vignette, test 2: A 'datatables' object is output when run inside pkgdown", { +## Test 2: A 'datatables' object is output when run inside pkgdown ---- +test_that("Test 2: A 'datatables' object is output when run inside pkgdown", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class(dataset_vignette(head(admiral_dm)), "datatables") }) -# ---- dataset_vignette, test 3: A 'knitr_kable' object is output when run outside pkgdown using display_vars input ---- -test_that("dataset_vignette, test 3: A 'knitr_kable' object is output when run outside pkgdown using display_vars input", { +## Test 3: A 'knitr_kable' object is output when run outside pkgdown with display_vars ---- +test_that("Test 3: A 'knitr_kable' object is output when run outside pkgdown with display_vars", { Sys.setenv(IN_PKGDOWN = "false") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class( @@ -23,8 +23,8 @@ test_that("dataset_vignette, test 3: A 'knitr_kable' object is output when run o ) }) -# ---- dataset_vignette, test 4: A 'datatables' object is output when run inside pkgdown using display_vars input ---- -test_that("dataset_vignette, test 4: A 'datatables' object is output when run inside pkgdown using display_vars input", { +## Test 4: A 'datatables' object is output when run inside pkgdown with display_vars ---- +test_that("Test 4: A 'datatables' object is output when run inside pkgdown with display_vars", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) expect_s3_class( From 2075443d3e1e5eee4469ea7ae695cefff4601f64 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 22:38:19 +0200 Subject: [PATCH 047/179] updated link --- vignettes/programming_strategy.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index c06ef774..543c4810 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -223,7 +223,7 @@ and, if there’s an invalid input, the function should stop immediately with an An exception is the case where a variable to be added by a function already exists in the input dataset: here only a warning should be displayed and the function should continue executing. -Inputs should be checked either using `asserthat::assert_that()` or custom assertion functions defined in [`R/assertions.R`](https://github.com/pharmaverse/admiral/blob/main/R/assertions.R). +Inputs should be checked either using `asserthat::assert_that()` or custom assertion functions defined in [`R/assertions.R`](https://github.com/pharmaverse/admiraldev/blob/main/R/assertions.R). These custom assertion functions should either return an error in case of an invalid input or return nothing. For the most common types of input parameters like a single variable, a list of From 30893356d4a3efbaff7d0a9c0ab7826ccb06c65d Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 22:39:04 +0200 Subject: [PATCH 048/179] auto_file --- docs/pkgdown.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index c93d3744..1714a7a3 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,4 +1,4 @@ -pandoc: 2.17.1.1 +pandoc: 2.11.4 pkgdown: 2.0.3 pkgdown_sha: ~ articles: @@ -10,8 +10,8 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-05T14:22Z +last_built: 2022-10-12T20:31Z urls: - reference: https://pharmaverse.github.io/admiraldev/devel/reference - article: https://pharmaverse.github.io/admiraldev/devel/articles + reference: https://pharmaverse.github.io/admiraldev/reference + article: https://pharmaverse.github.io/admiraldev/articles From 28fc19e5355b1f0432de8ea218aa488136b0e74c Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 23:42:23 +0200 Subject: [PATCH 049/179] links updated for admiraldev/devel --- docs/pkgdown.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 1714a7a3..62546e56 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -12,6 +12,6 @@ articles: writing_vignettes: writing_vignettes.html last_built: 2022-10-12T20:31Z urls: - reference: https://pharmaverse.github.io/admiraldev/reference - article: https://pharmaverse.github.io/admiraldev/articles + reference: https://pharmaverse.github.io/admiraldev/devel/reference/ + article: https://pharmaverse.github.io/admiraldev/devel/articles/ From 9fdb29226c9c6fe9f5a297c12da31ca45302b0ad Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Wed, 12 Oct 2022 23:53:01 +0200 Subject: [PATCH 050/179] added assert_atomic_vector function and used in get.R --- R/assertions.R | 42 ++++++++++++++++++++++++++++++++++++++++++ R/get.R | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/R/assertions.R b/R/assertions.R index 550c4fb2..4bb4c646 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -656,6 +656,48 @@ assert_numeric_vector <- function(arg, optional = FALSE) { } } +#' Is an Argument an Atomic Vector? +#' +#' Checks if an argument is an atomic vector +#' +#' @param arg A function argument to be checked +#' @param optional Is the checked parameter optional? If set to `FALSE` and `arg` +#' is `NULL` then an error is thrown +#' +#' @author Ania Golab +#' +#' @return +#' The function throws an error if `arg` is not an atomic vector. +#' Otherwise, the input is returned invisibly. +#' +#' @export +#' +#' @keywords assertion +#' @family assertion +#' @examples +#' example_fun <- function(x) { +#' assert_atomic_vector(x) +#' } +#' +#' example_fun(1:10) +#' +#' try(example_fun(list(1,2))) +assert_atomic_vector <- function(arg, optional = FALSE) { + assert_logical_scalar(optional) + + if (optional && is.null(arg)) { + return(invisible(arg)) + } + + if (!is.atomic(arg)) { + err_msg <- sprintf( + "`%s` must be an atomic vector but is %s", + arg_name(substitute(arg)), + what_is_it(arg) + ) + abort(err_msg) + } +} #' Is an Argument an Object of a Specific S3 Class? #' diff --git a/R/get.R b/R/get.R index 5f9793f2..eb288bdb 100644 --- a/R/get.R +++ b/R/get.R @@ -67,7 +67,7 @@ get_constant_vars <- function(dataset, by_vars, ignore_vars = NULL) { #' #' get_duplicates(c("a", "a", "b", "c", "d", "d")) get_duplicates <- function(x) { - assert_that(is.atomic(x), msg = "x must be an atomic vector") + assert_atomic_vector(x) unique(x[duplicated(x)]) } From 6c3dbc658409066f24063855944f649ac7896b9f Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Thu, 13 Oct 2022 00:09:32 +0200 Subject: [PATCH 051/179] styling comma --- R/assertions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/assertions.R b/R/assertions.R index 4bb4c646..55cacee0 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -681,7 +681,7 @@ assert_numeric_vector <- function(arg, optional = FALSE) { #' #' example_fun(1:10) #' -#' try(example_fun(list(1,2))) +#' try(example_fun(list(1, 2))) assert_atomic_vector <- function(arg, optional = FALSE) { assert_logical_scalar(optional) From bb5aad3ec2e98d22468b222b17f7d1170c875432 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Thu, 13 Oct 2022 00:20:12 +0200 Subject: [PATCH 052/179] man for assert_atomic_vector --- NAMESPACE | 1 + man/assert_atomic_vector.Rd | 60 ++++++++++++++++++++++++++++++ man/assert_character_scalar.Rd | 1 + man/assert_character_vector.Rd | 1 + man/assert_data_frame.Rd | 1 + man/assert_expr.Rd | 1 + man/assert_filter_cond.Rd | 1 + man/assert_function.Rd | 1 + man/assert_function_param.Rd | 1 + man/assert_has_variables.Rd | 1 + man/assert_integer_scalar.Rd | 1 + man/assert_list_element.Rd | 1 + man/assert_list_of.Rd | 1 + man/assert_logical_scalar.Rd | 1 + man/assert_named_exprs.Rd | 1 + man/assert_numeric_vector.Rd | 1 + man/assert_one_to_one.Rd | 1 + man/assert_order_vars.Rd | 1 + man/assert_param_does_not_exist.Rd | 1 + man/assert_s3_class.Rd | 1 + man/assert_symbol.Rd | 1 + man/assert_unit.Rd | 1 + man/assert_vars.Rd | 1 + man/assert_varval_list.Rd | 1 + 24 files changed, 83 insertions(+) create mode 100644 man/assert_atomic_vector.Rd diff --git a/NAMESPACE b/NAMESPACE index 7678668e..b5fa3946 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,6 +6,7 @@ export(add_suffix_to_vars) export(anti_join) export(arg_name) export(as_name) +export(assert_atomic_vector) export(assert_character_scalar) export(assert_character_vector) export(assert_data_frame) diff --git a/man/assert_atomic_vector.Rd b/man/assert_atomic_vector.Rd new file mode 100644 index 00000000..3cac18a5 --- /dev/null +++ b/man/assert_atomic_vector.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/assertions.R +\name{assert_atomic_vector} +\alias{assert_atomic_vector} +\title{Is an Argument an Atomic Vector?} +\usage{ +assert_atomic_vector(arg, optional = FALSE) +} +\arguments{ +\item{arg}{A function argument to be checked} + +\item{optional}{Is the checked parameter optional? If set to \code{FALSE} and \code{arg} +is \code{NULL} then an error is thrown} +} +\value{ +The function throws an error if \code{arg} is not an atomic vector. +Otherwise, the input is returned invisibly. +} +\description{ +Checks if an argument is an atomic vector +} +\examples{ +example_fun <- function(x) { + assert_atomic_vector(x) +} + +example_fun(1:10) + +try(example_fun(list(1, 2))) +} +\seealso{ +Checks for valid input and returns warning or errors messages: +\code{\link{assert_character_scalar}()}, +\code{\link{assert_character_vector}()}, +\code{\link{assert_data_frame}()}, +\code{\link{assert_expr}()}, +\code{\link{assert_filter_cond}()}, +\code{\link{assert_function_param}()}, +\code{\link{assert_function}()}, +\code{\link{assert_has_variables}()}, +\code{\link{assert_integer_scalar}()}, +\code{\link{assert_list_element}()}, +\code{\link{assert_list_of}()}, +\code{\link{assert_logical_scalar}()}, +\code{\link{assert_named_exprs}()}, +\code{\link{assert_numeric_vector}()}, +\code{\link{assert_one_to_one}()}, +\code{\link{assert_order_vars}()}, +\code{\link{assert_param_does_not_exist}()}, +\code{\link{assert_s3_class}()}, +\code{\link{assert_symbol}()}, +\code{\link{assert_unit}()}, +\code{\link{assert_vars}()}, +\code{\link{assert_varval_list}()} +} +\author{ +Ania Golab +} +\concept{assertion} +\keyword{assertion} diff --git a/man/assert_character_scalar.Rd b/man/assert_character_scalar.Rd index 06e7c7e1..f1fda796 100644 --- a/man/assert_character_scalar.Rd +++ b/man/assert_character_scalar.Rd @@ -59,6 +59,7 @@ example_fun2("Warning") } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, \code{\link{assert_expr}()}, diff --git a/man/assert_character_vector.Rd b/man/assert_character_vector.Rd index eb8405a8..bf138fbc 100644 --- a/man/assert_character_vector.Rd +++ b/man/assert_character_vector.Rd @@ -33,6 +33,7 @@ try(example_fun(1:10)) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_data_frame}()}, \code{\link{assert_expr}()}, diff --git a/man/assert_data_frame.Rd b/man/assert_data_frame.Rd index 8147f1b0..e55d0f1d 100644 --- a/man/assert_data_frame.Rd +++ b/man/assert_data_frame.Rd @@ -46,6 +46,7 @@ try(example_fun("Not a dataset")) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_expr}()}, diff --git a/man/assert_expr.Rd b/man/assert_expr.Rd index 143fd8c6..b391e381 100644 --- a/man/assert_expr.Rd +++ b/man/assert_expr.Rd @@ -21,6 +21,7 @@ Assert Argument is an Expression } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_filter_cond.Rd b/man/assert_filter_cond.Rd index a8a38864..f7f184fa 100644 --- a/man/assert_filter_cond.Rd +++ b/man/assert_filter_cond.Rd @@ -38,6 +38,7 @@ try(example_fun(admiral_dm, USUBJID)) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_function.Rd b/man/assert_function.Rd index edf3424b..600ee968 100644 --- a/man/assert_function.Rd +++ b/man/assert_function.Rd @@ -40,6 +40,7 @@ try(example_fun(sum)) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_function_param.Rd b/man/assert_function_param.Rd index a8ecc413..9aa517c4 100644 --- a/man/assert_function_param.Rd +++ b/man/assert_function_param.Rd @@ -29,6 +29,7 @@ try(assert_function_param("hello", "surname")) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_has_variables.Rd b/man/assert_has_variables.Rd index 8c7d4363..f8d5b050 100644 --- a/man/assert_has_variables.Rd +++ b/man/assert_has_variables.Rd @@ -28,6 +28,7 @@ try(assert_has_variables(admiral_dm, "AVAL")) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_integer_scalar.Rd b/man/assert_integer_scalar.Rd index 4f5df811..f43702f5 100644 --- a/man/assert_integer_scalar.Rd +++ b/man/assert_integer_scalar.Rd @@ -38,6 +38,7 @@ try(example_fun("2", 0)) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_list_element.Rd b/man/assert_list_element.Rd index da7423db..236e00f1 100644 --- a/man/assert_list_element.Rd +++ b/man/assert_list_element.Rd @@ -41,6 +41,7 @@ fulfilling the condition are listed. } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_list_of.Rd b/man/assert_list_of.Rd index 4c301055..febd59f3 100644 --- a/man/assert_list_of.Rd +++ b/man/assert_list_of.Rd @@ -35,6 +35,7 @@ try(example_fun(c(TRUE, FALSE))) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_logical_scalar.Rd b/man/assert_logical_scalar.Rd index c0c2ccdf..ad49dedd 100644 --- a/man/assert_logical_scalar.Rd +++ b/man/assert_logical_scalar.Rd @@ -36,6 +36,7 @@ try(example_fun(1:10)) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_named_exprs.Rd b/man/assert_named_exprs.Rd index da0860e7..a39e34df 100644 --- a/man/assert_named_exprs.Rd +++ b/man/assert_named_exprs.Rd @@ -21,6 +21,7 @@ Assert Argument is a Named List of Expressions } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_numeric_vector.Rd b/man/assert_numeric_vector.Rd index 2ef5b08c..11911305 100644 --- a/man/assert_numeric_vector.Rd +++ b/man/assert_numeric_vector.Rd @@ -30,6 +30,7 @@ try(example_fun(letters)) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_one_to_one.Rd b/man/assert_one_to_one.Rd index f3390305..bd47f32d 100644 --- a/man/assert_one_to_one.Rd +++ b/man/assert_one_to_one.Rd @@ -23,6 +23,7 @@ Checks if there is a one to one mapping between two lists of variables. } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_order_vars.Rd b/man/assert_order_vars.Rd index 30f762d1..822b672a 100644 --- a/man/assert_order_vars.Rd +++ b/man/assert_order_vars.Rd @@ -35,6 +35,7 @@ try(example_fun(vars(USUBJID, toupper(PARAMCD), -AVAL))) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_param_does_not_exist.Rd b/man/assert_param_does_not_exist.Rd index 019ef509..96cba648 100644 --- a/man/assert_param_does_not_exist.Rd +++ b/man/assert_param_does_not_exist.Rd @@ -30,6 +30,7 @@ try(assert_param_does_not_exist(advs, param = "WEIGHT")) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_s3_class.Rd b/man/assert_s3_class.Rd index 577e4960..05e8c639 100644 --- a/man/assert_s3_class.Rd +++ b/man/assert_s3_class.Rd @@ -34,6 +34,7 @@ try(example_fun(1:10)) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_symbol.Rd b/man/assert_symbol.Rd index ac228057..16e9b7de 100644 --- a/man/assert_symbol.Rd +++ b/man/assert_symbol.Rd @@ -38,6 +38,7 @@ try(example_fun(admiral_dm, toupper(PARAMCD))) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_unit.Rd b/man/assert_unit.Rd index e6c04a48..7ec79ca7 100644 --- a/man/assert_unit.Rd +++ b/man/assert_unit.Rd @@ -36,6 +36,7 @@ assert_unit(advs, param = "WEIGHT", required_unit = "kg", get_unit_expr = VSSTRE } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index b9fb012c..f4727797 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -45,6 +45,7 @@ try(example_fun_name(vars(APERSDT = APxxSDT, APxxEDT))) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, diff --git a/man/assert_varval_list.Rd b/man/assert_varval_list.Rd index 18f4d165..c6825148 100644 --- a/man/assert_varval_list.Rd +++ b/man/assert_varval_list.Rd @@ -44,6 +44,7 @@ try(example_fun(vars("AE", DTSEQ = AESEQ))) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, From 35fb8e1c6896c70b39b74ae69fdd94bac6cbc2f2 Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Thu, 13 Oct 2022 10:09:20 -0400 Subject: [PATCH 053/179] clean up formatting and remove admiral.test dependency --- tests/testthat/test-dataset_vignette.R | 49 ++++++++++++++++++-------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index 33ccfdc2..c4e62bfb 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -1,34 +1,53 @@ -library(admiral.test) - -# dataset_vignette +# dataset_vignette ---- ## Test 1: A 'knitr_kable' object is output when run outside pkgdown ---- -test_that("Test 1: A 'knitr_kable' object is output when run outside pkgdown", { - expect_s3_class(dataset_vignette(head(admiral_dm)), "knitr_kable") +test_that("dataset_vignette Test 1: A 'knitr_kable' object is output when run outside pkgdown", { + dm <- tibble( + STUDYID = rep("1001", 100), + USUBJID = as.character(1:100), + COUNTRY = rep("USA", 100) + ) + + expect_s3_class(dataset_vignette(dm), "knitr_kable") }) ## Test 2: A 'datatables' object is output when run inside pkgdown ---- -test_that("Test 2: A 'datatables' object is output when run inside pkgdown", { +test_that("dataset_vignette Test 2: A 'datatables' object is output when run inside pkgdown", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) - expect_s3_class(dataset_vignette(head(admiral_dm)), "datatables") + + dm <- tibble( + STUDYID = rep("1001", 100), + USUBJID = as.character(1:100), + COUNTRY = rep("USA", 100) + ) + + expect_s3_class(dataset_vignette(dm), "datatables") }) ## Test 3: A 'knitr_kable' object is output when run outside pkgdown with display_vars ---- -test_that("Test 3: A 'knitr_kable' object is output when run outside pkgdown with display_vars", { +test_that("dataset_vignette Test 3: A 'knitr_kable' object is output when run outside pkgdown with display_vars", { Sys.setenv(IN_PKGDOWN = "false") on.exit(Sys.setenv(IN_PKGDOWN = "")) - expect_s3_class( - dataset_vignette(head(admiral_dm), display_vars = vars(STUDYID, USUBJID)), - "knitr_kable" + + dm <- tibble( + STUDYID = rep("1001", 100), + USUBJID = as.character(1:100), + COUNTRY = rep("USA", 100) ) + + expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "knitr_kable") }) ## Test 4: A 'datatables' object is output when run inside pkgdown with display_vars ---- -test_that("Test 4: A 'datatables' object is output when run inside pkgdown with display_vars", { +test_that("dataset_vignette Test 4: A 'datatables' object is output when run inside pkgdown with display_vars", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) - expect_s3_class( - dataset_vignette(head(admiral_dm), display_vars = vars(STUDYID, USUBJID)), - "datatables" + + dm <- tibble( + STUDYID = rep("1001", 100), + USUBJID = as.character(1:100), + COUNTRY = rep("USA", 100) ) + + expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "datatables") }) From 34eb54d64aae0db1f8a3ab28498b30121042038e Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Thu, 13 Oct 2022 11:28:37 -0400 Subject: [PATCH 054/179] Fix formatting, use tibble::tribble() for dummy data, and add test for error when calling column not in dataset. --- tests/testthat/test-dataset_vignette.R | 59 ++++++++++++-------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index c4e62bfb..702b0209 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -1,13 +1,19 @@ # dataset_vignette ---- ## Test 1: A 'knitr_kable' object is output when run outside pkgdown ---- test_that("dataset_vignette Test 1: A 'knitr_kable' object is output when run outside pkgdown", { - dm <- tibble( - STUDYID = rep("1001", 100), - USUBJID = as.character(1:100), - COUNTRY = rep("USA", 100) + Sys.setenv(IN_PKGDOWN = "false") + on.exit(Sys.setenv(IN_PKGDOWN = "")) + + dm <- tibble::tribble( + ~STUDYID, ~USUBJID, ~COUNTRY, + "STUDY1", "1", "USA", + "STUDY1", "2", "USA", + "STUDY1", "3", "USA", + "STUDY1", "4", "USA" ) expect_s3_class(dataset_vignette(dm), "knitr_kable") + expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "knitr_kable") }) ## Test 2: A 'datatables' object is output when run inside pkgdown ---- @@ -15,39 +21,28 @@ test_that("dataset_vignette Test 2: A 'datatables' object is output when run ins Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) - dm <- tibble( - STUDYID = rep("1001", 100), - USUBJID = as.character(1:100), - COUNTRY = rep("USA", 100) + dm <- tibble::tribble( + ~STUDYID, ~USUBJID, ~COUNTRY, + "STUDY1", "1", "USA", + "STUDY1", "2", "USA", + "STUDY1", "3", "USA", + "STUDY1", "4", "USA" ) - expect_s3_class(dataset_vignette(dm), "datatables") -}) - -## Test 3: A 'knitr_kable' object is output when run outside pkgdown with display_vars ---- -test_that("dataset_vignette Test 3: A 'knitr_kable' object is output when run outside pkgdown with display_vars", { - Sys.setenv(IN_PKGDOWN = "false") - on.exit(Sys.setenv(IN_PKGDOWN = "")) - - dm <- tibble( - STUDYID = rep("1001", 100), - USUBJID = as.character(1:100), - COUNTRY = rep("USA", 100) - ) - expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "knitr_kable") + expect_s3_class(dataset_vignette(dm), "datatables") + expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "datatables") }) -## Test 4: A 'datatables' object is output when run inside pkgdown with display_vars ---- -test_that("dataset_vignette Test 4: A 'datatables' object is output when run inside pkgdown with display_vars", { - Sys.setenv(IN_PKGDOWN = "true") - on.exit(Sys.setenv(IN_PKGDOWN = "")) - - dm <- tibble( - STUDYID = rep("1001", 100), - USUBJID = as.character(1:100), - COUNTRY = rep("USA", 100) +## Test 3: An error is output when calling variable not in dataset ---- +test_that("dataset_vignette Test 3: An error is output when calling variable not in dataset", { + dm <- tibble::tribble( + ~STUDYID, ~USUBJID, ~COUNTRY, + "STUDY1", "1", "USA", + "STUDY1", "2", "USA", + "STUDY1", "3", "USA", + "STUDY1", "4", "USA" ) - expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "datatables") + expect_error(dataset_vignette(dm, display_vars = vars(AGE))) }) From 6246747654d71463d7aa0865558ad91e51905480 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Thu, 13 Oct 2022 18:04:48 +0200 Subject: [PATCH 055/179] moved the test to test-assertions as per Thomas' suggestion --- tests/testthat/test-assertions.R | 9 +++++++++ tests/testthat/test-get.R | 7 ------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index c0c60904..1592fe14 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -150,3 +150,12 @@ test_that("assert_order_vars Test 13: returns errors if used incorrectly", { expect_error(assert_order_vars(c("USUBJID", "PARAMCD", "VISIT"))) expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) }) + + +# assert_atomic_vector ---- +## Test 14: error if input is not atomic vector ---- +test_that("assert_atomic_vector Test 14: error if input is not atomic vector", { + x <- list("a", "a", "b", "c", "d", "d", 1, 1, 4) + + expect_error(assert_atomic_vector(x)) +}) diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index 95cda75e..8b203dfb 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -41,10 +41,3 @@ test_that("get_duplicates Test 1: x atomic vector", { c("a", "d", 1) ) }) - -## Test 2: x not atomic vector ---- -test_that("get_constant_vars Test 2: x not atomic vector", { - x <- list("a", "a", "b", "c", "d", "d", 1, 1, 4) - - expect_error(get_duplicates(x)) -}) From c1ec6dd3f4d0ec162f30bdf11067f8f8d183642b Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Thu, 13 Oct 2022 13:06:40 -0400 Subject: [PATCH 056/179] #96 - grammar fix output to outputted --- tests/testthat/test-dataset_vignette.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/testthat/test-dataset_vignette.R b/tests/testthat/test-dataset_vignette.R index 702b0209..03806c6c 100644 --- a/tests/testthat/test-dataset_vignette.R +++ b/tests/testthat/test-dataset_vignette.R @@ -1,6 +1,6 @@ # dataset_vignette ---- -## Test 1: A 'knitr_kable' object is output when run outside pkgdown ---- -test_that("dataset_vignette Test 1: A 'knitr_kable' object is output when run outside pkgdown", { +## Test 1: A 'knitr_kable' object is outputted when run outside pkgdown ---- +test_that("dataset_vignette Test 1: A 'knitr_kable' object is outputted when run outside pkgdown", { Sys.setenv(IN_PKGDOWN = "false") on.exit(Sys.setenv(IN_PKGDOWN = "")) @@ -16,8 +16,8 @@ test_that("dataset_vignette Test 1: A 'knitr_kable' object is output when run ou expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "knitr_kable") }) -## Test 2: A 'datatables' object is output when run inside pkgdown ---- -test_that("dataset_vignette Test 2: A 'datatables' object is output when run inside pkgdown", { +## Test 2: A 'datatables' object is outputted when run inside pkgdown ---- +test_that("dataset_vignette Test 2: A 'datatables' object is outputted when run inside pkgdown", { Sys.setenv(IN_PKGDOWN = "true") on.exit(Sys.setenv(IN_PKGDOWN = "")) @@ -34,8 +34,8 @@ test_that("dataset_vignette Test 2: A 'datatables' object is output when run ins expect_s3_class(dataset_vignette(dm, display_vars = vars(STUDYID, USUBJID)), "datatables") }) -## Test 3: An error is output when calling variable not in dataset ---- -test_that("dataset_vignette Test 3: An error is output when calling variable not in dataset", { +## Test 3: An error is outputted when calling variable not in dataset ---- +test_that("dataset_vignette Test 3: An error is outputted when calling variable not in dataset", { dm <- tibble::tribble( ~STUDYID, ~USUBJID, ~COUNTRY, "STUDY1", "1", "USA", From f3614bbbbfe895d55e0504469d2d4992d4f5b0f2 Mon Sep 17 00:00:00 2001 From: zdz2101 Date: Fri, 14 Oct 2022 14:36:07 -0400 Subject: [PATCH 057/179] #59 - Initial draft of doc update --- vignettes/programming_strategy.Rmd | 3 ++- vignettes/unit_test_guidance.Rmd | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 543c4810..b1cb1935 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -330,7 +330,8 @@ Any newly added variable(-s) should be mentioned here. * `@examples`: A fully self-contained example of how to use the function. Self-contained means that, if this code is executed in a new R session, it will run without errors. That means any packages need to be loaded with `library()` and any datasets needed either to be created directly inside the example code or loaded using `data()`. -If a dataset is created in the example, it should be done so using the function `tribble()` (specify `library(tibble)` before calling this function). +If a dataset is created in the example, it should be done so using the function `tribble()` (specify `library(tibble)` before calling this function). +If functions are called in the example, please specify `library(pkg_name)` then refer to the respective function `fun()`, instead of using `pkg_name::fun()` notation. Make sure to align columns as this ensures quick code readability. Copying descriptions should be avoided as it makes the documentation hard to diff --git a/vignettes/unit_test_guidance.Rmd b/vignettes/unit_test_guidance.Rmd index ecbaf945..a4d5acfe 100644 --- a/vignettes/unit_test_guidance.Rmd +++ b/vignettes/unit_test_guidance.Rmd @@ -182,7 +182,7 @@ The input and expected output for the unit tests must follow the following rules * Values should be hard-coded whenever possible. * If values need to be derived, only unit tested functions can be used. -If a dataset needs to be created for testing purpose, it should be done so using the function `tribble()` from the `tibble` package with the following command `tibble::tribble()`. +If a dataset needs to be created for testing purpose, it should be done so using the function `tribble()` from the `tibble` package with the following command `tibble::tribble()`. If additional functions from other packages need to be called, it should be done using the notation `pkg_name::fun()`. Make sure to align columns as well. This ensures quick code readability. Ensure you give a meaningful explanation of the test in the testthat call, as From 019777e18e189f5685c1012fdf251ec435d19155 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Mon, 17 Oct 2022 14:25:28 +0200 Subject: [PATCH 058/179] 126_create_aux: update programming strategy --- vignettes/programming_strategy.Rmd | 1 + 1 file changed, 1 insertion(+) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 543c4810..ea190ad9 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -388,6 +388,7 @@ add an issue in GitHub for discussion. |-----------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| | `com_date_time` | Date/Time Computation Functions that returns a vector | | `com_bds_findings` | BDS-Findings Functions that returns a vector | +| `create_aux` | Functions for Creating Auxiliary Datasets | | `datasets` | Example datasets used within admiral | | `der_gen` | General Derivation Functions that can be used for any ADaM. | | `der_date_time` | Date/Time Derivation Function | From 85829594723ab6adcfe2f5b32997b6e3de9445c8 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Mon, 17 Oct 2022 14:30:04 +0200 Subject: [PATCH 059/179] 126_create_aux: update Changelog --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 81e25219..a3cd9b24 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,8 @@ - Developer addin for formatting tests to admiral programming standards (#73) - New functions `replace_symbol_in_quo()` and `add_suffix_to_vars()` (#106) + + - New keyword/family `create_aux` for functions creating auxiliary datasets (#126) ## Updates of Existing Functions From 058194ce28c3c0d33b5064eee113bd490de380c9 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 17 Oct 2022 19:43:24 +0000 Subject: [PATCH 060/179] #59 - Grammar and flow edits. --- docs/pkgdown.yml | 8 ++++---- vignettes/programming_strategy.Rmd | 2 +- vignettes/unit_test_guidance.Rmd | 4 +++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index b21fb2a0..c7228064 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -1,4 +1,4 @@ -pandoc: 2.11.4 +pandoc: '2.18' pkgdown: 2.0.3 pkgdown_sha: ~ articles: @@ -10,8 +10,8 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-12T09:02Z +last_built: 2022-10-17T19:31Z urls: - reference: https://pharmaverse.github.io/admiraldev/devel/reference/ - article: https://pharmaverse.github.io/admiraldev/devel/articles/ + reference: https://pharmaverse.github.io/admiraldev/devel/reference + article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index b1cb1935..854bd073 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -331,7 +331,7 @@ Any newly added variable(-s) should be mentioned here. Self-contained means that, if this code is executed in a new R session, it will run without errors. That means any packages need to be loaded with `library()` and any datasets needed either to be created directly inside the example code or loaded using `data()`. If a dataset is created in the example, it should be done so using the function `tribble()` (specify `library(tibble)` before calling this function). -If functions are called in the example, please specify `library(pkg_name)` then refer to the respective function `fun()`, instead of using `pkg_name::fun()` notation. +If other functions are called in the example, please specify `library(pkg_name)` then refer to the respective function `fun()`, instead of `pkg_name::fun()` notation. Make sure to align columns as this ensures quick code readability. Copying descriptions should be avoided as it makes the documentation hard to diff --git a/vignettes/unit_test_guidance.Rmd b/vignettes/unit_test_guidance.Rmd index a4d5acfe..980423c6 100644 --- a/vignettes/unit_test_guidance.Rmd +++ b/vignettes/unit_test_guidance.Rmd @@ -182,7 +182,9 @@ The input and expected output for the unit tests must follow the following rules * Values should be hard-coded whenever possible. * If values need to be derived, only unit tested functions can be used. -If a dataset needs to be created for testing purpose, it should be done so using the function `tribble()` from the `tibble` package with the following command `tibble::tribble()`. If additional functions from other packages need to be called, it should be done using the notation `pkg_name::fun()`. +Test files should not include `library(pkg_name)` calls. +If a dataset needs to be created for testing purposes, it should be done so using the function `tribble()` from the `tibble` package with the following command `tibble::tribble()`. +Furthermore, if other functions need to be called, it should also be done using `pkg_name::fun()`notation. Make sure to align columns as well. This ensures quick code readability. Ensure you give a meaningful explanation of the test in the testthat call, as From 08603e07a60bfa41ac18e63d47d2bc42c63387aa Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 17 Oct 2022 20:41:03 +0000 Subject: [PATCH 061/179] #59 - Cross reference two vignettes to show difference in notation --- docs/pkgdown.yml | 2 +- vignettes/programming_strategy.Rmd | 2 +- vignettes/unit_test_guidance.Rmd | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index c7228064..cbe9ea5b 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-17T19:31Z +last_built: 2022-10-17T20:37Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 854bd073..11d8b28b 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -331,7 +331,7 @@ Any newly added variable(-s) should be mentioned here. Self-contained means that, if this code is executed in a new R session, it will run without errors. That means any packages need to be loaded with `library()` and any datasets needed either to be created directly inside the example code or loaded using `data()`. If a dataset is created in the example, it should be done so using the function `tribble()` (specify `library(tibble)` before calling this function). -If other functions are called in the example, please specify `library(pkg_name)` then refer to the respective function `fun()`, instead of `pkg_name::fun()` notation. +If other functions are called in the example, please specify `library(pkg_name)` then refer to the respective function `fun()` as opposed to the preferred `pkg_name::fun()` notation as specified in [Unit Test Guidance](unit_test_guidance.html#set-up-the-test-script). Make sure to align columns as this ensures quick code readability. Copying descriptions should be avoided as it makes the documentation hard to diff --git a/vignettes/unit_test_guidance.Rmd b/vignettes/unit_test_guidance.Rmd index 980423c6..b85a3287 100644 --- a/vignettes/unit_test_guidance.Rmd +++ b/vignettes/unit_test_guidance.Rmd @@ -182,7 +182,7 @@ The input and expected output for the unit tests must follow the following rules * Values should be hard-coded whenever possible. * If values need to be derived, only unit tested functions can be used. -Test files should not include `library(pkg_name)` calls. +In contrast to the [Programming Strategy](programming_strategy.html#function-header-documentation) documentation for function examples, test files should not include `library(pkg_name)` calls. If a dataset needs to be created for testing purposes, it should be done so using the function `tribble()` from the `tibble` package with the following command `tibble::tribble()`. Furthermore, if other functions need to be called, it should also be done using `pkg_name::fun()`notation. Make sure to align columns as well. This ensures quick code readability. From 881e98ca03d9a545726b0ea702f5debca4270e11 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Tue, 18 Oct 2022 08:16:49 +0000 Subject: [PATCH 062/179] New assert function to check date vectors --- NAMESPACE | 1 + R/assertions.R | 48 ++++++++++++++++++++++++++++++++ man/assert_date_vector.Rd | 41 +++++++++++++++++++++++++++ tests/testthat/test-assertions.R | 4 +++ 4 files changed, 94 insertions(+) create mode 100644 man/assert_date_vector.Rd diff --git a/NAMESPACE b/NAMESPACE index b50534d2..7b4b999a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -9,6 +9,7 @@ export(assert_character_scalar) export(assert_character_vector) export(assert_data_frame) export(assert_date_var) +export(assert_date_vector) export(assert_expr) export(assert_filter_cond) export(assert_function) diff --git a/R/assertions.R b/R/assertions.R index 550c4fb2..72eaece2 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1424,3 +1424,51 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) )) } } + +#' Is a Variable is a Date or Datetime Variable? +#' +#' Checks if a variable vector is a date or datetime variable +#' +#' @param var The variable to check +#' +#' @param var_name The name of the variable. If the argument is specified, the +#' specified name is displayed in the error message. +#' +#' @return +#' The function throws an error if `var` is not a date or datetime variable +#' and returns the input invisibly otherwise. +#' +#' @export +#' +#' @author Sadchla Mascary +#' +#' @keywords assertion +#' +#' @examples +#' library(tibble) +#' library(rlang) +#' +#' example_fun <- function(var) { +#' assert_date_vector(var) +#' } +#' +#' my_dates_chr <- c("2018-08-23", "2022-01-30", "1993-07-14") +#' my_dates_fmt <- as.Date(my_dates_chr, tz = "UTC") +#' +#' example_fun( +#' var = my_dates_fmt +#' ) +#' +assert_date_vector <- function(var, var_name = NULL) { + assert_character_scalar(var_name, optional = TRUE) + if (is.null(var_name)) { + var_name <- deparse(substitute(var)) + } + if (!is.instant(var)) { + abort(paste0( + var_name, + " is not a date or datetime variable but is ", + friendly_type_of(var) + )) + } +} diff --git a/man/assert_date_vector.Rd b/man/assert_date_vector.Rd new file mode 100644 index 00000000..9ea91b69 --- /dev/null +++ b/man/assert_date_vector.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/assertions.R +\name{assert_date_vector} +\alias{assert_date_vector} +\title{Is a Variable is a Date or Datetime Variable?} +\usage{ +assert_date_vector(var, var_name = NULL) +} +\arguments{ +\item{var}{The variable to check} + +\item{var_name}{The name of the variable. If the argument is specified, the +specified name is displayed in the error message.} +} +\value{ +The function throws an error if \code{var} is not a date or datetime variable +and returns the input invisibly otherwise. +} +\description{ +Checks if a variable vector is a date or datetime variable +} +\examples{ +library(tibble) +library(rlang) + +example_fun <- function(var) { + assert_date_vector(var) +} + +my_dates_chr <- c("2018-08-23", "2022-01-30", "1993-07-14") +my_dates_fmt <- as.Date(my_dates_chr, tz = "UTC") + +example_fun( + var = my_dates_fmt +) + +} +\author{ +Sadchla Mascary +} +\keyword{assertion} diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index ea5bfe8e..b08ac4d7 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -106,3 +106,7 @@ test_that("Test 11 : `assert_order_vars` returns errors if used incorrectly", { expect_error(assert_order_vars(c("USUBJID", "PARAMCD", "VISIT"))) expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) }) + +test_that("Test 12: `assert_date_vector` returns error if input vector is not a date formatted",{ + expect_error(assert_date_vector(var=c("2018-08-23", "2022-01-30", "1993-07-14"))) +}) From fd8e68c90510b7f7bd4990c27d34ae36bdea4b12 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Tue, 18 Oct 2022 18:58:02 +0000 Subject: [PATCH 063/179] Implemented the updates to the new assert function to replace assert_that(is.Date()) --- R/assertions.R | 42 ++++++++++++++++---------------- man/assert_date_vector.Rd | 29 ++++++++++------------ tests/testthat/test-assertions.R | 2 +- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 72eaece2..c7b1954f 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1429,14 +1429,17 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) #' #' Checks if a variable vector is a date or datetime variable #' -#' @param var The variable to check +#' @param arg The function argument to be checked #' -#' @param var_name The name of the variable. If the argument is specified, the -#' specified name is displayed in the error message. +#' @param var_name a character vector name of the `arg`. +#' `var_name` argument is optional. +#' +#' @param optional Is the checked parameter optional? If set to `FALSE` +#' and `arg` is `NULL` then an error is thrown. #' #' @return -#' The function throws an error if `var` is not a date or datetime variable -#' and returns the input invisibly otherwise. +#' The function returns an error if `arg` is not a date or datetime variable +#' but otherwise returns an invisible output. #' #' @export #' @@ -1445,30 +1448,27 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) #' @keywords assertion #' #' @examples -#' library(tibble) -#' library(rlang) -#' -#' example_fun <- function(var) { -#' assert_date_vector(var) +#' example_fun <- function(arg) { +#' assert_date_vector(arg) #' } #' -#' my_dates_chr <- c("2018-08-23", "2022-01-30", "1993-07-14") -#' my_dates_fmt <- as.Date(my_dates_chr, tz = "UTC") -#' #' example_fun( -#' var = my_dates_fmt +#' as.Date("2022-01-30", tz = "UTC") #' ) -#' -assert_date_vector <- function(var, var_name = NULL) { - assert_character_scalar(var_name, optional = TRUE) +#' try(example_fun(c("2018-08-23", "2022-01-30", "1993-07-14"))) +assert_date_vector <- function(arg, var_name=NULL, optional=FALSE) { + assert_character_vector(var_name, optional = TRUE) + assert_logical_scalar(optional) + if (is.null(var_name)) { - var_name <- deparse(substitute(var)) + var_name <-arg_name(substitute(arg)) } - if (!is.instant(var)) { + + if (!is.instant(arg)) { abort(paste0( var_name, - " is not a date or datetime variable but is ", - friendly_type_of(var) + " must be a date or datetime variable but it's ", + friendly_type_of(arg) )) } } diff --git a/man/assert_date_vector.Rd b/man/assert_date_vector.Rd index 9ea91b69..0db76a66 100644 --- a/man/assert_date_vector.Rd +++ b/man/assert_date_vector.Rd @@ -4,36 +4,33 @@ \alias{assert_date_vector} \title{Is a Variable is a Date or Datetime Variable?} \usage{ -assert_date_vector(var, var_name = NULL) +assert_date_vector(arg, var_name = NULL, optional = FALSE) } \arguments{ -\item{var}{The variable to check} +\item{arg}{The function argument to be checked} -\item{var_name}{The name of the variable. If the argument is specified, the -specified name is displayed in the error message.} +\item{var_name}{a character vector name of the \code{arg}. +\code{var_name} argument is optional.} + +\item{optional}{Is the checked parameter optional? If set to \code{FALSE} +and \code{arg} is \code{NULL} then an error is thrown.} } \value{ -The function throws an error if \code{var} is not a date or datetime variable -and returns the input invisibly otherwise. +The function returns an error if \code{arg} is not a date or datetime variable +but otherwise returns an invisible output. } \description{ Checks if a variable vector is a date or datetime variable } \examples{ -library(tibble) -library(rlang) - -example_fun <- function(var) { - assert_date_vector(var) +example_fun <- function(arg) { + assert_date_vector(arg) } -my_dates_chr <- c("2018-08-23", "2022-01-30", "1993-07-14") -my_dates_fmt <- as.Date(my_dates_chr, tz = "UTC") - example_fun( - var = my_dates_fmt + as.Date("2022-01-30", tz = "UTC") ) - +try(example_fun(c("2018-08-23", "2022-01-30", "1993-07-14"))) } \author{ Sadchla Mascary diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index b08ac4d7..13b22f12 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -108,5 +108,5 @@ test_that("Test 11 : `assert_order_vars` returns errors if used incorrectly", { }) test_that("Test 12: `assert_date_vector` returns error if input vector is not a date formatted",{ - expect_error(assert_date_vector(var=c("2018-08-23", "2022-01-30", "1993-07-14"))) + expect_error(assert_date_vector("2018-08-23")) }) From 75bdd1b80225a1dad630cbd5d977418537626717 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Tue, 18 Oct 2022 19:11:54 +0000 Subject: [PATCH 064/179] Styling updates --- R/assertions.R | 6 +++--- man/assert_date_vector.Rd | 2 +- tests/testthat/test-assertions.R | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index c7b1954f..db79c1bb 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1453,15 +1453,15 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) #' } #' #' example_fun( -#' as.Date("2022-01-30", tz = "UTC") +#' as.Date("2022-01-30", tz = "UTC") #' ) #' try(example_fun(c("2018-08-23", "2022-01-30", "1993-07-14"))) -assert_date_vector <- function(arg, var_name=NULL, optional=FALSE) { +assert_date_vector <- function(arg, var_name = NULL, optional = FALSE) { assert_character_vector(var_name, optional = TRUE) assert_logical_scalar(optional) if (is.null(var_name)) { - var_name <-arg_name(substitute(arg)) + var_name <- arg_name(substitute(arg)) } if (!is.instant(arg)) { diff --git a/man/assert_date_vector.Rd b/man/assert_date_vector.Rd index 0db76a66..6d2b7e76 100644 --- a/man/assert_date_vector.Rd +++ b/man/assert_date_vector.Rd @@ -28,7 +28,7 @@ example_fun <- function(arg) { } example_fun( - as.Date("2022-01-30", tz = "UTC") + as.Date("2022-01-30", tz = "UTC") ) try(example_fun(c("2018-08-23", "2022-01-30", "1993-07-14"))) } diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 13b22f12..56f61cf6 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -107,6 +107,6 @@ test_that("Test 11 : `assert_order_vars` returns errors if used incorrectly", { expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) }) -test_that("Test 12: `assert_date_vector` returns error if input vector is not a date formatted",{ +test_that("Test 12: `assert_date_vector` returns error if input vector is not a date formatted", { expect_error(assert_date_vector("2018-08-23")) }) From 852e1c1cea134a422690ffb64a077588392fe7cb Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Wed, 19 Oct 2022 16:15:07 +0000 Subject: [PATCH 065/179] Closes 128 Fixed typo add admiraldev inst and added code to match r-cmd-check.yml --- vignettes/pr_review_guidance.Rmd | 118 ++++++++++++++----------------- 1 file changed, 53 insertions(+), 65 deletions(-) diff --git a/vignettes/pr_review_guidance.Rmd b/vignettes/pr_review_guidance.Rmd index 6742ed4d..16ee32d5 100644 --- a/vignettes/pr_review_guidance.Rmd +++ b/vignettes/pr_review_guidance.Rmd @@ -21,53 +21,49 @@ knitr::opts_chunk$set( This document is intended to be guidance for creators and reviewers of pull requests (PRs) in the `{admiral}` package. PR authors will benefit from shorter review times by closely following the guidance provided here. -A pull request into the `devel` branch signifies that an issue that has been "addressed". This issue might be a bug, a feature request or a documentation update. For transparency, we keep the issue open until the `devel` branch is merged into the `main` branch, which usually coincides with a release of `{admiral}` to CRAN. This ensures that repeat issues are not raised and if they are raised are quickly marked as duplicates and closed. +A pull request into the `devel` branch signifies that an issue that has been "addressed". This issue might be a bug, a feature request or a documentation update. For transparency, we keep the issue open until the `devel` branch is merged into the `main` branch, which usually coincides with a release of `{admiral}` to CRAN. This ensures that repeat issues are not raised and if they are raised are quickly marked as duplicates and closed. -Closely following the below guidance will ensure that our all our "addressed" issues auto-close once we merge `devel` into `main`. +Closely following the below guidance will ensure that our all our "addressed" issues auto-close once we merge `devel` into `main`. # Review Criteria -For a pull request to be merged into `devel` it needs to pass the automated `R CMD check`, `lintr`, and `task-list-completed` workflows on GitHub at a minimum. The first two checks can be run locally using the `devtools::check()` and `lintr::lint_package()` commands and are recommended to be done before pushing to GitHub. The `task-list-completed` workflow is exclusive to GitHub and will be discussed later. In addition, the PR creator and reviewer should make sure that +For a pull request to be merged into `devel` it needs to pass the automated `R CMD check`, `lintr`, and `task-list-completed` workflows on GitHub at a minimum. The first two checks can be run locally using the `devtools::check()` and `lintr::lint_package()` commands and are recommended to be done before pushing to GitHub. The `task-list-completed` workflow is exclusive to GitHub and will be discussed later. In addition, the PR creator and reviewer should make sure that -- the [Programming Strategy](programming_strategy.html) and [Development Process](development_process.html) are followed +- the [Programming Strategy](programming_strategy.html) and [Development Process](development_process.html) are followed -- the function is ADaM IG compliant +- the function is ADaM IG compliant -- the function does what is intended for (as described in the header and corresponding issue) +- the function does what is intended for (as described in the header and corresponding issue) -- the function header properly explains the intention of the function, the expected inputs (incl. permitted values of parameters) and the output produced; after reading the documentation the reader should be able to predict the output of the function without having to read the source code +- the function header properly explains the intention of the function, the expected inputs (incl. permitted values of parameters) and the output produced; after reading the documentation the reader should be able to predict the output of the function without having to read the source code -- the function has an accompanying set of unit tests; for derivations these unit test should have a code coverage of at least 90%; the whole package should have a coverage of >= 80% +- the function has an accompanying set of unit tests; for derivations these unit test should have a code coverage of at least 90%; the whole package should have a coverage of \>= 80% -- the implemented derivation is in the scope of `{admiral}`, e.g. does not expect company specific input or hard-code company-specific rules +- the implemented derivation is in the scope of `{admiral}`, e.g. does not expect company specific input or hard-code company-specific rules -- meaningful error or warning messages are issued if the input is invalid +- meaningful error or warning messages are issued if the input is invalid -- documentation is created/updated by running `devtools::document()` +- documentation is created/updated by running `devtools::document()` -- functions which are supposed to be exported are listed in the `NAMESPACE` file; this requires an `@export` tag in the function header +- functions which are supposed to be exported are listed in the `NAMESPACE` file; this requires an `@export` tag in the function header -- examples print relevant source variables and newly created variables and/or records in their output +- examples print relevant source variables and newly created variables and/or records in their output -- the `NEWS.md` file is updated with an entry that explains the new features or changes +- the `NEWS.md` file is updated with an entry that explains the new features or changes -- the author of a function is listed in the `DESCRIPTION` file - -- all files affected by the implemented changes, e.g. vignettes and templates, are updated +- the author of a function is listed in the `DESCRIPTION` file +- all files affected by the implemented changes, e.g. vignettes and templates, are updated # So much Red Tape! -The `{admiral}` development team is aware and sympathetic to the great many checks, processes and -documents needed to work through in order to do a compliant Pull Request. The `task-list-completed` GitHub -workflow was created to help reduce this burden on contributors by providing a standardized checklist that compiles information from the Pull Request Review Guidance, [Programming Strategy](programming_strategy.html) and [Development Process](development_process.html) vignettes. +The `{admiral}` development team is aware and sympathetic to the great many checks, processes and documents needed to work through in order to do a compliant Pull Request. The `task-list-completed` GitHub workflow was created to help reduce this burden on contributors by providing a standardized checklist that compiles information from the Pull Request Review Guidance, [Programming Strategy](programming_strategy.html) and [Development Process](development_process.html) vignettes. The next three sections give a high-level overview of what a contributor faces in opening a PR, and how a contributor interacts with the `task-list-completed` workflow in their PR. ## Open a Pull Request -When a contributor opens a PR a lengthy standard text will be inserted into the comment section. Please do not alter any of the automated text. You will need to manually add `Closes #` into the title of the Pull Request. You can use the Edit button in the top right if you forget this step at the start of your Pull Request. Besides that you are free to add in additional textual information, screenshots, etc. at the bottom of the automated text if needed to clarify or contribute to the discussion around your PR. - +When a contributor opens a PR a lengthy standard text will be inserted into the comment section. Please do not alter any of the automated text. You will need to manually add `Closes #` into the title of the Pull Request. You can use the Edit button in the top right if you forget this step at the start of your Pull Request. Besides that you are free to add in additional textual information, screenshots, etc. at the bottom of the automated text if needed to clarify or contribute to the discussion around your PR. ```{r echo=FALSE, out.width='120%'} knitr::include_graphics("./pr_review_checklist.png") @@ -75,13 +71,13 @@ knitr::include_graphics("./pr_review_checklist.png") ## Create a Pull Request -After you click the green `Create pull request` button the automated text that was inserted will be turned into a checklist in your Pull Request. Each check box has been drawn from the previously mentioned vignettes and presented in the recommended sequence. These check boxes are meant to be a helpful aid in ensuring that you have created a compliant Pull Request. +After you click the green `Create pull request` button the automated text that was inserted will be turned into a checklist in your Pull Request. Each check box has been drawn from the previously mentioned vignettes and presented in the recommended sequence. These check boxes are meant to be a helpful aid in ensuring that you have created a compliant Pull Request. ```{r echo=FALSE, out.width='120%'} knitr::include_graphics("./pr_review_checkbox.png") ``` -## Complete the Pull Request checklist +## Complete the Pull Request checklist The check boxes are linked to the `task-list-completed` workflow. You need to check off each box in acknowledgment that you have done you due diligence in creating a compliant Pull Request. GitHub will refresh the Pull Request and trigger `task-list-completed` workflow that you have completed the task. The PR can not be merged into `devel` until the contributor has checked off each of the check box items. @@ -89,27 +85,27 @@ The check boxes are linked to the `task-list-completed` workflow. You need to ch knitr::include_graphics("./pr_review_actions.png") ``` -Please don't hesitate to reach out to the `{admiral}` team on [Slack](https://app.slack.com/client/T028PB489D3/C02M8KN8269) or through the [GitHub Issues](https://github.com/pharmaverse/admiral/issues) tracker if you think this checklist needs to be amended or further clarity is needed on a check box item. +Please don't hesitate to reach out to the `{admiral}` team on [Slack](https://app.slack.com/client/T028PB489D3/C02M8KN8269) or through the [GitHub Issues](https://github.com/pharmaverse/admiral/issues) tracker if you think this checklist needs to be amended or further clarity is needed on a check box item. # GitHub Actions/Workflows -The `task-list-completed` workflow is one of the several workflows/actions used within `{admiral}`. These workflows live in the `.github/workflows` folder and it is important to understand their use and how to remedy if the workflow fails. Workflows defined here are responsible for assuring high package quality standards without compromising performance, security, or reproducibility. +The `task-list-completed` workflow is one of the several workflows/actions used within `{admiral}`. These workflows live in the `.github/workflows` folder and it is important to understand their use and how to remedy if the workflow fails. Workflows defined here are responsible for assuring high package quality standards without compromising performance, security, or reproducibility. ## A synopsis of admiral's workflows Most workflows have a `BEGIN boilerplate steps` and `END boilerplate steps` section within them which define some standard steps required for installing system dependencies, R version and R packages which serve as dependencies for the package. -The underlying mechanisms for installing R and Pandoc are defined in `r-lib/actions`, while the installation of system dependencies and R package dependencies is managed via the Staged Dependencies GitHub Action]. The latter is used in conjunction with the `staged_dependencies.yaml` file in order to install dependencies that are in the _same stage of development_ as the current package. +The underlying mechanisms for installing R and Pandoc are defined in `r-lib/actions`, while the installation of system dependencies and R package dependencies is managed via the Staged Dependencies GitHub Action]. The latter is used in conjunction with the `staged_dependencies.yaml` file in order to install dependencies that are in the *same stage of development* as the current package. Following the installation of system dependencies, R, and package dependencies, each workflow checks the integrity of a specific component of the admiral codebase. ### `check-templates.yml` -This workflow checks for issues within template scripts. For example, in the admiral package there are several template scripts with admiral-based functions showing how to build certain ADaM datasets. As we update the admiral functions, we want to make sure these template scripts execute appropriately. Functions in the template scripts that are deprecated or used inappropriately will cause this workflow to fail. Click on the details button on a failing action provides information on the where the template is failing. +This workflow checks for issues within template scripts. For example, in the admiral package there are several template scripts with admiral-based functions showing how to build certain ADaM datasets. As we update the admiral functions, we want to make sure these template scripts execute appropriately. Functions in the template scripts that are deprecated or used inappropriately will cause this workflow to fail. Click on the details button on a failing action provides information on the where the template is failing. ### `code-coverage.yml` -This workflow measures code coverage for unit tests and reports the code coverage as a percentage of the _total number of lines covered by unit tests_ vs. the _total number of lines in the codebase_. +This workflow measures code coverage for unit tests and reports the code coverage as a percentage of the *total number of lines covered by unit tests* vs. the *total number of lines in the codebase*. The `{covr}` R package is used to calculate the coverage. @@ -117,11 +113,11 @@ Report summaries and badges for coverage are generated using a series of other G ### `links.yml` -This workflow checks whether URLs embedded in code and documentation are valid. Invalid URLs results in workflow failures. This workflow uses `lychee` to detect broken links. Occasionally this check will detect false positives of urls that look like urls. To remedy, please add this false positive to the `.lycheeignore` file. +This workflow checks whether URLs embedded in code and documentation are valid. Invalid URLs results in workflow failures. This workflow uses `lychee` to detect broken links. Occasionally this check will detect false positives of urls that look like urls. To remedy, please add this false positive to the `.lycheeignore` file. ### `lintr.yml` -Static code analysis is performed by this workflow, which in turn uses the `{lintr}` R package. The `.lintr` configurations in the repository will be by this workflow. +Static code analysis is performed by this workflow, which in turn uses the `{lintr}` R package. The `.lintr` configurations in the repository will be by this workflow. ### `man-pages.yml` @@ -149,11 +145,11 @@ If your codebase uses a [`README.Rmd` file](../../README.Rmd), then this workflo ### `spellcheck.yml` -Spellchecks are performed by this workflow, and the `{spelling}` R package is used to detect spelling mistakes. Failed workflows typically indicate misspelled words. In the `inst/WORDLIST` file, you can add words and or acronyms that you want the spell check to ignore, for example occds is not an English word but a common acronym used within Pharma. The workflow will flag this until a user adds it to the `inst/WORDLIST`. +Spellchecks are performed by this workflow, and the `{spelling}` R package is used to detect spelling mistakes. Failed workflows typically indicate misspelled words. In the `inst/WORDLIST` file, you can add words and or acronyms that you want the spell check to ignore, for example occds is not an English word but a common acronym used within Pharma. The workflow will flag this until a user adds it to the `inst/WORDLIST`. ### `style.yml` -Code style is enforced via the `styler` R package. Custom style configurations, if any, will be honored by this workflow. Failed workflows are indicative of unstyled code. +Code style is enforced via the `styler` R package. Custom style configurations, if any, will be honored by this workflow. Failed workflows are indicative of unstyled code. # Common R CMD Check Issues @@ -165,7 +161,7 @@ If the `R CMD check` workflow fails only on one or two R versions it can be help To reproduce a particular R version environment open the `{admiral}` project in the corresponding R version, comment the line `source("renv/activate.R")` in the `.Rprofile` file, restart the R session and then run the following commands in the R console. -```r +``` r Sys.setenv(R_REMOTES_NO_ERRORS_FROM_WARNINGS = "true") if (!dir.exists(".library")) { @@ -181,20 +177,20 @@ for (pkg in base_recommended_pkgs) { assign(".lib.loc", ".library", envir = environment(.libPaths)) r_version <- getRversion() -if (grepl("^3.6", r_version)) { - options(repos = "https://cran.microsoft.com/snapshot/2020-02-29") -} else if (grepl("^4.0", r_version)) { - options(repos = "https://cran.microsoft.com/snapshot/2021-03-31") +if ([grepl](https://rdrr.io/r/base/grep.html)("^3.6", r_version)) { + [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2020-02-29") +} else if ([grepl](https://rdrr.io/r/base/grep.html)("^4.0", r_version)) { + [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2021-03-31") } else { - options(repos = "https://cran.rstudio.com") + [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.rstudio.com") } if (!requireNamespace("remotes", quietly = TRUE)) { install.packages("remotes") } remotes::install_deps(dependencies = TRUE) -remotes::install_github("pharamaverse/admiral.test", ref = "devel") - +remotes::install_github("pharmaverse/admiral.test", ref = "devel") +remotes::install_github("pharmaverse/admiraldev", ref = "devel") rcmdcheck::rcmdcheck() ``` @@ -202,44 +198,36 @@ This will ensure that the exact package versions we use in the workflow are inst ## Package Dependencies -``` -> checking package dependencies ... ERROR - Namespace dependency not required: ‘pkg’ -``` + > checking package dependencies ... ERROR + Namespace dependency not required: ‘pkg’ Add `pkg` to the `Imports` or `Suggests` field in the `DESCRIPTION` file. In general, dependencies should be listed in the `Imports` field. However, if a package is only used inside vignettes or unit tests it should be listed in `Suggests` because all `{admiral}` functions would work without these "soft" dependencies being installed. ## Global Variables -``` -❯ checking R code for possible problems ... NOTE - function_xyz: no visible binding for global variable ‘some_var’ -``` + ❯ checking R code for possible problems ... NOTE + function_xyz: no visible binding for global variable ‘some_var’ Add `some_var` to the list of "global" variables in `R/globals.R`. ## Undocumented Function Parameter -``` -❯ checking Rd \usage sections ... WARNING - Undocumented arguments in documentation object 'function_xyz' - ‘some_param’ -``` + ❯ checking Rd \usage sections ... WARNING + Undocumented arguments in documentation object 'function_xyz' + ‘some_param’ Add an `@param some_param` section in the header of `function_xyz()` and run `devtools::document()` afterwards. ## Outdated Documentation -``` -❯ checking for code/documentation mismatches ... WARNING - Codoc mismatches from documentation object 'function_xyz': - ... - Argument names in code not in docs: - new_param_name - Argument names in docs not in code: - old_param_name - Mismatches in argument names: - Position: 6 Code: new_param_name Docs: old_param_name -``` + ❯ checking for code/documentation mismatches ... WARNING + Codoc mismatches from documentation object 'function_xyz': + ... + Argument names in code not in docs: + new_param_name + Argument names in docs not in code: + old_param_name + Mismatches in argument names: + Position: 6 Code: new_param_name Docs: old_param_name The name of a parameter has been changed in the function code but not yet in the header. Change `@param old_param_name` to `@param new_param_name` and run `devtools::document()`. From 18903f11066502da0d4e721a48d0799a4fca2007 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 19 Oct 2022 16:48:37 +0000 Subject: [PATCH 066/179] #103 - add quo_c coverage --- tests/testthat/test-quo.R | 45 +++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index a67ef5d4..16c0bf6b 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -1,6 +1,23 @@ +# quo_c ---- +## Test 1: `quo_c` works in concatenating and indexing quosures ---- +test_that("quo_c Test 1: `quo_c` works in concatenating and indexing quosures", { + x = quo(USUBJID) + y = quo(STUDYID) + + expect_equal(quo_c(x, NULL, y)[[1]], quo(USUBJID)) + expect_equal(quo_c(x, NULL, y)[[2]], quo(STUDYID)) +}) + +## Test 2: `quo_c` returns error if non-quosures are input ---- +test_that("quo_c Test 2: `quo_c` returns error if non-quosures are input", { + USUBJID = "STUDY-1001" + + expect_error(quo_c(quo(USUBJID), USUBJID)) +}) + # quo_not_missing ---- -## Test 1: `quo_not_missing` returns TRUE if no missing argument ---- -test_that("quo_not_missing Test 1: `quo_not_missing` returns TRUE if no missing argument", { +## Test 3: `quo_not_missing` returns TRUE if no missing argument ---- +test_that("quo_not_missing Test 3: `quo_not_missing` returns TRUE if no missing argument", { test_fun <- function(x) { x <- rlang::enquo(x) assertthat::assert_that(quo_not_missing(x)) @@ -8,8 +25,8 @@ test_that("quo_not_missing Test 1: `quo_not_missing` returns TRUE if no missing expect_true(test_fun(my_variable)) }) -## Test 2: `quo_not_missing` throws and Error if missing argument ---- -test_that("quo_not_missing Test 2: `quo_not_missing` throws and Error if missing argument", { +## Test 4: `quo_not_missing` throws and Error if missing argument ---- +test_that("quo_not_missing Test 4: `quo_not_missing` throws and Error if missing argument", { test_fun <- function(x) { x <- rlang::enquo(x) assertthat::assert_that(quo_not_missing(x)) @@ -18,8 +35,8 @@ test_that("quo_not_missing Test 2: `quo_not_missing` throws and Error if missing }) # replace_symbol_in_quo ---- -## Test 3: symbol is replaced ---- -test_that("replace_symbol_in_quo Test 3: symbol is replaced", { +## Test 5: symbol is replaced ---- +test_that("replace_symbol_in_quo Test 5: symbol is replaced", { expect_equal( expected = quo(AVAL.join), object = replace_symbol_in_quo( @@ -30,8 +47,8 @@ test_that("replace_symbol_in_quo Test 3: symbol is replaced", { ) }) -## Test 4: partial match is not replaced ---- -test_that("replace_symbol_in_quo Test 4: partial match is not replaced", { +## Test 6: partial match is not replaced ---- +test_that("replace_symbol_in_quo Test 6: partial match is not replaced", { expect_equal( expected = quo(AVALC), object = replace_symbol_in_quo( @@ -42,8 +59,8 @@ test_that("replace_symbol_in_quo Test 4: partial match is not replaced", { ) }) -## Test 5: symbol in expression is replaced ---- -test_that("replace_symbol_in_quo Test 5: symbol in expression is replaced", { +## Test 7: symbol in expression is replaced ---- +test_that("replace_symbol_in_quo Test 7: symbol in expression is replaced", { expect_equal( expected = quo(desc(AVAL.join)), object = replace_symbol_in_quo( @@ -55,16 +72,16 @@ test_that("replace_symbol_in_quo Test 5: symbol in expression is replaced", { }) # add_suffix_to_vars ---- -## Test 6: with single variable ---- -test_that("add_suffix_to_vars Test 6: with single variable", { +## Test 8: with single variable ---- +test_that("add_suffix_to_vars Test 8: with single variable", { expect_equal( expected = vars(ADT, desc(AVAL.join), AVALC), object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") ) }) -## Test 7: with more than one variable ---- -test_that("add_suffix_to_vars Test 7: with more than one variable", { +## Test 9: with more than one variable ---- +test_that("add_suffix_to_vars Test 9: with more than one variable", { expect_equal( expected = vars(ADT, desc(AVAL.join), AVALC.join), object = add_suffix_to_vars( From 5cfca26adfae01d2a43021e7fc6306425ef29a16 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 19 Oct 2022 17:44:58 +0000 Subject: [PATCH 067/179] #103 - Add test coverage for replace_values_by_names() --- tests/testthat/test-quo.R | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index 16c0bf6b..5ddeeed1 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -1,8 +1,8 @@ # quo_c ---- ## Test 1: `quo_c` works in concatenating and indexing quosures ---- test_that("quo_c Test 1: `quo_c` works in concatenating and indexing quosures", { - x = quo(USUBJID) - y = quo(STUDYID) + x <- quo(USUBJID) + y <- quo(STUDYID) expect_equal(quo_c(x, NULL, y)[[1]], quo(USUBJID)) expect_equal(quo_c(x, NULL, y)[[2]], quo(STUDYID)) @@ -10,7 +10,7 @@ test_that("quo_c Test 1: `quo_c` works in concatenating and indexing quosures", ## Test 2: `quo_c` returns error if non-quosures are input ---- test_that("quo_c Test 2: `quo_c` returns error if non-quosures are input", { - USUBJID = "STUDY-1001" + USUBJID <- "STUDY-1001" expect_error(quo_c(quo(USUBJID), USUBJID)) }) @@ -34,9 +34,22 @@ test_that("quo_not_missing Test 4: `quo_not_missing` throws and Error if missing expect_error(test_fun()) # missing argument -> throws error }) +# replace_values_by_names ---- +## Test 5: names of quosures replace value ---- +test_that("replace_values_by_names Test 5: names of quosures replace value", { + x <- quo(USUBJID) + y <- quo(STUDYID) + z <- quo_c(x, y) + names(z) <- c("Unique Subject Identifier", "Study Identifier") + z2 <- replace_values_by_names(z) + + expect_equal(z2[[1]], quo(`Unique Subject Identifier`)) + expect_equal(z2[[2]], quo(`Study Identifier`)) +}) + # replace_symbol_in_quo ---- -## Test 5: symbol is replaced ---- -test_that("replace_symbol_in_quo Test 5: symbol is replaced", { +## Test 6: symbol is replaced ---- +test_that("replace_symbol_in_quo Test 6: symbol is replaced", { expect_equal( expected = quo(AVAL.join), object = replace_symbol_in_quo( @@ -47,8 +60,8 @@ test_that("replace_symbol_in_quo Test 5: symbol is replaced", { ) }) -## Test 6: partial match is not replaced ---- -test_that("replace_symbol_in_quo Test 6: partial match is not replaced", { +## Test 7: partial match is not replaced ---- +test_that("replace_symbol_in_quo Test 7: partial match is not replaced", { expect_equal( expected = quo(AVALC), object = replace_symbol_in_quo( @@ -59,8 +72,8 @@ test_that("replace_symbol_in_quo Test 6: partial match is not replaced", { ) }) -## Test 7: symbol in expression is replaced ---- -test_that("replace_symbol_in_quo Test 7: symbol in expression is replaced", { +## Test 8: symbol in expression is replaced ---- +test_that("replace_symbol_in_quo Test 8: symbol in expression is replaced", { expect_equal( expected = quo(desc(AVAL.join)), object = replace_symbol_in_quo( @@ -72,16 +85,16 @@ test_that("replace_symbol_in_quo Test 7: symbol in expression is replaced", { }) # add_suffix_to_vars ---- -## Test 8: with single variable ---- -test_that("add_suffix_to_vars Test 8: with single variable", { +## Test 9: with single variable ---- +test_that("add_suffix_to_vars Test 9: with single variable", { expect_equal( expected = vars(ADT, desc(AVAL.join), AVALC), object = add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") ) }) -## Test 9: with more than one variable ---- -test_that("add_suffix_to_vars Test 9: with more than one variable", { +## Test 10: with more than one variable ---- +test_that("add_suffix_to_vars Test 10: with more than one variable", { expect_equal( expected = vars(ADT, desc(AVAL.join), AVALC.join), object = add_suffix_to_vars( From bd7abc0dc4ab1512714d4a198e9af108292a651b Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 19 Oct 2022 18:00:12 +0000 Subject: [PATCH 068/179] #103 - additional coverage for replace_values_by_names() to get to full coverage --- tests/testthat/test-quo.R | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index 5ddeeed1..d82f01e5 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -40,11 +40,17 @@ test_that("replace_values_by_names Test 5: names of quosures replace value", { x <- quo(USUBJID) y <- quo(STUDYID) z <- quo_c(x, y) - names(z) <- c("Unique Subject Identifier", "Study Identifier") + z2 <- replace_values_by_names(z) - expect_equal(z2[[1]], quo(`Unique Subject Identifier`)) - expect_equal(z2[[2]], quo(`Study Identifier`)) + names(z) <- c("Unique Subject Identifier", "Study Identifier") + z3 <- replace_values_by_names(z) + + expect_equal(z2[[1]], quo(USUBJID)) + expect_equal(z2[[2]], quo(STUDYID)) + + expect_equal(z3[[1]], quo(`Unique Subject Identifier`)) + expect_equal(z3[[2]], quo(`Study Identifier`)) }) # replace_symbol_in_quo ---- From 61fcabd44ef0018186a1d13c462074660e47640d Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 19 Oct 2022 18:20:00 +0000 Subject: [PATCH 069/179] #103 - styler and formatting edits --- tests/testthat/test-quo.R | 40 +++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index d82f01e5..3bc40d42 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -4,15 +4,23 @@ test_that("quo_c Test 1: `quo_c` works in concatenating and indexing quosures", x <- quo(USUBJID) y <- quo(STUDYID) - expect_equal(quo_c(x, NULL, y)[[1]], quo(USUBJID)) - expect_equal(quo_c(x, NULL, y)[[2]], quo(STUDYID)) + expect_equal( + expected = quo(USUBJID), + object = quo_c(x, NULL, y)[[1]] + ) + expect_equal( + expected = quo(STUDYID), + object = quo_c(x, NULL, y)[[2]] + ) }) ## Test 2: `quo_c` returns error if non-quosures are input ---- test_that("quo_c Test 2: `quo_c` returns error if non-quosures are input", { - USUBJID <- "STUDY-1001" + USUBJID <- "01-701-1015" #nolint - expect_error(quo_c(quo(USUBJID), USUBJID)) + expect_error( + object = quo_c(quo(USUBJID), USUBJID) + ) }) # quo_not_missing ---- @@ -41,16 +49,28 @@ test_that("replace_values_by_names Test 5: names of quosures replace value", { y <- quo(STUDYID) z <- quo_c(x, y) - z2 <- replace_values_by_names(z) + z_noname <- replace_values_by_names(z) names(z) <- c("Unique Subject Identifier", "Study Identifier") - z3 <- replace_values_by_names(z) + z_named <- replace_values_by_names(z) - expect_equal(z2[[1]], quo(USUBJID)) - expect_equal(z2[[2]], quo(STUDYID)) + expect_equal( + expected = quo(USUBJID), + object = z_noname[[1]] + ) + expect_equal( + expected = quo(STUDYID), + object = z_noname[[2]] + ) - expect_equal(z3[[1]], quo(`Unique Subject Identifier`)) - expect_equal(z3[[2]], quo(`Study Identifier`)) + expect_equal( + expected = quo(`Unique Subject Identifier`), + object = z_named[[1]] + ) + expect_equal( + expected = quo(`Study Identifier`), + object = z_named[[2]] + ) }) # replace_symbol_in_quo ---- From 81efcd2241c926018c17d924537a34c4c0bb0d94 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 19 Oct 2022 18:50:17 +0000 Subject: [PATCH 070/179] #103 - address styler error --- tests/testthat/test-quo.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index 3bc40d42..de0a4cbc 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -16,7 +16,7 @@ test_that("quo_c Test 1: `quo_c` works in concatenating and indexing quosures", ## Test 2: `quo_c` returns error if non-quosures are input ---- test_that("quo_c Test 2: `quo_c` returns error if non-quosures are input", { - USUBJID <- "01-701-1015" #nolint + USUBJID <- "01-701-1015" # nolint expect_error( object = quo_c(quo(USUBJID), USUBJID) From 6128634126b538cb4abe202dc3c5c4f96a1124a4 Mon Sep 17 00:00:00 2001 From: Jeff Dickinson Date: Wed, 19 Oct 2022 19:15:58 +0000 Subject: [PATCH 071/179] Add Test #7 for NULL dataset. --- tests/testthat/test-tmp_vars.R | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/testthat/test-tmp_vars.R b/tests/testthat/test-tmp_vars.R index 1ee57e63..35ba87c5 100644 --- a/tests/testthat/test-tmp_vars.R +++ b/tests/testthat/test-tmp_vars.R @@ -55,3 +55,10 @@ test_that("remove_tmp_vars Test 6: removing temp variables works with the pipe o } expect_identical(colnames(dm), colnames(do_something_with_pipe(dm))) }) + + +## Test 7: running get_new_tmp_var on NULL dataset creates generic variable ---- +test_that("running get_new_tmp_var on NULL dataset creates generic variable", { + df <- NULL + expect_identical(get_new_tmp_var(df), sym("tmp_var_1")) +}) From a7bdb23b4039e0e0e4aeb0ab85d426da1c10b0b8 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Thu, 20 Oct 2022 00:29:38 +0000 Subject: [PATCH 072/179] Added linked to the Github repository --- docs/pkgdown.yml | 2 +- vignettes/programming_strategy.Rmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index cbe9ea5b..b0344063 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-17T20:37Z +last_built: 2022-10-20T00:25Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 588ff552..f13edd29 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -671,7 +671,7 @@ If meaningful, comments can cover multiple variables within a piece of code # R and package versions for development * The choice of R Version is not set in stone. However, a common development environment is important to establish when working across multiple companies and multiple developers. We currently work in R Version 3.6.3, but that will change as we move forward with `{admiral}`. This need for a common development environment also carries over for our choice of package versions. -* GitHub allows us through the Actions/Workflows to test `{admiral}` under several versions of R as well as several versions of dependent R packages needed for `{admiral}`. Currently we test `{admiral}` against 3.6.3 with a CRAN package snapshot from 2020-02-29, 4.0 with a CRAN package snapshot from 2021-03-31 and the latest R version with the latest snapshots of packages. You can view this workflow on our [GitHub Repository](https://github.com/pharmaverse/admiral/blob/main/.github/workflows/R-CMD-check.yml) +* GitHub allows us through the Actions/Workflows to test `{admiral}` under several versions of R as well as several versions of dependent R packages needed for `{admiral}`. Currently we test `{admiral}` against 3.6.3 with a CRAN package snapshot from 2020-02-29, 4.0 with a CRAN package snapshot from 2021-03-31 and the latest R version with the latest snapshots of packages. You can view this workflow on our [GitHub Repository](https://github.com/pharmaverse/admiral/blob/main/.github/workflows/common.yml) * This common development allows us to easily re-create bugs and provide solutions to each other issues that developers will encounter. * Reviewers of Pull Requests when running code will know that their environment is identical to the initiator of the Pull Request. This ensures faster review times and higher quality Pull Request reviews. * We achieve this common development environment by using a **lockfile** created from the [`renv`](https://rstudio.github.io/renv/) package. New developers will encounter a suggested `renv::restore()` in the console to revert or move forward your R version and package versions. From 112690fb230481c44d56183f75637ca9c4540599 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Thu, 20 Oct 2022 13:30:01 +0000 Subject: [PATCH 073/179] #103 - Grammer fixes and removal of pkg_name for imports vs suggests --- tests/testthat/test-quo.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index de0a4cbc..87d67020 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -27,17 +27,17 @@ test_that("quo_c Test 2: `quo_c` returns error if non-quosures are input", { ## Test 3: `quo_not_missing` returns TRUE if no missing argument ---- test_that("quo_not_missing Test 3: `quo_not_missing` returns TRUE if no missing argument", { test_fun <- function(x) { - x <- rlang::enquo(x) - assertthat::assert_that(quo_not_missing(x)) + x <- enquo(x) + assert_that(quo_not_missing(x)) } expect_true(test_fun(my_variable)) }) -## Test 4: `quo_not_missing` throws and Error if missing argument ---- -test_that("quo_not_missing Test 4: `quo_not_missing` throws and Error if missing argument", { +## Test 4: `quo_not_missing` throws an Error if missing argument ---- +test_that("quo_not_missing Test 4: `quo_not_missing` throws an Error if missing argument", { test_fun <- function(x) { - x <- rlang::enquo(x) - assertthat::assert_that(quo_not_missing(x)) + x <- enquo(x) + assert_that(quo_not_missing(x)) } expect_error(test_fun()) # missing argument -> throws error }) From cb8ebb9b4917bedb73a1733bdac7c94c63d4dd28 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Thu, 20 Oct 2022 14:23:42 +0000 Subject: [PATCH 074/179] Updated repo link and R versions --- vignettes/git_usage.Rmd | 4 ++-- vignettes/programming_strategy.Rmd | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vignettes/git_usage.Rmd b/vignettes/git_usage.Rmd index 35056258..df704911 100644 --- a/vignettes/git_usage.Rmd +++ b/vignettes/git_usage.Rmd @@ -27,8 +27,8 @@ This article will give you an overview of how the `{admiral}` project is utilizi - The `main` branch contains the latest **released** version and should not be used for development. You can find the released versions [here](https://GitHub.com/pharmaverse/admiral/releases) - The `devel` branch contains the latest development version of the package. You will always be branching off and pulling into the `devel` branch. - The `gh-pages` branches contains the code used to render this website you are looking at right now! -- The `patch` branch is reserved for special hot fixes to address bugs. More info in [ Hot Fix Release](#hot-fix-release) -- The `pre-release` branch is used to as an intermediate step to releasing the package from `main`. More info in [Quarterly Release section](#quarterly-release) +- The `patch` branch is reserved for special hot fixes to address bugs. More info in [Hot Fix Release](release_strategy.html#hot-fix-release) +- The `pre-release` branch is used to as an intermediate step to releasing the package from `main`. More info in [Quarterly Release section](release_strategy.html#quarterly-release) - The `main`, `devel`, `gh-pages`, `patch` and `pre-release` branches are under protection. If you try and push changes to these branches you will get an error unless you are an administrator. - **Feature** branches are where actual development related to a specific issue happens. Feature branches are merged into `devel` once a pull request is merged. Check out the [Pull Request Review Guidance](pr_review_guidance.html). diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index f13edd29..a8c4ce6b 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -670,8 +670,8 @@ If meaningful, comments can cover multiple variables within a piece of code # R and package versions for development -* The choice of R Version is not set in stone. However, a common development environment is important to establish when working across multiple companies and multiple developers. We currently work in R Version 3.6.3, but that will change as we move forward with `{admiral}`. This need for a common development environment also carries over for our choice of package versions. -* GitHub allows us through the Actions/Workflows to test `{admiral}` under several versions of R as well as several versions of dependent R packages needed for `{admiral}`. Currently we test `{admiral}` against 3.6.3 with a CRAN package snapshot from 2020-02-29, 4.0 with a CRAN package snapshot from 2021-03-31 and the latest R version with the latest snapshots of packages. You can view this workflow on our [GitHub Repository](https://github.com/pharmaverse/admiral/blob/main/.github/workflows/common.yml) +* The choice of R Version is not set in stone. However, a common development environment is important to establish when working across multiple companies and multiple developers. We only use the three latest R versions and the closest date of snapshots of R packages available when that R version came out. This need for a common development environment also carries over for our choice of package versions. +* GitHub allows us through the Actions/Workflows to test `{admiral}` under several versions of R as well as several versions of dependent R packages needed for `{admiral}`. Currently we test `{admiral}` against 4.0 with a CRAN package snapshot from 2021-03-31 and the latest R version with the latest snapshots of packages. You can view this workflow on our [GitHub Repository](https://github.com/pharmaverse/admiralci/blob/main/.github/workflows/r-cmd-check.yml) * This common development allows us to easily re-create bugs and provide solutions to each other issues that developers will encounter. * Reviewers of Pull Requests when running code will know that their environment is identical to the initiator of the Pull Request. This ensures faster review times and higher quality Pull Request reviews. * We achieve this common development environment by using a **lockfile** created from the [`renv`](https://rstudio.github.io/renv/) package. New developers will encounter a suggested `renv::restore()` in the console to revert or move forward your R version and package versions. From 272f4702bccd0fc7ffa539555276880565d0bc76 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Thu, 20 Oct 2022 16:44:55 +0000 Subject: [PATCH 075/179] Updated the R versions to include, 4.0,4.1, 4.2 --- docs/pkgdown.yml | 2 +- vignettes/pr_review_guidance.Rmd | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index cbe9ea5b..e93a5c73 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-17T20:37Z +last_built: 2022-10-20T16:41Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/pr_review_guidance.Rmd b/vignettes/pr_review_guidance.Rmd index 16ee32d5..7a6d7935 100644 --- a/vignettes/pr_review_guidance.Rmd +++ b/vignettes/pr_review_guidance.Rmd @@ -177,10 +177,12 @@ for (pkg in base_recommended_pkgs) { assign(".lib.loc", ".library", envir = environment(.libPaths)) r_version <- getRversion() -if ([grepl](https://rdrr.io/r/base/grep.html)("^3.6", r_version)) { - [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2020-02-29") -} else if ([grepl](https://rdrr.io/r/base/grep.html)("^4.0", r_version)) { +if ([grepl](https://rdrr.io/r/base/grep.html)("^4.0", r_version)) { [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2021-03-31") +} else if ([grepl](https://rdrr.io/r/base/grep.html)("^4.1", r_version)) { + [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2022-03-10") +} else if ([grepl](https://rdrr.io/r/base/grep.html)("^4.2", r_version)) { + [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2022-06-23") } else { [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.rstudio.com") } From 04d1cf426bbb18e54b6a9976f9b16f0c8d1ef98b Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Fri, 21 Oct 2022 18:23:22 +0000 Subject: [PATCH 076/179] included logic for optional, re-enumerate test file, updated news note, remove var_name --- NEWS.md | 2 ++ R/assertions.R | 22 ++++++++--------- docs/pkgdown.yml | 2 +- man/assert_character_scalar.Rd | 1 + man/assert_character_vector.Rd | 1 + man/assert_data_frame.Rd | 1 + man/assert_date_vector.Rd | 39 ++++++++++++++++++++++++------ man/assert_expr.Rd | 1 + man/assert_filter_cond.Rd | 1 + man/assert_function.Rd | 1 + man/assert_function_param.Rd | 1 + man/assert_has_variables.Rd | 1 + man/assert_integer_scalar.Rd | 1 + man/assert_list_element.Rd | 1 + man/assert_list_of.Rd | 1 + man/assert_logical_scalar.Rd | 1 + man/assert_named_exprs.Rd | 1 + man/assert_numeric_vector.Rd | 1 + man/assert_one_to_one.Rd | 1 + man/assert_order_vars.Rd | 1 + man/assert_param_does_not_exist.Rd | 1 + man/assert_s3_class.Rd | 1 + man/assert_symbol.Rd | 1 + man/assert_unit.Rd | 1 + man/assert_vars.Rd | 1 + man/assert_varval_list.Rd | 1 + tests/testthat/test-assertions.R | 4 ++- 27 files changed, 69 insertions(+), 22 deletions(-) diff --git a/NEWS.md b/NEWS.md index a3cd9b24..718f1f90 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,8 @@ - New functions `replace_symbol_in_quo()` and `add_suffix_to_vars()` (#106) - New keyword/family `create_aux` for functions creating auxiliary datasets (#126) + + - New function `assert_date_vector` (#129) ## Updates of Existing Functions diff --git a/R/assertions.R b/R/assertions.R index 5c91766a..ba65dbb7 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1449,20 +1449,17 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) } } -#' Is a Variable is a Date or Datetime Variable? +#' Is a Variable vector is a Date or Datetime Variable? #' #' Checks if a variable vector is a date or datetime variable #' #' @param arg The function argument to be checked #' -#' @param var_name a character vector name of the `arg`. -#' `var_name` argument is optional. -#' #' @param optional Is the checked parameter optional? If set to `FALSE` -#' and `arg` is `NULL` then an error is thrown. +#' and `arg` is `NULL` then the function `assert_date_vector` exits early and throw and error. #' #' @return -#' The function returns an error if `arg` is not a date or datetime variable +#' The function returns an error if `arg` is missing, or not a date or datetime variable #' but otherwise returns an invisible output. #' #' @export @@ -1471,6 +1468,8 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) #' #' @keywords assertion #' +#' @family assertion +#' #' @examples #' example_fun <- function(arg) { #' assert_date_vector(arg) @@ -1479,18 +1478,17 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) #' example_fun( #' as.Date("2022-01-30", tz = "UTC") #' ) -#' try(example_fun(c("2018-08-23", "2022-01-30", "1993-07-14"))) -assert_date_vector <- function(arg, var_name = NULL, optional = FALSE) { - assert_character_vector(var_name, optional = TRUE) +#' try(example_fun("1993-07-14")) +assert_date_vector <- function(arg, optional = TRUE) { assert_logical_scalar(optional) - if (is.null(var_name)) { - var_name <- arg_name(substitute(arg)) + if (optional && is.null(arg)) { + stop() } if (!is.instant(arg)) { abort(paste0( - var_name, + deparse(substitute(arg)), " must be a date or datetime variable but it's ", friendly_type_of(arg) )) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index cbe9ea5b..ea45bef8 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-17T20:37Z +last_built: 2022-10-21T18:12Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/man/assert_character_scalar.Rd b/man/assert_character_scalar.Rd index 06e7c7e1..4435e813 100644 --- a/man/assert_character_scalar.Rd +++ b/man/assert_character_scalar.Rd @@ -61,6 +61,7 @@ example_fun2("Warning") Checks for valid input and returns warning or errors messages: \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_character_vector.Rd b/man/assert_character_vector.Rd index eb8405a8..d0c56f37 100644 --- a/man/assert_character_vector.Rd +++ b/man/assert_character_vector.Rd @@ -35,6 +35,7 @@ try(example_fun(1:10)) Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_data_frame.Rd b/man/assert_data_frame.Rd index 8147f1b0..c5dae995 100644 --- a/man/assert_data_frame.Rd +++ b/man/assert_data_frame.Rd @@ -48,6 +48,7 @@ try(example_fun("Not a dataset")) Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_date_vector.Rd b/man/assert_date_vector.Rd index 6d2b7e76..a61a4f57 100644 --- a/man/assert_date_vector.Rd +++ b/man/assert_date_vector.Rd @@ -2,21 +2,18 @@ % Please edit documentation in R/assertions.R \name{assert_date_vector} \alias{assert_date_vector} -\title{Is a Variable is a Date or Datetime Variable?} +\title{Is a Variable vector is a Date or Datetime Variable?} \usage{ -assert_date_vector(arg, var_name = NULL, optional = FALSE) +assert_date_vector(arg, optional = TRUE) } \arguments{ \item{arg}{The function argument to be checked} -\item{var_name}{a character vector name of the \code{arg}. -\code{var_name} argument is optional.} - \item{optional}{Is the checked parameter optional? If set to \code{FALSE} -and \code{arg} is \code{NULL} then an error is thrown.} +and \code{arg} is \code{NULL} then the function \code{assert_date_vector} exits early and throw and error.} } \value{ -The function returns an error if \code{arg} is not a date or datetime variable +The function returns an error if \code{arg} is missing, or not a date or datetime variable but otherwise returns an invisible output. } \description{ @@ -30,9 +27,35 @@ example_fun <- function(arg) { example_fun( as.Date("2022-01-30", tz = "UTC") ) -try(example_fun(c("2018-08-23", "2022-01-30", "1993-07-14"))) +try(example_fun("1993-07-14")) +} +\seealso{ +Checks for valid input and returns warning or errors messages: +\code{\link{assert_character_scalar}()}, +\code{\link{assert_character_vector}()}, +\code{\link{assert_data_frame}()}, +\code{\link{assert_expr}()}, +\code{\link{assert_filter_cond}()}, +\code{\link{assert_function_param}()}, +\code{\link{assert_function}()}, +\code{\link{assert_has_variables}()}, +\code{\link{assert_integer_scalar}()}, +\code{\link{assert_list_element}()}, +\code{\link{assert_list_of}()}, +\code{\link{assert_logical_scalar}()}, +\code{\link{assert_named_exprs}()}, +\code{\link{assert_numeric_vector}()}, +\code{\link{assert_one_to_one}()}, +\code{\link{assert_order_vars}()}, +\code{\link{assert_param_does_not_exist}()}, +\code{\link{assert_s3_class}()}, +\code{\link{assert_symbol}()}, +\code{\link{assert_unit}()}, +\code{\link{assert_vars}()}, +\code{\link{assert_varval_list}()} } \author{ Sadchla Mascary } +\concept{assertion} \keyword{assertion} diff --git a/man/assert_expr.Rd b/man/assert_expr.Rd index 143fd8c6..40684bd3 100644 --- a/man/assert_expr.Rd +++ b/man/assert_expr.Rd @@ -24,6 +24,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, \code{\link{assert_function}()}, diff --git a/man/assert_filter_cond.Rd b/man/assert_filter_cond.Rd index a8a38864..07dc82f7 100644 --- a/man/assert_filter_cond.Rd +++ b/man/assert_filter_cond.Rd @@ -41,6 +41,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_function_param}()}, \code{\link{assert_function}()}, diff --git a/man/assert_function.Rd b/man/assert_function.Rd index edf3424b..44d1ec7a 100644 --- a/man/assert_function.Rd +++ b/man/assert_function.Rd @@ -43,6 +43,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_function_param.Rd b/man/assert_function_param.Rd index a8ecc413..0d469ec0 100644 --- a/man/assert_function_param.Rd +++ b/man/assert_function_param.Rd @@ -32,6 +32,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function}()}, diff --git a/man/assert_has_variables.Rd b/man/assert_has_variables.Rd index 8c7d4363..f57940b6 100644 --- a/man/assert_has_variables.Rd +++ b/man/assert_has_variables.Rd @@ -31,6 +31,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_integer_scalar.Rd b/man/assert_integer_scalar.Rd index 4f5df811..efe82450 100644 --- a/man/assert_integer_scalar.Rd +++ b/man/assert_integer_scalar.Rd @@ -41,6 +41,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_list_element.Rd b/man/assert_list_element.Rd index da7423db..053b65e5 100644 --- a/man/assert_list_element.Rd +++ b/man/assert_list_element.Rd @@ -44,6 +44,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_list_of.Rd b/man/assert_list_of.Rd index 4c301055..4dee6643 100644 --- a/man/assert_list_of.Rd +++ b/man/assert_list_of.Rd @@ -38,6 +38,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_logical_scalar.Rd b/man/assert_logical_scalar.Rd index c0c2ccdf..5408b928 100644 --- a/man/assert_logical_scalar.Rd +++ b/man/assert_logical_scalar.Rd @@ -39,6 +39,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_named_exprs.Rd b/man/assert_named_exprs.Rd index da0860e7..b648f97d 100644 --- a/man/assert_named_exprs.Rd +++ b/man/assert_named_exprs.Rd @@ -24,6 +24,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_numeric_vector.Rd b/man/assert_numeric_vector.Rd index 2ef5b08c..ebe1ddda 100644 --- a/man/assert_numeric_vector.Rd +++ b/man/assert_numeric_vector.Rd @@ -33,6 +33,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_one_to_one.Rd b/man/assert_one_to_one.Rd index f3390305..52587a9b 100644 --- a/man/assert_one_to_one.Rd +++ b/man/assert_one_to_one.Rd @@ -26,6 +26,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_order_vars.Rd b/man/assert_order_vars.Rd index 30f762d1..1010ab76 100644 --- a/man/assert_order_vars.Rd +++ b/man/assert_order_vars.Rd @@ -38,6 +38,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_param_does_not_exist.Rd b/man/assert_param_does_not_exist.Rd index 019ef509..12da0690 100644 --- a/man/assert_param_does_not_exist.Rd +++ b/man/assert_param_does_not_exist.Rd @@ -33,6 +33,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_s3_class.Rd b/man/assert_s3_class.Rd index 577e4960..e5db3533 100644 --- a/man/assert_s3_class.Rd +++ b/man/assert_s3_class.Rd @@ -37,6 +37,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_symbol.Rd b/man/assert_symbol.Rd index ac228057..d1d96304 100644 --- a/man/assert_symbol.Rd +++ b/man/assert_symbol.Rd @@ -41,6 +41,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_unit.Rd b/man/assert_unit.Rd index e6c04a48..4e1e08b7 100644 --- a/man/assert_unit.Rd +++ b/man/assert_unit.Rd @@ -39,6 +39,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index b9fb012c..ae03a72b 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -48,6 +48,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_varval_list.Rd b/man/assert_varval_list.Rd index 18f4d165..218eaa49 100644 --- a/man/assert_varval_list.Rd +++ b/man/assert_varval_list.Rd @@ -47,6 +47,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 44ab0a6f..11522266 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -151,6 +151,8 @@ test_that("assert_order_vars Test 13: returns errors if used incorrectly", { expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) }) -test_that("Test 12: `assert_date_vector` returns error if input vector is not a date formatted", { +# assert_date_vector ---- +## Test 14: returns error if input vector is not a date formatted ---- +test_that("assert_date_vector Test 14: returns error if input vector is not a date formatted", { expect_error(assert_date_vector("2018-08-23")) }) From 7924d0c51d79d25bfb582c872fbe1aa578e603dc Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Fri, 21 Oct 2022 18:30:32 +0000 Subject: [PATCH 077/179] Resolving conflicts --- docs/pkgdown.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index ea45bef8..b0344063 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-21T18:12Z +last_built: 2022-10-20T00:25Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles From 92721001245039f1e701a4934fb8adb012702de4 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Fri, 21 Oct 2022 19:55:12 +0000 Subject: [PATCH 078/179] Updated title and description --- R/assertions.R | 4 ++-- man/assert_date_vector.Rd | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index ba65dbb7..e5137102 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1449,9 +1449,9 @@ assert_date_var <- function(dataset, var, dataset_name = NULL, var_name = NULL) } } -#' Is a Variable vector is a Date or Datetime Variable? +#' Is an object a date or datetime vector? #' -#' Checks if a variable vector is a date or datetime variable +#' Check if an object/vector is a date or datetime variable without needing a dataset as input #' #' @param arg The function argument to be checked #' diff --git a/man/assert_date_vector.Rd b/man/assert_date_vector.Rd index a61a4f57..89737b31 100644 --- a/man/assert_date_vector.Rd +++ b/man/assert_date_vector.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/assertions.R \name{assert_date_vector} \alias{assert_date_vector} -\title{Is a Variable vector is a Date or Datetime Variable?} +\title{Is an object a date or datetime vector?} \usage{ assert_date_vector(arg, optional = TRUE) } @@ -17,7 +17,7 @@ The function returns an error if \code{arg} is missing, or not a date or datetim but otherwise returns an invisible output. } \description{ -Checks if a variable vector is a date or datetime variable +Check if an object/vector is a date or datetime variable without needing a dataset as input } \examples{ example_fun <- function(arg) { From ccc98652cb89164accc07761a9c3d4bc8eafe6d0 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Tue, 25 Oct 2022 13:09:16 +0000 Subject: [PATCH 079/179] Fix: added test for no errors from function when vector is date formatted, updated the check in function --- R/assertions.R | 2 +- tests/testthat/test-assertions.R | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/R/assertions.R b/R/assertions.R index e5137102..7f56841d 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1483,7 +1483,7 @@ assert_date_vector <- function(arg, optional = TRUE) { assert_logical_scalar(optional) if (optional && is.null(arg)) { - stop() + return(invisible(arg)) } if (!is.instant(arg)) { diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 11522266..cc9fe135 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -156,3 +156,8 @@ test_that("assert_order_vars Test 13: returns errors if used incorrectly", { test_that("assert_date_vector Test 14: returns error if input vector is not a date formatted", { expect_error(assert_date_vector("2018-08-23")) }) + +## Test 15: returns invisible if input is date formatted ---- +test_that("assert_date_vector Test 15: returns invisible if input is date formatted", { + expect_invisible(assert_date_vector(as.Date("2022-10-25"))) +}) From 3db72ed7672882e22fb4cd9f695c7b155bce7cf4 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Wed, 26 Oct 2022 18:23:04 +0200 Subject: [PATCH 080/179] 145_metadata_keyword: add metadata keyword to programming strategy --- vignettes/programming_strategy.Rmd | 1 + 1 file changed, 1 insertion(+) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index a8c4ce6b..90c8008e 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -401,6 +401,7 @@ add an issue in GitHub for discussion. | `der_occds` | OCCDS specific derivation of helper Functions | | `der_prm_tte` | TTE Functions for adding Parameters to TTE Dataset | | `deprecated` | Function which will be removed from admiral after next release. See [Deprecation Guidance](#deprecation). | +| `metadata` | Auxiliary datasets providing definitions as input for derivations, e.g. grading criteria or dose frequencies | | `utils_ds_chk` | Utilities for Dataset Checking | | `utils_fil` | Utilities for Filtering Observations | | `utils_fmt` | Utilities for Formatting Observations | From f8b9e2c1d0e1ff3093a8ab982b51e0c3e36d9db9 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Thu, 27 Oct 2022 20:15:34 +0000 Subject: [PATCH 081/179] #133 - Rough draft of admiral options snippet --- docs/pkgdown.yml | 2 +- vignettes/programming_strategy.Rmd | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index b0344063..a30bc6cd 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-20T00:25Z +last_built: 2022-10-27T20:11Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 90c8008e..c1bb4422 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -100,7 +100,9 @@ i.e. all input like datasets, variable names, options, … must be provided to t * If the function needs to create temporary variables in an input dataset, these variables must start with `temp_` and must be removed from the output dataset. * If the input dataset includes variables starting with `temp_`, an error must be issued. -* The function must not have any side-effects like creating or modifying global objects, printing, writing files, ... +* In general, the function must not have any side-effects like creating or modifying global objects, printing, writing files, ... +* An exception is made for admiral options, see `get_admiral_option()` and `set_admiral_options()`, where we have certain pre-defined defaults with added flexibility to allow for user-defined defaults on *commonly used* function arguments e.g. `subject_keys` currently pre-defined as `vars(STUDYID, USUBJID)`, but can be modified using `set_admiral_options(subject_keys = vars(...))` at the top of a script. The reasoning behind this was to relieve the user of repeatedly changing aforementioned *commonly used* function arguments multiple times in a script, which may be called across many admiral functions. +* If this additional flexibility needs to be added for another *commonly used* function argument e.g. `future_input` to be set as `vars(...)` it can be added as an admiral option. In the function formals define `future_input = get_admiral_option(future_input)` then proceed to modify the body and roxygen documentation of `set_admiral_options()`. ## Function Names From f12c3d17405b0685a7d834e47e55ff22e7fa3e33 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Fri, 28 Oct 2022 00:04:52 +0200 Subject: [PATCH 082/179] added " - New function `assert_atomic_vector()` (#98)" to news.md --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 81e25219..496e99a1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,8 @@ - Developer addin for formatting tests to admiral programming standards (#73) - New functions `replace_symbol_in_quo()` and `add_suffix_to_vars()` (#106) + + - New function `assert_atomic_vector()` (#98) ## Updates of Existing Functions From ec1b4e69bdba59949bcf3c36ba29b238b4fe77af Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Fri, 28 Oct 2022 00:18:26 +0200 Subject: [PATCH 083/179] roxygen2::roxygenize('.', roclets = c('rd', 'collate', 'namespace')) --- man/assert_atomic_vector.Rd | 1 + man/assert_date_vector.Rd | 1 + 2 files changed, 2 insertions(+) diff --git a/man/assert_atomic_vector.Rd b/man/assert_atomic_vector.Rd index 3cac18a5..41a7f2d4 100644 --- a/man/assert_atomic_vector.Rd +++ b/man/assert_atomic_vector.Rd @@ -33,6 +33,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, \code{\link{assert_expr}()}, \code{\link{assert_filter_cond}()}, \code{\link{assert_function_param}()}, diff --git a/man/assert_date_vector.Rd b/man/assert_date_vector.Rd index 89737b31..63f0787d 100644 --- a/man/assert_date_vector.Rd +++ b/man/assert_date_vector.Rd @@ -31,6 +31,7 @@ try(example_fun("1993-07-14")) } \seealso{ Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, \code{\link{assert_character_scalar}()}, \code{\link{assert_character_vector}()}, \code{\link{assert_data_frame}()}, From 49ec8a6eb70e1bba3556ad7325fbef28dcceccad Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Fri, 28 Oct 2022 00:56:03 +0200 Subject: [PATCH 084/179] test coverage 100% --- tests/testthat/test-get.R | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index 8b203dfb..9d1fd975 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -41,3 +41,14 @@ test_that("get_duplicates Test 1: x atomic vector", { c("a", "d", 1) ) }) + +# get_source_vars ---- +## Test 1: x is a list of quosures ---- +test_that("get_source_vars Test 1: x is a list of quosures", { + x <- vars(DTHDOM = "AE", DTHSEQ = AESEQ) + + expect_equal( + get_source_vars(x), + x[2] + ) +}) From c6ae6df9719e17c14186ffdc1972e036309ef32e Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Fri, 28 Oct 2022 02:05:43 +0200 Subject: [PATCH 085/179] added tests 12-19 --- tests/testthat/test-assertions.R | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index ea5bfe8e..68538ac3 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -106,3 +106,82 @@ test_that("Test 11 : `assert_order_vars` returns errors if used incorrectly", { expect_error(assert_order_vars(c("USUBJID", "PARAMCD", "VISIT"))) expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) }) + +test_that("Test 12 : `assert_data_frame` doesn't throw an error if optional is TRUE and arg is NULL", { + example_fun <- function(dataset) { + assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID), optional=TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 13 : `assert_data_frame` throws an error if required variables are missing", { + example_fun <- function(dataset) { + assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) + } + + admiral_dm <- admiral_dm %>% select(-c(STUDYID,USUBJID)) + + expect_error( + example_fun(admiral_dm) + ) +}) + +test_that("Test 14 : `assert_character_scalar` doesn't throw an error if optional is TRUE and arg is NULL", { + example_fun <- function(character) { + assert_character_scalar(character, optional=TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 15 : `assert_character_scalar` doesn't throw an error if case_sensitive is FALSE", { + example_fun <- function(character) { + assert_character_scalar(character, values=c("test"), case_sensitive=FALSE) + } + + expect_invisible( + example_fun(character="TEST") + ) +}) + +test_that("Test 16 : `assert_character_scalar` throws an error if values are not NULL and arg not in values", { + example_fun <- function(character) { + assert_character_scalar(character, values=c("test")) + } + + expect_error( + example_fun(character="oak") + ) +}) + +test_that("Test 17 : `assert_character_vector` throws an error if arg is not a character vector", { + + expect_error( + assert_character_vector(c(1,2,3)) + ) +}) + +test_that("Test 18 : `assert_character_vector` throws an error if values are not NULL and arg not in values", { + example_fun <- function(character) { + assert_character_vector(character, values=c("test","oak")) + } + + expect_error( + example_fun(character=c("oak","mint")) + ) +}) + +test_that("Test 19 : `assert_logical_scalar` doesn't throw an error if optional is TRUE and arg is NULL", { + example_fun <- function(arg) { + assert_logical_scalar(arg, optional=TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) From 0ad7f8fe9e74c2d6283c97d4357e0de3027fb0d1 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 28 Oct 2022 17:13:41 +0000 Subject: [PATCH 086/179] #133 - Address comments about table and header --- docs/pkgdown.yml | 2 +- vignettes/programming_strategy.Rmd | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index a30bc6cd..cc933eeb 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-27T20:11Z +last_built: 2022-10-28T17:09Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index c1bb4422..3741c1f2 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -101,6 +101,9 @@ i.e. all input like datasets, variable names, options, … must be provided to t variables must start with `temp_` and must be removed from the output dataset. * If the input dataset includes variables starting with `temp_`, an error must be issued. * In general, the function must not have any side-effects like creating or modifying global objects, printing, writing files, ... + +## Admiral Options + * An exception is made for admiral options, see `get_admiral_option()` and `set_admiral_options()`, where we have certain pre-defined defaults with added flexibility to allow for user-defined defaults on *commonly used* function arguments e.g. `subject_keys` currently pre-defined as `vars(STUDYID, USUBJID)`, but can be modified using `set_admiral_options(subject_keys = vars(...))` at the top of a script. The reasoning behind this was to relieve the user of repeatedly changing aforementioned *commonly used* function arguments multiple times in a script, which may be called across many admiral functions. * If this additional flexibility needs to be added for another *commonly used* function argument e.g. `future_input` to be set as `vars(...)` it can be added as an admiral option. In the function formals define `future_input = get_admiral_option(future_input)` then proceed to modify the body and roxygen documentation of `set_admiral_options()`. @@ -205,10 +208,11 @@ Parameters which expect a boolean or boolean vector must start with a verb, e.g. | `filter` | Expression to filter a dataset, e.g., `PARAMCD == "TEMP"`. | | `start_date` | The start date of an event/interval. Expects a date object. | | `end_date` | The end date of an event/interval. Expects a date object. | -| `start_dtc` | (Partial) start date/datetime in ISO 8601 format. | +| `start_dtc` | (Partial) start date/datetime in ISO 8601 format. | | `dtc` | (Partial) date/datetime in ISO 8601 format. | | `date` | Date of an event / interval. Expects a date object. | | `set_values_to` | List of variable name-value pairs. | +| `subject_keys` | Variables to uniquely identify a subject, defaults to `vars(STUDYID, USUBJID)`. In function formals, use `subject_keys = get_admiral_option(subject_keys)` | ## Source Code Formatting From 0cc76c43809999544c24bfa792a0dd645a699d74 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Sat, 29 Oct 2022 00:38:19 +0200 Subject: [PATCH 087/179] added 57 tests with overall 100% test coverage on assertions.r file. editted assert_character_scalar to account for uppercase values and added instructions to make sure users only use lowercase values. --- R/assertions.R | 6 +- docs/pkgdown.yml | 2 +- man/assert_character_scalar.Rd | 3 +- tests/testthat/test-assertions.R | 709 ++++++++++++++++++++++++++++++- 4 files changed, 698 insertions(+), 22 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 550c4fb2..917e0d14 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -86,7 +86,8 @@ assert_data_frame <- function(arg, #' one of the provided `values`. #' #' @param arg A function argument to be checked -#' @param values A `character` vector of valid values for `arg` +#' @param values A `character` vector of valid values for `arg`. +#' Values should be a lower case vector if case_sensitive = FALSE is used. #' @param case_sensitive Should the argument be handled case-sensitive? #' If set to `FALSE`, the argument is converted to lower case for checking the #' permitted values and returning the argument. @@ -159,6 +160,9 @@ assert_character_scalar <- function(arg, if (!case_sensitive) { arg <- tolower(arg) + if (!is.null(values)) { + values <- tolower(values) + } } if (!is.null(values) && arg %notin% values) { diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 62e3a507..976e6365 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -9,7 +9,7 @@ articles: programming_strategy: programming_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-08-17T11:24Z +last_built: 2022-10-28T22:10Z urls: reference: https://pharmaverse.github.io/admiraldev/reference article: https://pharmaverse.github.io/admiraldev/articles diff --git a/man/assert_character_scalar.Rd b/man/assert_character_scalar.Rd index 06e7c7e1..0eec4253 100644 --- a/man/assert_character_scalar.Rd +++ b/man/assert_character_scalar.Rd @@ -14,7 +14,8 @@ assert_character_scalar( \arguments{ \item{arg}{A function argument to be checked} -\item{values}{A \code{character} vector of valid values for \code{arg}} +\item{values}{A \code{character} vector of valid values for \code{arg}. +Values should be a lower case vector if case_sensitive = FALSE is used.} \item{case_sensitive}{Should the argument be handled case-sensitive? If set to \code{FALSE}, the argument is converted to lower case for checking the diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 68538ac3..cf058ef0 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -1,13 +1,16 @@ library(admiral.test) test_that("Test 1 : `assert_has_variables` an error is thrown if a required - variable is missing", { + variable/s is missing", { data(admiral_dm) expect_error( assert_has_variables(admiral_dm, "TRT01P"), "Required variable `TRT01P` is missing." ) + expect_error( + assert_has_variables(admiral_dm, c("TRT01P", "AVAL")) + ) }) test_that("Test 2 : `assert_has_variables` no error is thrown if a required @@ -107,9 +110,10 @@ test_that("Test 11 : `assert_order_vars` returns errors if used incorrectly", { expect_error(assert_order_vars(vars(USUBJID, toupper(PARAMCD), -AVAL))) }) -test_that("Test 12 : `assert_data_frame` doesn't throw an error if optional is TRUE and arg is NULL", { +test_that("Test 12 : `assert_data_frame` doesn't throw an error + if optional is TRUE and `arg` is NULL", { example_fun <- function(dataset) { - assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID), optional=TRUE) + assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID), optional = TRUE) } expect_invisible( @@ -122,16 +126,29 @@ test_that("Test 13 : `assert_data_frame` throws an error if required variables a assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } - admiral_dm <- admiral_dm %>% select(-c(STUDYID,USUBJID)) + admiral_dm <- admiral_dm %>% select(-c(STUDYID, USUBJID)) expect_error( example_fun(admiral_dm) ) }) -test_that("Test 14 : `assert_character_scalar` doesn't throw an error if optional is TRUE and arg is NULL", { +test_that("Test 14 : `assert_data_frame` throws an error if required variable is missing", { + example_fun <- function(dataset) { + assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) + } + + admiral_dm <- admiral_dm %>% select(-c(USUBJID)) + + expect_error( + example_fun(admiral_dm) + ) +}) + +test_that("Test 15 : `assert_character_scalar` doesn't throw an error if + optional is TRUE and `arg` is NULL", { example_fun <- function(character) { - assert_character_scalar(character, optional=TRUE) + assert_character_scalar(character, optional = TRUE) } expect_invisible( @@ -139,49 +156,703 @@ test_that("Test 14 : `assert_character_scalar` doesn't throw an error if optiona ) }) -test_that("Test 15 : `assert_character_scalar` doesn't throw an error if case_sensitive is FALSE", { +test_that("Test 16 : `assert_character_scalar` doesn't throw an error if case_sensitive is FALSE", { example_fun <- function(character) { - assert_character_scalar(character, values=c("test"), case_sensitive=FALSE) + assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } expect_invisible( - example_fun(character="TEST") + example_fun(character = "TEST") ) }) -test_that("Test 16 : `assert_character_scalar` throws an error if values are not NULL and arg not in values", { +test_that("Test 17 : `assert_character_scalar` throws an error if + values are not NULL and `arg` not in values", { example_fun <- function(character) { - assert_character_scalar(character, values=c("test")) + assert_character_scalar(character, values = c("test")) } expect_error( - example_fun(character="oak") + example_fun(character = "oak") ) }) -test_that("Test 17 : `assert_character_vector` throws an error if arg is not a character vector", { +test_that("Test 18 : `assert_character_vector` throws an error if `arg` not a character vector", { + arg <- c(1, 2, 3) expect_error( - assert_character_vector(c(1,2,3)) + assert_character_vector(arg) ) }) -test_that("Test 18 : `assert_character_vector` throws an error if values are not NULL and arg not in values", { +test_that("Test 19 : `assert_character_vector` throws an error + if values are not NULL and `arg` not in values", { example_fun <- function(character) { - assert_character_vector(character, values=c("test","oak")) + assert_character_vector(character, values = c("test", "oak")) + } + + expect_error( + example_fun(character = c("oak", "mint")) + ) +}) + +test_that("Test 20 : `assert_logical_scalar` doesn't throw an error + if optional is TRUE and `arg` is NULL", { + example_fun <- function(arg) { + assert_logical_scalar(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 21 : `assert_logical_scalar` throws an error if `arg` is not TRUE or FALSE", { + example_fun <- function(arg) { + assert_logical_scalar(arg) + } + arg <- c() + expect_error(example_fun(NA)) + expect_error(example_fun(arg)) + expect_error(example_fun("test")) +}) + +test_that("Test 22 : `assert_symbol` doesn't throw an error if optional = TRUE and `arg` = NULL", { + f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_symbol(arg, optional = TRUE) + } + + expect_invisible( + example_fun( + f(NULL) + ) + ) +}) + +test_that("Test 23 : `assert_symbol` throws an error if `arg` is missing", { + f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_symbol(arg) + } + + expect_error( + example_fun( + f() + ) + ) +}) + +test_that("Test 24 : `assert_symbol` throws an error if `arg` is not a symbol", { + f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_symbol(arg) + } + + expect_error( + example_fun( + f(NULL) + ) + ) +}) + +test_that("Test 25 : `assert_symbol` doesn't throw an error if `arg` is a symbol", { + f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_symbol(arg) + } + + expect_invisible( + example_fun( + f(admiral_dm) + ) + ) +}) + +test_that("Test 26 : `assert_expr` doesn't throw an error if `arg` is an expression", { + f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_expr(arg) + } + + expect_invisible( + example_fun( + f(admiral_dm) + ) + ) +}) + +test_that("Test 27 : `assert_expr` doesn't throw an error if + optional is TRUE and `arg` is NULL", { +f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_expr(arg, optional = TRUE) + } + + expect_invisible( + example_fun( + f(NULL) + ) + ) +}) + +test_that("Test 28 : `assert_expr` throws an error if `arg` is missing", { + f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_expr(arg) + } + + expect_error( + example_fun(f()) + ) +}) + +test_that("Test 29 : `assert_expr` throws an error if `arg` is not an expression", { + f <- function(var) { + v <- enquo(var) + } + + example_fun <- function(arg) { + assert_expr(arg) + } + + expect_error( + example_fun( + f(NULL) + ) + ) +}) + +test_that("Test 30 : `assert_vars` throws an error + if `arg` is not a list of unquoted variable names", { + example_fun <- function(arg) { + assert_vars(arg) } expect_error( - example_fun(character=c("oak","mint")) + example_fun(c("USUBJID", "PARAMCD", "VISIT")) + ) + expect_error( + example_fun(TRUE) + ) +}) + +test_that("Test 31 : `assert_vars` throws an error + if some elements of `arg` are not unquoted variable names", { + example_fun <- function(arg) { + assert_vars(arg) + } + + expect_error( + example_fun(vars(USUBJID, PARAMCD, NULL)) + ) +}) + +test_that("Test 32 : `assert_order_vars` throws an error + if `arg` is not a list of unquoted variable names or `desc()` calls", { + example_fun <- function(arg) { + assert_order_vars(arg) + } + + expect_error( + example_fun(TRUE) + ) +}) + +test_that("Test 33 : `assert_order_vars` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + + example_fun <- function(arg) { + assert_order_vars(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) ) }) -test_that("Test 19 : `assert_logical_scalar` doesn't throw an error if optional is TRUE and arg is NULL", { +test_that("Test 34 : `assert_integer_scalar` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + example_fun <- function(arg) { - assert_logical_scalar(arg, optional=TRUE) + assert_integer_scalar(arg, optional = TRUE) } expect_invisible( example_fun(NULL) ) }) + +test_that("Test 35 : `assert_integer_scalar` throws an error if chosen subset not in subsets", { + + example_fun <- function(arg) { + assert_integer_scalar(arg, subset = "infinity") + } + + expect_error( + example_fun(1) + ) +}) + +test_that("Test 36 : `assert_integer_scalar` doesn't throw an error + if `arg` is an integer scalar in selected subset", { + + example_fun <- function(arg) { + assert_integer_scalar(arg, subset = "positive") + } + + expect_invisible( + example_fun(1) + ) +}) + +test_that("Test 37 : `assert_integer_scalar` throws an error + if `arg` is not an integer scalar", { + + example_fun <- function(arg) { + assert_integer_scalar(arg) + } + + arg <- c() + + expect_error(example_fun(TRUE)) + expect_error(example_fun(arg)) + expect_error(example_fun(Inf)) + expect_error(example_fun(1.5)) +}) + +test_that("Test 38 : `assert_numeric_vector` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + + example_fun <- function(arg) { + assert_numeric_vector(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 39 : `assert_integer_scalar` throws an error if `arg` is not an integer scalar", { + + example_fun <- function(arg) { + assert_numeric_vector(arg) + } + + arg <- c() + + expect_error(example_fun(TRUE)) + expect_error(example_fun(arg)) + expect_error(example_fun("1.5")) +}) + +test_that("Test 40 : `assert_s3_class` throws an error + if `arg` is not an object of a specific class S3", { + + example_fun <- function(arg) { + assert_s3_class(arg, "factor") + } + + expect_error(example_fun("test")) +}) + +test_that("Test 41 : `assert_s3_class` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + + example_fun <- function(arg) { + assert_s3_class(arg, class = "factor", optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 42 : `assert_s3_class` doesn't throw an error + if `arg` is an object of a specific class S3", { + + example_fun <- function(arg) { + assert_s3_class(arg, "factor") + } + + expect_invisible(example_fun(as.factor("test"))) +}) + +test_that("Test 43 : `assert_list_of` throws an error + if `arg` is not a list of objects of a specific class S3", { + + example_fun <- function(arg) { + assert_list_of(arg, "factor") + } + + expect_error(example_fun(list("test"))) +}) + +test_that("Test 44 : `assert_list_of` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + + example_fun <- function(arg) { + assert_list_of(arg, class = "factor", optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 45 : `assert_list_of` doesn't throw an error + if `arg` is a list of objects of a specific class S3", { + + example_fun <- function(arg) { + assert_list_of(arg, "factor") + } + + expect_invisible( + example_fun( + list(as.factor("test"), as.factor(1)) + ) + ) +}) + +test_that("Test 46 : `assert_named_exprs` throws an error + if `arg` is not a named list of expressions", { + + example_fun <- function(arg) { + assert_named_exprs(arg) + } + + arg <- list("test") + names(arg) <- c("") + + expect_error(example_fun(5)) + expect_error(example_fun(admiral_df)) + expect_error(example_fun(list(1, 2, TRUE))) + expect_error(example_fun(arg)) +}) + +test_that("Test 47 : `assert_named_exprs` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + + example_fun <- function(arg) { + assert_named_exprs(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 48 : `assert_named_exprs` doesn't throw an error + if `arg` is a named list of expressions", { + + example_fun <- function(arg) { + assert_named_exprs(arg) + } + + expect_invisible( + example_fun( + rlang::exprs() + ) + ) +}) + +test_that("Test 49 : `assert_function` throws an error + if `arg` is not a function", { + + example_fun <- function(arg) { + assert_function(arg) + } + + expect_error(example_fun(5)) + expect_error(example_fun()) +}) + +test_that("Test 50 : `assert_function` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + + example_fun <- function(arg) { + assert_function(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 51 : `assert_function` doesn't throw an error + if `arg` is a function with all parameters defined", { + + example_fun <- function(arg) { + assert_function(arg, params = c("x")) + } + + expect_invisible(example_fun(mean)) +}) + +test_that("Test 52 : `assert_function` throws an error + if `params` is missing with no default", { + + example_fun <- function(arg) { + assert_function(arg, params = c("x")) + } + + expect_error(example_fun(sum)) + + example_fun <- function(arg) { + assert_function(arg, params = c("x", "y")) + } + + expect_error(example_fun(sum)) + +}) + +test_that("Test 53 : `assert_function_param` doesn't throw an error + if `arg` is a parameter of a function", { + + hello <- function(name) { + print(sprintf("Hello %s", name)) + } + + expect_invisible(assert_function_param("hello", "name")) +}) + +test_that("Test 54 : `assert_function_param` throws an error + if any elements of `params` is not a parameter of the function given by `arg`", { + + hello <- function(name) { + print(sprintf("Hello %s", name)) + } + + expect_error(assert_function_param("hello", "surname")) + expect_error(assert_function_param("hello", params = c("surname", "firstname"))) +}) + +test_that("Test 55 : `assert_unit` doesn't throw an error + if the parameter is provided in the expected unit", { + + advs <- tibble::tribble( + ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, + "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, + "P02", "WEIGHT", 85.7, "kg", "WEIGHT", 85.7 + ) + + expect_invisible( + assert_unit(advs, param = "WEIGHT", required_unit = "kg", get_unit_expr = VSSTRESU) + ) +}) + +test_that("Test 56 : `assert_unit` throws an error + if there are multiple units in the input dataset", { + + advs <- tibble::tribble( + ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, + "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, + "P02", "WEIGHT", 85.7, "lb", "WEIGHT", 85.7 + ) + + expect_error( + assert_unit(advs, param = "WEIGHT", required_unit = "kg", get_unit_expr = VSSTRESU) + ) +}) + +test_that("Test 57 : `assert_unit` throws an error if the unit variable differs from + the unit for any observation of the parameter in the input dataset", { + + advs <- tibble::tribble( + ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, + "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, + "P02", "WEIGHT", 85.7, "kg", "WEIGHT", 85.7 + ) + + expect_error( + assert_unit(advs, param = "WEIGHT", required_unit = "lb", get_unit_expr = VSSTRESU) + ) +}) + +test_that("Test 58 : `assert_param_does_not_exist` throws an error + if the parameter exists in the input dataset", { + + advs <- tibble::tribble( + ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, + "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, + "P02", "WEIGHT", 85.7, "kg", "WEIGHT", 85.7 + ) + + expect_error( + assert_param_does_not_exist(advs, param = "WEIGHT") + ) +}) + +test_that("Test 59 : `assert_param_does_not_exist` doesn't throw an error + if the parameter exists in the input dataset", { + + advs <- tibble::tribble( + ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, + "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, + "P02", "WEIGHT", 85.7, "kg", "WEIGHT", 85.7 + ) + + expect_invisible( + assert_param_does_not_exist(advs, param = "HR") + ) +}) + +test_that("Test 60 : `assert_varval_list` throws an error + if `arg` is not a list of variable-value expressions", { + example_fun <- function(arg) { + assert_varval_list(arg, accept_var = FALSE) + } + + expect_error( + example_fun(c("USUBJID", "PARAMCD", "VISIT")) + ) + +}) + +test_that("Test 61 : `assert_varval_list` throws an error + if `arg` is not a list of variable-value expressions", { + example_fun <- function(arg) { + assert_varval_list(arg, accept_var = TRUE) + } + + expect_error( + example_fun(vars(USUBJID, PARAMCD, NULL)) + ) +}) + +test_that("Test 62 : `assert_varval_list` throws an error + if `required_elements` are missing from `arg`", { + example_fun <- function(arg) { + assert_varval_list(arg, required_elements = "DTHDOM") + } + + expect_error( + example_fun(vars(DTHSEQ = AESEQ)) + ) +}) + +test_that("Test 63 : `assert_varval_list` doesn't throw an error + if `arg` is NULL and optional is TRUE", { + + example_fun <- function(arg) { + assert_varval_list(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) + +test_that("Test 64 : `assert_varval_list` throws an error if `accept_expr` is TRUE + and the expressions in `arg` are not: a symbol, a string, a numeric , an + `NA` or an expression", { + example_fun <- function(arg) { + assert_varval_list(arg, accept_expr = TRUE) + } + + expect_error( + example_fun(vars(DTHSEQ = TRUE)) + ) +}) + +test_that("Test 65 : `assert_varval_list` throws an error if `accept_expr` is FALSE and + the expressions in `arg` are not: a symbol, a string, a numeric or `NA`", { + example_fun <- function(arg) { + assert_varval_list(arg, accept_expr = FALSE) + } + + expect_error( + example_fun(vars(DTHSEQ = vars())) + ) +}) + +test_that("Test 66 : `assert_varval_list` doesn't throw an error + if an argument is a variable-value list", { + example_fun <- function(arg) { + assert_varval_list(arg) + } + + expect_invisible( + example_fun(vars(DTHDOM = "AE", DTHSEQ = AESEQ)) + ) +}) + +test_that("Test 67 : `assert_list_element` doesn't throw an error + if the elements of a list of named lists/classes fulfill a certain condition", { + + expect_invisible( + assert_list_element(vars(DTHDOM = "AE", DTHSEQ = AESEQ), "DTHSEQ", TRUE, message_text = "") + ) + expect_invisible( + assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral_dm), "DTHSEQ", + admiral_dm$DOMAIN == "DM", message_text = "") + ) +}) + +test_that("Test 68 : `assert_list_element` throws an error + if the elements of a list of named lists/classes don't fulfill a certain condition", { + + expect_error( + assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral_dm), "DTHSEQ", + admiral_dm$DOMAIN == "GM", message_text = "") + ) +}) + +test_that("Test 69 : `assert_one_to_one` throws an error + if there is a one to many mapping between two lists of variables", { + + expect_error( + assert_one_to_one(admiral_dm, vars(DOMAIN), vars(USUBJID)) + ) +}) + +test_that("Test 70 : `assert_one_to_one` throws an error + if there is a many to one mapping between two lists of variables", { + + expect_error( + assert_one_to_one(admiral_dm, vars(USUBJID), vars(DOMAIN)) + ) +}) + +test_that("Test 71 : `assert_date_var` throws an error + if a variable in a dataset is not a date or datetime variable", { + + example_fun <- function(dataset, var) { + var <- assert_symbol(enquo(var)) + assert_date_var(dataset = dataset, var = !!var) + } + + my_data <- tibble::tribble( + ~USUBJID, ~ADT, + "1", ymd("2020-12-06"), + "2", ymd("") + ) + + expect_error( + example_fun( + dataset = my_data, + var = USUBJID + ) + ) +}) From c024f8dd04a47c571d01a405749af6b75bd67a08 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Sat, 29 Oct 2022 01:08:29 +0200 Subject: [PATCH 088/179] added library(admiral.test) --- tests/testthat/test-assertions.R | 87 ++++++++++---------------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 3c0f5682..2a8e0d61 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -264,7 +264,7 @@ test_that("Test 22 : `assert_symbol` doesn't throw an error if optional = TRUE a expect_invisible( example_fun( f(NULL) - ) + ) ) }) @@ -280,7 +280,7 @@ test_that("Test 23 : `assert_symbol` throws an error if `arg` is missing", { expect_error( example_fun( f() - ) + ) ) }) @@ -296,7 +296,7 @@ test_that("Test 24 : `assert_symbol` throws an error if `arg` is not a symbol", expect_error( example_fun( f(NULL) - ) + ) ) }) @@ -312,7 +312,7 @@ test_that("Test 25 : `assert_symbol` doesn't throw an error if `arg` is a symbol expect_invisible( example_fun( f(admiral_dm) - ) + ) ) }) @@ -329,13 +329,13 @@ test_that("Test 26 : `assert_expr` doesn't throw an error if `arg` is an express expect_invisible( example_fun( f(admiral_dm) - ) + ) ) }) test_that("Test 27 : `assert_expr` doesn't throw an error if optional is TRUE and `arg` is NULL", { -f <- function(var) { + f <- function(var) { v <- enquo(var) } @@ -346,7 +346,7 @@ f <- function(var) { expect_invisible( example_fun( f(NULL) - ) + ) ) }) @@ -376,7 +376,7 @@ test_that("Test 29 : `assert_expr` throws an error if `arg` is not an expression expect_error( example_fun( f(NULL) - ) + ) ) }) @@ -397,7 +397,7 @@ test_that("Test 30 : `assert_vars` throws an error test_that("Test 31 : `assert_vars` throws an error if some elements of `arg` are not unquoted variable names", { - example_fun <- function(arg) { + example_fun <- function(arg) { assert_vars(arg) } @@ -420,7 +420,6 @@ test_that("Test 32 : `assert_order_vars` throws an error test_that("Test 33 : `assert_order_vars` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_order_vars(arg, optional = TRUE) } @@ -433,7 +432,6 @@ test_that("Test 33 : `assert_order_vars` doesn't throw an error # assert_integer_scalar ---- test_that("Test 34 : `assert_integer_scalar` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_integer_scalar(arg, optional = TRUE) } @@ -444,7 +442,6 @@ test_that("Test 34 : `assert_integer_scalar` doesn't throw an error }) test_that("Test 35 : `assert_integer_scalar` throws an error if chosen subset not in subsets", { - example_fun <- function(arg) { assert_integer_scalar(arg, subset = "infinity") } @@ -456,7 +453,6 @@ test_that("Test 35 : `assert_integer_scalar` throws an error if chosen subset no test_that("Test 36 : `assert_integer_scalar` doesn't throw an error if `arg` is an integer scalar in selected subset", { - example_fun <- function(arg) { assert_integer_scalar(arg, subset = "positive") } @@ -468,7 +464,6 @@ test_that("Test 36 : `assert_integer_scalar` doesn't throw an error test_that("Test 37 : `assert_integer_scalar` throws an error if `arg` is not an integer scalar", { - example_fun <- function(arg) { assert_integer_scalar(arg) } @@ -484,7 +479,6 @@ test_that("Test 37 : `assert_integer_scalar` throws an error # assert_numeric_vector ---- test_that("Test 38 : `assert_numeric_vector` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_numeric_vector(arg, optional = TRUE) } @@ -496,7 +490,6 @@ test_that("Test 38 : `assert_numeric_vector` doesn't throw an error # assert_integer_scalar ---- test_that("Test 39 : `assert_integer_scalar` throws an error if `arg` is not an integer scalar", { - example_fun <- function(arg) { assert_numeric_vector(arg) } @@ -511,7 +504,6 @@ test_that("Test 39 : `assert_integer_scalar` throws an error if `arg` is not an # assert_s3_class ---- test_that("Test 40 : `assert_s3_class` throws an error if `arg` is not an object of a specific class S3", { - example_fun <- function(arg) { assert_s3_class(arg, "factor") } @@ -521,7 +513,6 @@ test_that("Test 40 : `assert_s3_class` throws an error test_that("Test 41 : `assert_s3_class` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_s3_class(arg, class = "factor", optional = TRUE) } @@ -533,7 +524,6 @@ test_that("Test 41 : `assert_s3_class` doesn't throw an error test_that("Test 42 : `assert_s3_class` doesn't throw an error if `arg` is an object of a specific class S3", { - example_fun <- function(arg) { assert_s3_class(arg, "factor") } @@ -544,7 +534,6 @@ test_that("Test 42 : `assert_s3_class` doesn't throw an error # assert_list_of ---- test_that("Test 43 : `assert_list_of` throws an error if `arg` is not a list of objects of a specific class S3", { - example_fun <- function(arg) { assert_list_of(arg, "factor") } @@ -554,7 +543,6 @@ test_that("Test 43 : `assert_list_of` throws an error test_that("Test 44 : `assert_list_of` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_list_of(arg, class = "factor", optional = TRUE) } @@ -566,7 +554,6 @@ test_that("Test 44 : `assert_list_of` doesn't throw an error test_that("Test 45 : `assert_list_of` doesn't throw an error if `arg` is a list of objects of a specific class S3", { - example_fun <- function(arg) { assert_list_of(arg, "factor") } @@ -574,14 +561,13 @@ test_that("Test 45 : `assert_list_of` doesn't throw an error expect_invisible( example_fun( list(as.factor("test"), as.factor(1)) - ) ) + ) }) # assert_named_exprs ---- test_that("Test 46 : `assert_named_exprs` throws an error if `arg` is not a named list of expressions", { - example_fun <- function(arg) { assert_named_exprs(arg) } @@ -597,7 +583,6 @@ test_that("Test 46 : `assert_named_exprs` throws an error test_that("Test 47 : `assert_named_exprs` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_named_exprs(arg, optional = TRUE) } @@ -609,7 +594,6 @@ test_that("Test 47 : `assert_named_exprs` doesn't throw an error test_that("Test 48 : `assert_named_exprs` doesn't throw an error if `arg` is a named list of expressions", { - example_fun <- function(arg) { assert_named_exprs(arg) } @@ -617,14 +601,13 @@ test_that("Test 48 : `assert_named_exprs` doesn't throw an error expect_invisible( example_fun( rlang::exprs() - ) ) + ) }) # assert_function ---- test_that("Test 49 : `assert_function` throws an error if `arg` is not a function", { - example_fun <- function(arg) { assert_function(arg) } @@ -635,7 +618,6 @@ test_that("Test 49 : `assert_function` throws an error test_that("Test 50 : `assert_function` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_function(arg, optional = TRUE) } @@ -647,7 +629,6 @@ test_that("Test 50 : `assert_function` doesn't throw an error test_that("Test 51 : `assert_function` doesn't throw an error if `arg` is a function with all parameters defined", { - example_fun <- function(arg) { assert_function(arg, params = c("x")) } @@ -657,7 +638,6 @@ test_that("Test 51 : `assert_function` doesn't throw an error test_that("Test 52 : `assert_function` throws an error if `params` is missing with no default", { - example_fun <- function(arg) { assert_function(arg, params = c("x")) } @@ -669,14 +649,12 @@ test_that("Test 52 : `assert_function` throws an error } expect_error(example_fun(sum)) - }) # assert_function_param ---- test_that("Test 53 : `assert_function_param` doesn't throw an error if `arg` is a parameter of a function", { - hello <- function(name) { print(sprintf("Hello %s", name)) } @@ -686,7 +664,6 @@ test_that("Test 53 : `assert_function_param` doesn't throw an error test_that("Test 54 : `assert_function_param` throws an error if any elements of `params` is not a parameter of the function given by `arg`", { - hello <- function(name) { print(sprintf("Hello %s", name)) } @@ -698,11 +675,10 @@ test_that("Test 54 : `assert_function_param` throws an error # assert_unit ---- test_that("Test 55 : `assert_unit` doesn't throw an error if the parameter is provided in the expected unit", { - advs <- tibble::tribble( - ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, - "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, - "P02", "WEIGHT", 85.7, "kg", "WEIGHT", 85.7 + ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, + "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, + "P02", "WEIGHT", 85.7, "kg", "WEIGHT", 85.7 ) expect_invisible( @@ -712,7 +688,6 @@ test_that("Test 55 : `assert_unit` doesn't throw an error test_that("Test 56 : `assert_unit` throws an error if there are multiple units in the input dataset", { - advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -726,7 +701,6 @@ test_that("Test 56 : `assert_unit` throws an error test_that("Test 57 : `assert_unit` throws an error if the unit variable differs from the unit for any observation of the parameter in the input dataset", { - advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -741,7 +715,6 @@ test_that("Test 57 : `assert_unit` throws an error if the unit variable differs # assert_param_does_not_exist ---- test_that("Test 58 : `assert_param_does_not_exist` throws an error if the parameter exists in the input dataset", { - advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -755,7 +728,6 @@ test_that("Test 58 : `assert_param_does_not_exist` throws an error test_that("Test 59 : `assert_param_does_not_exist` doesn't throw an error if the parameter exists in the input dataset", { - advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -777,7 +749,6 @@ test_that("Test 60 : `assert_varval_list` throws an error expect_error( example_fun(c("USUBJID", "PARAMCD", "VISIT")) ) - }) test_that("Test 61 : `assert_varval_list` throws an error @@ -804,7 +775,6 @@ test_that("Test 62 : `assert_varval_list` throws an error test_that("Test 63 : `assert_varval_list` doesn't throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { assert_varval_list(arg, optional = TRUE) } @@ -851,56 +821,55 @@ test_that("Test 66 : `assert_varval_list` doesn't throw an error # assert_list_element ---- test_that("Test 67 : `assert_list_element` doesn't throw an error if the elements of a list of named lists/classes fulfill a certain condition", { - expect_invisible( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = AESEQ), "DTHSEQ", TRUE, message_text = "") ) expect_invisible( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral_dm), "DTHSEQ", - admiral_dm$DOMAIN == "DM", message_text = "") + admiral_dm$DOMAIN == "DM", + message_text = "" + ) ) }) test_that("Test 68 : `assert_list_element` throws an error if the elements of a list of named lists/classes don't fulfill a certain condition", { - expect_error( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral_dm), "DTHSEQ", - admiral_dm$DOMAIN == "GM", message_text = "") + admiral_dm$DOMAIN == "GM", + message_text = "" + ) ) }) # assert_one_to_one ---- test_that("Test 69 : `assert_one_to_one` throws an error if there is a one to many mapping between two lists of variables", { - expect_error( assert_one_to_one(admiral_dm, vars(DOMAIN), vars(USUBJID)) - ) + ) }) test_that("Test 70 : `assert_one_to_one` throws an error if there is a many to one mapping between two lists of variables", { - expect_error( assert_one_to_one(admiral_dm, vars(USUBJID), vars(DOMAIN)) - ) + ) }) # assert_date_var ---- test_that("Test 71 : `assert_date_var` throws an error if a variable in a dataset is not a date or datetime variable", { - example_fun <- function(dataset, var) { - var <- assert_symbol(enquo(var)) - assert_date_var(dataset = dataset, var = !!var) + var <- assert_symbol(enquo(var)) + assert_date_var(dataset = dataset, var = !!var) } my_data <- tibble::tribble( - ~USUBJID, ~ADT, - "1", ymd("2020-12-06"), - "2", ymd("") - ) + ~USUBJID, ~ADT, + "1", ymd("2020-12-06"), + "2", ymd("") + ) expect_error( example_fun( From 328d26529ee649c38f75592a562a0ba1328d6983 Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Sat, 29 Oct 2022 01:28:27 +0200 Subject: [PATCH 089/179] admiral.test added to test_that --- tests/testthat/test-assertions.R | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 2a8e0d61..917f0409 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -6,13 +6,13 @@ test_that("assert_has_variables Test 1: error if a required variable is missing" "1" ) - expect_error( assert_has_variables(data, "TRT01P"), "Required variable `TRT01P` is missing." ) + expect_error( - assert_has_variables(admiral_dm, c("TRT01P", "AVAL")) + assert_has_variables(admiral.test::admiral_dm, c("TRT01P", "AVAL")) ) }) @@ -158,7 +158,7 @@ test_that("Test 13 : `assert_data_frame` throws an error if required variables a assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } - admiral_dm <- admiral_dm %>% select(-c(STUDYID, USUBJID)) + admiral_dm <- admiral.test::admiral_dm %>% select(-c(STUDYID, USUBJID)) expect_error( example_fun(admiral_dm) @@ -170,7 +170,7 @@ test_that("Test 14 : `assert_data_frame` throws an error if required variable is assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } - admiral_dm <- admiral_dm %>% select(-c(USUBJID)) + admiral_dm <- admiral.test::admiral_dm %>% select(-c(USUBJID)) expect_error( example_fun(admiral_dm) @@ -309,6 +309,8 @@ test_that("Test 25 : `assert_symbol` doesn't throw an error if `arg` is a symbol assert_symbol(arg) } + library(admiral.test) + expect_invisible( example_fun( f(admiral_dm) @@ -328,7 +330,7 @@ test_that("Test 26 : `assert_expr` doesn't throw an error if `arg` is an express expect_invisible( example_fun( - f(admiral_dm) + f(admiral.test::admiral_dm) ) ) }) @@ -821,11 +823,12 @@ test_that("Test 66 : `assert_varval_list` doesn't throw an error # assert_list_element ---- test_that("Test 67 : `assert_list_element` doesn't throw an error if the elements of a list of named lists/classes fulfill a certain condition", { + library(admiral.test) expect_invisible( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = AESEQ), "DTHSEQ", TRUE, message_text = "") ) expect_invisible( - assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral_dm), "DTHSEQ", + assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral.test::admiral_dm), "DTHSEQ", admiral_dm$DOMAIN == "DM", message_text = "" ) @@ -835,7 +838,7 @@ test_that("Test 67 : `assert_list_element` doesn't throw an error test_that("Test 68 : `assert_list_element` throws an error if the elements of a list of named lists/classes don't fulfill a certain condition", { expect_error( - assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral_dm), "DTHSEQ", + assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral.test::admiral_dm), "DTHSEQ", admiral_dm$DOMAIN == "GM", message_text = "" ) @@ -846,14 +849,14 @@ test_that("Test 68 : `assert_list_element` throws an error test_that("Test 69 : `assert_one_to_one` throws an error if there is a one to many mapping between two lists of variables", { expect_error( - assert_one_to_one(admiral_dm, vars(DOMAIN), vars(USUBJID)) + assert_one_to_one(admiral.test::admiral_dm, vars(DOMAIN), vars(USUBJID)) ) }) test_that("Test 70 : `assert_one_to_one` throws an error if there is a many to one mapping between two lists of variables", { expect_error( - assert_one_to_one(admiral_dm, vars(USUBJID), vars(DOMAIN)) + assert_one_to_one(admiral.test::admiral_dm, vars(USUBJID), vars(DOMAIN)) ) }) @@ -881,11 +884,11 @@ test_that("Test 71 : `assert_date_var` throws an error # assert_date_vector ---- ## Test 72: returns error if input vector is not a date formatted ---- -test_that("assert_date_vector Test 14: returns error if input vector is not a date formatted", { +test_that("assert_date_vector Test 72: returns error if input vector is not a date formatted", { expect_error(assert_date_vector("2018-08-23")) }) ## Test 73: returns invisible if input is date formatted ---- -test_that("assert_date_vector Test 15: returns invisible if input is date formatted", { +test_that("assert_date_vector Test 73: returns invisible if input is date formatted", { expect_invisible(assert_date_vector(as.Date("2022-10-25"))) }) From 37398390bdaf5f6a1fd1511c98583914c3358916 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Fri, 28 Oct 2022 23:31:48 +0000 Subject: [PATCH 090/179] Updated the code for the latest R version --- vignettes/pr_review_guidance.Rmd | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vignettes/pr_review_guidance.Rmd b/vignettes/pr_review_guidance.Rmd index 7a6d7935..d74331c2 100644 --- a/vignettes/pr_review_guidance.Rmd +++ b/vignettes/pr_review_guidance.Rmd @@ -177,12 +177,12 @@ for (pkg in base_recommended_pkgs) { assign(".lib.loc", ".library", envir = environment(.libPaths)) r_version <- getRversion() -if ([grepl](https://rdrr.io/r/base/grep.html)("^4.0", r_version)) { - [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2021-03-31") -} else if ([grepl](https://rdrr.io/r/base/grep.html)("^4.1", r_version)) { - [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2022-03-10") -} else if ([grepl](https://rdrr.io/r/base/grep.html)("^4.2", r_version)) { - [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.microsoft.com/snapshot/2022-06-23") +if ([grepl]("^4.0", r_version)) { + [options](repos = "https://cran.microsoft.com/snapshot/2021-03-31") +} else if ([grepl]("^4.1", r_version)) { + [options](repos = "https://cran.microsoft.com/snapshot/2022-03-10") +} else if ([grepl]("release", r_version)) { + [options](repos = "https://cran.microsoft.com/snapshot/2022-06-23") } else { [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.rstudio.com") } From a6968ceb5883f31a0e6d59ba16fc4579ae8c5582 Mon Sep 17 00:00:00 2001 From: AniaGolab <68300655+AniaGolab@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:13:18 -0700 Subject: [PATCH 091/179] Update tests/testthat/test-assertions.R Co-authored-by: Ben Straub --- tests/testthat/test-assertions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 917f0409..6b92f205 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -142,7 +142,7 @@ test_that("assert_vars Test 11: error if unexpected input", { }) # assert_data_frame ---- -test_that("Test 12 : `assert_data_frame` doesn't throw an error +test_that("Test 12 : `assert_data_frame` does not throw an error if optional is TRUE and `arg` is NULL", { example_fun <- function(dataset) { assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID), optional = TRUE) From 28c00b1d1e8285c2659f8b1c4bef0581b3ffd4db Mon Sep 17 00:00:00 2001 From: AniaGolab <68300655+AniaGolab@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:13:27 -0700 Subject: [PATCH 092/179] Update tests/testthat/test-assertions.R Co-authored-by: Ben Straub --- tests/testthat/test-assertions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 6b92f205..f2b35379 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -189,7 +189,7 @@ test_that("Test 15 : `assert_character_scalar` doesn't throw an error if ) }) -test_that("Test 16 : `assert_character_scalar` doesn't throw an error if case_sensitive is FALSE", { +test_that("Test 16 : `assert_character_scalar` does not throw an error if case_sensitive is FALSE", { example_fun <- function(character) { assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } From 046ced99ec447dfb4964611228ae2095dd0d4b42 Mon Sep 17 00:00:00 2001 From: AniaGolab <68300655+AniaGolab@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:13:38 -0700 Subject: [PATCH 093/179] Update tests/testthat/test-assertions.R Co-authored-by: Ben Straub --- tests/testthat/test-assertions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index f2b35379..e0bbd706 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -219,7 +219,7 @@ test_that("Test 18 : `assert_character_vector` throws an error if `arg` not a ch }) test_that("Test 19 : `assert_character_vector` throws an error - if values are not NULL and `arg` not in values", { + if values are not NULL and `arg` is not in values", { example_fun <- function(character) { assert_character_vector(character, values = c("test", "oak")) } From 802273d9751b4f4359366ab7361447a5f13a53d1 Mon Sep 17 00:00:00 2001 From: AniaGolab <68300655+AniaGolab@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:13:52 -0700 Subject: [PATCH 094/179] Update tests/testthat/test-assertions.R Co-authored-by: Ben Straub --- tests/testthat/test-assertions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index e0bbd706..1f82c91f 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -252,7 +252,7 @@ test_that("Test 21 : `assert_logical_scalar` throws an error if `arg` is not TRU }) # assert_symbol ---- -test_that("Test 22 : `assert_symbol` doesn't throw an error if optional = TRUE and `arg` = NULL", { +test_that("Test 22 : `assert_symbol` does not throw an error if optional = TRUE and `arg` = NULL", { f <- function(var) { v <- enquo(var) } From 4b6080b04c28cb6b71eaae49dc9e279a04d67dfa Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Mon, 31 Oct 2022 20:39:50 +0100 Subject: [PATCH 095/179] 1. grammar issues addressed. 2. library(admiral.test) removed. 3. added 2 more checks for new function added => currently at 100% in coverage on assertions.R. --- tests/testthat/test-assertions.R | 78 +++++++++++++++++++------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 1f82c91f..1f0187a0 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -178,7 +178,7 @@ test_that("Test 14 : `assert_data_frame` throws an error if required variable is }) # assert_character_scalar ---- -test_that("Test 15 : `assert_character_scalar` doesn't throw an error if +test_that("Test 15 : `assert_character_scalar` does not throw an error if optional is TRUE and `arg` is NULL", { example_fun <- function(character) { assert_character_scalar(character, optional = TRUE) @@ -189,7 +189,8 @@ test_that("Test 15 : `assert_character_scalar` doesn't throw an error if ) }) -test_that("Test 16 : `assert_character_scalar` does not throw an error if case_sensitive is FALSE", { +test_that("Test 16 : `assert_character_scalar` does not throw an error if + case_sensitive is FALSE", { example_fun <- function(character) { assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } @@ -230,7 +231,7 @@ test_that("Test 19 : `assert_character_vector` throws an error }) # assert_logical_scalar ---- -test_that("Test 20 : `assert_logical_scalar` doesn't throw an error +test_that("Test 20 : `assert_logical_scalar` does not throw an error if optional is TRUE and `arg` is NULL", { example_fun <- function(arg) { assert_logical_scalar(arg, optional = TRUE) @@ -300,17 +301,17 @@ test_that("Test 24 : `assert_symbol` throws an error if `arg` is not a symbol", ) }) -test_that("Test 25 : `assert_symbol` doesn't throw an error if `arg` is a symbol", { +test_that("Test 25 : `assert_symbol` does not throw an error if `arg` is a symbol", { f <- function(var) { v <- enquo(var) } + admiral_dm <- admiral.test::admiral_dm + example_fun <- function(arg) { assert_symbol(arg) } - library(admiral.test) - expect_invisible( example_fun( f(admiral_dm) @@ -319,7 +320,7 @@ test_that("Test 25 : `assert_symbol` doesn't throw an error if `arg` is a symbol }) # assert_expr ---- -test_that("Test 26 : `assert_expr` doesn't throw an error if `arg` is an expression", { +test_that("Test 26 : `assert_expr` does not throw an error if `arg` is an expression", { f <- function(var) { v <- enquo(var) } @@ -335,7 +336,7 @@ test_that("Test 26 : `assert_expr` doesn't throw an error if `arg` is an express ) }) -test_that("Test 27 : `assert_expr` doesn't throw an error if +test_that("Test 27 : `assert_expr` does not throw an error if optional is TRUE and `arg` is NULL", { f <- function(var) { v <- enquo(var) @@ -418,9 +419,12 @@ test_that("Test 32 : `assert_order_vars` throws an error expect_error( example_fun(TRUE) ) + expect_error( + example_fun(1) + ) }) -test_that("Test 33 : `assert_order_vars` doesn't throw an error +test_that("Test 33 : `assert_order_vars` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_order_vars(arg, optional = TRUE) @@ -432,7 +436,7 @@ test_that("Test 33 : `assert_order_vars` doesn't throw an error }) # assert_integer_scalar ---- -test_that("Test 34 : `assert_integer_scalar` doesn't throw an error +test_that("Test 34 : `assert_integer_scalar` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_integer_scalar(arg, optional = TRUE) @@ -453,7 +457,7 @@ test_that("Test 35 : `assert_integer_scalar` throws an error if chosen subset no ) }) -test_that("Test 36 : `assert_integer_scalar` doesn't throw an error +test_that("Test 36 : `assert_integer_scalar` does not throw an error if `arg` is an integer scalar in selected subset", { example_fun <- function(arg) { assert_integer_scalar(arg, subset = "positive") @@ -479,7 +483,7 @@ test_that("Test 37 : `assert_integer_scalar` throws an error }) # assert_numeric_vector ---- -test_that("Test 38 : `assert_numeric_vector` doesn't throw an error +test_that("Test 38 : `assert_numeric_vector` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_numeric_vector(arg, optional = TRUE) @@ -513,7 +517,7 @@ test_that("Test 40 : `assert_s3_class` throws an error expect_error(example_fun("test")) }) -test_that("Test 41 : `assert_s3_class` doesn't throw an error +test_that("Test 41 : `assert_s3_class` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_s3_class(arg, class = "factor", optional = TRUE) @@ -524,7 +528,7 @@ test_that("Test 41 : `assert_s3_class` doesn't throw an error ) }) -test_that("Test 42 : `assert_s3_class` doesn't throw an error +test_that("Test 42 : `assert_s3_class` does not throw an error if `arg` is an object of a specific class S3", { example_fun <- function(arg) { assert_s3_class(arg, "factor") @@ -543,7 +547,7 @@ test_that("Test 43 : `assert_list_of` throws an error expect_error(example_fun(list("test"))) }) -test_that("Test 44 : `assert_list_of` doesn't throw an error +test_that("Test 44 : `assert_list_of` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_list_of(arg, class = "factor", optional = TRUE) @@ -554,7 +558,7 @@ test_that("Test 44 : `assert_list_of` doesn't throw an error ) }) -test_that("Test 45 : `assert_list_of` doesn't throw an error +test_that("Test 45 : `assert_list_of` does not throw an error if `arg` is a list of objects of a specific class S3", { example_fun <- function(arg) { assert_list_of(arg, "factor") @@ -578,12 +582,12 @@ test_that("Test 46 : `assert_named_exprs` throws an error names(arg) <- c("") expect_error(example_fun(5)) - expect_error(example_fun(admiral_df)) + expect_error(example_fun(admiral.test::admiral_dm)) expect_error(example_fun(list(1, 2, TRUE))) expect_error(example_fun(arg)) }) -test_that("Test 47 : `assert_named_exprs` doesn't throw an error +test_that("Test 47 : `assert_named_exprs` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_named_exprs(arg, optional = TRUE) @@ -594,7 +598,7 @@ test_that("Test 47 : `assert_named_exprs` doesn't throw an error ) }) -test_that("Test 48 : `assert_named_exprs` doesn't throw an error +test_that("Test 48 : `assert_named_exprs` does not throw an error if `arg` is a named list of expressions", { example_fun <- function(arg) { assert_named_exprs(arg) @@ -618,7 +622,7 @@ test_that("Test 49 : `assert_function` throws an error expect_error(example_fun()) }) -test_that("Test 50 : `assert_function` doesn't throw an error +test_that("Test 50 : `assert_function` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_function(arg, optional = TRUE) @@ -629,7 +633,7 @@ test_that("Test 50 : `assert_function` doesn't throw an error ) }) -test_that("Test 51 : `assert_function` doesn't throw an error +test_that("Test 51 : `assert_function` does not throw an error if `arg` is a function with all parameters defined", { example_fun <- function(arg) { assert_function(arg, params = c("x")) @@ -655,7 +659,7 @@ test_that("Test 52 : `assert_function` throws an error # assert_function_param ---- -test_that("Test 53 : `assert_function_param` doesn't throw an error +test_that("Test 53 : `assert_function_param` does not throw an error if `arg` is a parameter of a function", { hello <- function(name) { print(sprintf("Hello %s", name)) @@ -675,7 +679,7 @@ test_that("Test 54 : `assert_function_param` throws an error }) # assert_unit ---- -test_that("Test 55 : `assert_unit` doesn't throw an error +test_that("Test 55 : `assert_unit` does not throw an error if the parameter is provided in the expected unit", { advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, @@ -728,7 +732,7 @@ test_that("Test 58 : `assert_param_does_not_exist` throws an error ) }) -test_that("Test 59 : `assert_param_does_not_exist` doesn't throw an error +test_that("Test 59 : `assert_param_does_not_exist` does not throw an error if the parameter exists in the input dataset", { advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, @@ -775,7 +779,7 @@ test_that("Test 62 : `assert_varval_list` throws an error ) }) -test_that("Test 63 : `assert_varval_list` doesn't throw an error +test_that("Test 63 : `assert_varval_list` does not throw an error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_varval_list(arg, optional = TRUE) @@ -809,7 +813,7 @@ test_that("Test 65 : `assert_varval_list` throws an error if `accept_expr` is FA ) }) -test_that("Test 66 : `assert_varval_list` doesn't throw an error +test_that("Test 66 : `assert_varval_list` does not throw an error if an argument is a variable-value list", { example_fun <- function(arg) { assert_varval_list(arg) @@ -821,25 +825,25 @@ test_that("Test 66 : `assert_varval_list` doesn't throw an error }) # assert_list_element ---- -test_that("Test 67 : `assert_list_element` doesn't throw an error +test_that("Test 67 : `assert_list_element` does not throw an error if the elements of a list of named lists/classes fulfill a certain condition", { - library(admiral.test) + expect_invisible( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = AESEQ), "DTHSEQ", TRUE, message_text = "") ) expect_invisible( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral.test::admiral_dm), "DTHSEQ", - admiral_dm$DOMAIN == "DM", + (admiral.test::admiral_dm)$DOMAIN == "DM", message_text = "" ) ) }) test_that("Test 68 : `assert_list_element` throws an error - if the elements of a list of named lists/classes don't fulfill a certain condition", { + if the elements of a list of named lists/classes do not fulfill a certain condition", { expect_error( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral.test::admiral_dm), "DTHSEQ", - admiral_dm$DOMAIN == "GM", + (admiral.test::admiral_dm)$DOMAIN == "GM", message_text = "" ) ) @@ -892,3 +896,15 @@ test_that("assert_date_vector Test 72: returns error if input vector is not a da test_that("assert_date_vector Test 73: returns invisible if input is date formatted", { expect_invisible(assert_date_vector(as.Date("2022-10-25"))) }) + +## Test 74: returns invisible if `arg` is NULL and optional is TRUE ---- +test_that("Test 74 : `assert_date_vector` does not throw an error + if `arg` is NULL and optional is TRUE", { + example_fun <- function(arg) { + assert_date_vector(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) + }) From a49c680c49cba4ecebb8497fe1ff43f16510534f Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Mon, 31 Oct 2022 20:44:52 +0100 Subject: [PATCH 096/179] styler::style_file("tests/testthat/test-assertions.R") --- tests/testthat/test-assertions.R | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 1f0187a0..58d85c52 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -827,7 +827,6 @@ test_that("Test 66 : `assert_varval_list` does not throw an error # assert_list_element ---- test_that("Test 67 : `assert_list_element` does not throw an error if the elements of a list of named lists/classes fulfill a certain condition", { - expect_invisible( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = AESEQ), "DTHSEQ", TRUE, message_text = "") ) @@ -900,11 +899,11 @@ test_that("assert_date_vector Test 73: returns invisible if input is date format ## Test 74: returns invisible if `arg` is NULL and optional is TRUE ---- test_that("Test 74 : `assert_date_vector` does not throw an error if `arg` is NULL and optional is TRUE", { - example_fun <- function(arg) { - assert_date_vector(arg, optional = TRUE) - } - - expect_invisible( - example_fun(NULL) - ) - }) + example_fun <- function(arg) { + assert_date_vector(arg, optional = TRUE) + } + + expect_invisible( + example_fun(NULL) + ) +}) From 082a452b5c4946dba6600aa547be898f383a45bc Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 1 Nov 2022 16:39:57 +0000 Subject: [PATCH 097/179] #133 - Adjust for new character argument --- vignettes/programming_strategy.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 3741c1f2..e0c15643 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -105,7 +105,7 @@ variables must start with `temp_` and must be removed from the output dataset. ## Admiral Options * An exception is made for admiral options, see `get_admiral_option()` and `set_admiral_options()`, where we have certain pre-defined defaults with added flexibility to allow for user-defined defaults on *commonly used* function arguments e.g. `subject_keys` currently pre-defined as `vars(STUDYID, USUBJID)`, but can be modified using `set_admiral_options(subject_keys = vars(...))` at the top of a script. The reasoning behind this was to relieve the user of repeatedly changing aforementioned *commonly used* function arguments multiple times in a script, which may be called across many admiral functions. -* If this additional flexibility needs to be added for another *commonly used* function argument e.g. `future_input` to be set as `vars(...)` it can be added as an admiral option. In the function formals define `future_input = get_admiral_option(future_input)` then proceed to modify the body and roxygen documentation of `set_admiral_options()`. +* If this additional flexibility needs to be added for another *commonly used* function argument e.g. `future_input` to be set as `vars(...)` it can be added as an admiral option. In the function formals define `future_input = get_admiral_option("future_input")` then proceed to modify the body and roxygen documentation of `set_admiral_options()`. ## Function Names @@ -212,7 +212,7 @@ Parameters which expect a boolean or boolean vector must start with a verb, e.g. | `dtc` | (Partial) date/datetime in ISO 8601 format. | | `date` | Date of an event / interval. Expects a date object. | | `set_values_to` | List of variable name-value pairs. | -| `subject_keys` | Variables to uniquely identify a subject, defaults to `vars(STUDYID, USUBJID)`. In function formals, use `subject_keys = get_admiral_option(subject_keys)` | +| `subject_keys` | Variables to uniquely identify a subject, defaults to `vars(STUDYID, USUBJID)`. In function formals, use `subject_keys = get_admiral_option("subject_keys")` | ## Source Code Formatting From 859721e118dcec07de3ad5e7b2d79f032c418f0b Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 2 Nov 2022 17:01:15 +0000 Subject: [PATCH 098/179] #144 - Remove mention of move_adm_dev --- docs/pkgdown.yml | 2 +- vignettes/programming_strategy.Rmd | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index b0344063..646ffbc9 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-20T00:25Z +last_built: 2022-11-02T16:58Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 90c8008e..9be83d0c 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -408,8 +408,7 @@ add an issue in GitHub for discussion. | `utils_help` | Utilities used within Derivation functions | | `utils_examples` | Utilities used for examples and template scripts | | `source_specifications` | Source Specifications | -| `high_order_function` | Higher Order Functions | -| `move_adm_dev` | Functions moved to `admiraldev` package [Link TBA] | +| `high_order_function` | Higher Order Functions | | | `internal` | Internal functions only available to admiral developers | | | | | `assertion`* | Asserts a certain type and gives warning, error to user | From 774cd9fc0aaff64ed584a085f23db4df5f364d5f Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 2 Nov 2022 17:59:57 +0000 Subject: [PATCH 099/179] #116 - Initial draft of deprecation testing guidance --- docs/pkgdown.yml | 2 +- vignettes/programming_strategy.Rmd | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index b0344063..997f59f5 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-10-20T00:25Z +last_built: 2022-11-02T17:55Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 90c8008e..3024d33e 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -583,7 +583,26 @@ will need to be quoted. ## Unit Testing Unit tests for deprecated functions and parameters must be added to -`tests/testthat/test-deprecation.R` to ensure that a warning or error is issued. +`tests/testthat/test-deprecation.R` to ensure that a warning or error is issued. + +When writing the unit test, check that the error or warning has the right class, i.e. "lifecycle_error_deprecated" or "lifecycle_warning_deprecated", respectively. The expect statement of the unit-test should follow the corresponding format: + +``` +# For deprecated functions that issues error +expect_error( + deprecated_function_error(), + class = "lifecycle_error_deprecated" +) +``` + +``` +# For deprecated functions that issues warning +expect_warning( + deprecated_function_warning(), + class = "lifecycle_warning_deprecated" +) +``` + Other unit tests of deprecated functions must be removed. From 02d2b623ac8e4157364049b0182ffe9147bd5d85 Mon Sep 17 00:00:00 2001 From: pharmaverse-bot <113703390+pharmaverse-bot@users.noreply.github.com> Date: Thu, 3 Nov 2022 13:23:13 +0000 Subject: [PATCH 100/179] Propagate renv.lock from pharmaverse/admiralci (#148) --- .github/workflows/common.yml | 16 +- DESCRIPTION | 2 +- renv.lock | 1258 +++++++++++++++++++++++++++------- renv/.gitignore | 2 + renv/activate.R | 807 +++++++++++++++------- 5 files changed, 1609 insertions(+), 476 deletions(-) diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index 9517d697..518c64af 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -45,25 +45,25 @@ jobs: uses: pharmaverse/admiralci/.github/workflows/style.yml@main if: github.event_name == 'pull_request' with: - r-version: $R_VERSION + r-version: "4.0" spellcheck: name: Spelling uses: pharmaverse/admiralci/.github/workflows/spellcheck.yml@main if: github.event_name == 'pull_request' with: - r-version: $R_VERSION + r-version: "4.0" readme: name: Render README uses: pharmaverse/admiralci/.github/workflows/readme-render.yml@main if: github.event_name == 'push' with: - r-version: $R_VERSION + r-version: "4.0" validation: name: Validation uses: pharmaverse/admiralci/.github/workflows/r-pkg-validation.yml@main if: github.event_name == 'release' with: - r-version: $R_VERSION + r-version: "4.0" check: name: Check uses: pharmaverse/admiralci/.github/workflows/r-cmd-check.yml@main @@ -73,7 +73,7 @@ jobs: uses: pharmaverse/admiralci/.github/workflows/pkgdown.yml@main if: github.event_name == 'push' with: - r-version: $R_VERSION + r-version: "4.0" # Whether to skip multiversion docs # Note that if you have multiple versions of docs, # your URL links are likely to break due to path changes @@ -85,7 +85,7 @@ jobs: uses: pharmaverse/admiralci/.github/workflows/lintr.yml@main if: github.event_name == 'pull_request' with: - r-version: $R_VERSION + r-version: "4.0" links: name: Links uses: pharmaverse/admiralci/.github/workflows/links.yml@main @@ -97,7 +97,7 @@ jobs: if: > github.event_name == 'push' || github.event_name == 'pull_request' with: - r-version: $R_VERSION + r-version: "4.0" # Whether to skip code coverage badge creation # Setting to 'false' will require you to create # an orphan branch called 'badges' in your repository @@ -107,4 +107,4 @@ jobs: uses: pharmaverse/admiralci/.github/workflows/man-pages.yml@main if: github.event_name == 'pull_request' with: - r-version: $R_VERSION + r-version: "4.0" diff --git a/DESCRIPTION b/DESCRIPTION index bde64344..6165c958 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -23,7 +23,7 @@ Encoding: UTF-8 Language: en-US LazyData: false Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.0 +RoxygenNote: 7.2.1 Depends: R (>= 3.5) Imports: assertthat (>= 0.2.1), diff --git a/renv.lock b/renv.lock index 89328655..b6e48a22 100644 --- a/renv.lock +++ b/renv.lock @@ -1,858 +1,1650 @@ { "R": { - "Version": "3.6.3", + "Version": "4.0.5", "Repositories": [ { "Name": "CRAN", - "URL": "https://cran.rstudio.com" + "URL": "https://cloud.r-project.org" + }, + { + "Name": "MRAN", + "URL": "https://cran.microsoft.com/snapshot/2021-03-31" } ] }, "Packages": { "BH": { "Package": "BH", - "Version": "1.72.0-3", + "Version": "1.75.0-0", "Source": "Repository", "Repository": "CRAN", - "Hash": "8f9ce74c6417d61f0782cbae5fd2b7b0" + "Hash": "e4c04affc2cac20c8fec18385cd14691", + "Requirements": [] }, "DT": { "Package": "DT", - "Version": "0.12", + "Version": "0.17", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "56b33b77f4cffd78ff96b8e5a69eabb0", + "Requirements": [ + "crosstalk", + "htmltools", + "htmlwidgets", + "jsonlite", + "magrittr", + "promises" + ] + }, + "KernSmooth": { + "Package": "KernSmooth", + "Version": "2.23-18", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "9e703ad8bf0e99f3691f05da32dfe68b", + "Requirements": [] + }, + "MASS": { + "Package": "MASS", + "Version": "7.3-53.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "4ef21dd0348b9abb7f8bd1d77e4cd0c3", + "Requirements": [] + }, + "Matrix": { + "Package": "Matrix", + "Version": "1.3-2", "Source": "Repository", "Repository": "CRAN", - "Hash": "0e120603cc57e4f1d741f739aa8147ba" + "Hash": "ff280503079ad8623d3c4b1519b24ea2", + "Requirements": [ + "lattice" + ] }, "R.cache": { "Package": "R.cache", - "Version": "0.15.0", + "Version": "0.16.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "e92a8ea8388c47c82ed8aa435ed3be50" + "Hash": "fe539ca3f8efb7410c3ae2cf5fe6c0f8", + "Requirements": [ + "R.methodsS3", + "R.oo", + "R.utils", + "digest" + ] }, "R.methodsS3": { "Package": "R.methodsS3", "Version": "1.8.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "4bf6453323755202d5909697b6f7c109" + "Hash": "4bf6453323755202d5909697b6f7c109", + "Requirements": [] }, "R.oo": { "Package": "R.oo", "Version": "1.24.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "5709328352717e2f0a9c012be8a97554" + "Hash": "5709328352717e2f0a9c012be8a97554", + "Requirements": [ + "R.methodsS3" + ] }, "R.utils": { "Package": "R.utils", - "Version": "2.11.0", + "Version": "2.12.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "a7ecb8e60815c7a18648e84cd121b23a" + "Hash": "d31333e10f14027e1cbbc6f266512806", + "Requirements": [ + "R.methodsS3", + "R.oo" + ] }, "R6": { "Package": "R6", - "Version": "2.4.1", + "Version": "2.5.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "292b54f8f4b94669b08f94e5acce6be2" + "Hash": "b203113193e70978a696b2809525649d", + "Requirements": [] }, "Rcpp": { "Package": "Rcpp", - "Version": "1.0.3", + "Version": "1.0.6", "Source": "Repository", "Repository": "CRAN", - "Hash": "f3ca785924863b0e4c8cb23b6a5c75a1" - }, - "admiral.test": { - "Package": "admiral.test", - "Version": "0.2.0", - "Source": "GitHub", - "RemoteType": "github", - "RemoteHost": "api.github.com", - "RemoteRepo": "admiral.test", - "RemoteUsername": "pharmaverse", - "RemoteRef": "devel", - "RemoteSha": "4f3bdd59a5a01bd0314a42e1d3ef32ca4d1745fe", - "Hash": "8378c5762f6be5a61854e880d6a8a223" + "Hash": "dbb5e436998a7eba5a9d682060533338", + "Requirements": [] }, "askpass": { "Package": "askpass", "Version": "1.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "e8a22846fff485f0be3770c2da758713" + "Hash": "e8a22846fff485f0be3770c2da758713", + "Requirements": [ + "sys" + ] }, "assertthat": { "Package": "assertthat", "Version": "0.2.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "50c838a310445e954bc13f26f26a6ecf" + "Hash": "50c838a310445e954bc13f26f26a6ecf", + "Requirements": [] }, "backports": { "Package": "backports", - "Version": "1.1.5", + "Version": "1.2.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "e9f705633dc932bfd5b02b17a5053a06" + "Hash": "644043219fc24e190c2f620c1a380a69", + "Requirements": [] }, "base64enc": { "Package": "base64enc", "Version": "0.1-3", "Source": "Repository", "Repository": "CRAN", - "Hash": "543776ae6848fde2f48ff3816d0628bc" + "Hash": "543776ae6848fde2f48ff3816d0628bc", + "Requirements": [] + }, + "boot": { + "Package": "boot", + "Version": "1.3-27", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "d9778c960792721e8433daaf3db8f16a", + "Requirements": [] }, "brew": { "Package": "brew", "Version": "1.0-6", "Source": "Repository", "Repository": "CRAN", - "Hash": "92a5f887f9ae3035ac7afde22ba73ee9" + "Hash": "92a5f887f9ae3035ac7afde22ba73ee9", + "Requirements": [] }, "brio": { "Package": "brio", - "Version": "1.1.2", + "Version": "1.1.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "2f01e16ff9571fe70381c7b9ae560dc4" + "Hash": "976cf154dfb043c012d87cddd8bca363", + "Requirements": [] }, "bslib": { "Package": "bslib", - "Version": "0.3.1", + "Version": "0.4.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "56ae7e1987b340186a8a5a157c2ec358" + "Hash": "be5ee090716ce1671be6cd5d7c34d091", + "Requirements": [ + "cachem", + "htmltools", + "jquerylib", + "jsonlite", + "memoise", + "rlang", + "sass" + ] + }, + "cachem": { + "Package": "cachem", + "Version": "1.0.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "2703a46dcabfb902f10060b2bca9f708", + "Requirements": [ + "fastmap", + "rlang" + ] }, "callr": { "Package": "callr", - "Version": "3.7.0", + "Version": "3.7.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "358689cac9fe93b1bb3a19088d2dbed8", + "Requirements": [ + "R6", + "processx" + ] + }, + "cellranger": { + "Package": "cellranger", + "Version": "1.1.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "461aa75a11ce2400245190ef5d3995df" + "Hash": "f61dbaec772ccd2e17705c1e872e9e7c", + "Requirements": [ + "rematch", + "tibble" + ] + }, + "class": { + "Package": "class", + "Version": "7.3-18", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "15ef288688a6919417ade6251deea2b3", + "Requirements": [ + "MASS" + ] }, "cli": { "Package": "cli", - "Version": "3.3.0", + "Version": "3.4.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "23abf173c2b783dcc43379ab9bba00ee" + "Hash": "0d297d01734d2bcea40197bd4971a764", + "Requirements": [] }, "clipr": { "Package": "clipr", - "Version": "0.7.0", + "Version": "0.7.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "08cf4045c149a0f0eaf405324c7495bd" + "Hash": "ebaa97ac99cc2daf04e77eecc7b781d7", + "Requirements": [] + }, + "cluster": { + "Package": "cluster", + "Version": "2.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5ea8f54741ff907e2c0b5efabc9de729", + "Requirements": [] }, "codetools": { "Package": "codetools", - "Version": "0.2-16", + "Version": "0.2-18", "Source": "Repository", "Repository": "CRAN", - "Hash": "89cf4b8207269ccf82fbeb6473fd662b" + "Hash": "019388fc48e48b3da0d3a76ff94608a8", + "Requirements": [] }, "commonmark": { "Package": "commonmark", "Version": "1.7", "Source": "Repository", "Repository": "CRAN", - "Hash": "0f22be39ec1d141fd03683c06f3a6e67" + "Hash": "0f22be39ec1d141fd03683c06f3a6e67", + "Requirements": [] }, "covr": { "Package": "covr", - "Version": "3.5.0", + "Version": "3.5.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "cbc6df1ef6ee576f844f973c1fc04ab4" + "Hash": "6d80a9fc3c0c8473153b54fa54719dfd", + "Requirements": [ + "crayon", + "digest", + "httr", + "jsonlite", + "rex", + "withr", + "yaml" + ] }, "cpp11": { "Package": "cpp11", - "Version": "0.3.1", + "Version": "0.4.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "e02edab2bc389c5e4b12949b13df44f2" + "Hash": "ed588261931ee3be2c700d22e94a29ab", + "Requirements": [] }, "crayon": { "Package": "crayon", - "Version": "1.3.4", + "Version": "1.4.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "0d57bc8e27b7ba9e45dba825ebc0de6b" + "Hash": "e75525c55c70e5f4f78c9960a4b402e9", + "Requirements": [] }, "credentials": { "Package": "credentials", "Version": "1.3.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "a96728288c75a814c900af9da84387be" + "Hash": "a96728288c75a814c900af9da84387be", + "Requirements": [ + "askpass", + "curl", + "jsonlite", + "openssl", + "sys" + ] }, "crosstalk": { "Package": "crosstalk", "Version": "1.1.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "2b06f9e415a62b6762e4b8098d2aecbc" + "Hash": "2b06f9e415a62b6762e4b8098d2aecbc", + "Requirements": [ + "R6", + "htmltools", + "jsonlite", + "lazyeval" + ] }, "curl": { "Package": "curl", "Version": "4.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "2b7d10581cc730804e9ed178c8374bd6" + "Hash": "2b7d10581cc730804e9ed178c8374bd6", + "Requirements": [] }, "cyclocomp": { "Package": "cyclocomp", "Version": "1.1.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "53cbed70a2f7472d48fb6aef08442f25" + "Hash": "53cbed70a2f7472d48fb6aef08442f25", + "Requirements": [ + "callr", + "crayon", + "desc", + "remotes", + "withr" + ] }, "desc": { "Package": "desc", - "Version": "1.4.0", + "Version": "1.4.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "28763d08fadd0b733e3cee9dab4e12fe" + "Hash": "6b9602c7ebbe87101a9c8edb6e8b6d21", + "Requirements": [ + "R6", + "cli", + "rprojroot" + ] }, "devtools": { "Package": "devtools", - "Version": "2.2.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "e12b66f9f6dc41b765b047b4df4b4a38" + "Version": "2.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "415656f50722f5b6e6bcf80855ce11b9", + "Requirements": [ + "DT", + "callr", + "cli", + "covr", + "desc", + "ellipsis", + "httr", + "jsonlite", + "memoise", + "pkgbuild", + "pkgload", + "rcmdcheck", + "remotes", + "rlang", + "roxygen2", + "rstudioapi", + "rversions", + "sessioninfo", + "testthat", + "usethis", + "withr" + ] }, "diffdf": { "Package": "diffdf", "Version": "1.0.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "9ddedef46959baad2080047a1b0117fe" + "Hash": "9ddedef46959baad2080047a1b0117fe", + "Requirements": [ + "tibble" + ] }, "diffobj": { "Package": "diffobj", "Version": "0.3.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "feb5b7455eba422a2c110bb89852e6a3" + "Hash": "feb5b7455eba422a2c110bb89852e6a3", + "Requirements": [ + "crayon" + ] }, "digest": { "Package": "digest", - "Version": "0.6.25", + "Version": "0.6.27", "Source": "Repository", "Repository": "CRAN", - "Hash": "f697db7d92b7028c4b3436e9603fb636" + "Hash": "a0cbe758a531d054b537d16dff4d58a1", + "Requirements": [] }, "downlit": { "Package": "downlit", - "Version": "0.4.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ba63dc9ab5a31f3209892437e40c5f60" + "Version": "0.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "79bf3f66590752ffbba20f8d2da94c7c", + "Requirements": [ + "brio", + "desc", + "digest", + "evaluate", + "fansi", + "memoise", + "rlang", + "vctrs", + "withr", + "yaml" + ] }, "dplyr": { "Package": "dplyr", - "Version": "0.8.5", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "57a42ddf80f429764ff7987128c3fd0a" + "Version": "1.0.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "d0d76c11ec807eb3f000eba4e3eb0f68", + "Requirements": [ + "R6", + "ellipsis", + "generics", + "glue", + "lifecycle", + "magrittr", + "rlang", + "tibble", + "tidyselect", + "vctrs" + ] }, "ellipsis": { "Package": "ellipsis", "Version": "0.3.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077" + "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077", + "Requirements": [ + "rlang" + ] }, "evaluate": { "Package": "evaluate", - "Version": "0.15", + "Version": "0.17", "Source": "Repository", "Repository": "CRAN", - "Hash": "699a7a93d08c962d9f8950b2d7a227f1" + "Hash": "9171b012a55a1ef53f1442b1d798a3b4", + "Requirements": [] }, "fansi": { "Package": "fansi", - "Version": "0.4.1", + "Version": "0.4.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "7fce217eaaf8016e72065e85c73027b5" + "Hash": "fea074fb67fe4c25d47ad09087da847d", + "Requirements": [] }, "fastmap": { "Package": "fastmap", "Version": "1.1.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "77bd60a6157420d4ffa93b27cf6a58b8" + "Hash": "77bd60a6157420d4ffa93b27cf6a58b8", + "Requirements": [] + }, + "foreign": { + "Package": "foreign", + "Version": "0.8-81", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "74628ea7a3be5ee8a7b5bb0a8e84882e", + "Requirements": [] }, "fs": { "Package": "fs", "Version": "1.5.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "7c89603d81793f0d5486d91ab1fc6f1d" + "Hash": "7c89603d81793f0d5486d91ab1fc6f1d", + "Requirements": [] + }, + "generics": { + "Package": "generics", + "Version": "0.1.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "4d243a9c10b00589889fe32314ffd902", + "Requirements": [] }, "gert": { "Package": "gert", - "Version": "1.4.3", + "Version": "1.9.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "3a3f29a74f8bd85ed8d53ab039b18bcd" + "Hash": "9a091a6d2fb91e43afd4337e2dcef2e7", + "Requirements": [ + "askpass", + "credentials", + "openssl", + "rstudioapi", + "sys", + "zip" + ] }, "gh": { "Package": "gh", - "Version": "1.3.0", + "Version": "1.3.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "38c2580abbda249bd6afeec00d14f531" + "Hash": "b6a12054ee13dce0f6696c019c10e539", + "Requirements": [ + "cli", + "gitcreds", + "httr", + "ini", + "jsonlite" + ] }, "git2r": { "Package": "git2r", - "Version": "0.26.1", + "Version": "0.28.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "135db4dbc94ed18f629ff8843a8064b7" + "Hash": "f64fd34026f6025de71a4354800e6d79", + "Requirements": [] }, "gitcreds": { "Package": "gitcreds", "Version": "0.1.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "f3aefccc1cc50de6338146b62f115de8" + "Hash": "f3aefccc1cc50de6338146b62f115de8", + "Requirements": [] }, "glue": { "Package": "glue", - "Version": "1.6.0", + "Version": "1.6.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "b8bb7aaf248e45bac08ebed86f3a0aa4" + "Hash": "4f2596dfb05dac67b9dc558e5c6fba2e", + "Requirements": [] }, "highr": { "Package": "highr", "Version": "0.8", "Source": "Repository", "Repository": "CRAN", - "Hash": "4dc5bb88961e347a0f4d8aad597cbfac" + "Hash": "4dc5bb88961e347a0f4d8aad597cbfac", + "Requirements": [] }, "hms": { "Package": "hms", - "Version": "0.5.3", + "Version": "1.0.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "726671f634529d470545f9fd1a9d1869" + "Hash": "bf552cdd96f5969873afdac7311c7d0d", + "Requirements": [ + "ellipsis", + "lifecycle", + "pkgconfig", + "rlang", + "vctrs" + ] }, "htmltools": { "Package": "htmltools", - "Version": "0.5.2", + "Version": "0.5.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "526c484233f42522278ab06fb185cb26" + "Hash": "6496090a9e00f8354b811d1a2d47b566", + "Requirements": [ + "base64enc", + "digest", + "fastmap", + "rlang" + ] }, "htmlwidgets": { "Package": "htmlwidgets", - "Version": "1.5.1", + "Version": "1.5.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "41bace23583fbc25089edae324de2dc3" + "Hash": "6fdaa86d0700f8b3e92ee3c445a5a10d", + "Requirements": [ + "htmltools", + "jsonlite", + "yaml" + ] }, "httpuv": { "Package": "httpuv", - "Version": "1.5.2", + "Version": "1.5.5", "Source": "Repository", "Repository": "CRAN", - "Hash": "f793dad2c9ae14fbb1d22f16f23f8326" + "Hash": "b9d5d39be2150cf86538b8488334b8f8", + "Requirements": [ + "BH", + "R6", + "Rcpp", + "later", + "promises" + ] }, "httr": { "Package": "httr", "Version": "1.4.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "a525aba14184fec243f9eaec62fbed43" + "Hash": "a525aba14184fec243f9eaec62fbed43", + "Requirements": [ + "R6", + "curl", + "jsonlite", + "mime", + "openssl" + ] }, "hunspell": { "Package": "hunspell", - "Version": "3.0", + "Version": "3.0.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "71e7853d60b6b4ba891d62ede21752e9" + "Hash": "3987784c19192ad0f2261c456d936df1", + "Requirements": [ + "Rcpp", + "digest" + ] }, "ini": { "Package": "ini", "Version": "0.3.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "6154ec2223172bce8162d4153cda21f7" + "Hash": "6154ec2223172bce8162d4153cda21f7", + "Requirements": [] }, "jquerylib": { "Package": "jquerylib", "Version": "0.1.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "5aab57a3bd297eee1c1d862735972182" + "Hash": "5aab57a3bd297eee1c1d862735972182", + "Requirements": [ + "htmltools" + ] }, "jsonlite": { "Package": "jsonlite", - "Version": "1.6.1", + "Version": "1.7.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "84b0ee361e2f78d6b7d670db9471c0c5" + "Hash": "98138e0994d41508c7a6b84a0600cfcb", + "Requirements": [] }, "knitr": { "Package": "knitr", - "Version": "1.39", + "Version": "1.40", "Source": "Repository", "Repository": "CRAN", - "Hash": "029ab7c4badd3cf8af69016b2ba27493" + "Hash": "caea8b0f899a0b1738444b9bc47067e7", + "Requirements": [ + "evaluate", + "highr", + "stringr", + "xfun", + "yaml" + ] }, "later": { "Package": "later", - "Version": "1.0.0", + "Version": "1.1.0.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "6d927978fc658d24175ce37db635f9e5" + "Hash": "d0a62b247165aabf397fded504660d8a", + "Requirements": [ + "BH", + "Rcpp", + "rlang" + ] + }, + "lattice": { + "Package": "lattice", + "Version": "0.20-41", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "fbd9285028b0263d76d18c95ae51a53d", + "Requirements": [] }, "lazyeval": { "Package": "lazyeval", "Version": "0.2.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "d908914ae53b04d4c0c0fd72ecc35370" + "Hash": "d908914ae53b04d4c0c0fd72ecc35370", + "Requirements": [] }, "lifecycle": { "Package": "lifecycle", - "Version": "1.0.1", + "Version": "1.0.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "a6b6d352e3ed897373ab19d8395c98d0" + "Hash": "001cecbeac1cff9301bdc3775ee46a86", + "Requirements": [ + "cli", + "glue", + "rlang" + ] }, "lintr": { "Package": "lintr", "Version": "2.0.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "023cecbdc0a32f86ad3cb1734c018d2e" + "Hash": "023cecbdc0a32f86ad3cb1734c018d2e", + "Requirements": [ + "codetools", + "crayon", + "cyclocomp", + "digest", + "httr", + "jsonlite", + "knitr", + "rex", + "rstudioapi", + "testthat", + "xml2", + "xmlparsedata" + ] }, "lubridate": { "Package": "lubridate", - "Version": "1.7.4", + "Version": "1.7.10", "Source": "Repository", "Repository": "CRAN", - "Hash": "796afeea047cda6bdb308d374a33eeb6" + "Hash": "1ebfdc8a3cfe8fe19184f5481972b092", + "Requirements": [ + "Rcpp", + "generics" + ] }, "magrittr": { "Package": "magrittr", "Version": "2.0.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "7ce2733a9826b3aeb1775d56fd305472" + "Hash": "7ce2733a9826b3aeb1775d56fd305472", + "Requirements": [] + }, + "markdown": { + "Package": "markdown", + "Version": "1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "61e4a10781dd00d7d81dd06ca9b94e95", + "Requirements": [ + "mime", + "xfun" + ] }, "memoise": { "Package": "memoise", - "Version": "1.1.0", + "Version": "2.0.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a0bc51650201a56d00a4798523cc91b3", + "Requirements": [ + "cachem", + "rlang" + ] + }, + "mgcv": { + "Package": "mgcv", + "Version": "1.8-34", "Source": "Repository", "Repository": "CRAN", - "Hash": "58baa74e4603fcfb9a94401c58c8f9b1" + "Hash": "bd4a6c4b600f58651d60d381b0e9a397", + "Requirements": [ + "Matrix", + "nlme" + ] }, "mime": { "Package": "mime", - "Version": "0.9", + "Version": "0.10", "Source": "Repository", "Repository": "CRAN", - "Hash": "e87a35ec73b157552814869f45a63aa3" + "Hash": "26fa77e707223e1ce042b2b5d09993dc", + "Requirements": [] }, "miniUI": { "Package": "miniUI", "Version": "0.1.1.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "fec5f52652d60615fdb3957b3d74324a" + "Hash": "fec5f52652d60615fdb3957b3d74324a", + "Requirements": [ + "htmltools", + "shiny" + ] + }, + "mockery": { + "Package": "mockery", + "Version": "0.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "313fa6504824ba5aab9308412135fb5f", + "Requirements": [ + "testthat" + ] + }, + "nlme": { + "Package": "nlme", + "Version": "3.1-152", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "35de1ce639f20b5e10f7f46260730c65", + "Requirements": [ + "lattice" + ] + }, + "nnet": { + "Package": "nnet", + "Version": "7.3-15", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "b67ac021b3fb3a4b69d0d3c2bc049e9f", + "Requirements": [] }, "openssl": { "Package": "openssl", - "Version": "1.4.1", + "Version": "2.0.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "49f7258fd86ebeaea1df24d9ded00478" + "Hash": "e86c5ffeb8474a9e03d75f5d2919683e", + "Requirements": [ + "askpass" + ] }, "pillar": { "Package": "pillar", - "Version": "1.4.3", + "Version": "1.5.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "fa3ed60396b6998d0427c57dab90fba4" + "Hash": "24622aa4a0d3de3463c34513edca99b2", + "Requirements": [ + "cli", + "crayon", + "ellipsis", + "fansi", + "lifecycle", + "rlang", + "utf8", + "vctrs" + ] }, "pkgbuild": { "Package": "pkgbuild", - "Version": "1.0.6", + "Version": "1.2.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "899835dfe286963471cbdb9591f8f94f" + "Hash": "725fcc30222d4d11ec68efb8ff11a9af", + "Requirements": [ + "R6", + "callr", + "cli", + "crayon", + "desc", + "prettyunits", + "rprojroot", + "withr" + ] }, "pkgconfig": { "Package": "pkgconfig", "Version": "2.0.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "01f28d4278f15c76cddbea05899c5d6f" + "Hash": "01f28d4278f15c76cddbea05899c5d6f", + "Requirements": [] }, "pkgdown": { "Package": "pkgdown", - "Version": "2.0.3", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "ec3139021900fa27faae7a821b732bf8" + "Version": "2.0.6", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "f958d0b2a5dabc5ffd414f062b1ffbe7", + "Requirements": [ + "bslib", + "callr", + "cli", + "desc", + "digest", + "downlit", + "fs", + "httr", + "jsonlite", + "magrittr", + "memoise", + "purrr", + "ragg", + "rlang", + "rmarkdown", + "tibble", + "whisker", + "withr", + "xml2", + "yaml" + ] }, "pkgload": { "Package": "pkgload", - "Version": "1.0.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "5e655fb54cceead0f095f22d7be33da3" - }, - "plogr": { - "Package": "plogr", - "Version": "0.2.0", + "Version": "1.2.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "09eb987710984fc2905c7129c7d85e65" + "Hash": "cb57de933545960a86f03513e4bd2911", + "Requirements": [ + "cli", + "crayon", + "desc", + "pkgbuild", + "rlang", + "rprojroot", + "rstudioapi", + "withr" + ] }, "praise": { "Package": "praise", "Version": "1.0.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "a555924add98c99d2f411e37e7d25e9f" + "Hash": "a555924add98c99d2f411e37e7d25e9f", + "Requirements": [] }, "prettyunits": { "Package": "prettyunits", "Version": "1.1.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "95ef9167b75dde9d2ccc3c7528393e7e" + "Hash": "95ef9167b75dde9d2ccc3c7528393e7e", + "Requirements": [] }, "processx": { "Package": "processx", - "Version": "3.5.2", + "Version": "3.8.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a33ee2d9bf07564efb888ad98410da84", + "Requirements": [ + "R6", + "ps" + ] + }, + "progress": { + "Package": "progress", + "Version": "1.2.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "0cbca2bc4d16525d009c4dbba156b37c" + "Hash": "14dc9f7a3c91ebb14ec5bb9208a07061", + "Requirements": [ + "R6", + "crayon", + "hms", + "prettyunits" + ] }, "promises": { "Package": "promises", - "Version": "1.1.0", + "Version": "1.2.0.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "efbbe62da4709f7040a380c702bc7103" + "Hash": "4ab2c43adb4d4699cf3690acd378d75d", + "Requirements": [ + "R6", + "Rcpp", + "later", + "magrittr", + "rlang" + ] }, "ps": { "Package": "ps", "Version": "1.6.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "32620e2001c1dce1af49c49dccbb9420" + "Hash": "32620e2001c1dce1af49c49dccbb9420", + "Requirements": [] }, "purrr": { "Package": "purrr", - "Version": "0.3.3", + "Version": "0.3.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "22aca7d1181718e927d403a8c2d69d62" + "Hash": "97def703420c8ab10d8f0e6c72101e02", + "Requirements": [ + "magrittr", + "rlang" + ] }, "ragg": { "Package": "ragg", - "Version": "1.2.2", + "Version": "1.2.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "14932bb6f2739c771ca4ceaba6b4248e" + "Hash": "0db17bd5a1d4abfec76487b6f5dd957b", + "Requirements": [ + "systemfonts", + "textshaping" + ] }, "rappdirs": { "Package": "rappdirs", "Version": "0.3.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "5e3c5dc0b071b21fa128676560dbe94d" + "Hash": "5e3c5dc0b071b21fa128676560dbe94d", + "Requirements": [] }, "rcmdcheck": { "Package": "rcmdcheck", "Version": "1.3.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "ed95895886dab6d2a584da45503555da" + "Hash": "ed95895886dab6d2a584da45503555da", + "Requirements": [ + "R6", + "callr", + "cli", + "crayon", + "desc", + "digest", + "pkgbuild", + "prettyunits", + "rprojroot", + "sessioninfo", + "withr", + "xopen" + ] + }, + "readxl": { + "Package": "readxl", + "Version": "1.3.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "63537c483c2dbec8d9e3183b3735254a", + "Requirements": [ + "Rcpp", + "cellranger", + "progress", + "tibble" + ] + }, + "rematch": { + "Package": "rematch", + "Version": "1.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c66b930d20bb6d858cd18e1cebcfae5c", + "Requirements": [] }, "rematch2": { "Package": "rematch2", - "Version": "2.1.0", + "Version": "2.1.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "a8e6ebbd476d2b8f6557ed3fab3b6139" + "Hash": "76c9e04c712a05848ae7a23d2f170a40", + "Requirements": [ + "tibble" + ] }, "remotes": { "Package": "remotes", - "Version": "2.1.1", + "Version": "2.2.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "57c3009534f805f0f6476ffee68483cc" + "Hash": "430a0908aee75b1fcba0e62857cab0ce", + "Requirements": [] }, "renv": { "Package": "renv", - "Version": "0.13.0", + "Version": "0.16.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "9f10d9db5b50400c348920c5c603385e" + "Hash": "c9e8442ab69bc21c9697ecf856c1e6c7", + "Requirements": [] }, "rex": { "Package": "rex", - "Version": "1.1.2", + "Version": "1.2.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "6d3dbb5d528c8f726861018472bc668c" + "Hash": "093584b944440c5cd07a696b3c8e0e4c", + "Requirements": [ + "lazyeval" + ] }, "rlang": { "Package": "rlang", - "Version": "1.0.2", + "Version": "1.0.6", "Source": "Repository", "Repository": "CRAN", - "Hash": "04884d9a75d778aca22c7154b8333ec9" + "Hash": "4ed1f8336c8d52c3e750adcdc57228a7", + "Requirements": [] }, "rmarkdown": { "Package": "rmarkdown", - "Version": "2.14", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "31b60a882fabfabf6785b8599ffeb8ba" + "Version": "2.17", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "e97c8be593e010f93520e8215c0f9189", + "Requirements": [ + "bslib", + "evaluate", + "htmltools", + "jquerylib", + "jsonlite", + "knitr", + "stringr", + "tinytex", + "xfun", + "yaml" + ] }, "roxygen2": { "Package": "roxygen2", - "Version": "7.2.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "b390c1d54fcd977cda48588e6172daba" + "Version": "7.2.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "da1f278262e563c835345872f2fef537", + "Requirements": [ + "R6", + "brew", + "cli", + "commonmark", + "cpp11", + "desc", + "digest", + "knitr", + "pkgload", + "purrr", + "rlang", + "stringi", + "stringr", + "withr", + "xml2" + ] + }, + "rpart": { + "Package": "rpart", + "Version": "4.1-15", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "9787c1fcb680e655d062e7611cadf78e", + "Requirements": [] }, "rprojroot": { "Package": "rprojroot", - "Version": "1.3-2", + "Version": "2.0.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "f6a407ae5dd21f6f80a6708bbb6eb3ae" + "Hash": "249d8cd1e74a8f6a26194a91b47f21d1", + "Requirements": [] }, "rstudioapi": { "Package": "rstudioapi", - "Version": "0.11", + "Version": "0.13", "Source": "Repository", "Repository": "CRAN", - "Hash": "33a5b27a03da82ac4b1d43268f80088a" + "Hash": "06c85365a03fdaf699966cc1d3cf53ea", + "Requirements": [] }, "rversions": { "Package": "rversions", - "Version": "2.0.1", + "Version": "2.0.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "2aa84e83767ba93ee6415b439fa981d2" + "Hash": "0ec41191f744d0f5afad8c6f35cc36e4", + "Requirements": [ + "curl", + "xml2" + ] }, "sass": { "Package": "sass", - "Version": "0.4.1", + "Version": "0.4.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "f37c0028d720bab3c513fd65d28c7234" + "Hash": "1b191143d7d3444d504277843f3a95fe", + "Requirements": [ + "R6", + "fs", + "htmltools", + "rappdirs", + "rlang" + ] }, "sessioninfo": { "Package": "sessioninfo", "Version": "1.1.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "308013098befe37484df72c39cf90d6e" + "Hash": "308013098befe37484df72c39cf90d6e", + "Requirements": [ + "cli", + "withr" + ] }, "shiny": { "Package": "shiny", - "Version": "1.4.0", + "Version": "1.6.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "6ca23724bb2c804c1d0b3db4862a39c7" + "Hash": "6e3b6ae7fe02b5859e4bb277f218b8ae", + "Requirements": [ + "R6", + "bslib", + "cachem", + "commonmark", + "crayon", + "digest", + "ellipsis", + "fastmap", + "glue", + "htmltools", + "httpuv", + "jsonlite", + "later", + "lifecycle", + "mime", + "promises", + "rlang", + "sourcetools", + "withr", + "xtable" + ] }, "sourcetools": { "Package": "sourcetools", "Version": "0.1.7", "Source": "Repository", "Repository": "CRAN", - "Hash": "947e4e02a79effa5d512473e10f41797" + "Hash": "947e4e02a79effa5d512473e10f41797", + "Requirements": [] + }, + "spatial": { + "Package": "spatial", + "Version": "7.3-13", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "8d0918547149f72e78ae942ccd1fdbc7", + "Requirements": [] }, "spelling": { "Package": "spelling", - "Version": "2.1", + "Version": "2.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "b3a5ecc3351f41eb30ef87f65cbff390" + "Hash": "b8c899a5c83f0d897286550481c91798", + "Requirements": [ + "commonmark", + "hunspell", + "knitr", + "xml2" + ] + }, + "staged.dependencies": { + "Package": "staged.dependencies", + "Version": "0.2.7", + "Source": "GitHub", + "RemoteType": "github", + "RemoteHost": "api.github.com", + "RemoteRepo": "staged.dependencies", + "RemoteUsername": "openpharma", + "RemoteRef": "HEAD", + "RemoteSha": "669f45a95d8772899551ad51fc3b38a3b5a1056a", + "Hash": "348648f944ce5dbcbdc2b120c9ba3a3c", + "Requirements": [ + "desc", + "devtools", + "digest", + "dplyr", + "fs", + "git2r", + "glue", + "httr", + "jsonlite", + "rcmdcheck", + "remotes", + "rlang", + "tidyr", + "withr", + "yaml" + ] }, "stringi": { "Package": "stringi", - "Version": "1.4.6", + "Version": "1.5.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "e99d8d656980d2dd416a962ae55aec90" + "Hash": "a063ebea753c92910a4cca7b18bc1f05", + "Requirements": [] }, "stringr": { "Package": "stringr", "Version": "1.4.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "0759e6b6c0957edb1311028a49a35e76" + "Hash": "0759e6b6c0957edb1311028a49a35e76", + "Requirements": [ + "glue", + "magrittr", + "stringi" + ] }, "styler": { "Package": "styler", - "Version": "1.5.1", + "Version": "1.8.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "eb62a56987abe8fa9d1de47eef53a790" + "Hash": "c855e70eb69b3dd8883660b7110e0c44", + "Requirements": [ + "R.cache", + "cli", + "magrittr", + "purrr", + "rlang", + "rprojroot", + "vctrs", + "withr" + ] + }, + "survival": { + "Package": "survival", + "Version": "3.2-10", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "6b7453cd9bb32b12577c78d54eeea56a", + "Requirements": [ + "Matrix" + ] }, "sys": { "Package": "sys", - "Version": "3.3", + "Version": "3.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "507f3116a38d37ad330a038b3be07b66" + "Hash": "b227d13e29222b4574486cfcbde077fa", + "Requirements": [] }, "systemfonts": { "Package": "systemfonts", "Version": "1.0.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "90b28393209827327de889f49935140a" + "Hash": "90b28393209827327de889f49935140a", + "Requirements": [ + "cpp11" + ] }, "testthat": { "Package": "testthat", - "Version": "3.0.0", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "13298cedd051cb7b8a8972d380b559a6" + "Version": "3.0.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "495e0434d9305716b6a87031570ce109", + "Requirements": [ + "R6", + "brio", + "callr", + "cli", + "crayon", + "desc", + "digest", + "ellipsis", + "evaluate", + "jsonlite", + "lifecycle", + "magrittr", + "pkgload", + "praise", + "processx", + "ps", + "rlang", + "waldo", + "withr" + ] }, "textshaping": { "Package": "textshaping", "Version": "0.3.6", "Source": "Repository", "Repository": "CRAN", - "Hash": "1ab6223d3670fac7143202cb6a2d43d5" + "Hash": "1ab6223d3670fac7143202cb6a2d43d5", + "Requirements": [ + "cpp11", + "systemfonts" + ] }, "tibble": { "Package": "tibble", - "Version": "3.0.0", + "Version": "3.1.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "e742bc8d72071ef9aba29f71f132d773" + "Hash": "4d894a114dbd4ecafeda5074e7c538e6", + "Requirements": [ + "ellipsis", + "fansi", + "lifecycle", + "magrittr", + "pillar", + "pkgconfig", + "rlang", + "vctrs" + ] }, "tidyr": { "Package": "tidyr", - "Version": "1.0.2", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "fb73a010ace00d6c584c2b53a21b969c" + "Version": "1.1.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "450d7dfaedde58e28586b854eeece4fa", + "Requirements": [ + "cpp11", + "dplyr", + "ellipsis", + "glue", + "lifecycle", + "magrittr", + "purrr", + "rlang", + "tibble", + "tidyselect", + "vctrs" + ] }, "tidyselect": { "Package": "tidyselect", - "Version": "1.0.0", + "Version": "1.1.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "7d4b0f1ab542d8cb7a40c593a4de2f36" + "Hash": "6ea435c354e8448819627cf686f66e0a", + "Requirements": [ + "ellipsis", + "glue", + "purrr", + "rlang", + "vctrs" + ] }, "tinytex": { "Package": "tinytex", - "Version": "0.38", + "Version": "0.42", "Source": "Repository", "Repository": "CRAN", - "Hash": "759d047596ac173433985deddf313450" + "Hash": "7629c6c1540835d5248e6e7df265fa74", + "Requirements": [ + "xfun" + ] }, "usethis": { "Package": "usethis", - "Version": "2.1.5", - "Source": "Repository", - "Repository": "CRAN", - "Hash": "c499f488e6dd7718accffaee5bc5a79b" + "Version": "2.1.6", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a67a22c201832b12c036cc059f1d137d", + "Requirements": [ + "cli", + "clipr", + "crayon", + "curl", + "desc", + "fs", + "gert", + "gh", + "glue", + "jsonlite", + "lifecycle", + "purrr", + "rappdirs", + "rlang", + "rprojroot", + "rstudioapi", + "whisker", + "withr", + "yaml" + ] }, "utf8": { "Package": "utf8", - "Version": "1.1.4", + "Version": "1.2.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "4a5081acfb7b81a572e4384a7aaf2af1" + "Hash": "c3ad47dc6da0751f18ed53c4613e3ac7", + "Requirements": [] }, "vctrs": { "Package": "vctrs", - "Version": "0.3.8", + "Version": "0.5.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "001fd6a5ebfff8316baf9fb2b5516dc9", + "Requirements": [ + "cli", + "glue", + "lifecycle", + "rlang" + ] + }, + "visNetwork": { + "Package": "visNetwork", + "Version": "2.0.9", "Source": "Repository", "Repository": "CRAN", - "Hash": "ecf749a1b39ea72bd9b51b76292261f1" + "Hash": "12545f2acf49d1d346d075580122d89c", + "Requirements": [ + "htmltools", + "htmlwidgets", + "jsonlite", + "magrittr" + ] }, "waldo": { "Package": "waldo", "Version": "0.2.5", "Source": "Repository", "Repository": "CRAN", - "Hash": "20c45f1d511a3f730b7b469f4d11e104" + "Hash": "20c45f1d511a3f730b7b469f4d11e104", + "Requirements": [ + "cli", + "diffobj", + "fansi", + "glue", + "rematch2", + "rlang", + "tibble" + ] }, "whisker": { "Package": "whisker", "Version": "0.4", "Source": "Repository", "Repository": "CRAN", - "Hash": "ca970b96d894e90397ed20637a0c1bbe" + "Hash": "ca970b96d894e90397ed20637a0c1bbe", + "Requirements": [] }, "withr": { "Package": "withr", - "Version": "2.4.3", + "Version": "2.5.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "a376b424c4817cda4920bbbeb3364e85" + "Hash": "c0e49a9760983e81e55cdd9be92e7182", + "Requirements": [] }, "xfun": { "Package": "xfun", - "Version": "0.30", + "Version": "0.34", "Source": "Repository", "Repository": "CRAN", - "Hash": "e83f48136b041845e50a6658feffb197" + "Hash": "9eba2411b0b1f879797141bd24df7407", + "Requirements": [] }, "xml2": { "Package": "xml2", "Version": "1.3.3", "Source": "Repository", "Repository": "CRAN", - "Hash": "40682ed6a969ea5abfd351eb67833adc" + "Hash": "40682ed6a969ea5abfd351eb67833adc", + "Requirements": [] }, "xmlparsedata": { "Package": "xmlparsedata", - "Version": "1.0.3", + "Version": "1.0.5", "Source": "Repository", "Repository": "CRAN", - "Hash": "90d3cba62daa9f1e5313afef106f719d" + "Hash": "45e4bf3c46476896e821fc0a408fb4fc", + "Requirements": [] }, "xopen": { "Package": "xopen", "Version": "1.0.0", "Source": "Repository", "Repository": "CRAN", - "Hash": "6c85f015dee9cc7710ddd20f86881f58" + "Hash": "6c85f015dee9cc7710ddd20f86881f58", + "Requirements": [ + "processx" + ] }, "xtable": { "Package": "xtable", "Version": "1.8-4", "Source": "Repository", "Repository": "CRAN", - "Hash": "b8acdf8af494d9ec19ccb2481a9b11c2" + "Hash": "b8acdf8af494d9ec19ccb2481a9b11c2", + "Requirements": [] }, "yaml": { "Package": "yaml", "Version": "2.2.1", "Source": "Repository", "Repository": "CRAN", - "Hash": "2826c5d9efb0a88f657c7a679c7106db" + "Hash": "2826c5d9efb0a88f657c7a679c7106db", + "Requirements": [] }, "zip": { "Package": "zip", - "Version": "2.2.0", + "Version": "2.2.2", "Source": "Repository", "Repository": "CRAN", - "Hash": "c7eef2996ac270a18c2715c997a727c5" + "Hash": "c42bfcec3fa6a0cce17ce1f8bc684f88", + "Requirements": [] } } } diff --git a/renv/.gitignore b/renv/.gitignore index 21296311..6ae4167d 100644 --- a/renv/.gitignore +++ b/renv/.gitignore @@ -1,3 +1,5 @@ +cellar/ +sandbox/ library/ local/ lock/ diff --git a/renv/activate.R b/renv/activate.R index 2cba08a4..e52e2dae 100644 --- a/renv/activate.R +++ b/renv/activate.R @@ -1,19 +1,49 @@ - local({ - # the requested version of renv - version <- "0.13.0" + version <- "0.16.0" # the project directory project <- getwd() + # figure out whether the autoloader is enabled + enabled <- local({ + + # first, check config option + override <- getOption("renv.config.autoloader.enabled") + if (!is.null(override)) + return(override) + + # next, check environment variables + # TODO: prefer using the configuration one in the future + envvars <- c( + "RENV_CONFIG_AUTOLOADER_ENABLED", + "RENV_AUTOLOADER_ENABLED", + "RENV_ACTIVATE_PROJECT" + ) + + for (envvar in envvars) { + envval <- Sys.getenv(envvar, unset = NA) + if (!is.na(envval)) + return(tolower(envval) %in% c("true", "t", "1")) + } + + # enable by default + TRUE + + }) + + if (!enabled) + return(FALSE) + # avoid recursion - if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA))) + if (identical(getOption("renv.autoloader.running"), TRUE)) { + warning("ignoring recursive attempt to run renv autoloader") return(invisible(TRUE)) + } # signal that we're loading renv during R startup - Sys.setenv("RENV_R_INITIALIZING" = "true") - on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) + options(renv.autoloader.running = TRUE) + on.exit(options(renv.autoloader.running = NULL), add = TRUE) # signal that we've consented to use renv options(renv.consent = TRUE) @@ -22,228 +52,329 @@ local({ # mask 'utils' packages, will come first on the search path library(utils, lib.loc = .Library) - # check to see if renv has already been loaded - if ("renv" %in% loadedNamespaces()) { - - # if renv has already been loaded, and it's the requested version of renv, - # nothing to do - spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") - if (identical(spec[["version"]], version)) - return(invisible(TRUE)) - - # otherwise, unload and attempt to load the correct version of renv + # unload renv if it's already been loaded + if ("renv" %in% loadedNamespaces()) unloadNamespace("renv") + # load bootstrap tools + `%||%` <- function(x, y) { + if (is.environment(x) || length(x)) x else y } - # load bootstrap tools bootstrap <- function(version, library) { - + # attempt to download renv tarball <- tryCatch(renv_bootstrap_download(version), error = identity) if (inherits(tarball, "error")) stop("failed to download renv ", version) - + # now attempt to install status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) if (inherits(status, "error")) stop("failed to install renv ", version) - + } - + renv_bootstrap_tests_running <- function() { getOption("renv.tests.running", default = FALSE) } - + renv_bootstrap_repos <- function() { - + # check for repos override repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) if (!is.na(repos)) return(repos) - + + # check for lockfile repositories + repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) + if (!inherits(repos, "error") && length(repos)) + return(repos) + # if we're testing, re-use the test repositories if (renv_bootstrap_tests_running()) return(getOption("renv.tests.repos")) - + # retrieve current repos repos <- getOption("repos") - + # ensure @CRAN@ entries are resolved - repos[repos == "@CRAN@"] <- "https://cloud.r-project.org" - + repos[repos == "@CRAN@"] <- getOption( + "renv.repos.cran", + "https://cloud.r-project.org" + ) + # add in renv.bootstrap.repos if set default <- c(FALLBACK = "https://cloud.r-project.org") extra <- getOption("renv.bootstrap.repos", default = default) repos <- c(repos, extra) - + # remove duplicates that might've snuck in dupes <- duplicated(repos) | duplicated(names(repos)) repos[!dupes] - + + } + + renv_bootstrap_repos_lockfile <- function() { + + lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") + if (!file.exists(lockpath)) + return(NULL) + + lockfile <- tryCatch(renv_json_read(lockpath), error = identity) + if (inherits(lockfile, "error")) { + warning(lockfile) + return(NULL) + } + + repos <- lockfile$R$Repositories + if (length(repos) == 0) + return(NULL) + + keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) + vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) + names(vals) <- keys + + return(vals) + } - + renv_bootstrap_download <- function(version) { - + # if the renv version number has 4 components, assume it must # be retrieved via github nv <- numeric_version(version) components <- unclass(nv)[[1]] - - methods <- if (length(components) == 4L) { - list( + + # if this appears to be a development version of 'renv', we'll + # try to restore from github + dev <- length(components) == 4L + + # begin collecting different methods for finding renv + methods <- c( + renv_bootstrap_download_tarball, + if (dev) renv_bootstrap_download_github - ) - } else { - list( + else c( renv_bootstrap_download_cran_latest, renv_bootstrap_download_cran_archive ) - } - + ) + for (method in methods) { path <- tryCatch(method(version), error = identity) if (is.character(path) && file.exists(path)) return(path) } - + stop("failed to download renv ", version) - + } - + renv_bootstrap_download_impl <- function(url, destfile) { - + mode <- "wb" - + # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 fixup <- Sys.info()[["sysname"]] == "Windows" && substring(url, 1L, 5L) == "file:" - + if (fixup) mode <- "w+b" - - utils::download.file( + + args <- list( url = url, destfile = destfile, mode = mode, quiet = TRUE ) - + + if ("headers" %in% names(formals(utils::download.file))) + args$headers <- renv_bootstrap_download_custom_headers(url) + + do.call(utils::download.file, args) + + } + + renv_bootstrap_download_custom_headers <- function(url) { + + headers <- getOption("renv.download.headers") + if (is.null(headers)) + return(character()) + + if (!is.function(headers)) + stopf("'renv.download.headers' is not a function") + + headers <- headers(url) + if (length(headers) == 0L) + return(character()) + + if (is.list(headers)) + headers <- unlist(headers, recursive = FALSE, use.names = TRUE) + + ok <- + is.character(headers) && + is.character(names(headers)) && + all(nzchar(names(headers))) + + if (!ok) + stop("invocation of 'renv.download.headers' did not return a named character vector") + + headers + } - + renv_bootstrap_download_cran_latest <- function(version) { - - repos <- renv_bootstrap_download_cran_latest_find(version) - + + spec <- renv_bootstrap_download_cran_latest_find(version) + type <- spec$type + repos <- spec$repos + message("* Downloading renv ", version, " ... ", appendLF = FALSE) - - downloader <- function(type) { - - tryCatch( - utils::download.packages( - pkgs = "renv", - destdir = tempdir(), - repos = repos, - type = type, - quiet = TRUE - ), - condition = identity - ) - - } - - # first, try downloading a binary on Windows + macOS if appropriate - binary <- - !identical(.Platform$pkgType, "source") && - !identical(getOption("pkgType"), "source") && - Sys.info()[["sysname"]] %in% c("Darwin", "Windows") - - if (binary) { - info <- downloader(type = "binary") - if (!inherits(info, "condition")) { - message("OK (downloaded binary)") - return(info[1, 2]) - } - } - - # otherwise, try downloading a source tarball - info <- downloader(type = "source") - if (inherits(info, "condition")) { + + baseurl <- utils::contrib.url(repos = repos, type = type) + ext <- if (identical(type, "source")) + ".tar.gz" + else if (Sys.info()[["sysname"]] == "Windows") + ".zip" + else + ".tgz" + name <- sprintf("renv_%s%s", version, ext) + url <- paste(baseurl, name, sep = "/") + + destfile <- file.path(tempdir(), name) + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (inherits(status, "condition")) { message("FAILED") return(FALSE) } - + # report success and return - message("OK (downloaded source)") - info[1, 2] - + message("OK (downloaded ", type, ")") + destfile + } - + renv_bootstrap_download_cran_latest_find <- function(version) { - - all <- renv_bootstrap_repos() - - for (repos in all) { - - db <- tryCatch( - as.data.frame( - x = utils::available.packages(repos = repos), - stringsAsFactors = FALSE - ), - error = identity - ) - - if (inherits(db, "error")) - next - - entry <- db[db$Package %in% "renv" & db$Version %in% version, ] - if (nrow(entry) == 0) - next - - return(repos) - + + # check whether binaries are supported on this system + binary <- + getOption("renv.bootstrap.binary", default = TRUE) && + !identical(.Platform$pkgType, "source") && + !identical(getOption("pkgType"), "source") && + Sys.info()[["sysname"]] %in% c("Darwin", "Windows") + + types <- c(if (binary) "binary", "source") + + # iterate over types + repositories + for (type in types) { + for (repos in renv_bootstrap_repos()) { + + # retrieve package database + db <- tryCatch( + as.data.frame( + utils::available.packages(type = type, repos = repos), + stringsAsFactors = FALSE + ), + error = identity + ) + + if (inherits(db, "error")) + next + + # check for compatible entry + entry <- db[db$Package %in% "renv" & db$Version %in% version, ] + if (nrow(entry) == 0) + next + + # found it; return spec to caller + spec <- list(entry = entry, type = type, repos = repos) + return(spec) + + } } - + + # if we got here, we failed to find renv fmt <- "renv %s is not available from your declared package repositories" stop(sprintf(fmt, version)) - + } - + renv_bootstrap_download_cran_archive <- function(version) { - + name <- sprintf("renv_%s.tar.gz", version) repos <- renv_bootstrap_repos() urls <- file.path(repos, "src/contrib/Archive/renv", name) destfile <- file.path(tempdir(), name) - + message("* Downloading renv ", version, " ... ", appendLF = FALSE) - + for (url in urls) { - + status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) - + if (identical(status, 0L)) { message("OK") return(destfile) } - + } - + message("FAILED") return(FALSE) - + + } + + renv_bootstrap_download_tarball <- function(version) { + + # if the user has provided the path to a tarball via + # an environment variable, then use it + tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) + if (is.na(tarball)) + return() + + # allow directories + info <- file.info(tarball, extra_cols = FALSE) + if (identical(info$isdir, TRUE)) { + name <- sprintf("renv_%s.tar.gz", version) + tarball <- file.path(tarball, name) + } + + # bail if it doesn't exist + if (!file.exists(tarball)) { + + # let the user know we weren't able to honour their request + fmt <- "* RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." + msg <- sprintf(fmt, tarball) + warning(msg) + + # bail + return() + + } + + fmt <- "* Bootstrapping with tarball at path '%s'." + msg <- sprintf(fmt, tarball) + message(msg) + + tarball + } - + renv_bootstrap_download_github <- function(version) { - + enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") if (!identical(enabled, "TRUE")) return(FALSE) - + # prepare download options pat <- Sys.getenv("GITHUB_PAT") if (nzchar(Sys.which("curl")) && nzchar(pat)) { @@ -259,42 +390,48 @@ local({ options(download.file.method = "wget", download.file.extra = extra) on.exit(do.call(base::options, saved), add = TRUE) } - + message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) - + url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) name <- sprintf("renv_%s.tar.gz", version) destfile <- file.path(tempdir(), name) - + status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) - + if (!identical(status, 0L)) { message("FAILED") return(FALSE) } - + message("OK") return(destfile) - + } - + renv_bootstrap_install <- function(version, tarball, library) { - + # attempt to install it into project library message("* Installing renv ", version, " ... ", appendLF = FALSE) dir.create(library, showWarnings = FALSE, recursive = TRUE) - + # invoke using system2 so we can capture and report output bin <- R.home("bin") exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" r <- file.path(bin, exe) - args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball)) + + args <- c( + "--vanilla", "CMD", "INSTALL", "--no-multiarch", + "-l", shQuote(path.expand(library)), + shQuote(path.expand(tarball)) + ) + output <- system2(r, args, stdout = TRUE, stderr = TRUE) message("Done!") - + # check for successful install status <- attr(output, "status") if (is.numeric(status) && !identical(status, 0L)) { @@ -303,101 +440,101 @@ local({ text <- c(header, lines, output) writeLines(text, con = stderr()) } - + status - + } - + renv_bootstrap_platform_prefix <- function() { - + # construct version prefix version <- paste(R.version$major, R.version$minor, sep = ".") prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") - + # include SVN revision for development versions of R # (to avoid sharing platform-specific artefacts with released versions of R) devel <- identical(R.version[["status"]], "Under development (unstable)") || identical(R.version[["nickname"]], "Unsuffered Consequences") - + if (devel) prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") - + # build list of path components components <- c(prefix, R.version$platform) - + # include prefix if provided by user prefix <- renv_bootstrap_platform_prefix_impl() if (!is.na(prefix) && nzchar(prefix)) components <- c(prefix, components) - + # build prefix paste(components, collapse = "/") - + } - + renv_bootstrap_platform_prefix_impl <- function() { - + # if an explicit prefix has been supplied, use it prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) if (!is.na(prefix)) return(prefix) - + # if the user has requested an automatic prefix, generate it auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) if (auto %in% c("TRUE", "True", "true", "1")) return(renv_bootstrap_platform_prefix_auto()) - + # empty string on failure "" - + } - + renv_bootstrap_platform_prefix_auto <- function() { - + prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) if (inherits(prefix, "error") || prefix %in% "unknown") { - + msg <- paste( "failed to infer current operating system", "please file a bug report at https://github.com/rstudio/renv/issues", sep = "; " ) - + warning(msg) - + } - + prefix - + } - + renv_bootstrap_platform_os <- function() { - + sysinfo <- Sys.info() sysname <- sysinfo[["sysname"]] - + # handle Windows + macOS up front if (sysname == "Windows") return("windows") else if (sysname == "Darwin") return("macos") - + # check for os-release files for (file in c("/etc/os-release", "/usr/lib/os-release")) if (file.exists(file)) return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) - + # check for redhat-release files if (file.exists("/etc/redhat-release")) return(renv_bootstrap_platform_os_via_redhat_release()) - + "unknown" - + } - + renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { - + # read /etc/os-release release <- utils::read.table( file = file, @@ -407,13 +544,13 @@ local({ comment.char = "#", stringsAsFactors = FALSE ) - + vars <- as.list(release$Value) names(vars) <- release$Key - + # get os name os <- tolower(sysinfo[["sysname"]]) - + # read id id <- "unknown" for (field in c("ID", "ID_LIKE")) { @@ -422,7 +559,7 @@ local({ break } } - + # read version version <- "unknown" for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { @@ -431,17 +568,17 @@ local({ break } } - + # join together paste(c(os, id, version), collapse = "-") - + } - + renv_bootstrap_platform_os_via_redhat_release <- function() { - + # read /etc/redhat-release contents <- readLines("/etc/redhat-release", warn = FALSE) - + # infer id id <- if (grepl("centos", contents, ignore.case = TRUE)) "centos" @@ -449,62 +586,77 @@ local({ "redhat" else "unknown" - + # try to find a version component (very hacky) version <- "unknown" - + parts <- strsplit(contents, "[[:space:]]")[[1L]] for (part in parts) { - + nv <- tryCatch(numeric_version(part), error = identity) if (inherits(nv, "error")) next - + version <- nv[1, 1] break - + } - + paste(c("linux", id, version), collapse = "-") - + } - + renv_bootstrap_library_root_name <- function(project) { - + # use project name as-is if requested asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") if (asis) return(basename(project)) - + # otherwise, disambiguate based on project's path id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) paste(basename(project), id, sep = "-") - + } - + renv_bootstrap_library_root <- function(project) { - + + prefix <- renv_bootstrap_profile_prefix() + path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) if (!is.na(path)) - return(path) - - path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) - if (!is.na(path)) { + return(paste(c(path, prefix), collapse = "/")) + + path <- renv_bootstrap_library_root_impl(project) + if (!is.null(path)) { name <- renv_bootstrap_library_root_name(project) - return(file.path(path, name)) + return(paste(c(path, prefix, name), collapse = "/")) } - - prefix <- renv_bootstrap_profile_prefix() - paste(c(project, prefix, "renv/library"), collapse = "/") - + + renv_bootstrap_paths_renv("library", project = project) + + } + + renv_bootstrap_library_root_impl <- function(project) { + + root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) + if (!is.na(root)) + return(root) + + type <- renv_bootstrap_project_type(project) + if (identical(type, "package")) { + userdir <- renv_bootstrap_user_dir() + return(file.path(userdir, "library")) + } + } - + renv_bootstrap_validate_version <- function(version) { - + loadedversion <- utils::packageDescription("renv", fields = "Version") if (version == loadedversion) return(TRUE) - + # assume four-component versions are from GitHub; three-component # versions are from CRAN components <- strsplit(loadedversion, "[.-]")[[1]] @@ -512,84 +664,84 @@ local({ paste("rstudio/renv", loadedversion, sep = "@") else paste("renv", loadedversion, sep = "@") - + fmt <- paste( "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", sep = "\n" ) - + msg <- sprintf(fmt, loadedversion, version, remote) warning(msg, call. = FALSE) - + FALSE - + } - + renv_bootstrap_hash_text <- function(text) { - + hashfile <- tempfile("renv-hash-") on.exit(unlink(hashfile), add = TRUE) - + writeLines(text, con = hashfile) tools::md5sum(hashfile) - + } - + renv_bootstrap_load <- function(project, libpath, version) { - + # try to load renv from the project library if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) return(FALSE) - + # warn if the version of renv loaded does not match renv_bootstrap_validate_version(version) - + # load the project renv::load(project) - + TRUE - + } - + renv_bootstrap_profile_load <- function(project) { - + # if RENV_PROFILE is already set, just use that profile <- Sys.getenv("RENV_PROFILE", unset = NA) if (!is.na(profile) && nzchar(profile)) return(profile) - + # check for a profile file (nothing to do if it doesn't exist) - path <- file.path(project, "renv/local/profile") + path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) if (!file.exists(path)) return(NULL) - + # read the profile, and set it if it exists contents <- readLines(path, warn = FALSE) if (length(contents) == 0L) return(NULL) - + # set RENV_PROFILE profile <- contents[[1L]] - if (nzchar(profile)) + if (!profile %in% c("", "default")) Sys.setenv(RENV_PROFILE = profile) - + profile - + } - + renv_bootstrap_profile_prefix <- function() { profile <- renv_bootstrap_profile_get() if (!is.null(profile)) - return(file.path("renv/profiles", profile)) + return(file.path("profiles", profile, "renv")) } - + renv_bootstrap_profile_get <- function() { profile <- Sys.getenv("RENV_PROFILE", unset = "") renv_bootstrap_profile_normalize(profile) } - + renv_bootstrap_profile_set <- function(profile) { profile <- renv_bootstrap_profile_normalize(profile) if (is.null(profile)) @@ -597,14 +749,201 @@ local({ else Sys.setenv(RENV_PROFILE = profile) } - + renv_bootstrap_profile_normalize <- function(profile) { - + if (is.null(profile) || profile %in% c("", "default")) return(NULL) - + profile - + + } + + renv_bootstrap_path_absolute <- function(path) { + + substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( + substr(path, 1L, 1L) %in% c(letters, LETTERS) && + substr(path, 2L, 3L) %in% c(":/", ":\\") + ) + + } + + renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { + renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") + root <- if (renv_bootstrap_path_absolute(renv)) NULL else project + prefix <- if (profile) renv_bootstrap_profile_prefix() + components <- c(root, renv, prefix, ...) + paste(components, collapse = "/") + } + + renv_bootstrap_project_type <- function(path) { + + descpath <- file.path(path, "DESCRIPTION") + if (!file.exists(descpath)) + return("unknown") + + desc <- tryCatch( + read.dcf(descpath, all = TRUE), + error = identity + ) + + if (inherits(desc, "error")) + return("unknown") + + type <- desc$Type + if (!is.null(type)) + return(tolower(type)) + + package <- desc$Package + if (!is.null(package)) + return("package") + + "unknown" + + } + + renv_bootstrap_user_dir <- function() { + dir <- renv_bootstrap_user_dir_impl() + path.expand(chartr("\\", "/", dir)) + } + + renv_bootstrap_user_dir_impl <- function() { + + # use local override if set + override <- getOption("renv.userdir.override") + if (!is.null(override)) + return(override) + + # use R_user_dir if available + tools <- asNamespace("tools") + if (is.function(tools$R_user_dir)) + return(tools$R_user_dir("renv", "cache")) + + # try using our own backfill for older versions of R + envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") + for (envvar in envvars) { + root <- Sys.getenv(envvar, unset = NA) + if (!is.na(root)) + return(file.path(root, "R/renv")) + } + + # use platform-specific default fallbacks + if (Sys.info()[["sysname"]] == "Windows") + file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") + else if (Sys.info()[["sysname"]] == "Darwin") + "~/Library/Caches/org.R-project.R/R/renv" + else + "~/.cache/R/renv" + + } + + + renv_json_read <- function(file = NULL, text = NULL) { + + # if jsonlite is loaded, use that instead + if ("jsonlite" %in% loadedNamespaces()) + renv_json_read_jsonlite(file, text) + else + renv_json_read_default(file, text) + + } + + renv_json_read_jsonlite <- function(file = NULL, text = NULL) { + text <- paste(text %||% read(file), collapse = "\n") + jsonlite::fromJSON(txt = text, simplifyVector = FALSE) + } + + renv_json_read_default <- function(file = NULL, text = NULL) { + + # find strings in the JSON + text <- paste(text %||% read(file), collapse = "\n") + pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' + locs <- gregexpr(pattern, text, perl = TRUE)[[1]] + + # if any are found, replace them with placeholders + replaced <- text + strings <- character() + replacements <- character() + + if (!identical(c(locs), -1L)) { + + # get the string values + starts <- locs + ends <- locs + attr(locs, "match.length") - 1L + strings <- substring(text, starts, ends) + + # only keep those requiring escaping + strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) + + # compute replacements + replacements <- sprintf('"\032%i\032"', seq_along(strings)) + + # replace the strings + mapply(function(string, replacement) { + replaced <<- sub(string, replacement, replaced, fixed = TRUE) + }, strings, replacements) + + } + + # transform the JSON into something the R parser understands + transformed <- replaced + transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) + transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) + transformed <- gsub("[]}]", ")", transformed, perl = TRUE) + transformed <- gsub(":", "=", transformed, fixed = TRUE) + text <- paste(transformed, collapse = "\n") + + # parse it + json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] + + # construct map between source strings, replaced strings + map <- as.character(parse(text = strings)) + names(map) <- as.character(parse(text = replacements)) + + # convert to list + map <- as.list(map) + + # remap strings in object + remapped <- renv_json_remap(json, map) + + # evaluate + eval(remapped, envir = baseenv()) + + } + + renv_json_remap <- function(json, map) { + + # fix names + if (!is.null(names(json))) { + lhs <- match(names(json), names(map), nomatch = 0L) + rhs <- match(names(map), names(json), nomatch = 0L) + names(json)[rhs] <- map[lhs] + } + + # fix values + if (is.character(json)) + return(map[[json]] %||% json) + + # handle true, false, null + if (is.name(json)) { + text <- as.character(json) + if (text == "true") + return(TRUE) + else if (text == "false") + return(FALSE) + else if (text == "null") + return(NULL) + } + + # recurse + if (is.recursive(json)) { + for (i in seq_along(json)) { + json[i] <- list(renv_json_remap(json[[i]], map)) + } + } + + json + } # load the renv profile, if any From cf68bff924c556b2f2477ec724c7d4890f10713c Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Thu, 3 Nov 2022 15:11:09 +0000 Subject: [PATCH 101/179] #116 - Clean up unit test examples and address #81 too --- docs/pkgdown.yml | 2 +- vignettes/programming_strategy.Rmd | 30 ++++++++++++++++++++---------- vignettes/release_strategy.Rmd | 1 + 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/pkgdown.yml b/docs/pkgdown.yml index 997f59f5..9c86a55f 100644 --- a/docs/pkgdown.yml +++ b/docs/pkgdown.yml @@ -10,7 +10,7 @@ articles: release_strategy: release_strategy.html unit_test_guidance: unit_test_guidance.html writing_vignettes: writing_vignettes.html -last_built: 2022-11-02T17:55Z +last_built: 2022-11-03T15:07Z urls: reference: https://pharmaverse.github.io/admiraldev/devel/reference article: https://pharmaverse.github.io/admiraldev/devel/articles diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 3024d33e..a568f89b 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -585,22 +585,32 @@ will need to be quoted. Unit tests for deprecated functions and parameters must be added to `tests/testthat/test-deprecation.R` to ensure that a warning or error is issued. -When writing the unit test, check that the error or warning has the right class, i.e. "lifecycle_error_deprecated" or "lifecycle_warning_deprecated", respectively. The expect statement of the unit-test should follow the corresponding format: +When writing the unit test, check that the error or warning has the right class, i.e. "lifecycle_error_deprecated" or "lifecycle_warning_deprecated", respectively. The unit-test should follow the corresponding format, per the [unit test guidance](unit_test_guidance.html#writing-unit-tests-in-admiral-): ``` # For deprecated functions that issues error -expect_error( - deprecated_function_error(), - class = "lifecycle_error_deprecated" -) + +## Test #: An error is thrown if `derive_var_example()` is called ---- +test_that("deprecation Test #: derive_var_example() An error is thrown if + `derive_var_example()` is called", { + expect_error( + derive_var_example(), + class = "lifecycle_error_deprecated" + ) +}) ``` ``` -# For deprecated functions that issues warning -expect_warning( - deprecated_function_warning(), - class = "lifecycle_warning_deprecated" -) +# For deprecated functions that issues warning + +## Test #: A warning is thrown if `derive_var_example()` is called ---- +test_that("deprecation Test #: derive_var_example() A warning is thrown if + `derive_var_example()` is called", { + expect_warning( + derive_var_example(), + class = "lifecycle_warning_deprecated" + ) +}) ``` diff --git a/vignettes/release_strategy.Rmd b/vignettes/release_strategy.Rmd index 62d309f0..cad575b5 100644 --- a/vignettes/release_strategy.Rmd +++ b/vignettes/release_strategy.Rmd @@ -57,6 +57,7 @@ Occasionally we will need to release a hot fix to address a package breaking bug 1) Identify all the bugs that need to be fixed for this hot fix release and label with hot fix label. 1) Branches addressing the bugs should have Pull Requests merged into the `patch` branch **NOT** the `devel` branch. +1) When naming the branch follow the [naming conventions](git_usage.html#implementing-an-issue) guide but use the `@main` suffix 1) Create a Pull Request from `patch` into the `pre-release` branch. Verify that all CI/CD checks are passing, merge and bundle up and send off to CRAN. 1) Once package is approved and available on CRAN, another Pull Request is created for merging the `pre-release` branch into the `main` branch. This will trigger the action to rebuild the `{admiral}` website with all the updates for this hot fix release. 1) Use the release button on GitHub to "release" the package onto GitHub. This release onto Github archives the version of code within the `main` branch, attaches the News/Changelog file, bundles the code into a `tar.gz` file and makes a validation report via the GitHub action `validation` from [insightsengineering/validatoR](https://github.com/insightsengineering/thevalidatoR). Please see past [admiral releases](https://github.com/pharmaverse/admiral/releases) for reference. From fa0c486f98775cd65a8823dbec4c41ad5bb1b32f Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Thu, 3 Nov 2022 15:42:39 +0000 Subject: [PATCH 102/179] update r_version sections to remove square brackets --- vignettes/pr_review_guidance.Rmd | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vignettes/pr_review_guidance.Rmd b/vignettes/pr_review_guidance.Rmd index d74331c2..8b64535d 100644 --- a/vignettes/pr_review_guidance.Rmd +++ b/vignettes/pr_review_guidance.Rmd @@ -177,14 +177,14 @@ for (pkg in base_recommended_pkgs) { assign(".lib.loc", ".library", envir = environment(.libPaths)) r_version <- getRversion() -if ([grepl]("^4.0", r_version)) { - [options](repos = "https://cran.microsoft.com/snapshot/2021-03-31") -} else if ([grepl]("^4.1", r_version)) { - [options](repos = "https://cran.microsoft.com/snapshot/2022-03-10") -} else if ([grepl]("release", r_version)) { - [options](repos = "https://cran.microsoft.com/snapshot/2022-06-23") +if (grepl("^4.0", r_version)) { + options(repos = "https://cran.microsoft.com/snapshot/2021-03-31") +} else if (grepl("^4.1", r_version)) { + options(repos = "https://cran.microsoft.com/snapshot/2022-03-10") +} else if (grepl("release", r_version)) { + options(repos = "https://cran.microsoft.com/snapshot/2022-06-23") } else { - [options](https://rdrr.io/r/base/options.html)(repos = "https://cran.rstudio.com") + options(https://rdrr.io/r/base/options.html)(repos = "https://cran.rstudio.com") } if (!requireNamespace("remotes", quietly = TRUE)) { From 0d16fd5f3351829fb08ac1768c2cd6af902c739b Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Thu, 3 Nov 2022 16:14:04 +0000 Subject: [PATCH 103/179] Update r_version link --- vignettes/pr_review_guidance.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/pr_review_guidance.Rmd b/vignettes/pr_review_guidance.Rmd index 8b64535d..d8ca399c 100644 --- a/vignettes/pr_review_guidance.Rmd +++ b/vignettes/pr_review_guidance.Rmd @@ -184,7 +184,7 @@ if (grepl("^4.0", r_version)) { } else if (grepl("release", r_version)) { options(repos = "https://cran.microsoft.com/snapshot/2022-06-23") } else { - options(https://rdrr.io/r/base/options.html)(repos = "https://cran.rstudio.com") + options(repos = "https://cran.rstudio.com") } if (!requireNamespace("remotes", quietly = TRUE)) { From 78621339a110ca8d7fb695f6676567389029f1c1 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Thu, 3 Nov 2022 19:16:45 +0000 Subject: [PATCH 104/179] #101 - Initial draft to increase coverage --- tests/testthat/test-compat_friendly_type.R | 64 +++++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-compat_friendly_type.R b/tests/testthat/test-compat_friendly_type.R index 3ac65e51..da00a2fa 100644 --- a/tests/testthat/test-compat_friendly_type.R +++ b/tests/testthat/test-compat_friendly_type.R @@ -1,11 +1,12 @@ +# friendly_type_of ---- ## Test 1: friendly_type_of() supports objects ---- -test_that("compat_friendly_type Test 1: friendly_type_of() supports objects", { +test_that("friendly_type_of Test 1: friendly_type_of() supports objects", { expect_equal(friendly_type_of(mtcars), "a object") expect_equal(friendly_type_of(quo(1)), "a object") }) ## Test 2: friendly_type_of() supports matrices and arrays (#141) ---- -test_that("compat_friendly_type Test 2: friendly_type_of() supports matrices and arrays (#141)", { +test_that("friendly_type_of Test 2: friendly_type_of() supports matrices and arrays (#141)", { expect_equal(friendly_type_of(list()), "an empty list") expect_equal(friendly_type_of(matrix(list(1, 2))), "a list matrix") expect_equal(friendly_type_of(array(list(1, 2, 3), dim = 1:3)), "a list array") @@ -18,7 +19,7 @@ test_that("compat_friendly_type Test 2: friendly_type_of() supports matrices and }) ## Test 3: friendly_type_of() handles scalars ---- -test_that("compat_friendly_type Test 3: friendly_type_of() handles scalars", { +test_that("friendly_type_of Test 3: friendly_type_of() handles scalars", { expect_equal(friendly_type_of(NA), "`NA`") expect_equal(friendly_type_of(TRUE), "`TRUE`") @@ -37,3 +38,60 @@ test_that("compat_friendly_type Test 3: friendly_type_of() handles scalars", { expect_equal(friendly_type_of(matrix(NA)), "a logical matrix") expect_equal(friendly_type_of(matrix(1)), "a double matrix") }) + +## Test 4: friendly_type_of() edge cases ---- +test_that("friendly_type_of Test 4: friendly_type_of() edge cases", { + expect_equal(friendly_type_of(), "absent") + expect_equal(friendly_type_of(NULL), "NULL") + expect_equal(friendly_type_of(1:2, length = TRUE), "an integer vector of length 2") +}) + +# .rlang_as_friendly_type ---- +## Test 5: .rlang_as_friendly_type() works as expected ---- +test_that(".rlang_as_friendly_type Test 5: .rlang_as_friendly_type() works as expected", { + expect_equal(.rlang_as_friendly_type(typeof(list(test = 1:3))), "a list") + expect_equal(.rlang_as_friendly_type(typeof(NULL)), "NULL") + expect_equal(.rlang_as_friendly_type(typeof(new.env(parent = emptyenv()))), "an environment") + expect_equal( + .rlang_as_friendly_type( + typeof(xml2::read_xml("")$node) + ), + "a pointer" + ) + + test_weakref <- new_weakref(new.env(parent = emptyenv()), + finalizer = function(e) message("finalized") + ) + expect_equal(.rlang_as_friendly_type(typeof(test_weakref)), "a weak reference") + + # setClass("Person", slots = c(name = "character", age = "numeric")) + # john <- new("Person", name = "John Smith", age = NA_real_) + # expect_equal(.rlang_as_friendly_type(typeof(john)), "an S4 object") + + # skip name + expect_equal(.rlang_as_friendly_type(typeof(sym("test symbol"))), "a symbol") + expect_equal(.rlang_as_friendly_type(typeof(expr(1 + 1))), "a call") + expect_equal(.rlang_as_friendly_type(typeof(pairlist(x = 1, y = 2))), "a pairlist node") + expect_equal(.rlang_as_friendly_type(typeof(expression(x <- 4, x))), "an expression vector") + # typo in char, supposed to be character? + # promise is impossible because it stops being a promise at evaluation? + # don't know how to check for ... + # don't know how to check for any + expect_equal(.rlang_as_friendly_type(typeof(compiler::compile(quote(1 + 3)))), "an internal bytecode object") + # skip primitive + # skip builtin + expect_equal(.rlang_as_friendly_type(typeof(switch)), "a primitive function") + expect_equal(.rlang_as_friendly_type(typeof(mean)), "a function") +}) + +# .rlang_stop_unexpected_typeof ---- +## Test 6: .rlang_stop_unexpected_typeof() works ---- +test_that(".rlang_stop_unexpected_typeof Test 6: .rlang_stop_unexpected_typeof() works", { + expect_error(.rlang_stop_unexpected_typeof("test"), "Unexpected type .") +}) + +# stop_input_type ---- +## Test 7: stop_input_type() works ---- +test_that("stop_input_type Test 7: stop_input_type() works", { + expect_error(stop_input_type(1, what = "character"), "`1` must be character, not a number") +}) From e956be91d11770ae2e3ff08457844c17b0dd9369 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Thu, 3 Nov 2022 20:22:55 +0000 Subject: [PATCH 105/179] #101 - More descriptive comments where lack of coverage --- tests/testthat/test-compat_friendly_type.R | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test-compat_friendly_type.R b/tests/testthat/test-compat_friendly_type.R index da00a2fa..c0d0515b 100644 --- a/tests/testthat/test-compat_friendly_type.R +++ b/tests/testthat/test-compat_friendly_type.R @@ -64,22 +64,25 @@ test_that(".rlang_as_friendly_type Test 5: .rlang_as_friendly_type() works as ex ) expect_equal(.rlang_as_friendly_type(typeof(test_weakref)), "a weak reference") + # For S4 classes -- think this triggers messages but the expect_equal comes fine # setClass("Person", slots = c(name = "character", age = "numeric")) # john <- new("Person", name = "John Smith", age = NA_real_) # expect_equal(.rlang_as_friendly_type(typeof(john)), "an S4 object") - # skip name + # Skip name expect_equal(.rlang_as_friendly_type(typeof(sym("test symbol"))), "a symbol") expect_equal(.rlang_as_friendly_type(typeof(expr(1 + 1))), "a call") expect_equal(.rlang_as_friendly_type(typeof(pairlist(x = 1, y = 2))), "a pairlist node") expect_equal(.rlang_as_friendly_type(typeof(expression(x <- 4, x))), "an expression vector") - # typo in char, supposed to be character? - # promise is impossible because it stops being a promise at evaluation? - # don't know how to check for ... - # don't know how to check for any + + # Unsure what char is in compat_friendly_type.R line 118 + # Promise seems impossible because it stops being a promise at evaluation? + # Unsure how to check for `...` + # Unsure how to check for `any` expect_equal(.rlang_as_friendly_type(typeof(compiler::compile(quote(1 + 3)))), "an internal bytecode object") - # skip primitive - # skip builtin + + # Skip primitive + # Skip builtin expect_equal(.rlang_as_friendly_type(typeof(switch)), "a primitive function") expect_equal(.rlang_as_friendly_type(typeof(mean)), "a function") }) From 336817e42e2b0cee829dcb5d05e46b55c352a079 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 4 Nov 2022 13:11:33 +0000 Subject: [PATCH 106/179] #101 - Cleanup lintr and address some formatting --- tests/testthat/test-compat_friendly_type.R | 49 ++++++++++------------ 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/tests/testthat/test-compat_friendly_type.R b/tests/testthat/test-compat_friendly_type.R index c0d0515b..ff1db0bc 100644 --- a/tests/testthat/test-compat_friendly_type.R +++ b/tests/testthat/test-compat_friendly_type.R @@ -42,49 +42,42 @@ test_that("friendly_type_of Test 3: friendly_type_of() handles scalars", { ## Test 4: friendly_type_of() edge cases ---- test_that("friendly_type_of Test 4: friendly_type_of() edge cases", { expect_equal(friendly_type_of(), "absent") - expect_equal(friendly_type_of(NULL), "NULL") expect_equal(friendly_type_of(1:2, length = TRUE), "an integer vector of length 2") -}) -# .rlang_as_friendly_type ---- -## Test 5: .rlang_as_friendly_type() works as expected ---- -test_that(".rlang_as_friendly_type Test 5: .rlang_as_friendly_type() works as expected", { - expect_equal(.rlang_as_friendly_type(typeof(list(test = 1:3))), "a list") - expect_equal(.rlang_as_friendly_type(typeof(NULL)), "NULL") - expect_equal(.rlang_as_friendly_type(typeof(new.env(parent = emptyenv()))), "an environment") - expect_equal( - .rlang_as_friendly_type( - typeof(xml2::read_xml("")$node) - ), - "a pointer" - ) + expect_equal(friendly_type_of(list(test = 1:3)), "a list") + expect_equal(friendly_type_of(NULL), "NULL") + expect_equal(friendly_type_of(new.env(parent = emptyenv())), "an environment") + expect_equal(friendly_type_of(xml2::read_xml("")$node), "a pointer") - test_weakref <- new_weakref(new.env(parent = emptyenv()), + test_weakref <- rlang::new_weakref(new.env(parent = emptyenv()), finalizer = function(e) message("finalized") ) - expect_equal(.rlang_as_friendly_type(typeof(test_weakref)), "a weak reference") - - # For S4 classes -- think this triggers messages but the expect_equal comes fine - # setClass("Person", slots = c(name = "character", age = "numeric")) - # john <- new("Person", name = "John Smith", age = NA_real_) - # expect_equal(.rlang_as_friendly_type(typeof(john)), "an S4 object") + expect_equal(friendly_type_of(test_weakref), "a weak reference") # Skip name - expect_equal(.rlang_as_friendly_type(typeof(sym("test symbol"))), "a symbol") - expect_equal(.rlang_as_friendly_type(typeof(expr(1 + 1))), "a call") - expect_equal(.rlang_as_friendly_type(typeof(pairlist(x = 1, y = 2))), "a pairlist node") - expect_equal(.rlang_as_friendly_type(typeof(expression(x <- 4, x))), "an expression vector") + expect_equal(friendly_type_of(sym("test symbol")), "a symbol") + expect_equal(friendly_type_of(expr(1 + 1)), "a call") + expect_equal(friendly_type_of(pairlist(x = 1, y = 2)), "a pairlist node") + expect_equal(friendly_type_of(expression(x <- 4, x)), "an expression vector") # Unsure what char is in compat_friendly_type.R line 118 # Promise seems impossible because it stops being a promise at evaluation? # Unsure how to check for `...` # Unsure how to check for `any` - expect_equal(.rlang_as_friendly_type(typeof(compiler::compile(quote(1 + 3)))), "an internal bytecode object") + + expect_equal(friendly_type_of(compiler::compile(quote(1 + 3))), "an internal bytecode object") # Skip primitive # Skip builtin - expect_equal(.rlang_as_friendly_type(typeof(switch)), "a primitive function") - expect_equal(.rlang_as_friendly_type(typeof(mean)), "a function") + expect_equal(friendly_type_of(switch), "a primitive function") + expect_equal(friendly_type_of(mean), "a function") +}) +# .rlang_as_friendly_type ---- +## Test 5: .rlang_as_friendly_type() works ---- +test_that(".rlang_as_friendly_type Test 5: .rlang_as_friendly_type() works", { + setClass("person", slots = c(name = "character", age = "numeric")) + john <- new("person", name = "John", age = 18) + expect_equal(.rlang_as_friendly_type(typeof(john)), "an S4 object") }) # .rlang_stop_unexpected_typeof ---- From cc4754b5fba1b9b8ef4b13fc920f71997336f4be Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 4 Nov 2022 13:33:47 +0000 Subject: [PATCH 107/179] #101 - Address cicd check errors --- tests/testthat/test-compat_friendly_type.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-compat_friendly_type.R b/tests/testthat/test-compat_friendly_type.R index ff1db0bc..bb1b580e 100644 --- a/tests/testthat/test-compat_friendly_type.R +++ b/tests/testthat/test-compat_friendly_type.R @@ -47,7 +47,9 @@ test_that("friendly_type_of Test 4: friendly_type_of() edge cases", { expect_equal(friendly_type_of(list(test = 1:3)), "a list") expect_equal(friendly_type_of(NULL), "NULL") expect_equal(friendly_type_of(new.env(parent = emptyenv())), "an environment") - expect_equal(friendly_type_of(xml2::read_xml("")$node), "a pointer") + + # If we go through with adding xml2 into namespace this should work, xml2 is in renv.lock + # expect_equal(friendly_type_of(xml2::read_xml("")$node), "a pointer") nolint test_weakref <- rlang::new_weakref(new.env(parent = emptyenv()), finalizer = function(e) message("finalized") @@ -89,5 +91,5 @@ test_that(".rlang_stop_unexpected_typeof Test 6: .rlang_stop_unexpected_typeof() # stop_input_type ---- ## Test 7: stop_input_type() works ---- test_that("stop_input_type Test 7: stop_input_type() works", { - expect_error(stop_input_type(1, what = "character"), "`1` must be character, not a number") + expect_error(stop_input_type(1, what = "character")) }) From c648a73cd16cc9129b201471de05d0927e1bbc72 Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Sun, 6 Nov 2022 11:12:05 -0500 Subject: [PATCH 108/179] Update test-assertions.R --- tests/testthat/test-assertions.R | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index fb5e4194..01ebffce 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -906,6 +906,7 @@ test_that("Test 74 : `assert_date_vector` does not throw an error expect_invisible( example_fun(NULL) ) +}) # assert_atomic_vector ---- ## Test 75: error if input is not atomic vector ---- From c3e37d384aa9ab4cc9214ea40d1136d6bcd8ae5d Mon Sep 17 00:00:00 2001 From: fshanlee Date: Mon, 7 Nov 2022 14:17:03 +0000 Subject: [PATCH 109/179] Amended assert_character_scalar() to fix a bug that occurred when case_sensitive = FALSE. --- R/assertions.R | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 19d0d21c..4fcfc24e 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -158,14 +158,19 @@ assert_character_scalar <- function(arg, abort(err_msg) } - if (!case_sensitive) { - arg <- tolower(arg) + if (case_sensitive) { + case_adjusted_arg <- arg if (!is.null(values)) { - values <- tolower(values) + case_adjusted_values <- values + } + } else { + case_adjusted_arg <- tolower(arg) + if (!is.null(values)) { + case_adjusted_values <- tolower(values) } } - if (!is.null(values) && arg %notin% values) { + if (!is.null(values) && case_adjusted_arg %notin% case_adjusted_values) { err_msg <- sprintf( "`%s` must be one of %s but is '%s'", arg_name(substitute(arg)), @@ -175,7 +180,7 @@ assert_character_scalar <- function(arg, abort(err_msg) } - invisible(arg) + invisible(case_adjusted_arg) } #' Is an Argument a Character Vector? From 000eaad4be0df30a7ff80d29a5b9173c7115787b Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 7 Nov 2022 18:50:19 +0000 Subject: [PATCH 110/179] #101 - Remove reference to rlang github issue --- tests/testthat/test-compat_friendly_type.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test-compat_friendly_type.R b/tests/testthat/test-compat_friendly_type.R index bb1b580e..a7600e1f 100644 --- a/tests/testthat/test-compat_friendly_type.R +++ b/tests/testthat/test-compat_friendly_type.R @@ -5,8 +5,8 @@ test_that("friendly_type_of Test 1: friendly_type_of() supports objects", { expect_equal(friendly_type_of(quo(1)), "a object") }) -## Test 2: friendly_type_of() supports matrices and arrays (#141) ---- -test_that("friendly_type_of Test 2: friendly_type_of() supports matrices and arrays (#141)", { +## Test 2: friendly_type_of() supports matrices and arrays ---- +test_that("friendly_type_of Test 2: friendly_type_of() supports matrices and arrays", { expect_equal(friendly_type_of(list()), "an empty list") expect_equal(friendly_type_of(matrix(list(1, 2))), "a list matrix") expect_equal(friendly_type_of(array(list(1, 2, 3), dim = 1:3)), "a list array") From c11c93dc348931aa6756e9c2a3df5b3cb6f75e1e Mon Sep 17 00:00:00 2001 From: "Golab, Ania {MDBL~South San Francisco}" Date: Mon, 7 Nov 2022 20:31:07 +0100 Subject: [PATCH 111/179] activate, empty line --- renv/activate.R | 478 ++++++++++++++++--------------- tests/testthat/test-assertions.R | 1 - 2 files changed, 240 insertions(+), 239 deletions(-) diff --git a/renv/activate.R b/renv/activate.R index e52e2dae..019b5a66 100644 --- a/renv/activate.R +++ b/renv/activate.R @@ -1,4 +1,6 @@ + local({ + # the requested version of renv version <- "0.16.0" @@ -56,100 +58,100 @@ local({ if ("renv" %in% loadedNamespaces()) unloadNamespace("renv") - # load bootstrap tools + # load bootstrap tools `%||%` <- function(x, y) { if (is.environment(x) || length(x)) x else y } - + bootstrap <- function(version, library) { - + # attempt to download renv tarball <- tryCatch(renv_bootstrap_download(version), error = identity) if (inherits(tarball, "error")) stop("failed to download renv ", version) - + # now attempt to install status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) if (inherits(status, "error")) stop("failed to install renv ", version) - + } - + renv_bootstrap_tests_running <- function() { getOption("renv.tests.running", default = FALSE) } - + renv_bootstrap_repos <- function() { - + # check for repos override repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) if (!is.na(repos)) return(repos) - + # check for lockfile repositories repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) if (!inherits(repos, "error") && length(repos)) return(repos) - + # if we're testing, re-use the test repositories if (renv_bootstrap_tests_running()) return(getOption("renv.tests.repos")) - + # retrieve current repos repos <- getOption("repos") - + # ensure @CRAN@ entries are resolved repos[repos == "@CRAN@"] <- getOption( "renv.repos.cran", "https://cloud.r-project.org" ) - + # add in renv.bootstrap.repos if set default <- c(FALLBACK = "https://cloud.r-project.org") extra <- getOption("renv.bootstrap.repos", default = default) repos <- c(repos, extra) - + # remove duplicates that might've snuck in dupes <- duplicated(repos) | duplicated(names(repos)) repos[!dupes] - + } - + renv_bootstrap_repos_lockfile <- function() { - + lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") if (!file.exists(lockpath)) return(NULL) - + lockfile <- tryCatch(renv_json_read(lockpath), error = identity) if (inherits(lockfile, "error")) { warning(lockfile) return(NULL) } - + repos <- lockfile$R$Repositories if (length(repos) == 0) return(NULL) - + keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) names(vals) <- keys - + return(vals) - + } - + renv_bootstrap_download <- function(version) { - + # if the renv version number has 4 components, assume it must # be retrieved via github nv <- numeric_version(version) components <- unclass(nv)[[1]] - + # if this appears to be a development version of 'renv', we'll # try to restore from github dev <- length(components) == 4L - + # begin collecting different methods for finding renv methods <- c( renv_bootstrap_download_tarball, @@ -160,79 +162,79 @@ local({ renv_bootstrap_download_cran_archive ) ) - + for (method in methods) { path <- tryCatch(method(version), error = identity) if (is.character(path) && file.exists(path)) return(path) } - + stop("failed to download renv ", version) - + } - + renv_bootstrap_download_impl <- function(url, destfile) { - + mode <- "wb" - + # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 fixup <- Sys.info()[["sysname"]] == "Windows" && substring(url, 1L, 5L) == "file:" - + if (fixup) mode <- "w+b" - + args <- list( url = url, destfile = destfile, mode = mode, quiet = TRUE ) - + if ("headers" %in% names(formals(utils::download.file))) args$headers <- renv_bootstrap_download_custom_headers(url) - + do.call(utils::download.file, args) - + } - + renv_bootstrap_download_custom_headers <- function(url) { - + headers <- getOption("renv.download.headers") if (is.null(headers)) return(character()) - + if (!is.function(headers)) stopf("'renv.download.headers' is not a function") - + headers <- headers(url) if (length(headers) == 0L) return(character()) - + if (is.list(headers)) headers <- unlist(headers, recursive = FALSE, use.names = TRUE) - + ok <- is.character(headers) && is.character(names(headers)) && all(nzchar(names(headers))) - + if (!ok) stop("invocation of 'renv.download.headers' did not return a named character vector") - + headers - + } - + renv_bootstrap_download_cran_latest <- function(version) { - + spec <- renv_bootstrap_download_cran_latest_find(version) type <- spec$type repos <- spec$repos - + message("* Downloading renv ", version, " ... ", appendLF = FALSE) - + baseurl <- utils::contrib.url(repos = repos, type = type) ext <- if (identical(type, "source")) ".tar.gz" @@ -242,39 +244,39 @@ local({ ".tgz" name <- sprintf("renv_%s%s", version, ext) url <- paste(baseurl, name, sep = "/") - + destfile <- file.path(tempdir(), name) status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) - + if (inherits(status, "condition")) { message("FAILED") return(FALSE) } - + # report success and return message("OK (downloaded ", type, ")") destfile - + } - + renv_bootstrap_download_cran_latest_find <- function(version) { - + # check whether binaries are supported on this system binary <- getOption("renv.bootstrap.binary", default = TRUE) && !identical(.Platform$pkgType, "source") && !identical(getOption("pkgType"), "source") && Sys.info()[["sysname"]] %in% c("Darwin", "Windows") - + types <- c(if (binary) "binary", "source") - + # iterate over types + repositories for (type in types) { for (repos in renv_bootstrap_repos()) { - + # retrieve package database db <- tryCatch( as.data.frame( @@ -283,98 +285,98 @@ local({ ), error = identity ) - + if (inherits(db, "error")) next - + # check for compatible entry entry <- db[db$Package %in% "renv" & db$Version %in% version, ] if (nrow(entry) == 0) next - + # found it; return spec to caller spec <- list(entry = entry, type = type, repos = repos) return(spec) - + } } - + # if we got here, we failed to find renv fmt <- "renv %s is not available from your declared package repositories" stop(sprintf(fmt, version)) - + } - + renv_bootstrap_download_cran_archive <- function(version) { - + name <- sprintf("renv_%s.tar.gz", version) repos <- renv_bootstrap_repos() urls <- file.path(repos, "src/contrib/Archive/renv", name) destfile <- file.path(tempdir(), name) - + message("* Downloading renv ", version, " ... ", appendLF = FALSE) - + for (url in urls) { - + status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) - + if (identical(status, 0L)) { message("OK") return(destfile) } - + } - + message("FAILED") return(FALSE) - + } - + renv_bootstrap_download_tarball <- function(version) { - + # if the user has provided the path to a tarball via # an environment variable, then use it tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) if (is.na(tarball)) return() - + # allow directories info <- file.info(tarball, extra_cols = FALSE) if (identical(info$isdir, TRUE)) { name <- sprintf("renv_%s.tar.gz", version) tarball <- file.path(tarball, name) } - + # bail if it doesn't exist if (!file.exists(tarball)) { - + # let the user know we weren't able to honour their request fmt <- "* RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." msg <- sprintf(fmt, tarball) warning(msg) - + # bail return() - + } - + fmt <- "* Bootstrapping with tarball at path '%s'." msg <- sprintf(fmt, tarball) message(msg) - + tarball - + } - + renv_bootstrap_download_github <- function(version) { - + enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") if (!identical(enabled, "TRUE")) return(FALSE) - + # prepare download options pat <- Sys.getenv("GITHUB_PAT") if (nzchar(Sys.which("curl")) && nzchar(pat)) { @@ -390,48 +392,48 @@ local({ options(download.file.method = "wget", download.file.extra = extra) on.exit(do.call(base::options, saved), add = TRUE) } - + message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) - + url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) name <- sprintf("renv_%s.tar.gz", version) destfile <- file.path(tempdir(), name) - + status <- tryCatch( renv_bootstrap_download_impl(url, destfile), condition = identity ) - + if (!identical(status, 0L)) { message("FAILED") return(FALSE) } - + message("OK") return(destfile) - + } - + renv_bootstrap_install <- function(version, tarball, library) { - + # attempt to install it into project library message("* Installing renv ", version, " ... ", appendLF = FALSE) dir.create(library, showWarnings = FALSE, recursive = TRUE) - + # invoke using system2 so we can capture and report output bin <- R.home("bin") exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" r <- file.path(bin, exe) - + args <- c( "--vanilla", "CMD", "INSTALL", "--no-multiarch", "-l", shQuote(path.expand(library)), shQuote(path.expand(tarball)) ) - + output <- system2(r, args, stdout = TRUE, stderr = TRUE) message("Done!") - + # check for successful install status <- attr(output, "status") if (is.numeric(status) && !identical(status, 0L)) { @@ -440,101 +442,101 @@ local({ text <- c(header, lines, output) writeLines(text, con = stderr()) } - + status - + } - + renv_bootstrap_platform_prefix <- function() { - + # construct version prefix version <- paste(R.version$major, R.version$minor, sep = ".") prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") - + # include SVN revision for development versions of R # (to avoid sharing platform-specific artefacts with released versions of R) devel <- identical(R.version[["status"]], "Under development (unstable)") || identical(R.version[["nickname"]], "Unsuffered Consequences") - + if (devel) prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") - + # build list of path components components <- c(prefix, R.version$platform) - + # include prefix if provided by user prefix <- renv_bootstrap_platform_prefix_impl() if (!is.na(prefix) && nzchar(prefix)) components <- c(prefix, components) - + # build prefix paste(components, collapse = "/") - + } - + renv_bootstrap_platform_prefix_impl <- function() { - + # if an explicit prefix has been supplied, use it prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) if (!is.na(prefix)) return(prefix) - + # if the user has requested an automatic prefix, generate it auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) if (auto %in% c("TRUE", "True", "true", "1")) return(renv_bootstrap_platform_prefix_auto()) - + # empty string on failure "" - + } - + renv_bootstrap_platform_prefix_auto <- function() { - + prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) if (inherits(prefix, "error") || prefix %in% "unknown") { - + msg <- paste( "failed to infer current operating system", "please file a bug report at https://github.com/rstudio/renv/issues", sep = "; " ) - + warning(msg) - + } - + prefix - + } - + renv_bootstrap_platform_os <- function() { - + sysinfo <- Sys.info() sysname <- sysinfo[["sysname"]] - + # handle Windows + macOS up front if (sysname == "Windows") return("windows") else if (sysname == "Darwin") return("macos") - + # check for os-release files for (file in c("/etc/os-release", "/usr/lib/os-release")) if (file.exists(file)) return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) - + # check for redhat-release files if (file.exists("/etc/redhat-release")) return(renv_bootstrap_platform_os_via_redhat_release()) - + "unknown" - + } - + renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { - + # read /etc/os-release release <- utils::read.table( file = file, @@ -544,13 +546,13 @@ local({ comment.char = "#", stringsAsFactors = FALSE ) - + vars <- as.list(release$Value) names(vars) <- release$Key - + # get os name os <- tolower(sysinfo[["sysname"]]) - + # read id id <- "unknown" for (field in c("ID", "ID_LIKE")) { @@ -559,7 +561,7 @@ local({ break } } - + # read version version <- "unknown" for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { @@ -568,17 +570,17 @@ local({ break } } - + # join together paste(c(os, id, version), collapse = "-") - + } - + renv_bootstrap_platform_os_via_redhat_release <- function() { - + # read /etc/redhat-release contents <- readLines("/etc/redhat-release", warn = FALSE) - + # infer id id <- if (grepl("centos", contents, ignore.case = TRUE)) "centos" @@ -586,77 +588,77 @@ local({ "redhat" else "unknown" - + # try to find a version component (very hacky) version <- "unknown" - + parts <- strsplit(contents, "[[:space:]]")[[1L]] for (part in parts) { - + nv <- tryCatch(numeric_version(part), error = identity) if (inherits(nv, "error")) next - + version <- nv[1, 1] break - + } - + paste(c("linux", id, version), collapse = "-") - + } - + renv_bootstrap_library_root_name <- function(project) { - + # use project name as-is if requested asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") if (asis) return(basename(project)) - + # otherwise, disambiguate based on project's path id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) paste(basename(project), id, sep = "-") - + } - + renv_bootstrap_library_root <- function(project) { - + prefix <- renv_bootstrap_profile_prefix() - + path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) if (!is.na(path)) return(paste(c(path, prefix), collapse = "/")) - + path <- renv_bootstrap_library_root_impl(project) if (!is.null(path)) { name <- renv_bootstrap_library_root_name(project) return(paste(c(path, prefix, name), collapse = "/")) } - + renv_bootstrap_paths_renv("library", project = project) - + } - + renv_bootstrap_library_root_impl <- function(project) { - + root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) if (!is.na(root)) return(root) - + type <- renv_bootstrap_project_type(project) if (identical(type, "package")) { userdir <- renv_bootstrap_user_dir() return(file.path(userdir, "library")) } - + } - + renv_bootstrap_validate_version <- function(version) { - + loadedversion <- utils::packageDescription("renv", fields = "Version") if (version == loadedversion) return(TRUE) - + # assume four-component versions are from GitHub; three-component # versions are from CRAN components <- strsplit(loadedversion, "[.-]")[[1]] @@ -664,84 +666,84 @@ local({ paste("rstudio/renv", loadedversion, sep = "@") else paste("renv", loadedversion, sep = "@") - + fmt <- paste( "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", sep = "\n" ) - + msg <- sprintf(fmt, loadedversion, version, remote) warning(msg, call. = FALSE) - + FALSE - + } - + renv_bootstrap_hash_text <- function(text) { - + hashfile <- tempfile("renv-hash-") on.exit(unlink(hashfile), add = TRUE) - + writeLines(text, con = hashfile) tools::md5sum(hashfile) - + } - + renv_bootstrap_load <- function(project, libpath, version) { - + # try to load renv from the project library if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) return(FALSE) - + # warn if the version of renv loaded does not match renv_bootstrap_validate_version(version) - + # load the project renv::load(project) - + TRUE - + } - + renv_bootstrap_profile_load <- function(project) { - + # if RENV_PROFILE is already set, just use that profile <- Sys.getenv("RENV_PROFILE", unset = NA) if (!is.na(profile) && nzchar(profile)) return(profile) - + # check for a profile file (nothing to do if it doesn't exist) path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) if (!file.exists(path)) return(NULL) - + # read the profile, and set it if it exists contents <- readLines(path, warn = FALSE) if (length(contents) == 0L) return(NULL) - + # set RENV_PROFILE profile <- contents[[1L]] if (!profile %in% c("", "default")) Sys.setenv(RENV_PROFILE = profile) - + profile - + } - + renv_bootstrap_profile_prefix <- function() { profile <- renv_bootstrap_profile_get() if (!is.null(profile)) return(file.path("profiles", profile, "renv")) } - + renv_bootstrap_profile_get <- function() { profile <- Sys.getenv("RENV_PROFILE", unset = "") renv_bootstrap_profile_normalize(profile) } - + renv_bootstrap_profile_set <- function(profile) { profile <- renv_bootstrap_profile_normalize(profile) if (is.null(profile)) @@ -749,25 +751,25 @@ local({ else Sys.setenv(RENV_PROFILE = profile) } - + renv_bootstrap_profile_normalize <- function(profile) { - + if (is.null(profile) || profile %in% c("", "default")) return(NULL) - + profile - + } - + renv_bootstrap_path_absolute <- function(path) { - + substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( substr(path, 1L, 1L) %in% c(letters, LETTERS) && substr(path, 2L, 3L) %in% c(":/", ":\\") ) - + } - + renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") root <- if (renv_bootstrap_path_absolute(renv)) NULL else project @@ -775,50 +777,50 @@ local({ components <- c(root, renv, prefix, ...) paste(components, collapse = "/") } - + renv_bootstrap_project_type <- function(path) { - + descpath <- file.path(path, "DESCRIPTION") if (!file.exists(descpath)) return("unknown") - + desc <- tryCatch( read.dcf(descpath, all = TRUE), error = identity ) - + if (inherits(desc, "error")) return("unknown") - + type <- desc$Type if (!is.null(type)) return(tolower(type)) - + package <- desc$Package if (!is.null(package)) return("package") - + "unknown" - + } - + renv_bootstrap_user_dir <- function() { dir <- renv_bootstrap_user_dir_impl() path.expand(chartr("\\", "/", dir)) } - + renv_bootstrap_user_dir_impl <- function() { - + # use local override if set override <- getOption("renv.userdir.override") if (!is.null(override)) return(override) - + # use R_user_dir if available tools <- asNamespace("tools") if (is.function(tools$R_user_dir)) return(tools$R_user_dir("renv", "cache")) - + # try using our own backfill for older versions of R envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") for (envvar in envvars) { @@ -826,7 +828,7 @@ local({ if (!is.na(root)) return(file.path(root, "R/renv")) } - + # use platform-specific default fallbacks if (Sys.info()[["sysname"]] == "Windows") file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") @@ -834,57 +836,57 @@ local({ "~/Library/Caches/org.R-project.R/R/renv" else "~/.cache/R/renv" - + } - - + + renv_json_read <- function(file = NULL, text = NULL) { - + # if jsonlite is loaded, use that instead if ("jsonlite" %in% loadedNamespaces()) renv_json_read_jsonlite(file, text) else renv_json_read_default(file, text) - + } - + renv_json_read_jsonlite <- function(file = NULL, text = NULL) { text <- paste(text %||% read(file), collapse = "\n") jsonlite::fromJSON(txt = text, simplifyVector = FALSE) } - + renv_json_read_default <- function(file = NULL, text = NULL) { - + # find strings in the JSON text <- paste(text %||% read(file), collapse = "\n") pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' locs <- gregexpr(pattern, text, perl = TRUE)[[1]] - + # if any are found, replace them with placeholders replaced <- text strings <- character() replacements <- character() - + if (!identical(c(locs), -1L)) { - + # get the string values starts <- locs ends <- locs + attr(locs, "match.length") - 1L strings <- substring(text, starts, ends) - + # only keep those requiring escaping strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) - + # compute replacements replacements <- sprintf('"\032%i\032"', seq_along(strings)) - + # replace the strings mapply(function(string, replacement) { replaced <<- sub(string, replacement, replaced, fixed = TRUE) }, strings, replacements) - + } - + # transform the JSON into something the R parser understands transformed <- replaced transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) @@ -892,38 +894,38 @@ local({ transformed <- gsub("[]}]", ")", transformed, perl = TRUE) transformed <- gsub(":", "=", transformed, fixed = TRUE) text <- paste(transformed, collapse = "\n") - + # parse it json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] - + # construct map between source strings, replaced strings map <- as.character(parse(text = strings)) names(map) <- as.character(parse(text = replacements)) - + # convert to list map <- as.list(map) - + # remap strings in object remapped <- renv_json_remap(json, map) - + # evaluate eval(remapped, envir = baseenv()) - + } - + renv_json_remap <- function(json, map) { - + # fix names if (!is.null(names(json))) { lhs <- match(names(json), names(map), nomatch = 0L) rhs <- match(names(map), names(json), nomatch = 0L) names(json)[rhs] <- map[lhs] } - + # fix values if (is.character(json)) return(map[[json]] %||% json) - + # handle true, false, null if (is.name(json)) { text <- as.character(json) @@ -934,16 +936,16 @@ local({ else if (text == "null") return(NULL) } - + # recurse if (is.recursive(json)) { for (i in seq_along(json)) { json[i] <- list(renv_json_remap(json[[i]], map)) } } - + json - + } # load the renv profile, if any diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 01ebffce..4aadb227 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -914,4 +914,3 @@ test_that("assert_atomic_vector Test 14: error if input is not atomic vector", { x <- list("a", "a", "b", "c", "d", "d", 1, 1, 4) expect_error(assert_atomic_vector(x)) }) - From 26ce669d085449cf0eb003d58863c4795cd6cc6d Mon Sep 17 00:00:00 2001 From: fshanlee Date: Mon, 7 Nov 2022 19:44:31 +0000 Subject: [PATCH 112/179] Added more tests for assert_character_scalar() when case_sensitive = FALSE. --- tests/testthat/test-assertions.R | 71 ++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 58d85c52..9e28ea6c 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -190,14 +190,41 @@ test_that("Test 15 : `assert_character_scalar` does not throw an error if }) test_that("Test 16 : `assert_character_scalar` does not throw an error if - case_sensitive is FALSE", { + case_sensitive is FALSE, and argument is returned in lower case", { example_fun <- function(character) { assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } - expect_invisible( - example_fun(character = "TEST") - ) + out <- expect_invisible(example_fun(character = "TEST") ) + expect_equal(out, "test") + + check_unit <- function(duration_unit) { + duration_unit <- assert_character_scalar( + duration_unit, + values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), + case_sensitive <- FALSE + ) + } + + out <- expect_invisible(check_unit("months")) + expect_equal(out, "months") + + out <- expect_invisible(check_unit("MONTHS")) + expect_equal(out, "months") + + check_unit2 <- function(duration_unit) { + duration_unit <- assert_character_scalar( + duration_unit, + values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), + case_sensitive <- FALSE + ) + } + + out <- expect_invisible(check_unit2("months")) + expect_equal(out, "months") + + out <- expect_invisible(check_unit2("MONTHS")) + expect_equal(out, "months") }) test_that("Test 17 : `assert_character_scalar` throws an error if @@ -209,6 +236,42 @@ test_that("Test 17 : `assert_character_scalar` throws an error if expect_error( example_fun(character = "oak") ) + + check_unit <- function(duration_unit) { + duration_unit <- assert_character_scalar( + duration_unit, + values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), + case_sensitive <- FALSE + ) + } + + expect_error(check_unit("month"), + paste0("`duration_unit` must be one of 'years', 'months', 'weeks', 'days', ", + "'hours', 'minutes' or 'seconds' but is 'month'") + ) + + expect_error(check_unit("MONTH"), + paste0("`duration_unit` must be one of 'years', 'months', 'weeks', 'days', ", + "'hours', 'minutes' or 'seconds' but is 'MONTH'") + ) + + check_unit2 <- function(duration_unit) { + duration_unit <- assert_character_scalar( + duration_unit, + values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), + case_sensitive <- FALSE + ) + } + + expect_error(check_unit2("month"), + paste0("`duration_unit` must be one of 'YEARS', 'MONTHS', 'WEEKS', 'DAYS', ", + "'HOURS', 'MINUTES' or 'SECONDS' but is 'month'") + ) + + expect_error(check_unit2("MONTH"), + paste0("`duration_unit` must be one of 'YEARS', 'MONTHS', 'WEEKS', 'DAYS', ", + "'HOURS', 'MINUTES' or 'SECONDS' but is 'MONTH'") + ) }) test_that("Test 18 : `assert_character_vector` throws an error if `arg` not a character vector", { From 8da3e831f69c6aecc8cd376595678d31c6b8deef Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Tue, 8 Nov 2022 06:21:13 +0000 Subject: [PATCH 113/179] #140 #37 #43 #155 #149 #147 #151 removed dependency on assertthat package, updated multiple function --- DESCRIPTION | 1 - NAMESPACE | 17 +- NEWS.md | 4 + R/admiraldev-package.R | 11 +- R/assertions.R | 8 +- R/dev_utilities.R | 39 +++- R/is.R | 318 ---------------------------- R/quo.R | 11 +- R/warnings.R | 2 +- README.Rmd | 2 +- docs/pkgdown.yml | 2 +- man/arg_name.Rd | 1 + man/as_name.Rd | 1 + man/convert_dtm_to_dtc.Rd | 1 + man/extract_vars.Rd | 1 + man/filter_if.Rd | 1 + man/grapes-notin-grapes.Rd | 1 + man/grapes-or-grapes.Rd | 1 + man/is_auto.Rd | 37 ---- man/is_date.Rd | 36 ---- man/is_named.Rd | 33 --- man/is_order_vars.Rd | 34 +-- man/is_timeunit.Rd | 37 ---- man/is_valid_date_entry.Rd | 37 ---- man/is_valid_day.Rd | 36 ---- man/is_valid_dtc.Rd | 33 --- man/is_valid_hour.Rd | 36 ---- man/is_valid_month.Rd | 36 ---- man/is_valid_sec_min.Rd | 36 ---- man/is_valid_time_entry.Rd | 37 ---- man/negate_vars.Rd | 1 + man/valid_time_units.Rd | 3 +- man/vars2chr.Rd | 1 + man/warn_if_invalid_dtc.Rd | 2 +- renv.lock | 7 - tests/testthat/test-assertions.R | 54 ++--- tests/testthat/test-dev_utilities.R | 28 ++- tests/testthat/test-is.R | 25 --- tests/testthat/test-quo.R | 4 +- vignettes/programming_strategy.Rmd | 5 +- 40 files changed, 146 insertions(+), 834 deletions(-) delete mode 100644 R/is.R delete mode 100644 man/is_auto.Rd delete mode 100644 man/is_date.Rd delete mode 100644 man/is_named.Rd delete mode 100644 man/is_timeunit.Rd delete mode 100644 man/is_valid_date_entry.Rd delete mode 100644 man/is_valid_day.Rd delete mode 100644 man/is_valid_dtc.Rd delete mode 100644 man/is_valid_hour.Rd delete mode 100644 man/is_valid_month.Rd delete mode 100644 man/is_valid_sec_min.Rd delete mode 100644 man/is_valid_time_entry.Rd delete mode 100644 tests/testthat/test-is.R diff --git a/DESCRIPTION b/DESCRIPTION index bde64344..c83cf019 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,7 +26,6 @@ Roxygen: list(markdown = TRUE) RoxygenNote: 7.2.0 Depends: R (>= 3.5) Imports: - assertthat (>= 0.2.1), dplyr (>= 0.8.4), hms (>= 0.5.3), lifecycle (>= 0.1.0), diff --git a/NAMESPACE b/NAMESPACE index 3c4c4d45..c8c3277f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -45,18 +45,7 @@ export(get_duplicates) export(get_new_tmp_var) export(get_source_vars) export(inner_join) -export(is_auto) -export(is_date) -export(is_named) export(is_order_vars) -export(is_timeunit) -export(is_valid_date_entry) -export(is_valid_day) -export(is_valid_dtc) -export(is_valid_hour) -export(is_valid_month) -export(is_valid_sec_min) -export(is_valid_time_entry) export(left_join) export(negate_vars) export(quo_c) @@ -75,9 +64,6 @@ export(warn_if_inconsistent_list) export(warn_if_invalid_dtc) export(warn_if_vars_exist) export(what_is_it) -importFrom(assertthat,"on_failure<-") -importFrom(assertthat,assert_that) -importFrom(assertthat,is.number) importFrom(dplyr,arrange) importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) @@ -123,6 +109,7 @@ importFrom(lubridate,duration) importFrom(lubridate,floor_date) importFrom(lubridate,hours) importFrom(lubridate,is.Date) +importFrom(lubridate,is.POSIXlt) importFrom(lubridate,is.instant) importFrom(lubridate,minutes) importFrom(lubridate,time_length) @@ -142,6 +129,7 @@ importFrom(purrr,map_if) importFrom(purrr,map_lgl) importFrom(purrr,modify_at) importFrom(purrr,modify_if) +importFrom(purrr,possibly) importFrom(purrr,reduce) importFrom(purrr,transpose) importFrom(purrr,walk) @@ -175,6 +163,7 @@ importFrom(rlang,is_logical) importFrom(rlang,is_quosure) importFrom(rlang,is_quosures) importFrom(rlang,is_symbol) +importFrom(rlang,missing_arg) importFrom(rlang,new_formula) importFrom(rlang,parse_expr) importFrom(rlang,parse_exprs) diff --git a/NEWS.md b/NEWS.md index 718f1f90..7f87b53a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,10 @@ - New keyword/family `create_aux` for functions creating auxiliary datasets (#126) - New function `assert_date_vector` (#129) + + - Remove dependency on `{assertthat}` (#149) + + - Test coverage for `admiraldev` have increased from 45% to approximately 100% (#94,#95, #96,#98,#101,#103) ## Updates of Existing Functions diff --git a/R/admiraldev-package.R b/R/admiraldev-package.R index c450e2d8..4b3c7b51 100644 --- a/R/admiraldev-package.R +++ b/R/admiraldev-package.R @@ -6,21 +6,20 @@ #' @importFrom magrittr %>% #' @importFrom rlang := abort arg_match as_function as_label as_string call2 caller_env #' call_name current_env .data enexpr enquo eval_bare eval_tidy expr -#' expr_interp expr_label f_lhs f_rhs inform +#' expr_interp expr_label f_lhs f_rhs inform missing_arg #' is_bare_formula is_call is_character is_formula is_integerish #' is_logical is_quosure is_quosures is_symbol new_formula #' parse_expr parse_exprs quo quo_get_expr quo_is_call #' quo_is_missing quo_is_null quo_is_symbol quos quo_squash quo_text #' set_names sym syms type_of warn quo_set_env quo_get_env #' @importFrom utils capture.output str -#' @importFrom purrr map map2 map_chr map_lgl reduce walk keep map_if transpose +#' @importFrom purrr map map2 map_chr map_lgl reduce walk keep map_if transpose possibly #' flatten every modify_at modify_if reduce compose #' @importFrom stringr str_c str_detect str_extract str_glue str_match -#' str_remove str_remove_all str_replace str_trim str_to_lower str_subset -#' str_to_title str_to_upper -#' @importFrom assertthat assert_that is.number on_failure<- +#' str_remove str_remove_all str_replace str_trim str_to_lower str_subset +#' str_to_title str_to_upper #' @importFrom lubridate as_datetime ceiling_date date days duration floor_date is.Date is.instant -#' time_length %--% ymd ymd_hms weeks years hours minutes +#' is.POSIXlt time_length %--% ymd ymd_hms weeks years hours minutes #' @importFrom tidyr drop_na nest pivot_longer pivot_wider unnest #' @importFrom tidyselect all_of contains vars_select #' @importFrom hms as_hms diff --git a/R/assertions.R b/R/assertions.R index 7f56841d..6d2e1f46 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -556,7 +556,7 @@ assert_order_vars <- function(arg, optional = FALSE) { default_err_msg <- paste( backquote(arg_name(substitute(arg))), - "must be a a list of unquoted variable names or `desc()` calls,", + "must be a list of unquoted variable names or `desc()` calls,", "e.g. `vars(USUBJID, desc(VISITNUM))`" ) @@ -572,7 +572,9 @@ assert_order_vars <- function(arg, optional = FALSE) { abort(default_err_msg) } - assert_that(is_order_vars(arg)) + if (!isTRUE(is_order_vars(arg)) == TRUE) { + abort(default_err_msg) + } invisible(arg) } @@ -1147,7 +1149,7 @@ assert_varval_list <- function(arg, # nolint valid_vals <- "a symbol, character scalar, numeric scalar, or `NA`" } - if (!accept_var & (!is_quosures(arg) || !is_named(arg))) { + if (!accept_var & (!is_quosures(arg) || !(!is.null(names(arg)) && all(names(arg) != "")))) { err_msg <- sprintf( paste0( "`%s` must be a named list of quosures where each element is ", diff --git a/R/dev_utilities.R b/R/dev_utilities.R index 63a99479..72d81cfa 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -95,7 +95,6 @@ extract_vars <- function(x, side = "lhs") { } } - #' Or #' #' @param lhs Any valid R expression @@ -140,6 +139,8 @@ as_name <- function(x) { #' Valid Time Units #' +#' Contains the acceptable character vector of valid time units +#' #' @return A `character` vector of valid time units #' #' @export @@ -231,3 +232,39 @@ filter_if <- function(dataset, filter) { filter(dataset, !!filter) } } + +#' Is order vars? +#' +#' Check if inputs are created using `vars()` or calls involving `desc()` +#' @param arg A 5, ", ..."), ")") - } - paste0( - "Argument ", - deparse(call$arg), - " = ", - msg, - " is not a lubridate date." - ) -} - -#' Is Time Unit? -#' -#' Checks if a string is a time unit, i.e., 'years', 'months', 'days', 'hours', -#' 'minutes', or 'seconds'. -#' -#' @param arg The argument to check -#' -#' @author Stefan Bundfuss -#' -#' @return `TRUE` if the argument is a time unit, `FALSE` otherwise -#' -#' @keywords is -#' @family is -#' @export -is_timeunit <- function(arg) { - arg %in% c("years", "months", "days", "hours", "minutes", "seconds") -} -on_failure(is_timeunit) <- function(call, env) { - paste0( - "Argument ", - deparse(call$arg), - " = ", - eval(call$arg, envir = env), - " is not a valid time unit.", - " Valid time units are 'years', 'months', 'days', 'hours', 'minutes', and 'seconds'." - ) -} - -#' Check Validity of the Date Imputation Input -#' -#' Date_imputation format should be specified as "dd-mm" (e.g. "01-01") -#' or as a keyword: "FIRST", "MID", "LAST" -#' -#' @param arg The argument to check -#' -#' @author Samia Kabi -#' -#' @return `TRUE` if the argument is a valid date_imputation input, `FALSE` otherwise -#' -#' @keywords is -#' @family is -#' @export -is_valid_date_entry <- function(arg) { - pattern <- "^(01|02|03|04|05|06|07|08|09|10|11|12)-([0-9]{2})$" - grepl(pattern, arg) | str_to_upper(arg) %in% c("FIRST", "MID", "LAST") -} -on_failure(is_valid_date_entry) <- function(call, env) { - paste0( - "Argument ", - deparse(call$arg), - " = ", - eval(call$arg, envir = env), - " is not a valid date entry.\n", - "date_imputation should be specified as 'mm-dd' (e.g. '01-21') or ", - "'FIRST', 'MID', 'LAST' to get the first/mid/last day/month" - ) -} - -#' Check Validity of the Time Imputation Input -#' -#' Time_imputation format should be specified as "hh:mm:ss" (e.g. "00:00:00") -#' or as a keyword: "FIRST", "LAST" -#' -#' @param arg The argument to check -#' -#' @author Samia Kabi -#' -#' @return `TRUE` if the argument is a valid time_imputation input, `FALSE` otherwise -#' -#' @keywords is -#' @family is -#' @export -is_valid_time_entry <- function(arg) { - pattern <- "^([0-9]{2}):([0-9]{2}):([0-9]{2})$" - grepl(pattern, arg) | str_to_upper(arg) %in% c("FIRST", "LAST") -} -on_failure(is_valid_time_entry) <- function(call, env) { - paste0( - "Argument ", - deparse(call$arg), - " = ", - eval(call$arg, envir = env), - " is not a valid time entry.\n", - "time_imputation should be specified as 'hh:mm:ss' (e.g. '00:00:00') or ", - "'FIRST', 'LAST' to get the first/last time of the day" - ) -} - -#' Check Validity of the Minute/Second Portion of the Time Input -#' -#' Minutes and seconds are expected to range from 0 to 59 -#' -#' @param arg The argument to check -#' -#' @author Samia Kabi -#' -#' @return `TRUE` if the argument is a valid min/sec input, `FALSE` otherwise -#' -#' @keywords is -#' @family is -#' @export -is_valid_sec_min <- function(arg) { - arg %in% 0:59 -} -on_failure(is_valid_sec_min) <- function(call, env) { - paste0( - "Argument ", - deparse(call$arg), - " = ", - eval(call$arg, envir = env), - " is not a valid min/sec.\n", - "Values must be between between 0-59" - ) -} - -#' Check Validity of the Hour Portion in the Time Input -#' -#' Hours are expected to range from 0 to 23 -#' -#' @param arg The argument to check -#' -#' @author Samia Kabi -#' -#' @return `TRUE` if the argument is a valid hour input, `FALSE` otherwise -#' -#' @keywords is -#' @family is -#' @export -is_valid_hour <- function(arg) { - arg %in% 0:23 -} -on_failure(is_valid_hour) <- function(call, env) { - paste0( - "Argument ", - deparse(call$arg), - "=", - eval(call$arg, envir = env), - " is not a valid hour.\n", - "Values must be between 0-23" - ) -} - -#' Check Validity of the Day Portion in the Date Input -#' -#' Days are expected to range from 1 to 31 -#' -#' @param arg The argument to check -#' -#' @author Samia Kabi -#' -#' @return `TRUE` if the argument is a day input, `FALSE` otherwise -#' -#' @keywords is -#' @family is -#' @export -is_valid_day <- function(arg) { - arg %in% 1:31 -} -on_failure(is_valid_day) <- function(call, env) { - paste0( - "Argument ", - deparse(call$arg), - " = ", - eval(call$arg, envir = env), - " is not a valid day.\n", - "Values must be between 1-31" - ) -} - -#' Check Validity of the Month Portion in the Date Input -#' -#' Days are expected to range from 1 to 12 -#' -#' @param arg The argument to check -#' -#' @author Samia Kabi -#' -#' @return `TRUE` if the argument is a month input, `FALSE` otherwise -#' -#' @keywords is -#' @family is -#' -#' @export -is_valid_month <- function(arg) { - arg %in% 1:12 -} -on_failure(is_valid_month) <- function(call, env) { - paste0( - "Argument ", - deparse(call$arg), - " = ", - eval(call$arg, envir = env), - " is not a valid month.\n", - "Values for month must be between 1-12. ", - "Please check the date_imputation input: it should be sepcified as 'dd-mm'" - ) -} - -#' Is order vars? -#' -#' @param arg A throws error }) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 90c8008e..f14667b4 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -108,7 +108,7 @@ variables must start with `temp_` and must be removed from the output dataset. | Function name prefix | Description | |----------------------------------------------|-----------------------------------------------------------------------------------------------------| -| `assert_` / `warn_` / `is_` | Functions that check other functions’ inputs | +| `assert_` / `warn_` | Functions that check other functions’ inputs | | `derive_` | Functions that take a dataset as input and return a new dataset with additional rows and/or columns | | `derive_var_` (e.g. `derive_var_trtdurd`) | Functions which add a single variable | | `derive_vars_` (e.g. `derive_vars_dt`) | Functions which add multiple variables | @@ -223,7 +223,7 @@ and, if there’s an invalid input, the function should stop immediately with an An exception is the case where a variable to be added by a function already exists in the input dataset: here only a warning should be displayed and the function should continue executing. -Inputs should be checked either using `asserthat::assert_that()` or custom assertion functions defined in [`R/assertions.R`](https://github.com/pharmaverse/admiraldev/blob/main/R/assertions.R). +Inputs should be checked either using custom assertion functions defined in [`R/assertions.R`](https://github.com/pharmaverse/admiraldev/blob/main/R/assertions.R). These custom assertion functions should either return an error in case of an invalid input or return nothing. For the most common types of input parameters like a single variable, a list of @@ -415,7 +415,6 @@ add an issue in GitHub for discussion. | `assertion`* | Asserts a certain type and gives warning, error to user | | `warning` | Provides custom warnings to user | | `what` | A function that ... | -| `is` | A function that ... | | `get` | A function that ... | **NOTE:** It is strongly encouraged that each `@keyword` and `@family` are to be identical. This eases the burden of development and maintenance for admiral functions. If you need to use multiple keywords or families, please reach out to the core development team for discussion. From 045c74beb34fddd267695646e5eb27768ee0b82e Mon Sep 17 00:00:00 2001 From: fshanlee Date: Tue, 8 Nov 2022 11:58:37 +0000 Subject: [PATCH 114/179] Added a comment to assert_character_scalar(), to explain the purpose of case_adjusted_arg and case_adjusted_values. --- R/assertions.R | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/R/assertions.R b/R/assertions.R index 4fcfc24e..593271ed 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -158,6 +158,17 @@ assert_character_scalar <- function(arg, abort(err_msg) } + # Create case_adjusted_arg and case_adjusted_values for the following purpose: + # + # 1. To simplify the comparison of arg and values; i.e. the "case_adjusted_" + # variables take into consideration whether case_sensitive = TRUE, or + # case_sensitive = FALSE. + # + # 2. To avoid overwriting the original "arg" and "values", so that subsequent + # code can refer directly to the initial function arguments: this is + # required whilst generating an error message if "arg" is not one of the + # user-specified valid values. + if (case_sensitive) { case_adjusted_arg <- arg if (!is.null(values)) { From f2a56d517e79e1291f518fff3850d288da9a2e10 Mon Sep 17 00:00:00 2001 From: fshanlee Date: Tue, 8 Nov 2022 12:03:00 +0000 Subject: [PATCH 115/179] Removed an unwanted blank space from test-assertions.R, which was detected by GitHub workflow running linter. --- tests/testthat/test-assertions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 9e28ea6c..b0718f22 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -195,7 +195,7 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } - out <- expect_invisible(example_fun(character = "TEST") ) + out <- expect_invisible(example_fun(character = "TEST")) expect_equal(out, "test") check_unit <- function(duration_unit) { From 355a45e9cae2adab388146ae77c5b634e8be2322 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Tue, 8 Nov 2022 15:27:15 +0100 Subject: [PATCH 116/179] 162_fix_admiraldev: allow NULL for quosures in get_source_vars() --- R/get.R | 2 +- staged_dependencies.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/get.R b/R/get.R index eb288bdb..53a78a6b 100644 --- a/R/get.R +++ b/R/get.R @@ -84,7 +84,7 @@ get_duplicates <- function(x) { #' @return A list of quosures #' @export get_source_vars <- function(quosures) { - assert_varval_list(quosures) + assert_varval_list(quosures, optional = TRUE) quo_c(quosures)[lapply(quo_c(quosures), quo_is_symbol) == TRUE] } diff --git a/staged_dependencies.yaml b/staged_dependencies.yaml index 1756e9f8..333435b8 100644 --- a/staged_dependencies.yaml +++ b/staged_dependencies.yaml @@ -1,6 +1,6 @@ --- current_repo: - repo: pharmaverse/admiraltemplate + repo: pharmaverse/admiraldev host: https://github.com upstream_repos: - repo: pharmaverse/admiral.test From dbbd2f0f4a7138c7adaaf1419cba13b4dde65187 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Tue, 8 Nov 2022 15:33:15 +0100 Subject: [PATCH 117/179] 162_fix_admiraldev: add test --- tests/testthat/test-get.R | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-get.R b/tests/testthat/test-get.R index 9d1fd975..1ec91228 100644 --- a/tests/testthat/test-get.R +++ b/tests/testthat/test-get.R @@ -32,8 +32,8 @@ test_that("get_constant_vars Test 2: with ignore_vars", { }) # get_duplicates ---- -## Test 1: x atomic vector ---- -test_that("get_duplicates Test 1: x atomic vector", { +## Test 3: x atomic vector ---- +test_that("get_duplicates Test 3: x atomic vector", { x <- c("a", "a", "b", "c", "d", "d", 1, 1, 4) expect_equal( @@ -43,8 +43,8 @@ test_that("get_duplicates Test 1: x atomic vector", { }) # get_source_vars ---- -## Test 1: x is a list of quosures ---- -test_that("get_source_vars Test 1: x is a list of quosures", { +## Test 4: x is a list of quosures ---- +test_that("get_source_vars Test 4: x is a list of quosures", { x <- vars(DTHDOM = "AE", DTHSEQ = AESEQ) expect_equal( @@ -52,3 +52,11 @@ test_that("get_source_vars Test 1: x is a list of quosures", { x[2] ) }) + +## Test 5: quosures is NULL ---- +test_that("get_source_vars Test 5: quosures is NULL", { + expect_equal( + get_source_vars(NULL), + quo_c(NULL) + ) +}) From 45c9334dd1c5312dd99cd71e54763b33df72717a Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 8 Nov 2022 14:37:57 +0000 Subject: [PATCH 118/179] Replaced library () calls with packagename::function(). --- tests/testthat/test-dev_utilities.R | 6 ++---- tests/testthat/test-tmp_vars.R | 4 +--- tests/testthat/test-warnings.R | 12 +++++------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 470bedf8..7afa4977 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -25,9 +25,8 @@ test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct f }) test_that("filter_if Test 1 : Input is returned as is if filter is NULL", { - library(tibble) - input <- tribble( + input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, "P01", "HEIGHT", 189.2 @@ -43,9 +42,8 @@ test_that("filter_if Test 1 : Input is returned as is if filter is NULL", { }) test_that("filter_if Test 2 : Input is filtered if filter is not NULL", { - library(tibble) - input <- tribble( + input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, "P01", "HEIGHT", 189.2 diff --git a/tests/testthat/test-tmp_vars.R b/tests/testthat/test-tmp_vars.R index 35ba87c5..321ad6c1 100644 --- a/tests/testthat/test-tmp_vars.R +++ b/tests/testthat/test-tmp_vars.R @@ -1,6 +1,4 @@ -library(admiral.test) -data(admiral_dm) -dm <- select(admiral_dm, USUBJID) +dm <- select(admiral.test::admiral_dm, USUBJID) # get_new_tmp_var ---- ## Test 1: creating temporary variables works ---- diff --git a/tests/testthat/test-warnings.R b/tests/testthat/test-warnings.R index 050cf6b6..d443b0e2 100644 --- a/tests/testthat/test-warnings.R +++ b/tests/testthat/test-warnings.R @@ -1,24 +1,22 @@ -library(admiral.test) - # warn_if_vars_exist ---- ## Test 1: warning if a variable already exists in the input dataset ---- test_that("warn_if_vars_exist Test 1: warning if a variable already exists in the input dataset", { - data(admiral_dm) + dm <- admiral.test::admiral_dm expect_warning( - warn_if_vars_exist(admiral_dm, "AGE"), + warn_if_vars_exist(dm, "AGE"), "Variable `AGE` already exists in the dataset" ) expect_warning( - warn_if_vars_exist(admiral_dm, c("AGE", "AGEU", "ARM")), + warn_if_vars_exist(dm, c("AGE", "AGEU", "ARM")), "Variables `AGE`, `AGEU` and `ARM` already exist in the dataset" ) expect_warning( - warn_if_vars_exist(admiral_dm, c("AAGE", "AGEU", "ARM")), + warn_if_vars_exist(dm, c("AAGE", "AGEU", "ARM")), "Variables `AGEU` and `ARM` already exist in the dataset" ) expect_warning( - warn_if_vars_exist(admiral_dm, "AAGE"), + warn_if_vars_exist(dm, "AAGE"), NA ) }) From 6b0c88cec591bf325efbc05a8668607c8fb5d731 Mon Sep 17 00:00:00 2001 From: fshanlee Date: Tue, 8 Nov 2022 16:23:55 +0000 Subject: [PATCH 119/179] Amended the comment regarding case_adjusted_arg and case_adjusted_values in assert_character_scalar(): put back ticks around references to code, to try to prevent linter from interpreting this part of the comment as "commented-out code". --- R/assertions.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 593271ed..6bb8d2cc 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -161,8 +161,8 @@ assert_character_scalar <- function(arg, # Create case_adjusted_arg and case_adjusted_values for the following purpose: # # 1. To simplify the comparison of arg and values; i.e. the "case_adjusted_" - # variables take into consideration whether case_sensitive = TRUE, or - # case_sensitive = FALSE. + # variables take into consideration whether `case_sensitive = TRUE`, or + # `case_sensitive = FALSE`. # # 2. To avoid overwriting the original "arg" and "values", so that subsequent # code can refer directly to the initial function arguments: this is From 5b7238f792557087d1240c8c14ee0d0596606a03 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 8 Nov 2022 16:37:50 +0000 Subject: [PATCH 120/179] styler changes. --- tests/testthat/test-dev_utilities.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 7afa4977..6edbc315 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -25,7 +25,6 @@ test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct f }) test_that("filter_if Test 1 : Input is returned as is if filter is NULL", { - input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, @@ -42,7 +41,6 @@ test_that("filter_if Test 1 : Input is returned as is if filter is NULL", { }) test_that("filter_if Test 2 : Input is filtered if filter is not NULL", { - input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, From fc5f6c65acc44735b9d312aa6114860085672bc5 Mon Sep 17 00:00:00 2001 From: fshanlee Date: Wed, 9 Nov 2022 11:52:44 +0000 Subject: [PATCH 121/179] Amended assert_character_scalar() and its corresponding testthat scripts, so that `arg` is always returned with its case unchanged. --- R/assertions.R | 2 +- tests/testthat/test-assertions.R | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 6bb8d2cc..fa71d1fd 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -191,7 +191,7 @@ assert_character_scalar <- function(arg, abort(err_msg) } - invisible(case_adjusted_arg) + invisible(arg) } #' Is an Argument a Character Vector? diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index b0718f22..1f361d20 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -190,16 +190,18 @@ test_that("Test 15 : `assert_character_scalar` does not throw an error if }) test_that("Test 16 : `assert_character_scalar` does not throw an error if - case_sensitive is FALSE, and argument is returned in lower case", { + case_sensitive is FALSE, and argument is returned with its case + unchanged.", + { example_fun <- function(character) { assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } out <- expect_invisible(example_fun(character = "TEST")) - expect_equal(out, "test") + expect_equal(out, "TEST") check_unit <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), case_sensitive <- FALSE @@ -210,10 +212,10 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if expect_equal(out, "months") out <- expect_invisible(check_unit("MONTHS")) - expect_equal(out, "months") + expect_equal(out, "MONTHS") check_unit2 <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), case_sensitive <- FALSE @@ -224,7 +226,7 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if expect_equal(out, "months") out <- expect_invisible(check_unit2("MONTHS")) - expect_equal(out, "months") + expect_equal(out, "MONTHS") }) test_that("Test 17 : `assert_character_scalar` throws an error if @@ -238,7 +240,7 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) check_unit <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), case_sensitive <- FALSE @@ -256,7 +258,7 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) check_unit2 <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), case_sensitive <- FALSE From f3762387d7ef66ff41c7e343b94cda17e1920e45 Mon Sep 17 00:00:00 2001 From: fshanlee Date: Wed, 9 Nov 2022 16:23:57 +0000 Subject: [PATCH 122/179] Revert "Amended assert_character_scalar() and its corresponding testthat scripts, so that `arg` is always returned with its case unchanged." This reverts commit fc5f6c65acc44735b9d312aa6114860085672bc5. --- R/assertions.R | 2 +- tests/testthat/test-assertions.R | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index fa71d1fd..6bb8d2cc 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -191,7 +191,7 @@ assert_character_scalar <- function(arg, abort(err_msg) } - invisible(arg) + invisible(case_adjusted_arg) } #' Is an Argument a Character Vector? diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 1f361d20..b0718f22 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -190,18 +190,16 @@ test_that("Test 15 : `assert_character_scalar` does not throw an error if }) test_that("Test 16 : `assert_character_scalar` does not throw an error if - case_sensitive is FALSE, and argument is returned with its case - unchanged.", - { + case_sensitive is FALSE, and argument is returned in lower case", { example_fun <- function(character) { assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } out <- expect_invisible(example_fun(character = "TEST")) - expect_equal(out, "TEST") + expect_equal(out, "test") check_unit <- function(duration_unit) { - assert_character_scalar( + duration_unit <- assert_character_scalar( duration_unit, values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), case_sensitive <- FALSE @@ -212,10 +210,10 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if expect_equal(out, "months") out <- expect_invisible(check_unit("MONTHS")) - expect_equal(out, "MONTHS") + expect_equal(out, "months") check_unit2 <- function(duration_unit) { - assert_character_scalar( + duration_unit <- assert_character_scalar( duration_unit, values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), case_sensitive <- FALSE @@ -226,7 +224,7 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if expect_equal(out, "months") out <- expect_invisible(check_unit2("MONTHS")) - expect_equal(out, "MONTHS") + expect_equal(out, "months") }) test_that("Test 17 : `assert_character_scalar` throws an error if @@ -240,7 +238,7 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) check_unit <- function(duration_unit) { - assert_character_scalar( + duration_unit <- assert_character_scalar( duration_unit, values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), case_sensitive <- FALSE @@ -258,7 +256,7 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) check_unit2 <- function(duration_unit) { - assert_character_scalar( + duration_unit <- assert_character_scalar( duration_unit, values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), case_sensitive <- FALSE From 795a4e57f8fd1c84e25758fcc218f6a0524592ea Mon Sep 17 00:00:00 2001 From: fshanlee Date: Wed, 9 Nov 2022 16:57:46 +0000 Subject: [PATCH 123/179] Removed some redundant code in the testthat script for assert_character_scalar(). --- tests/testthat/test-assertions.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index b0718f22..8a13334d 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -199,7 +199,7 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if expect_equal(out, "test") check_unit <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), case_sensitive <- FALSE @@ -213,7 +213,7 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if expect_equal(out, "months") check_unit2 <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), case_sensitive <- FALSE @@ -238,7 +238,7 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) check_unit <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("years", "months", "weeks", "days", "hours", "minutes", "seconds"), case_sensitive <- FALSE @@ -256,7 +256,7 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) check_unit2 <- function(duration_unit) { - duration_unit <- assert_character_scalar( + assert_character_scalar( duration_unit, values = c("YEARS", "MONTHS", "WEEKS", "DAYS", "HOURS", "MINUTES", "SECONDS"), case_sensitive <- FALSE From 4ab8eb3bdcc46520df0db7abe40bf7cf8513982a Mon Sep 17 00:00:00 2001 From: fshanlee Date: Wed, 9 Nov 2022 17:55:06 +0000 Subject: [PATCH 124/179] Updated the description of `values` in the roxygen header of assert_character_scalar(). --- R/assertions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/assertions.R b/R/assertions.R index 6bb8d2cc..dc3ce1ed 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -87,7 +87,7 @@ assert_data_frame <- function(arg, #' #' @param arg A function argument to be checked #' @param values A `character` vector of valid values for `arg`. -#' Values should be a lower case vector if case_sensitive = FALSE is used. +#' Values is converted to a lower case vector if case_sensitive = FALSE is used. #' @param case_sensitive Should the argument be handled case-sensitive? #' If set to `FALSE`, the argument is converted to lower case for checking the #' permitted values and returning the argument. From c5fa76255c7603d687afc9b909490ef53b169a89 Mon Sep 17 00:00:00 2001 From: fshanlee Date: Wed, 9 Nov 2022 17:58:52 +0000 Subject: [PATCH 125/179] assert_character_scalar.Rd has been updated after runnnig devtools::document(). --- man/assert_character_scalar.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/assert_character_scalar.Rd b/man/assert_character_scalar.Rd index 73a77c7d..13584059 100644 --- a/man/assert_character_scalar.Rd +++ b/man/assert_character_scalar.Rd @@ -15,7 +15,7 @@ assert_character_scalar( \item{arg}{A function argument to be checked} \item{values}{A \code{character} vector of valid values for \code{arg}. -Values should be a lower case vector if case_sensitive = FALSE is used.} +Values is converted to a lower case vector if case_sensitive = FALSE is used.} \item{case_sensitive}{Should the argument be handled case-sensitive? If set to \code{FALSE}, the argument is converted to lower case for checking the From acfd913970b4833400e3d3634b8416ab8a8bcc8f Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Thu, 10 Nov 2022 00:34:54 +0000 Subject: [PATCH 126/179] update: restore some is.R functions, created some corresponding tests --- NAMESPACE | 3 ++ R/assertions.R | 4 +- R/dev_utilities.R | 36 -------------- R/is.R | 77 +++++++++++++++++++++++++++++ R/warnings.R | 2 +- man/arg_name.Rd | 1 - man/as_name.Rd | 1 - man/convert_dtm_to_dtc.Rd | 1 - man/extract_vars.Rd | 1 - man/filter_if.Rd | 1 - man/grapes-notin-grapes.Rd | 1 - man/grapes-or-grapes.Rd | 1 - man/is_auto.Rd | 29 +++++++++++ man/is_named.Rd | 25 ++++++++++ man/is_order_vars.Rd | 21 +++----- man/is_valid_dtc.Rd | 25 ++++++++++ man/negate_vars.Rd | 1 - man/valid_time_units.Rd | 1 - man/vars2chr.Rd | 1 - man/warn_if_invalid_dtc.Rd | 2 +- tests/testthat/test-dev_utilities.R | 11 ----- tests/testthat/test-is.R | 26 ++++++++++ 22 files changed, 196 insertions(+), 75 deletions(-) create mode 100644 R/is.R create mode 100644 man/is_auto.Rd create mode 100644 man/is_named.Rd create mode 100644 man/is_valid_dtc.Rd create mode 100644 tests/testthat/test-is.R diff --git a/NAMESPACE b/NAMESPACE index c8c3277f..9afd1810 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -45,7 +45,10 @@ export(get_duplicates) export(get_new_tmp_var) export(get_source_vars) export(inner_join) +export(is_auto) +export(is_named) export(is_order_vars) +export(is_valid_dtc) export(left_join) export(negate_vars) export(quo_c) diff --git a/R/assertions.R b/R/assertions.R index 6d2e1f46..c51dcbab 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -572,7 +572,7 @@ assert_order_vars <- function(arg, optional = FALSE) { abort(default_err_msg) } - if (!isTRUE(is_order_vars(arg)) == TRUE) { + if (isFALSE(is_order_vars(arg))) { abort(default_err_msg) } @@ -1149,7 +1149,7 @@ assert_varval_list <- function(arg, # nolint valid_vals <- "a symbol, character scalar, numeric scalar, or `NA`" } - if (!accept_var & (!is_quosures(arg) || !(!is.null(names(arg)) && all(names(arg) != "")))) { + if (!accept_var & (!is_quosures(arg) || !is_named(arg))) { err_msg <- sprintf( paste0( "`%s` must be a named list of quosures where each element is ", diff --git a/R/dev_utilities.R b/R/dev_utilities.R index 72d81cfa..f9e1e0fc 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -232,39 +232,3 @@ filter_if <- function(dataset, filter) { filter(dataset, !!filter) } } - -#' Is order vars? -#' -#' Check if inputs are created using `vars()` or calls involving `desc()` -#' @param arg A Date: Thu, 10 Nov 2022 14:55:18 +0000 Subject: [PATCH 127/179] Update NAMESPACE and description for is_order_vars function --- NAMESPACE | 2 -- R/admiraldev-package.R | 4 ++-- R/is.R | 3 +-- man/is_order_vars.Rd | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index c87e9a7c..3b28f9e7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -113,7 +113,6 @@ importFrom(lubridate,duration) importFrom(lubridate,floor_date) importFrom(lubridate,hours) importFrom(lubridate,is.Date) -importFrom(lubridate,is.POSIXlt) importFrom(lubridate,is.instant) importFrom(lubridate,minutes) importFrom(lubridate,time_length) @@ -133,7 +132,6 @@ importFrom(purrr,map_if) importFrom(purrr,map_lgl) importFrom(purrr,modify_at) importFrom(purrr,modify_if) -importFrom(purrr,possibly) importFrom(purrr,reduce) importFrom(purrr,transpose) importFrom(purrr,walk) diff --git a/R/admiraldev-package.R b/R/admiraldev-package.R index 4b3c7b51..38b31500 100644 --- a/R/admiraldev-package.R +++ b/R/admiraldev-package.R @@ -13,13 +13,13 @@ #' quo_is_missing quo_is_null quo_is_symbol quos quo_squash quo_text #' set_names sym syms type_of warn quo_set_env quo_get_env #' @importFrom utils capture.output str -#' @importFrom purrr map map2 map_chr map_lgl reduce walk keep map_if transpose possibly +#' @importFrom purrr map map2 map_chr map_lgl reduce walk keep map_if transpose #' flatten every modify_at modify_if reduce compose #' @importFrom stringr str_c str_detect str_extract str_glue str_match #' str_remove str_remove_all str_replace str_trim str_to_lower str_subset #' str_to_title str_to_upper #' @importFrom lubridate as_datetime ceiling_date date days duration floor_date is.Date is.instant -#' is.POSIXlt time_length %--% ymd ymd_hms weeks years hours minutes +#' time_length %--% ymd ymd_hms weeks years hours minutes #' @importFrom tidyr drop_na nest pivot_longer pivot_wider unnest #' @importFrom tidyselect all_of contains vars_select #' @importFrom hms as_hms diff --git a/R/is.R b/R/is.R index 330a62ec..53732e9b 100644 --- a/R/is.R +++ b/R/is.R @@ -33,8 +33,7 @@ is_auto <- function(arg) { #' Check if inputs are created using `vars()` or calls involving `desc()` #' @param arg A Date: Thu, 10 Nov 2022 16:15:33 +0000 Subject: [PATCH 128/179] updating the programming strategy to mention is_ functions --- vignettes/programming_strategy.Rmd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 315b7d7c..03e2e891 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -113,7 +113,7 @@ variables must start with `temp_` and must be removed from the output dataset. | Function name prefix | Description | |----------------------------------------------|-----------------------------------------------------------------------------------------------------| -| `assert_` / `warn_` | Functions that check other functions’ inputs | +| `assert_` / `warn_` / `is_` | Functions that check other functions’ inputs | | `derive_` | Functions that take a dataset as input and return a new dataset with additional rows and/or columns | | `derive_var_` (e.g. `derive_var_trtdurd`) | Functions which add a single variable | | `derive_vars_` (e.g. `derive_vars_dt`) | Functions which add multiple variables | @@ -420,6 +420,7 @@ add an issue in GitHub for discussion. | `assertion`* | Asserts a certain type and gives warning, error to user | | `warning` | Provides custom warnings to user | | `what` | A function that ... | +| `is` | A function that ... | | `get` | A function that ... | **NOTE:** It is strongly encouraged that each `@keyword` and `@family` are to be identical. This eases the burden of development and maintenance for admiral functions. If you need to use multiple keywords or families, please reach out to the core development team for discussion. From 07e088aa86b7c6bdc29f9dc1df75a12ee36071ad Mon Sep 17 00:00:00 2001 From: Sadchla Mascary <112789549+sadchla-codes@users.noreply.github.com> Date: Mon, 14 Nov 2022 12:06:34 -0500 Subject: [PATCH 129/179] Update vignettes/programming_strategy.Rmd Co-authored-by: Ben Straub --- vignettes/programming_strategy.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 03e2e891..1ab8a3aa 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -229,7 +229,7 @@ and, if there’s an invalid input, the function should stop immediately with an An exception is the case where a variable to be added by a function already exists in the input dataset: here only a warning should be displayed and the function should continue executing. -Inputs should be checked either using custom assertion functions defined in [`R/assertions.R`](https://github.com/pharmaverse/admiraldev/blob/main/R/assertions.R). +Inputs should be checked using custom assertion functions defined in [`R/assertions.R`](https://github.com/pharmaverse/admiraldev/blob/main/R/assertions.R). These custom assertion functions should either return an error in case of an invalid input or return nothing. For the most common types of input parameters like a single variable, a list of From 0952015b5b5ae9c36e9f43c276d253b3cac4723f Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Mon, 14 Nov 2022 17:17:11 +0000 Subject: [PATCH 130/179] Updating the typo in is.R decription --- R/is.R | 2 +- man/is_order_vars.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/is.R b/R/is.R index 53732e9b..d74799d9 100644 --- a/R/is.R +++ b/R/is.R @@ -31,7 +31,7 @@ is_auto <- function(arg) { #' Is order vars? #' #' Check if inputs are created using `vars()` or calls involving `desc()` -#' @param arg A Date: Mon, 14 Nov 2022 17:29:41 +0000 Subject: [PATCH 131/179] Ran styler::style_file on R/quo.R, tests/testthat/test-assertions.R, tests/testthat/test-quo.R --- R/quo.R | 7 ++++--- tests/testthat/test-assertions.R | 36 +++++++++++++++++++++----------- tests/testthat/test-quo.R | 2 +- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/R/quo.R b/R/quo.R index fc396138..67dfe628 100644 --- a/R/quo.R +++ b/R/quo.R @@ -33,9 +33,10 @@ quo_not_missing <- function(x) { !rlang::quo_is_missing(x) if (is.null(missing(x)) || quo_is_missing(x)) { - stop(paste0("Argument `", - deparse(substitute(x)), - "` is missing, with no default" + stop(paste0( + "Argument `", + deparse(substitute(x)), + "` is missing, with no default" )) } } diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index ae2c52c6..97fab80c 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -233,14 +233,20 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) } - expect_error(check_unit("month"), - paste0("`duration_unit` must be one of 'years', 'months', 'weeks', 'days', ", - "'hours', 'minutes' or 'seconds' but is 'month'") + expect_error( + check_unit("month"), + paste0( + "`duration_unit` must be one of 'years', 'months', 'weeks', 'days', ", + "'hours', 'minutes' or 'seconds' but is 'month'" + ) ) - expect_error(check_unit("MONTH"), - paste0("`duration_unit` must be one of 'years', 'months', 'weeks', 'days', ", - "'hours', 'minutes' or 'seconds' but is 'MONTH'") + expect_error( + check_unit("MONTH"), + paste0( + "`duration_unit` must be one of 'years', 'months', 'weeks', 'days', ", + "'hours', 'minutes' or 'seconds' but is 'MONTH'" + ) ) check_unit2 <- function(duration_unit) { @@ -251,14 +257,20 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) } - expect_error(check_unit2("month"), - paste0("`duration_unit` must be one of 'YEARS', 'MONTHS', 'WEEKS', 'DAYS', ", - "'HOURS', 'MINUTES' or 'SECONDS' but is 'month'") + expect_error( + check_unit2("month"), + paste0( + "`duration_unit` must be one of 'YEARS', 'MONTHS', 'WEEKS', 'DAYS', ", + "'HOURS', 'MINUTES' or 'SECONDS' but is 'month'" + ) ) - expect_error(check_unit2("MONTH"), - paste0("`duration_unit` must be one of 'YEARS', 'MONTHS', 'WEEKS', 'DAYS', ", - "'HOURS', 'MINUTES' or 'SECONDS' but is 'MONTH'") + expect_error( + check_unit2("MONTH"), + paste0( + "`duration_unit` must be one of 'YEARS', 'MONTHS', 'WEEKS', 'DAYS', ", + "'HOURS', 'MINUTES' or 'SECONDS' but is 'MONTH'" + ) ) }) diff --git a/tests/testthat/test-quo.R b/tests/testthat/test-quo.R index 8fff2a30..78c4b7c2 100644 --- a/tests/testthat/test-quo.R +++ b/tests/testthat/test-quo.R @@ -28,7 +28,7 @@ test_that("quo_c Test 2: `quo_c` returns error if non-quosures are input", { test_that("quo_not_missing Test 3: `quo_not_missing` returns TRUE if no missing argument", { test_fun <- function(x) { x <- enquo(x) - !isTRUE(quo_not_missing(x)) + !isTRUE(quo_not_missing(x)) } expect_true(test_fun(my_variable)) }) From 17e63eb5e0c15585ca2ca8081a555cb931b467a0 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 18:50:08 +0000 Subject: [PATCH 132/179] #172, #158, #165 - general documentation update --- .github/pull_request_template.md | 15 ++++++++------- DESCRIPTION | 4 ++++ man/admiraldev-package.Rd | 4 ++++ vignettes/programming_strategy.Rmd | 4 ++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 87294b8f..7005314c 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,18 +1,19 @@ -Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/devel/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. + +Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/main/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. Please check off each taskbox as an acknowledgment that you completed the task or check off that it is not relevant to your Pull Request. This checklist is part of the Github Action workflows and the Pull Request will not be merged into the `devel` branch until you have checked off each task. - [ ] Place Closes # into the beginning of your Pull Request Title (Use Edit button in top-right if you need to update) - [ ] Code is formatted according to the [tidyverse style guide](https://style.tidyverse.org/). Run `styler::style_file()` to style R and Rmd files -- [ ] Updated relevant unit tests or have written new unit tests - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html) -- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#deprecation)? -- [ ] Update to all relevant roxygen headers and examples. +- [ ] Updated relevant unit tests or have written new unit tests, which should include edge cases, e.g. empty datasets, errors, etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/main/articles/unit_test_guidance.html#writing-unit-tests-in-admiral-) +- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/main/articles/programming_strategy.html#deprecation-1)? +- [ ] Update to all relevant roxygen headers and examples, including keywords and families. Refer to the [categorization of functions](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#categorization-of-functions) to tag appropriate keyword/family. - [ ] Run `devtools::document()` so all `.Rd` files in the `man` folder and the `NAMESPACE` file in the project root are updated appropriately - [ ] Address any updates needed for vignettes and/or templates - [ ] Update `NEWS.md` if the changes pertain to a user-facing function (i.e. it has an `@export` tag) or documentation aimed at users (rather than developers) -- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiraldev/devel/reference/index.html)" page. +- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiral/reference/index.html)" page. - [ ] Address or fix all lintr warnings and errors - `lintr::lint_package()` - [ ] Run `R CMD check` locally and address all errors and warnings - `devtools::check()` -- [ ] Link the issue so that it closes after successful merging. -- [ ] Address all merge conflicts and resolve appropriately. +- [ ] Link the issue in the Development Section on the right hand side. +- [ ] Address all merge conflicts and resolve appropriately - [ ] Pat yourself on the back for a job well done! Much love to your accomplishment! diff --git a/DESCRIPTION b/DESCRIPTION index 39146e22..f6a78342 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -11,6 +11,10 @@ Authors@R: c( person("Syed", "Mubasheer", role = "aut"), person("Ross", "Farrugia", role = "aut"), person("Ondrej", "Slama", role = "ctb"), + person("Saddchla", "Mascary", role = "ctb"), + person("Zelos", "Zhu", role = "ctb"), + person("Jeffrey", "Dickinson", role = "ctb"), + person("Ania", "Golab", role = "ctb"), person("F. Hoffmann-La Roche AG", role = c("cph", "fnd")), person("GlaxoSmithKline LLC", role = c("cph", "fnd")) ) diff --git a/man/admiraldev-package.Rd b/man/admiraldev-package.Rd index 921f9eb2..a8c12f77 100644 --- a/man/admiraldev-package.Rd +++ b/man/admiraldev-package.Rd @@ -34,6 +34,10 @@ Authors: Other contributors: \itemize{ \item Ondrej Slama [contributor] + \item Saddchla Mascary [contributor] + \item Zelos Zhu [contributor] + \item Jeffrey Dickinson [contributor] + \item Ania Golab [contributor] \item F. Hoffmann-La Roche AG [copyright holder, funder] \item GlaxoSmithKline LLC [copyright holder, funder] } diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 1ab8a3aa..5f3e8537 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -705,8 +705,8 @@ If meaningful, comments can cover multiple variables within a piece of code # R and package versions for development -* The choice of R Version is not set in stone. However, a common development environment is important to establish when working across multiple companies and multiple developers. We only use the three latest R versions and the closest date of snapshots of R packages available when that R version came out. This need for a common development environment also carries over for our choice of package versions. -* GitHub allows us through the Actions/Workflows to test `{admiral}` under several versions of R as well as several versions of dependent R packages needed for `{admiral}`. Currently we test `{admiral}` against 4.0 with a CRAN package snapshot from 2021-03-31 and the latest R version with the latest snapshots of packages. You can view this workflow on our [GitHub Repository](https://github.com/pharmaverse/admiralci/blob/main/.github/workflows/r-cmd-check.yml) +* The choice of R Version is not set in stone. However, a common development environment is important to establish when working across multiple companies and multiple developers. We currently work in the earliest of the three latest R Versions. This need for a common development environment also carries over for our choice of package versions. +* GitHub allows us through the Actions/Workflows to test `{admiral}` under several versions of R as well as several versions of dependent R packages needed for `{admiral}`. Currently we test `{admiral}` against the three latest R Versions and the closest snapshots of packages to those R versions. You can view this workflow and others on our [admiralci GitHub Repository](https://github.com/pharmaverse/admiralci). * This common development allows us to easily re-create bugs and provide solutions to each other issues that developers will encounter. * Reviewers of Pull Requests when running code will know that their environment is identical to the initiator of the Pull Request. This ensures faster review times and higher quality Pull Request reviews. * We achieve this common development environment by using a **lockfile** created from the [`renv`](https://rstudio.github.io/renv/) package. New developers will encounter a suggested `renv::restore()` in the console to revert or move forward your R version and package versions. From 83cd697a0b5da3470ba745d901f595c3cf533aac Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 19:05:34 +0000 Subject: [PATCH 133/179] #158 - Update typo in name --- DESCRIPTION | 2 +- man/admiraldev-package.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f6a78342..e7ea75af 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -11,7 +11,7 @@ Authors@R: c( person("Syed", "Mubasheer", role = "aut"), person("Ross", "Farrugia", role = "aut"), person("Ondrej", "Slama", role = "ctb"), - person("Saddchla", "Mascary", role = "ctb"), + person("Sadchla", "Mascary", role = "ctb"), person("Zelos", "Zhu", role = "ctb"), person("Jeffrey", "Dickinson", role = "ctb"), person("Ania", "Golab", role = "ctb"), diff --git a/man/admiraldev-package.Rd b/man/admiraldev-package.Rd index a8c12f77..4ba2affd 100644 --- a/man/admiraldev-package.Rd +++ b/man/admiraldev-package.Rd @@ -34,7 +34,7 @@ Authors: Other contributors: \itemize{ \item Ondrej Slama [contributor] - \item Saddchla Mascary [contributor] + \item Sadchla Mascary [contributor] \item Zelos Zhu [contributor] \item Jeffrey Dickinson [contributor] \item Ania Golab [contributor] From 9125bb32adf6455ad7ada63b66c4e549ce111eab Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 19:29:38 +0000 Subject: [PATCH 134/179] Use admiraldev/devel links --- .github/pull_request_template.md | 7 +++---- DESCRIPTION | 8 ++++---- man/admiraldev-package.Rd | 8 ++++---- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7005314c..dc24c98a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,17 +1,16 @@ - Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/main/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. Please check off each taskbox as an acknowledgment that you completed the task or check off that it is not relevant to your Pull Request. This checklist is part of the Github Action workflows and the Pull Request will not be merged into the `devel` branch until you have checked off each task. - [ ] Place Closes # into the beginning of your Pull Request Title (Use Edit button in top-right if you need to update) - [ ] Code is formatted according to the [tidyverse style guide](https://style.tidyverse.org/). Run `styler::style_file()` to style R and Rmd files -- [ ] Updated relevant unit tests or have written new unit tests, which should include edge cases, e.g. empty datasets, errors, etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/main/articles/unit_test_guidance.html#writing-unit-tests-in-admiral-) -- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/main/articles/programming_strategy.html#deprecation-1)? +- [ ] Updated relevant unit tests or have written new unit tests, which should include edge cases, e.g. empty datasets, errors, etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html#writing-unit-tests-in-admiral-) +- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#deprecation-1)? - [ ] Update to all relevant roxygen headers and examples, including keywords and families. Refer to the [categorization of functions](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#categorization-of-functions) to tag appropriate keyword/family. - [ ] Run `devtools::document()` so all `.Rd` files in the `man` folder and the `NAMESPACE` file in the project root are updated appropriately - [ ] Address any updates needed for vignettes and/or templates - [ ] Update `NEWS.md` if the changes pertain to a user-facing function (i.e. it has an `@export` tag) or documentation aimed at users (rather than developers) -- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiral/reference/index.html)" page. +- [ ] Build admiral site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the "[Reference](https://pharmaverse.github.io/admiraldev/devel/reference/index.html)" page. - [ ] Address or fix all lintr warnings and errors - `lintr::lint_package()` - [ ] Run `R CMD check` locally and address all errors and warnings - `devtools::check()` - [ ] Link the issue in the Development Section on the right hand side. diff --git a/DESCRIPTION b/DESCRIPTION index e7ea75af..ac2473e1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -10,11 +10,11 @@ Authors@R: c( person("Pooja", "Kumari", role = "aut"), person("Syed", "Mubasheer", role = "aut"), person("Ross", "Farrugia", role = "aut"), + person("Sadchla", "Mascary", role = "aut"), + person("Zelos", "Zhu", role = "aut"), + person("Jeffrey", "Dickinson", role = "aut"), + person("Ania", "Golab", role = "aut"), person("Ondrej", "Slama", role = "ctb"), - person("Sadchla", "Mascary", role = "ctb"), - person("Zelos", "Zhu", role = "ctb"), - person("Jeffrey", "Dickinson", role = "ctb"), - person("Ania", "Golab", role = "ctb"), person("F. Hoffmann-La Roche AG", role = c("cph", "fnd")), person("GlaxoSmithKline LLC", role = c("cph", "fnd")) ) diff --git a/man/admiraldev-package.Rd b/man/admiraldev-package.Rd index 4ba2affd..ce0bf3bc 100644 --- a/man/admiraldev-package.Rd +++ b/man/admiraldev-package.Rd @@ -29,15 +29,15 @@ Authors: \item Pooja Kumari \item Syed Mubasheer \item Ross Farrugia + \item Sadchla Mascary + \item Zelos Zhu + \item Jeffrey Dickinson + \item Ania Golab } Other contributors: \itemize{ \item Ondrej Slama [contributor] - \item Sadchla Mascary [contributor] - \item Zelos Zhu [contributor] - \item Jeffrey Dickinson [contributor] - \item Ania Golab [contributor] \item F. Hoffmann-La Roche AG [copyright holder, funder] \item GlaxoSmithKline LLC [copyright holder, funder] } From fc8176f4baef9dc10d1820073a8f4e01e1bc2f9e Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 19:35:02 +0000 Subject: [PATCH 135/179] Forgot one of the admiraldev/devel links --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dc24c98a..d157547a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/main/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. +Thank you for your Pull Request! We have developed this task checklist from the [Development Process Guide](https://pharmaverse.github.io/admiraldev/devel/articles/development_process.html) to help with the final steps of the process. Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the admiral codebase remains robust and consistent. Please check off each taskbox as an acknowledgment that you completed the task or check off that it is not relevant to your Pull Request. This checklist is part of the Github Action workflows and the Pull Request will not be merged into the `devel` branch until you have checked off each task. From a153a3a3d73ef358924774512f628bbe8616f725 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 19:43:01 +0000 Subject: [PATCH 136/179] Typo in deprecation url --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d157547a..6f5d1239 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -5,7 +5,7 @@ Please check off each taskbox as an acknowledgment that you completed the task o - [ ] Place Closes # into the beginning of your Pull Request Title (Use Edit button in top-right if you need to update) - [ ] Code is formatted according to the [tidyverse style guide](https://style.tidyverse.org/). Run `styler::style_file()` to style R and Rmd files - [ ] Updated relevant unit tests or have written new unit tests, which should include edge cases, e.g. empty datasets, errors, etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html#writing-unit-tests-in-admiral-) -- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#deprecation-1)? +- [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#deprecation)? - [ ] Update to all relevant roxygen headers and examples, including keywords and families. Refer to the [categorization of functions](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#categorization-of-functions) to tag appropriate keyword/family. - [ ] Run `devtools::document()` so all `.Rd` files in the `man` folder and the `NAMESPACE` file in the project root are updated appropriately - [ ] Address any updates needed for vignettes and/or templates From b1a9f9ba2bdfd1c1ae8272cb5bf2a91ba6ef64b9 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 19:59:32 +0000 Subject: [PATCH 137/179] #83 First draft of what we reexport and why --- vignettes/programming_strategy.Rmd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 1ab8a3aa..b552ca90 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -460,6 +460,13 @@ Functions from other packages have to be explicitly imported by using the `@impo To import the `if_else()` and `mutate()` function from `dplyr` the following line would have to be included in that file: `#' @importFrom dplyr if_else mutate`. +Some of these functions become critically important for admiral, such as `vars()` from `dplyr`, and should be included as a export. This may also include functions that are frequently called in roxygen examples. To export these functions, the following R code should be included in the `R/reexports.R` file using the format: + +``` +#' @export +pkg_name::fun +``` + # Metadata From 7ec2a7880951407200c4bb13ee5604757297dd40 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 20:05:37 +0000 Subject: [PATCH 138/179] Identify better URL for edge case of unit tests --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6f5d1239..5daf75a4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,7 +4,7 @@ Please check off each taskbox as an acknowledgment that you completed the task o - [ ] Place Closes # into the beginning of your Pull Request Title (Use Edit button in top-right if you need to update) - [ ] Code is formatted according to the [tidyverse style guide](https://style.tidyverse.org/). Run `styler::style_file()` to style R and Rmd files -- [ ] Updated relevant unit tests or have written new unit tests, which should include edge cases, e.g. empty datasets, errors, etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html#writing-unit-tests-in-admiral-) +- [ ] Updated relevant unit tests or have written new unit tests, which should include edge cases, e.g. empty datasets, errors, etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html#tests-should-be-robust-to-cover-realistic-data-scenarios) - [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#deprecation)? - [ ] Update to all relevant roxygen headers and examples, including keywords and families. Refer to the [categorization of functions](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#categorization-of-functions) to tag appropriate keyword/family. - [ ] Run `devtools::document()` so all `.Rd` files in the `man` folder and the `NAMESPACE` file in the project root are updated appropriately From 3f6732b530037382977884d5a8a6c0bac7ad9134 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 20:28:31 +0000 Subject: [PATCH 139/179] Better flow/wording for reason why we reexport --- vignettes/programming_strategy.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index b552ca90..0e2f124a 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -460,7 +460,7 @@ Functions from other packages have to be explicitly imported by using the `@impo To import the `if_else()` and `mutate()` function from `dplyr` the following line would have to be included in that file: `#' @importFrom dplyr if_else mutate`. -Some of these functions become critically important for admiral, such as `vars()` from `dplyr`, and should be included as a export. This may also include functions that are frequently called in roxygen examples. To export these functions, the following R code should be included in the `R/reexports.R` file using the format: +Some of these functions become critically important while using admiral, such as `vars()` from `dplyr`, and should be included as a export. This may also include functions that are frequently called in roxygen examples. To export these functions, the following R code should be included in the `R/reexports.R` file using the format: ``` #' @export From 908eccb1f3d63271cc240df04fbac59144065cfd Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 15:44:21 -0500 Subject: [PATCH 140/179] Accept syntax suggestion Co-authored-by: Ben Straub --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5daf75a4..213e0063 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,7 +4,7 @@ Please check off each taskbox as an acknowledgment that you completed the task o - [ ] Place Closes # into the beginning of your Pull Request Title (Use Edit button in top-right if you need to update) - [ ] Code is formatted according to the [tidyverse style guide](https://style.tidyverse.org/). Run `styler::style_file()` to style R and Rmd files -- [ ] Updated relevant unit tests or have written new unit tests, which should include edge cases, e.g. empty datasets, errors, etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html#tests-should-be-robust-to-cover-realistic-data-scenarios) +- [ ] Updated relevant unit tests or have written new unit tests, which should consider realistic data scenarios and edge cases, e.g. empty datasets, errors, boundary cases etc. - See [Unit Test Guide](https://pharmaverse.github.io/admiraldev/devel/articles/unit_test_guidance.html#tests-should-be-robust-to-cover-realistic-data-scenarios) - [ ] If you removed/replaced any function and/or function parameters, did you fully follow the [deprecation guidance](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#deprecation)? - [ ] Update to all relevant roxygen headers and examples, including keywords and families. Refer to the [categorization of functions](https://pharmaverse.github.io/admiraldev/devel/articles/programming_strategy.html#categorization-of-functions) to tag appropriate keyword/family. - [ ] Run `devtools::document()` so all `.Rd` files in the `man` folder and the `NAMESPACE` file in the project root are updated appropriately From e648b2d4f84f1a317013ceb2257ce36481dab50f Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 21:38:36 +0000 Subject: [PATCH 141/179] #141 - Rough draft of example --- R/expect_dfs_equal.R | 35 ++++++++++++++++++++++++++++++++++- man/expect_dfs_equal.Rd | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/R/expect_dfs_equal.R b/R/expect_dfs_equal.R index 191f2407..2ae67dc1 100644 --- a/R/expect_dfs_equal.R +++ b/R/expect_dfs_equal.R @@ -1,6 +1,7 @@ #' Expectation: Are Two Datasets Equal? #' -#' Uses [diffdf::diffdf()] to compares 2 datasets for any differences +#' Uses [diffdf::diffdf()] to compares 2 datasets for any differences. This function can be +#' thought of as an R-equivalent of SAS' proc compare. #' #' @param base Input dataset #' @param compare Comparison dataset @@ -15,6 +16,38 @@ #' @keywords test_helper #' @family test_helper #' +#' @examples +#' library(dplyr) +#' library(tibble) +#' +#' tbl1 <- tribble( +#' ~USUBJID, ~AGE, ~SEX, +#' "1001", 18, "M", +#' "1002", 19, "F", +#' "1003", 20, "M", +#' "1004", 18, "F" +#' ) +#' +#' tbl2 <- tribble( +#' ~USUBJID, ~AGE, ~SEX, +#' "1001", 18, "M", +#' "1002", 18.9, "F", +#' "1003", 20, NA +#' ) +#' +#' expect_dfs_equal(tbl1, tbl2, keys = "USUBJID") +#' +#' tlb3 <- tribble( +#' ~USUBJID, ~AGE, ~SEX, +#' "1004", 18, "F", +#' "1003", 20, "M", +#' "1002", 19, "F", +#' "1001", 18, "M", +#' ) +#' +#' # Note the sorting order of the keys is not required +#' expect_dfs_equal(tbl1, tlb3, keys = "USUBJID") +#' #' @export expect_dfs_equal <- function(base, compare, keys, ...) { diff <- diffdf::diffdf(base, compare, keys, suppress_warnings = TRUE, ...) diff --git a/man/expect_dfs_equal.Rd b/man/expect_dfs_equal.Rd index 17d8d261..57008d0f 100644 --- a/man/expect_dfs_equal.Rd +++ b/man/expect_dfs_equal.Rd @@ -20,7 +20,41 @@ expect_dfs_equal(base, compare, keys, ...) An error if \code{base} and \code{compare} do not match or \code{NULL} invisibly if they do } \description{ -Uses \code{\link[diffdf:diffdf]{diffdf::diffdf()}} to compares 2 datasets for any differences +Uses \code{\link[diffdf:diffdf]{diffdf::diffdf()}} to compares 2 datasets for any differences. This function can be +thought of as an R-equivalent of SAS' proc compare. +} +\examples{ +library(dplyr) +library(tibble) + +tbl1 <- tribble( + ~USUBJID, ~AGE, ~SEX, + "1001", 18, "M", + "1002", 19, "F", + "1003", 20, "M", + "1004", 18, "F" +) + +tbl2 <- tribble( + ~USUBJID, ~AGE, ~SEX, + "1001", 18, "M", + "1002", 18.9, "F", + "1003", 20, NA +) + +expect_dfs_equal(tbl1, tbl2, keys = "USUBJID") + +tlb3 <- tribble( + ~USUBJID, ~AGE, ~SEX, + "1004", 18, "F", + "1003", 20, "M", + "1002", 19, "F", + "1001", 18, "M", +) + +# Note the sorting order of the keys is not required +expect_dfs_equal(tbl1, tlb3, keys = "USUBJID") + } \author{ Thomas Neitmann From bddc0318f83312744e6fe300c0324ad4c9a966ad Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 15 Nov 2022 21:47:24 +0000 Subject: [PATCH 142/179] #141 - Improve flow and description of function --- R/expect_dfs_equal.R | 4 ++-- man/expect_dfs_equal.Rd | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/expect_dfs_equal.R b/R/expect_dfs_equal.R index 2ae67dc1..6bca34cc 100644 --- a/R/expect_dfs_equal.R +++ b/R/expect_dfs_equal.R @@ -1,7 +1,7 @@ #' Expectation: Are Two Datasets Equal? #' #' Uses [diffdf::diffdf()] to compares 2 datasets for any differences. This function can be -#' thought of as an R-equivalent of SAS' proc compare. +#' thought of as an R-equivalent of SAS proc compare and a useful tool for unit testing as well. #' #' @param base Input dataset #' @param compare Comparison dataset @@ -17,7 +17,7 @@ #' @family test_helper #' #' @examples -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) #' library(tibble) #' #' tbl1 <- tribble( diff --git a/man/expect_dfs_equal.Rd b/man/expect_dfs_equal.Rd index 57008d0f..2b7ff6e2 100644 --- a/man/expect_dfs_equal.Rd +++ b/man/expect_dfs_equal.Rd @@ -21,10 +21,10 @@ An error if \code{base} and \code{compare} do not match or \code{NULL} invisibly } \description{ Uses \code{\link[diffdf:diffdf]{diffdf::diffdf()}} to compares 2 datasets for any differences. This function can be -thought of as an R-equivalent of SAS' proc compare. +thought of as an R-equivalent of SAS proc compare and a useful tool for unit testing as well. } \examples{ -library(dplyr) +library(dplyr, warn.conflicts = FALSE) library(tibble) tbl1 <- tribble( From 731ea3620f00afcab645ec27e5d3956fd249f593 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 16 Nov 2022 16:18:44 +0000 Subject: [PATCH 143/179] #141 - Use try() --- R/expect_dfs_equal.R | 2 +- man/expect_dfs_equal.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/expect_dfs_equal.R b/R/expect_dfs_equal.R index 6bca34cc..b837fc52 100644 --- a/R/expect_dfs_equal.R +++ b/R/expect_dfs_equal.R @@ -35,7 +35,7 @@ #' "1003", 20, NA #' ) #' -#' expect_dfs_equal(tbl1, tbl2, keys = "USUBJID") +#' try(expect_dfs_equal(tbl1, tbl2, keys = "USUBJID")) #' #' tlb3 <- tribble( #' ~USUBJID, ~AGE, ~SEX, diff --git a/man/expect_dfs_equal.Rd b/man/expect_dfs_equal.Rd index 2b7ff6e2..48ee196a 100644 --- a/man/expect_dfs_equal.Rd +++ b/man/expect_dfs_equal.Rd @@ -42,7 +42,7 @@ tbl2 <- tribble( "1003", 20, NA ) -expect_dfs_equal(tbl1, tbl2, keys = "USUBJID") +try(expect_dfs_equal(tbl1, tbl2, keys = "USUBJID")) tlb3 <- tribble( ~USUBJID, ~AGE, ~SEX, From f0882e04a4bf41a84f5e19d61aa71cbbd25dcd8d Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 16 Nov 2022 17:23:09 +0000 Subject: [PATCH 144/179] #141 - Address styler error with tribble() section --- R/expect_dfs_equal.R | 22 +++++++++++----------- man/expect_dfs_equal.Rd | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/R/expect_dfs_equal.R b/R/expect_dfs_equal.R index b837fc52..0e83c0b3 100644 --- a/R/expect_dfs_equal.R +++ b/R/expect_dfs_equal.R @@ -22,27 +22,27 @@ #' #' tbl1 <- tribble( #' ~USUBJID, ~AGE, ~SEX, -#' "1001", 18, "M", -#' "1002", 19, "F", -#' "1003", 20, "M", -#' "1004", 18, "F" +#' "1001", 18, "M", +#' "1002", 19, "F", +#' "1003", 20, "M", +#' "1004", 18, "F" #' ) #' #' tbl2 <- tribble( #' ~USUBJID, ~AGE, ~SEX, -#' "1001", 18, "M", -#' "1002", 18.9, "F", -#' "1003", 20, NA +#' "1001", 18, "M", +#' "1002", 18.9, "F", +#' "1003", 20, NA #' ) #' #' try(expect_dfs_equal(tbl1, tbl2, keys = "USUBJID")) #' #' tlb3 <- tribble( #' ~USUBJID, ~AGE, ~SEX, -#' "1004", 18, "F", -#' "1003", 20, "M", -#' "1002", 19, "F", -#' "1001", 18, "M", +#' "1004", 18, "F", +#' "1003", 20, "M", +#' "1002", 19, "F", +#' "1001", 18, "M", #' ) #' #' # Note the sorting order of the keys is not required diff --git a/man/expect_dfs_equal.Rd b/man/expect_dfs_equal.Rd index 48ee196a..75eba41a 100644 --- a/man/expect_dfs_equal.Rd +++ b/man/expect_dfs_equal.Rd @@ -29,27 +29,27 @@ library(tibble) tbl1 <- tribble( ~USUBJID, ~AGE, ~SEX, - "1001", 18, "M", - "1002", 19, "F", - "1003", 20, "M", - "1004", 18, "F" + "1001", 18, "M", + "1002", 19, "F", + "1003", 20, "M", + "1004", 18, "F" ) tbl2 <- tribble( ~USUBJID, ~AGE, ~SEX, - "1001", 18, "M", - "1002", 18.9, "F", - "1003", 20, NA + "1001", 18, "M", + "1002", 18.9, "F", + "1003", 20, NA ) try(expect_dfs_equal(tbl1, tbl2, keys = "USUBJID")) tlb3 <- tribble( ~USUBJID, ~AGE, ~SEX, - "1004", 18, "F", - "1003", 20, "M", - "1002", 19, "F", - "1001", 18, "M", + "1004", 18, "F", + "1003", 20, "M", + "1002", 19, "F", + "1001", 18, "M", ) # Note the sorting order of the keys is not required From 7a86c4727a9ea263bd833f7f4113b303a7706a59 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Thu, 17 Nov 2022 15:07:34 +0100 Subject: [PATCH 145/179] 176_assert_same_type: implement assert_same_type() --- NAMESPACE | 1 + NEWS.md | 4 +- R/assertions.R | 50 ++++++ man/assert_atomic_vector.Rd | 1 + man/assert_character_scalar.Rd | 1 + man/assert_character_vector.Rd | 1 + man/assert_data_frame.Rd | 1 + man/assert_date_vector.Rd | 1 + man/assert_expr.Rd | 1 + man/assert_filter_cond.Rd | 1 + man/assert_function.Rd | 1 + man/assert_function_param.Rd | 1 + man/assert_has_variables.Rd | 1 + man/assert_integer_scalar.Rd | 1 + man/assert_list_element.Rd | 1 + man/assert_list_of.Rd | 1 + man/assert_logical_scalar.Rd | 1 + man/assert_named_exprs.Rd | 1 + man/assert_numeric_vector.Rd | 1 + man/assert_one_to_one.Rd | 1 + man/assert_order_vars.Rd | 1 + man/assert_param_does_not_exist.Rd | 1 + man/assert_s3_class.Rd | 1 + man/assert_same_type.Rd | 66 +++++++ man/assert_symbol.Rd | 1 + man/assert_unit.Rd | 1 + man/assert_vars.Rd | 1 + man/assert_varval_list.Rd | 1 + tests/testthat/test-assertions.R | 272 ++++++++++++++++------------- 29 files changed, 299 insertions(+), 118 deletions(-) create mode 100644 man/assert_same_type.Rd diff --git a/NAMESPACE b/NAMESPACE index 3b28f9e7..ea617edb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -27,6 +27,7 @@ export(assert_one_to_one) export(assert_order_vars) export(assert_param_does_not_exist) export(assert_s3_class) +export(assert_same_type) export(assert_symbol) export(assert_unit) export(assert_vars) diff --git a/NEWS.md b/NEWS.md index 9028c7d5..146acf32 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,7 +10,9 @@ - New keyword/family `create_aux` for functions creating auxiliary datasets (#126) - - New function `assert_date_vector` (#129) + - New function `assert_date_vector()` (#129) + + - New function `assert_same_type()` (#176) - Remove dependency on `{assertthat}` (#149) diff --git a/R/assertions.R b/R/assertions.R index 537a6245..733dab1f 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1558,3 +1558,53 @@ assert_date_vector <- function(arg, optional = TRUE) { )) } } + +#' Are All Argument of the Same Type? +#' +#' +#' Checks if all arguments are of the same type. +#' +#' @param ... Arguments to be checked +#' +#' @author Stefan Bundfuss +#' +#' @return The function throws an error if not all arguments are of the same type. +#' +#' @export +#' +#' @keywords assertion +#' @family assertion +#' +#' @examples +#' example_fun <- function(true_value, false_value, missing_value) { +#' assert_same_type(true_value, false_value, missing_value) +#' } +#' +#' example_fun( +#' true_value = "Y", +#' false_value = "N", +#' missing_value = NA_character_ +#' ) +#' +#' try(example_fun( +#' true_value = 1, +#' false_value = 0, +#' missing_value = "missing" +#' )) +assert_same_type <- function(...) { + args <- rlang::dots_list(..., .named = TRUE) + arg_names <- lapply(args, function(x) deparse(substitute(x))) + types <- lapply(args, typeof) + + if (length(unique(types)) > 1) { + abort( + paste( + "All arguments must be of the same type.", + "Argument: Type", + "--------------", + paste0(names(args), ": ", types, collapse = "\n"), + sep = "\n" + ) + ) + } +} diff --git a/man/assert_atomic_vector.Rd b/man/assert_atomic_vector.Rd index 41a7f2d4..9dc6ae85 100644 --- a/man/assert_atomic_vector.Rd +++ b/man/assert_atomic_vector.Rd @@ -49,6 +49,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_character_scalar.Rd b/man/assert_character_scalar.Rd index 96ca9670..a312669c 100644 --- a/man/assert_character_scalar.Rd +++ b/man/assert_character_scalar.Rd @@ -79,6 +79,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_character_vector.Rd b/man/assert_character_vector.Rd index 6d43916f..8927ad57 100644 --- a/man/assert_character_vector.Rd +++ b/man/assert_character_vector.Rd @@ -52,6 +52,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_data_frame.Rd b/man/assert_data_frame.Rd index eaabc58a..8cd1c6ec 100644 --- a/man/assert_data_frame.Rd +++ b/man/assert_data_frame.Rd @@ -65,6 +65,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_date_vector.Rd b/man/assert_date_vector.Rd index 63f0787d..442afa65 100644 --- a/man/assert_date_vector.Rd +++ b/man/assert_date_vector.Rd @@ -50,6 +50,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_expr.Rd b/man/assert_expr.Rd index 74df9846..5dba5c09 100644 --- a/man/assert_expr.Rd +++ b/man/assert_expr.Rd @@ -40,6 +40,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_filter_cond.Rd b/man/assert_filter_cond.Rd index 1ed9d2d4..ada690ab 100644 --- a/man/assert_filter_cond.Rd +++ b/man/assert_filter_cond.Rd @@ -57,6 +57,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_function.Rd b/man/assert_function.Rd index 5590c39b..1f82f577 100644 --- a/man/assert_function.Rd +++ b/man/assert_function.Rd @@ -59,6 +59,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_function_param.Rd b/man/assert_function_param.Rd index df7dbd64..8c7fcf2b 100644 --- a/man/assert_function_param.Rd +++ b/man/assert_function_param.Rd @@ -48,6 +48,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_has_variables.Rd b/man/assert_has_variables.Rd index 47dc8227..578653af 100644 --- a/man/assert_has_variables.Rd +++ b/man/assert_has_variables.Rd @@ -47,6 +47,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_integer_scalar.Rd b/man/assert_integer_scalar.Rd index 205adae2..5014f8c6 100644 --- a/man/assert_integer_scalar.Rd +++ b/man/assert_integer_scalar.Rd @@ -57,6 +57,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_list_element.Rd b/man/assert_list_element.Rd index c94cb917..f93386a4 100644 --- a/man/assert_list_element.Rd +++ b/man/assert_list_element.Rd @@ -60,6 +60,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_list_of.Rd b/man/assert_list_of.Rd index c8fa4d10..8c1e3826 100644 --- a/man/assert_list_of.Rd +++ b/man/assert_list_of.Rd @@ -54,6 +54,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_logical_scalar.Rd b/man/assert_logical_scalar.Rd index d9f8ceac..b89908fc 100644 --- a/man/assert_logical_scalar.Rd +++ b/man/assert_logical_scalar.Rd @@ -55,6 +55,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_named_exprs.Rd b/man/assert_named_exprs.Rd index 940584c3..45b2a907 100644 --- a/man/assert_named_exprs.Rd +++ b/man/assert_named_exprs.Rd @@ -40,6 +40,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_numeric_vector.Rd b/man/assert_numeric_vector.Rd index 7a0f6fa0..c037a952 100644 --- a/man/assert_numeric_vector.Rd +++ b/man/assert_numeric_vector.Rd @@ -49,6 +49,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_one_to_one.Rd b/man/assert_one_to_one.Rd index b0a092e3..a1608838 100644 --- a/man/assert_one_to_one.Rd +++ b/man/assert_one_to_one.Rd @@ -42,6 +42,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_order_vars.Rd b/man/assert_order_vars.Rd index 0911a444..28676597 100644 --- a/man/assert_order_vars.Rd +++ b/man/assert_order_vars.Rd @@ -54,6 +54,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_one_to_one}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_param_does_not_exist.Rd b/man/assert_param_does_not_exist.Rd index f3639d5e..3f0d0d3e 100644 --- a/man/assert_param_does_not_exist.Rd +++ b/man/assert_param_does_not_exist.Rd @@ -49,6 +49,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_one_to_one}()}, \code{\link{assert_order_vars}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_s3_class.Rd b/man/assert_s3_class.Rd index c85ce64c..cf5e3bb4 100644 --- a/man/assert_s3_class.Rd +++ b/man/assert_s3_class.Rd @@ -53,6 +53,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_one_to_one}()}, \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, diff --git a/man/assert_same_type.Rd b/man/assert_same_type.Rd new file mode 100644 index 00000000..af50dabe --- /dev/null +++ b/man/assert_same_type.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/assertions.R +\name{assert_same_type} +\alias{assert_same_type} +\title{Are All Argument of the Same Type?} +\usage{ +assert_same_type(...) +} +\arguments{ +\item{...}{Arguments to be checked} +} +\value{ +The function throws an error if not all arguments are of the same type. +} +\description{ +Checks if all arguments are of the same type. +} +\examples{ +example_fun <- function(true_value, false_value, missing_value) { + assert_same_type(true_value, false_value, missing_value) +} + +example_fun( + true_value = "Y", + false_value = "N", + missing_value = NA_character_ +) + +try(example_fun( + true_value = 1, + false_value = 0, + missing_value = "missing" + )) +} +\seealso{ +Checks for valid input and returns warning or errors messages: +\code{\link{assert_atomic_vector}()}, +\code{\link{assert_character_scalar}()}, +\code{\link{assert_character_vector}()}, +\code{\link{assert_data_frame}()}, +\code{\link{assert_date_vector}()}, +\code{\link{assert_expr}()}, +\code{\link{assert_filter_cond}()}, +\code{\link{assert_function_param}()}, +\code{\link{assert_function}()}, +\code{\link{assert_has_variables}()}, +\code{\link{assert_integer_scalar}()}, +\code{\link{assert_list_element}()}, +\code{\link{assert_list_of}()}, +\code{\link{assert_logical_scalar}()}, +\code{\link{assert_named_exprs}()}, +\code{\link{assert_numeric_vector}()}, +\code{\link{assert_one_to_one}()}, +\code{\link{assert_order_vars}()}, +\code{\link{assert_param_does_not_exist}()}, +\code{\link{assert_s3_class}()}, +\code{\link{assert_symbol}()}, +\code{\link{assert_unit}()}, +\code{\link{assert_vars}()}, +\code{\link{assert_varval_list}()} +} +\author{ +Stefan Bundfuss +} +\concept{assertion} +\keyword{assertion} diff --git a/man/assert_symbol.Rd b/man/assert_symbol.Rd index c314d25b..d1e4878a 100644 --- a/man/assert_symbol.Rd +++ b/man/assert_symbol.Rd @@ -58,6 +58,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()}, \code{\link{assert_varval_list}()} diff --git a/man/assert_unit.Rd b/man/assert_unit.Rd index 5251bf6d..4821819a 100644 --- a/man/assert_unit.Rd +++ b/man/assert_unit.Rd @@ -56,6 +56,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_vars}()}, \code{\link{assert_varval_list}()} diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index 34de92de..9ba708af 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -65,6 +65,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_varval_list}()} diff --git a/man/assert_varval_list.Rd b/man/assert_varval_list.Rd index b134c59b..796d9a7d 100644 --- a/man/assert_varval_list.Rd +++ b/man/assert_varval_list.Rd @@ -64,6 +64,7 @@ Checks for valid input and returns warning or errors messages: \code{\link{assert_order_vars}()}, \code{\link{assert_param_does_not_exist}()}, \code{\link{assert_s3_class}()}, +\code{\link{assert_same_type}()}, \code{\link{assert_symbol}()}, \code{\link{assert_unit}()}, \code{\link{assert_vars}()} diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 97fab80c..35dcd461 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -130,8 +130,8 @@ test_that("assert_vars Test 9: error if unexpected input", { }) # assert_data_frame ---- -test_that("Test 12 : `assert_data_frame` does not throw an error - if optional is TRUE and `arg` is NULL", { +## Test 10: no error if optional is TRUE and `arg` is NULL ---- +test_that("assert_data_frame Test 10: no error if optional is TRUE and `arg` is NULL", { example_fun <- function(dataset) { assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID), optional = TRUE) } @@ -141,7 +141,8 @@ test_that("Test 12 : `assert_data_frame` does not throw an error ) }) -test_that("Test 13 : `assert_data_frame` throws an error if required variables are missing", { +## Test 11: error if required variables are missing ---- +test_that("assert_data_frame Test 11: error if required variables are missing", { example_fun <- function(dataset) { assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } @@ -153,7 +154,8 @@ test_that("Test 13 : `assert_data_frame` throws an error if required variables a ) }) -test_that("Test 14 : `assert_data_frame` throws an error if required variable is missing", { +## Test 12: error if required variable is missing ---- +test_that("assert_data_frame Test 12: error if required variable is missing", { example_fun <- function(dataset) { assert_data_frame(dataset, required_vars = vars(STUDYID, USUBJID)) } @@ -166,8 +168,8 @@ test_that("Test 14 : `assert_data_frame` throws an error if required variable is }) # assert_character_scalar ---- -test_that("Test 15 : `assert_character_scalar` does not throw an error if - optional is TRUE and `arg` is NULL", { +## Test 13: no error if optional is TRUE and `arg` is NULL ---- +test_that("assert_character_scalar Test 13: no error if optional is TRUE and `arg` is NULL", { example_fun <- function(character) { assert_character_scalar(character, optional = TRUE) } @@ -177,8 +179,8 @@ test_that("Test 15 : `assert_character_scalar` does not throw an error if ) }) -test_that("Test 16 : `assert_character_scalar` does not throw an error if - case_sensitive is FALSE, and argument is returned in lower case", { +## Test 14: no error, case_sensitive = FALSE ---- +test_that("assert_character_scalar Test 14: no error, case_sensitive = FALSE", { example_fun <- function(character) { assert_character_scalar(character, values = c("test"), case_sensitive = FALSE) } @@ -215,8 +217,8 @@ test_that("Test 16 : `assert_character_scalar` does not throw an error if expect_equal(out, "months") }) -test_that("Test 17 : `assert_character_scalar` throws an error if - values are not NULL and `arg` not in values", { +## Test 15: error if `arg` not in values ---- +test_that("assert_character_scalar Test 15: error if `arg` not in values", { example_fun <- function(character) { assert_character_scalar(character, values = c("test")) } @@ -274,7 +276,8 @@ test_that("Test 17 : `assert_character_scalar` throws an error if ) }) -test_that("Test 18 : `assert_character_vector` throws an error if `arg` not a character vector", { +## Test 16: error if `arg` not a character vector ---- +test_that("assert_character_scalar Test 16: error if `arg` not a character vector", { arg <- c(1, 2, 3) expect_error( @@ -282,8 +285,8 @@ test_that("Test 18 : `assert_character_vector` throws an error if `arg` not a ch ) }) -test_that("Test 19 : `assert_character_vector` throws an error - if values are not NULL and `arg` is not in values", { +## Test 17: error if `arg` is not in values ---- +test_that("assert_character_scalar Test 17: error if `arg` is not in values", { example_fun <- function(character) { assert_character_vector(character, values = c("test", "oak")) } @@ -294,8 +297,8 @@ test_that("Test 19 : `assert_character_vector` throws an error }) # assert_logical_scalar ---- -test_that("Test 20 : `assert_logical_scalar` does not throw an error - if optional is TRUE and `arg` is NULL", { +## Test 18: no error if optional is TRUE and `arg` is NULL ---- +test_that("assert_logical_scalar Test 18: no error if optional is TRUE and `arg` is NULL", { example_fun <- function(arg) { assert_logical_scalar(arg, optional = TRUE) } @@ -305,7 +308,8 @@ test_that("Test 20 : `assert_logical_scalar` does not throw an error ) }) -test_that("Test 21 : `assert_logical_scalar` throws an error if `arg` is not TRUE or FALSE", { +## Test 19: error if `arg` is not TRUE or FALSE ---- +test_that("assert_logical_scalar Test 19: error if `arg` is not TRUE or FALSE", { example_fun <- function(arg) { assert_logical_scalar(arg) } @@ -316,7 +320,8 @@ test_that("Test 21 : `assert_logical_scalar` throws an error if `arg` is not TRU }) # assert_symbol ---- -test_that("Test 22 : `assert_symbol` does not throw an error if optional = TRUE and `arg` = NULL", { +## Test 20: no error if optional = TRUE and `arg` = NULL ---- +test_that("assert_symbol Test 20: no error if optional = TRUE and `arg` = NULL", { f <- function(var) { v <- enquo(var) } @@ -332,7 +337,8 @@ test_that("Test 22 : `assert_symbol` does not throw an error if optional = TRUE ) }) -test_that("Test 23 : `assert_symbol` throws an error if `arg` is missing", { +## Test 21: `assert_symbol` throws an error if `arg` is missing ---- +test_that("assert_symbol Test 21: `assert_symbol` throws an error if `arg` is missing", { f <- function(var) { v <- enquo(var) } @@ -348,7 +354,8 @@ test_that("Test 23 : `assert_symbol` throws an error if `arg` is missing", { ) }) -test_that("Test 24 : `assert_symbol` throws an error if `arg` is not a symbol", { +## Test 22: `assert_symbol` throws an error if `arg` is not a symbol ---- +test_that("assert_symbol Test 22: `assert_symbol` throws an error if `arg` is not a symbol", { f <- function(var) { v <- enquo(var) } @@ -364,7 +371,8 @@ test_that("Test 24 : `assert_symbol` throws an error if `arg` is not a symbol", ) }) -test_that("Test 25 : `assert_symbol` does not throw an error if `arg` is a symbol", { +## Test 23: `assert_symbol` does not throw an error if `arg` is a symbol ---- +test_that("assert_symbol Test 23: `assert_symbol` does not throw an error if `arg` is a symbol", { f <- function(var) { v <- enquo(var) } @@ -383,7 +391,8 @@ test_that("Test 25 : `assert_symbol` does not throw an error if `arg` is a symbo }) # assert_expr ---- -test_that("Test 26 : `assert_expr` does not throw an error if `arg` is an expression", { +## Test 24: `assert_expr` does not throw an error if `arg` is an expression ---- +test_that("assert_expr Test 24: `assert_expr` does not throw an error if `arg` is an expression", { f <- function(var) { v <- enquo(var) } @@ -399,8 +408,8 @@ test_that("Test 26 : `assert_expr` does not throw an error if `arg` is an expres ) }) -test_that("Test 27 : `assert_expr` does not throw an error if - optional is TRUE and `arg` is NULL", { +## Test 25: no error if optional is TRUE and `arg` is NULL ---- +test_that("assert_expr Test 25: no error if optional is TRUE and `arg` is NULL", { f <- function(var) { v <- enquo(var) } @@ -416,7 +425,8 @@ test_that("Test 27 : `assert_expr` does not throw an error if ) }) -test_that("Test 28 : `assert_expr` throws an error if `arg` is missing", { +## Test 26: `assert_expr` throws an error if `arg` is missing ---- +test_that("assert_expr Test 26: `assert_expr` throws an error if `arg` is missing", { f <- function(var) { v <- enquo(var) } @@ -430,7 +440,8 @@ test_that("Test 28 : `assert_expr` throws an error if `arg` is missing", { ) }) -test_that("Test 29 : `assert_expr` throws an error if `arg` is not an expression", { +## Test 27: `assert_expr` throws an error if `arg` is not an expression ---- +test_that("assert_expr Test 27: `assert_expr` throws an error if `arg` is not an expression", { f <- function(var) { v <- enquo(var) } @@ -447,8 +458,8 @@ test_that("Test 29 : `assert_expr` throws an error if `arg` is not an expression }) # assert_vars ---- -test_that("Test 30 : `assert_vars` throws an error - if `arg` is not a list of unquoted variable names", { +## Test 28: no error if `arg` is not a list of unquoted variable names ---- +test_that("assert_vars Test 28: no error if `arg` is not a list of unquoted variable names", { example_fun <- function(arg) { assert_vars(arg) } @@ -461,8 +472,8 @@ test_that("Test 30 : `assert_vars` throws an error ) }) -test_that("Test 31 : `assert_vars` throws an error - if some elements of `arg` are not unquoted variable names", { +## Test 29: error if some elements of `arg` are not unquoted variable names ---- +test_that("assert_vars Test 29: error if some elements of `arg` are not unquoted variable names", { example_fun <- function(arg) { assert_vars(arg) } @@ -473,8 +484,8 @@ test_that("Test 31 : `assert_vars` throws an error }) # assert_order_vars ---- -test_that("Test 32 : `assert_order_vars` throws an error - if `arg` is not a list of unquoted variable names or `desc()` calls", { +## Test 30: error if `arg` is not a list variable names or `desc()` ---- +test_that("assert_order_vars Test 30: error if `arg` is not a list variable names or `desc()`", { example_fun <- function(arg) { assert_order_vars(arg) } @@ -487,8 +498,8 @@ test_that("Test 32 : `assert_order_vars` throws an error ) }) -test_that("Test 33 : `assert_order_vars` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 31: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_order_vars Test 31: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_order_vars(arg, optional = TRUE) } @@ -499,8 +510,8 @@ test_that("Test 33 : `assert_order_vars` does not throw an error }) # assert_integer_scalar ---- -test_that("Test 34 : `assert_integer_scalar` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 32: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_integer_scalar Test 32: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_integer_scalar(arg, optional = TRUE) } @@ -510,7 +521,8 @@ test_that("Test 34 : `assert_integer_scalar` does not throw an error ) }) -test_that("Test 35 : `assert_integer_scalar` throws an error if chosen subset not in subsets", { +## Test 33: error if chosen subset not in subsets ---- +test_that("assert_integer_scalar Test 33: error if chosen subset not in subsets", { example_fun <- function(arg) { assert_integer_scalar(arg, subset = "infinity") } @@ -520,8 +532,8 @@ test_that("Test 35 : `assert_integer_scalar` throws an error if chosen subset no ) }) -test_that("Test 36 : `assert_integer_scalar` does not throw an error - if `arg` is an integer scalar in selected subset", { +## Test 34: no error if `arg` is in selected subset ---- +test_that("assert_integer_scalar Test 34: no error if `arg` is in selected subset", { example_fun <- function(arg) { assert_integer_scalar(arg, subset = "positive") } @@ -531,8 +543,8 @@ test_that("Test 36 : `assert_integer_scalar` does not throw an error ) }) -test_that("Test 37 : `assert_integer_scalar` throws an error - if `arg` is not an integer scalar", { +## Test 35: error if `arg` is not an integer scalar ---- +test_that("assert_integer_scalar Test 35: error if `arg` is not an integer scalar", { example_fun <- function(arg) { assert_integer_scalar(arg) } @@ -546,8 +558,8 @@ test_that("Test 37 : `assert_integer_scalar` throws an error }) # assert_numeric_vector ---- -test_that("Test 38 : `assert_numeric_vector` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 36: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_numeric_vector Test 36: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_numeric_vector(arg, optional = TRUE) } @@ -558,7 +570,8 @@ test_that("Test 38 : `assert_numeric_vector` does not throw an error }) # assert_integer_scalar ---- -test_that("Test 39 : `assert_integer_scalar` throws an error if `arg` is not an integer scalar", { +## Test 37: error if `arg` is not an integer scalar ---- +test_that("assert_integer_scalar Test 37: error if `arg` is not an integer scalar", { example_fun <- function(arg) { assert_numeric_vector(arg) } @@ -571,8 +584,8 @@ test_that("Test 39 : `assert_integer_scalar` throws an error if `arg` is not an }) # assert_s3_class ---- -test_that("Test 40 : `assert_s3_class` throws an error - if `arg` is not an object of a specific class S3", { +## Test 38: error if `arg` is not an object of a specific class S3 ---- +test_that("assert_s3_class Test 38: error if `arg` is not an object of a specific class S3", { example_fun <- function(arg) { assert_s3_class(arg, "factor") } @@ -580,8 +593,8 @@ test_that("Test 40 : `assert_s3_class` throws an error expect_error(example_fun("test")) }) -test_that("Test 41 : `assert_s3_class` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 39: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_s3_class Test 39: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_s3_class(arg, class = "factor", optional = TRUE) } @@ -591,8 +604,8 @@ test_that("Test 41 : `assert_s3_class` does not throw an error ) }) -test_that("Test 42 : `assert_s3_class` does not throw an error - if `arg` is an object of a specific class S3", { +## Test 40: no error if `arg` is an object of a specific class S3 ---- +test_that("assert_s3_class Test 40: no error if `arg` is an object of a specific class S3", { example_fun <- function(arg) { assert_s3_class(arg, "factor") } @@ -601,8 +614,8 @@ test_that("Test 42 : `assert_s3_class` does not throw an error }) # assert_list_of ---- -test_that("Test 43 : `assert_list_of` throws an error - if `arg` is not a list of objects of a specific class S3", { +## Test 41: error if `arg` is not a list of specific class S3 objects ---- +test_that("assert_list_of Test 41: error if `arg` is not a list of specific class S3 objects", { example_fun <- function(arg) { assert_list_of(arg, "factor") } @@ -610,8 +623,8 @@ test_that("Test 43 : `assert_list_of` throws an error expect_error(example_fun(list("test"))) }) -test_that("Test 44 : `assert_list_of` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 42: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_list_of Test 42: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_list_of(arg, class = "factor", optional = TRUE) } @@ -621,8 +634,8 @@ test_that("Test 44 : `assert_list_of` does not throw an error ) }) -test_that("Test 45 : `assert_list_of` does not throw an error - if `arg` is a list of objects of a specific class S3", { +## Test 43: no error if `arg` is a list of specific class S3 objects ---- +test_that("assert_list_of Test 43: no error if `arg` is a list of specific class S3 objects", { example_fun <- function(arg) { assert_list_of(arg, "factor") } @@ -635,8 +648,8 @@ test_that("Test 45 : `assert_list_of` does not throw an error }) # assert_named_exprs ---- -test_that("Test 46 : `assert_named_exprs` throws an error - if `arg` is not a named list of expressions", { +## Test 44: error if `arg` is not a named list of expressions ---- +test_that("assert_named_exprs Test 44: error if `arg` is not a named list of expressions", { example_fun <- function(arg) { assert_named_exprs(arg) } @@ -650,8 +663,8 @@ test_that("Test 46 : `assert_named_exprs` throws an error expect_error(example_fun(arg)) }) -test_that("Test 47 : `assert_named_exprs` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 45: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_named_exprs Test 45: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_named_exprs(arg, optional = TRUE) } @@ -661,8 +674,8 @@ test_that("Test 47 : `assert_named_exprs` does not throw an error ) }) -test_that("Test 48 : `assert_named_exprs` does not throw an error - if `arg` is a named list of expressions", { +## Test 46: no error if `arg` is a named list of expressions ---- +test_that("assert_named_exprs Test 46: no error if `arg` is a named list of expressions", { example_fun <- function(arg) { assert_named_exprs(arg) } @@ -675,8 +688,8 @@ test_that("Test 48 : `assert_named_exprs` does not throw an error }) # assert_function ---- -test_that("Test 49 : `assert_function` throws an error - if `arg` is not a function", { +## Test 47: error if `arg` is not a function ---- +test_that("assert_function Test 47: error if `arg` is not a function", { example_fun <- function(arg) { assert_function(arg) } @@ -685,8 +698,8 @@ test_that("Test 49 : `assert_function` throws an error expect_error(example_fun()) }) -test_that("Test 50 : `assert_function` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 48: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_function Test 48: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_function(arg, optional = TRUE) } @@ -696,8 +709,8 @@ test_that("Test 50 : `assert_function` does not throw an error ) }) -test_that("Test 51 : `assert_function` does not throw an error - if `arg` is a function with all parameters defined", { +## Test 49: no error if `arg` is a function with all parameters defined ---- +test_that("assert_function Test 49: no error if `arg` is a function with all parameters defined", { example_fun <- function(arg) { assert_function(arg, params = c("x")) } @@ -705,8 +718,8 @@ test_that("Test 51 : `assert_function` does not throw an error expect_invisible(example_fun(mean)) }) -test_that("Test 52 : `assert_function` throws an error - if `params` is missing with no default", { +## Test 50: error if `params` is missing with no default ---- +test_that("assert_function Test 50: error if `params` is missing with no default", { example_fun <- function(arg) { assert_function(arg, params = c("x")) } @@ -722,8 +735,8 @@ test_that("Test 52 : `assert_function` throws an error # assert_function_param ---- -test_that("Test 53 : `assert_function_param` does not throw an error - if `arg` is a parameter of a function", { +## Test 51: no error if `arg` is a parameter of a function ---- +test_that("assert_function_param Test 51: no error if `arg` is a parameter of a function", { hello <- function(name) { print(sprintf("Hello %s", name)) } @@ -731,8 +744,8 @@ test_that("Test 53 : `assert_function_param` does not throw an error expect_invisible(assert_function_param("hello", "name")) }) -test_that("Test 54 : `assert_function_param` throws an error - if any elements of `params` is not a parameter of the function given by `arg`", { +## Test 52: error if expected function parameters are missing ---- +test_that("assert_function_param Test 52: error if expected function parameters are missing", { hello <- function(name) { print(sprintf("Hello %s", name)) } @@ -742,8 +755,8 @@ test_that("Test 54 : `assert_function_param` throws an error }) # assert_unit ---- -test_that("Test 55 : `assert_unit` does not throw an error - if the parameter is provided in the expected unit", { +## Test 53: no error if the parameter is provided in the expected unit ---- +test_that("assert_unit Test 53: no error if the parameter is provided in the expected unit", { advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -755,8 +768,8 @@ test_that("Test 55 : `assert_unit` does not throw an error ) }) -test_that("Test 56 : `assert_unit` throws an error - if there are multiple units in the input dataset", { +## Test 54: error if there are multiple units in the input dataset ---- +test_that("assert_unit Test 54: error if there are multiple units in the input dataset", { advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -768,8 +781,8 @@ test_that("Test 56 : `assert_unit` throws an error ) }) -test_that("Test 57 : `assert_unit` throws an error if the unit variable differs from - the unit for any observation of the parameter in the input dataset", { +## Test 55: error if unexpected unit in the input dataset ---- +test_that("assert_unit Test 55: error if unexpected unit in the input dataset", { advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -782,8 +795,8 @@ test_that("Test 57 : `assert_unit` throws an error if the unit variable differs }) # assert_param_does_not_exist ---- -test_that("Test 58 : `assert_param_does_not_exist` throws an error - if the parameter exists in the input dataset", { +## Test 56: error if parameter exists in the input dataset ---- +test_that("assert_param_does_not_exist Test 56: error if parameter exists in the input dataset", { advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -795,8 +808,8 @@ test_that("Test 58 : `assert_param_does_not_exist` throws an error ) }) -test_that("Test 59 : `assert_param_does_not_exist` does not throw an error - if the parameter exists in the input dataset", { +## Test 57: no error if the parameter exists in the dataset ---- +test_that("assert_param_does_not_exist Test 57: no error if the parameter exists in the dataset", { advs <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSTRESN, ~VSSTRESU, ~PARAMCD, ~AVAL, "P01", "WEIGHT", 80.1, "kg", "WEIGHT", 80.1, @@ -809,8 +822,8 @@ test_that("Test 59 : `assert_param_does_not_exist` does not throw an error }) # assert_varval_list ---- -test_that("Test 60 : `assert_varval_list` throws an error - if `arg` is not a list of variable-value expressions", { +## Test 58: error if `arg` is not a list of var-value expressions ---- +test_that("assert_varval_list Test 58: error if `arg` is not a list of var-value expressions", { example_fun <- function(arg) { assert_varval_list(arg, accept_var = FALSE) } @@ -820,8 +833,8 @@ test_that("Test 60 : `assert_varval_list` throws an error ) }) -test_that("Test 61 : `assert_varval_list` throws an error - if `arg` is not a list of variable-value expressions", { +## Test 59: error if `arg` is not a list of var-value expressions ---- +test_that("assert_varval_list Test 59: error if `arg` is not a list of var-value expressions", { example_fun <- function(arg) { assert_varval_list(arg, accept_var = TRUE) } @@ -831,8 +844,8 @@ test_that("Test 61 : `assert_varval_list` throws an error ) }) -test_that("Test 62 : `assert_varval_list` throws an error - if `required_elements` are missing from `arg`", { +## Test 60: error if `required_elements` are missing from `arg` ---- +test_that("assert_varval_list Test 60: error if `required_elements` are missing from `arg`", { example_fun <- function(arg) { assert_varval_list(arg, required_elements = "DTHDOM") } @@ -842,8 +855,8 @@ test_that("Test 62 : `assert_varval_list` throws an error ) }) -test_that("Test 63 : `assert_varval_list` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 61: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_varval_list Test 61: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_varval_list(arg, optional = TRUE) } @@ -853,9 +866,8 @@ test_that("Test 63 : `assert_varval_list` does not throw an error ) }) -test_that("Test 64 : `assert_varval_list` throws an error if `accept_expr` is TRUE - and the expressions in `arg` are not: a symbol, a string, a numeric , an - `NA` or an expression", { +## Test 62: error if `accept_expr` is TRUE and value is invalid ---- +test_that("assert_varval_list Test 62: error if `accept_expr` is TRUE and value is invalid", { example_fun <- function(arg) { assert_varval_list(arg, accept_expr = TRUE) } @@ -865,8 +877,8 @@ test_that("Test 64 : `assert_varval_list` throws an error if `accept_expr` is TR ) }) -test_that("Test 65 : `assert_varval_list` throws an error if `accept_expr` is FALSE and - the expressions in `arg` are not: a symbol, a string, a numeric or `NA`", { +## Test 63: error if `accept_expr` is FALSE and value is invalid ---- +test_that("assert_varval_list Test 63: error if `accept_expr` is FALSE and value is invalid", { example_fun <- function(arg) { assert_varval_list(arg, accept_expr = FALSE) } @@ -876,8 +888,8 @@ test_that("Test 65 : `assert_varval_list` throws an error if `accept_expr` is FA ) }) -test_that("Test 66 : `assert_varval_list` does not throw an error - if an argument is a variable-value list", { +## Test 64: no error if an argument is a variable-value list ---- +test_that("assert_varval_list Test 64: no error if an argument is a variable-value list", { example_fun <- function(arg) { assert_varval_list(arg) } @@ -888,8 +900,8 @@ test_that("Test 66 : `assert_varval_list` does not throw an error }) # assert_list_element ---- -test_that("Test 67 : `assert_list_element` does not throw an error - if the elements of a list of named lists/classes fulfill a certain condition", { +## Test 65: no error if the elements fulfill a certain condition ---- +test_that("assert_list_element Test 65: no error if the elements fulfill a certain condition", { expect_invisible( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = AESEQ), "DTHSEQ", TRUE, message_text = "") ) @@ -901,8 +913,8 @@ test_that("Test 67 : `assert_list_element` does not throw an error ) }) -test_that("Test 68 : `assert_list_element` throws an error - if the elements of a list of named lists/classes do not fulfill a certain condition", { +## Test 66: error if the elements do not fulfill the condition ---- +test_that("assert_list_element Test 66: error if the elements do not fulfill the condition", { expect_error( assert_list_element(vars(DTHDOM = "AE", DTHSEQ = admiral.test::admiral_dm), "DTHSEQ", (admiral.test::admiral_dm)$DOMAIN == "GM", @@ -912,23 +924,23 @@ test_that("Test 68 : `assert_list_element` throws an error }) # assert_one_to_one ---- -test_that("Test 69 : `assert_one_to_one` throws an error - if there is a one to many mapping between two lists of variables", { +## Test 67: error if there is a one to many mapping ---- +test_that("assert_one_to_one Test 67: error if there is a one to many mapping", { expect_error( assert_one_to_one(admiral.test::admiral_dm, vars(DOMAIN), vars(USUBJID)) ) }) -test_that("Test 70 : `assert_one_to_one` throws an error - if there is a many to one mapping between two lists of variables", { +## Test 68: error if there is a many to one mapping ---- +test_that("assert_one_to_one Test 68: error if there is a many to one mapping", { expect_error( assert_one_to_one(admiral.test::admiral_dm, vars(USUBJID), vars(DOMAIN)) ) }) # assert_date_var ---- -test_that("Test 71 : `assert_date_var` throws an error - if a variable in a dataset is not a date or datetime variable", { +## Test 69: error if variable is not a date or datetime variable ---- +test_that("assert_date_var Test 69: error if variable is not a date or datetime variable", { example_fun <- function(dataset, var) { var <- assert_symbol(enquo(var)) assert_date_var(dataset = dataset, var = !!var) @@ -949,19 +961,18 @@ test_that("Test 71 : `assert_date_var` throws an error }) # assert_date_vector ---- -## Test 72: returns error if input vector is not a date formatted ---- -test_that("assert_date_vector Test 72: returns error if input vector is not a date formatted", { +## Test 70: returns error if input vector is not a date formatted ---- +test_that("assert_date_vector Test 70: returns error if input vector is not a date formatted", { expect_error(assert_date_vector("2018-08-23")) }) -## Test 73: returns invisible if input is date formatted ---- -test_that("assert_date_vector Test 73: returns invisible if input is date formatted", { +## Test 71: returns invisible if input is date formatted ---- +test_that("assert_date_vector Test 71: returns invisible if input is date formatted", { expect_invisible(assert_date_vector(as.Date("2022-10-25"))) }) -## Test 74: returns invisible if `arg` is NULL and optional is TRUE ---- -test_that("Test 74 : `assert_date_vector` does not throw an error - if `arg` is NULL and optional is TRUE", { +## Test 72: no error if `arg` is NULL and optional is TRUE ---- +test_that("assert_date_vector Test 72: no error if `arg` is NULL and optional is TRUE", { example_fun <- function(arg) { assert_date_vector(arg, optional = TRUE) } @@ -972,8 +983,35 @@ test_that("Test 74 : `assert_date_vector` does not throw an error }) # assert_atomic_vector ---- -## Test 75: error if input is not atomic vector ---- -test_that("assert_atomic_vector Test 14: error if input is not atomic vector", { +## Test 73: error if input is not atomic vector ---- +test_that("assert_atomic_vector Test 73: error if input is not atomic vector", { x <- list("a", "a", "b", "c", "d", "d", 1, 1, 4) expect_error(assert_atomic_vector(x)) }) + +# assert_same_type ---- +## Test 74: no error if same type ---- +test_that("assert_same_type Test 74: no error if same type", { + true_value <- "Y" + false_value <- "N" + expect_invisible(assert_same_type(true_value, false_value)) +}) + +## Test 75: error if different type ---- +test_that("assert_same_type Test 75: error if different type", { + true_value <- "Y" + false_value <- "N" + missing_value <- 0 + expect_error( + assert_same_type(true_value, false_value, missing_value), + regexp = paste( + "All arguments must be of the same type.", + "Argument: Type", + "--------------", + "true_value: character", + "false_value: character", + "missing_value: double", + sep = "\n"), + fixed = TRUE + ) +}) From f20acff3e8b2c929aad89de714eb399efca42120 Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Thu, 17 Nov 2022 15:11:28 +0100 Subject: [PATCH 146/179] 176_assert_same_type: style files --- R/assertions.R | 4 ++-- tests/testthat/test-assertions.R | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 733dab1f..aa9456a4 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1590,9 +1590,9 @@ assert_date_vector <- function(arg, optional = TRUE) { #' true_value = 1, #' false_value = 0, #' missing_value = "missing" -#' )) +#' )) assert_same_type <- function(...) { - args <- rlang::dots_list(..., .named = TRUE) + args <- rlang::dots_list(..., .named = TRUE) arg_names <- lapply(args, function(x) deparse(substitute(x))) types <- lapply(args, typeof) diff --git a/tests/testthat/test-assertions.R b/tests/testthat/test-assertions.R index 35dcd461..3b904736 100644 --- a/tests/testthat/test-assertions.R +++ b/tests/testthat/test-assertions.R @@ -1011,7 +1011,8 @@ test_that("assert_same_type Test 75: error if different type", { "true_value: character", "false_value: character", "missing_value: double", - sep = "\n"), + sep = "\n" + ), fixed = TRUE - ) + ) }) From e36abccb549a8da0d6b2f2478196b114c521b93c Mon Sep 17 00:00:00 2001 From: "Bundfuss, Stefan {MDBB~Basel}" Date: Thu, 17 Nov 2022 15:19:36 +0100 Subject: [PATCH 147/179] 176_assert_same_type: update man --- man/assert_same_type.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/assert_same_type.Rd b/man/assert_same_type.Rd index af50dabe..34683f76 100644 --- a/man/assert_same_type.Rd +++ b/man/assert_same_type.Rd @@ -30,7 +30,7 @@ try(example_fun( true_value = 1, false_value = 0, missing_value = "missing" - )) +)) } \seealso{ Checks for valid input and returns warning or errors messages: From 21aad9e144770d8d08dadcb5469bbb30c1e6f7e5 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Thu, 17 Nov 2022 19:56:17 +0000 Subject: [PATCH 148/179] #110 - Proposed additions rough draft --- vignettes/programming_strategy.Rmd | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 5f3e8537..f4a03f1e 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -119,6 +119,19 @@ variables must start with `temp_` and must be removed from the output dataset. | `derive_vars_` (e.g. `derive_vars_dt`) | Functions which add multiple variables | | `derive_param_` (e.g. `derive_param_os`) | Functions which add a single parameter | | `compute_` / `calculate_` / ... | Functions that take vectors as input and return a vector | +| `create_` / `consolidate_` | Functions that create datasets without keeping the original observations | +| `get_` | Usually utility functions that return very specific objects that get passed through other functions | +| `filter_` | Functions that filter observations based on conditions associated with common clinical trial syntax | + +| Function name suffix | Description | +|----------------------------------------------------------------------------------------------------------------------------------------------------| +| `_derivation` (suffix) | High order functions that takes another function as input | +| `_date` / `_time` / `_dt` / `_dtc` / `_dtm` | Functions associated with dates, times, datetimes, and their character equivalents. | +| `_source` | Functions that create source datasets that usually will be passed through other `derive_` functions.| + +| Other common function name terms | Description | +|----------------------------------------------------------------------------------------------------------------------------------------------------| +| `_merged_` / `_joined_` / `_extreme_` | Functions that follow the generic function user-guide. | Please note that the appropriate *var*/*vars* prefix should be used for all cases in which the function creates any variable(s), regardless of the presence of a `new_var` parameter in the function call. From 3d85f553863dfb2d55db8cd898ce2871da40fa9c Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 18 Nov 2022 14:52:06 +0000 Subject: [PATCH 149/179] #110 - fix typo and clarify _derivation description --- vignettes/programming_strategy.Rmd | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index f4a03f1e..589553f9 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -123,16 +123,18 @@ variables must start with `temp_` and must be removed from the output dataset. | `get_` | Usually utility functions that return very specific objects that get passed through other functions | | `filter_` | Functions that filter observations based on conditions associated with common clinical trial syntax | -| Function name suffix | Description | -|----------------------------------------------------------------------------------------------------------------------------------------------------| -| `_derivation` (suffix) | High order functions that takes another function as input | +| Function Name Suffix | Description | +|----------------------------------------------|-----------------------------------------------------------------------------------------------------| +| `_derivation` (suffix) | High order functions that call a user specified derivation | | `_date` / `_time` / `_dt` / `_dtc` / `_dtm` | Functions associated with dates, times, datetimes, and their character equivalents. | | `_source` | Functions that create source datasets that usually will be passed through other `derive_` functions.| -| Other common function name terms | Description | -|----------------------------------------------------------------------------------------------------------------------------------------------------| +| Other Common Function Name Terms | Description | +|----------------------------------------------|-----------------------------------------------------------------------------------------------------| | `_merged_` / `_joined_` / `_extreme_` | Functions that follow the generic function user-guide. | + + Please note that the appropriate *var*/*vars* prefix should be used for all cases in which the function creates any variable(s), regardless of the presence of a `new_var` parameter in the function call. ## Function Parameters From 289b2626a6cc5d7a7ee94a347df16e3ef4f2393e Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 18 Nov 2022 17:37:21 +0000 Subject: [PATCH 150/179] #83 - remove reexport of dplyr functions --- NAMESPACE | 2 -- R/assertions.R | 18 ++++++++++++++---- R/dev_utilities.R | 4 ++++ R/quo.R | 1 + R/reexports.R | 5 ----- R/warnings.R | 2 ++ man/add_suffix_to_vars.Rd | 1 + man/assert_data_frame.Rd | 1 + man/assert_filter_cond.Rd | 6 ++++-- man/assert_order_vars.Rd | 1 + man/assert_symbol.Rd | 6 ++++-- man/assert_vars.Rd | 2 ++ man/assert_varval_list.Rd | 2 ++ man/negate_vars.Rd | 2 ++ man/reexports.Rd | 17 ----------------- man/vars2chr.Rd | 2 ++ man/warn_if_inconsistent_list.Rd | 2 ++ 17 files changed, 42 insertions(+), 32 deletions(-) delete mode 100644 R/reexports.R delete mode 100644 man/reexports.Rd diff --git a/NAMESPACE b/NAMESPACE index 3b28f9e7..88f69972 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -34,7 +34,6 @@ export(assert_varval_list) export(backquote) export(convert_dtm_to_dtc) export(dataset_vignette) -export(desc) export(dquote) export(enumerate) export(expect_dfs_equal) @@ -61,7 +60,6 @@ export(set_dataset) export(squote) export(suppress_warning) export(valid_time_units) -export(vars) export(vars2chr) export(warn_if_incomplete_dtc) export(warn_if_inconsistent_list) diff --git a/R/assertions.R b/R/assertions.R index 537a6245..4c795891 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -23,6 +23,7 @@ #' #' @examples #' library(admiral.test) +#' library(dplyr) #' data(admiral_dm) #' #' example_fun <- function(dataset) { @@ -323,11 +324,13 @@ assert_logical_scalar <- function(arg, optional = FALSE) { #' @family assertion #' @examples #' library(admiral.test) +#' library(dplyr) +#' library(rlang) #' data(admiral_dm) #' #' example_fun <- function(dat, var) { -#' var <- assert_symbol(rlang::enquo(var)) -#' dplyr::select(dat, !!var) +#' var <- assert_symbol(enquo(var)) +#' select(dat, !!var) #' } #' #' example_fun(admiral_dm, USUBJID) @@ -415,12 +418,14 @@ assert_expr <- function(arg, optional = FALSE) { #' #' @examples #' library(admiral.test) +#' library(dplyr) +#' library(rlang) #' data(admiral_dm) #' #' # typical usage in a function as a parameter check #' example_fun <- function(dat, x) { -#' x <- assert_filter_cond(rlang::enquo(x)) -#' dplyr::filter(dat, !!x) +#' x <- assert_filter_cond(enquo(x)) +#' filter(dat, !!x) #' } #' #' example_fun(admiral_dm, AGE == 64) @@ -475,6 +480,8 @@ assert_filter_cond <- function(arg, optional = FALSE) { #' @keywords assertion #' @family assertion #' @examples +#' library(dplyr) +#' #' example_fun <- function(by_vars) { #' assert_vars(by_vars) #' } @@ -559,6 +566,7 @@ assert_vars <- function(arg, optional = FALSE, expect_names = FALSE) { #' @keywords assertion #' @family assertion #' @examples +#' library(dplyr) #' #' example_fun <- function(by_vars) { #' assert_order_vars(by_vars) @@ -1183,6 +1191,8 @@ assert_param_does_not_exist <- function(dataset, param) { #' @export #' #' @examples +#' library(dplyr) +#' #' example_fun <- function(vars) { #' assert_varval_list(vars) #' } diff --git a/R/dev_utilities.R b/R/dev_utilities.R index f9e1e0fc..653c154d 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -169,6 +169,8 @@ contains_vars <- function(arg) { #' @family dev_utility #' #' @examples +#' library(dplyr) +#' #' vars2chr(vars(USUBJID, AVAL)) vars2chr <- function(quosures) { rlang::set_names( @@ -196,6 +198,8 @@ vars2chr <- function(quosures) { #' @family dev_utility #' #' @examples +#' library(dplyr) +#' #' negate_vars(vars(USUBJID, STUDYID)) negate_vars <- function(vars = NULL) { assert_vars(vars, optional = TRUE) diff --git a/R/quo.R b/R/quo.R index 67dfe628..a0f4b805 100644 --- a/R/quo.R +++ b/R/quo.R @@ -143,6 +143,7 @@ replace_symbol_in_quo <- function(quosure, #' @export #' #' @examples +#' library(dplyr) #' add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") add_suffix_to_vars <- function(order, vars, diff --git a/R/reexports.R b/R/reexports.R deleted file mode 100644 index 9d41576a..00000000 --- a/R/reexports.R +++ /dev/null @@ -1,5 +0,0 @@ -#' @export -dplyr::vars - -#' @export -dplyr::desc diff --git a/R/warnings.R b/R/warnings.R index 960c9a72..0751b9c4 100644 --- a/R/warnings.R +++ b/R/warnings.R @@ -149,6 +149,8 @@ warn_if_incomplete_dtc <- function(dtc, n) { #' @export #' #' @examples +#' library(dplyr) +#' #' # no warning #' warn_if_inconsistent_list( #' base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ), diff --git a/man/add_suffix_to_vars.Rd b/man/add_suffix_to_vars.Rd index 098f11a3..0aef077a 100644 --- a/man/add_suffix_to_vars.Rd +++ b/man/add_suffix_to_vars.Rd @@ -28,6 +28,7 @@ added to every symbol specified for \code{vars} Add a suffix to variables in a list of quosures } \examples{ +library(dplyr) add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") } \seealso{ diff --git a/man/assert_data_frame.Rd b/man/assert_data_frame.Rd index eaabc58a..23291bd3 100644 --- a/man/assert_data_frame.Rd +++ b/man/assert_data_frame.Rd @@ -32,6 +32,7 @@ a set of required variables } \examples{ library(admiral.test) +library(dplyr) data(admiral_dm) example_fun <- function(dataset) { diff --git a/man/assert_filter_cond.Rd b/man/assert_filter_cond.Rd index 1ed9d2d4..c132d146 100644 --- a/man/assert_filter_cond.Rd +++ b/man/assert_filter_cond.Rd @@ -24,12 +24,14 @@ functions like \code{subset} or \code{dplyr::filter}. } \examples{ library(admiral.test) +library(dplyr) +library(rlang) data(admiral_dm) # typical usage in a function as a parameter check example_fun <- function(dat, x) { - x <- assert_filter_cond(rlang::enquo(x)) - dplyr::filter(dat, !!x) + x <- assert_filter_cond(enquo(x)) + filter(dat, !!x) } example_fun(admiral_dm, AGE == 64) diff --git a/man/assert_order_vars.Rd b/man/assert_order_vars.Rd index 0911a444..c9621031 100644 --- a/man/assert_order_vars.Rd +++ b/man/assert_order_vars.Rd @@ -20,6 +20,7 @@ calls created using \code{vars()} and returns the input invisibly otherwise. Checks if an argument is a valid list of order variables created using \code{vars()} } \examples{ +library(dplyr) example_fun <- function(by_vars) { assert_order_vars(by_vars) diff --git a/man/assert_symbol.Rd b/man/assert_symbol.Rd index c314d25b..18f5ea12 100644 --- a/man/assert_symbol.Rd +++ b/man/assert_symbol.Rd @@ -21,11 +21,13 @@ Checks if an argument is a symbol } \examples{ library(admiral.test) +library(dplyr) +library(rlang) data(admiral_dm) example_fun <- function(dat, var) { - var <- assert_symbol(rlang::enquo(var)) - dplyr::select(dat, !!var) + var <- assert_symbol(enquo(var)) + select(dat, !!var) } example_fun(admiral_dm, USUBJID) diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index 34de92de..99c1c287 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -23,6 +23,8 @@ and returns the input invisibly otherwise. Checks if an argument is a valid list of variables created using \code{vars()} } \examples{ +library(dplyr) + example_fun <- function(by_vars) { assert_vars(by_vars) } diff --git a/man/assert_varval_list.Rd b/man/assert_varval_list.Rd index b134c59b..bee4d4fe 100644 --- a/man/assert_varval_list.Rd +++ b/man/assert_varval_list.Rd @@ -35,6 +35,8 @@ variable-value pairs. The value can be a symbol, a string, a numeric, or \code{NA}. More general expression are not allowed. } \examples{ +library(dplyr) + example_fun <- function(vars) { assert_varval_list(vars) } diff --git a/man/negate_vars.Rd b/man/negate_vars.Rd index dfd0c25b..6d09c349 100644 --- a/man/negate_vars.Rd +++ b/man/negate_vars.Rd @@ -20,6 +20,8 @@ This is useful if a list of variables should be removed from a dataset, e.g., \code{select(!!!negate_vars(by_vars))} removes all by variables. } \examples{ +library(dplyr) + negate_vars(vars(USUBJID, STUDYID)) } \seealso{ diff --git a/man/reexports.Rd b/man/reexports.Rd deleted file mode 100644 index af251f24..00000000 --- a/man/reexports.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reexports.R -\docType{import} -\name{reexports} -\alias{reexports} -\alias{vars} -\alias{desc} -\title{Objects exported from other packages} -\keyword{internal} -\description{ -These objects are imported from other packages. Follow the links -below to see their documentation. - -\describe{ - \item{dplyr}{\code{\link[dplyr]{desc}}, \code{\link[dplyr]{vars}}} -}} - diff --git a/man/vars2chr.Rd b/man/vars2chr.Rd index 654bc8f2..23205bba 100644 --- a/man/vars2chr.Rd +++ b/man/vars2chr.Rd @@ -16,6 +16,8 @@ A character vector Turn a List of Quosures into a Character Vector } \examples{ +library(dplyr) + vars2chr(vars(USUBJID, AVAL)) } \seealso{ diff --git a/man/warn_if_inconsistent_list.Rd b/man/warn_if_inconsistent_list.Rd index 01f50ab8..863a1a8d 100644 --- a/man/warn_if_inconsistent_list.Rd +++ b/man/warn_if_inconsistent_list.Rd @@ -24,6 +24,8 @@ Checks if two list inputs have the same names and same number of elements and issues a warning otherwise. } \examples{ +library(dplyr) + # no warning warn_if_inconsistent_list( base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ), From edff9d8595624822d877e6ea3de4b56b1ea1e1e0 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 18 Nov 2022 19:13:21 +0000 Subject: [PATCH 151/179] #179 - Set up admiraldev_environment --- R/admiraldev_environment.R | 4 ++++ R/datasets.R | 8 +++----- man/set_dataset.Rd | 4 ++-- vignettes/programming_strategy.Rmd | 2 ++ 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 R/admiraldev_environment.R diff --git a/R/admiraldev_environment.R b/R/admiraldev_environment.R new file mode 100644 index 00000000..02f35ad6 --- /dev/null +++ b/R/admiraldev_environment.R @@ -0,0 +1,4 @@ +admiraldev_environment <- new.env(parent = emptyenv()) + +# datasets.R +# Used for get_dataset()/set_dataset() diff --git a/R/datasets.R b/R/datasets.R index a0371214..c688cc24 100644 --- a/R/datasets.R +++ b/R/datasets.R @@ -1,6 +1,4 @@ -.datasets <- new.env(parent = emptyenv()) - -#' Set a Dataset in the `.datasets` environment +#' Set a Dataset in the `admiraldev_environment` environment #' #' @param dataset A `data.frame` #' @param name A name for `dataset` @@ -22,7 +20,7 @@ set_dataset <- function(dataset, name) { assert_data_frame(dataset, check_is_grouped = FALSE) assert_character_scalar(name) - .datasets[[name]] <- dataset + admiraldev_environment[[name]] <- dataset } #' Retrieve a Dataset from the `.datasets` environment @@ -40,5 +38,5 @@ set_dataset <- function(dataset, name) { get_dataset <- function(name) { assert_character_scalar(name) - .datasets[[name]] + admiraldev_environment[[name]] } diff --git a/man/set_dataset.Rd b/man/set_dataset.Rd index dc40bddd..1c0ae25d 100644 --- a/man/set_dataset.Rd +++ b/man/set_dataset.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/datasets.R \name{set_dataset} \alias{set_dataset} -\title{Set a Dataset in the \code{.datasets} environment} +\title{Set a Dataset in the \code{admiraldev_environment} environment} \usage{ set_dataset(dataset, name) } @@ -15,7 +15,7 @@ set_dataset(dataset, name) No return value, called for side effects } \description{ -Set a Dataset in the \code{.datasets} environment +Set a Dataset in the \code{admiraldev_environment} environment } \details{ The object passed to the \code{dataset} argument will be assigned to \code{name} in diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 589553f9..b5f7623d 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -89,6 +89,8 @@ is restricted, otherwise this step is skipped. on `BASE` or based on `ANRIND`. It should not be implemented within one function as the algorithms are completely different. If `BASE` is used, the values are categorized while if `ANRIND` is used, the values are merged from the baseline observation. + * If developers find the need to use or create *environment* objects to achieve flexibility, use the `admiral_environment` environment object created in `admiral_environment.R`. All objects which are stored in this environment must be documented in `admiral_environment.R`. An equivalent environment object and `.R` file exist for admiraldev as well. For more details how environments work, please refer to this [chapter](https://r-pkgs.org/data.html#sec-data-state) in the latest edition of the R Packages book. + ## Input, Output, and Side-effects * The behavior of the function is only determined by its input, not by any global object, From d8f52c33b5f4c8ea1db980d996bd22177ab29e5d Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 18 Nov 2022 19:32:01 +0000 Subject: [PATCH 152/179] #179 - Swap out remaining ".datasets" reference to "admiraldev_environment" --- R/datasets.R | 4 ++-- _pkgdown.yml | 2 +- man/get_dataset.Rd | 4 ++-- man/set_dataset.Rd | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/datasets.R b/R/datasets.R index c688cc24..12770836 100644 --- a/R/datasets.R +++ b/R/datasets.R @@ -13,7 +13,7 @@ #' #' @details #' The object passed to the `dataset` argument will be assigned to `name` in -#' the `.datasets` environment. It can be retrieved later on using [get_dataset()] +#' the `admiraldev_environment` environment. It can be retrieved later on using [get_dataset()] #' #' @export set_dataset <- function(dataset, name) { @@ -23,7 +23,7 @@ set_dataset <- function(dataset, name) { admiraldev_environment[[name]] <- dataset } -#' Retrieve a Dataset from the `.datasets` environment +#' Retrieve a Dataset from the `admiraldev_environment` environment #' #' @param name The name of the dataset to retrieve #' diff --git a/_pkgdown.yml b/_pkgdown.yml index fb594647..30c58bb0 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -50,7 +50,7 @@ reference: - has_keyword('tmp_vars') - title: Getting and Setting Datasets - desc: Assign or retrieve a dataset into/from the `.datasets` enironment + desc: Assign or retrieve a dataset into/from the `admiraldev_environment` enironment contents: - has_keyword('datasets') diff --git a/man/get_dataset.Rd b/man/get_dataset.Rd index 75b52e21..dddf5d89 100644 --- a/man/get_dataset.Rd +++ b/man/get_dataset.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/datasets.R \name{get_dataset} \alias{get_dataset} -\title{Retrieve a Dataset from the \code{.datasets} environment} +\title{Retrieve a Dataset from the \code{admiraldev_environment} environment} \usage{ get_dataset(name) } @@ -13,7 +13,7 @@ get_dataset(name) A \code{data.frame} } \description{ -Retrieve a Dataset from the \code{.datasets} environment +Retrieve a Dataset from the \code{admiraldev_environment} environment } \seealso{ Other datasets: diff --git a/man/set_dataset.Rd b/man/set_dataset.Rd index 1c0ae25d..da6ad22c 100644 --- a/man/set_dataset.Rd +++ b/man/set_dataset.Rd @@ -19,7 +19,7 @@ Set a Dataset in the \code{admiraldev_environment} environment } \details{ The object passed to the \code{dataset} argument will be assigned to \code{name} in -the \code{.datasets} environment. It can be retrieved later on using \code{\link[=get_dataset]{get_dataset()}} +the \code{admiraldev_environment} environment. It can be retrieved later on using \code{\link[=get_dataset]{get_dataset()}} } \seealso{ Other datasets: From 59d7618f61d279910a85c4f30186c25ecb857193 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 18 Nov 2022 20:13:07 +0000 Subject: [PATCH 153/179] #179 swap out admiraldev_environment for admiral_environment --- R/admiraldev_environment.R | 2 +- R/datasets.R | 10 +++++----- _pkgdown.yml | 2 +- man/get_dataset.Rd | 4 ++-- man/set_dataset.Rd | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/R/admiraldev_environment.R b/R/admiraldev_environment.R index 02f35ad6..195b9db0 100644 --- a/R/admiraldev_environment.R +++ b/R/admiraldev_environment.R @@ -1,4 +1,4 @@ -admiraldev_environment <- new.env(parent = emptyenv()) +admiral_environment <- new.env(parent = emptyenv()) # datasets.R # Used for get_dataset()/set_dataset() diff --git a/R/datasets.R b/R/datasets.R index 12770836..5c90a648 100644 --- a/R/datasets.R +++ b/R/datasets.R @@ -1,4 +1,4 @@ -#' Set a Dataset in the `admiraldev_environment` environment +#' Set a Dataset in the `admiral_environment` environment #' #' @param dataset A `data.frame` #' @param name A name for `dataset` @@ -13,17 +13,17 @@ #' #' @details #' The object passed to the `dataset` argument will be assigned to `name` in -#' the `admiraldev_environment` environment. It can be retrieved later on using [get_dataset()] +#' the `admiral_environment` environment. It can be retrieved later on using [get_dataset()] #' #' @export set_dataset <- function(dataset, name) { assert_data_frame(dataset, check_is_grouped = FALSE) assert_character_scalar(name) - admiraldev_environment[[name]] <- dataset + admiral_environment[[name]] <- dataset } -#' Retrieve a Dataset from the `admiraldev_environment` environment +#' Retrieve a Dataset from the `admiral_environment` environment #' #' @param name The name of the dataset to retrieve #' @@ -38,5 +38,5 @@ set_dataset <- function(dataset, name) { get_dataset <- function(name) { assert_character_scalar(name) - admiraldev_environment[[name]] + admiral_environment[[name]] } diff --git a/_pkgdown.yml b/_pkgdown.yml index 30c58bb0..f12c4abc 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -50,7 +50,7 @@ reference: - has_keyword('tmp_vars') - title: Getting and Setting Datasets - desc: Assign or retrieve a dataset into/from the `admiraldev_environment` enironment + desc: Assign or retrieve a dataset into/from the `admiral_environment` enironment contents: - has_keyword('datasets') diff --git a/man/get_dataset.Rd b/man/get_dataset.Rd index dddf5d89..79d27d1c 100644 --- a/man/get_dataset.Rd +++ b/man/get_dataset.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/datasets.R \name{get_dataset} \alias{get_dataset} -\title{Retrieve a Dataset from the \code{admiraldev_environment} environment} +\title{Retrieve a Dataset from the \code{admiral_environment} environment} \usage{ get_dataset(name) } @@ -13,7 +13,7 @@ get_dataset(name) A \code{data.frame} } \description{ -Retrieve a Dataset from the \code{admiraldev_environment} environment +Retrieve a Dataset from the \code{admiral_environment} environment } \seealso{ Other datasets: diff --git a/man/set_dataset.Rd b/man/set_dataset.Rd index da6ad22c..7a37676c 100644 --- a/man/set_dataset.Rd +++ b/man/set_dataset.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/datasets.R \name{set_dataset} \alias{set_dataset} -\title{Set a Dataset in the \code{admiraldev_environment} environment} +\title{Set a Dataset in the \code{admiral_environment} environment} \usage{ set_dataset(dataset, name) } @@ -15,11 +15,11 @@ set_dataset(dataset, name) No return value, called for side effects } \description{ -Set a Dataset in the \code{admiraldev_environment} environment +Set a Dataset in the \code{admiral_environment} environment } \details{ The object passed to the \code{dataset} argument will be assigned to \code{name} in -the \code{admiraldev_environment} environment. It can be retrieved later on using \code{\link[=get_dataset]{get_dataset()}} +the \code{admiral_environment} environment. It can be retrieved later on using \code{\link[=get_dataset]{get_dataset()}} } \seealso{ Other datasets: From 4548a48fa119802ca0e7bdeb07706948e1744a0b Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 18 Nov 2022 20:23:14 +0000 Subject: [PATCH 154/179] Remove library() from tests. --- tests/testthat/test-dev_utilities.R | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index c5a0e702..6137816e 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -26,9 +26,8 @@ test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct f ## Test 4: Input is returned as is if filter is NULL ---- test_that("convert_dtm_to_dtc Test 4: Input is returned as is if filter is NULL", { - library(tibble) - input <- tribble( + input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, "P01", "HEIGHT", 189.2 @@ -45,9 +44,8 @@ test_that("convert_dtm_to_dtc Test 4: Input is returned as is if filter is NULL" ## Test 5: Input is filtered if filter is not NULL ---- test_that("convert_dtm_to_dtc Test 5: Input is filtered if filter is not NULL", { - library(tibble) - input <- tribble( + input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, "P01", "HEIGHT", 189.2 From 6bd03d7bf85e6c740ae23b8da8e88f3519eec9bc Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Fri, 18 Nov 2022 20:41:14 +0000 Subject: [PATCH 155/179] #179 swap back to admiraldev_environment to test something --- R/admiraldev_environment.R | 2 +- R/datasets.R | 10 +++++----- _pkgdown.yml | 2 +- man/get_dataset.Rd | 4 ++-- man/set_dataset.Rd | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/R/admiraldev_environment.R b/R/admiraldev_environment.R index 195b9db0..02f35ad6 100644 --- a/R/admiraldev_environment.R +++ b/R/admiraldev_environment.R @@ -1,4 +1,4 @@ -admiral_environment <- new.env(parent = emptyenv()) +admiraldev_environment <- new.env(parent = emptyenv()) # datasets.R # Used for get_dataset()/set_dataset() diff --git a/R/datasets.R b/R/datasets.R index 5c90a648..12770836 100644 --- a/R/datasets.R +++ b/R/datasets.R @@ -1,4 +1,4 @@ -#' Set a Dataset in the `admiral_environment` environment +#' Set a Dataset in the `admiraldev_environment` environment #' #' @param dataset A `data.frame` #' @param name A name for `dataset` @@ -13,17 +13,17 @@ #' #' @details #' The object passed to the `dataset` argument will be assigned to `name` in -#' the `admiral_environment` environment. It can be retrieved later on using [get_dataset()] +#' the `admiraldev_environment` environment. It can be retrieved later on using [get_dataset()] #' #' @export set_dataset <- function(dataset, name) { assert_data_frame(dataset, check_is_grouped = FALSE) assert_character_scalar(name) - admiral_environment[[name]] <- dataset + admiraldev_environment[[name]] <- dataset } -#' Retrieve a Dataset from the `admiral_environment` environment +#' Retrieve a Dataset from the `admiraldev_environment` environment #' #' @param name The name of the dataset to retrieve #' @@ -38,5 +38,5 @@ set_dataset <- function(dataset, name) { get_dataset <- function(name) { assert_character_scalar(name) - admiral_environment[[name]] + admiraldev_environment[[name]] } diff --git a/_pkgdown.yml b/_pkgdown.yml index f12c4abc..30c58bb0 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -50,7 +50,7 @@ reference: - has_keyword('tmp_vars') - title: Getting and Setting Datasets - desc: Assign or retrieve a dataset into/from the `admiral_environment` enironment + desc: Assign or retrieve a dataset into/from the `admiraldev_environment` enironment contents: - has_keyword('datasets') diff --git a/man/get_dataset.Rd b/man/get_dataset.Rd index 79d27d1c..dddf5d89 100644 --- a/man/get_dataset.Rd +++ b/man/get_dataset.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/datasets.R \name{get_dataset} \alias{get_dataset} -\title{Retrieve a Dataset from the \code{admiral_environment} environment} +\title{Retrieve a Dataset from the \code{admiraldev_environment} environment} \usage{ get_dataset(name) } @@ -13,7 +13,7 @@ get_dataset(name) A \code{data.frame} } \description{ -Retrieve a Dataset from the \code{admiral_environment} environment +Retrieve a Dataset from the \code{admiraldev_environment} environment } \seealso{ Other datasets: diff --git a/man/set_dataset.Rd b/man/set_dataset.Rd index 7a37676c..da6ad22c 100644 --- a/man/set_dataset.Rd +++ b/man/set_dataset.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/datasets.R \name{set_dataset} \alias{set_dataset} -\title{Set a Dataset in the \code{admiral_environment} environment} +\title{Set a Dataset in the \code{admiraldev_environment} environment} \usage{ set_dataset(dataset, name) } @@ -15,11 +15,11 @@ set_dataset(dataset, name) No return value, called for side effects } \description{ -Set a Dataset in the \code{admiral_environment} environment +Set a Dataset in the \code{admiraldev_environment} environment } \details{ The object passed to the \code{dataset} argument will be assigned to \code{name} in -the \code{admiral_environment} environment. It can be retrieved later on using \code{\link[=get_dataset]{get_dataset()}} +the \code{admiraldev_environment} environment. It can be retrieved later on using \code{\link[=get_dataset]{get_dataset()}} } \seealso{ Other datasets: From 71be8f1afdc4fe1029ad0bfe4fbaba6761dc1ec4 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 18 Nov 2022 20:58:02 +0000 Subject: [PATCH 156/179] styler removed a couple of blank lines. --- tests/testthat/test-dev_utilities.R | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 6137816e..68d13a3e 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -26,7 +26,6 @@ test_that("convert_dtm_to_dtc Test 3: Error is thrown if dtm is not in correct f ## Test 4: Input is returned as is if filter is NULL ---- test_that("convert_dtm_to_dtc Test 4: Input is returned as is if filter is NULL", { - input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, @@ -44,7 +43,6 @@ test_that("convert_dtm_to_dtc Test 4: Input is returned as is if filter is NULL" ## Test 5: Input is filtered if filter is not NULL ---- test_that("convert_dtm_to_dtc Test 5: Input is filtered if filter is not NULL", { - input <- tibble::tribble( ~USUBJID, ~VSTESTCD, ~VSSTRESN, "P01", "WEIGHT", 80.9, From 3615a8f91064c5004eacdb71d94d5dc810f2f51c Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 21 Nov 2022 14:29:29 +0000 Subject: [PATCH 157/179] #83 - Migrate negate_vars to admiral, remove reexport documentation --- NAMESPACE | 1 - R/dev_utilities.R | 31 --------------------- man/arg_name.Rd | 1 - man/as_name.Rd | 1 - man/convert_dtm_to_dtc.Rd | 1 - man/extract_vars.Rd | 1 - man/filter_if.Rd | 1 - man/grapes-notin-grapes.Rd | 1 - man/grapes-or-grapes.Rd | 1 - man/negate_vars.Rd | 43 ------------------------------ man/valid_time_units.Rd | 1 - man/vars2chr.Rd | 1 - vignettes/programming_strategy.Rmd | 7 ----- 13 files changed, 91 deletions(-) delete mode 100644 man/negate_vars.Rd diff --git a/NAMESPACE b/NAMESPACE index fd25000d..da698efe 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -51,7 +51,6 @@ export(is_named) export(is_order_vars) export(is_valid_dtc) export(left_join) -export(negate_vars) export(quo_c) export(quo_not_missing) export(remove_tmp_vars) diff --git a/R/dev_utilities.R b/R/dev_utilities.R index 653c154d..015ba245 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -179,37 +179,6 @@ vars2chr <- function(quosures) { ) } -#' Negate List of Variables -#' -#' The function adds a minus sign as prefix to each variable. -#' -#' This is useful if a list of variables should be removed from a dataset, -#' e.g., `select(!!!negate_vars(by_vars))` removes all by variables. -#' -#' @param vars List of variables created by `vars()` -#' -#' @return A list of `quosures` -#' -#' @author Stefan Bundfuss -#' -#' @export -#' -#' @keywords dev_utility -#' @family dev_utility -#' -#' @examples -#' library(dplyr) -#' -#' negate_vars(vars(USUBJID, STUDYID)) -negate_vars <- function(vars = NULL) { - assert_vars(vars, optional = TRUE) - if (is.null(vars)) { - NULL - } else { - lapply(vars, function(var) expr(-!!quo_get_expr(var))) - } -} - #' Optional Filter #' #' Filters the input dataset if the provided expression is not `NULL` diff --git a/man/arg_name.Rd b/man/arg_name.Rd index 699734cf..7d5a4272 100644 --- a/man/arg_name.Rd +++ b/man/arg_name.Rd @@ -23,7 +23,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/as_name.Rd b/man/as_name.Rd index 70bd65ee..6221bf71 100644 --- a/man/as_name.Rd +++ b/man/as_name.Rd @@ -27,7 +27,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/convert_dtm_to_dtc.Rd b/man/convert_dtm_to_dtc.Rd index 59598efb..6737d47d 100644 --- a/man/convert_dtm_to_dtc.Rd +++ b/man/convert_dtm_to_dtc.Rd @@ -25,7 +25,6 @@ Developer Utility Functions: \code{\link{as_name}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/extract_vars.Rd b/man/extract_vars.Rd index 064f44a5..9bef33e8 100644 --- a/man/extract_vars.Rd +++ b/man/extract_vars.Rd @@ -25,7 +25,6 @@ Developer Utility Functions: \code{\link{as_name}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/filter_if.Rd b/man/filter_if.Rd index 01d766da..eec1692e 100644 --- a/man/filter_if.Rd +++ b/man/filter_if.Rd @@ -26,7 +26,6 @@ Developer Utility Functions: \code{\link{as_name}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/grapes-notin-grapes.Rd b/man/grapes-notin-grapes.Rd index 5df54a5a..ab4bf721 100644 --- a/man/grapes-notin-grapes.Rd +++ b/man/grapes-notin-grapes.Rd @@ -26,7 +26,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/grapes-or-grapes.Rd b/man/grapes-or-grapes.Rd index 2c3badb5..5ad124c1 100644 --- a/man/grapes-or-grapes.Rd +++ b/man/grapes-or-grapes.Rd @@ -30,7 +30,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/negate_vars.Rd b/man/negate_vars.Rd deleted file mode 100644 index 6d09c349..00000000 --- a/man/negate_vars.Rd +++ /dev/null @@ -1,43 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dev_utilities.R -\name{negate_vars} -\alias{negate_vars} -\title{Negate List of Variables} -\usage{ -negate_vars(vars = NULL) -} -\arguments{ -\item{vars}{List of variables created by \code{vars()}} -} -\value{ -A list of \code{quosures} -} -\description{ -The function adds a minus sign as prefix to each variable. -} -\details{ -This is useful if a list of variables should be removed from a dataset, -e.g., \code{select(!!!negate_vars(by_vars))} removes all by variables. -} -\examples{ -library(dplyr) - -negate_vars(vars(USUBJID, STUDYID)) -} -\seealso{ -Developer Utility Functions: -\code{\link{\%notin\%}()}, -\code{\link{\%or\%}()}, -\code{\link{arg_name}()}, -\code{\link{as_name}()}, -\code{\link{convert_dtm_to_dtc}()}, -\code{\link{extract_vars}()}, -\code{\link{filter_if}()}, -\code{\link{valid_time_units}()}, -\code{\link{vars2chr}()} -} -\author{ -Stefan Bundfuss -} -\concept{dev_utility} -\keyword{dev_utility} diff --git a/man/valid_time_units.Rd b/man/valid_time_units.Rd index e484592f..5af25c24 100644 --- a/man/valid_time_units.Rd +++ b/man/valid_time_units.Rd @@ -21,7 +21,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{vars2chr}()} } \concept{dev_utility} diff --git a/man/vars2chr.Rd b/man/vars2chr.Rd index 23205bba..d0bec2a8 100644 --- a/man/vars2chr.Rd +++ b/man/vars2chr.Rd @@ -29,7 +29,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()} } \author{ diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index e4ca9ea8..589553f9 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -475,13 +475,6 @@ Functions from other packages have to be explicitly imported by using the `@impo To import the `if_else()` and `mutate()` function from `dplyr` the following line would have to be included in that file: `#' @importFrom dplyr if_else mutate`. -Some of these functions become critically important while using admiral, such as `vars()` from `dplyr`, and should be included as a export. This may also include functions that are frequently called in roxygen examples. To export these functions, the following R code should be included in the `R/reexports.R` file using the format: - -``` -#' @export -pkg_name::fun -``` - # Metadata From 39ad2f34f9c6663f5f85ca4a0be087072c04eb88 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 21 Nov 2022 14:35:17 +0000 Subject: [PATCH 158/179] #83 - account for negate_vars from testthat --- tests/testthat/test-dev_utilities.R | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 68d13a3e..3a4a0074 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -57,14 +57,3 @@ test_that("convert_dtm_to_dtc Test 5: Input is filtered if filter is not NULL", keys = c("USUBJID", "VSTESTCD") ) }) - -# negate_vars ---- -## Test 6: negate_vars returns list of negated variables ---- -test_that("negate_vars Test 6: negate_vars returns list of negated variables", { - expect_identical(negate_vars(vars(var1, var2)), rlang::exprs(-var1, -var2)) -}) - -## Test 7: negate_vars returns NULL if input is NULL ---- -test_that("negate_vars Test 7: negate_vars returns NULL if input is NULL", { - expect_identical(negate_vars(NULL), NULL) -}) From d9f607f4a4aeada309bf9a6bfe8043e18d64a8a1 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 21 Nov 2022 18:18:56 +0000 Subject: [PATCH 159/179] #179 - Move the documentation and add extra book URL --- vignettes/programming_strategy.Rmd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index b5f7623d..44a153b5 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -89,8 +89,6 @@ is restricted, otherwise this step is skipped. on `BASE` or based on `ANRIND`. It should not be implemented within one function as the algorithms are completely different. If `BASE` is used, the values are categorized while if `ANRIND` is used, the values are merged from the baseline observation. - * If developers find the need to use or create *environment* objects to achieve flexibility, use the `admiral_environment` environment object created in `admiral_environment.R`. All objects which are stored in this environment must be documented in `admiral_environment.R`. An equivalent environment object and `.R` file exist for admiraldev as well. For more details how environments work, please refer to this [chapter](https://r-pkgs.org/data.html#sec-data-state) in the latest edition of the R Packages book. - ## Input, Output, and Side-effects * The behavior of the function is only determined by its input, not by any global object, @@ -102,6 +100,7 @@ i.e. all input like datasets, variable names, options, … must be provided to t * If the function needs to create temporary variables in an input dataset, these variables must start with `temp_` and must be removed from the output dataset. * If the input dataset includes variables starting with `temp_`, an error must be issued. +* If developers find the need to use or create *environment* objects to achieve flexibility, use the `admiral_environment` environment object created in `admiral_environment.R`. All objects which are stored in this environment must be documented in `admiral_environment.R`. An equivalent environment object and `.R` file exist for admiraldev as well. For more details how environments work, please refer to [Chapter 8](https://r-pkgs.org/data.html#sec-data-state) of the latest edition of the R Packages book and [Chapter 7](https://adv-r.hadley.nz/environments.html) of the latest edition of the Advanced R book. * In general, the function must not have any side-effects like creating or modifying global objects, printing, writing files, ... ## Admiral Options From d7e661c43828acfe323fd5c1fff4ed3d547e37e5 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 22 Nov 2022 15:04:00 +0000 Subject: [PATCH 160/179] #179 - address many_to_one and one_to_many --- NAMESPACE | 1 - R/admiraldev_environment.R | 5 +++-- R/assertions.R | 4 ++-- R/datasets.R | 27 +-------------------------- man/get_dataset.Rd | 4 ---- man/set_dataset.Rd | 32 -------------------------------- 6 files changed, 6 insertions(+), 67 deletions(-) delete mode 100644 man/set_dataset.Rd diff --git a/NAMESPACE b/NAMESPACE index ea617edb..e7604de8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -58,7 +58,6 @@ export(quo_not_missing) export(remove_tmp_vars) export(replace_symbol_in_quo) export(replace_values_by_names) -export(set_dataset) export(squote) export(suppress_warning) export(valid_time_units) diff --git a/R/admiraldev_environment.R b/R/admiraldev_environment.R index 02f35ad6..3b05ef13 100644 --- a/R/admiraldev_environment.R +++ b/R/admiraldev_environment.R @@ -1,4 +1,5 @@ admiraldev_environment <- new.env(parent = emptyenv()) -# datasets.R -# Used for get_dataset()/set_dataset() +# assertions.R +admiraldev_environment$many_to_one <- NULL +admiraldev_environment$one_to_many <- NULL diff --git a/R/assertions.R b/R/assertions.R index aa9456a4..b7ff6507 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1396,7 +1396,7 @@ assert_one_to_one <- function(dataset, vars1, vars2) { filter(n() > 1) %>% arrange(!!!vars1) if (nrow(one_to_many) > 0) { - set_dataset(one_to_many, "one_to_many") + admiraldev_environment$one_to_many <- one_to_many abort( paste0( "For some values of ", @@ -1412,7 +1412,7 @@ assert_one_to_one <- function(dataset, vars1, vars2) { filter(n() > 1) %>% arrange(!!!vars2) if (nrow(many_to_one) > 0) { - set_dataset(many_to_one, "many_to_one") + admiraldev_environment$many_to_one <- many_to_one abort( paste0( "There is more than one value of ", diff --git a/R/datasets.R b/R/datasets.R index 12770836..0b4a84b5 100644 --- a/R/datasets.R +++ b/R/datasets.R @@ -1,28 +1,3 @@ -#' Set a Dataset in the `admiraldev_environment` environment -#' -#' @param dataset A `data.frame` -#' @param name A name for `dataset` -#' -#' @return -#' No return value, called for side effects -#' -#' @author Thomas Neitmann -#' -#' @keywords datasets -#' @family datasets -#' -#' @details -#' The object passed to the `dataset` argument will be assigned to `name` in -#' the `admiraldev_environment` environment. It can be retrieved later on using [get_dataset()] -#' -#' @export -set_dataset <- function(dataset, name) { - assert_data_frame(dataset, check_is_grouped = FALSE) - assert_character_scalar(name) - - admiraldev_environment[[name]] <- dataset -} - #' Retrieve a Dataset from the `admiraldev_environment` environment #' #' @param name The name of the dataset to retrieve @@ -36,7 +11,7 @@ set_dataset <- function(dataset, name) { #' #' @export get_dataset <- function(name) { - assert_character_scalar(name) + assert_character_scalar(name, values = c("one_to_many", "many_to_one")) admiraldev_environment[[name]] } diff --git a/man/get_dataset.Rd b/man/get_dataset.Rd index dddf5d89..d9795738 100644 --- a/man/get_dataset.Rd +++ b/man/get_dataset.Rd @@ -15,10 +15,6 @@ A \code{data.frame} \description{ Retrieve a Dataset from the \code{admiraldev_environment} environment } -\seealso{ -Other datasets: -\code{\link{set_dataset}()} -} \author{ Thomas Neitmann } diff --git a/man/set_dataset.Rd b/man/set_dataset.Rd deleted file mode 100644 index da6ad22c..00000000 --- a/man/set_dataset.Rd +++ /dev/null @@ -1,32 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/datasets.R -\name{set_dataset} -\alias{set_dataset} -\title{Set a Dataset in the \code{admiraldev_environment} environment} -\usage{ -set_dataset(dataset, name) -} -\arguments{ -\item{dataset}{A \code{data.frame}} - -\item{name}{A name for \code{dataset}} -} -\value{ -No return value, called for side effects -} -\description{ -Set a Dataset in the \code{admiraldev_environment} environment -} -\details{ -The object passed to the \code{dataset} argument will be assigned to \code{name} in -the \code{admiraldev_environment} environment. It can be retrieved later on using \code{\link[=get_dataset]{get_dataset()}} -} -\seealso{ -Other datasets: -\code{\link{get_dataset}()} -} -\author{ -Thomas Neitmann -} -\concept{datasets} -\keyword{datasets} From 0a6833bc52f799b5cadd688d1358562f87161718 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 23 Nov 2022 15:22:14 +0000 Subject: [PATCH 161/179] #83 - Add magrittr pipe to reexports --- NAMESPACE | 1 + R/reexports.R | 3 +++ man/reexports.Rd | 3 +++ 3 files changed, 7 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 3b28f9e7..e5f2cc73 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export("%>%") export("%notin%") export("%or%") export(add_suffix_to_vars) diff --git a/R/reexports.R b/R/reexports.R index 9d41576a..f03e9cb5 100644 --- a/R/reexports.R +++ b/R/reexports.R @@ -3,3 +3,6 @@ dplyr::vars #' @export dplyr::desc + +#' @export +magrittr::`%>%` diff --git a/man/reexports.Rd b/man/reexports.Rd index af251f24..eb33c97e 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -5,6 +5,7 @@ \alias{reexports} \alias{vars} \alias{desc} +\alias{\%>\%} \title{Objects exported from other packages} \keyword{internal} \description{ @@ -13,5 +14,7 @@ below to see their documentation. \describe{ \item{dplyr}{\code{\link[dplyr]{desc}}, \code{\link[dplyr]{vars}}} + + \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} }} From 39c136cdb319d359ed966a2608ca4196d25e92e7 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 23 Nov 2022 15:38:06 +0000 Subject: [PATCH 162/179] #83 - add magrittr pipe --- NAMESPACE | 2 ++ R/reexports.R | 8 ++++++++ man/reexports.Rd | 20 ++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 R/reexports.R create mode 100644 man/reexports.Rd diff --git a/NAMESPACE b/NAMESPACE index b0b727d5..48f19456 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -36,6 +36,7 @@ export(assert_varval_list) export(backquote) export(convert_dtm_to_dtc) export(dataset_vignette) +export(desc) export(dquote) export(enumerate) export(expect_dfs_equal) @@ -61,6 +62,7 @@ export(set_dataset) export(squote) export(suppress_warning) export(valid_time_units) +export(vars) export(vars2chr) export(warn_if_incomplete_dtc) export(warn_if_inconsistent_list) diff --git a/R/reexports.R b/R/reexports.R new file mode 100644 index 00000000..f03e9cb5 --- /dev/null +++ b/R/reexports.R @@ -0,0 +1,8 @@ +#' @export +dplyr::vars + +#' @export +dplyr::desc + +#' @export +magrittr::`%>%` diff --git a/man/reexports.Rd b/man/reexports.Rd new file mode 100644 index 00000000..eb33c97e --- /dev/null +++ b/man/reexports.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/reexports.R +\docType{import} +\name{reexports} +\alias{reexports} +\alias{vars} +\alias{desc} +\alias{\%>\%} +\title{Objects exported from other packages} +\keyword{internal} +\description{ +These objects are imported from other packages. Follow the links +below to see their documentation. + +\describe{ + \item{dplyr}{\code{\link[dplyr]{desc}}, \code{\link[dplyr]{vars}}} + + \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} +}} + From 864ff67f1aa44e4bbaf2124b01bb7120f3083d42 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 23 Nov 2022 15:41:59 +0000 Subject: [PATCH 163/179] #83 - add back programming strategy blurb --- vignettes/programming_strategy.Rmd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 589553f9..993c0c6a 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -475,6 +475,12 @@ Functions from other packages have to be explicitly imported by using the `@impo To import the `if_else()` and `mutate()` function from `dplyr` the following line would have to be included in that file: `#' @importFrom dplyr if_else mutate`. +Some of these functions become critically important while using admiral and should be included as an export. This applies to functions which are frequently called within admiral function calls like `vars()`, `desc()` or the pipe operator `%>%`. To export these functions, the following R code should be included in the `R/reexports.R` file using the format: + +``` +#' @export +pkg_name::fun +``` # Metadata From a0e2c72b6391057aa873af62050c82f3342a86a6 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 23 Nov 2022 15:49:08 +0000 Subject: [PATCH 164/179] #83 - clean up roxygen examples --- R/assertions.R | 20 ++++++++++---------- R/dev_utilities.R | 2 +- R/quo.R | 1 - R/tmp_vars.R | 2 +- R/warnings.R | 2 -- man/add_suffix_to_vars.Rd | 1 - man/assert_data_frame.Rd | 4 ++-- man/assert_filter_cond.Rd | 2 +- man/assert_order_vars.Rd | 5 +++-- man/assert_symbol.Rd | 2 +- man/assert_vars.Rd | 5 +++-- man/assert_varval_list.Rd | 2 -- man/remove_tmp_vars.Rd | 2 +- man/vars2chr.Rd | 2 +- man/warn_if_inconsistent_list.Rd | 2 -- vignettes/programming_strategy.Rmd | 2 +- 16 files changed, 25 insertions(+), 31 deletions(-) diff --git a/R/assertions.R b/R/assertions.R index 7a15a17a..a7335142 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -23,7 +23,7 @@ #' #' @examples #' library(admiral.test) -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) #' data(admiral_dm) #' #' example_fun <- function(dataset) { @@ -32,7 +32,7 @@ #' #' example_fun(admiral_dm) #' -#' try(example_fun(dplyr::select(admiral_dm, -STUDYID))) +#' try(example_fun(select(admiral_dm, -STUDYID))) #' #' try(example_fun("Not a dataset")) assert_data_frame <- function(arg, @@ -324,7 +324,7 @@ assert_logical_scalar <- function(arg, optional = FALSE) { #' @family assertion #' @examples #' library(admiral.test) -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) #' library(rlang) #' data(admiral_dm) #' @@ -418,7 +418,7 @@ assert_expr <- function(arg, optional = FALSE) { #' #' @examples #' library(admiral.test) -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) #' library(rlang) #' data(admiral_dm) #' @@ -480,7 +480,8 @@ assert_filter_cond <- function(arg, optional = FALSE) { #' @keywords assertion #' @family assertion #' @examples -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) +#' library(rlang) #' #' example_fun <- function(by_vars) { #' assert_vars(by_vars) @@ -488,7 +489,7 @@ assert_filter_cond <- function(arg, optional = FALSE) { #' #' example_fun(vars(USUBJID, PARAMCD)) #' -#' try(example_fun(rlang::exprs(USUBJID, PARAMCD))) +#' try(example_fun(exprs(USUBJID, PARAMCD))) #' #' try(example_fun(c("USUBJID", "PARAMCD", "VISIT"))) #' @@ -566,7 +567,8 @@ assert_vars <- function(arg, optional = FALSE, expect_names = FALSE) { #' @keywords assertion #' @family assertion #' @examples -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) +#' library(rlang) #' #' example_fun <- function(by_vars) { #' assert_order_vars(by_vars) @@ -574,7 +576,7 @@ assert_vars <- function(arg, optional = FALSE, expect_names = FALSE) { #' #' example_fun(vars(USUBJID, PARAMCD, desc(AVISITN))) #' -#' try(example_fun(rlang::exprs(USUBJID, PARAMCD))) +#' try(example_fun(exprs(USUBJID, PARAMCD))) #' #' try(example_fun(c("USUBJID", "PARAMCD", "VISIT"))) #' @@ -1191,8 +1193,6 @@ assert_param_does_not_exist <- function(dataset, param) { #' @export #' #' @examples -#' library(dplyr) -#' #' example_fun <- function(vars) { #' assert_varval_list(vars) #' } diff --git a/R/dev_utilities.R b/R/dev_utilities.R index 015ba245..03987d25 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -169,7 +169,7 @@ contains_vars <- function(arg) { #' @family dev_utility #' #' @examples -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) #' #' vars2chr(vars(USUBJID, AVAL)) vars2chr <- function(quosures) { diff --git a/R/quo.R b/R/quo.R index a0f4b805..67dfe628 100644 --- a/R/quo.R +++ b/R/quo.R @@ -143,7 +143,6 @@ replace_symbol_in_quo <- function(quosure, #' @export #' #' @examples -#' library(dplyr) #' add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") add_suffix_to_vars <- function(order, vars, diff --git a/R/tmp_vars.R b/R/tmp_vars.R index b0f37315..72deb84a 100644 --- a/R/tmp_vars.R +++ b/R/tmp_vars.R @@ -68,7 +68,7 @@ get_new_tmp_var <- function(dataset, prefix = "tmp_var") { #' The input dataset with temporary variables removed #' #' @examples -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) #' library(admiral.test) #' data(admiral_dm) #' dm <- select(admiral_dm, USUBJID) diff --git a/R/warnings.R b/R/warnings.R index 0751b9c4..960c9a72 100644 --- a/R/warnings.R +++ b/R/warnings.R @@ -149,8 +149,6 @@ warn_if_incomplete_dtc <- function(dtc, n) { #' @export #' #' @examples -#' library(dplyr) -#' #' # no warning #' warn_if_inconsistent_list( #' base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ), diff --git a/man/add_suffix_to_vars.Rd b/man/add_suffix_to_vars.Rd index 0aef077a..098f11a3 100644 --- a/man/add_suffix_to_vars.Rd +++ b/man/add_suffix_to_vars.Rd @@ -28,7 +28,6 @@ added to every symbol specified for \code{vars} Add a suffix to variables in a list of quosures } \examples{ -library(dplyr) add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") } \seealso{ diff --git a/man/assert_data_frame.Rd b/man/assert_data_frame.Rd index 54e14495..a2faa0d2 100644 --- a/man/assert_data_frame.Rd +++ b/man/assert_data_frame.Rd @@ -32,7 +32,7 @@ a set of required variables } \examples{ library(admiral.test) -library(dplyr) +library(dplyr, warn.conflicts = FALSE) data(admiral_dm) example_fun <- function(dataset) { @@ -41,7 +41,7 @@ example_fun <- function(dataset) { example_fun(admiral_dm) -try(example_fun(dplyr::select(admiral_dm, -STUDYID))) +try(example_fun(select(admiral_dm, -STUDYID))) try(example_fun("Not a dataset")) } diff --git a/man/assert_filter_cond.Rd b/man/assert_filter_cond.Rd index 3480c3f5..5abd7f69 100644 --- a/man/assert_filter_cond.Rd +++ b/man/assert_filter_cond.Rd @@ -24,7 +24,7 @@ functions like \code{subset} or \code{dplyr::filter}. } \examples{ library(admiral.test) -library(dplyr) +library(dplyr, warn.conflicts = FALSE) library(rlang) data(admiral_dm) diff --git a/man/assert_order_vars.Rd b/man/assert_order_vars.Rd index c1f30a56..52cd087a 100644 --- a/man/assert_order_vars.Rd +++ b/man/assert_order_vars.Rd @@ -20,7 +20,8 @@ calls created using \code{vars()} and returns the input invisibly otherwise. Checks if an argument is a valid list of order variables created using \code{vars()} } \examples{ -library(dplyr) +library(dplyr, warn.conflicts = FALSE) +library(rlang) example_fun <- function(by_vars) { assert_order_vars(by_vars) @@ -28,7 +29,7 @@ example_fun <- function(by_vars) { example_fun(vars(USUBJID, PARAMCD, desc(AVISITN))) -try(example_fun(rlang::exprs(USUBJID, PARAMCD))) +try(example_fun(exprs(USUBJID, PARAMCD))) try(example_fun(c("USUBJID", "PARAMCD", "VISIT"))) diff --git a/man/assert_symbol.Rd b/man/assert_symbol.Rd index a547916c..dd1a8a52 100644 --- a/man/assert_symbol.Rd +++ b/man/assert_symbol.Rd @@ -21,7 +21,7 @@ Checks if an argument is a symbol } \examples{ library(admiral.test) -library(dplyr) +library(dplyr, warn.conflicts = FALSE) library(rlang) data(admiral_dm) diff --git a/man/assert_vars.Rd b/man/assert_vars.Rd index 72b280b2..99dc150f 100644 --- a/man/assert_vars.Rd +++ b/man/assert_vars.Rd @@ -23,7 +23,8 @@ and returns the input invisibly otherwise. Checks if an argument is a valid list of variables created using \code{vars()} } \examples{ -library(dplyr) +library(dplyr, warn.conflicts = FALSE) +library(rlang) example_fun <- function(by_vars) { assert_vars(by_vars) @@ -31,7 +32,7 @@ example_fun <- function(by_vars) { example_fun(vars(USUBJID, PARAMCD)) -try(example_fun(rlang::exprs(USUBJID, PARAMCD))) +try(example_fun(exprs(USUBJID, PARAMCD))) try(example_fun(c("USUBJID", "PARAMCD", "VISIT"))) diff --git a/man/assert_varval_list.Rd b/man/assert_varval_list.Rd index b752964e..796d9a7d 100644 --- a/man/assert_varval_list.Rd +++ b/man/assert_varval_list.Rd @@ -35,8 +35,6 @@ variable-value pairs. The value can be a symbol, a string, a numeric, or \code{NA}. More general expression are not allowed. } \examples{ -library(dplyr) - example_fun <- function(vars) { assert_varval_list(vars) } diff --git a/man/remove_tmp_vars.Rd b/man/remove_tmp_vars.Rd index 06c4b0f1..b8275256 100644 --- a/man/remove_tmp_vars.Rd +++ b/man/remove_tmp_vars.Rd @@ -16,7 +16,7 @@ The input dataset with temporary variables removed Remove All Temporary Variables Created Within the Current Function Environment } \examples{ -library(dplyr) +library(dplyr, warn.conflicts = FALSE) library(admiral.test) data(admiral_dm) dm <- select(admiral_dm, USUBJID) diff --git a/man/vars2chr.Rd b/man/vars2chr.Rd index d0bec2a8..ec6cd459 100644 --- a/man/vars2chr.Rd +++ b/man/vars2chr.Rd @@ -16,7 +16,7 @@ A character vector Turn a List of Quosures into a Character Vector } \examples{ -library(dplyr) +library(dplyr, warn.conflicts = FALSE) vars2chr(vars(USUBJID, AVAL)) } diff --git a/man/warn_if_inconsistent_list.Rd b/man/warn_if_inconsistent_list.Rd index 863a1a8d..01f50ab8 100644 --- a/man/warn_if_inconsistent_list.Rd +++ b/man/warn_if_inconsistent_list.Rd @@ -24,8 +24,6 @@ Checks if two list inputs have the same names and same number of elements and issues a warning otherwise. } \examples{ -library(dplyr) - # no warning warn_if_inconsistent_list( base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ), diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 993c0c6a..292a3a26 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -316,7 +316,7 @@ An example is given below: #' #' @examples #' library(lubridate) -#' library(dplyr) +#' library(dplyr, warn.conflicts = FALSE) #' library(tibble) #' #' datain <- tribble( From 2881a8a980a8256495805aa0f057c9535847639d Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 23 Nov 2022 17:32:15 +0000 Subject: [PATCH 165/179] #179 - Add comment about how get_dataset retrieves the objects created --- R/admiraldev_environment.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/admiraldev_environment.R b/R/admiraldev_environment.R index 3b05ef13..fc42f0ae 100644 --- a/R/admiraldev_environment.R +++ b/R/admiraldev_environment.R @@ -3,3 +3,6 @@ admiraldev_environment <- new.env(parent = emptyenv()) # assertions.R admiraldev_environment$many_to_one <- NULL admiraldev_environment$one_to_many <- NULL + +# datasets.R +# get_dataset() is used to retrieve many_to_one and one_to_many From f28239db39c95de6aa7075321394ee336f2568f1 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Wed, 23 Nov 2022 19:03:53 +0000 Subject: [PATCH 166/179] closes #39 added documentation for contains_vars function --- NAMESPACE | 1 + R/dev_utilities.R | 10 +++++++++ man/arg_name.Rd | 1 + man/as_name.Rd | 1 + man/contains_vars.Rd | 32 +++++++++++++++++++++++++++++ man/convert_dtm_to_dtc.Rd | 1 + man/extract_vars.Rd | 1 + man/filter_if.Rd | 1 + man/grapes-notin-grapes.Rd | 1 + man/grapes-or-grapes.Rd | 1 + man/negate_vars.Rd | 1 + man/valid_time_units.Rd | 1 + man/vars2chr.Rd | 1 + tests/testthat/test-dev_utilities.R | 11 ++++++++++ 14 files changed, 64 insertions(+) create mode 100644 man/contains_vars.Rd diff --git a/NAMESPACE b/NAMESPACE index ea617edb..941ff546 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -33,6 +33,7 @@ export(assert_unit) export(assert_vars) export(assert_varval_list) export(backquote) +export(contains_vars) export(convert_dtm_to_dtc) export(dataset_vignette) export(desc) diff --git a/R/dev_utilities.R b/R/dev_utilities.R index f9e1e0fc..091e8ca6 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -151,6 +151,16 @@ valid_time_units <- function() { c("years", "months", "days", "hours", "minutes", "seconds") } +#' check that argument contains valid variable created with `vars()` +#' +#' @param arg A function argument to be checked +#' +#' @return A TRUE if variables were valid variable +#' +#' @export +#' +#' @keywords dev_utility +#' @family dev_utility contains_vars <- function(arg) { inherits(arg, "quosures") && all(map_lgl(arg, quo_is_symbol) | names(arg) != "") } diff --git a/man/arg_name.Rd b/man/arg_name.Rd index 699734cf..383d2395 100644 --- a/man/arg_name.Rd +++ b/man/arg_name.Rd @@ -20,6 +20,7 @@ Developer Utility Functions: \code{\link{\%notin\%}()}, \code{\link{\%or\%}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, diff --git a/man/as_name.Rd b/man/as_name.Rd index 70bd65ee..c21c81d9 100644 --- a/man/as_name.Rd +++ b/man/as_name.Rd @@ -24,6 +24,7 @@ Developer Utility Functions: \code{\link{\%notin\%}()}, \code{\link{\%or\%}()}, \code{\link{arg_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, diff --git a/man/contains_vars.Rd b/man/contains_vars.Rd new file mode 100644 index 00000000..6b86afa0 --- /dev/null +++ b/man/contains_vars.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dev_utilities.R +\name{contains_vars} +\alias{contains_vars} +\title{check that argument contains valid variable created with \code{vars()}} +\usage{ +contains_vars(arg) +} +\arguments{ +\item{arg}{A function argument to be checked} +} +\value{ +A TRUE if variables were valid variable +} +\description{ +check that argument contains valid variable created with \code{vars()} +} +\seealso{ +Developer Utility Functions: +\code{\link{\%notin\%}()}, +\code{\link{\%or\%}()}, +\code{\link{arg_name}()}, +\code{\link{as_name}()}, +\code{\link{convert_dtm_to_dtc}()}, +\code{\link{extract_vars}()}, +\code{\link{filter_if}()}, +\code{\link{negate_vars}()}, +\code{\link{valid_time_units}()}, +\code{\link{vars2chr}()} +} +\concept{dev_utility} +\keyword{dev_utility} diff --git a/man/convert_dtm_to_dtc.Rd b/man/convert_dtm_to_dtc.Rd index 59598efb..59472890 100644 --- a/man/convert_dtm_to_dtc.Rd +++ b/man/convert_dtm_to_dtc.Rd @@ -23,6 +23,7 @@ Developer Utility Functions: \code{\link{\%or\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, diff --git a/man/extract_vars.Rd b/man/extract_vars.Rd index 064f44a5..436aff2f 100644 --- a/man/extract_vars.Rd +++ b/man/extract_vars.Rd @@ -23,6 +23,7 @@ Developer Utility Functions: \code{\link{\%or\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{filter_if}()}, \code{\link{negate_vars}()}, diff --git a/man/filter_if.Rd b/man/filter_if.Rd index 01d766da..d86aaf26 100644 --- a/man/filter_if.Rd +++ b/man/filter_if.Rd @@ -24,6 +24,7 @@ Developer Utility Functions: \code{\link{\%or\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{negate_vars}()}, diff --git a/man/grapes-notin-grapes.Rd b/man/grapes-notin-grapes.Rd index 5df54a5a..81edecf8 100644 --- a/man/grapes-notin-grapes.Rd +++ b/man/grapes-notin-grapes.Rd @@ -23,6 +23,7 @@ Developer Utility Functions: \code{\link{\%or\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, diff --git a/man/grapes-or-grapes.Rd b/man/grapes-or-grapes.Rd index 2c3badb5..53e1b5ae 100644 --- a/man/grapes-or-grapes.Rd +++ b/man/grapes-or-grapes.Rd @@ -27,6 +27,7 @@ Developer Utility Functions: \code{\link{\%notin\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, diff --git a/man/negate_vars.Rd b/man/negate_vars.Rd index dfd0c25b..63c683eb 100644 --- a/man/negate_vars.Rd +++ b/man/negate_vars.Rd @@ -28,6 +28,7 @@ Developer Utility Functions: \code{\link{\%or\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, diff --git a/man/valid_time_units.Rd b/man/valid_time_units.Rd index e484592f..d9e004b9 100644 --- a/man/valid_time_units.Rd +++ b/man/valid_time_units.Rd @@ -18,6 +18,7 @@ Developer Utility Functions: \code{\link{\%or\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, diff --git a/man/vars2chr.Rd b/man/vars2chr.Rd index 654bc8f2..74561ae6 100644 --- a/man/vars2chr.Rd +++ b/man/vars2chr.Rd @@ -24,6 +24,7 @@ Developer Utility Functions: \code{\link{\%or\%}()}, \code{\link{arg_name}()}, \code{\link{as_name}()}, +\code{\link{contains_vars}()}, \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 68d13a3e..fc7109cf 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -68,3 +68,14 @@ test_that("negate_vars Test 6: negate_vars returns list of negated variables", { test_that("negate_vars Test 7: negate_vars returns NULL if input is NULL", { expect_identical(negate_vars(NULL), NULL) }) + +# contains_vars ---- +## Test 8: returns TRUE for valid arguments ---- +test_that("contains_vars Test 8: returns TRUE for valid arguments", { + expect_true(contains_vars(vars(USUBJID, PARAMCD))) +}) + +## Test 9: returns FALSE for improper arguments ---- +test_that("contains_vars Test 9: returns TRUE for valid arguments", { + expect_error(contains_vars(USUBJID)) +}) From ac7e5b03731ae208a929a814d833c4a6c850d317 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Sat, 26 Nov 2022 02:03:36 +0000 Subject: [PATCH 167/179] #39 updated NEWS.md --- NEWS.md | 3 ++- R/dev_utilities.R | 3 ++- man/contains_vars.Rd | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 146acf32..e6074d4d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -27,7 +27,8 @@ ## Breaking Changes - NA ## Documentation - - New vignette for our package release strategy (#79) + - New vignette for our package release strategy (#79) + - Updated multiple roxygen updates (#116, #133, #134, #141, #145, #172) ## Various - NA diff --git a/R/dev_utilities.R b/R/dev_utilities.R index 091e8ca6..2aa30881 100644 --- a/R/dev_utilities.R +++ b/R/dev_utilities.R @@ -151,7 +151,8 @@ valid_time_units <- function() { c("years", "months", "days", "hours", "minutes", "seconds") } -#' check that argument contains valid variable created with `vars()` +#' check that argument contains valid variable(s) created with `vars()` or +#' Source Variables from a List of Quosures #' #' @param arg A function argument to be checked #' diff --git a/man/contains_vars.Rd b/man/contains_vars.Rd index 6b86afa0..2b00b446 100644 --- a/man/contains_vars.Rd +++ b/man/contains_vars.Rd @@ -2,7 +2,8 @@ % Please edit documentation in R/dev_utilities.R \name{contains_vars} \alias{contains_vars} -\title{check that argument contains valid variable created with \code{vars()}} +\title{check that argument contains valid variable(s) created with \code{vars()} or +Source Variables from a List of Quosures} \usage{ contains_vars(arg) } @@ -13,7 +14,8 @@ contains_vars(arg) A TRUE if variables were valid variable } \description{ -check that argument contains valid variable created with \code{vars()} +check that argument contains valid variable(s) created with \code{vars()} or +Source Variables from a List of Quosures } \seealso{ Developer Utility Functions: From 7e10ce6a71552c56adfe933d1ce97d69aa273fa3 Mon Sep 17 00:00:00 2001 From: Sadchla Mascary <112789549+sadchla-codes@users.noreply.github.com> Date: Sun, 27 Nov 2022 18:06:10 -0500 Subject: [PATCH 168/179] Update NEWS.md Co-authored-by: Ben Straub --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index e6074d4d..c8fd20bf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,7 +28,7 @@ - NA ## Documentation - New vignette for our package release strategy (#79) - - Updated multiple roxygen updates (#116, #133, #134, #141, #145, #172) + - Updated multiple roxygen headers (#116, #133, #134, #141, #145, #172) ## Various - NA From c40cbc45b756e068996b32ae946cfb4146116644 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 28 Nov 2022 08:32:07 -0500 Subject: [PATCH 169/179] Update vignettes/programming_strategy.Rmd Co-authored-by: Ben Straub --- vignettes/programming_strategy.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 292a3a26..0ba5477e 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -475,7 +475,7 @@ Functions from other packages have to be explicitly imported by using the `@impo To import the `if_else()` and `mutate()` function from `dplyr` the following line would have to be included in that file: `#' @importFrom dplyr if_else mutate`. -Some of these functions become critically important while using admiral and should be included as an export. This applies to functions which are frequently called within admiral function calls like `vars()`, `desc()` or the pipe operator `%>%`. To export these functions, the following R code should be included in the `R/reexports.R` file using the format: +Some of these functions become critically important while using admiral and should be included as an export. This applies to functions which are frequently called within `{admiral }`function calls like `dplyr::vars()`, `dplyr::desc()` or the pipe operator `magrittr::%>%`. To export these functions, the following R code should be included in the `R/reexports.R` file using the format: ``` #' @export From 2eec9ca23cb994b7f939f333b78ea3aecdee2ca1 Mon Sep 17 00:00:00 2001 From: sadchla-codes Date: Mon, 28 Nov 2022 15:09:45 +0000 Subject: [PATCH 170/179] closes #39 updated wordlist --- inst/WORDLIST | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/WORDLIST b/inst/WORDLIST index 3ac0472e..50b14099 100644 --- a/inst/WORDLIST +++ b/inst/WORDLIST @@ -31,5 +31,6 @@ onboarding pharmaverse renv repo +roxygen www th From 40d92a28fda7582e52c437a664bd80f6fe2b6bcf Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 28 Nov 2022 16:15:57 +0000 Subject: [PATCH 171/179] negate_vars moved to admiral --- man/contains_vars.Rd | 1 - man/negate_vars.Rd | 42 ----------------------------- tests/testthat/test-dev_utilities.R | 22 +++------------ 3 files changed, 4 insertions(+), 61 deletions(-) delete mode 100644 man/negate_vars.Rd diff --git a/man/contains_vars.Rd b/man/contains_vars.Rd index 2b00b446..6fc77eb7 100644 --- a/man/contains_vars.Rd +++ b/man/contains_vars.Rd @@ -26,7 +26,6 @@ Developer Utility Functions: \code{\link{convert_dtm_to_dtc}()}, \code{\link{extract_vars}()}, \code{\link{filter_if}()}, -\code{\link{negate_vars}()}, \code{\link{valid_time_units}()}, \code{\link{vars2chr}()} } diff --git a/man/negate_vars.Rd b/man/negate_vars.Rd deleted file mode 100644 index 63c683eb..00000000 --- a/man/negate_vars.Rd +++ /dev/null @@ -1,42 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/dev_utilities.R -\name{negate_vars} -\alias{negate_vars} -\title{Negate List of Variables} -\usage{ -negate_vars(vars = NULL) -} -\arguments{ -\item{vars}{List of variables created by \code{vars()}} -} -\value{ -A list of \code{quosures} -} -\description{ -The function adds a minus sign as prefix to each variable. -} -\details{ -This is useful if a list of variables should be removed from a dataset, -e.g., \code{select(!!!negate_vars(by_vars))} removes all by variables. -} -\examples{ -negate_vars(vars(USUBJID, STUDYID)) -} -\seealso{ -Developer Utility Functions: -\code{\link{\%notin\%}()}, -\code{\link{\%or\%}()}, -\code{\link{arg_name}()}, -\code{\link{as_name}()}, -\code{\link{contains_vars}()}, -\code{\link{convert_dtm_to_dtc}()}, -\code{\link{extract_vars}()}, -\code{\link{filter_if}()}, -\code{\link{valid_time_units}()}, -\code{\link{vars2chr}()} -} -\author{ -Stefan Bundfuss -} -\concept{dev_utility} -\keyword{dev_utility} diff --git a/tests/testthat/test-dev_utilities.R b/tests/testthat/test-dev_utilities.R index 3459cae9..6457e8d8 100644 --- a/tests/testthat/test-dev_utilities.R +++ b/tests/testthat/test-dev_utilities.R @@ -57,28 +57,14 @@ test_that("convert_dtm_to_dtc Test 5: Input is filtered if filter is not NULL", keys = c("USUBJID", "VSTESTCD") ) }) -<<<<<<< HEAD -======= - -# negate_vars ---- -## Test 6: negate_vars returns list of negated variables ---- -test_that("negate_vars Test 6: negate_vars returns list of negated variables", { - expect_identical(negate_vars(vars(var1, var2)), rlang::exprs(-var1, -var2)) -}) - -## Test 7: negate_vars returns NULL if input is NULL ---- -test_that("negate_vars Test 7: negate_vars returns NULL if input is NULL", { - expect_identical(negate_vars(NULL), NULL) -}) # contains_vars ---- -## Test 8: returns TRUE for valid arguments ---- -test_that("contains_vars Test 8: returns TRUE for valid arguments", { +## Test 6: returns TRUE for valid arguments ---- +test_that("contains_vars Test 6: returns TRUE for valid arguments", { expect_true(contains_vars(vars(USUBJID, PARAMCD))) }) -## Test 9: returns FALSE for improper arguments ---- -test_that("contains_vars Test 9: returns TRUE for valid arguments", { +## Test 7: returns TRUE for valid arguments ---- +test_that("contains_vars Test 7: returns TRUE for valid arguments", { expect_error(contains_vars(USUBJID)) }) ->>>>>>> devel From 4b6fbe925002ddbe804c432874999c351c536490 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 28 Nov 2022 17:25:59 +0000 Subject: [PATCH 172/179] #179 - Add roxygen-style paragraph to describe environments --- R/admiraldev_environment.R | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/R/admiraldev_environment.R b/R/admiraldev_environment.R index fc42f0ae..58004080 100644 --- a/R/admiraldev_environment.R +++ b/R/admiraldev_environment.R @@ -1,8 +1,23 @@ +#' Environment Objects +#' +#' @details +#' Once in a while, we may encounter "locked binding for 'xxx'." errors +#' during the development process while building out functions. This may arise because +#' we want to create dynamic data/objects based on user-inputs that need modification +#' at points in time after the package has been loaded. To manage such data or objects, +#' R has a data structure known as an 'environment'. These environment objects are created +#' at build time, but can be populated with values after the package has been loaded and +#' update those values over the course of an R session. See Chapter 8.5 of the R Packages textbook +#' or Chapter 7 of the Advanced R textbook for more details. +#' @noRd admiraldev_environment <- new.env(parent = emptyenv()) +# See respective ...R page for usage -# assertions.R +# assertions.R ---- +## assert_one_to_one admiraldev_environment$many_to_one <- NULL admiraldev_environment$one_to_many <- NULL -# datasets.R -# get_dataset() is used to retrieve many_to_one and one_to_many +# datasets.R ---- +## get_dataset +# Function above is used to retrieve many_to_one and one_to_many From 84085b636ce01deed0b8ef96eb0a493ec4ad8701 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Mon, 28 Nov 2022 18:19:06 +0000 Subject: [PATCH 173/179] #179 - Edits on environments blurb --- R/admiraldev_environment.R | 4 ++-- vignettes/programming_strategy.Rmd | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/admiraldev_environment.R b/R/admiraldev_environment.R index 58004080..28176315 100644 --- a/R/admiraldev_environment.R +++ b/R/admiraldev_environment.R @@ -7,8 +7,8 @@ #' at points in time after the package has been loaded. To manage such data or objects, #' R has a data structure known as an 'environment'. These environment objects are created #' at build time, but can be populated with values after the package has been loaded and -#' update those values over the course of an R session. See Chapter 8.5 of the R Packages textbook -#' or Chapter 7 of the Advanced R textbook for more details. +#' update those values over the course of an R session. For more details how environments work, +#' see relevant sections on environments in R Packages and Advanced R textbooks for more details. #' @noRd admiraldev_environment <- new.env(parent = emptyenv()) # See respective ...R page for usage diff --git a/vignettes/programming_strategy.Rmd b/vignettes/programming_strategy.Rmd index 44a153b5..26ab43ad 100644 --- a/vignettes/programming_strategy.Rmd +++ b/vignettes/programming_strategy.Rmd @@ -100,7 +100,7 @@ i.e. all input like datasets, variable names, options, … must be provided to t * If the function needs to create temporary variables in an input dataset, these variables must start with `temp_` and must be removed from the output dataset. * If the input dataset includes variables starting with `temp_`, an error must be issued. -* If developers find the need to use or create *environment* objects to achieve flexibility, use the `admiral_environment` environment object created in `admiral_environment.R`. All objects which are stored in this environment must be documented in `admiral_environment.R`. An equivalent environment object and `.R` file exist for admiraldev as well. For more details how environments work, please refer to [Chapter 8](https://r-pkgs.org/data.html#sec-data-state) of the latest edition of the R Packages book and [Chapter 7](https://adv-r.hadley.nz/environments.html) of the latest edition of the Advanced R book. +* If developers find the need to use or create *environment* objects to achieve flexibility, use the `admiral_environment` environment object created in `admiral_environment.R`. All objects which are stored in this environment must be documented in `admiral_environment.R`. An equivalent environment object and `.R` file exist for admiraldev as well. For more details how environments work, see relevant sections on environments in [R Packages](https://r-pkgs.org) and [Advanced R](https://adv-r.hadley.nz) textbooks. * In general, the function must not have any side-effects like creating or modifying global objects, printing, writing files, ... ## Admiral Options From 8988a458c6ac1bbd8ec2ccbeaf7c71f823160461 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Tue, 29 Nov 2022 14:46:15 +0000 Subject: [PATCH 174/179] #83 - Remove reexports from admiraldev --- NAMESPACE | 3 --- R/assertions.R | 2 ++ R/quo.R | 2 ++ R/reexports.R | 8 -------- R/warnings.R | 2 ++ man/add_suffix_to_vars.Rd | 2 ++ man/assert_varval_list.Rd | 2 ++ man/reexports.Rd | 20 -------------------- man/warn_if_inconsistent_list.Rd | 2 ++ 9 files changed, 12 insertions(+), 31 deletions(-) delete mode 100644 R/reexports.R delete mode 100644 man/reexports.Rd diff --git a/NAMESPACE b/NAMESPACE index 13769e38..c7c4a2f5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,5 @@ # Generated by roxygen2: do not edit by hand -export("%>%") export("%notin%") export("%or%") export(add_suffix_to_vars) @@ -37,7 +36,6 @@ export(backquote) export(contains_vars) export(convert_dtm_to_dtc) export(dataset_vignette) -export(desc) export(dquote) export(enumerate) export(expect_dfs_equal) @@ -63,7 +61,6 @@ export(set_dataset) export(squote) export(suppress_warning) export(valid_time_units) -export(vars) export(vars2chr) export(warn_if_incomplete_dtc) export(warn_if_inconsistent_list) diff --git a/R/assertions.R b/R/assertions.R index a7335142..8a797f11 100644 --- a/R/assertions.R +++ b/R/assertions.R @@ -1193,6 +1193,8 @@ assert_param_does_not_exist <- function(dataset, param) { #' @export #' #' @examples +#' library(dplyr, warn.conflicts = FALSE) +#' #' example_fun <- function(vars) { #' assert_varval_list(vars) #' } diff --git a/R/quo.R b/R/quo.R index 67dfe628..6386f2a1 100644 --- a/R/quo.R +++ b/R/quo.R @@ -143,6 +143,8 @@ replace_symbol_in_quo <- function(quosure, #' @export #' #' @examples +#' library(dplyr, warn.conflicts = FALSE) +#' #' add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") add_suffix_to_vars <- function(order, vars, diff --git a/R/reexports.R b/R/reexports.R deleted file mode 100644 index f03e9cb5..00000000 --- a/R/reexports.R +++ /dev/null @@ -1,8 +0,0 @@ -#' @export -dplyr::vars - -#' @export -dplyr::desc - -#' @export -magrittr::`%>%` diff --git a/R/warnings.R b/R/warnings.R index 960c9a72..279d1973 100644 --- a/R/warnings.R +++ b/R/warnings.R @@ -149,6 +149,8 @@ warn_if_incomplete_dtc <- function(dtc, n) { #' @export #' #' @examples +#' library(dplyr, warn.conflicts = FALSE) +#' #' # no warning #' warn_if_inconsistent_list( #' base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ), diff --git a/man/add_suffix_to_vars.Rd b/man/add_suffix_to_vars.Rd index 098f11a3..2f9a591a 100644 --- a/man/add_suffix_to_vars.Rd +++ b/man/add_suffix_to_vars.Rd @@ -28,6 +28,8 @@ added to every symbol specified for \code{vars} Add a suffix to variables in a list of quosures } \examples{ +library(dplyr, warn.conflicts = FALSE) + add_suffix_to_vars(vars(ADT, desc(AVAL), AVALC), vars = vars(AVAL), suffix = ".join") } \seealso{ diff --git a/man/assert_varval_list.Rd b/man/assert_varval_list.Rd index 796d9a7d..104da0e4 100644 --- a/man/assert_varval_list.Rd +++ b/man/assert_varval_list.Rd @@ -35,6 +35,8 @@ variable-value pairs. The value can be a symbol, a string, a numeric, or \code{NA}. More general expression are not allowed. } \examples{ +library(dplyr, warn.conflicts = FALSE) + example_fun <- function(vars) { assert_varval_list(vars) } diff --git a/man/reexports.Rd b/man/reexports.Rd deleted file mode 100644 index eb33c97e..00000000 --- a/man/reexports.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reexports.R -\docType{import} -\name{reexports} -\alias{reexports} -\alias{vars} -\alias{desc} -\alias{\%>\%} -\title{Objects exported from other packages} -\keyword{internal} -\description{ -These objects are imported from other packages. Follow the links -below to see their documentation. - -\describe{ - \item{dplyr}{\code{\link[dplyr]{desc}}, \code{\link[dplyr]{vars}}} - - \item{magrittr}{\code{\link[magrittr:pipe]{\%>\%}}} -}} - diff --git a/man/warn_if_inconsistent_list.Rd b/man/warn_if_inconsistent_list.Rd index 01f50ab8..f7af69a6 100644 --- a/man/warn_if_inconsistent_list.Rd +++ b/man/warn_if_inconsistent_list.Rd @@ -24,6 +24,8 @@ Checks if two list inputs have the same names and same number of elements and issues a warning otherwise. } \examples{ +library(dplyr, warn.conflicts = FALSE) + # no warning warn_if_inconsistent_list( base = vars(DTHDOM = "DM", DTHSEQ = DMSEQ), From c5918d81b9c477e49cfa1608275ca656c2020ee2 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 30 Nov 2022 14:33:29 +0000 Subject: [PATCH 175/179] Run requested styler line --- tests/testthat/test-addin_format_testthat.R | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/testthat/test-addin_format_testthat.R b/tests/testthat/test-addin_format_testthat.R index fa474611..f9e10498 100644 --- a/tests/testthat/test-addin_format_testthat.R +++ b/tests/testthat/test-addin_format_testthat.R @@ -1,6 +1,5 @@ ## Test 1: works as expected ---- test_that("addin_format_testthat Test 1: works as expected", { - # test: file exists expect_error( prepare_test_that_file("file_does_not_exist"), From d9d3c8815b1ce45cb2f4b6a3c0fd32d79514cdb8 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 30 Nov 2022 16:07:53 +0000 Subject: [PATCH 176/179] Update news and reference page desciptions --- NEWS.md | 28 +++++++++++++--------------- _pkgdown.yml | 4 ++-- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/NEWS.md b/NEWS.md index c8fd20bf..71eefee1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,36 +1,31 @@ # admiraldev 0.2.0 ## New Features - - Developer addin for formatting tests to admiral programming standards (#73) - - New functions `replace_symbol_in_quo()` and `add_suffix_to_vars()` (#106) - - New function `assert_atomic_vector()` (#98) - - New keyword/family `create_aux` for functions creating auxiliary datasets (#126) - - New function `assert_date_vector()` (#129) - - New function `assert_same_type()` (#176) - - Remove dependency on `{assertthat}` (#149) - - - Test coverage for `admiraldev` have increased from 45% to approximately 100% (#94,#95, #96,#98,#101,#103) - + - Test coverage for `admiraldev` have increased from 45% to approximately 100% (#94, #95, #96, #98, #101, #103) + - _Environment_ objects were consolidated into a single `admiraldev_environment` object under `R/admiraldev_environment.R`. (#179) ## Updates of Existing Functions - - - `expect_names` argument added to `assert_vars()` to check if all variables - are named (#117) + - `expect_names` argument added to `assert_vars()` to check if all variables are named (#117) + - Remove `dplyr` function exports (#83) ## Breaking Changes - - NA + - No longer compatible with admiral (<0.9) + ## Documentation - New vignette for our package release strategy (#79) - Updated multiple roxygen headers (#116, #133, #134, #141, #145, #172) + - Description on how admiral options work for certain function inputs, i.e `subject_keys` (#133) + ## Various - - NA + - PR Checklist Template updated (#172) + - New authors/contributors (#158) # admiraldev 0.1.0 @@ -42,10 +37,13 @@ ## Updates of Existing Functions - NA + ## Breaking Changes - NA + ## Documentation - NA + ## Various - NA diff --git a/_pkgdown.yml b/_pkgdown.yml index 30c58bb0..7a60c532 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -49,8 +49,8 @@ reference: contents: - has_keyword('tmp_vars') -- title: Getting and Setting Datasets - desc: Assign or retrieve a dataset into/from the `admiraldev_environment` enironment +- title: Getting Datasets + desc: Retrieve a dataset into/from the `admiraldev_environment` enironment contents: - has_keyword('datasets') From b1659371b241bbb8fd89b807e57ad46727061bb4 Mon Sep 17 00:00:00 2001 From: Zelos Zhu Date: Wed, 30 Nov 2022 16:13:26 +0000 Subject: [PATCH 177/179] Missed a description and wordflow --- NEWS.md | 2 +- _pkgdown.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 71eefee1..f3e0a739 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,7 +13,7 @@ ## Updates of Existing Functions - `expect_names` argument added to `assert_vars()` to check if all variables are named (#117) - - Remove `dplyr` function exports (#83) + - Remove `dplyr` function exports and migration of user facing function `negate_vars()` to admiral (#83) ## Breaking Changes - No longer compatible with admiral (<0.9) diff --git a/_pkgdown.yml b/_pkgdown.yml index 7a60c532..0d231f0a 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -50,7 +50,7 @@ reference: - has_keyword('tmp_vars') - title: Getting Datasets - desc: Retrieve a dataset into/from the `admiraldev_environment` enironment + desc: Retrieve a dataset from the `admiraldev_environment` enironment contents: - has_keyword('datasets') From 6d5a748657abd76c1d2ffe8b7268a8a7bd7a7588 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 30 Nov 2022 18:18:55 +0000 Subject: [PATCH 178/179] [actions skip] Add/Update README.md for pre-release --- README.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b42b2472..69eaf654 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ADaM in R Asset Library Development Utilities + ## Purpose @@ -13,26 +14,29 @@ Tools for developing functions and maintaining a healthy code base within the family of admiral R packages. `{admiraldev}` is intended to be used when developing `{admiral}` or `{admiral}` extension packages. -**NOTE:** Use of this package as a standalone package is currently not -recommended. +__NOTE:__ This package is not intended for standalone use but rather as +a central dependency for all developer utilities of `{admiral}` and its +extension packages ## Installation The package is available from CRAN and can be installed by running -install.packages(“admiraldev”). +`install.packages("admiraldev")`. To install the latest development version of the package directly from GitHub use the following code: - if (!requireNamespace("remotes", quietly = TRUE)) { - install.packages("remotes") - } +``` +if (!requireNamespace("remotes", quietly = TRUE)) { + install.packages("remotes") +} - remotes::install_github("pharmaverse/admiraldev", ref = "devel") +remotes::install_github("pharmaverse/admiraldev", ref = "devel") +``` ## Release Schedule -`{admiraldev}` is to be official released to CRAN one week before the -release of `{admiral}`. You can find the release schedule for +`{admiraldev}` is to be officially released to CRAN one week before an +official release of `{admiral}`. You can find the release schedule for `{admiral}` packages -[here](https://github.com/pharmaverse/admiral/tree/devel#release-schedule). +[here](https://pharmaverse.github.io/admiral/#release-schedule). From 956bc0bc2a12da5d683da8c90bdc3c6f0e61c5f5 Mon Sep 17 00:00:00 2001 From: bms63 Date: Thu, 1 Dec 2022 02:12:03 +0000 Subject: [PATCH 179/179] wayward vignette in the package - now ignored --- .Rbuildignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.Rbuildignore b/.Rbuildignore index 7adf8bb6..ed4e8d8a 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -17,6 +17,7 @@ ^vignettes/git_usage\.Rmd$ ^vignettes/writing_vignettes\.Rmd$ ^vignettes/unit_test_guidance\.Rmd$ +^vignettes/release_strategy\.Rmd$ ^vignettes/.+png$ ^LICENSE\.md$ ^\.github$