diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index f16b098..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -.github/workflows/* @cicdguy diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml deleted file mode 100644 index 5789012..0000000 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Bug Report -description: Something is not working correctly or is not working at all! -title: "Bug: " -labels: ["bug", "programming"] -assignees: - - octocat -body: - - type: markdown - attributes: - value: | - **Example:** Bug: rprofile() is returning NA values when using the config argument - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us what were you expecting to happen before the bug? - placeholder: "A bug happened!" - validations: - required: true - - type: textarea - id: session-info - attributes: - label: Session Information - description: Use `sessionInfo()` in the R console to gather all the details of your environment when the bug happened. - placeholder: "Place the console output here" - validations: - required: false - - type: textarea - id: logs - attributes: - label: Reproducible Example - description: We love code that can reproduce the example. Check out [reprex](https://reprex.tidyverse.org/articles/reprex-dos-and-donts.html) - placeholder: "Please give us as many details as you can! The faster we can recreate the bug, the faster we can get a fix in the works. Warning, Error Messages and Screenshots are also great." - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/02_feature_request.yml b/.github/ISSUE_TEMPLATE/02_feature_request.yml deleted file mode 100644 index 896e4dc..0000000 --- a/.github/ISSUE_TEMPLATE/02_feature_request.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Feature Request -description: Enchancement to admiral functionality -title: "Feature Request: " -labels: ["enhancement", "programming"] -assignees: - - octocat -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this feature request! We love keeping envsetup fresh! - - type: textarea - id: feature - attributes: - label: Feature Idea - description: Tell us your idea in as few words as possible - placeholder: "`rprofile()` should do x, y and z" - validations: - required: true - - type: textarea - id: input - attributes: - label: Relevant Input - description: Can you provide what the inputs should look like? - placeholder: "What should the input look like? REMINDER: no patient level data or company sensitive information should be shared via this open public issue" - validations: - required: false - - type: textarea - id: output - attributes: - label: Relevant Output - description: Can you provide what the final output should look like? - placeholder: "What should the output look like? REMINDER: no patient level data or company sensitive information should be shared via this open public issue" - validations: - required: false - - type: textarea - id: code - attributes: - label: Reproducible Example/Pseudo Code - description: Can you provide a working example or a sketch of how the code should work? - placeholder: "We love example code and it will speed up the process! REMINDER: no patient level data or company sensitive information should be shared via this open public issue" - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/03_docs_update.yml b/.github/ISSUE_TEMPLATE/03_docs_update.yml deleted file mode 100644 index 6241879..0000000 --- a/.github/ISSUE_TEMPLATE/03_docs_update.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Updates or Enhancements for Documentation -description: Documents out of date? Examples need reworking? Got an idea for a new article? -title: "Documentation: " -labels: ["vignettes", "website", "documentation"] -assignees: - - octocat -body: - - type: markdown - attributes: - value: | - The envsetup team loves documentation! Help us keep it up to date and fresh! - - type: dropdown - id: browsers - attributes: - label: Please select a category the issue is focused on? - multiple: true - options: - - Function Documentation - - User Guides - - Developer Guides - - Other - - I have a new documentation idea! Yay! - - type: textarea - id: docs-out-if-sync - attributes: - label: Let us know where something needs a refresh or put your idea here! - description: Screen shots are super helpful here! You can do a screenshot and cut and paste it here right in here. - placeholder: "Something is out of sync or needs a refresh" - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/06_general_issue.yml b/.github/ISSUE_TEMPLATE/06_general_issue.yml deleted file mode 100644 index ece0b2d..0000000 --- a/.github/ISSUE_TEMPLATE/06_general_issue.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: General Issue -description: Issue that is difficult to fit into bug, feature or documentation issue. -title: "General Issue: " -labels: ["general issue"] -assignees: - - octocat -body: - - type: markdown - attributes: - value: | - **Example:** General Issue: Linked In Blog Post for updates on the latest release? - - type: textarea - id: background - attributes: - label: Background Information - description: Please provide the context and purpose of this piece of work. Links and screenshots are encouraged. - placeholder: "Example: Use changelog/news.md file to gather relevant changes and create into a nice post for Linked In. The more information the better and the faster we can get this issue completed." - validations: - required: true - - type: textarea - id: done - attributes: - label: Definition of Done - description: Please define the deliverable or the desired outcome. - placeholder: "Example: All changes since last release are highlighted in Blog Post and posted to Linked In" - validations: - required: false diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index c911d69..0000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,17 +0,0 @@ -Thank you for your Pull Request! Completing the below tasks helps to ensure our reviewers can maximize their time on your code as well as making sure the 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 -- [ ] 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 site `pkgdown::build_site()` and check that all affected examples are displayed correctly and that all new functions occur on the reference 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. -- [ ] Pat yourself on the back for a job well done! Much love to your accomplishment! diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml new file mode 100644 index 0000000..e67cd75 --- /dev/null +++ b/.github/workflows/R-CMD-check.yaml @@ -0,0 +1,59 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: + - main + - master + - dev + pull_request: + branches: + - main + - master + - dev + +name: R-CMD-check.yaml + +permissions: read-all + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + + name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + + strategy: + fail-fast: false + matrix: + config: + - {os: windows-latest, r: 'release'} + - {os: macOS-latest, r: 'release'} + - {os: ubuntu-22.04, r: 'release', rspm: "https://packagemanager.posit.co/cran/__linux__/jammy/latest"} + - {os: ubuntu-22.04, r: 'devel', rspm: "https://packagemanager.posit.co/cran/__linux__/jammy/latest"} + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::rcmdcheck + needs: check + + - uses: r-lib/actions/check-r-package@v2 + with: + upload-snapshots: true + build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' \ No newline at end of file diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml deleted file mode 100644 index 334d3f2..0000000 --- a/.github/workflows/common.yml +++ /dev/null @@ -1,107 +0,0 @@ ---- -# Source: https://github.com/pharmaverse/admiralci -# Common workflows designed for Admiral -# but can be easily used by any other R package -name: Admiral Workflows - -on: - # 'workflow_dispatch' gives you the ability - # to run this workflow on demand, anytime - workflow_dispatch: - # 'push' events are triggered when commits - # are pushed to one of these branches - push: - branches: - - main - - devel - - pre-release - - test - # 'pull_request' events are triggered when PRs are - # created against one of these target branches. - pull_request: - branches: - - main - - devel - - pre-release - - test - # 'release' events are triggered when... - # you guessed it - when releases are made. - release: - types: [published] - schedule: - - cron: '21 13 * * 1,3,5' - -env: - # R version to use for the workflows - R_VERSION: "3.6" - -# Docs on concurrency: -# https://docs.github.com/en/actions/using-jobs/using-concurrency -concurrency: - group: admiral-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - style: - name: Code Style - uses: pharmaverse/admiralci/.github/workflows/style.yml@main - if: github.event_name == 'pull_request' - with: - r-version: $R_VERSION - spellcheck: - name: Spelling - uses: pharmaverse/admiralci/.github/workflows/spellcheck.yml@main - if: github.event_name == 'pull_request' - with: - r-version: $R_VERSION - readme: - name: Render README - uses: pharmaverse/admiralci/.github/workflows/readme-render.yml@main - if: github.event_name == 'push' - with: - r-version: $R_VERSION - # This workflow autoformats the README.md file after - # it has been rendered. If you want to skip that, - # set the following value to 'true' - skip-md-formatting: false - check: - name: Check - uses: pharmaverse/admiralci/.github/workflows/r-cmd-check.yml@main - if: github.event_name == 'pull_request' - with: - # Control how you want the errors to manifest - # by supplying the 'error_on' parameter to - # rcmdcheck::rcmdcheck() here. - error-on: error - docs: - name: Documentation - uses: pharmaverse/admiralci/.github/workflows/pkgdown.yml@main - if: github.event_name == 'push' - with: - r-version: $R_VERSION - # 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 - skip-multiversion-docs: false - coverage: - name: Code Coverage - uses: pharmaverse/admiralci/.github/workflows/code-coverage.yml@main - if: > - github.event_name == 'push' || github.event_name == 'pull_request' - with: - r-version: $R_VERSION - # Whether to skip code coverage badge creation - # Setting to 'false' will require you to create - # an orphan branch called 'badges' in your repository - skip-coverage-badges: false - links: - name: Links - uses: pharmaverse/admiralci/.github/workflows/links.yml@main - if: > - github.event_name == 'push' || github.event_name == 'pull_request' - man-pages: - name: Man Pages - uses: pharmaverse/admiralci/.github/workflows/man-pages.yml@main - if: github.event_name == 'pull_request' - with: - r-version: $R_VERSION diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml new file mode 100644 index 0000000..bfc9f4d --- /dev/null +++ b/.github/workflows/pkgdown.yaml @@ -0,0 +1,49 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + release: + types: [published] + workflow_dispatch: + +name: pkgdown.yaml + +permissions: read-all + +jobs: + pkgdown: + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::pkgdown, local::. + needs: website + + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml new file mode 100644 index 0000000..5e9a7e5 --- /dev/null +++ b/.github/workflows/test-coverage.yaml @@ -0,0 +1,62 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] + pull_request: + +name: test-coverage.yaml + +permissions: read-all + +jobs: + test-coverage: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::xml2 + needs: coverage + + - name: Test coverage + run: | + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + print(cov) + covr::to_cobertura(cov) + shell: Rscript {0} + + - uses: codecov/codecov-action@v4 + with: + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package diff --git a/.lintr b/.lintr index 976fcdd..65a862b 100644 --- a/.lintr +++ b/.lintr @@ -1,2 +1,2 @@ -linters: with_defaults(line_length_linter(125), object_usage_linter=NULL, cyclocomp_linter(complexity_limit = 20), object_name_linter(styles = c("snake_case", "symbols", "dotted.case")), single_quotes_linter=NULL) +linters: linters_with_defaults(line_length_linter(125), object_usage_linter=NULL, cyclocomp_linter(complexity_limit = 20), object_name_linter(styles = c("snake_case", "symbols", "dotted.case"))) exclusions: list("R/data.R") diff --git a/DESCRIPTION b/DESCRIPTION index c0de556..e6dfa5a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: envsetup Title: Support the Setup of the R Environment for Clinical Trial Programming Workflows -Version: 0.2.0 +Version: 0.2.1 Authors@R: c( person("Nicholas", "Masel", email = "nmasel@its.jnj.com", role = c("aut", "cre")), person("Mike", "Stackhouse", email = "mike.stackhouse@atorusresearch.com", role = c("aut"), comment = c(ORCID = "0000-0001-6030-723X")), @@ -17,13 +17,14 @@ Description: The purpose of this package is to support the setup the R environme License: Apache License 2.0 Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Imports: config, fs, purrr, rlang, - usethis + usethis, + roxygen2 Suggests: rmarkdown, testthat, diff --git a/NEWS.md b/NEWS.md index b681d68..23caa15 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,12 @@ +# envsetup 0.2.1 + +- `set_autos()` will now handle NULL hierarchical paths (#66) +- `set_autos()` will account for using `@include` to define function dependencies (#70) + # envsetup 0.2.0 - `library()` will no longer actively reset autos, instead placing newly attached packages in the correct position that respects existing autos (#59) - # envsetup 0.1.0 - Minor updates to prepare for initial CRAN release (#55) diff --git a/R/autos.R b/R/autos.R index 37b30b8..286b79b 100644 --- a/R/autos.R +++ b/R/autos.R @@ -21,16 +21,21 @@ set_autos <- function(autos, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) # Must be named list if (!is_named(autos)) { - stop("Paths for autos in your envsetup configuration file must be named", call.=FALSE) + stop("Paths for autos in your envsetup configuration file must be named", call. = FALSE) } + # remove NULL before further processing + # NULL is expected for hierarchical paths when running in an environment + # after the first level of the hierarchy + autos <- autos[!vapply(autos, is.null, FALSE)] + for (i in seq_along(autos)) { cur_autos <- autos[[i]] if (length(cur_autos) > 1) { # Hierarchical paths must be named if (!is_named(cur_autos)) { - stop("Hierarchical autos paths in your envsetup configuration file must be named", call.=FALSE) + stop("Hierarchical autos paths in your envsetup configuration file must be named", call. = FALSE) } # envsetup_environ must be used if using hierarchical paths @@ -43,7 +48,7 @@ set_autos <- function(autos, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) } if (!is.null(names(cur_autos)) && !envsetup_environ %in% names(cur_autos) - && envsetup_environ != ""){ + && envsetup_environ != "") { warning(paste( "The", ui_field(names(autos[i])), "autos has named", "environments", ui_field(names(cur_autos)), @@ -85,6 +90,30 @@ set_autos <- function(autos, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) ) } +#' Source order of functions +#' +#' This function is used to define the sorting order of functions if +#' `@include` is used to define function dependencies. +#' +#' @param path Directory path +#' @noRd +collate_func <- function(path){ + r_scripts <- list.files(path, + pattern = ".r$", + ignore.case = TRUE, + full.names = TRUE + ) + + collated_func <- roxygen2:::generate_collate(path) + + if (is.null(collated_func)) { + r_scripts + } else { + sapply(1:length(collated_func), function(x) file.path(path, collated_func[[x]])) + } + +} + #' Attach a function directory #' @@ -105,22 +134,17 @@ attach_auto <- function(path, name) { if (!(dir.exists(path) || file.exists(path))) { # Check if the auto actually exists warning(sprintf("An autos path specified in your envsetup configuration file does not exist: %s = %s", name, path), - call.=FALSE) + call. = FALSE) } else if (file.exists(path) && !dir.exists(path)) { # if file, source it sys.source(path, envir = attach(NULL, name = name_with_prefix)) message("Attaching functions from ", path, " to ", name_with_prefix) } else { - # Find all the R files in the given path - r_scripts <- list.files(path, - pattern = ".r$", - ignore.case = TRUE, - full.names = TRUE - ) + collated_r_scripts <- collate_func(path) - if (!identical(r_scripts, character(0))) { - walk(r_scripts, + if (!identical(collated_r_scripts, character(0))) { + walk(collated_r_scripts, sys.source, envir = attach(NULL, name = name_with_prefix) ) @@ -282,13 +306,13 @@ detach_autos <- function() { #' # see autos are still above purrr in the search path #' search() library <- function(..., pos = NULL) { - if(is.null(pos)) { - ## we have at least one package loaded (envsetup itself) - ## use earliest current package position as place to - ## attach all future packages, regardless of what - ## envsetup, devtools, or anything else has put - ## in front of them - pos <- min(grep("^package:", search())) + if (is.null(pos)) { + ## we have at least one package loaded (envsetup itself) + ## use earliest current package position as place to + ## attach all future packages, regardless of what + ## envsetup, devtools, or anything else has put + ## in front of them + pos <- min(grep("^package:", search())) } base::library(..., pos = pos) -} \ No newline at end of file +} diff --git a/R/init.R b/R/init.R index f701308..a659141 100644 --- a/R/init.R +++ b/R/init.R @@ -68,7 +68,7 @@ init <- function(project, config_path = NULL, create_paths = NULL) { ui_info( c("The following paths in your configuration do not exist:", paths[missing_directories]) - ) + ) # if not, ask if user would like them built if (is.null(create_paths)) { @@ -76,7 +76,7 @@ init <- function(project, config_path = NULL, create_paths = NULL) { usethis::ui_yeah( "Would you like us to create your directories to match your configuration?", n_no = 1 - ) + ) } if (!create_paths) { @@ -134,7 +134,8 @@ envsetup_write_rprofile <- function(add, file) { # if there is a call to `rprofile()` in the .Rprofile, assume setup was already done and exit if (any(grepl("rprofile\\(", before))) { - warning("It looks like your project has already been initialized to use envsetup. Manually adjust your .Rprofile if you need to change the environment setup.", + warning("It looks like your project has already been initialized to use envsetup. + Manually adjust your .Rprofile if you need to change the environment setup.", call. = FALSE ) return(invisible()) diff --git a/R/paths.R b/R/paths.R index 25063cf..241cca3 100644 --- a/R/paths.R +++ b/R/paths.R @@ -218,7 +218,7 @@ object_in_path <- function(path, object) { #' Build directory structure from a configuration file #' -#' @param config configuration object from config::get() containing paths#' +#' @param config configuration object from config::get() containing paths #' @param root root directory to build from. #' Leave as NULL if using absolute paths. Set to working directory if using relative paths. #' diff --git a/R/utils.R b/R/utils.R index c4055bd..6a2e92c 100644 --- a/R/utils.R +++ b/R/utils.R @@ -63,8 +63,6 @@ #' validate_config(config::get(file = file.path(tmpdir, "no_paths.yml"))) validate_config <- function(config) { validate_paths(config) - - # validate_autos(config) } diff --git a/man/build_from_config.Rd b/man/build_from_config.Rd index 798d83e..8dd39ca 100644 --- a/man/build_from_config.Rd +++ b/man/build_from_config.Rd @@ -7,7 +7,7 @@ build_from_config(config, root = NULL) } \arguments{ -\item{config}{configuration object from config::get() containing paths#'} +\item{config}{configuration object from config::get() containing paths} \item{root}{root directory to build from. Leave as NULL if using absolute paths. Set to working directory if using relative paths.} diff --git a/tests/testthat/_snaps/R4.4/autos.md b/tests/testthat/_snaps/R4.4/autos.md new file mode 100644 index 0000000..018ac22 --- /dev/null +++ b/tests/testthat/_snaps/R4.4/autos.md @@ -0,0 +1,8 @@ +# Autos warns user when ENVSETUP_ENVIRON does not match named environments in autos + + Code + suppressMessages(rprofile(custom_name)) + Condition + Warning: + The projects autos has named environments DEV, QA, PROD that do not match with the envsetup_environ parameter or ENVSETUP_ENVIRON environment variable bad_name + diff --git a/tests/testthat/_snaps/R4.4/init.md b/tests/testthat/_snaps/R4.4/init.md new file mode 100644 index 0000000..55cdbd6 --- /dev/null +++ b/tests/testthat/_snaps/R4.4/init.md @@ -0,0 +1,57 @@ +# init creates a .Rprofile + + Code + init(init_tmpdir, config_path, create_paths = FALSE) + Message + v Configuration file found! + i The following paths in your configuration do not exist: + /DEV/username/project1/data + /PROD/project1/data + /DEV/username/project1/programs + /PROD/project1/programs + /DEV/username/project1/functions + /PROD/project1/functions + /DEV/username/project1/output + /PROD/project1/output + i All path objects will not work since directories are missing. + v .Rprofile created + v envsetup initialization complete + +# init initializes an .Rprofile correcty when one does not exist + + Code + init(init_tmpdir, config_path, create_paths = FALSE) + Message + v Configuration file found! + i The following paths in your configuration do not exist: + /DEV/username/project1/data + /PROD/project1/data + /DEV/username/project1/programs + /PROD/project1/programs + /DEV/username/project1/functions + /PROD/project1/functions + /DEV/username/project1/output + /PROD/project1/output + i All path objects will not work since directories are missing. + v .Rprofile created + v envsetup initialization complete + +# init initializes an .Rprofile correcty when one does exist + + Code + init(init_tmpdir, config_path, create_paths = FALSE) + Message + v Configuration file found! + i The following paths in your configuration do not exist: + /DEV/username/project1/data + /PROD/project1/data + /DEV/username/project1/programs + /PROD/project1/programs + /DEV/username/project1/functions + /PROD/project1/functions + /DEV/username/project1/output + /PROD/project1/output + i All path objects will not work since directories are missing. + v .Rprofile updated + v envsetup initialization complete + diff --git a/tests/testthat/man/_envsetup_testthat_null.yml b/tests/testthat/man/_envsetup_testthat_null.yml new file mode 100644 index 0000000..8a2cb1d --- /dev/null +++ b/tests/testthat/man/_envsetup_testthat_null.yml @@ -0,0 +1,33 @@ +default: + project_path: !expr Sys.setenv(ENVSETUP_PROJECT_PATH = Sys.getenv("testpath")); + Sys.getenv("testpath") + paths: + data: !expr list( + DEV = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "DEV", "data"), + QA = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "QA", "data"), + PROD = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "PROD", "data") + ) + programs: !expr list( + DEV = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "DEV", "programs"), + QA = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "QA", "programs"), + PROD = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "PROD", "programs") + ) + functions: !expr list( + DEV = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "DEV", "functions"), + QA = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "QA", "functions"), + PROD = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "PROD", "functions") + ) + output: !expr list( + DEV = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "DEV", "output"), + QA = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "QA", "output"), + PROD = file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "PROD", "output") + ) + autos: + projects: !expr NULL + global: !expr file.path(Sys.getenv("ENVSETUP_PROJECT_PATH"), "global", "functions") + envre: !expr file.path( + Sys.getenv("ENVSETUP_PROJECT_PATH"), + "PROD", + "functions", + "envre.R" + ) diff --git a/tests/testthat/man/testdir/DEV/functions/inc1.R b/tests/testthat/man/testdir/DEV/functions/inc1.R new file mode 100644 index 0000000..25714da --- /dev/null +++ b/tests/testthat/man/testdir/DEV/functions/inc1.R @@ -0,0 +1,3 @@ +#' @include inc2.R inc3.R +inc1 <- function(){ +} diff --git a/tests/testthat/man/testdir/DEV/functions/inc2.R b/tests/testthat/man/testdir/DEV/functions/inc2.R new file mode 100644 index 0000000..8eceb1b --- /dev/null +++ b/tests/testthat/man/testdir/DEV/functions/inc2.R @@ -0,0 +1,3 @@ +inc2 <- function(){ + print("inc2") +} diff --git a/tests/testthat/man/testdir/DEV/functions/inc3.R b/tests/testthat/man/testdir/DEV/functions/inc3.R new file mode 100644 index 0000000..7acd3fa --- /dev/null +++ b/tests/testthat/man/testdir/DEV/functions/inc3.R @@ -0,0 +1,3 @@ +inc3 <- function(){ + print("inc3") +} diff --git a/tests/testthat/man/testdir/QA/functions/inc1.R b/tests/testthat/man/testdir/QA/functions/inc1.R new file mode 100644 index 0000000..69d9d41 --- /dev/null +++ b/tests/testthat/man/testdir/QA/functions/inc1.R @@ -0,0 +1,2 @@ +inc1 <- function(){ +} diff --git a/tests/testthat/man/testdir/QA/functions/inc2.R b/tests/testthat/man/testdir/QA/functions/inc2.R new file mode 100644 index 0000000..8eceb1b --- /dev/null +++ b/tests/testthat/man/testdir/QA/functions/inc2.R @@ -0,0 +1,3 @@ +inc2 <- function(){ + print("inc2") +} diff --git a/tests/testthat/man/testdir/QA/functions/inc3.R b/tests/testthat/man/testdir/QA/functions/inc3.R new file mode 100644 index 0000000..7acd3fa --- /dev/null +++ b/tests/testthat/man/testdir/QA/functions/inc3.R @@ -0,0 +1,3 @@ +inc3 <- function(){ + print("inc3") +} diff --git a/tests/testthat/ref/envre.R b/tests/testthat/ref/envre.R index 66013a1..5e8ad79 100644 --- a/tests/testthat/ref/envre.R +++ b/tests/testthat/ref/envre.R @@ -2,14 +2,14 @@ test_envre <- list() test_envre$PDEV <- get_path( topdrv, "PDEV", username, compound, protocol, dbrelease, rpteff, "analysis" - ) +) test_envre$PREPROD <- get_path( topdrv, "PREPROD", NA, compound, protocol, dbrelease, rpteff, "analysis" - ) +) test_envre$PROD <- get_path( topdrv, "PROD", NA, compound, protocol, dbrelease, rpteff, "analysis" - ) +) envre_loc <- "pdev" diff --git a/tests/testthat/ref/envre_preprod.R b/tests/testthat/ref/envre_preprod.R index c9a8db6..ac7450b 100644 --- a/tests/testthat/ref/envre_preprod.R +++ b/tests/testthat/ref/envre_preprod.R @@ -2,14 +2,14 @@ test_envre <- list() test_envre$PDEV <- get_path( topdrv, "PDEV", username, compound, protocol, dbrelease, rpteff, "analysis" - ) +) test_envre$PREPROD <- get_path( topdrv, "PREPROD", NA, compound, protocol, dbrelease, rpteff, "analysis" - ) +) test_envre$PROD <- get_path( topdrv, "PROD", NA, compound, protocol, dbrelease, rpteff, "analysis" - ) +) envre_loc <- "preprod" diff --git a/tests/testthat/ref/envre_prod.R b/tests/testthat/ref/envre_prod.R index 054316e..dbf3f1e 100644 --- a/tests/testthat/ref/envre_prod.R +++ b/tests/testthat/ref/envre_prod.R @@ -2,14 +2,14 @@ test_envre <- list() test_envre$PDEV <- get_path( topdrv, "PDEV", username, compound, protocol, dbrelease, rpteff, "analysis" - ) +) test_envre$PREPROD <- get_path( topdrv, "PREPROD", NA, compound, protocol, dbrelease, rpteff, "analysis" - ) +) test_envre$PROD <- get_path( topdrv, "PROD", NA, compound, protocol, dbrelease, rpteff, "analysis" - ) +) envre_loc <- "prod" diff --git a/tests/testthat/ref/func.R b/tests/testthat/ref/func.R index 652f88e..2748a13 100644 --- a/tests/testthat/ref/func.R +++ b/tests/testthat/ref/func.R @@ -1,3 +1,3 @@ func <- function() { print(rlang::env_parent()) - } +} diff --git a/tests/testthat/test-autos.R b/tests/testthat/test-autos.R index 52ea3bb..7597ad2 100644 --- a/tests/testthat/test-autos.R +++ b/tests/testthat/test-autos.R @@ -8,6 +8,7 @@ file.copy(testthat::test_path("man/testdir/DEV"), tmpdir, recursive = TRUE) file.copy(testthat::test_path("man/testdir/global"), tmpdir, recursive = TRUE) file.copy(testthat::test_path("man/testdir/PROD"), tmpdir, recursive = TRUE) file.copy(testthat::test_path("man/testdir/QA"), tmpdir, recursive = TRUE) +dir.create(file.path(tmpdir, "returns_null")) custom_name <- config::get( file = testthat::test_path("man/_envsetup_testthat.yml") @@ -24,6 +25,34 @@ test_that("Autos set and test_dev from highest level appears correctly", { expect_equal(c(test_global()), c("Test of global autos")) }) + +#' @editor Nick Masel +#' @editDate 2024-12-30 +test_that("Order of functions appears correctly when @include is used", { + dev_order <- collate_func(custom_name$autos$projects$DEV) + expect_equal(dev_order, + c(file.path(tmpdir, "DEV/functions/TestDev.R"), + file.path(tmpdir, "DEV/functions/inc3.R"), + file.path(tmpdir, "DEV/functions/inc2.R"), + file.path(tmpdir, "DEV/functions/inc1.R") + ) + ) +}) + + +#' @editor Nick Masel +#' @editDate 2024-12-30 +test_that("Order of functions appears correctly when @include is not used", { + qa_order <- collate_func(custom_name$autos$projects$QA) + expect_equal(qa_order, + c(file.path(tmpdir, "QA/functions/QATest.R"), + file.path(tmpdir, "QA/functions/inc1.R"), + file.path(tmpdir, "QA/functions/inc2.R"), + file.path(tmpdir, "QA/functions/inc3.R") + ) + ) +}) + #' @editor Gabe Becker #' @editDate 2023-11-22 test_that("library returns invisibly", { @@ -45,13 +74,16 @@ test_that("Autos validation from yml happens correctly", { # Hierarchical list is named expect_error( - set_autos(list(project = c("path1", "path2"))), "Hierarchical autos paths in your envsetup configuration file must be named" + set_autos(list(project = c("path1", "path2"))), + "Hierarchical autos paths in your envsetup configuration file must be named" ) # Paths are characters - expect_error(set_autos(list(global = 1)), "Paths provided for autos must be directories") + expect_error(set_autos(list(global = 1)), + "Paths provided for autos must be directories") - expect_warning(set_autos(list(x = "/bad/path/")), "An autos path specified in your envsetup configuration file does not exist") + expect_warning(set_autos(list(x = "/bad/path/")), + "An autos path specified in your envsetup configuration file does not exist") }) # Detatch and re-setup for QA now @@ -108,16 +140,17 @@ test_that("Autos no longer exist when detached", { test_that("the configuration can be named anything and library will reattach the autos correctly", { - suppressMessages(rprofile(custom_name)) + suppressMessages(rprofile(custom_name)) - expect_invisible(suppressPackageStartupMessages(library("purrr"))) + expect_invisible(suppressPackageStartupMessages(library("purrr"))) - purrr_location <- which(search() == "package:purrr") - autos_locatios <- which(grepl("^autos:", search())) + purrr_location <- which(search() == "package:purrr") + autos_locatios <- which(grepl("^autos:", search())) - expect_true(all(purrr_location > autos_locatios)) - detach("package:purrr") -}) + expect_true(all(purrr_location > autos_locatios)) + detach("package:purrr") + } +) test_that("Autos warns user when ENVSETUP_ENVIRON does not match named environments in autos", { @@ -125,3 +158,15 @@ test_that("Autos warns user when ENVSETUP_ENVIRON does not match named environme expect_snapshot(suppressMessages(rprofile(custom_name)), variant = r_version()) }) + + +#' @editor Nick Masel +#' @editDate 2024-10-24 +detach_autos() +Sys.setenv(ENVSETUP_ENVIRON = "QA") +null_test <- config::get( + file = testthat::test_path("man/_envsetup_testthat_null.yml") +) +test_that("NULL paths do not throw an error", { + expect_no_error(set_autos(null_test$autos)) +}) diff --git a/tests/testthat/test-paths.R b/tests/testthat/test-paths.R index 51b9805..1231a0a 100644 --- a/tests/testthat/test-paths.R +++ b/tests/testthat/test-paths.R @@ -86,11 +86,16 @@ test_that("1.1", { #' @editor Aidan Ceney #' @editDate 2022-05-12 test_that("1.2", { - expect_error(readr::read_csv(read_path(data, - "iris.csv", - full.path = TRUE, - envsetup_environ = "PROD" - ))) + expect_error( + readr::read_csv( + read_path( + data, + "iris.csv", + full.path = TRUE, + envsetup_environ = "PROD" + ) + ) + ) }) #' @editor Aidan Ceney diff --git a/tests/testthat/test-rprofile.R b/tests/testthat/test-rprofile.R index d156b13..1011f40 100644 --- a/tests/testthat/test-rprofile.R +++ b/tests/testthat/test-rprofile.R @@ -41,7 +41,7 @@ test_that("rprofile stores the configuration", { stored_config <- base::get( "auto_stored_envsetup_config", pos = which(search() == "envsetup:paths") - ) + ) expect_equal(custom_name, stored_config) }) diff --git a/vignettes/config.Rmd b/vignettes/config.Rmd index d58a407..6639130 100644 --- a/vignettes/config.Rmd +++ b/vignettes/config.Rmd @@ -155,11 +155,11 @@ writeLines( paste0( "default: paths: - data: !expr list(DEV = '",dir,"/demo/DEV/username/project1/data', PROD = '",dir,"/demo/PROD/project1/data') - output: '",dir,"/demo/DEV/username/project1/output' - programs: '",dir,"/demo/DEV/username/project1/programs' + data: !expr list(DEV = '", dir,"/demo/DEV/username/project1/data', PROD = '", dir, "/demo/PROD/project1/data') + output: '", dir, "/demo/DEV/username/project1/output' + programs: '", dir, "/demo/DEV/username/project1/programs' envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'" - ), file_conn) + ), file_conn) close(file_conn) ``` diff --git a/vignettes/tips.Rmd b/vignettes/tips.Rmd new file mode 100644 index 0000000..a7e6299 --- /dev/null +++ b/vignettes/tips.Rmd @@ -0,0 +1,90 @@ +--- +title: "Tips" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Tips} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +# 1: Safely set autos + +If you would like to account for potential syntax errors when sourcing in your `autos`, you can wrap `rprofile()` in `purrr::safely()`. This function will attempt to set the autos and return a list containing the result and any error that occurred. + +Let's show an example. First we'll make an example configuration file that will automatically source your autos. We'll intentionally add a syntax error to show how `safely()` works. + +Here is our configuration file, which will automatically source the autos from the `DEV` and `PROD` directories: + +```{r eval = FALSE} +default: + autos: + projects: !expr list( + "DEV" = file.path("demo", "DEV", "username", "project1", "functions"), + "PROD" = file.path("demo", "PROD", "project1", "functions") + ) +``` + +We will do a little work here to create the directory, place a script into the directory. We'll add a syntax error by leaving off a closing `}` in `test_error.R` script in the PROD folder. + + +```{r} +# create the temp directory +dir <- fs::file_temp() +dir.create(dir) +dir.create(file.path(dir, "/demo/PROD/project1/functions"), recursive = TRUE) + +# write a function to the folder with an error +file_conn <- file(file.path(dir, "/demo/PROD/project1/functions/test_error.R")) +writeLines( +"test <- function(){print('test')", file_conn) +close(file_conn) + +# write the config +config_path <- file.path(dir, "_envsetup.yml") +file_conn <- file(config_path) +writeLines( + paste0( +"default: + autos: + PROD: '", dir,"/demo/PROD/project1/functions'" + ), file_conn) +close(file_conn) +``` + +So if we call `rprofile()` passing in this config file, we will get an error because of the syntax error in `test_error.R`: + +```{r error = TRUE} +library(envsetup) + +envsetup_config <- config::get(file = config_path) + +rprofile(envsetup_config) +``` + +To handle this error, we can use `purrr::safely()` to wrap the `rprofile()` function. This will allow us to catch the error and handle it gracefully. + +```{r setup} +safely_rprofile <- purrr::safely(rprofile) + +ret <- safely_rprofile(envsetup_config) +``` + +We still have an error, but safely allow the setup to continue. We can check the result of the `safely_rprofile()` function to see if there was an error, identify the issue and correct the syntax error in the function. + +```{r check} +# check for errors and return if any occurred +if(!is.null(ret$error)) { + print(ret$error) +} +``` + +```{r echo = FALSE} +unlink(dir, recursive=TRUE) +```