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/DESCRIPTION b/DESCRIPTION
index e6dfa5a..cf80e35 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.1
+Version: 0.3.0
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")),
@@ -24,7 +24,8 @@ Imports:
purrr,
rlang,
usethis,
- roxygen2
+ envnames,
+ utils
Suggests:
rmarkdown,
testthat,
diff --git a/NAMESPACE b/NAMESPACE
index d2363d1..68b27e3 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -2,13 +2,15 @@
export(build_from_config)
export(detach_autos)
+export(envsetup_environment)
+export(get_path)
export(init)
-export(library)
export(read_path)
export(rprofile)
export(validate_config)
export(write_path)
importFrom(config,get)
+importFrom(envnames,environment_name)
importFrom(fs,dir_tree)
importFrom(purrr,walk)
importFrom(purrr,walk2)
@@ -20,4 +22,6 @@ importFrom(usethis,ui_done)
importFrom(usethis,ui_field)
importFrom(usethis,ui_info)
importFrom(usethis,ui_oops)
+importFrom(usethis,ui_value)
importFrom(usethis,ui_yeah)
+importFrom(utils,getFromNamespace)
diff --git a/NEWS.md b/NEWS.md
index 23caa15..05db1ec 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,14 @@
+# envsetup 0.3.0
+
+- `paths` no are no longer attached to the search path (#80)
+- `paths` objects default to the global environment now, but they can be changed using the `envsetup.path.environment` option (#80)
+- `get_path()` was added to help you retrieve a path with changing the storage environment using the `envsetup.path.environment` option (#80)
+- `autos` are no longer attached to the search path, and are sourced to global (#81)
+- `rprofile` lets you specify if sourcing of autos should overwrite an object in global if it already exists (#81)
+- extensive messaging added to make users aware of autos being attached and conflicts (#81)
+- object metadata storage is added to track what functions were sourced from where, see `envsetup_environment$object_metadata` (#81)
+- vignettes updated to divide content into smaller chunks (#82)
+
# envsetup 0.2.1
- `set_autos()` will now handle NULL hierarchical paths (#66)
diff --git a/R/autos.R b/R/autos.R
index 286b79b..8d00235 100644
--- a/R/autos.R
+++ b/R/autos.R
@@ -10,6 +10,7 @@
#' @param envsetup_environ name of the environment you would like to read from;
#' default values comes from the value in the system variable ENVSETUP_ENVIRON
#' which can be set by Sys.setenv(ENVSETUP_ENVIRON = "environment name")
+#' @param overwrite logical indicating if sourcing of autos should overwrite an object in global if it already exists
#'
#' @return Called for side-effects. Directory paths of the R autos added to search path are printed.
#'
@@ -17,7 +18,7 @@
#' @importFrom rlang is_named
#' @importFrom usethis ui_field
#' @noRd
-set_autos <- function(autos, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON")) {
+set_autos <- function(autos, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON"), overwrite = TRUE) {
# Must be named list
if (!is_named(autos)) {
@@ -79,23 +80,126 @@ set_autos <- function(autos, envsetup_environ = Sys.getenv("ENVSETUP_ENVIRON"))
# If there are any existing autos then reset them
detach_autos()
- # Now attach everything. Note that attach will put an environment behind
- # global and in front of the package namespaces. By reversing the list,
- # the search path will be set to apply the autos to the name space so that
- # the path at element one of the list is directly behind global
+ # Now source everything
walk2(
- rev(flattened_paths),
- rev(names(flattened_paths)),
- ~ attach_auto(.x, .y)
+ flattened_paths,
+ names(flattened_paths),
+ ~ attach_auto(.x, .y, overwrite = overwrite)
)
}
+#' Source scripts and warn of conflicts
+#'
+#' Source a script, only adding objects to global if they do not already exist
+#'
+#' @param file path to a script containing object to add to global
+#' @param overwrite logical indicating if sourcing should overwrite an object in global if it already exists
+#'
+#' @importFrom usethis ui_value
+#'
+#' @return Called for side-effects. Objects are added to the global environment.
+#'
+#' @noRd
+source_warn_conflicts <- function(file, overwrite = TRUE){
+
+ # create a new environment to source into
+ new_env <- new.env()
+
+ cat("Sourcing file: ", ui_value(file), "\n")
+
+ # source directory into a this environment
+ sys.source(file,
+ envir = new_env)
+
+ # compare objects to find unique and non-unique
+ objects_in_new_env <- ls(new_env)
+ objects_in_global <- ls(.GlobalEnv)
+
+ if (overwrite == FALSE) {
+ objects_to_assign <- setdiff(objects_in_new_env, objects_in_global)
+ objects_to_skip_assign <- intersect(objects_in_new_env, objects_in_global)
+ objects_that_are_overwritten <- NULL
+ } else if (overwrite == TRUE) {
+ objects_to_assign <- objects_in_new_env
+ objects_to_skip_assign <- NULL
+ objects_that_are_overwritten <- intersect(objects_in_new_env, objects_in_global)
+ } else {
+ warning("overwrite must contain a logical")
+ }
+
+ for (obj_name in objects_to_assign) {
+ assign_and_move_function(obj_name, temp_env = new_env, envir = .GlobalEnv)
+ record_function_metadata(obj_name, file)
+ }
+
+ if (length(objects_to_assign) != 0) {
+ cat("\n The following objects are added to .GlobalEnv:", sep = "\n")
+ cat("", sep = "\n")
+ cat(paste0(" ", ui_value(objects_to_assign), "\n"))
+ }
+
+
+ if (length(objects_to_skip_assign) != 0) {
+ cat("\n The following objects were not added to .GlobalEnv as they already exist:", sep = "\n")
+ cat("", sep = "\n")
+ cat(paste0(" ", ui_value(objects_to_skip_assign), "\n"))
+ }
+
+
+ if (length(objects_that_are_overwritten) != 0) {
+ cat("\n The following objects were overwritten in .GlobalEnv:", sep = "\n")
+ cat("", sep = "\n")
+ cat(paste0(" ", ui_value(objects_that_are_overwritten), "\n"))
+ }
+
+ cat("", sep = "\n")
+
+}
+
+
+assign_and_move_function <- function(obj_name, temp_env, envir){
+ assign(obj_name, base::get(obj_name, envir = temp_env), envir = envir)
+}
+
+
+record_function_metadata <- function(obj_name, file){
+
+ # store the metadata for the objects
+ new_record <- data.frame(
+ object_name = obj_name,
+ script = file
+ )
+
+ if (exists("object_metadata", envsetup_environment)) {
+ df <- merge(
+ base::get("object_metadata", envsetup_environment),
+ new_record,
+ by = "object_name",
+ all = TRUE
+ )
+
+ if (any(c("script.x", "script.y") %in% names(df))) {
+ df$script <- ifelse(is.na(df$script.y), df$script.x, df$script.y)
+ df$script.x <- NULL
+ df$script.y <- NULL
+ }
+
+ envsetup_environment$object_metadata <- df
+ } else {
+ envsetup_environment$object_metadata <- new_record
+ }
+
+}
+
#' 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
+#'
+#' @importFrom utils getFromNamespace
+#'
#' @noRd
collate_func <- function(path){
r_scripts <- list.files(path,
@@ -104,7 +208,9 @@ collate_func <- function(path){
full.names = TRUE
)
- collated_func <- roxygen2:::generate_collate(path)
+ generate_collate <- utils::getFromNamespace("generate_collate", "roxygen2")
+
+ collated_func <- generate_collate(path)
if (is.null(collated_func)) {
r_scripts
@@ -125,11 +231,11 @@ collate_func <- function(path){
#'
#' @param path Directory path
#' @param name Directory name
+#' @param overwrite logical indicating if sourcing of autos should overwrite an object in global if it already exists
#' @noRd
#'
#' @return Called for side-effects. Directory paths of the R autos added to search path are printed.
-attach_auto <- function(path, name) {
- name_with_prefix <- paste0("autos:", name)
+attach_auto <- function(path, name, overwrite = TRUE) {
if (!(dir.exists(path) || file.exists(path))) {
# Check if the auto actually exists
@@ -137,18 +243,12 @@ attach_auto <- function(path, name) {
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)
+ source_warn_conflicts(path, overwrite = overwrite)
} else {
collated_r_scripts <- collate_func(path)
if (!identical(collated_r_scripts, character(0))) {
- walk(collated_r_scripts,
- sys.source,
- envir = attach(NULL, name = name_with_prefix)
- )
- message("Attaching functions from ", path, " to ", name_with_prefix)
+ walk(collated_r_scripts, source_warn_conflicts, overwrite = overwrite)
} else {
message("No files found in ", path, ". Nothing to attach.")
}
@@ -224,95 +324,10 @@ attach_auto <- function(path, name) {
#' # remove autos from search
#' detach_autos()
detach_autos <- function() {
- in_search <- search()[grepl("^autos:", search())]
-
- # Walk the list of autos and detach them
- walk(
- in_search,
- detach,
- character.only = TRUE
- )
-}
-#' Wrapper around library to place packages after any current autos
-#'
-#' Autos need to immediately follow the global environment.
-#' This wrapper around `base::library()` will position any
-#' attached packages in the earliest position on the
-#' search path currently occupied by a package environment,
-#' guaranteeing newly loaded packages appear before previously
-#' loaded packages but after any currently attached non-packages.
-#'
-#' @usage NULL
-#' @param ... pass directly through to base::library
-#' @param pos see base::library. NULL (the default) is taken
-#' to mean the earliest position of a package environment
-#' within the current search path. If non-null, underlying
-#' behavior of base::library is respected.
-#'
-#' @return returns (invisibly) the list of attached packages
-#' @export
-#'
-#' @examples
-#' # Simple example
-#' library(purrr)
-#'
-#' # Illustrative example to show that autos will always remain above attached libraries
-#' tmpdir <- tempdir()
-#' print(tmpdir)
-#'
-#' # account for windows
-#' if (Sys.info()['sysname'] == "Windows") {
-#' tmpdir <- gsub("\\", "\\\\", tmpdir, fixed = TRUE)
-#' }
-#'
-#' # Create an example config file
-#' hierarchy <- paste0("default:
-#' paths:
-#' functions: !expr list(
-#' DEV = file.path('",tmpdir,"', 'demo', 'DEV', 'username', 'project1', 'functions'),
-#' PROD = file.path('",tmpdir,"', 'demo', 'PROD', 'project1', 'functions'))
-#' autos:
-#' my_functions: !expr list(
-#' DEV = file.path('",tmpdir,"', 'demo', 'DEV', 'username', 'project1', 'functions'),
-#' PROD = file.path('",tmpdir,"', 'demo', 'PROD', 'project1', 'functions'))")
-#'
-#'
-#' # write config
-#' writeLines(hierarchy, file.path(tmpdir, "hierarchy.yml"))
-#'
-#' config <- config::get(file = file.path(tmpdir, "hierarchy.yml"))
-#'
-#' build_from_config(config)
-#'
-#' # write function to DEV
-#' writeLines("dev_function <- function() {print(environment(dev_function))}",
-#' file.path(tmpdir, 'demo/DEV/username/project1/functions/dev_function.r'))
-#'
-#' # write function to PROD
-#' writeLines("prod_function <- function() {print(environment(prod_function))}",
-#' file.path(tmpdir, 'demo/PROD/project1/functions/prod_function.r'))
-#'
-#' # setup the environment
-#' Sys.setenv(ENVSETUP_ENVIRON = "DEV")
-#' rprofile(config::get(file = file.path(tmpdir, "hierarchy.yml")))
-#'
-#' # show search
-#' search()
-#'
-#' # now attach purrr
-#' library(purrr)
-#'
-#' # 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 (exists("object_metadata", envir = envsetup_environment)){
+ rm(list = envsetup_environment$object_metadata$object_name, envir = .GlobalEnv)
+ rm("object_metadata", envir = envsetup_environment)
}
- base::library(..., pos = pos)
+
}
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 241cca3..3a5bc1e 100644
--- a/R/paths.R
+++ b/R/paths.R
@@ -1,3 +1,74 @@
+#' 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 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, envir = getOption("envsetup.path.environment")){
+ base::get(substitute(path), envir)
+}
+
#' Read path
#'
#' Check each environment for the file and return the path to the first.
@@ -13,6 +84,8 @@
#' @param envsetup_environ name of the environment you would like to read the file from;
#' default values comes from the value in the system variable ENVSETUP_ENVIRON
#' which can be set by Sys.setenv(ENVSETUP_ENVIRON = "environment name")
+#' @param envir The environment to search for the path object. Defaults to the
+#' value of `getOption("envsetup.path.environment")`.
#'
#' @importFrom rlang quo_get_expr enquo is_string
#'
@@ -53,10 +126,11 @@
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:paths
+ # get this directly from envsetup_environment
lib_arg <- quo_get_expr(enquo(lib))
if (is_string(lib_arg)) {
@@ -66,7 +140,7 @@ read_path <- function(lib,
), call. = FALSE)
}
- read_lib <- base::get(toString(lib_arg), "envsetup:paths")
+ read_lib <- base::get(toString(lib_arg), envir)
restricted_paths <- read_lib
@@ -125,6 +199,8 @@ read_path <- function(lib,
#' @param filename Name of the file you would like to write
#' @param envsetup_environ Name of the environment to which you would like to
#' write. Defaults to the ENVSETUP_ENVIRON environment variable
+#' @param envir The environment to search for the path object. Defaults to the
+#' value of `getOption("envsetup.path.environment")`.
#'
#' @importFrom rlang quo_get_expr enquo is_string
#'
@@ -162,7 +238,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))
@@ -174,7 +251,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), envir)
path <- write_path
if (length(write_path) > 1 && envsetup_environ == "") {
diff --git a/R/rprofile.R b/R/rprofile.R
index 0084f4b..b3b6051 100644
--- a/R/rprofile.R
+++ b/R/rprofile.R
@@ -1,6 +1,10 @@
#' Function used to pass through code to the .Rprofile
#'
#' @param config configuration object from config::get()
+#' @param envir The environment to search for the path object. Defaults to the
+#' value of `getOption("envsetup.path.environment")`.
+#' @param overwrite logical indicating if sourcing of autos should overwrite an object in global if it already exists
+#' @importFrom envnames environment_name
#' @export
#' @return Called for its side effects. Directory paths and autos are added to the search path based on your config.
#'
@@ -22,35 +26,26 @@
#' 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"),
+ overwrite = TRUE) {
# 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)
- }
-
- attach(config_minus_autos$paths,
- name = "envsetup:paths",
- pos = pos
- )
+ walk2(names(config_minus_autos$paths),
+ config_minus_autos$paths,
+ assign,
+ envir = envir)
- message("Attaching paths to envsetup:paths")
+ message(paste0("Assigned paths to ", 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, pos)
+ assign("auto_stored_envsetup_config", config, envir = envir)
# If autos exist, set them
if (!is.null(config$autos)) {
- set_autos(config$autos)
+ set_autos(config$autos, overwrite = overwrite)
}
}
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)
+[](https://github.com/pharmaverse/envsetup/actions/workflows/R-CMD-check.yaml)
+[](https://pharmaverse.github.io/envsetup/)
+[](https://pharmaverse.github.io/envsetup/_xml_coverage_reports/coverage.html)
+
+
+
+
+
+[](https://www.repostatus.org/#active)
+[](https://github.com/pharmaverse/envsetup/tree/main)
+[](https://github.com/pharmaverse/envsetup/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
+
+
+
-
-[
](https://pharmaverse.org)
-
-[](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)
-
-[](https://app.codecov.io/gh/pharmaverse/envsetup?branch=main)
-
+[](https://github.com/pharmaverse/envsetup/actions/workflows/R-CMD-check.yaml)
+[](https://pharmaverse.github.io/envsetup/)
+[](https://pharmaverse.github.io/envsetup/_xml_coverage_reports/coverage.html)
+
+
+
+
+
+[](https://www.repostatus.org/#active)
+[](https://github.com/pharmaverse/envsetup/tree/main)
+[](https://github.com/pharmaverse/envsetup/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
+
+
-## 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/_pkgdown.yml b/_pkgdown.yml
index 9e73315..daad10a 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -9,3 +9,12 @@ repo:
user: https://github.com/
news:
cran_dates: false
+articles:
+- title: Get started
+ navbar: ~
+ contents:
+ - basic_paths
+ - multiple_environments
+ - dynamic_paths
+ - auto_sourcing
+ - tips
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/library.Rd b/man/library.Rd
deleted file mode 100644
index eb29f76..0000000
--- a/man/library.Rd
+++ /dev/null
@@ -1,77 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/autos.R
-\name{library}
-\alias{library}
-\title{Wrapper around library to place packages after any current autos}
-\arguments{
-\item{...}{pass directly through to base::library}
-
-\item{pos}{see base::library. NULL (the default) is taken
-to mean the earliest position of a package environment
-within the current search path. If non-null, underlying
-behavior of base::library is respected.}
-}
-\value{
-returns (invisibly) the list of attached packages
-}
-\description{
-Autos need to immediately follow the global environment.
-This wrapper around \code{base::library()} will position any
-attached packages in the earliest position on the
-search path currently occupied by a package environment,
-guaranteeing newly loaded packages appear before previously
-loaded packages but after any currently attached non-packages.
-}
-\examples{
-# Simple example
-library(purrr)
-
-# Illustrative example to show that autos will always remain above attached libraries
-tmpdir <- tempdir()
-print(tmpdir)
-
-# account for windows
-if (Sys.info()['sysname'] == "Windows") {
- tmpdir <- gsub("\\\\", "\\\\\\\\", tmpdir, fixed = TRUE)
-}
-
-# Create an example config file
-hierarchy <- paste0("default:
- paths:
- functions: !expr list(
- DEV = file.path('",tmpdir,"', 'demo', 'DEV', 'username', 'project1', 'functions'),
- PROD = file.path('",tmpdir,"', 'demo', 'PROD', 'project1', 'functions'))
- autos:
- my_functions: !expr list(
- DEV = file.path('",tmpdir,"', 'demo', 'DEV', 'username', 'project1', 'functions'),
- PROD = file.path('",tmpdir,"', 'demo', 'PROD', 'project1', 'functions'))")
-
-
-# write config
-writeLines(hierarchy, file.path(tmpdir, "hierarchy.yml"))
-
-config <- config::get(file = file.path(tmpdir, "hierarchy.yml"))
-
-build_from_config(config)
-
-# write function to DEV
-writeLines("dev_function <- function() {print(environment(dev_function))}",
- file.path(tmpdir, 'demo/DEV/username/project1/functions/dev_function.r'))
-
-# write function to PROD
-writeLines("prod_function <- function() {print(environment(prod_function))}",
- file.path(tmpdir, 'demo/PROD/project1/functions/prod_function.r'))
-
-# setup the environment
-Sys.setenv(ENVSETUP_ENVIRON = "DEV")
-rprofile(config::get(file = file.path(tmpdir, "hierarchy.yml")))
-
-# show search
-search()
-
-# now attach purrr
-library(purrr)
-
-# see autos are still above purrr in the search path
-search()
-}
diff --git a/man/read_path.Rd b/man/read_path.Rd
index e646ced..c8e6263 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{
@@ -21,6 +22,9 @@ read_path(
\item{envsetup_environ}{name of the environment you would like to read the file from;
default values comes from the value in the system variable ENVSETUP_ENVIRON
which can be set by Sys.setenv(ENVSETUP_ENVIRON = "environment name")}
+
+\item{envir}{The environment to search for the path object. Defaults to the
+value of \code{getOption("envsetup.path.environment")}.}
}
\value{
string containing the path of the first directory the file is found
diff --git a/man/rprofile.Rd b/man/rprofile.Rd
index 3a9a3d3..0818204 100644
--- a/man/rprofile.Rd
+++ b/man/rprofile.Rd
@@ -4,10 +4,19 @@
\alias{rprofile}
\title{Function used to pass through code to the .Rprofile}
\usage{
-rprofile(config)
+rprofile(
+ config,
+ envir = getOption("envsetup.path.environment"),
+ overwrite = TRUE
+)
}
\arguments{
\item{config}{configuration object from config::get()}
+
+\item{envir}{The environment to search for the path object. Defaults to the
+value of \code{getOption("envsetup.path.environment")}.}
+
+\item{overwrite}{logical indicating if sourcing of autos should overwrite an object in global if it already exists}
}
\value{
Called for its side effects. Directory paths and autos are added to the search path based on your config.
diff --git a/man/write_path.Rd b/man/write_path.Rd
index cecfc1d..41cc240 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{
@@ -17,6 +18,9 @@ write_path(
\item{envsetup_environ}{Name of the environment to which you would like to
write. Defaults to the ENVSETUP_ENVIRON environment variable}
+
+\item{envir}{The environment to search for the path object. Defaults to the
+value of \code{getOption("envsetup.path.environment")}.}
}
\value{
path to write
diff --git a/tests/testthat/_snaps/R4.2/autos.new.md b/tests/testthat/_snaps/R4.2/autos.new.md
new file mode 100644
index 0000000..8797947
--- /dev/null
+++ b/tests/testthat/_snaps/R4.2/autos.new.md
@@ -0,0 +1,7 @@
+# Autos warns user when ENVSETUP_ENVIRON does not match named environments in autos
+
+ Code
+ suppressMessages(rprofile(custom_name))
+ 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.2/init.new.md b/tests/testthat/_snaps/R4.2/init.new.md
new file mode 100644
index 0000000..8773ae3
--- /dev/null
+++ b/tests/testthat/_snaps/R4.2/init.new.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/_snaps/R4.4/autos.md b/tests/testthat/_snaps/R4.4/autos.md
index 018ac22..4d3cfc1 100644
--- a/tests/testthat/_snaps/R4.4/autos.md
+++ b/tests/testthat/_snaps/R4.4/autos.md
@@ -5,4 +5,109 @@
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
+ Output
+
+ The following objects are added to .GlobalEnv:
+
+ 'test_dev'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'my_conflict', 'not_a_conflict_dev'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'inc3'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'inc2'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'inc1'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'mtcars', 'paste', 'test_qa'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'my_conflict', 'not_a_conflict_qa'
+
+ The following objects were overwritten in .GlobalEnv:
+
+ 'my_conflict'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'inc1'
+
+ The following objects were overwritten in .GlobalEnv:
+
+ 'inc1'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'inc2'
+
+ The following objects were overwritten in .GlobalEnv:
+
+ 'inc2'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'inc3'
+
+ The following objects were overwritten in .GlobalEnv:
+
+ 'inc3'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'my_conflict', 'not_a_conflict_prod'
+
+ The following objects were overwritten in .GlobalEnv:
+
+ 'my_conflict'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'atest'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'test_prod'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'test_prod2'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'test_global'
+
+
+ The following objects are added to .GlobalEnv:
+
+ 'atest'
+
+ The following objects were overwritten in .GlobalEnv:
+
+ 'atest'
+
diff --git a/tests/testthat/_snaps/autos.md b/tests/testthat/_snaps/autos.md
new file mode 100644
index 0000000..20d7bcc
--- /dev/null
+++ b/tests/testthat/_snaps/autos.md
@@ -0,0 +1,33 @@
+# source_warn_conflicts works with one directory in global
+
+ Code
+ source_warn_conflicts(dirs)
+ Output
+
+ The following objects are added to .GlobalEnv:
+
+ 'my_conflict', 'not_a_conflict_dev'
+
+
+---
+
+ Code
+ envsetup_environment$object_metadata$object_name
+ Output
+ [1] "my_conflict" "not_a_conflict_dev"
+
+# source_warn_conflicts works when adding a second directory with conflicts in global
+
+ Code
+ source_warn_conflicts(dirs[[2]])
+ Output
+
+ The following objects are added to .GlobalEnv:
+
+ 'my_conflict', 'not_a_conflict_qa'
+
+ The following objects were overwritten in .GlobalEnv:
+
+ 'my_conflict'
+
+
diff --git a/tests/testthat/man/testdir/DEV/functions/conflicts.R b/tests/testthat/man/testdir/DEV/functions/conflicts.R
new file mode 100644
index 0000000..eaf33f9
--- /dev/null
+++ b/tests/testthat/man/testdir/DEV/functions/conflicts.R
@@ -0,0 +1,7 @@
+my_conflict <- function(){
+ print("This is a function that makes a conflict. It is in DEV.")
+}
+
+not_a_conflict_dev <- function(){
+ print("This function does not cause a conflict. It is in DEV.")
+}
diff --git a/tests/testthat/man/testdir/PROD/functions/conflicts.R b/tests/testthat/man/testdir/PROD/functions/conflicts.R
new file mode 100644
index 0000000..f73b1a8
--- /dev/null
+++ b/tests/testthat/man/testdir/PROD/functions/conflicts.R
@@ -0,0 +1,7 @@
+my_conflict <- function(){
+ print("This is a function that makes a conflict. It is in PROD.")
+}
+
+not_a_conflict_prod <- function(){
+ print("This function does not cause a conflict. It is in PROD.")
+}
diff --git a/tests/testthat/man/testdir/QA/functions/conflicts.R b/tests/testthat/man/testdir/QA/functions/conflicts.R
new file mode 100644
index 0000000..3c2c171
--- /dev/null
+++ b/tests/testthat/man/testdir/QA/functions/conflicts.R
@@ -0,0 +1,7 @@
+my_conflict <- function(){
+ print("This is a function that makes a conflict. It is in QA.")
+}
+
+not_a_conflict_qa <- function(){
+ print("This function does not cause a conflict. It is in QA.")
+}
diff --git a/tests/testthat/test-autos.R b/tests/testthat/test-autos.R
index 7597ad2..43bc811 100644
--- a/tests/testthat/test-autos.R
+++ b/tests/testthat/test-autos.R
@@ -14,24 +14,124 @@ custom_name <- config::get(
file = testthat::test_path("man/_envsetup_testthat.yml")
)
+remove_sourcing_file <- function(x) {
+ # Use regular expressions to remove the line containing "Sourcing file"
+ x[!grepl("^Sourcing file:", x)]
+}
+
# Dev tests
Sys.setenv(ENVSETUP_ENVIRON = "DEV")
-#' @editor Mike Stackhouse
-#' @editDate 2023-02-11
-test_that("Autos set and test_dev from highest level appears correctly", {
+#' @editor Nick Masel
+#' @editDate 2025-07-22
+test_that("Autos set correctly when default of overwrite is used", {
suppressMessages(set_autos(custom_name$autos))
+
expect_equal(c(test_dev()), c("Test of dev autos"))
expect_equal(c(test_global()), c("Test of global autos"))
+
+ # my_conflict is in dev, qa and prod. overwrite is TRUE so we should see the prod version.
+ expect_equal(c(my_conflict()), c("This is a function that makes a conflict. It is in PROD."))
+
+ # check object metadata stores files correctly
+ expect_equal(
+ envsetup_environment$object_metadata,
+ data.frame(
+ stringsAsFactors = FALSE,
+ object_name = c("atest",
+ "inc1",
+ "inc2",
+ "inc3",
+ "mtcars",
+ "my_conflict",
+ "not_a_conflict_dev",
+ "not_a_conflict_prod",
+ "not_a_conflict_qa",
+ "paste",
+ "test_dev",
+ "test_global",
+ "test_prod",
+ "test_prod2",
+ "test_qa"),
+ script = c(file.path(tmpdir, "PROD/functions/envre.R"),
+ file.path(tmpdir, "QA/functions/inc1.R"),
+ file.path(tmpdir, "QA/functions/inc2.R"),
+ file.path(tmpdir, "QA/functions/inc3.R"),
+ file.path(tmpdir, "QA/functions/QATest.R"),
+ file.path(tmpdir, "PROD/functions/conflicts.R"),
+ file.path(tmpdir, "DEV/functions/conflicts.R"),
+ file.path(tmpdir, "PROD/functions/conflicts.R"),
+ file.path(tmpdir, "QA/functions/conflicts.R"),
+ file.path(tmpdir, "QA/functions/QATest.R"),
+ file.path(tmpdir, "DEV/functions/TestDev.R"),
+ file.path(tmpdir, "global/functions/globaltest.R"),
+ file.path(tmpdir, "PROD/functions/prodtest.R"),
+ file.path(tmpdir, "PROD/functions/prodtest2.R"),
+ file.path(tmpdir, "QA/functions/QATest.R"))
+ ))
+
+ detach_autos()
+})
+
+#' @editor Nick Masel
+#' @editDate 2025-07-22
+test_that("Autos set correctly when overwrite is FALSE", {
+ suppressMessages(set_autos(custom_name$autos, overwrite = FALSE))
+
+ expect_equal(c(test_dev()), c("Test of dev autos"))
+ expect_equal(c(test_global()), c("Test of global autos"))
+
+ # my_conflict is in dev, qa and prod. overwrite is FALSE so we should see the dev version.
+ expect_equal(c(my_conflict()), c("This is a function that makes a conflict. It is in DEV."))
+
+ # check object metadata stores files correctly
+ expect_equal(
+ envsetup_environment$object_metadata,
+ data.frame(
+ stringsAsFactors = FALSE,
+ object_name = c("atest",
+ "inc1",
+ "inc2",
+ "inc3",
+ "mtcars",
+ "my_conflict",
+ "not_a_conflict_dev",
+ "not_a_conflict_prod",
+ "not_a_conflict_qa",
+ "paste",
+ "test_dev",
+ "test_global",
+ "test_prod",
+ "test_prod2",
+ "test_qa"),
+ script = c(file.path(tmpdir, "PROD/functions/envre.R"),
+ file.path(tmpdir, "DEV/functions/inc1.R"),
+ file.path(tmpdir, "DEV/functions/inc2.R"),
+ file.path(tmpdir, "DEV/functions/inc3.R"),
+ file.path(tmpdir, "QA/functions/QATest.R"),
+ file.path(tmpdir, "DEV/functions/conflicts.R"),
+ file.path(tmpdir, "DEV/functions/conflicts.R"),
+ file.path(tmpdir, "PROD/functions/conflicts.R"),
+ file.path(tmpdir, "QA/functions/conflicts.R"),
+ file.path(tmpdir, "QA/functions/QATest.R"),
+ file.path(tmpdir, "DEV/functions/TestDev.R"),
+ file.path(tmpdir, "global/functions/globaltest.R"),
+ file.path(tmpdir, "PROD/functions/prodtest.R"),
+ file.path(tmpdir, "PROD/functions/prodtest2.R"),
+ file.path(tmpdir, "QA/functions/QATest.R"))
+ ))
+
+ detach_autos()
})
#' @editor Nick Masel
-#' @editDate 2024-12-30
+#' @editDate 2025-07-10
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/conflicts.R"),
file.path(tmpdir, "DEV/functions/inc3.R"),
file.path(tmpdir, "DEV/functions/inc2.R"),
file.path(tmpdir, "DEV/functions/inc1.R")
@@ -41,11 +141,12 @@ test_that("Order of functions appears correctly when @include is used", {
#' @editor Nick Masel
-#' @editDate 2024-12-30
+#' @editDate 2025-07-10
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/conflicts.R"),
file.path(tmpdir, "QA/functions/inc1.R"),
file.path(tmpdir, "QA/functions/inc2.R"),
file.path(tmpdir, "QA/functions/inc3.R")
@@ -53,19 +154,6 @@ test_that("Order of functions appears correctly when @include is not used", {
)
})
-#' @editor Gabe Becker
-#' @editDate 2023-11-22
-test_that("library returns invisibly", {
- # Detatch envsetup:paths if it exists
- if (any(search() == "envsetup:paths")) {
- detach("envsetup:paths")
- }
- expect_silent(expect_invisible(suppressPackageStartupMessages(library("purrr"))))
- suppressMessages(rprofile(custom_name))
- detach("package:purrr")
-})
-
-
#' @editor Aidan Ceney
#' @editDate 2022-05-12
test_that("Autos validation from yml happens correctly", {
@@ -84,10 +172,18 @@ 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
-detach_autos()
Sys.setenv(ENVSETUP_ENVIRON = "QA")
#' @editor Mike Stackhouse
@@ -100,6 +196,8 @@ test_that("Setting environment to QA filters out dev autos", {
)
expect_error(test_dev())
expect_equal(c(test_global()), c("Test of global autos"))
+
+ detach_autos()
})
#' @editor Mike Stackhouse
@@ -107,11 +205,15 @@ test_that("Setting environment to QA filters out dev autos", {
test_that("Data output in namespace appears", {
suppressMessages(set_autos(custom_name$autos))
expect_equal(mtcars, iris)
+
+ detach_autos()
})
#' @editor Mike Stackhouse
#' @editDate 2022-02-11
-test_that("set_autos effectively clears and resets namespace", {
+test_that("set_autos effectively clears previously sourced autos", {
+ Sys.setenv(ENVSETUP_ENVIRON = "DEV")
+ suppressMessages(set_autos(custom_name$autos))
Sys.setenv(ENVSETUP_ENVIRON = "QA")
suppressMessages(set_autos(custom_name$autos))
expect_error(test_dev())
@@ -120,6 +222,7 @@ test_that("set_autos effectively clears and resets namespace", {
suppressMessages(set_autos(custom_name$autos))
expect_error(test_qa())
expect_equal(c(test_global()), c("Test of global autos"))
+ detach_autos()
})
#' @editor Mike Stackhouse
@@ -128,6 +231,8 @@ test_that("Functions in higher level hierarchy export and multiple functions may
suppressMessages(set_autos(custom_name$autos))
expect_equal(test_prod(), "Test of prod autos")
expect_equal(test_prod2(), "Test of prod autos second")
+
+ detach_autos()
})
#' @editor Mike Stackhouse
@@ -138,35 +243,101 @@ test_that("Autos no longer exist when detached", {
expect_error(test_prod())
})
-test_that("the configuration can be named anything and library will
- reattach the autos correctly", {
- suppressMessages(rprofile(custom_name))
-
- expect_invisible(suppressPackageStartupMessages(library("purrr")))
-
- purrr_location <- which(search() == "package:purrr")
- autos_locatios <- which(grepl("^autos:", search()))
-
- expect_true(all(purrr_location > autos_locatios))
- detach("package:purrr")
- }
-)
-
-
+#' @editor Nick Masel
+#' @editDate 2025-07-10
test_that("Autos warns user when ENVSETUP_ENVIRON does not match named environments in autos", {
withr::local_envvar(ENVSETUP_ENVIRON = "bad_name")
- expect_snapshot(suppressMessages(rprofile(custom_name)), variant = r_version())
+ expect_snapshot(
+ suppressMessages(rprofile(custom_name)),
+ variant = r_version(),
+ transform = remove_sourcing_file
+ )
+
+ detach_autos()
})
#' @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))
+
+ detach_autos()
+})
+
+
+
+#' @editor Nick Masel
+#' @editDate 2025-07-10
+test_that("source_warn_conflicts works with one directory in global", {
+ dirs <- testthat::test_path("man/testdir/DEV/functions/conflicts.R")
+
+ expect_snapshot(
+ source_warn_conflicts(dirs),
+ transform = remove_sourcing_file
+ )
+
+ # check object_metadata
+ expect_snapshot(envsetup_environment$object_metadata$object_name)
+
+ detach_autos()
+})
+
+#' @editor Nick Masel
+#' @editDate 2025-07-10
+test_that("source_warn_conflicts works when adding a second directory with conflicts in global", {
+
+ dirs <- list(
+ testthat::test_path("man/testdir/DEV/functions/conflicts.R"),
+ testthat::test_path("man/testdir/QA/functions/conflicts.R")
+ )
+
+ # source first file
+ source_warn_conflicts(dirs[[1]])
+
+ expect_equal(
+ envsetup_environment$object_metadata,
+ data.frame(
+ stringsAsFactors = FALSE,
+ object_name = c("my_conflict", "not_a_conflict_dev"),
+ script = c(dirs[[1]],
+ dirs[[1]])
+ )
+ )
+
+ # now source second to confirm functions added, and those not added to global
+ expect_snapshot(
+ source_warn_conflicts(dirs[[2]]),
+ transform = remove_sourcing_file
+ )
+
+ expect_equal(
+ envsetup_environment$object_metadata,
+ data.frame(
+ stringsAsFactors = FALSE,
+ object_name = c("my_conflict", "not_a_conflict_dev", "not_a_conflict_qa"),
+ script = c(dirs[[2]],
+ dirs[[1]],
+ dirs[[2]])
+ )
+ )
+
+ detach_autos()
+
+})
+
+#' @editor Nick Masel
+#' @editDate 2025-07-10
+test_that("source_warn_conflicts throws an error when a path is not valid in global", {
+
+ dirs <- testthat::test_path("man/testdir/DEV/functions/conflictss.R")
+ expect_error(source_warn_conflicts(dirs))
+
+ detach_autos()
+
})
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 1011f40..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,12 +39,7 @@ test_that("rprofile stores the configuration", {
rprofile(custom_name)
- stored_config <- base::get(
- "auto_stored_envsetup_config",
- pos = which(search() == "envsetup:paths")
- )
-
- expect_equal(custom_name, stored_config)
+ expect_equal(custom_name, auto_stored_envsetup_config)
})
@@ -52,8 +48,6 @@ 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)
@@ -70,8 +64,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 +81,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)
})
diff --git a/vignettes/auto_sourcing.Rmd b/vignettes/auto_sourcing.Rmd
new file mode 100644
index 0000000..c4cf8ac
--- /dev/null
+++ b/vignettes/auto_sourcing.Rmd
@@ -0,0 +1,555 @@
+---
+title: "Automatic Script Sourcing"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Automatic Script Sourcing}
+ %\VignetteEncoding{UTF-8}
+ %\VignetteEngine{knitr::rmarkdown}
+editor_options:
+ markdown:
+ wrap: 72
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>"
+)
+```
+
+The `autos` configuration automatically sources R scripts from specified
+directories, making custom functions immediately available without
+manual sourcing. This is perfect for project-specific utility functions
+and shared code libraries.
+
+## Basic AUTOS Configuration
+
+``` yaml
+default:
+ autos:
+ script_library: '/path/to/your/scripts'
+```
+
+**Note**: By default, auto-sourcing will overwrite any existing
+functions with the same name. This behavior can be controlled through
+the `overwrite` parameter in the underlying functions, though this is
+typically managed automatically by the system.
+
+## Working Example: Single Script Library
+
+Let's create a practical example where Tidy McVerse has custom functions
+in a script library:
+
+```{r}
+library(envsetup)
+
+# Create temporary directory structure
+dir <- fs::file_temp()
+dir.create(dir)
+dir.create(file.path(dir, "/demo/DEV/username/project1/script_library"), recursive = TRUE)
+
+# Create a custom function
+file_conn <- file(file.path(dir, "/demo/DEV/username/project1/script_library/test.R"))
+writeLines(
+"test <- function(){print('Hello from auto-sourced function!')}", file_conn)
+close(file_conn)
+
+# Write the configuration
+config_path <- file.path(dir, "_envsetup.yml")
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ dev_script_library: '", dir,"/demo/DEV/username/project1/script_library'"
+ ), file_conn)
+close(file_conn)
+```
+
+## Loading and Using Auto-Sourced Functions
+
+```{r}
+# Load configuration and apply it
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+```
+
+The auto-sourced functions are now available:
+
+```{r}
+# See what functions are available
+objects()
+
+# Use the function directly (no manual sourcing needed!)
+test()
+```
+
+## Multiple Script Libraries
+
+Real projects often have multiple script libraries for different
+purposes:
+
+```{r}
+# Create production script library
+dir.create(file.path(dir, "/demo/PROD/project1/script_library"), recursive = TRUE)
+
+# Add production functions
+file_conn <- file(file.path(dir, "/demo/PROD/project1/script_library/test2.R"))
+writeLines(
+"test2 <- function(){print('Hello from production function!')}", file_conn)
+close(file_conn)
+
+# Update configuration with multiple libraries
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ dev_script_library: '", dir,"/demo/DEV/username/project1/script_library'
+ prod_script_library: '", dir,"/demo/PROD/project1/script_library'"
+ ), file_conn)
+close(file_conn)
+
+# Reload configuration
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+```
+
+## Using Multiple Libraries
+
+```{r}
+# Check search path - now includes both libraries
+# Functions from both libraries are available
+objects()
+
+# Use functions from both libraries
+test() # From dev library
+test2() # From prod library
+```
+
+## Understanding Function Conflicts and the Overwrite Parameter
+
+When auto-sourcing functions, you might encounter situations where
+function names conflict with existing objects in your environment. The
+`overwrite` parameter controls how these conflicts are handled.
+
+### Quick Example of Function Conflicts
+
+```{r}
+# Create a function that might conflict
+summary_stats <- function(data) {
+ print("Original summary function")
+}
+
+# Create a script with the same function name
+conflict_dir <- file.path(dir, "conflict_demo")
+dir.create(conflict_dir)
+
+file_conn <- file(file.path(conflict_dir, "stats.R"))
+writeLines(
+"summary_stats <- function(data) {
+ print('Updated summary function from the new conflict_demo script')
+}", file_conn)
+close(file_conn)
+
+# Add to configuration
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ dev_script_library: '", dir,"/demo/DEV/username/project1/script_library'
+ prod_script_library: '", dir,"/demo/PROD/project1/script_library'
+ conflict_demo: '", conflict_dir, "'"
+ ), file_conn)
+close(file_conn)
+
+# When we reload, the auto-sourced version will overwrite the original
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+
+# Test which version we have now
+summary_stats()
+```
+
+The output shows detailed information about what was overwritten,
+helping you track conflicts.
+
+## Environment-Specific Auto-Sourcing
+
+You might want different script libraries for different environments.
+For example, exclude development functions when running in production:
+
+```{r}
+# Configuration that blanks out dev scripts in production
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ dev_script_library: '", dir,"/demo/DEV/username/project1/script_library'
+ prod_script_library: '", dir,"/demo/PROD/project1/script_library'
+
+prod:
+ autos:
+ dev_script_library: NULL" # NULL disables this library
+ ), file_conn)
+close(file_conn)
+
+# Load production configuration
+envsetup_config <- config::get(file = config_path, config = "prod")
+rprofile(envsetup_config)
+```
+
+So we can see now that only production functions are available:
+
+```{r}
+# Functions from production only
+objects()
+
+# Use functions from production only
+test2() # From prod library
+```
+
+## How Auto-Sourcing Works
+
+When you call `rprofile()` with autos configuration:
+
+1. **Script Discovery**: Finds all `.R` files in specified directories
+2. **Conflict Detection**: Compares new functions with existing global
+ environment objects
+3. **Automatic Sourcing**: Sources each script into its environment
+4. **Conflict Resolution**: Based on the `overwrite` parameter: 5 -
+ `overwrite = TRUE` (default): Replaces existing functions and
+ reports what was overwritten
+ - `overwrite = FALSE`: Preserves existing functions and reports
+ what was skipped . **Metadata Tracking**: Records which script
+ each function came from for debugging
+5. **Function Availability**: Functions become directly accessible
+
+### Technical Details of Conflict Handling
+
+The auto-sourcing system uses a sophisticated approach to handle
+conflicts:
+
+- **Temporary Environment**: Each script is first sourced into a
+ temporary environment
+- **Object Comparison**: New objects are compared against the global
+ environment
+- **Selective Assignment**: Only specified objects are moved to the
+ global environment
+- **Metadata Recording**: Each function's source script is recorded in
+ `object_metadata`
+- **Detailed Reporting**: Users receive clear feedback about what was
+ added, skipped, or overwritten
+- **Cleanup Integration**: Metadata enables precise cleanup when using
+ `detach_autos()`
+
+The `record_function_metadata()` function creates a comprehensive audit
+trail by maintaining a data frame with:
+
+- `object_name`: The name of each sourced function
+
+- `script`: The full path to the source script
+
+- This is automatically updated when functions are overwritten by
+ newer versions
+
+## Benefits of Auto-Sourcing
+
+1. **No Manual Sourcing**: Functions are automatically available
+2. **Organized Libraries**: Separate environments for different script
+ collections
+3. **Environment Isolation**: Functions don't interfere with each other
+4. **Dynamic Loading**: Easy to add/remove script libraries
+5. **Team Collaboration**: Shared function libraries across team
+ members
+6. **Comprehensive Tracking**: Metadata system tracks function sources
+ for debugging
+7. **Intelligent Cleanup**: Precise removal of auto-sourced functions
+ via metadata
+
+## Common Use Cases
+
+### Project Utilities
+
+``` yaml
+autos:
+ project_utils: '/project/utilities'
+ data_processing: '/project/data_functions'
+ plotting_functions: '/project/viz_functions'
+```
+
+### Environment-Specific Functions
+
+``` yaml
+default:
+ autos:
+ dev_helpers: '/dev/helper_functions'
+ shared_utils: '/shared/utilities'
+
+prod:
+ autos:
+ shared_utils: '/shared/utilities'
+ # dev_helpers excluded in production
+```
+
+### Team Libraries
+
+``` yaml
+autos:
+ team_functions: '/shared/team_library'
+ personal_utils: '~/my_r_functions'
+ project_specific: './project_functions'
+```
+
+## Managing Function Conflicts with the Overwrite Parameter
+
+The `overwrite` parameter controls how auto-sourcing handles situations
+where functions with the same name already exist in your global
+environment. Understanding this parameter is crucial for managing
+function conflicts effectively.
+
+### Default Behavior: Overwrite = TRUE
+
+By default, auto-sourcing will overwrite existing functions:
+
+```{r}
+# Create a function in global environment
+my_function <- function() {
+ print("Original function from global environment")
+}
+
+# Check it works
+my_function()
+
+# Create a script with the same function name
+dir <- fs::file_temp()
+dir.create(dir)
+script_dir <- file.path(dir, "scripts")
+dir.create(script_dir)
+
+file_conn <- file(file.path(script_dir, "my_function.R"))
+writeLines(
+"my_function <- function() {
+ print('Updated function from auto-sourced script')
+}", file_conn)
+close(file_conn)
+
+# Configuration with default overwrite = TRUE
+config_path <- file.path(dir, "_envsetup.yml")
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ my_scripts: '", script_dir, "'"
+ ), file_conn)
+close(file_conn)
+
+# Load configuration - this will overwrite the existing function
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+
+# The function has been overwritten
+my_function()
+```
+
+### Conservative Behavior: Overwrite = FALSE
+
+When `overwrite = FALSE`, existing functions are preserved:
+
+```{r}
+# clean up previous runs, removing all previously attached autos
+detach_autos()
+
+# Create a function in global environment
+my_function <- function() {
+ print("Original function from global environment")
+}
+
+# Check it works
+my_function()
+
+# Create a script with the same function name
+dir <- fs::file_temp()
+dir.create(dir)
+script_dir <- file.path(dir, "scripts")
+dir.create(script_dir)
+
+file_conn <- file(file.path(script_dir, "my_function.R"))
+writeLines(
+"my_function <- function() {
+ print('Updated function from auto-sourced script')
+}", file_conn)
+close(file_conn)
+
+# Configuration with default overwrite = FALSE
+config_path <- file.path(dir, "_envsetup.yml")
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ my_scripts: '", script_dir, "'"
+ ), file_conn)
+close(file_conn)
+
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config, overwrite = FALSE)
+
+my_function()
+
+```
+
+### Understanding Conflict Detection
+
+The auto-sourcing system provides detailed feedback about what happens
+during sourcing:
+
+```{r}
+# Create multiple functions to demonstrate conflict detection
+existing_func1 <- function() "I exist in global"
+existing_func2 <- function() "I also exist in global"
+
+# Create script with mix of new and conflicting functions
+file_conn <- file(file.path(script_dir, "mixed_functions.R"))
+writeLines(
+"# This will conflict with existing function
+existing_func1 <- function() {
+ 'Updated from script'
+}
+
+# This is a new function
+new_func <- function() {
+ 'Brand new function'
+}
+
+# This will also conflict
+existing_func2 <- function() {
+ 'Also updated from script'
+}", file_conn)
+close(file_conn)
+
+# Update configuration
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ my_scripts: '", script_dir, "'"
+ ), file_conn)
+close(file_conn)
+
+# Reload - watch the detailed output
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+```
+
+## Function Metadata Tracking
+
+The auto-sourcing system includes sophisticated metadata tracking that
+records detailed information about every function that gets sourced.
+This tracking system is invaluable for debugging, auditing, and
+understanding your function ecosystem.
+
+### How Metadata Tracking Works
+
+Every time a function is sourced through the autos system, the
+`record_function_metadata()` function captures:
+
+- **Object Name**: The name of the function or object
+- **Source Script**: The full path to the script file that contains
+ the function
+
+This information is stored in a special `object_metadata` data frame
+within the `envsetup_environment`.
+
+### Accessing Function Metadata
+
+```{r}
+# After sourcing functions, you can access the metadata
+# Note: This example shows the concept - actual access depends on envsetup internals
+
+# Create some functions to demonstrate metadata tracking
+metadata_demo_dir <- file.path(dir, "metadata_demo")
+dir.create(metadata_demo_dir)
+
+# Create multiple scripts with different functions
+file_conn <- file(file.path(metadata_demo_dir, "data_functions.R"))
+writeLines(
+"load_data <- function(file) {
+ paste('Loading data from:', file)
+}
+
+clean_data <- function(data) {
+ paste('Cleaning data with', nrow(data), 'rows')
+}", file_conn)
+close(file_conn)
+
+file_conn <- file(file.path(metadata_demo_dir, "plot_functions.R"))
+writeLines(
+"create_plot <- function(data) {
+ paste('Creating plot for', ncol(data), 'variables')
+}
+
+save_plot <- function(plot, filename) {
+ paste('Saving plot to:', filename)
+}", file_conn)
+close(file_conn)
+
+# Update configuration to include metadata demo
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ autos:
+ metadata_demo: '", metadata_demo_dir, "'"
+ ), file_conn)
+close(file_conn)
+
+# Source the functions
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+
+# The system now tracks which script each function came from
+cat("Functions sourced with metadata tracking:")
+knitr::kable(envsetup_environment$object_metadata)
+```
+
+### Benefits of Metadata Tracking
+
+#### 1. **Debugging Function Issues**
+
+When a function isn't working as expected, metadata helps you quickly
+identifyw hich script file contains the function
+
+#### 2. **Audit Trail**
+
+Metadata provides a complete audit trail of your function ecosystem.
+
+### Metadata and the detach_autos() Function
+
+The metadata tracking system integrates closely with cleanup operations:
+
+1. Identify all auto-sourced functions
+2. Remove them from the global environment
+3. Clean up the metadata records
+
+## Best Practices
+
+Even though your functions are not a part of a package, you should
+follow best practices to ensure your functions work as expected.
+
+1. **Use Clear Names**: Library names should indicate their purpose
+2. **Monitor Conflicts**: Regularly check for and resolve function name
+ conflicts
+3. **Document Functions**: Include roxygen2 comments in your functions
+4. **Test Functions**: Ensure auto-sourced functions work correctly
+5. **Package Prefix**: Use package prefix when writing your functions,
+ for example, `dplyr::filter`
+
+```{r echo = FALSE}
+# Clean up
+unlink(dir, recursive=TRUE)
+```
diff --git a/vignettes/basic_paths.Rmd b/vignettes/basic_paths.Rmd
new file mode 100644
index 0000000..dc1f538
--- /dev/null
+++ b/vignettes/basic_paths.Rmd
@@ -0,0 +1,157 @@
+---
+title: "Basic Path Configuration"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Basic Path Configuration}
+ %\VignetteEncoding{UTF-8}
+ %\VignetteEngine{knitr::rmarkdown}
+editor_options:
+ markdown:
+ wrap: 72
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>"
+)
+```
+
+# Understanding Path Configuration
+
+This guide walks through how to set up basic path configurations in your
+`_envsetup.yml` file.
+
+## Configuration Structure Levels
+
+### Level 1: Execution Environment
+
+Scripts typically execute in different environments depending on your
+workflow:
+
+``` yaml
+default:
+
+dev:
+
+qa:
+
+prod:
+```
+
+### Level 2: Paths and Autos
+
+Each execution environment can have different configurations:
+
+``` yaml
+default:
+ paths:
+ autos:
+
+dev:
+ paths:
+ autos:
+
+qa:
+ paths:
+ autos:
+
+prod:
+ paths:
+ autos:
+```
+
+### Level 3: Specific Configuration
+
+Configure the actual environment settings:
+
+``` yaml
+default:
+ paths:
+ data: "/demo/DEV/username/project1/data"
+ output: "/demo/DEV/username/project1/output"
+ programs: "/demo/DEV/username/project1/programs"
+```
+
+## Working Example
+
+Let's create a practical example for a project called **project1** that
+needs data input, result output, and program storage locations.
+
+```{r}
+library(envsetup)
+
+# Create temporary directory for demonstration
+dir <- fs::file_temp()
+dir.create(dir)
+config_path <- file.path(dir, "_envsetup.yml")
+
+# Write a basic config file
+file_conn <- file(config_path)
+writeLines(
+"default:
+ paths:
+ data: '/demo/DEV/username/project1/data'
+ output: '/demo/DEV/username/project1/output'
+ programs: '/demo/DEV/username/project1/programs'", file_conn)
+close(file_conn)
+```
+
+## Loading and Using the Configuration
+
+```{r}
+# Load the configuration
+envsetup_config <- config::get(file = config_path)
+
+# Apply the configuration to your R session
+rprofile(envsetup_config)
+```
+
+## Accessing Your Configured Paths
+
+Once configured, your paths are available in the `envsetup_environment`
+environment within the envsetup package environment:
+
+```{r echo = TRUE}
+# See all available path objects
+ls(envsetup_environment)
+
+# Access individual paths
+get_path(data)
+get_path(output)
+get_path(programs)
+```
+
+## How It Works
+
+The `rprofile()` function:
+
+1\. Creates a special environment called `envsetup_environment`
+
+2\. Populates it with your configured path objects
+
+3\. Makes these objects accessible in your code via the `get_path()`,
+`read_path()`, and `write_path()`
+
+## Benefits
+
+With this setup:
+
+- **Consistency**: All team members use the same path structure
+
+- **Flexibility**: Easy to change paths without modifying code
+
+- **Clarity**: Path purposes are clearly defined
+
+- **Maintainability**: Centralized configuration management
+
+```{r echo = FALSE}
+# Clean up
+unlink(dir, recursive=TRUE)
+```
+
+## Next Steps
+
+Now that you understand basic path configuration, the next guide will
+show you how to manage multiple environments (dev, qa, prod) with
+different configurations.
diff --git a/vignettes/config.Rmd b/vignettes/config.Rmd
deleted file mode 100644
index 6639130..0000000
--- a/vignettes/config.Rmd
+++ /dev/null
@@ -1,476 +0,0 @@
----
-title: "Create Your Config"
-output: rmarkdown::html_vignette
-vignette: >
- %\VignetteIndexEntry{config}
- %\VignetteEngine{knitr::rmarkdown}
- %\VignetteEncoding{UTF-8}
----
-
-```{r, include = FALSE}
-knitr::opts_chunk$set(
- collapse = TRUE,
- comment = "#>"
-)
-```
-
-Here we will walk-through how to update \_envsetup.yml to meet your needs. The configuration is currently setup to address:
-
-1. paths
-2. autos
-
-# PATHS
-
-This adds `envsetup:paths` to your search path which contains all of the relevant objects needed to point to different directories in your environment.
-
-## Level 1 of config: the execution environment (ex. dev, qa or prod)
-
-Scripts typically execute in different environments depending on your workflow. Here we have a workflow where multiple developers work in dev making scripts, they move to qa for some quality checks and sign off, then move to prod where they are executed for delivery.
-
-``` yaml
-default:
-
-dev:
-
-qa:
-
-prod:
-```
-
-## Level 2 of config: paths and autos
-
-Each execution environment might have slightly different configurations. This allows us to change the configuration to meet the needs of each environment.
-
-``` yaml
-default:
- paths:
- autos:
-
-dev:
- paths:
- autos:
-
-qa:
- paths:
- autos:
-
-prod:
- paths:
- autos:
-```
-
-## Level 3 of config: configure the environment
-
-This is best illustrated with an example. For this example, we will focus on setting up one environment, the default configuration.
-
-If you wish to have different configurations based off your environment, you would need to expand this to fit your needs.
-
-``` yaml
-default:
- paths:
- autos:
-```
-
-## A working example
-
-First we will need to read in data, write out results and save the script for future reference for a project we'll call **project1**. So we need an object to point to each of these locations, and we add the `data`, `output` and `programs` objects to our config.
-
-``` yaml
-default:
- paths:
- data: "/demo/DEV/username/project1/data"
- output: "/demo/DEV/username/project1/output"
- programs: "/demo/DEV/username/project1/programs"
-```
-
-A working example is even better, so let's create a temporary directory and store this config file as `_envsetup.yml`.
-
-
-```{r}
-library(envsetup)
-
-# create temporary directory
-dir <- fs::file_temp()
-dir.create(dir)
-config_path <- file.path(dir, "_envsetup.yml")
-
-# write a config file to it
-file_conn <- file(config_path)
-writeLines(
-"default:
- paths:
- data: '/demo/DEV/username/project1/data'
- output: '/demo/DEV/username/project1/output'
- programs: '/demo/DEV/username/project1/programs'", file_conn)
-close(file_conn)
-```
-
-We can then call `rprofile()`, passing in this configuration.
-
-```{r}
-# Set up the project
-envsetup_config <- config::get(file = config_path)
-
-rprofile(envsetup_config)
-```
-
-We now have data, output and programs available to us in our search path within `envsetup:paths`. Let's take a look:
-
-```{r echo = TRUE}
-objects("envsetup:paths")
-
-data
-output
-programs
-```
-
-Alright!
-
-Now let's go one step further and imagine a programmer, we'll call them Tidy McVerse. Miss McVerse needs to read in some data and this data is in the development area when she started programming.
-
-This is great! We already have the object data that points to "/demo/DEV/username/project1/data".
-
-Half way through programming, the data was considered production ready and the data moved from "/demo/DEV/username/project1/data" to "/demo/PROD/project1/data". Miss McVerse should not need to change her programs now, she needs a way to read data that is smarter than the average bear.
-
-The same object she uses to read in the data should work if the data is in "/demo/DEV/username/project1/data" or "/demo/PROD/project1/data".
-
-Let's create a config to keep Tidy McVerse happy and focused on the results, not data locations.
-
-Here we have a configuration where we execute some R code to build a list for our possible data sources, [see the config package for details](https://rstudio.github.io/config/articles/introduction.html#r-code).
-
-``` yaml
-default:
- paths:
- data: !expr list(DEV = '/demo/DEV/username/project1/data', PROD = '/demo/PROD/project1/data')
- output: '/demo/DEV/username/project1/output'
- programs: '/demo/DEV/username/project1/programs'
- envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'
-```
-
-Once again, we have a working example if you would like to code along. We will overwrite the previous config file with our new config.
-
-```{r}
-file_conn <- file(config_path)
-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'
- envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'"
- ), file_conn)
-close(file_conn)
-```
-
-Now we can re-setup the project.
-
-```{r}
-# Set up the project
-envsetup_config <- config::get(file = config_path)
-
-rprofile(envsetup_config)
-```
-
-We have `data`, `output` and `programs` available to us in our search path within `envsetup:paths`, but `data` is now a named list with two locations. We also now have `envsetup_environ` which, we will get into more details later, just accept it exists for now.
-
-```{r echo = TRUE}
-objects("envsetup:paths")
-
-data
-output
-programs
-envsetup_environ
-```
-
-We can use `envsetup::read_path()` to help us find where the data is we would like to read.
-
-Let's create the directories in our temporary folder structure ...
-
-```{r}
-dir.create(file.path(dir, "/demo/DEV/username/project1/data"), recursive = TRUE)
-dir.create(file.path(dir, "/demo/PROD/project1/data"), recursive = TRUE)
-```
-
-... and add `mtcars` to the PROD data directory, "/demo/PROD/project1/data".
-
-```{r}
-saveRDS(mtcars, file.path(dir, "/demo/PROD/project1/data/mtcars.RDS"))
-```
-
-Now we can use `read_path()`, passing in the path object `data` to find where to read `mtcars.RDS`. The data is only in PROD so the function returns the path to PROD `mtcars.RDS`.
-
-```{r}
-read_path(data, "mtcars.RDS")
-```
-
-Let's keep going!
-
-What if the data was in DEV and PROD?
-
-Let's save the same data to DEV ...
-
-```{r}
-saveRDS(mtcars, file.path(dir, "/demo/DEV/username/project1/data/mtcars.RDS"))
-```
-
-... and see what `read_path()` returns.
-
-```{r}
-read_path(data, "mtcars.RDS")
-```
-
-We see the path to DEV now instead of the path to PROD.
-
-To explain this, we will now talk about `envsetup_environ`, which we set in the config earlier.
-
-When we have multiple paths, as we do here with data, this controls which paths should be checked. This is just an index. Wherever the environment is found in the list, only this location to the end will be checked for data.
-
-In this example below, we set `envsetup_environ = 'DEV'`. So DEV is first in our `data` list, meaning all locations are checked until the object is found or nothing is found.
-
-```{r eval = FALSE}
-default:
- paths:
- data: !expr list(DEV = '/demo/DEV/username/project1/data', PROD = '/demo/PROD/project1/data')
- output: '/demo/DEV/username/project1/output'
- programs: '/demo/DEV/username/project1/programs'
- envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'
-```
-
-Let's now add an execution environment for `PROD`. We cannot simply change `envsetup_environ` from `DEV` to `PROD`, or `DEV` wouldn't work. We need to add a configuration to `PROD`, otherwise it will use `default`.
-
-```{r eval = FALSE}
-default:
- paths:
- data: !expr list(DEV = '/demo/DEV/username/project1/data', PROD = '/demo/PROD/project1/data')
- output: '/demo/DEV/username/project1/output'
- programs: '/demo/DEV/username/project1/programs'
- envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'
-
-prod:
- paths:
- envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'PROD'); 'PROD'
-```
-
-So we will write this new config out ...
-
-```{r}
-# overwrite the config file to the temporary directory previously setup
-file_conn <- file(config_path)
-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'
- envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'
-
-prod:
- paths:
- envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'PROD'); 'PROD'"
- ), file_conn)
-close(file_conn)
-```
-
-... and use it to overwrite the project with our new configuration.
-
-```{r}
-# setup the project
-envsetup_config <- config::get(file = config_path)
-
-rprofile(envsetup_config)
-```
-
-Let's check that `envsetup_environ` is now PROD.
-
-```{r}
-envsetup_environ
-```
-
-What! It isn't PROD.
-
-We must pass the configuration to `config:get()` telling it to use PROD.
-
-```{r}
-envsetup_config <- config::get(file = config_path, config = "prod")
-
-rprofile(envsetup_config)
-
-envsetup_environ
-```
-
-Now lets see what has changed when we call `read_path()` for `mtcars.RDS` using the PROD configuration.
-
-```{r}
-read_path(data, "mtcars.RDS")
-```
-
-We see the path to PROD, even though data exists in both DEV and PROD. This is because data was indexed starting with the location of PROD, which is the last element in data, so only this location was checked, excluding DEV.
-
-Miss McVerse no longer needs to think about where her data is in the workflow, and can use `read_path(data, ...)` to determine the correct path.
-
-We can apply the same steps to update our configuration for `output` and `programs` to account for `PROD` as well.
-
-```{r echo = FALSE}
-unlink(dir, recursive=TRUE)
-```
-
-# AUTOS
-
-This adds multiple environments to your search path, each of which contain objects that are automatically sourced.
-
-```{r eval = FALSE}
-default:
- autos:
-```
-
-## A working example
-
-So let's go back to Tidy McVerse. She has created a custom, one off function and stored this in `/demo/DEV/username/project1/script_library`.
-
-We will add this path to the autos config.
-
-```{r eval = FALSE}
-default:
- autos:
- dev_script_library: '/demo/DEV/username/project1/script_library'
-```
-
-Let's look at a working example. We will create the directory, place a script into the folder ...
-
-```{r}
-# create the temp directory
-dir <- fs::file_temp()
-dir.create(dir)
-dir.create(file.path(dir, "/demo/DEV/username/project1/script_library"), recursive = TRUE)
-
-# write a function to the folder
-file_conn <- file(file.path(dir, "/demo/DEV/username/project1/script_library/test.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:
- dev_script_library: '", dir,"/demo/DEV/username/project1/script_library'"
- ), file_conn)
-close(file_conn)
-```
-
-... and call `rprofile()` passing in this config file.
-
-```{r}
-envsetup_config <- config::get(file = config_path)
-
-rprofile(envsetup_config)
-```
-
-Now we can see `autos:dev_script_library` was added to the search path.
-
-```{r}
-search()
-```
-
-`test()` is available within this environment, and we can execute this function without sourcing.
-
-```{r}
-objects("autos:dev_script_library")
-
-test()
-```
-
-Why on earth would we need this?
-
-Just as with our previous data example, these scripts can be in multiple locations during their qualification lifecycle.
-
-So let's say Tidy McVerse's friend, Sir Purrr, has a function that is useful for others in this specific project, but it is already in prod. Miss McVerse would like to use her function in dev and Sir Purrr's function in prod.
-
-To illustrate this, let's add the prod script library to our config ...
-
-```{r eval = FALSE}
-default:
- autos:
- dev_script_library: '/demo/DEV/username/project1/script_library'
- prod_script_library: '/demo/PROD/project1/script_library'
-```
-
-... create the `PROD` directory and Sir Purrr's function to `PROD`.
-
-```{r}
-dir.create(file.path(dir, "/demo/PROD/project1/script_library"), recursive = TRUE)
-
-# write a function to the folder
-file_conn <- file(file.path(dir, "/demo/PROD/project1/script_library/test2.R"))
-writeLines(
-"test2 <- function(){print('test2')}", file_conn)
-close(file_conn)
-```
-
-Then we can overwrite our `_envsetup.yml` ...
-
-```{r}
-# write the config
-file_conn <- file(config_path)
-writeLines(
- paste0(
-"default:
- autos:
- dev_script_library: '", dir,"/demo/DEV/username/project1/script_library'
- prod_script_library: '", dir,"/demo/PROD/project1/script_library'"
- ), file_conn)
-close(file_conn)
-```
-
-... and overwrite the project with our new configuration.
-
-```{r}
-envsetup_config <- config::get(file = config_path)
-
-rprofile(envsetup_config)
-```
-
-Now we can see `prod_script_library` was added to the search path, the function `test()` and `test2()` are available, and we can execute these functions without a need for sourcing.
-
-```{r}
-search()
-
-objects("autos:prod_script_library")
-
-test()
-test2()
-```
-
-We can keep going and create different configurations for each execution environment, similar to what we did for PATHS above.
-
-One example that we would not want to source any functions in dev, when executing in prod. This configuration example is one way you can handle this situation, by blanking out the dev script location when executing in prod.
-
-```{r eval=FALSE}
-# write the config
-file_conn <- file(config_path)
-writeLines(
- paste0(
-"default:
- autos:
- dev_script_library: '", dir,"/demo/DEV/username/project1/script_library'
- prod_script_library: '", dir,"/demo/PROD/project1/script_library'
-prod:
- autos:
- dev_script_library: ''"
- ), file_conn)
-close(file_conn)
-
-envsetup_config <- config::get(file = config_path, config = "prod")
-
-rprofile(envsetup_config)
-```
-
-
-```{r echo = FALSE}
-unlink(dir, recursive=TRUE)
-```
diff --git a/vignettes/dynamic_paths.Rmd b/vignettes/dynamic_paths.Rmd
new file mode 100644
index 0000000..29fa492
--- /dev/null
+++ b/vignettes/dynamic_paths.Rmd
@@ -0,0 +1,202 @@
+---
+title: "Dynamic Path Management"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Dynamic Path Management}
+ %\VignetteEngine{knitr::rmarkdown}
+ %\VignetteEncoding{UTF-8}
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>"
+)
+```
+
+# Advanced Path Resolution
+
+Sometimes data moves between environments during development, or you need to check multiple locations for files. This guide shows you how to set up dynamic path resolution that adapts to your workflow.
+
+## The Problem: Moving Data
+
+Imagine this scenario with our friend Tidy McVerse:
+
+1. She starts programming with data in development: `/demo/DEV/username/project1/data`
+2. Halfway through, the data becomes production-ready and moves to: `/demo/PROD/project1/data`
+3. Her code should work without changes, regardless of where the data lives
+
+## Solution: Multiple Path Locations
+
+Configure paths as lists with multiple possible locations:
+
+```yaml
+default:
+ paths:
+ data: !expr list(DEV = '/demo/DEV/username/project1/data', PROD = '/demo/PROD/project1/data')
+ output: '/demo/DEV/username/project1/output'
+ programs: '/demo/DEV/username/project1/programs'
+ envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'
+```
+
+## Working Example Setup
+
+```{r}
+library(envsetup)
+
+# Create temporary directory structure
+dir <- fs::file_temp()
+dir.create(dir)
+config_path <- file.path(dir, "_envsetup.yml")
+
+# Write configuration with multiple data paths
+file_conn <- file(config_path)
+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'
+ envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'"
+ ), file_conn)
+close(file_conn)
+
+# Load and apply configuration
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+```
+
+## Understanding the Configuration
+
+Let's examine what we now have available:
+
+```{r echo = TRUE}
+# See all configured objects
+ls(envsetup_environment)
+
+# Data is now a named list with multiple locations
+get_path(data)
+get_path(output)
+get_path(programs)
+get_path(envsetup_environ)
+```
+
+## Using read_path() for Smart File Location
+
+The `read_path()` function searches through your path list to find files:
+
+```{r}
+# Create the directory structure
+dir.create(file.path(dir, "/demo/DEV/username/project1/data"), recursive = TRUE)
+dir.create(file.path(dir, "/demo/PROD/project1/data"), recursive = TRUE)
+
+# Add data only to PROD location
+saveRDS(mtcars, file.path(dir, "/demo/PROD/project1/data/mtcars.RDS"))
+
+# read_path() finds the file in PROD
+read_path(data, "mtcars.RDS")
+```
+
+## Path Search Order
+
+When data exists in multiple locations, `read_path()` follows the search order:
+
+```{r}
+# Add the same data to DEV location
+saveRDS(mtcars, file.path(dir, "/demo/DEV/username/project1/data/mtcars.RDS"))
+
+# Now read_path() returns DEV location (first in search order)
+read_path(data, "mtcars.RDS")
+```
+
+## Controlling Search Order with envsetup_environ
+
+The `envsetup_environ` variable controls which paths are searched:
+
+- **DEV**: Searches DEV first, then PROD
+- **PROD**: Searches only PROD (skips DEV)
+
+## Environment-Specific Path Resolution
+
+Let's add a production configuration that changes the search behavior:
+
+```{r}
+# Update config to include prod environment
+file_conn <- file(config_path)
+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'
+ envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'DEV'); 'DEV'
+
+prod:
+ paths:
+ envsetup_environ: !expr Sys.setenv(ENVSETUP_ENVIRON = 'PROD'); 'PROD'"
+ ), file_conn)
+close(file_conn)
+
+# Load production configuration
+envsetup_config <- config::get(file = config_path, config = "prod")
+rprofile(envsetup_config)
+
+# Check the environment setting
+get_path(envsetup_environ)
+```
+
+## Production Path Resolution
+
+With the production configuration, path resolution behavior changes:
+
+```{r}
+# In production, read_path() returns PROD location even though DEV exists
+read_path(data, "mtcars.RDS")
+```
+
+## Practical Usage Pattern
+
+Here's how you'd typically use this in your code:
+
+```{r eval=FALSE}
+# Instead of hardcoding paths:
+# my_data <- readRDS("/some/hardcoded/path/mtcars.RDS")
+
+# Use dynamic path resolution:
+data_path <- read_path(data, "mtcars.RDS")
+my_data <- readRDS(data_path)
+
+# This works regardless of environment or data location!
+```
+
+## Benefits of Dynamic Paths
+
+1. **Workflow Flexibility**: Code works as data moves through environments
+2. **Environment Awareness**: Different search strategies per environment
+3. **Fallback Logic**: Automatic fallback to alternative locations
+4. **Code Stability**: No code changes needed when paths change
+
+## Common Patterns
+
+### Development-First Search
+```yaml
+data: !expr list(DEV = '/dev/path', PROD = '/prod/path')
+envsetup_environ: 'DEV' # Searches all locations starting from DEV
+```
+
+### Production-Only Search
+```yaml
+data: !expr list(DEV = '/dev/path', PROD = '/prod/path')
+envsetup_environ: 'PROD' # Searches only PROD location
+```
+
+```{r echo = FALSE}
+# Clean up
+unlink(dir, recursive=TRUE)
+```
+
+## Next Steps
+
+The next guide covers automatic script sourcing, which lets you automatically load custom functions from multiple script libraries across environments.
diff --git a/vignettes/multiple_environments.Rmd b/vignettes/multiple_environments.Rmd
new file mode 100644
index 0000000..6dc1985
--- /dev/null
+++ b/vignettes/multiple_environments.Rmd
@@ -0,0 +1,188 @@
+---
+title: "Managing Multiple Environments"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Managing Multiple Environments}
+ %\VignetteEngine{knitr::rmarkdown}
+ %\VignetteEncoding{UTF-8}
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>"
+)
+```
+
+# Multiple Environment Configuration
+
+Real-world projects typically need different configurations for development, quality assurance, and production environments. This guide shows you how to set up environment-specific configurations.
+
+## The Challenge
+
+Consider this common workflow:
+
+- **Development**: Developers work with data to produce outputs
+
+- **QA**: Quality assurance independently confirms the results of the developer
+
+- **Production**: Verified data and final outputs are made available to
+
+Each environment needs different paths, but you don't want to change your code.
+
+## Environment-Specific Configuration
+
+Here's how to set up different configurations for each environment:
+
+``` yaml
+default:
+ paths:
+ data: "/demo/DEV/username/project1/data"
+ output: "/demo/DEV/username/project1/output"
+ programs: "/demo/DEV/username/project1/programs"
+
+qa:
+ paths:
+ data: "/demo/QA/project1/data"
+ output: "/demo/QA/project1/output"
+ programs: "/demo/QA/project1/programs"
+
+prod:
+ paths:
+ data: "/demo/PROD/project1/data"
+ output: "/demo/PROD/project1/output"
+ programs: "/demo/PROD/project1/programs"
+```
+
+## Working Example
+
+Let's create a multi-environment configuration:
+
+```{r}
+library(envsetup)
+
+# Create temporary directory
+dir <- fs::file_temp()
+dir.create(dir)
+config_path <- file.path(dir, "_envsetup.yml")
+
+# Write multi-environment config
+file_conn <- file(config_path)
+writeLines(
+ paste0(
+"default:
+ paths:
+ data: '", dir, "/demo/DEV/username/project1/data'
+ output: '", dir, "/demo/DEV/username/project1/output'
+ programs: '", dir, "/demo/DEV/username/project1/programs'
+
+qa:
+ paths:
+ data: '", dir, "/demo/QA/project1/data'
+ output: '", dir, "/demo/QA/project1/output'
+ programs: '", dir, "/demo/QA/project1/programs'
+
+prod:
+ paths:
+ data: '", dir, "/demo/PROD/project1/data'
+ output: '", dir, "/demo/PROD/project1/output'
+ programs: '", dir, "/demo/PROD/project1/programs'"
+ ), file_conn)
+close(file_conn)
+```
+
+## Loading Different Environments
+
+### Default Environment
+
+```{r}
+# Load default configuration (development)
+envsetup_config <- config::get(file = config_path)
+rprofile(envsetup_config)
+
+# Check the paths
+cat("Default environment paths:\n")
+cat("Data:", get_path(data), "\n")
+cat("Output:", get_path(output), "\n")
+```
+
+### QA Environment
+
+```{r}
+# Load QA configuration
+envsetup_config <- config::get(file = config_path, config = "qa")
+rprofile(envsetup_config)
+
+# Check the paths
+cat("QA environment paths:\n")
+cat("Data:", get_path(data), "\n")
+cat("Output:", get_path(output), "\n")
+```
+
+### Production Environment
+
+```{r}
+# Load production configuration
+envsetup_config <- config::get(file = config_path, config = "prod")
+rprofile(envsetup_config)
+
+# Check the paths
+cat("Production environment paths:\n")
+cat("Data:", get_path(data), "\n")
+cat("Output:", get_path(output), "\n")
+```
+
+## Configuration Inheritance
+
+The `config` package supports inheritance, meaning environments can inherit from `default` and only override specific settings:
+
+``` yaml
+default:
+ paths:
+ data: "/demo/DEV/username/project1/data"
+ output: "/demo/DEV/username/project1/output"
+ programs: "/demo/DEV/username/project1/programs"
+ log_level: "DEBUG"
+
+prod:
+ paths:
+ data: "/demo/PROD/project1/data"
+ output: "/demo/PROD/project1/output"
+ log_level: "ERROR"
+ # programs inherits from default
+```
+
+## Environment Selection Strategies
+
+### 1. Environment Variable
+
+``` r
+# Set environment variable
+Sys.setenv(R_CONFIG_ACTIVE = "prod")
+envsetup_config <- config::get(file = "_envsetup.yml")
+```
+
+### 2. Programmatic Selection
+
+``` r
+# Choose environment in code
+environment <- "prod"
+envsetup_config <- config::get(file = "_envsetup.yml", config = environment)
+```
+
+## Best Practices
+
+1. **Use `default` for development**: Most development work happens here
+2. **Minimize environment differences**: Only change what's necessary
+3. **Document environment purposes**: Clear comments in your YAML
+4. **Test all environments**: Ensure configurations work as expected
+5. **Version control**: Keep `_envsetup.yml` in your repository
+
+```{r echo = FALSE}
+# Clean up
+unlink(dir, recursive=TRUE)
+```
+
+## Next Steps
+
+The next guide covers advanced path management, including how to handle situations where data might exist in multiple locations and you need smart path resolution.