From 65d9117f60c36b4ccfa39976e8fb0b61f405f9da Mon Sep 17 00:00:00 2001 From: Nicholas Masel Date: Wed, 9 Jul 2025 11:46:09 -0400 Subject: [PATCH 1/3] move paths to an environment w/in envsetup --- R/paths.R | 10 +++++++--- R/rprofile.R | 22 ++++++++++++++++------ tests/testthat/test-rprofile.R | 11 +++-------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/R/paths.R b/R/paths.R index 241cca3..aa306cf 100644 --- a/R/paths.R +++ b/R/paths.R @@ -1,3 +1,7 @@ +#' Path environment +#' @export +envsetup_path_environment <- new.env(parent = emptyenv()) + #' Read path #' #' Check each environment for the file and return the path to the first. @@ -56,7 +60,7 @@ read_path <- function(lib, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) { # lib can be a object in a different environment - # get this directly from envsetup:paths + # get this directly from envsetup_path_environment lib_arg <- quo_get_expr(enquo(lib)) if (is_string(lib_arg)) { @@ -66,7 +70,7 @@ read_path <- function(lib, ), call. = FALSE) } - read_lib <- base::get(toString(lib_arg), "envsetup:paths") + read_lib <- base::get(toString(lib_arg), envsetup_path_environment) restricted_paths <- read_lib @@ -174,7 +178,7 @@ write_path <- function(lib, filename = NULL, envsetup_environ = Sys.getenv("ENVS ), call. = FALSE) } - write_path <- base::get(toString(lib_arg), "envsetup:paths") + write_path <- base::get(toString(lib_arg), envsetup_path_environment) path <- write_path if (length(write_path) > 1 && envsetup_environ == "") { diff --git a/R/rprofile.R b/R/rprofile.R index 0084f4b..afba59c 100644 --- a/R/rprofile.R +++ b/R/rprofile.R @@ -38,16 +38,26 @@ rprofile <- function(config) { (pos <- 2L) } - attach(config_minus_autos$paths, - name = "envsetup:paths", - pos = pos - ) + # browser() - message("Attaching paths to envsetup:paths") + walk2(names(config_minus_autos$paths), + config_minus_autos$paths, + assign, + envir = envsetup_path_environment) + # + # assign(names(config_minus_autos$paths)[[1]], config_minus_autos$paths[[1]], + # envir = envsetup_path_environment) + # + # attach(config_minus_autos$paths, + # name = "envsetup:paths", + # pos = pos + # ) + + message("Assigned paths to envsetup_path_environment") # store config with a standard name in a standard location # this will allow `envsetup::library()` to re-attach autos - assign("auto_stored_envsetup_config", config, pos) + assign("auto_stored_envsetup_config", config, envir = envsetup_path_environment) # If autos exist, set them if (!is.null(config$autos)) { diff --git a/tests/testthat/test-rprofile.R b/tests/testthat/test-rprofile.R index 1011f40..bc9fb82 100644 --- a/tests/testthat/test-rprofile.R +++ b/tests/testthat/test-rprofile.R @@ -40,7 +40,8 @@ test_that("rprofile stores the configuration", { stored_config <- base::get( "auto_stored_envsetup_config", - pos = which(search() == "envsetup:paths") + envsetup_path_environment + # pos = which(search() == "envsetup:paths") ) expect_equal(custom_name, stored_config) @@ -52,15 +53,13 @@ test_that("rprofile stores the configuration", { test_that("1.1", { rprofile(envsetup_config) - withr::defer(detach(envsetup:paths)) - expected <- list() folder <- "data" expected$DEV <- file.path(tmpdir, rootpath_dev, folder) expected$QA <- file.path(tmpdir, rootpath_qa, folder) expected$PROD <- file.path(tmpdir, rootpath_prod, folder) - expect_identical(expected, data) + expect_identical(expected, envsetup_path_environment$data) }) @@ -70,8 +69,6 @@ test_that("2.1", { Sys.setenv(ENVSETUP_ENVIRON = "DEV") rprofile(envsetup_config) - withr::defer(detach(envsetup:paths)) - expect_equal(c(test_dev()), c("Test of dev autos")) }) @@ -89,7 +86,5 @@ test_that("3.1", { read_path(data, "iris.csv", full.path = TRUE, envsetup_environ = "DEV") ) - withr::defer(detach(envsetup:paths)) - expect_equal(tidyr::as_tibble(iris)$Petal.Length, readin$Petal.Length) }) From 0ef8b8d68d07f8b62290a7626f350f2102f6a6f2 Mon Sep 17 00:00:00 2001 From: Nicholas Masel Date: Tue, 15 Jul 2025 16:10:18 -0400 Subject: [PATCH 2/3] add get_path and align environment name --- R/paths.R | 14 ++++++++++---- R/rprofile.R | 16 +++------------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/R/paths.R b/R/paths.R index aa306cf..c249647 100644 --- a/R/paths.R +++ b/R/paths.R @@ -1,6 +1,12 @@ #' Path environment #' @export -envsetup_path_environment <- new.env(parent = emptyenv()) +envsetup_environment <- new.env() + +#' Get a path from the envsetup environment +#' @export +get_path <- function(path){ + base::get(substitute(path), envsetup_environment) +} #' Read path #' @@ -60,7 +66,7 @@ read_path <- function(lib, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) { # lib can be a object in a different environment - # get this directly from envsetup_path_environment + # get this directly from envsetup_environment lib_arg <- quo_get_expr(enquo(lib)) if (is_string(lib_arg)) { @@ -70,7 +76,7 @@ read_path <- function(lib, ), call. = FALSE) } - read_lib <- base::get(toString(lib_arg), envsetup_path_environment) + read_lib <- base::get(toString(lib_arg), envsetup_environment) restricted_paths <- read_lib @@ -178,7 +184,7 @@ write_path <- function(lib, filename = NULL, envsetup_environ = Sys.getenv("ENVS ), call. = FALSE) } - write_path <- base::get(toString(lib_arg), envsetup_path_environment) + write_path <- base::get(toString(lib_arg), envsetup_environment) path <- write_path if (length(write_path) > 1 && envsetup_environ == "") { diff --git a/R/rprofile.R b/R/rprofile.R index afba59c..38082c4 100644 --- a/R/rprofile.R +++ b/R/rprofile.R @@ -38,26 +38,16 @@ rprofile <- function(config) { (pos <- 2L) } - # browser() - walk2(names(config_minus_autos$paths), config_minus_autos$paths, assign, - envir = envsetup_path_environment) - # - # assign(names(config_minus_autos$paths)[[1]], config_minus_autos$paths[[1]], - # envir = envsetup_path_environment) - # - # attach(config_minus_autos$paths, - # name = "envsetup:paths", - # pos = pos - # ) + envir = envsetup_environment) - message("Assigned paths to envsetup_path_environment") + message("Assigned paths to envsetup_environment") # store config with a standard name in a standard location # this will allow `envsetup::library()` to re-attach autos - assign("auto_stored_envsetup_config", config, envir = envsetup_path_environment) + assign("auto_stored_envsetup_config", config, envir = envsetup_environment) # If autos exist, set them if (!is.null(config$autos)) { From f681554198ab5dbf43115cf3d16592e5f39f07d4 Mon Sep 17 00:00:00 2001 From: Nicholas Masel Date: Fri, 18 Jul 2025 09:50:12 -0400 Subject: [PATCH 3/3] default to global --- .github/workflows/test-coverage.yaml | 82 ++++++++----------- NAMESPACE | 2 + R/envsetup.R | 3 +- R/paths.R | 79 ++++++++++++++++-- R/rprofile.R | 20 ++--- README.Rmd | 97 ++++++++++++++++------ README.md | 117 ++++++++++++++++++++------- man/envsetup_environment.Rd | 39 +++++++++ man/get_path.Rd | 48 +++++++++++ man/read_path.Rd | 3 +- man/rprofile.Rd | 2 +- man/write_path.Rd | 3 +- tests/testthat/test-autos.R | 9 +++ tests/testthat/test-paths.R | 80 ++++++++++++++++++ tests/testthat/test-rprofile.R | 13 +-- 15 files changed, 464 insertions(+), 133 deletions(-) create mode 100644 man/envsetup_environment.Rd create mode 100644 man/get_path.Rd diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 5e9a7e5..97a755d 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,62 +1,50 @@ -# 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 +name: test-coverage + on: push: - branches: [main, master] + branches: [main] pull_request: - -name: test-coverage.yaml - -permissions: read-all + branches: [main] jobs: - test-coverage: + coverage: runs-on: ubuntu-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + + permissions: + contents: write steps: - uses: actions/checkout@v4 - - uses: r-lib/actions/setup-r@v2 - with: - use-public-rspm: true + - name: Set up R + uses: r-lib/actions/setup-r@v2 - - uses: r-lib/actions/setup-r-dependencies@v2 - with: - extra-packages: any::covr, any::xml2 - needs: coverage + - name: Install system dependencies + uses: r-lib/actions/setup-r-dependencies@v2 - - 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() + - name: Install dependencies run: | - ## -------------------------------------------------------------------- - find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true - shell: bash + Rscript -e 'install.packages(c("remotes", "covr", " testthat", "DT", "htmltools"))' + Rscript -e 'remotes::install_deps(dependencies = TRUE)' - - name: Upload test results - if: failure() - uses: actions/upload-artifact@v4 + + - name: Run coverage and generate badge + run: | + mkdir -p coverage + Rscript -e ' + cov <- covr::package_coverage(type = "tests") + covr::to_cobertura(cov, "coverage/cobertura.xml") + covr::report(cov, file = "coverage/coverage.html") + pct <- round(covr::percent_coverage(cov)) + col <- if (pct >= 90) "brightgreen" else if (pct >= 75) "orange" else "red" + url <- sprintf("https://img.shields.io/badge/coverage-%s%%25-%s.svg", pct, col) + download.file(url, "coverage/badge.svg", quiet = TRUE) + ' + + - name: Deploy badge and reports to gh-pages + uses: peaceiris/actions-gh-pages@v3 with: - name: coverage-test-failures - path: ${{ runner.temp }}/package + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./coverage + publish_branch: gh-pages + destination_dir: _xml_coverage_reports/ diff --git a/NAMESPACE b/NAMESPACE index d2363d1..b800bdd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,6 +2,8 @@ export(build_from_config) export(detach_autos) +export(envsetup_environment) +export(get_path) export(init) export(library) export(read_path) diff --git a/R/envsetup.R b/R/envsetup.R index 76c5ce0..2ce4e18 100644 --- a/R/envsetup.R +++ b/R/envsetup.R @@ -6,7 +6,8 @@ mustWork = TRUE ), envsetup.rprofile.path = system.file(".Rprofile", package = "envsetup"), - envsetup.renviron.path = system.file(".Renviron", package = "envsetup") + envsetup.renviron.path = system.file(".Renviron", package = "envsetup"), + envsetup.path.environment = .GlobalEnv ) toset <- !(names(op.envsetup) %in% names(op)) diff --git a/R/paths.R b/R/paths.R index c249647..0b4f340 100644 --- a/R/paths.R +++ b/R/paths.R @@ -1,11 +1,72 @@ -#' Path environment +#' Environment Setup Environment +#' +#' A dedicated environment object used to store and manage path configurations +#' and other setup variables for the envsetup package. This environment provides +#' an isolated namespace for storing path objects that can be retrieved using +#' the package's path management functions. +#' +#' @format An environment object created with \code{new.env()}. +#' +#' @details +#' This environment serves as the default storage location for path objects +#' when using envsetup package functions. It helps maintain clean separation +#' between user workspace and package-managed paths. +#' +#' @examples +#' # Store a path in the envsetup environment +#' assign("project_root", "/path/to/project", envir = envsetup_environment) +#' +#' # List objects in the environment +#' ls(envir = envsetup_environment) +#' +#' # Check if the environment exists and is an environment +#' exists("envsetup_environment") +#' is.environment(envsetup_environment) +#' +#' @seealso \code{\link{get_path}}, \code{\link[base]{new.env}} +#' #' @export envsetup_environment <- new.env() -#' Get a path from the envsetup environment +#' Get Path Object from Environment +#' +#' Retrieves a path object from the specified environment using non-standard +#' evaluation. The function uses `substitute()` to capture the unevaluated +#' expression and `get()` to retrieve the corresponding object. +#' +#' @param path An unquoted name of the path object to retrieve from the environment. +#' @param envir The environment to search for the path object. Defaults to the +#' value of `getOption("envsetup.path.environment")`. +#' +#' @return The path object stored in the specified environment under the given name. +#' +#' @examples +#' # Create a custom environment and store some paths +#' path_env <- new.env() +#' assign("data_dir", "/home/user/data", envir = path_env) +#' assign("output_dir", "/home/user/output", envir = path_env) +#' +#' # Set up the option to use our custom environment +#' options(envsetup.path.environment = path_env) +#' +#' # Retrieve paths using the function +#' data_path <- get_path(data_dir) +#' output_path <- get_path(output_dir) +#' +#' print(data_path) # "/home/user/data" +#' print(output_path) # "/home/user/output" +#' +#' # Using with a different environment +#' temp_env <- new.env() +#' assign("temp_dir", "/tmp/analysis", envir = temp_env) +#' temp_path <- get_path(temp_dir, envir = temp_env) +#' print(temp_path) # "/tmp/analysis" +#' +#' @seealso \code{\link[base]{get}}, \code{\link[base]{substitute}} +#' #' @export -get_path <- function(path){ - base::get(substitute(path), envsetup_environment) +get_path <- function(path, envir = getOption("envsetup.path.environment")){ + base::get(substitute(path), envir) } #' Read path @@ -63,7 +124,8 @@ get_path <- function(path){ read_path <- function(lib, filename, full.path = TRUE, - envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) { + envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON"), + envir = getOption("envsetup.path.environment")) { # lib can be a object in a different environment # get this directly from envsetup_environment @@ -76,7 +138,7 @@ read_path <- function(lib, ), call. = FALSE) } - read_lib <- base::get(toString(lib_arg), envsetup_environment) + read_lib <- base::get(toString(lib_arg), envir) restricted_paths <- read_lib @@ -172,7 +234,8 @@ read_path <- function(lib, #' #' # save data in data folder using write_path #' saveRDS(mtcars, write_path(data, "mtcars.rds")) -write_path <- function(lib, filename = NULL, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) { +write_path <- function(lib, filename = NULL, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON"), + envir = getOption("envsetup.path.environment")) { # examine lib to ensure it's not a string # if it's a string, you end up with an incorrect path lib_arg <- quo_get_expr(enquo(lib)) @@ -184,7 +247,7 @@ write_path <- function(lib, filename = NULL, envsetup_environ = Sys.getenv("ENVS ), call. = FALSE) } - write_path <- base::get(toString(lib_arg), envsetup_environment) + write_path <- base::get(toString(lib_arg), envir) path <- write_path if (length(write_path) > 1 && envsetup_environ == "") { diff --git a/R/rprofile.R b/R/rprofile.R index 38082c4..694ced8 100644 --- a/R/rprofile.R +++ b/R/rprofile.R @@ -22,32 +22,22 @@ #' writeLines(hierarchy, file.path(tmpdir, "hierarchy.yml")) #' #' rprofile(config::get(file = file.path(tmpdir, "hierarchy.yml"))) -rprofile <- function(config) { - if ("envsetup:paths" %in% search()) { - detach("envsetup:paths", character.only = TRUE) - } +rprofile <- function(config, + envir = getOption("envsetup.path.environment")) { # remove autos and pass everything else to "envsetup:paths" config_minus_autos <- config config_minus_autos$autos <- NULL - # attach after package to allow functions from package to be used in config - if (any(search() == "package:envsetup")) { - pos <- which(search() == "package:envsetup") + 1 - } else { - (pos <- 2L) - } - walk2(names(config_minus_autos$paths), config_minus_autos$paths, assign, - envir = envsetup_environment) + envir = envir) - message("Assigned paths to envsetup_environment") + message(paste0("Assigned paths to ", envnames::environment_name(envir))) # store config with a standard name in a standard location - # this will allow `envsetup::library()` to re-attach autos - assign("auto_stored_envsetup_config", config, envir = envsetup_environment) + assign("auto_stored_envsetup_config", config, envir = envir) # If autos exist, set them if (!is.null(config$autos)) { diff --git a/README.Rmd b/README.Rmd index 212f332..cc9db02 100644 --- a/README.Rmd +++ b/README.Rmd @@ -15,33 +15,84 @@ knitr::opts_chunk$set( # envsetup + +[](https://pharmaverse.org) +[![Check ๐Ÿ› ](https://github.com/pharmaverse/envsetup/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/pharmaverse/envsetup/actions/workflows/R-CMD-check.yaml) +[![Docs ๐Ÿ“š](https://github.com/pharmaverse/envsetup/actions/workflows/pkgdown.yaml/badge.svg)](https://pharmaverse.github.io/envsetup/) +[![Code Coverage ๐Ÿ“”](https://raw.githubusercontent.com/pharmaverse/envsetup/refs/heads/gh-pages/_xml_coverage_reports/badge.svg)](https://pharmaverse.github.io/envsetup/_xml_coverage_reports/coverage.html) +![GitHub commit activity](https://img.shields.io/github/commit-activity/m/pharmaverse/envsetup) +![GitHub contributors](https://img.shields.io/github/contributors/pharmaverse/envsetup) +![GitHub last commit](https://img.shields.io/github/last-commit/pharmaverse/envsetup) +![GitHub pull requests](https://img.shields.io/github/issues-pr/pharmaverse/envsetup) +![GitHub repo size](https://img.shields.io/github/repo-size/pharmaverse/envsetup) +[![Project Status: Active โ€“ The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Current Version](https://img.shields.io/github/r-package/v/pharmaverse/envsetup/main?color=purple&label=package%20version)](https://github.com/pharmaverse/envsetup/tree/main) +[![Open Issues](https://img.shields.io/github/issues-raw/pharmaverse/envsetup?color=red&label=open%20issues)](https://github.com/pharmaverse/envsetup/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) +![GitHub forks](https://img.shields.io/github/forks/pharmaverse/envsetup?style=social) +![GitHub repo stars](https://img.shields.io/github/stars/pharmaverse/envsetup?style=social) + - -[](https://pharmaverse.org) - -[![codecov](https://codecov.io/gh/pharmaverse/envsetup/branch/main/graph/badge.svg)](https://app.codecov.io/gh/pharmaverse/envsetup?branch=main) +# Overview - +The `envsetup` package helps you manage R project environments by +providing a flexible configuration system that adapts to different +deployment stages (development, testing, production) without requiring +code changes. -## Overview +## Why Use envsetup? -The purpose of this package is to support the setup of the R environment. The two main features are: +When working on R projects, you often need to: +- Point to different data sources across environments -- `autos` to automatically source files and/or directories into your environment +- Use different output directories -- `paths` to consistently set path objects across projects for I/O +- Load environment-specific functions -Both are implemented using a configuration file to allow easy, custom configurations that can be used for multiple or all projects. +- Maintain consistent code across environments like dev, qa, and prod +Instead of hardcoding paths or manually changing configurations, +`envsetup` uses YAML configuration files to manage these differences +automatically. -## Installation +## Basic Concepts -```{r eval = FALSE} -install.packages("envsetup") +The `envsetup` package works with two main components: + +1. **PATHS**: Manages file system locations (data, output, programs) +2. **AUTOS**: Automatically sources R scripts from specified + directories + +## Your First Configuration + +Here's the simplest possible `_envsetup.yml` configuration: + +``` yaml +default: + paths: + data: "/path/to/your/data" + output: "/path/to/your/output" +``` + +## Quick Start Example + +```{r eval=FALSE} +library(envsetup) + +# Load your configuration +envsetup_config <- config::get(file = "_envsetup.yml") + +# Apply the configuration +rprofile(envsetup_config) + +# Now you can use the configured paths +print(data) # Points to your data directory +print(output) # Points to your output directory ``` +## Installation + ### Development version ```{r eval = FALSE} @@ -49,18 +100,18 @@ install.packages("envsetup") devtools::install_github("pharmaverse/envsetup") ``` +## What's Next? -## Usage +In the following guides, you'll learn: -1. Create the _envsetup.yml configuration file to specify your autos and paths and store centrally. See `vignette("config")` for more details on how to create your config. -2. Create or update your `.Rprofile` to read in the config and call `rprofile()` +- How to set up basic path configurations -```{r eval = FALSE} -library(envsetup) +- Managing multiple environments -# read configuration -envsetup_config <- config::get(file = "path/to/_envsetup.yml") +- Advanced path resolution -# pass configuration to rprofile() to setup the environment -rprofile(envsetup_config) -``` +- Automatic script sourcing + +- Real-world examples and best practices + +Let's start with basic path configuration in the next section. diff --git a/README.md b/README.md index 5bb25c8..c2210d9 100644 --- a/README.md +++ b/README.md @@ -3,33 +3,98 @@ # envsetup - + [](https://pharmaverse.org) - -[![codecov](https://codecov.io/gh/pharmaverse/envsetup/branch/main/graph/badge.svg)](https://app.codecov.io/gh/pharmaverse/envsetup?branch=main) - +[![Check +๐Ÿ› ](https://github.com/pharmaverse/envsetup/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/pharmaverse/envsetup/actions/workflows/R-CMD-check.yaml) +[![Docs +๐Ÿ“š](https://github.com/pharmaverse/envsetup/actions/workflows/pkgdown.yaml/badge.svg)](https://pharmaverse.github.io/envsetup/) +[![Code Coverage +๐Ÿ“”](https://raw.githubusercontent.com/pharmaverse/envsetup/refs/heads/gh-pages/_xml_coverage_reports/badge.svg)](https://pharmaverse.github.io/envsetup/_xml_coverage_reports/coverage.html) +![GitHub commit +activity](https://img.shields.io/github/commit-activity/m/pharmaverse/envsetup) +![GitHub +contributors](https://img.shields.io/github/contributors/pharmaverse/envsetup) +![GitHub last +commit](https://img.shields.io/github/last-commit/pharmaverse/envsetup) +![GitHub pull +requests](https://img.shields.io/github/issues-pr/pharmaverse/envsetup) +![GitHub repo +size](https://img.shields.io/github/repo-size/pharmaverse/envsetup) +[![Project Status: Active โ€“ The project has reached a stable, usable +state and is being actively +developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Current +Version](https://img.shields.io/github/r-package/v/pharmaverse/envsetup/main?color=purple&label=package%20version)](https://github.com/pharmaverse/envsetup/tree/main) +[![Open +Issues](https://img.shields.io/github/issues-raw/pharmaverse/envsetup?color=red&label=open%20issues)](https://github.com/pharmaverse/envsetup/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) +![GitHub +forks](https://img.shields.io/github/forks/pharmaverse/envsetup?style=social) +![GitHub repo +stars](https://img.shields.io/github/stars/pharmaverse/envsetup?style=social) -## Overview +# Overview -The purpose of this package is to support the setup of the R -environment. The two main features are: +The `envsetup` package helps you manage R project environments by +providing a flexible configuration system that adapts to different +deployment stages (development, testing, production) without requiring +code changes. -- `autos` to automatically source files and/or directories into your - environment +## Why Use envsetup? -- `paths` to consistently set path objects across projects for I/O +When working on R projects, you often need to: -Both are implemented using a configuration file to allow easy, custom -configurations that can be used for multiple or all projects. +- Point to different data sources across environments -## Installation +- Use different output directories + +- Load environment-specific functions + +- Maintain consistent code across environments like dev, qa, and prod + +Instead of hardcoding paths or manually changing configurations, +`envsetup` uses YAML configuration files to manage these differences +automatically. + +## Basic Concepts + +The `envsetup` package works with two main components: + +1. **PATHS**: Manages file system locations (data, output, programs) +2. **AUTOS**: Automatically sources R scripts from specified + directories + +## Your First Configuration + +Hereโ€™s the simplest possible `_envsetup.yml` configuration: + +``` yaml +default: + paths: + data: "/path/to/your/data" + output: "/path/to/your/output" +``` + +## Quick Start Example ``` r -install.packages("envsetup") +library(envsetup) + +# Load your configuration +envsetup_config <- config::get(file = "_envsetup.yml") + +# Apply the configuration +rprofile(envsetup_config) + +# Now you can use the configured paths +print(data) # Points to your data directory +print(output) # Points to your output directory ``` +## Installation + ### Development version ``` r @@ -37,20 +102,18 @@ install.packages("envsetup") devtools::install_github("pharmaverse/envsetup") ``` -## Usage +## Whatโ€™s Next? -1. Create the \_envsetup.yml configuration file to specify your autos - and paths and store centrally. See `vignette("config")` for more - details on how to create your config. -2. Create or update your `.Rprofile` to read in the config and call - `rprofile()` +In the following guides, youโ€™ll learn: -``` r -library(envsetup) +- How to set up basic path configurations -# read configuration -envsetup_config <- config::get(file = "path/to/_envsetup.yml") +- Managing multiple environments -# pass configuration to rprofile() to setup the environment -rprofile(envsetup_config) -``` +- Advanced path resolution + +- Automatic script sourcing + +- Real-world examples and best practices + +Letโ€™s start with basic path configuration in the next section. diff --git a/man/envsetup_environment.Rd b/man/envsetup_environment.Rd new file mode 100644 index 0000000..ee44c53 --- /dev/null +++ b/man/envsetup_environment.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/paths.R +\docType{data} +\name{envsetup_environment} +\alias{envsetup_environment} +\title{Environment Setup Environment} +\format{ +An environment object created with \code{new.env()}. +} +\usage{ +envsetup_environment +} +\description{ +A dedicated environment object used to store and manage path configurations +and other setup variables for the envsetup package. This environment provides +an isolated namespace for storing path objects that can be retrieved using +the package's path management functions. +} +\details{ +This environment serves as the default storage location for path objects +when using envsetup package functions. It helps maintain clean separation +between user workspace and package-managed paths. +} +\examples{ +# Store a path in the envsetup environment +assign("project_root", "/path/to/project", envir = envsetup_environment) + +# List objects in the environment +ls(envir = envsetup_environment) + +# Check if the environment exists and is an environment +exists("envsetup_environment") +is.environment(envsetup_environment) + +} +\seealso{ +\code{\link{get_path}}, \code{\link[base]{new.env}} +} +\keyword{datasets} diff --git a/man/get_path.Rd b/man/get_path.Rd new file mode 100644 index 0000000..3367276 --- /dev/null +++ b/man/get_path.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/paths.R +\name{get_path} +\alias{get_path} +\title{Get Path Object from Environment} +\usage{ +get_path(path, envir = getOption("envsetup.path.environment")) +} +\arguments{ +\item{path}{An unquoted name of the path object to retrieve from the environment.} + +\item{envir}{The environment to search for the path object. Defaults to the +value of \code{getOption("envsetup.path.environment")}.} +} +\value{ +The path object stored in the specified environment under the given name. +} +\description{ +Retrieves a path object from the specified environment using non-standard +evaluation. The function uses \code{substitute()} to capture the unevaluated +expression and \code{get()} to retrieve the corresponding object. +} +\examples{ +# Create a custom environment and store some paths +path_env <- new.env() +assign("data_dir", "/home/user/data", envir = path_env) +assign("output_dir", "/home/user/output", envir = path_env) + +# Set up the option to use our custom environment +options(envsetup.path.environment = path_env) + +# Retrieve paths using the function +data_path <- get_path(data_dir) +output_path <- get_path(output_dir) + +print(data_path) # "/home/user/data" +print(output_path) # "/home/user/output" + +# Using with a different environment +temp_env <- new.env() +assign("temp_dir", "/tmp/analysis", envir = temp_env) +temp_path <- get_path(temp_dir, envir = temp_env) +print(temp_path) # "/tmp/analysis" + +} +\seealso{ +\code{\link[base]{get}}, \code{\link[base]{substitute}} +} diff --git a/man/read_path.Rd b/man/read_path.Rd index e646ced..ab8d075 100644 --- a/man/read_path.Rd +++ b/man/read_path.Rd @@ -8,7 +8,8 @@ read_path( lib, filename, full.path = TRUE, - envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON") + envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON"), + envir = getOption("envsetup.path.environment") ) } \arguments{ diff --git a/man/rprofile.Rd b/man/rprofile.Rd index 3a9a3d3..8c68596 100644 --- a/man/rprofile.Rd +++ b/man/rprofile.Rd @@ -4,7 +4,7 @@ \alias{rprofile} \title{Function used to pass through code to the .Rprofile} \usage{ -rprofile(config) +rprofile(config, envir = getOption("envsetup.path.environment")) } \arguments{ \item{config}{configuration object from config::get()} diff --git a/man/write_path.Rd b/man/write_path.Rd index cecfc1d..d1421bc 100644 --- a/man/write_path.Rd +++ b/man/write_path.Rd @@ -7,7 +7,8 @@ write_path( lib, filename = NULL, - envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON") + envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON"), + envir = getOption("envsetup.path.environment") ) } \arguments{ diff --git a/tests/testthat/test-autos.R b/tests/testthat/test-autos.R index 7597ad2..b226270 100644 --- a/tests/testthat/test-autos.R +++ b/tests/testthat/test-autos.R @@ -84,6 +84,15 @@ test_that("Autos validation from yml happens correctly", { expect_warning(set_autos(list(x = "/bad/path/")), "An autos path specified in your envsetup configuration file does not exist") + + + withr::local_envvar(c("ENVSETUP_ENVIRON" = "")) + + expect_error( + set_autos(list(project = c(DEV="path1", PROD="path2"))), + "The envsetup_environ parameter or ENVSETUP_ENVIRON environment variable must be used if hierarchical autos are set." + ) + }) # Detatch and re-setup for QA now diff --git a/tests/testthat/test-paths.R b/tests/testthat/test-paths.R index 1231a0a..5761e64 100644 --- a/tests/testthat/test-paths.R +++ b/tests/testthat/test-paths.R @@ -21,6 +21,22 @@ test_that("read_path will return the correct path if the object exists in anothe rm(data) }) +test_that("read_path checks fire as expected", { + expect_error( + read_path("data", "iris.csv"), + "The lib argument should be an object containing the paths for all environments of a directory, not a string." + ) + + withr::local_envvar(ENVSETUP_ENVIRON = "dev") + + expect_warning( + read_path(data, "iris.csv"), + "The path has named environments DEV, QA, PROD that do not match with the envsetup_environ parameter or ENVSETUP_ENVIRON environment variable" + ) + + +}) + test_that("write_path will return the correct path if the object exists in another environment", { data <- function() { stop() @@ -31,6 +47,52 @@ test_that("write_path will return the correct path if the object exists in anoth rm(data) }) +test_that("write_path checks fire as expected", { + expect_error( + write_path("data", "iris.csv"), + "The lib argument should be an object containing the paths for all environments of a directory, not a string." + ) + expect_error( + write_path(data, "iris.csv", envsetup_environ = ""), + "The envsetup_environ parameter or ENVSETUP_ENVIRON environment variable must be used if hierarchical paths are set." + ) + + withr::local_envvar(ENVSETUP_ENVIRON = "dev") + + expect_warning( + write_path(data, "iris.csv"), + "The path has named environments DEV, QA, PROD that do not match with the envsetup_environ parameter or ENVSETUP_ENVIRON environment variable" + ) + + +}) + +test_that("build_from_config checks fire as expected", { + build_tmpdir <- tempdir() + withr::defer(unlink(build_tmpdir)) + + hierarchy <- "default: + path: + data: !expr list(DEV = '/demo/DEV/username/project1/data', + PROD = '/demo/PROD/project1/data') + output: !expr list(DEV = '/demo/DEV/username/project1/output', + PROD = '/demo/PROD/project1/output') + programs: !expr list(DEV = '/demo/DEV/username/project1/programs', + PROD = '/demo/PROD/project1/programs') + docs: !expr list(DEV = 'docs', + PROD = 'docs')" + + writeLines(hierarchy, file.path(build_tmpdir, "hierarchy.yml")) + + config <- config::get(file = file.path(build_tmpdir, "hierarchy.yml")) + + expect_message( + build_from_config(config, build_tmpdir), + "No paths are specified as part of your configuration. Update your config file to add paths." + ) + +}) + test_that("build_from_config builds the correct directories", { build_tmpdir <- tempdir() withr::defer(unlink(build_tmpdir)) @@ -63,6 +125,7 @@ test_that("build_from_config builds the correct directories", { expect_true(dir.exists(file.path(build_tmpdir, "docs"))) }) + #' @editor Aidan Ceney #' @editDate 2022-05-12 test_that("1.1", { @@ -150,3 +213,20 @@ test_that("write_path works with unset envsetup_environ and non-hierarhical path readin <- write_path(data) expect_equal(readin, paste0(tmpdir, "/DEV/data")) }) + +#' @editor Nick Masel +#' @editDate 2025-07-16 +test_that("path environment option will store and retrieve paths from envsetup environment", { + withr::local_options( + envsetup.path.environment = envsetup_environment + ) + + expect_message(rprofile(envsetup_config), "^Assigned paths to package\\:envsetup\\$envsetup_environment") + + expect_equal(getOption("envsetup.path.environment"), envsetup_environment) + + expect_equal(all(c("auto_stored_envsetup_config", "data", "programs", "functions", "output") %in% ls(envir = envsetup_environment)), TRUE) + + expect_equal(get_path(data), envsetup_environment$data) +}) + diff --git a/tests/testthat/test-rprofile.R b/tests/testthat/test-rprofile.R index bc9fb82..e23e417 100644 --- a/tests/testthat/test-rprofile.R +++ b/tests/testthat/test-rprofile.R @@ -18,7 +18,8 @@ rootpath_dev <- "DEV" rootpath_qa <- "QA" rootpath_prod <- "PROD" - +#' @editor Nick Masel +#' @editDate 2025-07-17 test_that("rprofile stores the configuration", { config_tmpdir <- tempdir() withr::defer(unlink(file.path(config_tmpdir, ".Rprofile"))) @@ -38,13 +39,7 @@ test_that("rprofile stores the configuration", { rprofile(custom_name) - stored_config <- base::get( - "auto_stored_envsetup_config", - envsetup_path_environment - # pos = which(search() == "envsetup:paths") - ) - - expect_equal(custom_name, stored_config) + expect_equal(custom_name, auto_stored_envsetup_config) }) @@ -59,7 +54,7 @@ test_that("1.1", { expected$QA <- file.path(tmpdir, rootpath_qa, folder) expected$PROD <- file.path(tmpdir, rootpath_prod, folder) - expect_identical(expected, envsetup_path_environment$data) + expect_identical(expected, data) })