├── .Rbuildignore ├── .codecov.yml ├── .github ├── .gitignore └── README.md ├── .gitignore ├── .woodpecker └── r-pkg-standard.yaml ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── aaa.R ├── crayon.R ├── define_option.R ├── envvars.R ├── errors.R ├── naming.R ├── options_env.R ├── options_get.R ├── options_roxygen.R ├── options_spec.R └── utils.R ├── README.md ├── inst └── options.example │ ├── DESCRIPTION │ ├── LICENSE │ ├── NAMESPACE │ ├── R │ ├── helpers.R │ ├── options.R │ └── roxygen2.R │ └── man │ ├── fizzbuzz.Rd │ ├── hello.Rd │ ├── options.Rd │ ├── options_params.Rd │ └── show_option.Rd ├── man ├── as_params.Rd ├── as_roxygen_docs.Rd ├── assert_naming_fn_signature.Rd ├── defining_options.Rd ├── envvar_fns.Rd ├── err.Rd ├── format.option_spec.Rd ├── format.options_env.Rd ├── format.options_list.Rd ├── format_field.Rd ├── format_value.Rd ├── get_options_env.Rd ├── naming.Rd ├── naming_formats.Rd ├── opt.Rd ├── option_spec.Rd ├── options_env.Rd ├── options_fmts.Rd ├── pkgname.Rd └── reflow_option_desc.Rd ├── pkgdown ├── _pkgdown.yml └── extra.scss ├── tests ├── testthat.R └── testthat │ ├── setup.R │ ├── test-define-option.R │ ├── test-envvars.R │ ├── test-naming.R │ ├── test-opt.R │ ├── test-opt_set.R │ ├── test-opt_set_local.R │ ├── test-options_env.R │ ├── test-opts_list.R │ ├── test-output.R │ ├── test-precedence.R │ ├── test-rcmdcheck.R │ └── test-roxygen2.R └── vignettes ├── .gitignore ├── envvars.Rmd └── options.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.git$ 2 | ^\.github$ 3 | 4 | ^LICENSE\.md$ 5 | ^_pkgdown\.yml$ 6 | ^docs$ 7 | ^pkgdown$ 8 | 9 | ^inst/options\.example/man$ 10 | 11 | ^\.woodpecker$ 12 | ^\.codecov\.yml$ 13 | 14 | \.Rcheck$ 15 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | ci: 3 | - codeberg.org 4 | - ci.codeberg.org 5 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # :no_entry: Read-Only Mirror :no_entry: 2 | 3 | _This repository has moved to 4 | [codeberg.org/dgkf/options](https://codeberg.org/dgkf/options)_ 5 | 6 | Don't worry! This mirror is in place so that you can still file issues. 7 | That said, it would mean a lot to me if you joined the discussion over at 8 | [codeberg.org/dgkf/options](https://codeberg.org/dgkf/options). 9 | 10 | ## Why [codeberg](https://codeberg.org)? 11 | 12 | I build open-source tools because I want to give back to the general public 13 | by improving our collective software stack. Codeberg is an alternative to 14 | GitHub that is better aligned with this perspective. 15 | 16 | Codeberg is based on a fully open-source stack, is run by a non-profit, 17 | and has no other ancillary for-profit products that it is trying to sell using 18 | its platform. 19 | 20 | ## Why migrate `options`? 21 | 22 | Of all my tools, `options` is the one that is most specifically targetted at 23 | developers, and developers are the ones who need to spark a change in which 24 | platforms the open source community adopts. If you're here, you're the 25 | person who needs to drive the change toward better software ecosystem 26 | diversity and I believe the best alternative at this moment is 27 | [codeberg](https://codeberg.org). 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs 2 | inst/doc 3 | **.tar.gz 4 | **.Rcheck 5 | -------------------------------------------------------------------------------- /.woodpecker/r-pkg-standard.yaml: -------------------------------------------------------------------------------- 1 | # this workflow maintained at https://codeberg.org/r-codeberg/woodpecker-ci 2 | # please file bug reports and feature requests to help it improve! 3 | 4 | matrix: 5 | IMAGE: 6 | - rocker/verse 7 | 8 | when: 9 | - branch: "${CI_REPO_DEFAULT_BRANCH}" 10 | event: push 11 | - event: pull_request 12 | - event: tag 13 | 14 | variables: 15 | site_config: &site_config 16 | - export WOODPECKER_TMP=/woodpecker/tmp 17 | 18 | r_envvars: &r_envvars 19 | R_NCPU: 1 20 | RCMDCHECK_ERROR_ON: "note" 21 | 22 | _R_CHECK_FORCE_SUGGESTS_: "false" 23 | _R_CHECK_CRAN_INCOMING_: "false" 24 | _R_CHECK_SYSTEM_CLOCK_: "false" 25 | 26 | # CRAN OMP thread limit (https://stackoverflow.com/a/77323812) 27 | # Resolves issue with R CMD check for CPU time exceeding elapsed time 28 | OMP_THREAD_LIMIT: 2 29 | 30 | r_site_config: &r_site_config 31 | # These environment variables used to help find appropriate binaries in PPM 32 | - cat /etc/os-release >> "$(R RHOME)/etc/Renviron.site" 33 | 34 | # Store our temporary directory, accessible across workflow steps 35 | - echo "WOODPECKER_TMP=/woodpecker/tmp" >> "$(R RHOME)/etc/Renviron.site" 36 | 37 | # Configure R profile to use during ci 38 | # - sets options used throughout workflow 39 | # - configures a package library that will be re-used by each step 40 | - | 41 | cat << END >> $(R RHOME)/etc/Rprofile.site 42 | options( 43 | Ncpus = Sys.getenv("R_NCPU", unset = getOption("Ncpus", 1L)), 44 | repos = c( 45 | "p3m.dev" = sprintf( 46 | "https://p3m.dev/cran/__linux__/%s/latest", 47 | if (nchar(envvar <- Sys.getenv("VERSION_CODENAME"))) envvar 48 | ), 49 | CRAN = "https://cloud.r-project.org" 50 | ), 51 | pak.sysreqs = TRUE, 52 | 53 | # these settings used to provide prettier ci log output 54 | cli.dynamic = FALSE, 55 | cli.default_num_colors = 256, 56 | crayon.enabled = TRUE 57 | ) 58 | 59 | local({ 60 | tmp <- Sys.getenv("WOODPECKER_TMP") 61 | if (!dir.exists(tmp)) dir.create(tmp, recursive = TRUE) 62 | lib <- file.path("", "woodpecker", "lib") 63 | if (!dir.exists(lib)) dir.create(lib, recursive = TRUE) 64 | .libPaths(c(lib, .libPaths())) 65 | }) 66 | END 67 | 68 | step_setup: &setup 69 | - name: setup 70 | image: ${IMAGE} 71 | pull: true 72 | environment: 73 | <<: *r_envvars 74 | commands: 75 | - <<: *r_site_config 76 | - | 77 | R -q -s --no-save << "END" 78 | pak_repo <- sprintf( 79 | "https://r-lib.github.io/p/pak/stable/%s/%s/%s", 80 | .Platform$pkgType, 81 | R.Version()$os, 82 | R.Version()$arch 83 | ) 84 | 85 | install.packages("pak", repos = pak_repo) 86 | pak::local_install_dev_deps() 87 | END 88 | 89 | step_check: &check 90 | - name: check 91 | image: ${IMAGE} 92 | depends_on: [setup] 93 | pull: true 94 | environment: 95 | <<: *r_envvars 96 | commands: 97 | - <<: *r_site_config 98 | - | 99 | R -q -s --no-save << "END" 100 | pak::local_install_dev_deps() 101 | pak::pkg_install("rcmdcheck") 102 | rcmdcheck::rcmdcheck( 103 | args = c("--no-manual", "--as-cran", "--no-tests", "--timings"), 104 | build_args = c("--no-manual"), 105 | check_dir = "check" 106 | ) 107 | END 108 | 109 | step_test: &test 110 | - name: test 111 | image: ${IMAGE} 112 | depends_on: [setup] 113 | pull: true 114 | environment: 115 | <<: *r_envvars 116 | commands: 117 | - <<: *r_site_config 118 | - | 119 | R -q -s --no-save << "END" 120 | pak::local_install_dev_deps() 121 | pak::pkg_install(c("covr", "xml2")) 122 | 123 | woodpecker_tmp <- normalizePath(Sys.getenv("WOODPECKER_TMP"), winslash = "/") 124 | woodpecker_package <- file.path(woodpecker_tmp, "package") 125 | 126 | cov <- covr::package_coverage( 127 | quiet = FALSE, 128 | clean = FALSE, 129 | install_path = woodpecker_package 130 | ) 131 | 132 | woodpecker_cobertura_xml <- file.path(woodpecker_tmp, "cobertura.xml") 133 | covr::to_cobertura(cov, filename = woodpecker_cobertura_xml) 134 | END 135 | - | 136 | echo '─ test results ────────────────────────────────────────────────────────────────' 137 | find '/woodpecker/tmp/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 138 | 139 | step_pkgdown: &pkgdown 140 | - name: pkgdown 141 | image: ${IMAGE} 142 | depends_on: [setup] 143 | environment: 144 | <<: *r_envvars 145 | commands: 146 | - <<: *r_site_config 147 | - | 148 | R -q -s --no-save << "END" 149 | pak::local_install_dev_deps() 150 | pak::pkg_install(".") 151 | pak::pkg_install("pkgdown") 152 | woodpecker_tmp <- normalizePath(Sys.getenv("WOODPECKER_TMP"), winslash = "/") 153 | woodpecker_pages <- file.path(woodpecker_tmp, "pages") 154 | pkg <- pkgdown::as_pkgdown(".", override = list(destination = woodpecker_pages)) 155 | pkgdown::build_site(pkg, new_process = FALSE, install = FALSE) 156 | END 157 | 158 | step_deploy: &deploy 159 | - name: deploy 160 | image: alpine/git 161 | depends_on: [pkgdown] 162 | when: 163 | - branch: "${CI_REPO_DEFAULT_BRANCH}" 164 | evaluate: "DEPLOY_SSH_KEY != nil" 165 | event: push 166 | environment: 167 | <<: *r_envvars 168 | DEPLOY_SSH_KEY: 169 | # expects woodpecker ci ssh access 170 | # - generated with `ssh-keygen -t ed25519 -f /tmp/key` 171 | # - private key: store as woodpecker ci secret `deploy_ssh_key`, 172 | # optionally set only accessible in `alpine/git` image 173 | # - public key: provide as a Deploy Key in codeberg repository 174 | # settings, granting write access to repository 175 | from_secret: deploy_ssh_key 176 | commands: 177 | - <<: *site_config 178 | # copy author details of last commit 179 | - export GIT_USER_NAME=$$(git log -1 --pretty=format:'%an') 180 | - export GIT_USER_EMAIL=$$(git log -1 --pretty=format:'%ae') 181 | 182 | # set up ssh key, apply known hosts 183 | - eval $(ssh-agent -s) 184 | - echo "$${DEPLOY_SSH_KEY}" | ssh-add - 185 | - mkdir -p ~/.ssh 186 | - ssh-keyscan -H codeberg.org >> ~/.ssh/known_hosts 187 | 188 | # clone repo, create new branch, copy in pages 189 | - git clone --depth 1 $${CI_REPO_CLONE_SSH_URL} $${WOODPECKER_TMP}/deploy 190 | - cd $${WOODPECKER_TMP}/deploy 191 | - git switch --orphan pages 192 | - git pull origin pages || echo "pages branch not found, creating ..." 193 | - git rm --cached --ignore-unmatch -r . 194 | - cp -r $${WOODPECKER_TMP}/pages/* . 195 | 196 | # commit new pages contents & deploy 197 | - git add -A 198 | - git config --global user.name $${GIT_USER_NAME} 199 | - git config --global user.email $${GIT_USER_EMAIL} 200 | - git commit -m "deploy to pages from $${CI_COMMIT_BRANCH} @ $${CI_COMMIT_SHA:0:8}" 201 | - git push origin pages 202 | 203 | steps: 204 | - <<: *setup 205 | - <<: *check 206 | - <<: *test 207 | - <<: *pkgdown 208 | - <<: *deploy 209 | 210 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: options 2 | Title: Simple, Consistent Package Options 3 | Version: 0.3.1 4 | Authors@R: 5 | person( 6 | "Doug", 7 | "Kelkhoff", 8 | email = "doug.kelkhoff@gmail.com", 9 | role = c("aut", "cre") 10 | ) 11 | Description: 12 | Simple mechanisms for defining and interpreting package options. Provides 13 | helpers for interpreting environment variables, global options, defining 14 | default values and more. 15 | License: 16 | MIT + file LICENSE 17 | URL: 18 | https://dgkf.github.io/options/, 19 | https://codeberg.org/dgkf/options 20 | BugReports: 21 | https://codeberg.org/dgkf/options/issues 22 | Imports: 23 | utils 24 | Suggests: 25 | crayon, 26 | knitr, 27 | rmarkdown, 28 | roxygen2, 29 | rcmdcheck, 30 | pkgload, 31 | withr, 32 | testthat (>= 3.0.0) 33 | Config/Needs/website: 34 | pkgdown 35 | Roxygen: 36 | list(markdown = TRUE) 37 | VignetteBuilder: 38 | knitr 39 | Encoding: UTF-8 40 | LazyData: true 41 | RoxygenNote: 7.3.2 42 | Config/testthat/edition: 3 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2022 2 | COPYRIGHT HOLDER: options authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2022 options authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(as.list,options_env) 4 | S3method(as_check_names_fn,"function") 5 | S3method(as_check_names_fn,character) 6 | S3method(conditionCall,options_error) 7 | S3method(define_option,character) 8 | S3method(define_option,option_spec) 9 | S3method(envvar_is,"NULL") 10 | S3method(envvar_is,character) 11 | S3method(envvar_is,default) 12 | S3method(envvar_is,logical) 13 | S3method(envvar_is,numeric) 14 | S3method(format,option_spec) 15 | S3method(format,options_env) 16 | S3method(get_options_env,default) 17 | S3method(get_options_env,options_env) 18 | S3method(get_options_env,options_list) 19 | S3method(opts,"NULL") 20 | S3method(opts,character) 21 | S3method(opts,list) 22 | S3method(print,option_spec) 23 | S3method(print,options_env) 24 | S3method(print,options_list) 25 | export("opt<-") 26 | export(as_params) 27 | export(as_roxygen_docs) 28 | export(define_option) 29 | export(define_options) 30 | export(envvar_choice_of) 31 | export(envvar_eval) 32 | export(envvar_eval_or_raw) 33 | export(envvar_is) 34 | export(envvar_is_false) 35 | export(envvar_is_one_of) 36 | export(envvar_is_set) 37 | export(envvar_is_true) 38 | export(envvar_name_default) 39 | export(envvar_name_generic) 40 | export(envvar_str_split) 41 | export(get_options_env) 42 | export(opt) 43 | export(opt_set) 44 | export(opt_source) 45 | export(option_name_default) 46 | export(option_spec) 47 | export(opts) 48 | export(opts_list) 49 | export(set_envvar_name_fn) 50 | export(set_option_name_fn) 51 | importFrom(utils,capture.output) 52 | importFrom(utils,packageName) 53 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # options 0.3.1 2 | 3 | ## Bug Fixes 4 | 5 | * Fixed `define_options` when using a `NULL` default value. Previously, 6 | the `NULL` value would not be recognized and would silently ignore the 7 | option definition. (@dgkf #34) 8 | 9 | * Fixed `envvar_is.logical` (and thereby, `envvar_is_true`, `envvar_is_false`), 10 | leveraging `as.logical` internally to make the parsing from strings align 11 | more closely to what one may expect in R. Notably, all lowercase values 12 | `true` and `false` will now be recognized as their respective logical values. 13 | (@dgkf #34) 14 | 15 | # options 0.3.0 16 | 17 | * Introduces `opts_list()`, a utility for producing a list of option values with 18 | appropriate global option names that can be used more readily with 19 | `options()` and `withr::with_options()`. (@dgkf #19) 20 | 21 | ## Bug Fixes 22 | 23 | * Fixed `envvar_str_split()` not making us of `delim` parameter. (@dgkf #23) 24 | 25 | # options 0.2.0 26 | 27 | * Fixes `opts()`, which would previously return default values after being 28 | updated. Will now appropriately return values just as they would be fetched 29 | using `opt()`. (@dgkf #17) 30 | 31 | * Exposes `get_options_env()` (currently experimental), for the purpose of 32 | accessing a listing of option names. (@dgkf #17) 33 | 34 | * Adds an optional `option_fn` parameter to `option_spec`, allowing for the 35 | stored option values to be processed, or to produce side-effects when 36 | accessed. (@dgkf #12) 37 | 38 | ## Breaking Changes 39 | 40 | * The result of `opt_source()` when a value is derived from an environment 41 | variable was changed from `"envir"` to `"envvar"` to be more consistent with 42 | the rest of the package's messaging about sources. (@dgkf #12) 43 | 44 | # options 0.1.0 45 | 46 | * Adds various utility functions for modifying options: `opt_set()`, `opt()<-` 47 | and `opt_set_local()`. 48 | 49 | * Trying to retrieve an option that is not yet defined will now default to 50 | throwing a warning. This behavior can be modified using the `on_missing` 51 | argument to functions that fetch option values. 52 | 53 | # options 0.0.2 54 | 55 | * `opts()` slightly refactored to produce more constructive output when no 56 | option names are provided. You can now use `opts(env = package_name)` to 57 | fetch a full named list of option values. (@dgkf #2) 58 | 59 | * Generated `roxygen2` documentation using `as_roxygen_docs()` is now more 60 | consciencious about `R CMD check` requirements, moving `\usage{}` to a new 61 | section titled "Checking Option Values". (@dgkf #2) 62 | 63 | # options 0.0.1 64 | 65 | * `options` split from `dgkf/devutils` 66 | -------------------------------------------------------------------------------- /R/aaa.R: -------------------------------------------------------------------------------- 1 | CONST_OPTIONS_ENV_NAME <- ".options" 2 | CONST_OPTIONS_META <- ".meta" 3 | -------------------------------------------------------------------------------- /R/crayon.R: -------------------------------------------------------------------------------- 1 | #' Optional Crayon Handling 2 | #' 3 | #' Generate a list of styling functions using `crayon`, while safely falling 4 | #' back to non-`crayon` output when `crayon` is unavailable. 5 | #' 6 | #' @return formatted text 7 | #' 8 | #' @keywords internal 9 | options_fmts <- function() { 10 | crayon <- tryCatch(getNamespace("crayon"), error = function(e) list()) 11 | 12 | italic <- crayon$italic %||% identity 13 | bold <- crayon$bold %||% identity 14 | blue <- crayon$blue %||% identity 15 | cyan <- crayon$cyan %||% identity 16 | yellow <- crayon$yellow %||% identity 17 | green <- crayon$green %||% identity 18 | silver <- crayon$silver %||% identity 19 | 20 | fmt <- list() 21 | fmt$name <- function(x) italic(bold(blue(x))) 22 | fmt$desc <- identity 23 | fmt$field_inactive <- identity 24 | fmt$field_active <- green 25 | fmt$optname <- cyan 26 | fmt$fade <- silver 27 | fmt$shorthand <- function(x) italic(blue(x)) 28 | 29 | fmt 30 | } 31 | -------------------------------------------------------------------------------- /R/define_option.R: -------------------------------------------------------------------------------- 1 | #' Defining Options 2 | #' 3 | #' Define options which can be used throughout your package. 4 | #' 5 | #' At their simplest, defining options lets you refer to a global option using a 6 | #' shorthand option name throughout your package, with the added benefit of 7 | #' looking for configurations in global options and environment variables. 8 | #' 9 | #' @param option An option name to use 10 | #' 11 | #' @return the package options environment 12 | #' 13 | #' @examples 14 | #' define_options( 15 | #' "Whether execution should emit console output", 16 | #' quiet = FALSE, 17 | #' "Whether to use detailed console output (showcasing additional 18 | #' configuration parameters)", 19 | #' verbose = TRUE, 20 | #' envvar_fn = envvar_is_true() 21 | #' ) 22 | #' 23 | #' define_option( 24 | #' "deprecations", 25 | #' desc = "Whether deprecation warnings should be suppressed automatically", 26 | #' default = FALSE, 27 | #' option_name = "MypackageDeprecations", 28 | #' envvar_name = "MYPACKAGE_ENVVARS_DEPRECATIONS" 29 | #' ) 30 | #' 31 | #' @name defining_options 32 | #' @rdname defining_options 33 | NULL 34 | 35 | #' @describeIn defining_options 36 | #' 37 | #' Define an option. Unlike [define_options()], this function allows detailed 38 | #' customization of all option behaviors. Accepts either an [option_spec()] 39 | #' object, or an option named followed by arguments to provide to 40 | #' [option_spec()]. 41 | #' 42 | #' @param ... Additional arguments passed to [option_spec()] 43 | #' 44 | #' @export 45 | define_option <- function(option, ...) { 46 | UseMethod("define_option") 47 | } 48 | 49 | #' @inheritParams option_spec 50 | #' @export 51 | define_option.character <- function( 52 | option, 53 | default = bquote(), 54 | ..., 55 | quoted = FALSE, 56 | eager = FALSE, 57 | envir = parent.frame() 58 | ) { 59 | if (!missing(default) && !quoted && !eager) { 60 | default <- match.call()[["default"]] 61 | } 62 | 63 | if (quoted && eager) { 64 | default <- eval(default, envir = envir) 65 | } 66 | 67 | define_option(option_spec( 68 | name = option, 69 | default = default, 70 | ..., 71 | quoted = TRUE, 72 | envir = envir 73 | )) 74 | } 75 | 76 | #' @export 77 | define_option.option_spec <- function(option, ...) { 78 | optenv <- get_options_env(option$envir, inherits = TRUE) 79 | do.call(delayedAssign, list(option$name, option$expr, option$envir, optenv)) 80 | set_option_spec(option$name, option, env = optenv) 81 | optenv 82 | } 83 | 84 | 85 | 86 | #' @describeIn defining_options 87 | #' 88 | #' Define multiple options. This function provides a shorthand syntax for 89 | #' succinctly defining many options. Arguments are defined in groups, each 90 | #' starting with an unnamed description argument. For more details see Section 91 | #' _Non-Standard Evaluation_. 92 | #' 93 | #' @section Non-Standard Evaluation: 94 | #' 95 | #' `define_options()` accepts arguments in a _non-standard_ 96 | #' way, as groups of arguments which each are used to specify an option (See 97 | #' `options_spec()`). Groups of arguments must start with an unnamed argument, 98 | #' which provides the description for the argument, followed immediately by a 99 | #' named argument providing the name of option and default value, followed by 100 | #' any additional arguments to provie to `options_spec()`. 101 | #' 102 | #' The environment in which options are defined is always assumed to be the 103 | #' parent environment. If you'd prefer to specify options in a different 104 | #' environment, this is best done using `define_option()` or 105 | #' `with(, define_options(...))`. 106 | #' 107 | #' Although `define_options()` provides all the functionality of 108 | #' `define_option()` in a succinct shorthand, it is only recommended in cases 109 | #' where the overwhelming majority of your options leverage default behaviors. 110 | #' It is encouraged to use `define_option()` if you repeatedly need more 111 | #' involved definitions to minimize non-standard evaluation bugs. 112 | #' 113 | #' @export 114 | define_options <- function(...) { 115 | eval_env <- parent.frame() 116 | x <- as.list(substitute(...())) 117 | 118 | # always use named arguments, even if no names are used 119 | if (is.null(names(x))) { 120 | names(x) <- rep("", length(x)) 121 | } 122 | 123 | # test against common non-standard eval syntax issues 124 | verify_define_options_syntax(x) 125 | 126 | # split arguments into groupings, building `option_spec` args 127 | specs <- lapply(split(x, cumsum(names(x) == "")), function(group) { 128 | # reassign option name, default from second arg in group 129 | args <- list() 130 | args$name <- names(group[2]) 131 | args["default"] <- list(group[[2]]) 132 | 133 | # build description from first (unnamed) arg in group 134 | args$desc <- reflow_option_desc(eval(group[[1]], envir = eval_env))[[1]] 135 | 136 | # build other arguments from remaining args 137 | other_args <- lapply(group[c(-1, -2)], eval, envir = eval_env) 138 | args[names(other_args)] <- other_args 139 | 140 | do.call(option_spec, args, envir = eval_env) 141 | }) 142 | 143 | for (spec in specs) define_option(spec) 144 | get_options_env(eval_env, inherits = TRUE) 145 | } 146 | 147 | 148 | 149 | verify_define_options_syntax <- function(x) { 150 | no_desc <- names(x)[[1]] != "" 151 | no_arg <- names(x) == "" & vlapply(x, function(i) all(nchar(i) == 0)) 152 | no_named_arg <- names(x) == "" & c(names(x)[-1] == "", TRUE) 153 | arg_desc <- c(names(x)[-length(x)] != "" & names(x)[-1] == "desc", FALSE) 154 | arg_name <- c(names(x)[-length(x)] != "" & names(x)[-1] == "name", FALSE) 155 | 156 | if (!any(no_desc | no_named_arg | arg_desc | arg_name)) { 157 | return(TRUE) 158 | } 159 | 160 | # helper for creating an itemized "issue" message as part of error message 161 | opt_n <- cumsum(names(x) != "" & c(TRUE, names(x)[-length(x)] == "")) 162 | issue <- function(at, ..., verbatim = FALSE) { 163 | if (!any(at)) { 164 | return(NULL) 165 | } 166 | opts <- paste0(unique(opt_n[which(at)]), collapse = ",") 167 | s <- if (sum(at) > 1) "s" else "" 168 | 169 | msg <- paste0(...) 170 | if (!verbatim) msg <- sprintf("option%s (%s) %s", s, opts, msg) 171 | 172 | out <- list(paste0(msg, collapse = "\n")) 173 | names(out) <- opts 174 | out 175 | } 176 | 177 | issues <- Filter(Negate(is.null), c( 178 | issue(no_arg, 179 | verbatim = TRUE, 180 | sprintf("missing argument %s (trailing comma in call)", which(no_arg)[1]) 181 | ), 182 | issue( 183 | no_desc, 184 | "should begin with an unnamed argument, providing a description of the ", 185 | "option's behavior." 186 | ), 187 | issue( 188 | !no_arg & no_named_arg, 189 | "should always follow the description with a named argument to indicate ", 190 | "the option name and default value." 191 | ), 192 | issue( 193 | arg_desc, 194 | "should not provide a redundant `desc` argument." 195 | ), 196 | issue( 197 | arg_name, 198 | "should not provide a redundant `name` argument." 199 | ) 200 | )) 201 | 202 | err("found issues in option definitions", issues, which = -1) 203 | } 204 | 205 | 206 | 207 | #' Reflow multiline strings 208 | #' 209 | #' A small helper function for allowing multiline strings to be collapsed into 210 | #' continuous lines, similar to markdown's paragraph handling. 211 | #' 212 | #' @param x A vector of multiline strings to reflow 213 | #' @return The reflowed strings 214 | #' 215 | #' @keywords internal 216 | reflow_option_desc <- function(x) { 217 | x <- strsplit(x, "\n{2,}\\s*") 218 | x <- lapply(x, paste, collapse = "\n") 219 | x <- lapply(x, gsub, pattern = "^\\s+|\\s+$", replacement = "") 220 | x 221 | } 222 | -------------------------------------------------------------------------------- /R/envvars.R: -------------------------------------------------------------------------------- 1 | fn_with_desc <- function(f, desc) { 2 | attr(f, "desc") <- desc 3 | f 4 | } 5 | 6 | envvar_fn_emit_warning <- function(name) { 7 | warning(sprintf( 8 | paste0( 9 | "Environment variable '%s' produced an error while interpretting its ", 10 | "value. Environment variable will be ignored." 11 | ), 12 | name 13 | )) 14 | } 15 | 16 | 17 | 18 | #' Generator functions for environment variable processors 19 | #' 20 | #' These functions return environment variable processor functions. Arguments to 21 | #' them are used to specify behaviors. 22 | #' 23 | #' @param value A value to test against 24 | #' @param values A list or vector of values to match 25 | #' @param default A default value used when a value cannot be coerced from the 26 | #' environment variable value 27 | #' @param case_sensitive A logical value indicating whether string comparisons 28 | #' should be case-sensitive. 29 | #' @param delim A character value to use as a delimiter to use when splitting 30 | #' the environment variable value 31 | #' @param ... Other arguments unused 32 | #' @return A function to be used for processing an environment variable value 33 | #' 34 | #' @name envvar_fns 35 | #' @rdname envvar_fns 36 | #' 37 | #' @keywords envvar_parsers 38 | #' 39 | NULL 40 | 41 | #' @describeIn envvar_fns 42 | #' Test for equality with handlers for most atomic R types, performing sensible 43 | #' interpretation of environment variable values. 44 | #' @export 45 | envvar_is <- function(value, ...) { 46 | UseMethod("envvar_is", value) 47 | } 48 | 49 | #' @export 50 | envvar_is.default <- function(value, ...) { 51 | fn_with_desc( 52 | function(raw, ...) { 53 | tryCatch( 54 | identical(value, eval(parse(text = raw))), 55 | error = function(e) FALSE 56 | ) 57 | }, 58 | sprintf("TRUE if evaluated value is identical to '%s'", format(value)) 59 | ) 60 | } 61 | 62 | #' @describeIn envvar_fns 63 | #' environment variable has value `"null"` 64 | #' @export 65 | envvar_is.NULL <- function(value, case_sensitive = FALSE, ...) { 66 | fn_with_desc( 67 | function(raw, ...) { 68 | tryCatch( 69 | is.null(eval(parse(text = toupper(raw)))), 70 | error = function(e) FALSE 71 | ) 72 | }, 73 | sprintf( 74 | "TRUE if value is 'null'%s, FALSE otherwise", 75 | if (case_sensitive) "" else " (case insensitive)" 76 | ) 77 | ) 78 | } 79 | 80 | #' @describeIn envvar_fns 81 | #' environment variable is equal to string `value` 82 | #' @export 83 | envvar_is.character <- function(value, case_sensitive = FALSE, ...) { 84 | stopifnot(length(value) == 1) 85 | fn_with_desc( 86 | function(raw, ...) { 87 | if (case_sensitive) isTRUE(value == raw) 88 | else isTRUE(toupper(value) == toupper(raw)) 89 | }, 90 | sprintf( 91 | "TRUE if equal to '%s'%s", 92 | value, 93 | if (case_sensitive) "" else " (case insensitive)" 94 | ) 95 | ) 96 | } 97 | 98 | #' @describeIn envvar_fns 99 | #' environment variable is equal to string representation of numeric `value` 100 | #' @export 101 | envvar_is.numeric <- function(value, ...) { 102 | stopifnot(length(value) == 1) 103 | fn_with_desc( 104 | function(raw, ...) { 105 | tryCatch( 106 | value == as.numeric(raw), 107 | warning = function(w) FALSE 108 | ) 109 | }, 110 | paste0("TRUE if equal to ", value) 111 | ) 112 | } 113 | 114 | #' @describeIn envvar_fns 115 | #' environment variable is equal to string representation of logical `value` 116 | #' @export 117 | envvar_is.logical <- function(value, case_sensitive = FALSE, ...) { 118 | stopifnot(length(value) == 1) 119 | fn_with_desc( 120 | function(raw, ...) { 121 | if (!case_sensitive) { 122 | raw <- toupper(raw) 123 | } 124 | 125 | tryCatch( 126 | identical(value, as.logical(raw)), 127 | error = function(e) FALSE 128 | ) 129 | }, 130 | sprintf( 131 | "TRUE if value is '%s'%s, FALSE otherwise", 132 | format(value), 133 | if (case_sensitive) "" else " (case insensitive)" 134 | ) 135 | ) 136 | } 137 | 138 | #' @describeIn envvar_fns 139 | #' Parse the environment variable value as R code and and evaluate it to 140 | #' produce a return value, emitting an error if the expression fails to parse 141 | #' or evaluate. This option is a sensible default for most R-specific 142 | #' environment variables, but may fail for string literals, and meaningful 143 | #' values that don't conform to R's syntax like `"true`" (see 144 | #' [envvar_is_true()]), `"false"` (see [envvar_is_false()]) or `"null"`. 145 | #' @export 146 | envvar_eval <- function(...) { 147 | fn_with_desc( 148 | function(raw, name, ...) { 149 | parse_error_fmt <- paste0( 150 | "Environment variable '%s' could not be parsed into a valid R ", 151 | "expression" 152 | ) 153 | 154 | tryCatch( 155 | eval(parse(text = raw)), 156 | error = function(e) stop(sprintf(parse_error_fmt, name)) 157 | ) 158 | }, 159 | "as evaluated expression" 160 | ) 161 | } 162 | 163 | #' @describeIn envvar_fns 164 | #' Parse the environment variable value as R code and and evaluate it to 165 | #' produce a return value, or falling back to the raw value as a string if an 166 | #' error occurs. 167 | #' @export 168 | envvar_eval_or_raw <- function(...) { 169 | fn_with_desc( 170 | function(raw, name, ...) { 171 | tryCatch(eval(parse(text = raw)), error = function(e) raw) 172 | }, 173 | "evaluated if possible, raw string otherwise" 174 | ) 175 | } 176 | 177 | #' @describeIn envvar_fns 178 | #' For meaningful string comparisons, check whether the environment variable is 179 | #' equal to some meaningful string. Optionally with case-sensitivity. 180 | #' @export 181 | envvar_is_one_of <- function(values, ...) { 182 | msg <- sprintf( 183 | "TRUE if %s, FALSE otherwise", 184 | if (length(values) == 1) { 185 | paste0("'", values[[1]], "'") 186 | } else { 187 | paste0("one of ", paste0("'", as.character(values), "'", collapse = ", ")) 188 | } 189 | ) 190 | 191 | fn <- function(v) do.call(envvar_is, list(v, ...)) 192 | 193 | fn_with_desc( 194 | function(raw, ...) { 195 | for (v in values) { 196 | if (isTRUE(fn(v)(raw, ...))) return(TRUE) 197 | } 198 | FALSE 199 | }, 200 | msg 201 | ) 202 | } 203 | 204 | #' @describeIn envvar_fns 205 | #' Check whether environment variable can be coerced to match one of `values`, 206 | #' returning the value if it matches or `default` otherwise. 207 | #' @export 208 | envvar_choice_of <- function(values, default = NULL, ...) { 209 | msg <- sprintf( 210 | "%s as value, NULL otherwise", 211 | if (length(values) == 1) { 212 | paste0("'", values[[1]], "'") 213 | } else { 214 | paste0("one of ", paste0("'", as.character(values), "'", collapse = ", ")) 215 | } 216 | ) 217 | 218 | fn <- function(v) do.call(envvar_is, list(v, ...)) 219 | 220 | fn_with_desc( 221 | function(raw, ...) { 222 | for (value in values) if (fn(value)(raw, ...)) return(value) 223 | default 224 | }, 225 | msg 226 | ) 227 | } 228 | 229 | #' @describeIn envvar_fns 230 | #' Test whether the environment variable is "truthy", that is whether it is 231 | #' case-insensitive `"true"` or `1` 232 | #' @export 233 | envvar_is_true <- function(...) { 234 | envvar_is_one_of(list(TRUE, 1), ...) 235 | } 236 | 237 | #' @describeIn envvar_fns 238 | #' Test whether the environment variable is "falsy", that is whether it is 239 | #' case-insensitive `"false"` or `0` 240 | #' @export 241 | envvar_is_false <- function(...) { 242 | envvar_is_one_of(list(FALSE, 0), ...) 243 | } 244 | 245 | #' @describeIn envvar_fns 246 | #' Test whether the environment variable is set. This is somewhat 247 | #' operating-system dependent, as not all operating systems can distinguish 248 | #' between an empty string as a value and an unset environment variable. For 249 | #' details see [Sys.getenv()]'s Details about its `unset` parameter. 250 | #' @export 251 | envvar_is_set <- function(...) { 252 | fn_with_desc( 253 | function(raw, ...) TRUE, 254 | "TRUE if set, FALSE otherwise" 255 | ) 256 | } 257 | 258 | #' @describeIn envvar_fns 259 | #' Interpret the environment variable as a delimited list of strings, such as 260 | #' `PATH` variables. 261 | #' @export 262 | envvar_str_split <- function(delim = ";", ...) { 263 | fn_with_desc( 264 | function(raw, ...) trimws(strsplit(raw, delim)[[1L]]), 265 | sprintf("as character vector, split on '%s' delimiter", delim) 266 | ) 267 | } 268 | -------------------------------------------------------------------------------- /R/errors.R: -------------------------------------------------------------------------------- 1 | #' Raise a package error 2 | #' 3 | #' @param title A title for the error 4 | #' @param issues An optionally named list of issues to associate with the error. 5 | #' When named, the issues are first sorted by issue name. 6 | #' @param which A relative frame to use to build the associated call 7 | #' @return An options_error class 8 | #' 9 | #' @keywords internal 10 | err <- function(title, issues = list(), which = 0) { 11 | which <- max(which, -sys.nframe() + 2) 12 | call <- sys.call(which = which - 1) 13 | w <- getOption("width", 80) 14 | 15 | # order issues by relevant option definition 16 | issues <- Filter(Negate(is.null), issues) 17 | if (is.null(names(issues))) names(issues) <- rep("", length(issues)) 18 | if (length(issues)) issues <- issues[order(names(issues))] 19 | 20 | # apply indentation and wrap lines 21 | issues <- lapply(issues, function(msg) { 22 | msg <- strwrap(width = w - 5, indent = 4, exdent = 6, paste0("* ", msg)) 23 | paste0(msg, collapse = "\n") 24 | }) 25 | 26 | title <- paste0(" ", title) 27 | stop(structure( 28 | list(message = paste0(c("", title, issues), collapse = "\n"), call = call), 29 | class = c("options_error", "error", "condition") 30 | )) 31 | } 32 | 33 | 34 | 35 | #' @export 36 | conditionCall.options_error <- function(c) { 37 | fn <- call("::", quote(options), c$call[[1]]) 38 | call <- as.call(list(fn)) 39 | attributes(call) <- attributes(c$call) 40 | call 41 | } 42 | -------------------------------------------------------------------------------- /R/naming.R: -------------------------------------------------------------------------------- 1 | #' Define Naming Conventions 2 | #' 3 | #' Option naming conventions use sensible defaults so that you can get started 4 | #' quickly with minimal configuration. 5 | #' 6 | #' @section Defaults: 7 | #' 8 | #' Given a package `mypackage` and option `myoption`, the default settings 9 | #' will generate options and environment variables using the convention: 10 | #' 11 | #' option: 12 | #' 13 | #' ``` 14 | #' mypackage.myoption 15 | #' ``` 16 | #' 17 | #' environment variable: 18 | #' 19 | #' ``` 20 | #' R_MYPACKAGE_MYOPTION 21 | #' ``` 22 | #' 23 | #' This convention is intended to track closely with how options and environment 24 | #' variables are handled frequently in the wild. Perhaps in contrast to the 25 | #' community conventions, an `R_` prefix is tacked on to the default environment 26 | #' variables. This prefix helps to differentiate environment variables when 27 | #' similarly named tools exist outside of the R ecosystem. 28 | #' 29 | #' @section Setting Alternative Conventions: 30 | #' 31 | #' If you choose to use alternative naming conventions, you must set the 32 | #' callback function _before_ defining options. This is best achieved by 33 | #' altering these settings in the file where you define your options. 34 | #' 35 | #' If you choose to break up your options across multiple files, then it is best 36 | #' to define the collate order for your R scripts to ensure that the options are 37 | #' consistently configured across operating systems. 38 | #' 39 | #' @param fn A callback function which expects two arguments, the package name 40 | #' and option name, and returns a single character value to use as an 41 | #' environment variable name. 42 | #' @param env An environment in which to search for options settings 43 | #' @return The callback function `fn` 44 | #' 45 | #' @examples 46 | #' set_envvar_name_fn(envvar_name_generic) 47 | #' 48 | #' set_envvar_name_fn(function(package, name) { 49 | #' toupper(paste("ENV", package, name, sep = "_")) 50 | #' }) 51 | #' 52 | #' @seealso naming_formats 53 | #' @name naming 54 | #' 55 | #' @keywords naming 56 | NULL 57 | 58 | 59 | 60 | #' Assert signature for naming functions 61 | #' 62 | #' @param fn A function to inspect 63 | #' @return NULL 64 | #' 65 | #' @keywords internal 66 | assert_naming_fn_signature <- function(fn) { 67 | if (length(formals(fn)) < 2) 68 | err("naming functions must accept at least two arguments", which = -1) 69 | } 70 | 71 | 72 | 73 | #' @describeIn naming 74 | #' Set a callback function to use to format environment variable names. 75 | #' @export 76 | set_envvar_name_fn <- function(fn, env = parent.frame()) { 77 | assert_naming_fn_signature(fn) 78 | optenv <- get_options_env(env) 79 | attr(optenv, "envvar_name_fn") <- fn 80 | } 81 | 82 | #' @describeIn naming 83 | #' Set a callback function to use to format option names. 84 | #' @export 85 | set_option_name_fn <- function(fn, env = parent.frame()) { 86 | assert_naming_fn_signature(fn) 87 | optenv <- get_options_env(env) 88 | attr(optenv, "option_name_fn") <- fn 89 | } 90 | 91 | 92 | 93 | get_option_name_fn <- function(env = parent.frame()) { 94 | optenv <- get_options_env(env, inherits = TRUE) 95 | attr(optenv, "option_name_fn") 96 | } 97 | 98 | get_envvar_name_fn <- function(env = parent.frame()) { 99 | optenv <- get_options_env(env, inherits = TRUE) 100 | attr(optenv, "envvar_name_fn") 101 | } 102 | 103 | 104 | 105 | #' Naming Convention Formatters 106 | #' 107 | #' This family of functions is used internally to generate global option and 108 | #' environment variable names from the package name and internal option name. 109 | #' 110 | #' @param package,option The package name and internal option name used for 111 | #' generating a global R option and environment variable name. As these 112 | #' functions are often provided as values, their arguments rarely need to be 113 | #' provided by package authors directly. 114 | #' @return A character value to use as the global option name or environment 115 | #' variable name 116 | #' 117 | #' @name naming_formats 118 | #' @seealso naming 119 | #' 120 | #' @keywords naming_formats 121 | #' 122 | NULL 123 | 124 | #' @usage option_name_default(package, option) # "package.option" 125 | #' @describeIn naming_formats 126 | #' A default naming convention, producing a global R option name from the 127 | #' package name and internal option name (`mypackage.myoption`) 128 | #' @family naming_formats 129 | #' @export 130 | option_name_default <- function(package, option) { 131 | paste(c(package, option), collapse = ".") 132 | } 133 | 134 | #' @usage envvar_name_default(package, option) # "R_PACKAGE_OPTION" 135 | #' @describeIn naming_formats 136 | #' A default naming convention, producing an environment variable name from the 137 | #' package name and internal option name (`R_MYPACKAGE_MYOPTION`) 138 | #' @family naming_formats 139 | #' @export 140 | envvar_name_default <- function(package, option) { 141 | parts <- c("R", package, option) 142 | paste(gsub("[^A-Z0-9]", "_", toupper(parts)), collapse = "_") 143 | } 144 | 145 | #' @usage envvar_name_generic(package, option) # "PACKAGE_OPTION" 146 | #' @describeIn naming_formats 147 | #' A generic naming convention, producing an environment variable name from the 148 | #' package name and internal option name. Useful when a generic convention might 149 | #' be used to share environment variables with other tools of the same name, or 150 | #' when you're confident that your R package will not conflict with other tools. 151 | #' (`MYPACKAGE_MYOPTION`) 152 | #' @family naming_formats 153 | #' @export 154 | envvar_name_generic <- function(package, option) { 155 | parts <- c(package, option) 156 | paste(gsub("[^A-Z0-9]", "_", toupper(parts)), collapse = "_") 157 | } 158 | 159 | 160 | as_check_names_fn <- function(x) { 161 | UseMethod("as_check_names_fn") 162 | } 163 | 164 | #' @export 165 | as_check_names_fn.character <- function(x) { 166 | switch( 167 | x[[1]], 168 | "warn" = check_names_warn_missing, 169 | "error" = check_names_stop_missing, 170 | "asis" = identity 171 | ) 172 | } 173 | 174 | #' @export 175 | as_check_names_fn.function <- function(x) { 176 | x 177 | } 178 | 179 | check_names_warn_missing <- function(optnames, env = parent.frame()) { 180 | valid <- names(get_options_spec(env)) 181 | if (length(miss <- setdiff(optnames, valid)) > 0) { 182 | warning( 183 | "Option name(s) not found in environment: ", 184 | paste0("'", miss, "'", collapse = ", ") 185 | ) 186 | } 187 | } 188 | 189 | check_names_stop_missing <- function(optnames, env = parent.frame()) { 190 | valid <- names(get_options_spec(env)) 191 | if (length(miss <- setdiff(optnames, valid)) > 0) { 192 | stop( 193 | "Option name(s) not found in environment: ", 194 | paste0("'", miss, "'", collapse = ", ") 195 | ) 196 | } 197 | } 198 | 199 | check_names_asis <- function(optnames, env = parent.frame()) { 200 | } 201 | -------------------------------------------------------------------------------- /R/options_env.R: -------------------------------------------------------------------------------- 1 | #' Options Environment Class 2 | #' 3 | #' The options environment stores primarily, the default values for options. In 4 | #' addition, it stores metadata pertaining to each option in the form of 5 | #' attributes. 6 | #' 7 | #' @section Attributes: 8 | #' - `spec`: A named list of option specifications 9 | #' - `option_name_fn`: A function used to derive default option names for 10 | #' newly defined options. See [set_option_name_fn()]. 11 | #' - `envvar_name_fn`: A function used to derive default environment variable 12 | #' names for newly defined options. See [set_envvar_name_fn()]. 13 | #' 14 | #' @param env An environment in which to search for an options environment 15 | #' @param inherits Whether to search upward through parent environments 16 | #' @param ... Additional arguments unused 17 | #' 18 | #' @name options_env 19 | #' @rdname options_env 20 | #' @family options_env 21 | #' 22 | #' @keywords internal 23 | NULL 24 | 25 | #' Retrieve options environment (experimental) 26 | #' 27 | #' The options environment stores metadata regarding the various options 28 | #' defined in the local scope - often the top environment of a package 29 | #' namespace. 30 | #' 31 | #' @note This function's public interface is still under consideration. It is 32 | #' surfaced to provide access to option names, though the exact mechanism 33 | #' of retrieving these names should be considered experimental. 34 | #' 35 | #' @inheritParams options_env 36 | #' @return An environment containing option specifications and default values, 37 | #' or `ifnotfound` if no environment is found. 38 | #' 39 | #' @export 40 | get_options_env <- function(env, ...) { 41 | UseMethod("get_options_env") 42 | } 43 | 44 | #' @name get_options_env 45 | #' @export 46 | get_options_env.options_env <- function(env, ...) { 47 | env 48 | } 49 | 50 | #' @name get_options_env 51 | #' @export 52 | get_options_env.options_list <- function(env, ...) { 53 | attr(env, "environment") 54 | } 55 | 56 | #' @name get_options_env 57 | #' @param ifnotfound A result to return of no options environment is found. 58 | #' @export 59 | get_options_env.default <- function( 60 | env = parent.frame(), 61 | ..., 62 | inherits = FALSE, 63 | ifnotfound = emptyenv()) { 64 | if (!missing(env) && !options_initialized(env, inherits = inherits)) { 65 | init_options_env(env = env) 66 | } 67 | 68 | opt <- get0(CONST_OPTIONS_ENV_NAME, envir = env, inherits = inherits) 69 | if (!inherits(opt, "options_env")) { 70 | if (missing(env)) { 71 | return(ifnotfound) 72 | } 73 | stop("options object not found in this environment.") 74 | } 75 | 76 | opt 77 | } 78 | 79 | #' @describeIn options_env 80 | #' Test whether options is initialized in environment 81 | options_initialized <- function(env, inherits = FALSE) { 82 | exists(CONST_OPTIONS_ENV_NAME, envir = env, inherits = inherits) 83 | } 84 | 85 | #' @describeIn options_env 86 | #' Initialize an options object 87 | init_options_env <- function(env = parent.frame()) { 88 | optenv <- structure( 89 | new.env(parent = env), 90 | spec = list(), 91 | option_name_fn = option_name_default, 92 | envvar_name_fn = envvar_name_default, 93 | class = c("options_env", "environment") 94 | ) 95 | 96 | assign(CONST_OPTIONS_ENV_NAME, optenv, envir = env) 97 | } 98 | 99 | #' @describeIn options_env 100 | #' Convert into an options list 101 | as_options_list <- function(x, ...) { 102 | UseMethod("as_options_list") 103 | } 104 | 105 | #' @name options_env 106 | as_options_list.list <- function(x, ...) { 107 | structure(x, class = c("options_list", "list")) 108 | } 109 | 110 | #' @name options_env 111 | as_options_list.options_env <- function(x, ...) { 112 | res <- structure(as.list(x), class = c("options_list", "list")) 113 | 114 | for (attr_name in names(attributes(x))) { 115 | if (attr_name %in% names(attributes(res))) next 116 | attr(res, attr_name) <- attr(x, attr_name) 117 | } 118 | 119 | attr(res, "environment") <- x 120 | res 121 | } 122 | 123 | #' @describeIn options_env 124 | #' Get the option's default value 125 | get_option_default_value <- function(x, env = parent.frame()) { 126 | optenv <- get_options_env(env) 127 | 128 | # initialize value by evaluating expression at time of first access 129 | if (!exists(x, envir = optenv, inherits = FALSE)) { 130 | spec <- get_option_spec(x, optenv) 131 | optenv[[x]] <- eval(spec$expr, envir = spec$envir) 132 | } 133 | 134 | optenv[[x]] 135 | } 136 | 137 | #' @describeIn options_env 138 | #' Get all options specifications as named list 139 | get_options_spec <- function(env = parent.frame()) { 140 | optenv <- get_options_env(env) 141 | attr(optenv, "spec") 142 | } 143 | 144 | #' @describeIn options_env 145 | #' Get single option specification 146 | get_option_spec <- function( 147 | name, 148 | env = parent.frame(), 149 | inherits = FALSE, 150 | on_missing = warning) { 151 | optenv <- get_options_env(env, inherits = inherits) 152 | spec <- attr(optenv, "spec") 153 | 154 | if (!is.null(name) && name %in% names(spec)) { 155 | return(spec[[name]]) 156 | } else if (!is.null(on_missing)) { 157 | raise( 158 | on_missing, 159 | msg = paste0("option '", name, "' is not defined in environment") 160 | ) 161 | } 162 | 163 | NULL 164 | } 165 | 166 | #' @describeIn options_env 167 | #' Set single option specification 168 | set_option_spec <- function(name, details, env = parent.frame()) { 169 | optenv <- get_options_env(env) 170 | attr(optenv, "spec")[[name]] <- details 171 | } 172 | 173 | 174 | 175 | #' Format an options environment 176 | #' 177 | #' @param x An option environment ("option_env") class object 178 | #' @param ... Additional arguments unused 179 | #' @param fmt A list of formats to use for formatting individual text elements 180 | #' 181 | #' @return A formatted character value 182 | #' 183 | #' @keywords internal 184 | #' @exportS3Method format options_env 185 | format.options_env <- function(x, ..., fmt = options_fmts()) { 186 | spec <- get_options_spec(x) 187 | values <- as.list(x) 188 | 189 | formatted_spec <- character(length(spec)) 190 | for (i in seq_along(spec)) { 191 | n <- names(spec)[[i]] 192 | formatted_spec[[i]] <- format(spec[[n]], values[[n]], fmt = fmt) 193 | } 194 | 195 | paste0(formatted_spec, collapse = "\n\n") 196 | } 197 | 198 | #' Format an options list 199 | #' 200 | #' @param x An option list ("option_list") class object 201 | #' @inheritParams format.options_env 202 | #' 203 | #' @return A formatted character value 204 | #' 205 | #' @keywords internal 206 | #' @exportS3Method format options_env 207 | format.options_list <- format.options_env 208 | 209 | #' @exportS3Method print options_env 210 | print.options_env <- function(x, ...) { 211 | cat("\n", format(x, ...), "\n\n", sep = "") 212 | } 213 | 214 | #' @exportS3Method print options_list 215 | print.options_list <- print.options_env 216 | 217 | #' @exportS3Method as.list options_env 218 | as.list.options_env <- function(x, ...) { 219 | values <- list() 220 | for (n in names(x)) { 221 | values[[n]] <- if (do.call(missing, list(n), envir = x)) { 222 | bquote() 223 | } else { 224 | x[[n]] 225 | } 226 | } 227 | values 228 | } 229 | -------------------------------------------------------------------------------- /R/options_get.R: -------------------------------------------------------------------------------- 1 | #' Inspecting Option Values 2 | #' 3 | #' @param x,xs An option name, vector of option names, or a named list of new 4 | #' option values 5 | #' @param value A new value for the associated global option 6 | #' @param default A default value if the option is not set 7 | #' @param env An environment, namespace or package name to pull options from 8 | #' @param ... See specific functions to see behavior. 9 | #' @param opts A `list` of values, for use in functions that accept `...` 10 | #' arguments. In rare cases where your argument names conflict with other 11 | #' named arguments to these functions, you can specify them directly using 12 | #' this parameter. 13 | #' @param check_names (experimental) A behavior used when checking option 14 | #' names against specified options. Expects one of `"asis"`, `"warn"` or 15 | #' `"stop"`. 16 | #' 17 | #' @param add,after,scope Passed to [on.exit], with alternative defaults. 18 | #' `scope` is passed to the [on.exit] `envir` parameter to disambiguate it 19 | #' from `env`. 20 | #' 21 | #' @name opt 22 | NULL 23 | 24 | 25 | 26 | #' @describeIn opt 27 | #' 28 | #' Retrieve an option. Additional `...` arguments passed to an optional 29 | #' `option_fn`. See [`option_spec()`] for details. 30 | #' 31 | #' @return For `opt()` and `opts()`; the result of the option (or a list of 32 | #' results), either the value from a global option, the result of processing 33 | #' the environment variable or the default value, depending on which of the 34 | #' alternative sources are defined. 35 | #' 36 | #' @examples 37 | #' define_options("Whether execution should emit console output", quiet = FALSE) 38 | #' opt("quiet") 39 | #' 40 | #' @export 41 | opt <- function(x, default, env = parent.frame(), ...) { 42 | optenv <- get_options_env(as_env(env), inherits = TRUE) 43 | spec <- get_option_spec(x, env = optenv) 44 | 45 | source <- opt_source(spec, env = optenv) 46 | value <- switch(source, 47 | "envvar" = spec$envvar_fn(Sys.getenv(spec$envvar_name), spec$envvar_name), 48 | "option" = getOption(spec$option_name), 49 | "default" = get_option_default_value(x, optenv), 50 | if (missing(default)) { 51 | stop(sprintf("option '%s' not found.", x)) 52 | } else { 53 | default 54 | } 55 | ) 56 | 57 | spec$option_fn( 58 | value, 59 | x = x, 60 | default = default, 61 | env = env, 62 | ..., 63 | source = source 64 | ) 65 | } 66 | 67 | 68 | 69 | #' @describeIn opt 70 | #' 71 | #' Set an option's value. Additional `...` arguments passed to 72 | #' [`get_option_spec()`]. 73 | #' 74 | #' @param value A new value to update the associated global option 75 | #' 76 | #' @return For modifying functions ([opt_set] and [opt<-]: the value of the 77 | #' option prior to modification 78 | #' 79 | #' @export 80 | opt_set <- function(x, value, env = parent.frame(), ...) { 81 | spec <- get_option_spec(x, env = as_env(env), inherits = TRUE, ...) 82 | if (is.null(spec)) { 83 | return(invisible(NULL)) 84 | } 85 | 86 | args <- list(value) 87 | names(args) <- spec$option_name 88 | invisible(do.call(options, args)[[spec$option_name]]) 89 | } 90 | 91 | 92 | 93 | #' @describeIn opt 94 | #' 95 | #' An alias for [`opt_set()`] 96 | #' 97 | #' @export 98 | `opt<-` <- function(x, ..., value) { 99 | opt_set(x = x, value = value, ...) 100 | } 101 | 102 | 103 | 104 | #' @describeIn opt 105 | #' 106 | #' Determine source of option value. Primarily used for diagnosing options 107 | #' behaviors. 108 | #' 109 | #' @return For [opt_source]; the source that is used for a specific option, 110 | #' one of `"option"`, `"envvar"` or `"default"`. 111 | #' 112 | #' @examples 113 | #' define_options("Whether execution should emit console output", quiet = FALSE) 114 | #' opt_source("quiet") 115 | #' 116 | #' Sys.setenv(R_GLOBALENV_QUIET = TRUE) 117 | #' opt_source("quiet") 118 | #' 119 | #' options(globalenv.quiet = FALSE) 120 | #' opt_source("quiet") 121 | #' 122 | #' @export 123 | opt_source <- function(x, env = parent.frame()) { 124 | if (!is_option_spec(x)) { 125 | x <- get_option_spec(x, env = env) 126 | } 127 | 128 | if (is.null(x)) { 129 | return(NA_character_) 130 | } 131 | 132 | # determine whether option is set in various places 133 | opt_sources <- list( 134 | option = function(x) x$option_name %in% names(.Options), 135 | envvar = function(x) !is.na(Sys.getenv(x$envvar_name, unset = NA)), 136 | default = function(x) !(is.name(x$expr) && nchar(x$expr) == 0) 137 | ) 138 | 139 | # TODO: priority possibly configurable per-option in the future 140 | sources <- c("option", "envvar", "default") 141 | 142 | for (origin in sources) { 143 | if (opt_sources[[origin]](x)) { 144 | return(origin) 145 | } 146 | } 147 | 148 | NA_character_ 149 | } 150 | 151 | 152 | 153 | #' @describeIn opt 154 | #' 155 | #' Retrieve multiple options. When no names are provided, return a list 156 | #' containing all options from a given environment. Accepts a character 157 | #' vector of option names or a named list of new values to modify global 158 | #' option values. 159 | #' 160 | #' @examples 161 | #' define_options("Quietly", quiet = TRUE, "Verbosity", verbose = FALSE) 162 | #' 163 | #' # retrieve multiple options 164 | #' opts(c("quiet", "verbose")) 165 | #' 166 | #' # update multiple options, returns unmodified values 167 | #' opts(list(quiet = 42, verbose = TRUE)) 168 | #' 169 | #' # next time we check their values we'll see the modified values 170 | #' opts(c("quiet", "verbose")) 171 | #' 172 | #' @export 173 | opts <- function(xs = NULL, env = parent.frame()) { 174 | UseMethod("opts", xs) 175 | } 176 | 177 | #' @export 178 | opts.NULL <- function(xs, env = parent.frame()) { 179 | env <- get_options_env(as_env(env), inherits = TRUE) 180 | res <- as_options_list(list()) 181 | for (n in names(env)) { 182 | res[[n]] <- opt(n, env = env) 183 | } 184 | res 185 | } 186 | 187 | #' @export 188 | opts.list <- function(xs, env = parent.frame()) { 189 | env <- get_options_env(as_env(env), inherits = TRUE) 190 | 191 | if (list_is_all_named(xs)) { 192 | old <- as_options_list(env)[names(xs)] 193 | 194 | for (i in seq_along(xs)) { 195 | opt_set(names(xs)[[i]], xs[[i]], env) 196 | } 197 | 198 | old 199 | } else if (list_is_all_unnamed(xs)) { 200 | as_options_list(env)[as.character(xs)] 201 | } else { 202 | stop(paste0( 203 | "lists provided to `opts()` must either have no names, or names for ", 204 | "every value." 205 | )) 206 | } 207 | } 208 | 209 | #' @export 210 | opts.character <- function(xs, env = parent.frame()) { 211 | names(xs) <- xs 212 | lapply(xs, opt, env = env) 213 | } 214 | 215 | 216 | 217 | #' @describeIn opt 218 | #' 219 | #' Set an option only in the local frame. Additional `...` arguments passed to 220 | #' [`on.exit()`]. 221 | #' 222 | #' @note 223 | #' Local options are set with [on.exit], which can be prone to error if 224 | #' subsequent calls are not called with `add = TRUE` (masking existing 225 | #' [on.exit] callbacks). A more rigorous alternative might make use of 226 | #' [`withr::defer`]. 227 | #' 228 | #' old <- opt_set("option", value) 229 | #' withr::defer(opt_set("option", old)) 230 | #' 231 | #' If you'd prefer to use this style, see [`opts_list()`], which is designed 232 | #' to work nicely with \code{\link[withr]{withr}}. 233 | #' 234 | opt_set_local <- function( 235 | x, 236 | value, 237 | env = parent.frame(), 238 | ..., 239 | add = TRUE, 240 | after = FALSE, 241 | scope = parent.frame()) { 242 | old <- opt_set(x, value, env = env) 243 | opt_set_call <- as.call(list(quote(opt_set), x, value = old, env = env)) 244 | on_exit_args <- list(opt_set_call, ..., add = add, after = after) 245 | do.call(base::on.exit, on_exit_args, envir = scope) 246 | invisible(old) 247 | } 248 | 249 | 250 | #' @describeIn opt 251 | #' 252 | #' Produce a named list of namespaced option values, for use with [`options()`] 253 | #' and \code{\link[withr]{withr}}. Additional `...` arguments used to provide 254 | #' named option values. 255 | #' 256 | #' @examples 257 | #' define_options("print quietly", quiet = TRUE) 258 | #' 259 | #' print.example <- function(x, ...) if (!opt("quiet")) NextMethod() 260 | #' example <- structure("Hello, World!", class = "example") 261 | #' print(example) 262 | #' 263 | #' # using base R options to manage temporary options 264 | #' orig_opts <- options(opts_list(quiet = FALSE)) 265 | #' print(example) 266 | #' options(orig_opts) 267 | #' 268 | #' @examplesIf length(find.package("withr")) > 0L 269 | #' # using `withr` to manage temporary options 270 | #' withr::with_options(opts_list(quiet = FALSE), print(example)) 271 | #' 272 | #' @export 273 | opts_list <- function( 274 | ..., 275 | env = parent.frame(), 276 | check_names = c("asis", "warn", "error"), 277 | opts = list(...) 278 | ) { 279 | env <- get_options_env(as_env(env), inherits = TRUE) 280 | spec <- get_options_spec(env) 281 | 282 | as_check_names_fn(check_names)(names(opts)) 283 | names(opts) <- vcapply(names(opts), function(name) { 284 | if (name %in% names(spec)) { 285 | spec[[name]]$option_name 286 | } else { 287 | name 288 | } 289 | }) 290 | 291 | opts 292 | } 293 | -------------------------------------------------------------------------------- /R/options_roxygen.R: -------------------------------------------------------------------------------- 1 | #' Generate Standalone `?options` Documentation 2 | #' 3 | #' Produce a comprehensive documentation page outlining all your defined 4 | #' options' behaviors. 5 | #' 6 | #' @param title An optional, customized title (defaults to "Options") 7 | #' @param desc An optional, customized description of behaviors 8 | #' @param env An environemnt in which to find the associated options object 9 | #' @return A character vector of `roxygen2` tag segments 10 | #' 11 | #' @examples 12 | #' #' @eval options::as_roxygen_docs() 13 | #' NULL 14 | #' 15 | #' @family options_roxygen2 16 | #' @keywords roxygen2 17 | #' @importFrom utils packageName 18 | #' @export 19 | as_roxygen_docs <- function( 20 | title = paste(pkgname(env), "Options"), 21 | desc = default_options_rd_desc(), 22 | env = parent.frame()) { 23 | 24 | pkg <- pkgname(env) 25 | optenv <- get_options_env(env, inherits = TRUE) 26 | details <- get_options_spec(optenv) 27 | 28 | c( 29 | sprintf("@title %s", title), 30 | sprintf("@description %s", desc), 31 | "@rdname options", 32 | "@name options", 33 | "@section Checking Option Values:", 34 | "Option values specific to `", pkg, "` can be ", 35 | "accessed by passing the package name to `env`.", 36 | "", 37 | sprintf(" options::opts(env = \"%s\")", pkg), 38 | "", 39 | sprintf(" options::opt(x, default, env = \"%s\")", pkg), 40 | "", 41 | 42 | "@seealso options getOption Sys.setenv Sys.getenv", 43 | "@section Options:", 44 | "\\describe{", 45 | vapply(setdiff(names(details), CONST_OPTIONS_META), function(n) { 46 | sprintf( 47 | "\\item{%s}{\\describe{%s}}\n", n, 48 | paste0( 49 | sep = "\n", 50 | details[[n]]$desc, 51 | sprintf( 52 | "\\item{default: }{\\preformatted{%s}}\n", 53 | paste0( 54 | collapse = "\n", 55 | deparse(eval(bquote(substitute(.(as.symbol(n)), optenv)))) 56 | ) 57 | ), 58 | sprintf("\\item{option: }{%s}\n", details[[n]]$option_name), 59 | sprintf( 60 | "\\item{envvar: }{%s (%s)}\n", 61 | details[[n]]$envvar_name, 62 | attr(details[[n]]$envvar_fn, "desc") %||% "preprocessed" 63 | ) 64 | ) 65 | ) 66 | }, character(1L)), 67 | "}" 68 | ) 69 | } 70 | 71 | 72 | 73 | #' Produce `@param` roxygen sections for options 74 | #' 75 | #' Generate parameter documentation based on option behaviors. Especially useful 76 | #' for ubiquitous function parameters that default to option values. 77 | #' 78 | #' @param ... Character values of options to use. If named arguments are 79 | #' provided, the option description provided as the value is mapped to a 80 | #' parameter of the argument's name. 81 | #' @return A character vector of `roxygen2` `@param` tags 82 | #' 83 | #' @examples 84 | #' options::define_options( 85 | #' "whether messages should be written softly, or in all-caps", 86 | #' quiet = TRUE 87 | #' ) 88 | #' 89 | #' #' Hello, World 90 | #' #' 91 | #' #' @eval options::as_params("softly" = "quiet") 92 | #' #' 93 | #' hello <- function(who, softly = opt("quiet")) { 94 | #' say_what <- paste0("Hello, ", who, "!") 95 | #' if (quiet) say_what else toupper(say_what) 96 | #' } 97 | #' 98 | #' @family options_roxygen2 99 | #' @keywords roxygen2 100 | #' @export 101 | as_params <- function(...) { 102 | env <- parent.frame() 103 | opts <- list(...) 104 | optenv <- get_options_env(env, inherits = TRUE) 105 | details <- get_options_spec(optenv) 106 | 107 | missing_opt_names <- setdiff(opts, names(optenv)) 108 | if (length(missing_opt_names) > 0) { 109 | stop(sprintf( 110 | "options %s not found.", 111 | paste0("'", missing_opt_names, "'", collapse = ", ") 112 | )) 113 | } 114 | 115 | if (length(opts) == 0) { 116 | opts <- setdiff(names(optenv), CONST_OPTIONS_META) 117 | } 118 | 119 | if (is.null(names(opts))) { 120 | names(opts) <- opts 121 | } 122 | 123 | unnamed <- names(opts) == "" 124 | names(opts[unnamed]) <- opts[unnamed] 125 | 126 | format_param <- function(n) { 127 | optname <- opts[[n]] 128 | optdetails <- details[[optname]] 129 | 130 | default <- paste0(deparse(optdetails$expr), collapse = "; ") 131 | 132 | sprintf( 133 | paste0( 134 | "@param %s %s (Defaults to `%s`, overwritable using option '%s' or ", 135 | "environment variable '%s')" 136 | ), 137 | n, 138 | optdetails$desc %||% "From package option", 139 | default, 140 | optdetails$option_name, 141 | optdetails$envvar_name 142 | ) 143 | } 144 | 145 | vapply(names(opts), format_param, character(1L)) 146 | } 147 | 148 | 149 | 150 | default_options_rd_desc <- function() { 151 | paste0( 152 | "Internally used, package-specific options. All options will prioritize ", 153 | "R options() values, and fall back to environment variables if ", 154 | "undefined. If neither the option nor the environment variable is set, ", 155 | "a default value is used." 156 | ) 157 | } 158 | -------------------------------------------------------------------------------- /R/options_spec.R: -------------------------------------------------------------------------------- 1 | #' Specify Option 2 | #' 3 | #' An option specification outlines the various behaviors of an option. It's 4 | #' default value, related global R option, and related environment variable 5 | #' name, as well as a description. This information defines the operating 6 | #' behavior of the option. 7 | #' 8 | #' @details 9 | #' 10 | #' # Processing Functions 11 | #' 12 | #' Parameters `option_fn` and `envvar_fn` allow for customizing the way values 13 | #' are interpreted and processed before being returned by [`opt`] functions. 14 | #' 15 | #' ## `envvar_fn` 16 | #' 17 | #' When a value is retrieved from an environment variable, the string value 18 | #' contained in the environment variable is first processed by `envvar_fn`. 19 | #' 20 | #' An `envvar_fn` accepts only a single positional argument, and should have a 21 | #' signature such as: 22 | #' 23 | #' ```r 24 | #' function(value) 25 | #' ``` 26 | #' 27 | #' ## `option_fn` 28 | #' 29 | #' Regardless of how a value is produced - either retrieved from an environment 30 | #' variable, option, a stored default value or from a default provided to an 31 | #' [`opt`] accessor function - it is then further processed by `option_fn`. 32 | #' 33 | #' The first argument provided to `option_fn` will always be the retrieved 34 | #' value. The remaining parameters in the signature should be considered 35 | #' experimental. In addition to the value, the arguments provided to [`opt()`], 36 | #' as well as an additional `source` parameter from [`opt_source()`] may be 37 | #' used. 38 | #' 39 | #' **Stable** 40 | #' 41 | #' ``` 42 | #' function(value, ...) 43 | #' ``` 44 | #' 45 | #' **Experimental** 46 | #' 47 | #' ``` 48 | #' function(value, x, default, env, ..., source) 49 | #' ``` 50 | #' 51 | #' @param name A string representing the internal name for the option. This is 52 | #' the short form `` used within a namespace and relates to, for 53 | #' example, `.` global R option. 54 | #' @param default Either a quoted expression (if parameter `quote == TRUE`) or 55 | #' default value for the option. Defaults to an empty expression, indicating 56 | #' that it is unset. The default value is lazily evaluated, evaluated only 57 | #' when the option is first requested unless parameter `eager == TRUE`. 58 | #' @param desc A written description of the option's effects 59 | #' @param option_name,envvar_name A character value or function. If a character 60 | #' value is provided it is used as the corresponding global option name or 61 | #' environment variable name. If a function is provided it is provided with 62 | #' the package name and internal option name to derive the global option name. 63 | #' For example, provided with package `"mypkg"` and option `"myoption"`, the 64 | #' function might return global option name `"mypkg.myoption"` or environment 65 | #' variable name `"R_MYPKG_MYOPTION"`. Defaults to configured default 66 | #' functions which fall back to `option_name_default` and 67 | #' `envvar_name_default`, and can be configured using `set_option_name_fn` 68 | #' and `set_envvar_name_fn`. 69 | #' @param option_fn A function to use for processing an option value before 70 | #' being returned from the [opt] accessor functions. For further details see 71 | #' section "Processing Functions". 72 | #' @param envvar_fn A function to use for parsing environment variable values. 73 | #' Defaults to `envvar_eval_or_raw()`. For further details see section 74 | #' "Processing Functions". 75 | #' @param quoted A logical value indicating whether the `default` argument 76 | #' should be treated as a quoted expression or as a value. 77 | #' @param eager A logical value indicating whether the `default` argument should 78 | #' be eagerly evaluated (upon call), or lazily evaluated (upon first use). 79 | #' This distinction will only affect default values that rely on evaluation of 80 | #' an expression, which may produce a different result depending on the 81 | #' context in which it is evaluated. 82 | #' @param envir An environment in which to search for an options envir object. 83 | #' It is rarely necessary to use anything but the default. 84 | #' 85 | #' @return An `option_spec` object, which is a simple S3 class wrapping a list 86 | #' containing these arguments. 87 | #' 88 | #' @importFrom utils packageName 89 | #' @export 90 | option_spec <- function( 91 | name, 92 | default = bquote(), 93 | desc = NULL, 94 | option_name = get_option_name_fn(envir), 95 | envvar_name = get_envvar_name_fn(envir), 96 | option_fn = function(value, ...) value, 97 | envvar_fn = envvar_eval_or_raw(), 98 | quoted = FALSE, 99 | eager = FALSE, 100 | envir = parent.frame() 101 | ) { 102 | package <- pkgname(envir) 103 | 104 | if (!missing(default) && !quoted && !eager) { 105 | default <- match.call()[["default"]] 106 | } 107 | 108 | if (quoted && eager) { 109 | default <- eval(default, envir = envir) 110 | } 111 | 112 | if (is.function(option_name)) { 113 | option_name <- option_name(package, name) 114 | } 115 | 116 | if (is.function(envvar_name)) { 117 | envvar_name <- envvar_name(package, name) 118 | } 119 | 120 | structure( 121 | list( 122 | name = name, 123 | expr = default, 124 | desc = desc, 125 | option_name = option_name, 126 | envvar_name = envvar_name, 127 | option_fn = option_fn, 128 | envvar_fn = envvar_fn, 129 | envir = envir 130 | ), 131 | class = "option_spec" 132 | ) 133 | } 134 | 135 | is_option_spec <- function(x) { 136 | inherits(x, "option_spec") 137 | } 138 | 139 | are_option_spec <- function(x) { 140 | vapply(x, is_option_spec, logical(1L)) 141 | } 142 | 143 | 144 | 145 | #' @exportS3Method print option_spec 146 | print.option_spec <- function(x, ...) { 147 | cat(format(x, ...)) 148 | } 149 | 150 | 151 | 152 | #' Format an option specification 153 | #' 154 | #' @param x An option specification ("option_spec") class object 155 | #' @param value Optionally, the current value to display for the option being 156 | #' specified 157 | #' @param ... Additional arguments unused 158 | #' @param fmt A list of formats to use for formatting individual text elements 159 | #' 160 | #' @return A formatted character value 161 | #' 162 | #' @keywords internal 163 | #' @exportS3Method format option_spec 164 | format.option_spec <- function(x, value, ..., fmt = options_fmts()) { 165 | if (!is.null(x$desc)) { 166 | desc <- paste(collapse = "\n\n", lapply( 167 | strsplit(x$desc, "\n\n")[[1]], 168 | function(line) { 169 | paste(strwrap(line, exdent = 2, indent = 2), collapse = "\n") 170 | } 171 | )) 172 | } else { 173 | desc <- NULL 174 | } 175 | 176 | envvar_help <- sprintf( 177 | " (%s)", 178 | attr(x$envvar_fn, "desc") 179 | ) 180 | 181 | src <- opt_source(x) 182 | paste0( 183 | # name 184 | fmt$name(x$name), " = ", format_value(value, fmt = fmt), 185 | # description 186 | "\n\n", sprintf("%s\n\n", fmt$desc(desc)), 187 | # defaults 188 | " ", format_field("option", src == "option", fmt$optname(x$option_name), fmt), "\n", 189 | " ", format_field("envvar", src == "envvar", fmt$optname(x$envvar_name), fmt), envvar_help, "\n", 190 | " ", format_field("default", src == "default", deparse(x$expr), fmt), 191 | collapse = "" 192 | ) 193 | } 194 | 195 | 196 | 197 | #' Format a possible option source 198 | #' 199 | #' @param field The field for the option source 200 | #' @param active Whether this source is the source of the option's value 201 | #' @param value The value from this source that was used 202 | #' @inheritParams format.option_spec 203 | #' @return A formatted character value 204 | #' 205 | #' @keywords internal 206 | format_field <- function(field, active, value, fmt = options_fmts()) { 207 | active <- isTRUE(active) 208 | f <- if (active) fmt$field_active else fmt$field_inactive 209 | paste0( 210 | fmt$fade(if (active) "*" else " "), 211 | f(field), 212 | strrep(" ", 7 - nchar(field)), 213 | fmt$fade(" : "), 214 | value 215 | ) 216 | } 217 | 218 | #' Format value shorthands for command line display 219 | #' 220 | #' @param x An R object to display, attempting to show the actual value, but 221 | #' falling back to shorthands for more complex data types. 222 | #' @inheritParams format.option_spec 223 | #' @return A formatted character value 224 | #' 225 | #' @keywords internal 226 | format_value <- function(x, ..., fmt = NULL) { 227 | if (missing(x)) { 228 | return("") 229 | } 230 | UseMethod("format_value") 231 | } 232 | 233 | #' @method format_value default 234 | #' @name format_value 235 | format_value.default <- function(x, ..., fmt = options_fmts()) { 236 | if (isS4(x)) { 237 | UseMethod("format_value", structure(list(), class = "S4")) 238 | } 239 | 240 | if (!is.null(attr(x, "class"))) { 241 | UseMethod("format_value", structure(list(), class = "S3")) 242 | } 243 | 244 | str <- deparse(x) 245 | fmt$shorthand(paste0( 246 | substring(str[[1]], 1, 40), 247 | if (length(str) > 1 || nchar(str[[1]]) > 40) " ..." 248 | )) 249 | } 250 | 251 | #' @name format_value 252 | format_value.S3 <- function(x, ..., fmt = options_fmts()) { 253 | fmt$shorthand(sprintf("", paste0(class(x), collapse = ", "))) 254 | } 255 | 256 | #' @name format_value 257 | format_value.S4 <- function(x, ..., fmt = options_fmts()) { 258 | fmt$shorthand(sprintf("", paste0(class(x), collapse = ", "))) 259 | } 260 | 261 | #' @name format_value 262 | format_value.function <- function(x, ..., fmt = options_fmts()) { 263 | fmt$shorthand(paste0("fn(", paste0(names(formals(x)), collapse = ", "), ")")) 264 | } 265 | 266 | #' @importFrom utils capture.output 267 | #' @name format_value 268 | format_value.environment <- function(x, ..., fmt = options_fmts()) { 269 | fmt$shorthand(utils::capture.output(print(x))[[1]]) 270 | } 271 | 272 | #' @name format_value 273 | format_value.expression <- function(x, ..., fmt = options_fmts()) { 274 | fmt$shorthand("") 275 | } 276 | 277 | #' @name format_value 278 | format_value.quote <- function(x, ..., fmt = options_fmts()) { 279 | fmt$shorthand("") 280 | } 281 | 282 | #' @name format_value 283 | format_value.call <- function(x, ..., fmt = options_fmts()) { 284 | fmt$shorthand("") 285 | } 286 | 287 | #' @name format_value 288 | format_value.name <- function(x, ..., fmt = options_fmts()) { 289 | name <- as.character(x) 290 | if (nchar(name) == 0) { 291 | return(fmt$shorthand("")) 292 | } 293 | fmt$shorthand(paste0("`", name, "`")) 294 | } 295 | 296 | #' @name format_value 297 | format_value.symbol <- format_value.name 298 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' Grab package name, at runtime 2 | #' 3 | #' Lazily grab `packageName()` within calling environment, not within function 4 | #' environment. 5 | #' 6 | #' @param env An environment in which to search for a package name 7 | #' @return A package name or "globalenv" if not found 8 | #' 9 | #' @importFrom utils packageName 10 | #' @keywords internal 11 | pkgname <- function(env = parent.frame()) { 12 | pkg <- utils::packageName(env) 13 | if (is.null(pkg)) "globalenv" else pkg 14 | } 15 | 16 | 17 | `%||%` <- function(lhs, rhs) if (is.null(lhs)) rhs else lhs 18 | 19 | vlapply <- function(..., FUN.VALUE = logical(1L)) { # nolint object_name_linter 20 | vapply(..., FUN.VALUE = FUN.VALUE) 21 | } 22 | 23 | vcapply <- function(..., FUN.VALUE = character(1L)) { # nolint object_name_linter 24 | vapply(..., FUN.VALUE = FUN.VALUE) 25 | } 26 | 27 | #' @keywords internal 28 | as_env <- function(x) { 29 | UseMethod("as_env") 30 | } 31 | 32 | #' @keywords internal 33 | as_env.character <- function(x) { 34 | getNamespace(x) 35 | } 36 | 37 | #' @keywords internal 38 | as_env.environment <- function(x) { 39 | x 40 | } 41 | 42 | list_is_all_named <- function(x) { 43 | !(is.null(names(x)) || "" %in% names(x)) 44 | } 45 | 46 | list_is_all_unnamed <- function(x) { 47 | is.null(names(x)) || all(names(x) == "") 48 | } 49 | 50 | #' @keywords internal 51 | raise <- function(x, ...) { 52 | UseMethod("raise") 53 | } 54 | 55 | #' @keywords internal 56 | raise.character <- function(x, ...) { 57 | x <- switch(x, 58 | "print" = , 59 | "info" = , 60 | "message" = message, 61 | "warn" = , 62 | "warning" = warning, 63 | "error" = , 64 | "stop" = stop 65 | ) 66 | 67 | raise.function(x, ...) 68 | } 69 | 70 | #' @keywords internal 71 | raise.function <- function(x, msg, ...) { 72 | args <- list(msg, ...) 73 | 74 | if (!"call." %in% names(args) && "call." %in% names(formals(x))) { 75 | args[["call."]] <- FALSE 76 | } 77 | 78 | do.call(x, args) 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `options` 2 | 3 | [](https://cran.r-project.org/package=options) 4 | [](https://ci.codeberg.org/repos/13756) 5 | [](https://app.codecov.io/gh/dgkf/options) 6 | [](https://cran.r-project.org/package=options) 7 | [](https://matrix.to/#/#r-pkg-options:matrix.org) 8 | 9 | _Simple, Consistent Package Options_ 10 | 11 | If you've exposed options from a package before, you've inevitably re-written 12 | one or more pieces of trivial boilerplate code: 13 | 14 | - Prefixing option names with some sort of package namespace 15 | - Building your own option documentation 16 | - Preferentially using a default value, global options or environment variables 17 | - Parsing of environment variables into useful R data 18 | 19 | `options` aims to make these things easy, without having to copy around 20 | boilerplate code. 21 | 22 | ## Quick Start 23 | 24 | ### Defining Options 25 | 26 | Define your options using the `define_options` shorthand. Interlace descriptions 27 | and default values to define multiple options at once. 28 | 29 | ```r 30 | #' @import options 31 | options::define_options( 32 | "This is an example of how a package author would document their internally 33 | used options. This option could make the package default to executing 34 | quietly.", 35 | quiet = TRUE, 36 | 37 | "Multiple options can be defined, providing default values if a global option 38 | or environment variable isn't set.", 39 | second_example = FALSE, 40 | 41 | "Default values are lazily evaluated, so you are free to use package functions 42 | without worrying about build-time evaluation order", 43 | lazy_example = fn_not_defined_until_later() 44 | ) 45 | ``` 46 | 47 | When you want more control, you can use `define_option` to declare all aspects 48 | of how your option behaves. 49 | 50 | ```r 51 | options::define_option( 52 | option = "concrete_example", 53 | default = TRUE, 54 | desc = paste0( 55 | "Or, if you prefer a more concrete constructor you can define each option ", 56 | "explicitly." 57 | ), 58 | option_name = "mypackage_concrete", # define custom option names 59 | envvar_name = "MYPACKAGE_CONCRETE", # and custom environment variable names 60 | envvar_fn = options::envvar_is_true() # and use helpers to handle envvar parsing 61 | ) 62 | ``` 63 | 64 | ### Documentation 65 | 66 | As long as the options have been created as shown above, documenting your 67 | options is as easy as adding this small roxygen stub within your package. 68 | 69 | ```r 70 | #' @eval options::as_roxygen_docs() 71 | NULL 72 | ``` 73 | 74 | Produces `?mypackage::options` 75 | 76 | ``` 77 | mypackage Options 78 | 79 | Description: 80 | 81 | Internally used, package-specific options. All options will 82 | prioritize R options() values, and fall back to environment 83 | variables if undefined. If neither the option nor the environment 84 | variable is set, a default value is used. 85 | 86 | Options: 87 | 88 | quiet 89 | This is an example of how a package author would document their 90 | internally used options. This option could make the package default to 91 | executing quietly. 92 | 93 | default: 94 | 95 | TRUE 96 | 97 | option: mypackage.quiet 98 | 99 | envvar: R_MYPACKAGE_QUIET (raw) 100 | ... 101 | ``` 102 | 103 | When your options are used as default values to parameters, you can use the 104 | option documentation to populate your function parameter docs. 105 | 106 | This is made simple when all of your parameters share the same names as your 107 | options. 108 | 109 | ```r 110 | #' @eval options::as_params() 111 | #' @name options_params 112 | #' 113 | NULL 114 | 115 | #' Count to Three 116 | #' 117 | #' @inheritParams option_params 118 | #' 119 | count_to_three <- function(quiet = opt("quiet")) { 120 | for (i in 1:3) if (!quiet) cat(i, "\n") 121 | } 122 | ``` 123 | 124 | In situations where you have identically named parameters where you _don't_ want 125 | to inherit the option documentation, you can provide their names to `as_params` 126 | to use just a subset of options. You can also reassign documentation for an 127 | option to a parameter of a different name. 128 | 129 | ```r 130 | #' Hello World! 131 | #' 132 | #' @eval options::as_params("silent" = "quiet") 133 | #' 134 | hello <- function(who, silent = opt("quiet")) { 135 | cat(paste0("Hello, ", who, "!"), "\n") 136 | } 137 | ``` 138 | 139 | ### Customizing Behaviors 140 | 141 | When using `define_option` you can set the `option_name` and `envvar_name` that 142 | will be used directly. 143 | 144 | But it can be tedious and typo-prone to write these out for each and every 145 | option. Instead, you might consider providing a function that sets the default 146 | format for your option and environment variable names. 147 | 148 | For this, you can use `set_option_name_fn` and `set_envvar_name_fn`, which each 149 | accept a function as an argument. This function accepts two arguments, a 150 | package name and internal option name, which it uses to produce and return the 151 | corresponding global option name or environment variable name. 152 | 153 | ```r 154 | options::set_option_name_fn(function(package, name) { 155 | tolower(paste0(package, ".", name)) 156 | }) 157 | 158 | options::set_envvar_name_fn(function(package, name) { 159 | gsub("[^A-Z0-9]", "_", toupper(paste0(package, "_", name))) 160 | }) 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /inst/options.example/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: options.example 2 | Version: 0.0.0.9000 3 | Title: Showcase of options package 4 | Description: Showcase of options package. 5 | License: file LICENSE 6 | Authors@R: 7 | person( 8 | "authors of `options`", 9 | email = "authors@optio.ns", 10 | role = c("aut", "cre") 11 | ) 12 | Imports: 13 | options 14 | Encoding: UTF-8 15 | Roxygen: list(markdown = TRUE) 16 | RoxygenNote: 7.2.3 17 | -------------------------------------------------------------------------------- /inst/options.example/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 authors of `options`. All rights reserved. 2 | -------------------------------------------------------------------------------- /inst/options.example/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(fizzbuzz) 4 | export(hello) 5 | export(show_option) 6 | import(options) 7 | -------------------------------------------------------------------------------- /inst/options.example/R/helpers.R: -------------------------------------------------------------------------------- 1 | #' Show an option value as it would be used within this package 2 | #' 3 | #' Explore what value is picked up. You can try setting environment variables or 4 | #' global options to see how they affect the internally used option value. 5 | #' 6 | #' @param x An option name to explore 7 | #' @inheritDotParams options::opt 8 | #' 9 | #' @return the option value as it would be discovered inside this package 10 | #' 11 | #' @examples 12 | #' show_option("quiet") 13 | #' # [1] FALSE 14 | #' 15 | #' Sys.setenv(R_OPTIONS_EXAMPLE_QUIET = 3) 16 | #' show_option("quiet") 17 | #' # [1] "3" 18 | #' 19 | #' options(options.example.quiet = TRUE) 20 | #' show_option("quiet") 21 | #' # [1] TRUE 22 | #' 23 | #' @export 24 | show_option <- function(x, ...) { 25 | opt(x, ...) 26 | } 27 | -------------------------------------------------------------------------------- /inst/options.example/R/options.R: -------------------------------------------------------------------------------- 1 | #' @import options 2 | 3 | 4 | # 5 | # Although it is nice to be consistent with the defaults, you may find that you 6 | # prefer to format your environment variables and option names differently. 7 | # 8 | # You can assign global callbacks that will be used to derive new option names. 9 | # 10 | 11 | options::set_envvar_name_fn(function(package, name) { 12 | gsub("[^a-z0-9]", "_", tolower(paste0(c("R", package, name), collapse = "_"))) 13 | }) 14 | 15 | options::set_option_name_fn(function(package, name) { 16 | tolower(paste0(package, ".", name)) 17 | }) 18 | 19 | 20 | 21 | options::define_options( 22 | "Define options within your package. Using `define` shorthand syntax 23 | will auto-generate global R option names and environment variables names to 24 | fall back to.", 25 | use_options = TRUE, 26 | 27 | "Additional options can be chained together. They're also only evaluated 28 | on first use, so you're free to use package functions without worrying about 29 | order of execution.", 30 | another_option = fizzbuzz(1:15), 31 | 32 | "Whether printing should be quiet or loud. See how it's used throughout this 33 | package!", 34 | quiet = TRUE 35 | ) 36 | 37 | options::define_option( 38 | "detailed", 39 | default = FALSE, 40 | desc = paste0( 41 | "An example of a more detailed mechanism of defining options, which gives ", 42 | "further control over option names, environment variable names and how ", 43 | "environment variables are internalized as values." 44 | ), 45 | envvar_fn = options::envvar_is_set() 46 | ) 47 | -------------------------------------------------------------------------------- /inst/options.example/R/roxygen2.R: -------------------------------------------------------------------------------- 1 | #' @eval options::as_roxygen_docs() 2 | NULL 3 | 4 | 5 | #' Generated Package Options as Params 6 | #' 7 | #' Here we create a small stub of a function. It's used to inherit parameters 8 | #' from throughout the package. With this function as a basis, you can use 9 | #' roxygen2's `@inheritParams` tag to document any parameters that share the 10 | #' name with an option. 11 | #' 12 | #' @eval options::as_params() 13 | #' @name options_params 14 | #' 15 | NULL 16 | 17 | #' Hello, World! 18 | #' 19 | #' We use the stub function above to inherit parameter definitions. 20 | #' 21 | #' @inheritParams options_params 22 | #' 23 | #' @export 24 | hello <- function(quiet = opt("quiet")) { 25 | str <- "hello, world!" 26 | if (!quiet) str <- toupper(str) 27 | cat(str, "\n") 28 | } 29 | 30 | 31 | #' FizzBuzz 32 | #' 33 | #' You can also cherry-pick options that you want to use, or rename them to 34 | #' suite your functions. 35 | #' 36 | #' @param xs a numeric vector of values to consider 37 | #' @param m1 "fizz" dividend 38 | #' @param m2 "buzz" dividend 39 | #' @eval options::as_params("silent" = "quiet") 40 | #' 41 | #' @export 42 | fizzbuzz <- function(xs, m1 = 3, m2 = 5, silent = opt("quiet")) { 43 | out <- paste0( 44 | ifelse(xs %% m1 == 0, "fizz", ""), 45 | ifelse(xs %% m2 == 0, "buzz", "") 46 | ) 47 | 48 | if (!silent) print(out) 49 | 50 | out 51 | } 52 | -------------------------------------------------------------------------------- /inst/options.example/man/fizzbuzz.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{fizzbuzz} 4 | \alias{fizzbuzz} 5 | \title{FizzBuzz} 6 | \usage{ 7 | fizzbuzz(xs, m1 = 3, m2 = 5, silent = opt("quiet")) 8 | } 9 | \arguments{ 10 | \item{xs}{a numeric vector of values to consider} 11 | 12 | \item{m1}{"fizz" dividend} 13 | 14 | \item{m2}{"buzz" dividend} 15 | 16 | \item{silent}{Whether printing should be quiet or loud. See how it's used throughout this 17 | package! (Defaults to \code{TRUE}, overwritable using option 'options.example.quiet' or environment variable 'r_options_example_quiet')} 18 | } 19 | \description{ 20 | You can also cherry-pick options that you want to use, or rename them to 21 | suite your functions. 22 | } 23 | -------------------------------------------------------------------------------- /inst/options.example/man/hello.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{hello} 4 | \alias{hello} 5 | \title{Hello, World!} 6 | \usage{ 7 | hello(quiet = opt("quiet")) 8 | } 9 | \arguments{ 10 | \item{quiet}{Whether printing should be quiet or loud. See how it's used throughout this 11 | package! (Defaults to \code{TRUE}, overwritable using option 'options.example.quiet' or environment variable 'r_options_example_quiet')} 12 | } 13 | \description{ 14 | We use the stub function above to inherit parameter definitions. 15 | } 16 | -------------------------------------------------------------------------------- /inst/options.example/man/options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{options} 4 | \alias{options} 5 | \title{options.example Options} 6 | \description{ 7 | Internally used, package-specific options. All options will prioritize R options() values, and fall back to environment variables if undefined. If neither the option nor the environment variable is set, a default value is used. 8 | } 9 | \section{Checking Option Values}{ 10 | 11 | Option values specific to \code{options.example} can be 12 | accessed by passing the package name to \code{env}. 13 | 14 | \if{html}{\out{}}\preformatted{options::opts(env = "options.example") 15 | 16 | options::opt(x, default, env = "options.example") 17 | }\if{html}{\out{}} 18 | } 19 | 20 | \section{Options}{ 21 | 22 | \describe{ 23 | \item{detailed}{\describe{ 24 | An example of a more detailed mechanism of defining options, which gives further control over option names, environment variable names and how environment variables are internalized as values.\item{default: }{\preformatted{FALSE}} 25 | \item{option: }{options.example.detailed} 26 | \item{envvar: }{r_options_example_detailed (TRUE if set, FALSE otherwise)} 27 | }} 28 | 29 | \item{use_options}{\describe{ 30 | Define options within your package. Using \code{define} shorthand syntax 31 | will auto-generate global R option names and environment variables names to 32 | fall back to.\item{default: }{\preformatted{TRUE}} 33 | \item{option: }{options.example.use_options} 34 | \item{envvar: }{r_options_example_use_options (evaluated if possible, raw string otherwise)} 35 | }} 36 | 37 | \item{another_option}{\describe{ 38 | Additional options can be chained together. They're also only evaluated 39 | on first use, so you're free to use package functions without worrying about 40 | order of execution.\item{default: }{\preformatted{fizzbuzz(1:15)}} 41 | \item{option: }{options.example.another_option} 42 | \item{envvar: }{r_options_example_another_option (evaluated if possible, raw string otherwise)} 43 | }} 44 | 45 | \item{quiet}{\describe{ 46 | Whether printing should be quiet or loud. See how it's used throughout this 47 | package!\item{default: }{\preformatted{TRUE}} 48 | \item{option: }{options.example.quiet} 49 | \item{envvar: }{r_options_example_quiet (evaluated if possible, raw string otherwise)} 50 | }} 51 | 52 | } 53 | } 54 | 55 | \seealso{ 56 | options getOption Sys.getenv Sys.getenv 57 | } 58 | -------------------------------------------------------------------------------- /inst/options.example/man/options_params.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{options_params} 4 | \alias{options_params} 5 | \title{Generated Package Options as Params} 6 | \arguments{ 7 | \item{detailed}{An example of a more detailed mechanism of defining options, which gives further control over option names, environment variable names and how environment variables are internalized as values. (Defaults to \code{FALSE}, overwritable using option 'options.example.detailed' or environment variable 'r_options_example_detailed')} 8 | 9 | \item{use_options}{Define options within your package. Using \code{define} shorthand syntax 10 | will auto-generate global R option names and environment variables names to 11 | fall back to. (Defaults to \code{TRUE}, overwritable using option 'options.example.use_options' or environment variable 'r_options_example_use_options')} 12 | 13 | \item{another_option}{Additional options can be chained together. They're also only evaluated 14 | on first use, so you're free to use package functions without worrying about 15 | order of execution. (Defaults to \code{fizzbuzz(1:15)}, overwritable using option 'options.example.another_option' or environment variable 'r_options_example_another_option')} 16 | 17 | \item{quiet}{Whether printing should be quiet or loud. See how it's used throughout this 18 | package! (Defaults to \code{TRUE}, overwritable using option 'options.example.quiet' or environment variable 'r_options_example_quiet')} 19 | } 20 | \description{ 21 | Here we create a small stub of a function. It's used to inherit parameters 22 | from throughout the package. With this function as a basis, you can use 23 | roxygen2's \verb{@inheritParams} tag to document any parameters that share the 24 | name with an option. 25 | } 26 | -------------------------------------------------------------------------------- /inst/options.example/man/show_option.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{show_option} 4 | \alias{show_option} 5 | \title{Show an option value as it would be used within this package} 6 | \usage{ 7 | show_option(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{An option name to explore} 11 | 12 | \item{...}{ 13 | Arguments passed on to \code{\link[options:opt]{options::opt}} 14 | \describe{ 15 | \item{\code{default}}{A default value if the option is not set} 16 | \item{\code{env}}{An environment, namespace or package name to pull options from} 17 | }} 18 | } 19 | \value{ 20 | the option value as it would be discovered inside this package 21 | } 22 | \description{ 23 | Explore what value is picked up. You can try setting environment variables or 24 | global options to see how they affect the internally used option value. 25 | } 26 | \examples{ 27 | show_option("quiet") 28 | # [1] FALSE 29 | 30 | Sys.setenv(R_OPTIONS_EXAMPLE_QUIET = 3) 31 | show_option("quiet") 32 | # [1] "3" 33 | 34 | options(options.example.quiet = TRUE) 35 | show_option("quiet") 36 | # [1] TRUE 37 | 38 | } 39 | -------------------------------------------------------------------------------- /man/as_params.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_roxygen.R 3 | \name{as_params} 4 | \alias{as_params} 5 | \title{Produce \verb{@param} roxygen sections for options} 6 | \usage{ 7 | as_params(...) 8 | } 9 | \arguments{ 10 | \item{...}{Character values of options to use. If named arguments are 11 | provided, the option description provided as the value is mapped to a 12 | parameter of the argument's name.} 13 | } 14 | \value{ 15 | A character vector of \code{roxygen2} \verb{@param} tags 16 | } 17 | \description{ 18 | Generate parameter documentation based on option behaviors. Especially useful 19 | for ubiquitous function parameters that default to option values. 20 | } 21 | \examples{ 22 | options::define_options( 23 | "whether messages should be written softly, or in all-caps", 24 | quiet = TRUE 25 | ) 26 | 27 | #' Hello, World 28 | #' 29 | #' @eval options::as_params("softly" = "quiet") 30 | #' 31 | hello <- function(who, softly = opt("quiet")) { 32 | say_what <- paste0("Hello, ", who, "!") 33 | if (quiet) say_what else toupper(say_what) 34 | } 35 | 36 | } 37 | \seealso{ 38 | Other options_roxygen2: 39 | \code{\link{as_roxygen_docs}()} 40 | } 41 | \concept{options_roxygen2} 42 | \keyword{roxygen2} 43 | -------------------------------------------------------------------------------- /man/as_roxygen_docs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_roxygen.R 3 | \name{as_roxygen_docs} 4 | \alias{as_roxygen_docs} 5 | \title{Generate Standalone \code{?options} Documentation} 6 | \usage{ 7 | as_roxygen_docs( 8 | title = paste(pkgname(env), "Options"), 9 | desc = default_options_rd_desc(), 10 | env = parent.frame() 11 | ) 12 | } 13 | \arguments{ 14 | \item{title}{An optional, customized title (defaults to "Options")} 15 | 16 | \item{desc}{An optional, customized description of behaviors} 17 | 18 | \item{env}{An environemnt in which to find the associated options object} 19 | } 20 | \value{ 21 | A character vector of \code{roxygen2} tag segments 22 | } 23 | \description{ 24 | Produce a comprehensive documentation page outlining all your defined 25 | options' behaviors. 26 | } 27 | \examples{ 28 | #' @eval options::as_roxygen_docs() 29 | NULL 30 | 31 | } 32 | \seealso{ 33 | Other options_roxygen2: 34 | \code{\link{as_params}()} 35 | } 36 | \concept{options_roxygen2} 37 | \keyword{roxygen2} 38 | -------------------------------------------------------------------------------- /man/assert_naming_fn_signature.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/naming.R 3 | \name{assert_naming_fn_signature} 4 | \alias{assert_naming_fn_signature} 5 | \title{Assert signature for naming functions} 6 | \usage{ 7 | assert_naming_fn_signature(fn) 8 | } 9 | \arguments{ 10 | \item{fn}{A function to inspect} 11 | } 12 | \description{ 13 | Assert signature for naming functions 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/defining_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/define_option.R 3 | \name{defining_options} 4 | \alias{defining_options} 5 | \alias{define_option} 6 | \alias{define_options} 7 | \title{Defining Options} 8 | \usage{ 9 | define_option(option, ...) 10 | 11 | define_options(...) 12 | } 13 | \arguments{ 14 | \item{option}{An option name to use} 15 | 16 | \item{...}{Additional arguments passed to \code{\link[=option_spec]{option_spec()}}} 17 | } 18 | \value{ 19 | the package options environment 20 | } 21 | \description{ 22 | Define options which can be used throughout your package. 23 | } 24 | \details{ 25 | At their simplest, defining options lets you refer to a global option using a 26 | shorthand option name throughout your package, with the added benefit of 27 | looking for configurations in global options and environment variables. 28 | } 29 | \section{Functions}{ 30 | \itemize{ 31 | \item \code{define_option()}: Define an option. Unlike \code{\link[=define_options]{define_options()}}, this function allows detailed 32 | customization of all option behaviors. Accepts either an \code{\link[=option_spec]{option_spec()}} 33 | object, or an option named followed by arguments to provide to 34 | \code{\link[=option_spec]{option_spec()}}. 35 | 36 | \item \code{define_options()}: Define multiple options. This function provides a shorthand syntax for 37 | succinctly defining many options. Arguments are defined in groups, each 38 | starting with an unnamed description argument. For more details see Section 39 | \emph{Non-Standard Evaluation}. 40 | 41 | }} 42 | \section{Non-Standard Evaluation}{ 43 | 44 | 45 | \code{define_options()} accepts arguments in a \emph{non-standard} 46 | way, as groups of arguments which each are used to specify an option (See 47 | \code{options_spec()}). Groups of arguments must start with an unnamed argument, 48 | which provides the description for the argument, followed immediately by a 49 | named argument providing the name of option and default value, followed by 50 | any additional arguments to provie to \code{options_spec()}. 51 | 52 | The environment in which options are defined is always assumed to be the 53 | parent environment. If you'd prefer to specify options in a different 54 | environment, this is best done using \code{define_option()} or 55 | \verb{with(, define_options(...))}. 56 | 57 | Although \code{define_options()} provides all the functionality of 58 | \code{define_option()} in a succinct shorthand, it is only recommended in cases 59 | where the overwhelming majority of your options leverage default behaviors. 60 | It is encouraged to use \code{define_option()} if you repeatedly need more 61 | involved definitions to minimize non-standard evaluation bugs. 62 | } 63 | 64 | \examples{ 65 | define_options( 66 | "Whether execution should emit console output", 67 | quiet = FALSE, 68 | "Whether to use detailed console output (showcasing additional 69 | configuration parameters)", 70 | verbose = TRUE, 71 | envvar_fn = envvar_is_true() 72 | ) 73 | 74 | define_option( 75 | "deprecations", 76 | desc = "Whether deprecation warnings should be suppressed automatically", 77 | default = FALSE, 78 | option_name = "MypackageDeprecations", 79 | envvar_name = "MYPACKAGE_ENVVARS_DEPRECATIONS" 80 | ) 81 | 82 | } 83 | -------------------------------------------------------------------------------- /man/envvar_fns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/envvars.R 3 | \name{envvar_fns} 4 | \alias{envvar_fns} 5 | \alias{envvar_is} 6 | \alias{envvar_is.NULL} 7 | \alias{envvar_is.character} 8 | \alias{envvar_is.numeric} 9 | \alias{envvar_is.logical} 10 | \alias{envvar_eval} 11 | \alias{envvar_eval_or_raw} 12 | \alias{envvar_is_one_of} 13 | \alias{envvar_choice_of} 14 | \alias{envvar_is_true} 15 | \alias{envvar_is_false} 16 | \alias{envvar_is_set} 17 | \alias{envvar_str_split} 18 | \title{Generator functions for environment variable processors} 19 | \usage{ 20 | envvar_is(value, ...) 21 | 22 | \method{envvar_is}{`NULL`}(value, case_sensitive = FALSE, ...) 23 | 24 | \method{envvar_is}{character}(value, case_sensitive = FALSE, ...) 25 | 26 | \method{envvar_is}{numeric}(value, ...) 27 | 28 | \method{envvar_is}{logical}(value, case_sensitive = FALSE, ...) 29 | 30 | envvar_eval(...) 31 | 32 | envvar_eval_or_raw(...) 33 | 34 | envvar_is_one_of(values, ...) 35 | 36 | envvar_choice_of(values, default = NULL, ...) 37 | 38 | envvar_is_true(...) 39 | 40 | envvar_is_false(...) 41 | 42 | envvar_is_set(...) 43 | 44 | envvar_str_split(delim = ";", ...) 45 | } 46 | \arguments{ 47 | \item{value}{A value to test against} 48 | 49 | \item{...}{Other arguments unused} 50 | 51 | \item{case_sensitive}{A logical value indicating whether string comparisons 52 | should be case-sensitive.} 53 | 54 | \item{values}{A list or vector of values to match} 55 | 56 | \item{default}{A default value used when a value cannot be coerced from the 57 | environment variable value} 58 | 59 | \item{delim}{A character value to use as a delimiter to use when splitting 60 | the environment variable value} 61 | } 62 | \value{ 63 | A function to be used for processing an environment variable value 64 | } 65 | \description{ 66 | These functions return environment variable processor functions. Arguments to 67 | them are used to specify behaviors. 68 | } 69 | \section{Functions}{ 70 | \itemize{ 71 | \item \code{envvar_is()}: Test for equality with handlers for most atomic R types, performing sensible 72 | interpretation of environment variable values. 73 | 74 | \item \code{envvar_is(`NULL`)}: environment variable has value \code{"null"} 75 | 76 | \item \code{envvar_is(character)}: environment variable is equal to string \code{value} 77 | 78 | \item \code{envvar_is(numeric)}: environment variable is equal to string representation of numeric \code{value} 79 | 80 | \item \code{envvar_is(logical)}: environment variable is equal to string representation of logical \code{value} 81 | 82 | \item \code{envvar_eval()}: Parse the environment variable value as R code and and evaluate it to 83 | produce a return value, emitting an error if the expression fails to parse 84 | or evaluate. This option is a sensible default for most R-specific 85 | environment variables, but may fail for string literals, and meaningful 86 | values that don't conform to R's syntax like \verb{"true}" (see 87 | \code{\link[=envvar_is_true]{envvar_is_true()}}), \code{"false"} (see \code{\link[=envvar_is_false]{envvar_is_false()}}) or \code{"null"}. 88 | 89 | \item \code{envvar_eval_or_raw()}: Parse the environment variable value as R code and and evaluate it to 90 | produce a return value, or falling back to the raw value as a string if an 91 | error occurs. 92 | 93 | \item \code{envvar_is_one_of()}: For meaningful string comparisons, check whether the environment variable is 94 | equal to some meaningful string. Optionally with case-sensitivity. 95 | 96 | \item \code{envvar_choice_of()}: Check whether environment variable can be coerced to match one of \code{values}, 97 | returning the value if it matches or \code{default} otherwise. 98 | 99 | \item \code{envvar_is_true()}: Test whether the environment variable is "truthy", that is whether it is 100 | case-insensitive \code{"true"} or \code{1} 101 | 102 | \item \code{envvar_is_false()}: Test whether the environment variable is "falsy", that is whether it is 103 | case-insensitive \code{"false"} or \code{0} 104 | 105 | \item \code{envvar_is_set()}: Test whether the environment variable is set. This is somewhat 106 | operating-system dependent, as not all operating systems can distinguish 107 | between an empty string as a value and an unset environment variable. For 108 | details see \code{\link[=Sys.getenv]{Sys.getenv()}}'s Details about its \code{unset} parameter. 109 | 110 | \item \code{envvar_str_split()}: Interpret the environment variable as a delimited list of strings, such as 111 | \code{PATH} variables. 112 | 113 | }} 114 | \keyword{envvar_parsers} 115 | -------------------------------------------------------------------------------- /man/err.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/errors.R 3 | \name{err} 4 | \alias{err} 5 | \title{Raise a package error} 6 | \usage{ 7 | err(title, issues = list(), which = 0) 8 | } 9 | \arguments{ 10 | \item{title}{A title for the error} 11 | 12 | \item{issues}{An optionally named list of issues to associate with the error. 13 | When named, the issues are first sorted by issue name.} 14 | 15 | \item{which}{A relative frame to use to build the associated call} 16 | } 17 | \value{ 18 | An options_error class 19 | } 20 | \description{ 21 | Raise a package error 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/format.option_spec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{format.option_spec} 4 | \alias{format.option_spec} 5 | \title{Format an option specification} 6 | \usage{ 7 | \method{format}{option_spec}(x, value, ..., fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{x}{An option specification ("option_spec") class object} 11 | 12 | \item{value}{Optionally, the current value to display for the option being 13 | specified} 14 | 15 | \item{...}{Additional arguments unused} 16 | 17 | \item{fmt}{A list of formats to use for formatting individual text elements} 18 | } 19 | \value{ 20 | A formatted character value 21 | } 22 | \description{ 23 | Format an option specification 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/format.options_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{format.options_env} 4 | \alias{format.options_env} 5 | \title{Format an options environment} 6 | \usage{ 7 | \method{format}{options_env}(x, ..., fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{x}{An option environment ("option_env") class object} 11 | 12 | \item{...}{Additional arguments unused} 13 | 14 | \item{fmt}{A list of formats to use for formatting individual text elements} 15 | } 16 | \value{ 17 | A formatted character value 18 | } 19 | \description{ 20 | Format an options environment 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/format.options_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{format.options_list} 4 | \alias{format.options_list} 5 | \title{Format an options list} 6 | \usage{ 7 | \method{format}{options_list}(x, ..., fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{x}{An option list ("option_list") class object} 11 | 12 | \item{...}{Additional arguments unused} 13 | 14 | \item{fmt}{A list of formats to use for formatting individual text elements} 15 | } 16 | \value{ 17 | A formatted character value 18 | } 19 | \description{ 20 | Format an options list 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/format_field.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{format_field} 4 | \alias{format_field} 5 | \title{Format a possible option source} 6 | \usage{ 7 | format_field(field, active, value, fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{field}{The field for the option source} 11 | 12 | \item{active}{Whether this source is the source of the option's value} 13 | 14 | \item{value}{The value from this source that was used} 15 | 16 | \item{fmt}{A list of formats to use for formatting individual text elements} 17 | } 18 | \value{ 19 | A formatted character value 20 | } 21 | \description{ 22 | Format a possible option source 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/format_value.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{format_value} 4 | \alias{format_value} 5 | \alias{format_value.default} 6 | \alias{format_value.S3} 7 | \alias{format_value.S4} 8 | \alias{format_value.function} 9 | \alias{format_value.environment} 10 | \alias{format_value.expression} 11 | \alias{format_value.quote} 12 | \alias{format_value.call} 13 | \alias{format_value.name} 14 | \alias{format_value.symbol} 15 | \title{Format value shorthands for command line display} 16 | \usage{ 17 | format_value(x, ..., fmt = NULL) 18 | 19 | \method{format_value}{default}(x, ..., fmt = options_fmts()) 20 | 21 | \method{format_value}{S3}(x, ..., fmt = options_fmts()) 22 | 23 | \method{format_value}{S4}(x, ..., fmt = options_fmts()) 24 | 25 | \method{format_value}{`function`}(x, ..., fmt = options_fmts()) 26 | 27 | \method{format_value}{environment}(x, ..., fmt = options_fmts()) 28 | 29 | \method{format_value}{expression}(x, ..., fmt = options_fmts()) 30 | 31 | \method{format_value}{quote}(x, ..., fmt = options_fmts()) 32 | 33 | \method{format_value}{call}(x, ..., fmt = options_fmts()) 34 | 35 | \method{format_value}{name}(x, ..., fmt = options_fmts()) 36 | 37 | \method{format_value}{symbol}(x, ..., fmt = options_fmts()) 38 | } 39 | \arguments{ 40 | \item{x}{An R object to display, attempting to show the actual value, but 41 | falling back to shorthands for more complex data types.} 42 | 43 | \item{...}{Additional arguments unused} 44 | 45 | \item{fmt}{A list of formats to use for formatting individual text elements} 46 | } 47 | \value{ 48 | A formatted character value 49 | } 50 | \description{ 51 | Format value shorthands for command line display 52 | } 53 | \keyword{internal} 54 | -------------------------------------------------------------------------------- /man/get_options_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{get_options_env} 4 | \alias{get_options_env} 5 | \alias{get_options_env.options_env} 6 | \alias{get_options_env.options_list} 7 | \alias{get_options_env.default} 8 | \title{Retrieve options environment (experimental)} 9 | \usage{ 10 | get_options_env(env, ...) 11 | 12 | \method{get_options_env}{options_env}(env, ...) 13 | 14 | \method{get_options_env}{options_list}(env, ...) 15 | 16 | \method{get_options_env}{default}( 17 | env = parent.frame(), 18 | ..., 19 | inherits = FALSE, 20 | ifnotfound = emptyenv() 21 | ) 22 | } 23 | \arguments{ 24 | \item{env}{An environment in which to search for an options environment} 25 | 26 | \item{...}{Additional arguments unused} 27 | 28 | \item{inherits}{Whether to search upward through parent environments} 29 | 30 | \item{ifnotfound}{A result to return of no options environment is found.} 31 | } 32 | \value{ 33 | An environment containing option specifications and default values, 34 | or \code{ifnotfound} if no environment is found. 35 | } 36 | \description{ 37 | The options environment stores metadata regarding the various options 38 | defined in the local scope - often the top environment of a package 39 | namespace. 40 | } 41 | \note{ 42 | This function's public interface is still under consideration. It is 43 | surfaced to provide access to option names, though the exact mechanism 44 | of retrieving these names should be considered experimental. 45 | } 46 | -------------------------------------------------------------------------------- /man/naming.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/naming.R 3 | \name{naming} 4 | \alias{naming} 5 | \alias{set_envvar_name_fn} 6 | \alias{set_option_name_fn} 7 | \title{Define Naming Conventions} 8 | \usage{ 9 | set_envvar_name_fn(fn, env = parent.frame()) 10 | 11 | set_option_name_fn(fn, env = parent.frame()) 12 | } 13 | \arguments{ 14 | \item{fn}{A callback function which expects two arguments, the package name 15 | and option name, and returns a single character value to use as an 16 | environment variable name.} 17 | 18 | \item{env}{An environment in which to search for options settings} 19 | } 20 | \value{ 21 | The callback function \code{fn} 22 | } 23 | \description{ 24 | Option naming conventions use sensible defaults so that you can get started 25 | quickly with minimal configuration. 26 | } 27 | \section{Functions}{ 28 | \itemize{ 29 | \item \code{set_envvar_name_fn()}: Set a callback function to use to format environment variable names. 30 | 31 | \item \code{set_option_name_fn()}: Set a callback function to use to format option names. 32 | 33 | }} 34 | \section{Defaults}{ 35 | 36 | 37 | Given a package \code{mypackage} and option \code{myoption}, the default settings 38 | will generate options and environment variables using the convention: 39 | 40 | option: 41 | 42 | \if{html}{\out{}}\preformatted{mypackage.myoption 43 | }\if{html}{\out{}} 44 | 45 | environment variable: 46 | 47 | \if{html}{\out{}}\preformatted{R_MYPACKAGE_MYOPTION 48 | }\if{html}{\out{}} 49 | 50 | This convention is intended to track closely with how options and environment 51 | variables are handled frequently in the wild. Perhaps in contrast to the 52 | community conventions, an \code{R_} prefix is tacked on to the default environment 53 | variables. This prefix helps to differentiate environment variables when 54 | similarly named tools exist outside of the R ecosystem. 55 | } 56 | 57 | \section{Setting Alternative Conventions}{ 58 | 59 | 60 | If you choose to use alternative naming conventions, you must set the 61 | callback function \emph{before} defining options. This is best achieved by 62 | altering these settings in the file where you define your options. 63 | 64 | If you choose to break up your options across multiple files, then it is best 65 | to define the collate order for your R scripts to ensure that the options are 66 | consistently configured across operating systems. 67 | } 68 | 69 | \examples{ 70 | set_envvar_name_fn(envvar_name_generic) 71 | 72 | set_envvar_name_fn(function(package, name) { 73 | toupper(paste("ENV", package, name, sep = "_")) 74 | }) 75 | 76 | } 77 | \seealso{ 78 | naming_formats 79 | } 80 | \keyword{naming} 81 | -------------------------------------------------------------------------------- /man/naming_formats.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/naming.R 3 | \name{naming_formats} 4 | \alias{naming_formats} 5 | \alias{option_name_default} 6 | \alias{envvar_name_default} 7 | \alias{envvar_name_generic} 8 | \title{Naming Convention Formatters} 9 | \usage{ 10 | option_name_default(package, option) # "package.option" 11 | 12 | envvar_name_default(package, option) # "R_PACKAGE_OPTION" 13 | 14 | envvar_name_generic(package, option) # "PACKAGE_OPTION" 15 | } 16 | \arguments{ 17 | \item{package, option}{The package name and internal option name used for 18 | generating a global R option and environment variable name. As these 19 | functions are often provided as values, their arguments rarely need to be 20 | provided by package authors directly.} 21 | } 22 | \value{ 23 | A character value to use as the global option name or environment 24 | variable name 25 | } 26 | \description{ 27 | This family of functions is used internally to generate global option and 28 | environment variable names from the package name and internal option name. 29 | } 30 | \section{Functions}{ 31 | \itemize{ 32 | \item \code{option_name_default()}: A default naming convention, producing a global R option name from the 33 | package name and internal option name (\code{mypackage.myoption}) 34 | 35 | \item \code{envvar_name_default()}: A default naming convention, producing an environment variable name from the 36 | package name and internal option name (\code{R_MYPACKAGE_MYOPTION}) 37 | 38 | \item \code{envvar_name_generic()}: A generic naming convention, producing an environment variable name from the 39 | package name and internal option name. Useful when a generic convention might 40 | be used to share environment variables with other tools of the same name, or 41 | when you're confident that your R package will not conflict with other tools. 42 | (\code{MYPACKAGE_MYOPTION}) 43 | 44 | }} 45 | \seealso{ 46 | naming 47 | } 48 | \concept{naming_formats} 49 | \keyword{naming_formats} 50 | -------------------------------------------------------------------------------- /man/opt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_get.R 3 | \name{opt} 4 | \alias{opt} 5 | \alias{opt_set} 6 | \alias{opt<-} 7 | \alias{opt_source} 8 | \alias{opts} 9 | \alias{opt_set_local} 10 | \alias{opts_list} 11 | \title{Inspecting Option Values} 12 | \usage{ 13 | opt(x, default, env = parent.frame(), ...) 14 | 15 | opt_set(x, value, env = parent.frame(), ...) 16 | 17 | opt(x, ...) <- value 18 | 19 | opt_source(x, env = parent.frame()) 20 | 21 | opts(xs = NULL, env = parent.frame()) 22 | 23 | opt_set_local( 24 | x, 25 | value, 26 | env = parent.frame(), 27 | ..., 28 | add = TRUE, 29 | after = FALSE, 30 | scope = parent.frame() 31 | ) 32 | 33 | opts_list( 34 | ..., 35 | env = parent.frame(), 36 | check_names = c("asis", "warn", "error"), 37 | opts = list(...) 38 | ) 39 | } 40 | \arguments{ 41 | \item{x, xs}{An option name, vector of option names, or a named list of new 42 | option values} 43 | 44 | \item{default}{A default value if the option is not set} 45 | 46 | \item{env}{An environment, namespace or package name to pull options from} 47 | 48 | \item{...}{See specific functions to see behavior.} 49 | 50 | \item{value}{A new value to update the associated global option} 51 | 52 | \item{add, after, scope}{Passed to \link{on.exit}, with alternative defaults. 53 | \code{scope} is passed to the \link{on.exit} \code{envir} parameter to disambiguate it 54 | from \code{env}.} 55 | 56 | \item{check_names}{(experimental) A behavior used when checking option 57 | names against specified options. Expects one of \code{"asis"}, \code{"warn"} or 58 | \code{"stop"}.} 59 | 60 | \item{opts}{A \code{list} of values, for use in functions that accept \code{...} 61 | arguments. In rare cases where your argument names conflict with other 62 | named arguments to these functions, you can specify them directly using 63 | this parameter.} 64 | } 65 | \value{ 66 | For \code{opt()} and \code{opts()}; the result of the option (or a list of 67 | results), either the value from a global option, the result of processing 68 | the environment variable or the default value, depending on which of the 69 | alternative sources are defined. 70 | 71 | For modifying functions (\link{opt_set} and \link{opt<-}: the value of the 72 | option prior to modification 73 | 74 | For \link{opt_source}; the source that is used for a specific option, 75 | one of \code{"option"}, \code{"envvar"} or \code{"default"}. 76 | } 77 | \description{ 78 | Inspecting Option Values 79 | } 80 | \section{Functions}{ 81 | \itemize{ 82 | \item \code{opt()}: Retrieve an option. Additional \code{...} arguments passed to an optional 83 | \code{option_fn}. See \code{\link[=option_spec]{option_spec()}} for details. 84 | 85 | \item \code{opt_set()}: Set an option's value. Additional \code{...} arguments passed to 86 | \code{\link[=get_option_spec]{get_option_spec()}}. 87 | 88 | \item \code{opt(x, ...) <- value}: An alias for \code{\link[=opt_set]{opt_set()}} 89 | 90 | \item \code{opt_source()}: Determine source of option value. Primarily used for diagnosing options 91 | behaviors. 92 | 93 | \item \code{opts()}: Retrieve multiple options. When no names are provided, return a list 94 | containing all options from a given environment. Accepts a character 95 | vector of option names or a named list of new values to modify global 96 | option values. 97 | 98 | \item \code{opt_set_local()}: Set an option only in the local frame. Additional \code{...} arguments passed to 99 | \code{\link[=on.exit]{on.exit()}}. 100 | 101 | \item \code{opts_list()}: Produce a named list of namespaced option values, for use with \code{\link[=options]{options()}} 102 | and \code{\link[withr]{withr}}. Additional \code{...} arguments used to provide 103 | named option values. 104 | 105 | }} 106 | \note{ 107 | Local options are set with \link{on.exit}, which can be prone to error if 108 | subsequent calls are not called with \code{add = TRUE} (masking existing 109 | \link{on.exit} callbacks). A more rigorous alternative might make use of 110 | \code{\link[withr:defer]{withr::defer}}. 111 | 112 | \if{html}{\out{}}\preformatted{old <- opt_set("option", value) 113 | withr::defer(opt_set("option", old)) 114 | }\if{html}{\out{}} 115 | 116 | If you'd prefer to use this style, see \code{\link[=opts_list]{opts_list()}}, which is designed 117 | to work nicely with \code{\link[withr]{withr}}. 118 | } 119 | \examples{ 120 | define_options("Whether execution should emit console output", quiet = FALSE) 121 | opt("quiet") 122 | 123 | define_options("Whether execution should emit console output", quiet = FALSE) 124 | opt_source("quiet") 125 | 126 | Sys.setenv(R_GLOBALENV_QUIET = TRUE) 127 | opt_source("quiet") 128 | 129 | options(globalenv.quiet = FALSE) 130 | opt_source("quiet") 131 | 132 | define_options("Quietly", quiet = TRUE, "Verbosity", verbose = FALSE) 133 | 134 | # retrieve multiple options 135 | opts(c("quiet", "verbose")) 136 | 137 | # update multiple options, returns unmodified values 138 | opts(list(quiet = 42, verbose = TRUE)) 139 | 140 | # next time we check their values we'll see the modified values 141 | opts(c("quiet", "verbose")) 142 | 143 | define_options("print quietly", quiet = TRUE) 144 | 145 | print.example <- function(x, ...) if (!opt("quiet")) NextMethod() 146 | example <- structure("Hello, World!", class = "example") 147 | print(example) 148 | 149 | # using base R options to manage temporary options 150 | orig_opts <- options(opts_list(quiet = FALSE)) 151 | print(example) 152 | options(orig_opts) 153 | 154 | \dontshow{if (length(find.package("withr")) > 0L) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 155 | # using `withr` to manage temporary options 156 | withr::with_options(opts_list(quiet = FALSE), print(example)) 157 | \dontshow{\}) # examplesIf} 158 | } 159 | -------------------------------------------------------------------------------- /man/option_spec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{option_spec} 4 | \alias{option_spec} 5 | \title{Specify Option} 6 | \usage{ 7 | option_spec( 8 | name, 9 | default = bquote(), 10 | desc = NULL, 11 | option_name = get_option_name_fn(envir), 12 | envvar_name = get_envvar_name_fn(envir), 13 | option_fn = function(value, ...) value, 14 | envvar_fn = envvar_eval_or_raw(), 15 | quoted = FALSE, 16 | eager = FALSE, 17 | envir = parent.frame() 18 | ) 19 | } 20 | \arguments{ 21 | \item{name}{A string representing the internal name for the option. This is 22 | the short form \verb{} used within a namespace and relates to, for 23 | example, \verb{.} global R option.} 24 | 25 | \item{default}{Either a quoted expression (if parameter \code{quote == TRUE}) or 26 | default value for the option. Defaults to an empty expression, indicating 27 | that it is unset. The default value is lazily evaluated, evaluated only 28 | when the option is first requested unless parameter \code{eager == TRUE}.} 29 | 30 | \item{desc}{A written description of the option's effects} 31 | 32 | \item{option_name, envvar_name}{A character value or function. If a character 33 | value is provided it is used as the corresponding global option name or 34 | environment variable name. If a function is provided it is provided with 35 | the package name and internal option name to derive the global option name. 36 | For example, provided with package \code{"mypkg"} and option \code{"myoption"}, the 37 | function might return global option name \code{"mypkg.myoption"} or environment 38 | variable name \code{"R_MYPKG_MYOPTION"}. Defaults to configured default 39 | functions which fall back to \code{option_name_default} and 40 | \code{envvar_name_default}, and can be configured using \code{set_option_name_fn} 41 | and \code{set_envvar_name_fn}.} 42 | 43 | \item{option_fn}{A function to use for processing an option value before 44 | being returned from the \link{opt} accessor functions. For further details see 45 | section "Processing Functions".} 46 | 47 | \item{envvar_fn}{A function to use for parsing environment variable values. 48 | Defaults to \code{envvar_eval_or_raw()}. For further details see section 49 | "Processing Functions".} 50 | 51 | \item{quoted}{A logical value indicating whether the \code{default} argument 52 | should be treated as a quoted expression or as a value.} 53 | 54 | \item{eager}{A logical value indicating whether the \code{default} argument should 55 | be eagerly evaluated (upon call), or lazily evaluated (upon first use). 56 | This distinction will only affect default values that rely on evaluation of 57 | an expression, which may produce a different result depending on the 58 | context in which it is evaluated.} 59 | 60 | \item{envir}{An environment in which to search for an options envir object. 61 | It is rarely necessary to use anything but the default.} 62 | } 63 | \value{ 64 | An \code{option_spec} object, which is a simple S3 class wrapping a list 65 | containing these arguments. 66 | } 67 | \description{ 68 | An option specification outlines the various behaviors of an option. It's 69 | default value, related global R option, and related environment variable 70 | name, as well as a description. This information defines the operating 71 | behavior of the option. 72 | } 73 | \section{Processing Functions}{ 74 | Parameters \code{option_fn} and \code{envvar_fn} allow for customizing the way values 75 | are interpreted and processed before being returned by \code{\link{opt}} functions. 76 | \subsection{\code{envvar_fn}}{ 77 | 78 | When a value is retrieved from an environment variable, the string value 79 | contained in the environment variable is first processed by \code{envvar_fn}. 80 | 81 | An \code{envvar_fn} accepts only a single positional argument, and should have a 82 | signature such as: 83 | 84 | \if{html}{\out{}}\preformatted{function(value) 85 | }\if{html}{\out{}} 86 | } 87 | 88 | \subsection{\code{option_fn}}{ 89 | 90 | Regardless of how a value is produced - either retrieved from an environment 91 | variable, option, a stored default value or from a default provided to an 92 | \code{\link{opt}} accessor function - it is then further processed by \code{option_fn}. 93 | 94 | The first argument provided to \code{option_fn} will always be the retrieved 95 | value. The remaining parameters in the signature should be considered 96 | experimental. In addition to the value, the arguments provided to \code{\link[=opt]{opt()}}, 97 | as well as an additional \code{source} parameter from \code{\link[=opt_source]{opt_source()}} may be 98 | used. 99 | 100 | \strong{Stable} 101 | 102 | \if{html}{\out{}}\preformatted{function(value, ...) 103 | }\if{html}{\out{}} 104 | 105 | \strong{Experimental} 106 | 107 | \if{html}{\out{}}\preformatted{function(value, x, default, env, ..., source) 108 | }\if{html}{\out{}} 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /man/options_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{options_env} 4 | \alias{options_env} 5 | \alias{options_initialized} 6 | \alias{init_options_env} 7 | \alias{as_options_list} 8 | \alias{as_options_list.list} 9 | \alias{as_options_list.options_env} 10 | \alias{get_option_default_value} 11 | \alias{get_options_spec} 12 | \alias{get_option_spec} 13 | \alias{set_option_spec} 14 | \title{Options Environment Class} 15 | \usage{ 16 | options_initialized(env, inherits = FALSE) 17 | 18 | init_options_env(env = parent.frame()) 19 | 20 | as_options_list(x, ...) 21 | 22 | \method{as_options_list}{list}(x, ...) 23 | 24 | \method{as_options_list}{options_env}(x, ...) 25 | 26 | get_option_default_value(x, env = parent.frame()) 27 | 28 | get_options_spec(env = parent.frame()) 29 | 30 | get_option_spec( 31 | name, 32 | env = parent.frame(), 33 | inherits = FALSE, 34 | on_missing = warning 35 | ) 36 | 37 | set_option_spec(name, details, env = parent.frame()) 38 | } 39 | \arguments{ 40 | \item{env}{An environment in which to search for an options environment} 41 | 42 | \item{inherits}{Whether to search upward through parent environments} 43 | 44 | \item{...}{Additional arguments unused} 45 | } 46 | \description{ 47 | The options environment stores primarily, the default values for options. In 48 | addition, it stores metadata pertaining to each option in the form of 49 | attributes. 50 | } 51 | \section{Functions}{ 52 | \itemize{ 53 | \item \code{options_initialized()}: Test whether options is initialized in environment 54 | 55 | \item \code{init_options_env()}: Initialize an options object 56 | 57 | \item \code{as_options_list()}: Convert into an options list 58 | 59 | \item \code{get_option_default_value()}: Get the option's default value 60 | 61 | \item \code{get_options_spec()}: Get all options specifications as named list 62 | 63 | \item \code{get_option_spec()}: Get single option specification 64 | 65 | \item \code{set_option_spec()}: Set single option specification 66 | 67 | }} 68 | \section{Attributes}{ 69 | 70 | \itemize{ 71 | \item \code{spec}: A named list of option specifications 72 | \item \code{option_name_fn}: A function used to derive default option names for 73 | newly defined options. See \code{\link[=set_option_name_fn]{set_option_name_fn()}}. 74 | \item \code{envvar_name_fn}: A function used to derive default environment variable 75 | names for newly defined options. See \code{\link[=set_envvar_name_fn]{set_envvar_name_fn()}}. 76 | } 77 | } 78 | 79 | \concept{options_env} 80 | \keyword{internal} 81 | -------------------------------------------------------------------------------- /man/options_fmts.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/crayon.R 3 | \name{options_fmts} 4 | \alias{options_fmts} 5 | \title{Optional Crayon Handling} 6 | \usage{ 7 | options_fmts() 8 | } 9 | \value{ 10 | formatted text 11 | } 12 | \description{ 13 | Generate a list of styling functions using \code{crayon}, while safely falling 14 | back to non-\code{crayon} output when \code{crayon} is unavailable. 15 | } 16 | \keyword{internal} 17 | -------------------------------------------------------------------------------- /man/pkgname.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{pkgname} 4 | \alias{pkgname} 5 | \title{Grab package name, at runtime} 6 | \usage{ 7 | pkgname(env = parent.frame()) 8 | } 9 | \arguments{ 10 | \item{env}{An environment in which to search for a package name} 11 | } 12 | \value{ 13 | A package name or "globalenv" if not found 14 | } 15 | \description{ 16 | Lazily grab \code{packageName()} within calling environment, not within function 17 | environment. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/reflow_option_desc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/define_option.R 3 | \name{reflow_option_desc} 4 | \alias{reflow_option_desc} 5 | \title{Reflow multiline strings} 6 | \usage{ 7 | reflow_option_desc(x) 8 | } 9 | \arguments{ 10 | \item{x}{A vector of multiline strings to reflow} 11 | } 12 | \value{ 13 | The reflowed strings 14 | } 15 | \description{ 16 | A small helper function for allowing multiline strings to be collapsed into 17 | continuous lines, similar to markdown's paragraph handling. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /pkgdown/_pkgdown.yml: -------------------------------------------------------------------------------- 1 | development: 2 | mode: auto 3 | 4 | url: ~ 5 | template: 6 | bootstrap: 5 7 | theme: arrow-light #atom-one-light 8 | bslib: 9 | fg: "#404040" 10 | bg: "#F4F4F4" 11 | primary: "#fa3939" 12 | navbar-dark-active-color: var(--bs-fg) 13 | navbar-dark-color: var(--bs-fg) 14 | navbar-dark-brand-color: var(--bs-fg) 15 | navbar-dark-brand-hover-color: white 16 | navbar-dark-hover-color: white 17 | 18 | navbar: 19 | bg: primary 20 | 21 | reference: 22 | - title: Accessing Options 23 | - contents: 24 | - opt 25 | - get_options_env 26 | - title: Defining Options 27 | - contents: 28 | - define_option 29 | - define_options 30 | - option_spec 31 | - title: "`roxygen2` Docs" 32 | - desc: Autogenerate documentation your package options 33 | - contents: 34 | - has_keyword("roxygen2") 35 | - title: Customize 36 | - subtitle: Naming Conventions 37 | - desc: Customize how global R options or environment variables are named 38 | - contents: 39 | - has_keyword("naming") 40 | - has_keyword("naming_formats") 41 | - subtitle: Environment Variable Parsing 42 | - desc: Control how environment variables are parsed into useful R data 43 | - contents: 44 | - has_keyword("envvar_parsers") 45 | -------------------------------------------------------------------------------- /pkgdown/extra.scss: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | --bs-code-color: rgba(var(--bs-primary-rgb), 0.8); 4 | } 5 | 6 | h1, h2, h3, h4, h5, h6 { 7 | @extend %headings !optional; 8 | } 9 | 10 | %headings { 11 | margin-top: 1rem; 12 | } 13 | 14 | pre { 15 | padding: 1rem; 16 | background: #fefbfa; 17 | border-style: none none none solid; 18 | border-color: rgba(var(--bs-primary-rgb), 0.6); 19 | border-radius: 0; 20 | border-width: 0.2em; 21 | } 22 | 23 | .btn-copy-ex { 24 | padding: 0.5em 1em; 25 | } 26 | 27 | // for dev version tag, which can be hard to read with bright navbars 28 | .nav-text.text-danger { 29 | background-color: rgba(255, 255, 255, 0.8); 30 | padding: 0.2em 0.8em; 31 | border-radius: 1em; 32 | } 33 | 34 | .navbar-dark { 35 | --bs-navbar-color: rgba(var(--bs-body-bg-rgb), 0.8); 36 | --bs-navbar-hover-color: rgba(var(--bs-body-bg-rgb), 1.0); 37 | font-weight: bolder; 38 | 39 | .navbar-nav { 40 | .active > .nav-link { 41 | background: rgba(255, 255, 255, 0.2); 42 | } 43 | } 44 | 45 | input[type="search"] { 46 | border: none; 47 | border-radius: 0.1em; 48 | background-color: rgba(var(--bs-body-bg-rgb), 0.8); 49 | color: rgba(var(--bs-primary-rgb), 0.6); 50 | &:focus { background-color: rgba(var(--bs-body-bg-rgb), 0.90); } 51 | } 52 | } 53 | 54 | .text-muted { 55 | color: rgba(var(--bs-body-bg-rgb), 0.6) !important; 56 | } 57 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/tests.html 7 | # * https://testthat.r-lib.org/reference/test_package.html#special-files 8 | 9 | library(testthat) 10 | library(options) 11 | 12 | test_check("options") 13 | -------------------------------------------------------------------------------- /tests/testthat/setup.R: -------------------------------------------------------------------------------- 1 | paths <- local({ 2 | sys_path <- system.file("options.example", package = "options") 3 | options.example_path <- if (dir.exists(sys_path)) { 4 | sys_path 5 | } else { 6 | path <- testthat::test_path() 7 | while (!file.exists(file.path(path, "DESCRIPTION"))) path <- dirname(path) 8 | if (dir.exists(pinst <- file.path(path, "inst"))) path <- pinst 9 | file.path(path, "options.example") 10 | } 11 | 12 | list( 13 | options.example = options.example_path 14 | ) 15 | }) 16 | 17 | test_env <- function() { 18 | new.env(parent = baseenv()) 19 | } 20 | 21 | # Configure a set of masking environment variables to allow R CMD check to run 22 | # as a test within R CMD check of `options`. Without masking these, they are 23 | # inherited in the child process which causes false positive R CMD check 24 | # errors. 25 | reset_envvars <- function() { 26 | envvars <- character() 27 | for (v in names(Sys.getenv())) { 28 | if (startsWith(v, "_R_CHECK") || 29 | startsWith(v, "RCMDCHECK") || 30 | startsWith(v, "R_TESTS") || 31 | startsWith(v, "TESTTHAT")) { 32 | envvars[v] <- "" 33 | } 34 | } 35 | envvars 36 | } 37 | 38 | remove_options_env <- function() { 39 | env <- get_options_env.default(parent.frame(), inherits = TRUE) 40 | specs <- get_options_spec(env) 41 | opt_names <- vcapply(specs, function(spec) spec$option_name) 42 | args <- rep_len(list(NULL), length(opt_names)) 43 | names(args) <- opt_names 44 | do.call(options, args) 45 | rm(".options", envir = parent.env(env)) 46 | } 47 | 48 | pkgload::load_all(paths$options.example, export_all = FALSE) 49 | -------------------------------------------------------------------------------- /tests/testthat/test-define-option.R: -------------------------------------------------------------------------------- 1 | test_that("define_options accepts multiple option definitions", { 2 | e <- test_env() 3 | 4 | expect_silent(with(e, { 5 | options::define_options( 6 | "option A", 7 | A = 1, 8 | "option B", 9 | B = 2 10 | ) 11 | })) 12 | 13 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 2) 14 | expect_equal(opt("A", env = e), 1) 15 | expect_equal(opt("B", env = e), 2) 16 | }) 17 | 18 | test_that("define_options accepts option_spec parameters", { 19 | e <- test_env() 20 | 21 | expect_silent(with(e, options::define_options( 22 | "this is option A", 23 | A = 1, 24 | option_name = "opt_a", 25 | envvar_name = "OPT_A" 26 | ))) 27 | 28 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 29 | expect_silent(spec <- get_option_spec("A", env = e)) 30 | expect_equal(spec$option_name, "opt_a") 31 | }) 32 | 33 | test_that("define_options errors when 'desc' argument is passed", { 34 | expect_error( 35 | with(test_env(), options::define_options( 36 | "this is option A", 37 | A = 1, 38 | desc = 10, 39 | option_name = "opt_a", 40 | envvar_name = "OPT_A" 41 | )), 42 | "desc" 43 | ) 44 | 45 | # but not when "desc" is the option name 46 | expect_no_error( 47 | with(test_env(), options::define_options( 48 | "this option is for descriptions", 49 | desc = 10, 50 | option_name = "opt_a", 51 | envvar_name = "OPT_A" 52 | )) 53 | ) 54 | }) 55 | 56 | test_that("define_options errors when 'name' argument is passed", { 57 | expect_error( 58 | with(test_env(), options::define_options( 59 | "this is option A", 60 | A = 1, 61 | name = "A", 62 | option_name = "opt_a", 63 | envvar_name = "OPT_A" 64 | )), 65 | "name" 66 | ) 67 | 68 | # but not when "name" is the option name 69 | expect_no_error( 70 | with(test_env(), options::define_options( 71 | "this is option 'name'", 72 | name = "bob", 73 | option_name = "opt_a", 74 | envvar_name = "OPT_A" 75 | )) 76 | ) 77 | }) 78 | 79 | test_that("define_options errors with empty last argument", { 80 | expect_error( 81 | with(test_env(), options::define_options( 82 | "this is option A", 83 | A = 1, 84 | )), 85 | "comma" 86 | ) 87 | }) 88 | 89 | test_that("define_options errors on multiple consecutive unnamed args", { 90 | expect_error( 91 | with(test_env(), options::define_options( 92 | "this isn't write", 93 | "this is option A", 94 | A = 1 95 | )), 96 | "follow.*description" 97 | ) 98 | }) 99 | 100 | test_that("define_options error when first argument is named", { 101 | expect_error( 102 | with(test_env(), options::define_options( 103 | A = 1 104 | )), 105 | "begin.*description" 106 | ) 107 | }) 108 | 109 | test_that("define_options errors report appropriate options", { 110 | expect_error( 111 | with(test_env(), options::define_options(A = 1, "B", B = 2)), 112 | "option \\(1\\)" 113 | ) 114 | 115 | expect_error( 116 | with(test_env(), options::define_options( 117 | "A", 118 | A = 1, 119 | "B", 120 | B = 2, 121 | desc = "b" 122 | )), 123 | "option \\(2\\)" 124 | ) 125 | }) 126 | 127 | test_that("define_option accepts parameterized options specification", { 128 | e <- test_env() 129 | 130 | expect_silent(with(e, { 131 | options::define_option( 132 | "A", 133 | default = 1, 134 | desc = "this is option A", 135 | option_name = "opt_a", 136 | envvar_name = "OPT_A" 137 | ) 138 | })) 139 | 140 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 141 | expect_equal(opt("A", env = e), 1) 142 | }) 143 | 144 | test_that("define_option accepts an option_spec object", { 145 | e <- test_env() 146 | 147 | expect_silent(with(e, { 148 | options::define_option(options::option_spec( 149 | "A", 150 | default = 1, 151 | desc = "this is option A", 152 | option_name = "opt_a", 153 | envvar_name = "OPT_A" 154 | )) 155 | })) 156 | 157 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 158 | expect_equal(opt("A", env = e), 1) 159 | }) 160 | 161 | test_that("option_spec option_fn processes option values", { 162 | e <- test_env() 163 | 164 | expect_silent(with(e, { 165 | options::define_option(options::option_spec( 166 | "A", 167 | default = 1, 168 | desc = "this is option A", 169 | option_name = "opt_a", 170 | option_fn = function(value, ..., source = source) { 171 | print(source) 172 | value + 1 173 | }, 174 | envvar_name = "OPT_A" 175 | )) 176 | })) 177 | 178 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 179 | expect_output(expect_equal(opt("A", env = e), 2), "default") 180 | expect_output(withr::with_envvar( 181 | list(OPT_A = "10"), 182 | expect_equal(opt("A", env = e), 11) 183 | ), "envvar") 184 | }) 185 | -------------------------------------------------------------------------------- /tests/testthat/test-envvars.R: -------------------------------------------------------------------------------- 1 | test_that("envvar_is trys for complex data", { 2 | expect_true(envvar_is(list(1, 2))("list(1, 2)")) 3 | expect_false(envvar_is(list(1, 2))("c(1, 2)")) 4 | expect_false(envvar_is(list(1, 2))("1 + 'a'")) 5 | }) 6 | 7 | test_that("envvar_is works for numerics", { 8 | expect_true(envvar_is(1)("1")) 9 | expect_false(envvar_is(1)("0")) 10 | expect_false(envvar_is(1)("str")) 11 | }) 12 | 13 | test_that("envvar_is works for logicals", { 14 | expect_true(envvar_is(TRUE)("true")) 15 | expect_true(envvar_is(TRUE)("True")) 16 | expect_true(envvar_is(TRUE)("TRUE")) 17 | expect_true(envvar_is(FALSE)("false")) 18 | expect_true(envvar_is(FALSE)("False")) 19 | expect_true(envvar_is(FALSE)("FALSE")) 20 | expect_false(envvar_is(TRUE)("1")) 21 | expect_false(envvar_is(TRUE)("0")) 22 | expect_true(envvar_is(TRUE, case_sensitive = FALSE)("TrUe")) 23 | expect_false(envvar_is(TRUE, case_sensitive = TRUE)("TrUe")) 24 | }) 25 | 26 | test_that("envvar_is works for strings", { 27 | expect_true(envvar_is("test")("test")) 28 | expect_true(envvar_is("test")("Test")) 29 | expect_false(envvar_is("test", case_sensitive = TRUE)("Test")) 30 | expect_false(envvar_is("test")("1")) 31 | }) 32 | 33 | test_that("envvar_is works for nulls", { 34 | expect_true(envvar_is(NULL)("null")) 35 | expect_true(envvar_is(NULL)("NULL")) 36 | expect_false(envvar_is(NULL)("not null")) 37 | }) 38 | 39 | test_that("envvar_eval returns evaluated expression or error", { 40 | expect_equal(envvar_eval()("1 + 2", "VAR"), 3) 41 | expect_error( 42 | envvar_eval()("1 + 'a'", "VAR"), 43 | "Environment variable 'VAR' could not" 44 | ) 45 | }) 46 | 47 | test_that("envvar_eval_or_raw returns evaluated expression or error, or raw string", { 48 | expect_equal(envvar_eval_or_raw()("1 + 2"), 3) 49 | expect_equal(envvar_eval_or_raw()("1 + 'a'"), "1 + 'a'") 50 | }) 51 | 52 | test_that("envvar_is_one_of returns logical if in set", { 53 | expect_true(envvar_is_one_of(1:3)(1)) 54 | expect_false(envvar_is_one_of(1:3)(4)) 55 | expect_true(envvar_is_one_of(list(1, "a"))("a")) 56 | expect_false(envvar_is_one_of(list(1, "a"))("b")) 57 | }) 58 | 59 | test_that("envvar_choice_of returns value if in set, default otherwise", { 60 | expect_equal(envvar_choice_of(1:3)(1), 1) 61 | expect_equal(envvar_choice_of(1:3)(4), NULL) 62 | expect_equal(envvar_choice_of(1:3, default = 10)(4), 10) 63 | }) 64 | 65 | test_that("envvar_is_true returns TRUE for truthy values", { 66 | expect_true(envvar_is_true()("TRUE")) 67 | expect_true(envvar_is_true()("True")) 68 | expect_true(envvar_is_true()("true")) 69 | expect_true(envvar_is_true()("1")) 70 | expect_false(envvar_is_true()("other")) 71 | expect_false(envvar_is_true()("0")) 72 | }) 73 | 74 | test_that("envvar_is_false returns TRUE for falsy values", { 75 | expect_true(envvar_is_false()("FALSE")) 76 | expect_true(envvar_is_false()("False")) 77 | expect_true(envvar_is_false()("false")) 78 | expect_true(envvar_is_false()("0")) 79 | expect_false(envvar_is_false()("other")) 80 | expect_false(envvar_is_false()("1")) 81 | }) 82 | 83 | test_that("envvar_is_set always returns TRUE (unset condition handled elsewhere)", { 84 | expect_true(envvar_is_set()("anything"), TRUE) 85 | }) 86 | 87 | test_that("envvar_str_split splits raw string on delimiter", { 88 | expect_equal(envvar_str_split()("a;b;c"), c("a", "b", "c")) 89 | expect_equal(envvar_str_split(delim = "-")("a-b-c"), c("a", "b", "c")) 90 | expect_equal(envvar_str_split()("a"), c("a")) 91 | }) 92 | -------------------------------------------------------------------------------- /tests/testthat/test-naming.R: -------------------------------------------------------------------------------- 1 | test_that("changing naming functions affects future options", { 2 | e <- new.env() 3 | 4 | f_env <- function(pkg, name) toupper(name) 5 | f_opt <- function(pkg, name) tolower(name) 6 | 7 | expect_silent(set_envvar_name_fn(f_env, env = e)) 8 | expect_silent(set_option_name_fn(f_opt, env = e)) 9 | 10 | expect_identical(get_envvar_name_fn(env = e), f_env) 11 | expect_identical(get_option_name_fn(env = e), f_opt) 12 | 13 | expect_silent(with(e, define_options("option A", A = 1))) 14 | expect_silent(spec <- get_option_spec("A", env = e)) 15 | 16 | expect_identical(spec$envvar_name, "A") 17 | expect_identical(spec$option_name, "a") 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat/test-opt.R: -------------------------------------------------------------------------------- 1 | test_that("opts() fetches options by name if provided", { 2 | expect_silent(o <- opts("quiet", env = "options.example")) 3 | expect_length(o, 1) 4 | expect_named(o, "quiet") 5 | 6 | expect_silent(o <- opts(c("quiet", "use_options"), env = "options.example")) 7 | expect_length(o, 2) 8 | expect_named(o, c("quiet", "use_options")) 9 | }) 10 | 11 | test_that("opts() can be used to set options when provided a named list", { 12 | define_options("quietly", quiet = FALSE, "verbosity", verbose = FALSE) 13 | on.exit(remove_options_env()) 14 | 15 | # opts updates values and returns originals 16 | expect_silent(o <- opts(list(quiet = 42, verbose = TRUE))) 17 | expect_length(o, 2) 18 | expect_named(o, c("quiet", "verbose")) 19 | expect_equal(o$quiet, FALSE) 20 | expect_equal(o$verbose, FALSE) 21 | 22 | # retrieving values are modified 23 | expect_silent(o <- opts(c("quiet", "verbose"))) 24 | expect_length(o, 2) 25 | expect_named(o, c("quiet", "verbose")) 26 | expect_equal(o$quiet, 42) 27 | expect_equal(o$verbose, TRUE) 28 | }) 29 | 30 | test_that("opts() can be used to retrieve options when provided a unnamed list", { 31 | define_options("quietly", quiet = FALSE, "verbosity", verbose = FALSE) 32 | on.exit(remove_options_env()) 33 | 34 | expect_silent(o <- opts(list("quiet", "verbose"))) 35 | expect_length(o, 2) 36 | expect_named(o, c("quiet", "verbose")) 37 | expect_equal(o$quiet, FALSE) 38 | expect_equal(o$verbose, FALSE) 39 | }) 40 | 41 | test_that("opts() fetches options by name if provided", { 42 | expect_silent(o <- opts(env = "options.example")) 43 | expect_s3_class(o, "options_list") 44 | expect_true(length(o) > 2) 45 | expect_true(length(o) == length(unique(names(o)))) 46 | }) 47 | 48 | test_that("opts() throws error when mixing named and unnamed list values", { 49 | expect_error(opts(list(env = "options.example", "quiet"))) 50 | }) 51 | -------------------------------------------------------------------------------- /tests/testthat/test-opt_set.R: -------------------------------------------------------------------------------- 1 | test_that("opt_set modifies option value", { 2 | define_options("option quiet", quiet = FALSE) 3 | on.exit(remove_options_env()) 4 | 5 | expect_silent(opt_set("quiet", 42)) 6 | expect_equal(opt("quiet"), 42) 7 | }) 8 | 9 | test_that("opt_set returns original value", { 10 | define_options("option quiet", quiet = FALSE) 11 | on.exit(remove_options_env()) 12 | 13 | expect_equal(opt_set("quiet", 42), NULL) 14 | expect_equal(opt_set("quiet", 123), 42) 15 | }) 16 | 17 | test_that("opt_set raises on_missing callback when option not defined", { 18 | define_options("option quiet", quiet = FALSE) 19 | on.exit(remove_options_env()) 20 | 21 | expect_warning(opt_set("verbose", 42), "not defined") 22 | }) 23 | 24 | test_that("opt_set raises on_missing accepts callback functions", { 25 | define_options("option quiet", quiet = FALSE) 26 | on.exit(remove_options_env()) 27 | 28 | custom <- function(...) { 29 | cond <- simpleCondition("testing 1 2 3") 30 | class(cond) <- c("custom", "condition") 31 | signalCondition(cond) 32 | } 33 | 34 | expect_error(opt_set("verbose", 42, on_missing = stop), "not defined") 35 | expect_condition(opt_set("verbose", 42, on_missing = custom), class = "custom") 36 | }) 37 | 38 | test_that("opt_set raises on_missing accepts callback shorthand names", { 39 | define_options("option quiet", quiet = FALSE) 40 | on.exit(remove_options_env()) 41 | 42 | expect_error(opt_set("verbose", 42, on_missing = "error"), "not defined") 43 | }) 44 | -------------------------------------------------------------------------------- /tests/testthat/test-opt_set_local.R: -------------------------------------------------------------------------------- 1 | test_that("opt_set_local, modifying env options, restores original value when frame exits", { 2 | fn <- function() { 3 | opt_set_local("quiet", 42, env = "options.example") 4 | opt("quiet", env = "options.example") 5 | } 6 | 7 | orig <- opt("quiet", env = "options.example") 8 | expect_true(!identical(orig, 42)) 9 | expect_equal(fn(), 42) 10 | expect_equal(opt("quiet", env = "options.example"), orig) 11 | }) 12 | 13 | test_that("opt_set_local, modifying global options, restores original value when frame exits", { 14 | define_options("option quiet", quiet = FALSE) 15 | on.exit(remove_options_env()) 16 | 17 | fn <- function() { 18 | opt_set_local("quiet", 42) 19 | opt("quiet") 20 | } 21 | 22 | orig <- opt("quiet") 23 | expect_true(!identical(orig, 42)) 24 | expect_equal(fn(), 42) 25 | expect_equal(opt("quiet"), orig) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/testthat/test-options_env.R: -------------------------------------------------------------------------------- 1 | test_that("get_options_env() returns options env if it exists", { 2 | e <- test_env() 3 | child_env <- new.env(parent = e) 4 | 5 | expect_silent(with(e, options::define_option( 6 | "A", 7 | default = 1, 8 | option_name = "opt.a", 9 | envvar_name = "OPT_A" 10 | ))) 11 | 12 | expect_equal(names(get_options_env(env = e)), "A") 13 | expect_equal(names(get_options_env(env = child_env, inherits = TRUE)), "A") 14 | expect_equal(get_options_env(), emptyenv()) 15 | }) 16 | -------------------------------------------------------------------------------- /tests/testthat/test-opts_list.R: -------------------------------------------------------------------------------- 1 | test_that("opts_list produces an option of namespaced option names", { 2 | e <- new.env(parent = baseenv()) 3 | 4 | expect_silent(with(e, options::define_options( 5 | "option quiet", 6 | quiet = TRUE 7 | ))) 8 | 9 | l <- expect_silent(with(e, options::opts_list(quiet = FALSE))) 10 | 11 | expect_match(names(l), ".+\\.quiet") 12 | expect_type(l, "list") 13 | expect_equal(l[[1]], FALSE) 14 | }) 15 | 16 | test_that("opts_list returns raw name if not part of namespaced option spec", { 17 | e <- new.env(parent = baseenv()) 18 | expect_silent(with(e, options::define_options( 19 | "option quiet", 20 | quiet = TRUE 21 | ))) 22 | 23 | l <- expect_silent(with(e, options::opts_list(quiet = FALSE, max.print = 10))) 24 | 25 | expect_match(names(l)[[1]], ".+\\.quiet") 26 | expect_true("max.print" %in% names(l)) 27 | }) 28 | 29 | test_that("opts_list emit warnings when names missing with check_names warn", { 30 | e <- new.env(parent = baseenv()) 31 | expect_silent(with(e, options::define_options( 32 | "option quiet", 33 | quiet = TRUE 34 | ))) 35 | 36 | expect_warning(with(e, { 37 | options::opts_list(quiet = FALSE, max.print = 10, check_names = "warn") 38 | })) 39 | }) 40 | 41 | test_that("opts_list emit error when names missing with check_names stop", { 42 | e <- new.env(parent = baseenv()) 43 | expect_silent(with(e, options::define_options( 44 | "option quiet", 45 | quiet = TRUE 46 | ))) 47 | 48 | expect_error(with(e, { 49 | options::opts_list(quiet = FALSE, max.print = 10, check_names = "stop") 50 | })) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/testthat/test-output.R: -------------------------------------------------------------------------------- 1 | test_that("options objects pretty print", { 2 | e <- new.env(parent = baseenv()) 3 | 4 | expect_silent(with(e, options::define_option( 5 | "A", 6 | default = 1, 7 | option_name = "opt.A", 8 | envvar_name = "OPT_A" 9 | ))) 10 | 11 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 12 | 13 | # name and current value print 14 | expect_match(out, "A = 1") 15 | 16 | # sources print 17 | expect_match(out, "option\\s+:\\s*opt\\.A") 18 | expect_match(out, "envvar\\s+:\\s*OPT_A") 19 | expect_match(out, "default\\s+:\\s*1") 20 | 21 | # current source is flagged 22 | expect_match(out, " option") 23 | expect_match(out, " envvar") 24 | expect_match(out, "*default") 25 | 26 | # and updates when current source changes 27 | Sys.setenv("OPT_A" = 3) 28 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 29 | expect_match(out, " option") 30 | expect_match(out, "*envvar") 31 | expect_match(out, " default") 32 | Sys.unsetenv("OPT_A") 33 | }) 34 | 35 | test_that("options objects prints options in definition order", { 36 | e <- new.env(parent = baseenv()) 37 | 38 | expect_silent(with(e, options::define_option( 39 | "B", 40 | default = 2, 41 | envvar_name = "OPT_B" 42 | ))) 43 | 44 | expect_silent(with(e, options::define_option( 45 | "A", 46 | default = 1, 47 | envvar_name = "OPT_A" 48 | ))) 49 | 50 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 51 | expect_match(out, "OPT_B.*OPT_A") 52 | }) 53 | 54 | test_that("options objects print even without default", { 55 | e <- new.env(parent = baseenv()) 56 | expect_silent(with(e, options::define_option("B"))) 57 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 58 | expect_match(out, "") 59 | }) 60 | -------------------------------------------------------------------------------- /tests/testthat/test-precedence.R: -------------------------------------------------------------------------------- 1 | test_that("option, envvar, default precedence is used for option values", { 2 | withr::defer({ 3 | Sys.unsetenv("OPT_A") 4 | Sys.unsetenv("OPT_B") 5 | options(opt.A = NULL, opt.B = NULL) 6 | }) 7 | 8 | e <- new.env() 9 | expect_silent(with(e, define_option( 10 | "A", 11 | default = 1, 12 | option_name = "opt.A", 13 | envvar_name = "OPT_A" 14 | ))) 15 | 16 | expect_equal(opt("A", env = e), 1) 17 | 18 | Sys.setenv("OPT_A" = 2) 19 | expect_equal(opt("A", env = e), 2) 20 | 21 | options("opt.A" = 3) 22 | expect_equal(opt("A", env = e), 3) 23 | 24 | options("opt.A" = NULL) 25 | expect_equal(opt("A", env = e), 2) 26 | 27 | Sys.unsetenv("OPT_A") 28 | expect_equal(opt("A", env = e), 1) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/testthat/test-rcmdcheck.R: -------------------------------------------------------------------------------- 1 | test_that("Packages that use options pass R CMD check", { 2 | skip_on_os(c("windows", "solaris")) 3 | skip_on_covr() 4 | skip_if_not( 5 | "options" %in% rownames(installed.packages()), 6 | paste0( 7 | "Skipping R CMD check integration tests as package 'options' ", 8 | "is not installed and would be unavailable to satisfy dependency ", 9 | "requirements." 10 | ) 11 | ) 12 | 13 | results <- rcmdcheck::rcmdcheck( 14 | paths$options.example, 15 | build_args = "--no-manual", 16 | args = "--no-manual", 17 | env = reset_envvars(), 18 | quiet = TRUE 19 | ) 20 | 21 | expect_length(results$errors, 0) 22 | expect_length(results$warnings, 0) 23 | expect_length(results$notes, 0) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/testthat/test-roxygen2.R: -------------------------------------------------------------------------------- 1 | test_that("roxygen2-style block is generated from options env object", { 2 | e <- test_env() 3 | 4 | expect_silent(with(e, options::define_option( 5 | "A", 6 | default = 1, 7 | option_name = "opt.a", 8 | envvar_name = "OPT_A" 9 | ))) 10 | 11 | expect_silent(block <- paste0(as_roxygen_docs(env = e), collapse = "\n")) 12 | expect_match(block, "@title") 13 | expect_match(block, "\\{default: \\}\\{\\\\preformatted") 14 | expect_match(block, "\\{option: \\}\\{opt.a") 15 | expect_match(block, "\\{envvar: \\}\\{OPT_A") 16 | }) 17 | 18 | test_that("roxygen2 options documentation is in definition order", { 19 | e <- test_env() 20 | expect_silent(with(e, options::define_option("B"))) 21 | expect_silent(with(e, options::define_option("A"))) 22 | 23 | expect_silent(block <- paste0(as_roxygen_docs(env = e), collapse = "\n")) 24 | expect_match(block, "\\\\item\\{B\\}.*\\\\item\\{A\\}") 25 | }) 26 | 27 | test_that("roxygen2-style params block is generated from as_params", { 28 | e <- test_env() 29 | 30 | expect_silent(with(e, options::define_option( 31 | "A", 32 | default = 1, 33 | desc = "my description", 34 | option_name = "opt.a", 35 | envvar_name = "OPT_A" 36 | ))) 37 | 38 | expect_silent(block <- paste0(with(e, options::as_params()), collapse = "\n")) 39 | expect_match(block, "^@param A") 40 | expect_match(block, "my description") 41 | expect_match(block, "Defaults to `1`") 42 | expect_match(block, "'opt.a'") 43 | expect_match(block, "'OPT_A'") 44 | }) 45 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/envvars.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Environment Variables" 3 | subtitle: Customizing environment variable handling 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{environment_variables} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>" 15 | ) 16 | ``` 17 | 18 | Environment variables can be a tremendously helpful alternative to options for 19 | behaviors that one might want to handle ubiquitously across R sessions, that 20 | might be administered, or that might be needed in a command-line context. 21 | 22 | They can provide a helpful interface for supporting tasks that might be called 23 | during continuous integration jobs, on a compute cluster, or within a 24 | containerized environment. Since use cases can be hard to predict, `options` 25 | will consider appropriately named environment variables by default. 26 | 27 | # Customizing names 28 | 29 | Perhaps the default environment names don't suit you. When defining your 30 | options, you can always use your own! However, this level of customization is 31 | only available through the `define_option()` interface. 32 | 33 | > By default environment variables look like `R__` 34 | 35 | ```{r setup} 36 | library(options) 37 | ``` 38 | 39 | ```{r custom_envvar_name} 40 | define_option( 41 | "volume", 42 | default = "shout", 43 | desc = "Print output in uppercase ('shout') or lowercase ('whisper')", 44 | option_name = "volume", 45 | envvar_name = "VOL" 46 | ) 47 | 48 | twist_and <- function(what = opt("volume")) { 49 | lyric <- paste( 50 | "Well, shake it up, baby, now (Shake it up, baby)", 51 | "Twist and shout (Twist and shout)", 52 | sep = "\n" 53 | ) 54 | 55 | cat(if (what == "shout") toupper(lyric) else tolower(lyric), "\n") 56 | } 57 | ``` 58 | 59 | ```{r custom_envvar_ex} 60 | twist_and() # by default, "shout" 61 | ``` 62 | 63 | We can now alter our behavior using the environment variable, `VOL`. 64 | 65 | ```{r custom_envvar_val, error = TRUE} 66 | Sys.setenv(VOL = "whisper") 67 | 68 | twist_and() # picks up our environment variable, "whisper" 69 | ``` 70 | 71 | That's better! 72 | 73 | # Setting Naming Rules 74 | 75 | Although individually mapping options to environment variables is handy for 76 | one-off options, it can be tedious to do throughout your package, especially if 77 | you want all your variables to follow some consistent naming scheme. 78 | 79 | For this we can provide a function which is used to name all future options' 80 | environment variables. 81 | 82 | ```{r envvar_name_convention} 83 | set_envvar_name_fn(function(package, name) { 84 | gsub("[^A-Z0-9]", "_", toupper(paste0(package, "_", name))) 85 | }) 86 | ``` 87 | 88 | Now any future option environment variables will use this convention when they 89 | are defined. Existing options will be unaffected until they are redefined, so 90 | it's often best to make sure this code runs before you start defining options. 91 | 92 | ```{r, echo = FALSE} 93 | Sys.unsetenv("VOL") 94 | ``` 95 | 96 | ```{r redefine_option} 97 | define_options( 98 | "Print output in uppercase ('shout') or lowercase ('whisper')", 99 | volume = "shout" 100 | ) 101 | ``` 102 | 103 | You'll notice that our redefined option now uses our custom naming scheme for 104 | its environment variable. 105 | 106 | You can always write your own function, or choose from some of the pre-built 107 | ones in `?naming_formats`. 108 | 109 | # Parsing Values 110 | 111 | So far we've just been using the environment variable's value as-is. Environment 112 | variable values, by default, will try to be parsed into R objects. If that 113 | fails, they'll deliver the raw string value. 114 | 115 | This can be a nice default behavior, handling many simple cases as expected 116 | 117 | +--------------------------------------+---------------------------------------+ 118 | | Environment Variable | Default `options` value | 119 | +======================================+=======================================+ 120 | | `whisper` | `[1] "whisper"` (_character_) | 121 | +--------------------------------------+---------------------------------------+ 122 | | `"shout"` | `[1] "shout"` (_character_) | 123 | +--------------------------------------+---------------------------------------+ 124 | | `12345` | `[1] 12345` (_numeric_) | 125 | +--------------------------------------+---------------------------------------+ 126 | | `TRUE` | `[1] TRUE` (_logical_) | 127 | +--------------------------------------+---------------------------------------+ 128 | | `NULL` | `NULL` | 129 | +--------------------------------------+---------------------------------------+ 130 | | `list(1, 'a')` | ``` | 131 | | | [[1]] | 132 | | | [1] 1 | 133 | | | | 134 | | | [[2]] | 135 | | | [1] "a" | 136 | | | | 137 | | | ``` | 138 | +--------------------------------------+---------------------------------------+ 139 | | `list(1, 'a',)` (_error!_) | `[1] "list(1, 'a',)"` (_character_) | 140 | +--------------------------------------+---------------------------------------+ 141 | 142 | But you'll notice that a typo in our last example completely changed the type of 143 | data that is read in. Depending on the way that you intend to use this variable, 144 | perhaps this default is more error-prone than necessary. 145 | 146 | To help with this, there is a whole family of functions that allow you to 147 | customize the way that environment variables are internalized as option values 148 | (`?envvar_fns`). Generally, it's best to keep these specific to the data type of 149 | the option that you intend to use - for example, using `envvar_is_true` to 150 | always coerce the value to a logical scalar. 151 | 152 | Just like before, these are used to specify your option's behaviors: 153 | 154 | ```{r, echo = FALSE} 155 | Sys.unsetenv("VOL") 156 | ``` 157 | 158 | ```{r define_envvar_fn} 159 | define_option( 160 | "volume", 161 | default = TRUE, 162 | desc = "Print output in uppercase (TRUE) or lowercase (FALSE)", 163 | envvar_fn = envvar_is_true() 164 | ) 165 | ``` 166 | 167 | Of course you can define this function however you like. 168 | 169 | # Example 170 | 171 | Let's put it all together. We'll customize our environment variable name, and 172 | provide a custom `envvar_fn` which handles how we interpret the raw environment 173 | variable value. 174 | 175 | ```{r, echo = FALSE} 176 | Sys.unsetenv("VOL") 177 | ``` 178 | 179 | ```{r define_envvar_fn_2} 180 | define_option( 181 | "volume", 182 | default = 1, 183 | desc = paste0( 184 | "Print output in uppercase (shout) or lowercase (whisper), or any ", 185 | "number from 1-10 for random uppercasing" 186 | ), 187 | envvar_name = "VOL", 188 | envvar_fn = function(raw, ...) { 189 | choice_of_nums <- envvar_choice_of(1:11) 190 | switch(raw, shout = 10, whisper = 1, choice_of_nums(raw)) 191 | } 192 | ) 193 | ``` 194 | 195 | > Note that the `?envvar_fns` family of functions, like `envvar_choice_of()` 196 | > return _functions_. Although this is a very powerful mechanism of customizing 197 | > behaviors, it can look odd at first glance. 198 | > 199 | > We first generate our function by giving it which values to choose from 200 | > (`envvar_choice_of(1:11)`), then use that function when we have our raw value 201 | > (`choice_of_nums(raw)`). 202 | 203 | Now we need to update our `twist_and` function to work with our newly consistent 204 | numeric volumes. 205 | 206 | ```{r twist_and_eleven} 207 | twist_and_shout <- function(vol = opt("volume")) { 208 | lyric <- c( 209 | "Well, shake it up, baby, now (Shake it up, baby)", 210 | "Twist and shout (Twist and shout)" 211 | ) 212 | 213 | # handle case where volume knob is broken 214 | if (is.null(vol)) stop("someone turned off the stereo") 215 | 216 | # randomly uppercase characters to match volume 217 | lyric <- strsplit(tolower(lyric), "") 218 | lyric <- lapply(lyric, function(line) { 219 | char_sample <- runif(nchar(line)) < (vol - 1) / 9 220 | line[char_sample] <- toupper(line[char_sample]) 221 | paste0(line, collapse = "") 222 | }) 223 | 224 | # in case someone turns it up to 11 225 | if (vol == 11) lyric <- gsub("(\\s*\\(|\\))", "!!!\\1", lyric) 226 | 227 | cat(paste(lyric, collapse = "\n"), "\n") 228 | } 229 | ``` 230 | 231 | Let's try it out! 232 | 233 | ```{r} 234 | Sys.setenv(VOL = "whisper") 235 | twist_and_shout() 236 | ``` 237 | 238 | ```{r} 239 | Sys.setenv(VOL = 5) 240 | twist_and_shout() 241 | ``` 242 | 243 | ```{r} 244 | Sys.setenv(VOL = "shout") 245 | twist_and_shout() 246 | ``` 247 | 248 | ```{r} 249 | Sys.setenv(VOL = 11) 250 | twist_and_shout() 251 | ``` 252 | 253 | ```{r, error = TRUE} 254 | Sys.setenv(VOL = "off") # parsed as NULL 255 | twist_and_shout() 256 | ``` 257 | 258 | Looks pretty good! We handle just the inputs we want, without having to worry 259 | about unexpected data slipping into our R code. 260 | -------------------------------------------------------------------------------- /vignettes/options.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "options" 3 | subtitle: _Simple, Consistent Package Options_ 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{options} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | eval = FALSE, 14 | collapse = TRUE, 15 | comment = "#>" 16 | ) 17 | ``` 18 | 19 | R's options are a useful tool for managing global settings. `options` aims to 20 | make them easy to configure and use, reduce boilerplate code and handle more 21 | involved options handling so you don't have to. 22 | 23 | # Defining Options 24 | 25 | While writing your package, we start by defining which options our package might 26 | use. 27 | 28 | Define your options using the `define_options()` shorthand. Interlace 29 | descriptions and default values to define multiple options at once. 30 | 31 | ```{r defining_options} 32 | options::define_options( 33 | "This is an example of how a package author would document their internally 34 | used options. This option could make the package default to executing 35 | quietly.", 36 | quiet = TRUE, 37 | 38 | "Multiple options can be defined, providing default values if a global option 39 | or environment variable isn't set.", 40 | second_example = FALSE, 41 | 42 | "Default values are lazily evaluated, so you are free to use package functions 43 | without worrying about build-time evaluation order", 44 | lazy_example = fn_not_defined_until_later() 45 | ) 46 | ``` 47 | 48 | When you want more control, you can use `define_option()` to declare all aspects 49 | of how your option behaves. 50 | 51 | ```{r defining_an_option} 52 | options::define_option( 53 | option = "concrete_example", 54 | default = TRUE, 55 | desc = paste0( 56 | "Or, if you prefer a more concrete constructor you can define each option ", 57 | "explicitly." 58 | ), 59 | option_name = "mypackage_concrete", # define custom option names 60 | envvar_name = "MYPACKAGE_CONCRETE", # and custom environment variable names 61 | envvar_fn = envvar_is_true() # and use helpers to handle envvar parsing 62 | ) 63 | ``` 64 | 65 | # Documentation 66 | 67 | As long as the options have been created as shown above, documenting your 68 | options is as easy as adding this small roxygen stub within your package. 69 | 70 | ```{r as_roxygen_docs} 71 | #' @eval options::as_roxygen_docs() 72 | NULL 73 | ``` 74 | 75 | Which produces a `?mypackage::options` help page. Moreover, any `options` page 76 | will show up in an index when using `?options` to search for documentation, 77 | making it easier to discover which packages have options for you to use. 78 | 79 | ``` 80 | mypackage Options 81 | 82 | Description: 83 | 84 | Internally used, package-specific options. All options will 85 | prioritize R options() values, and fall back to environment 86 | variables if undefined. If neither the option nor the environment 87 | variable is set, a default value is used. 88 | 89 | Options: 90 | 91 | quiet 92 | This is an example of how a package author would document their 93 | internally used options. This option could make the package default to 94 | executing quietly. 95 | 96 | default: 97 | 98 | TRUE 99 | 100 | option: mypackage.quiet 101 | 102 | envvar: R_MYPACKAGE_QUIET (raw) 103 | ... 104 | ``` 105 | 106 | When your options are used as default values to parameters, you can use the 107 | option documentation to populate your function parameter docs. 108 | 109 | This is made simple when all of your parameters share the same names as your 110 | options. 111 | 112 | ```{r as_params} 113 | #' @eval options::as_params() 114 | #' @name options_params 115 | #' 116 | NULL 117 | 118 | #' Count to Three 119 | #' 120 | #' @inheritParams option_params 121 | #' 122 | count_to_three <- function(quiet = opt("quiet")) { 123 | for (i in 1:3) if (!quiet) cat(i, "\n") 124 | } 125 | ``` 126 | 127 | In situations where you have identically named parameters where you _don't_ want 128 | to inherit the option documentation, you can provide their names to `as_params` 129 | to use just a subset of options. You can also reassign documentation for an 130 | option to a parameter of a different name. 131 | 132 | ```{r as_params_renamed} 133 | #' Hello World! 134 | #' 135 | #' @eval options::as_params("silent" = "quiet") 136 | #' 137 | hello <- function(who, silent = opt("quiet")) { 138 | cat(paste0("Hello, ", who, "!"), "\n") 139 | } 140 | ``` 141 | 142 | # Customizing Behaviors 143 | 144 | When using `define_option` you can set the `option_name` and `envvar_name` that 145 | will be used directly. 146 | 147 | But it can be tedious and typo-prone to write these out for each and every 148 | option. Instead, you might consider providing a function that sets the default 149 | format for your option and environment variable names. 150 | 151 | For this, you can use `set_option_name_fn` and `set_envvar_name_fn`, which each 152 | accept a function as an argument. This function accepts two arguments, a 153 | package name and internal option name, which it uses to produce and return the 154 | corresponding global option name or environment variable name. 155 | 156 | ```{r set_name_fn} 157 | options::set_option_name_fn(function(package, name) { 158 | tolower(paste0(package, ".", name)) 159 | }) 160 | 161 | options::set_envvar_name_fn(function(package, name) { 162 | gsub("[^A-Z0-9]", "_", toupper(paste0(package, "_", name))) 163 | }) 164 | ``` 165 | --------------------------------------------------------------------------------
") 280 | } 281 | 282 | #' @name format_value 283 | format_value.call <- function(x, ..., fmt = options_fmts()) { 284 | fmt$shorthand("") 285 | } 286 | 287 | #' @name format_value 288 | format_value.name <- function(x, ..., fmt = options_fmts()) { 289 | name <- as.character(x) 290 | if (nchar(name) == 0) { 291 | return(fmt$shorthand("")) 292 | } 293 | fmt$shorthand(paste0("`", name, "`")) 294 | } 295 | 296 | #' @name format_value 297 | format_value.symbol <- format_value.name 298 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' Grab package name, at runtime 2 | #' 3 | #' Lazily grab `packageName()` within calling environment, not within function 4 | #' environment. 5 | #' 6 | #' @param env An environment in which to search for a package name 7 | #' @return A package name or "globalenv" if not found 8 | #' 9 | #' @importFrom utils packageName 10 | #' @keywords internal 11 | pkgname <- function(env = parent.frame()) { 12 | pkg <- utils::packageName(env) 13 | if (is.null(pkg)) "globalenv" else pkg 14 | } 15 | 16 | 17 | `%||%` <- function(lhs, rhs) if (is.null(lhs)) rhs else lhs 18 | 19 | vlapply <- function(..., FUN.VALUE = logical(1L)) { # nolint object_name_linter 20 | vapply(..., FUN.VALUE = FUN.VALUE) 21 | } 22 | 23 | vcapply <- function(..., FUN.VALUE = character(1L)) { # nolint object_name_linter 24 | vapply(..., FUN.VALUE = FUN.VALUE) 25 | } 26 | 27 | #' @keywords internal 28 | as_env <- function(x) { 29 | UseMethod("as_env") 30 | } 31 | 32 | #' @keywords internal 33 | as_env.character <- function(x) { 34 | getNamespace(x) 35 | } 36 | 37 | #' @keywords internal 38 | as_env.environment <- function(x) { 39 | x 40 | } 41 | 42 | list_is_all_named <- function(x) { 43 | !(is.null(names(x)) || "" %in% names(x)) 44 | } 45 | 46 | list_is_all_unnamed <- function(x) { 47 | is.null(names(x)) || all(names(x) == "") 48 | } 49 | 50 | #' @keywords internal 51 | raise <- function(x, ...) { 52 | UseMethod("raise") 53 | } 54 | 55 | #' @keywords internal 56 | raise.character <- function(x, ...) { 57 | x <- switch(x, 58 | "print" = , 59 | "info" = , 60 | "message" = message, 61 | "warn" = , 62 | "warning" = warning, 63 | "error" = , 64 | "stop" = stop 65 | ) 66 | 67 | raise.function(x, ...) 68 | } 69 | 70 | #' @keywords internal 71 | raise.function <- function(x, msg, ...) { 72 | args <- list(msg, ...) 73 | 74 | if (!"call." %in% names(args) && "call." %in% names(formals(x))) { 75 | args[["call."]] <- FALSE 76 | } 77 | 78 | do.call(x, args) 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `options` 2 | 3 | [](https://cran.r-project.org/package=options) 4 | [](https://ci.codeberg.org/repos/13756) 5 | [](https://app.codecov.io/gh/dgkf/options) 6 | [](https://cran.r-project.org/package=options) 7 | [](https://matrix.to/#/#r-pkg-options:matrix.org) 8 | 9 | _Simple, Consistent Package Options_ 10 | 11 | If you've exposed options from a package before, you've inevitably re-written 12 | one or more pieces of trivial boilerplate code: 13 | 14 | - Prefixing option names with some sort of package namespace 15 | - Building your own option documentation 16 | - Preferentially using a default value, global options or environment variables 17 | - Parsing of environment variables into useful R data 18 | 19 | `options` aims to make these things easy, without having to copy around 20 | boilerplate code. 21 | 22 | ## Quick Start 23 | 24 | ### Defining Options 25 | 26 | Define your options using the `define_options` shorthand. Interlace descriptions 27 | and default values to define multiple options at once. 28 | 29 | ```r 30 | #' @import options 31 | options::define_options( 32 | "This is an example of how a package author would document their internally 33 | used options. This option could make the package default to executing 34 | quietly.", 35 | quiet = TRUE, 36 | 37 | "Multiple options can be defined, providing default values if a global option 38 | or environment variable isn't set.", 39 | second_example = FALSE, 40 | 41 | "Default values are lazily evaluated, so you are free to use package functions 42 | without worrying about build-time evaluation order", 43 | lazy_example = fn_not_defined_until_later() 44 | ) 45 | ``` 46 | 47 | When you want more control, you can use `define_option` to declare all aspects 48 | of how your option behaves. 49 | 50 | ```r 51 | options::define_option( 52 | option = "concrete_example", 53 | default = TRUE, 54 | desc = paste0( 55 | "Or, if you prefer a more concrete constructor you can define each option ", 56 | "explicitly." 57 | ), 58 | option_name = "mypackage_concrete", # define custom option names 59 | envvar_name = "MYPACKAGE_CONCRETE", # and custom environment variable names 60 | envvar_fn = options::envvar_is_true() # and use helpers to handle envvar parsing 61 | ) 62 | ``` 63 | 64 | ### Documentation 65 | 66 | As long as the options have been created as shown above, documenting your 67 | options is as easy as adding this small roxygen stub within your package. 68 | 69 | ```r 70 | #' @eval options::as_roxygen_docs() 71 | NULL 72 | ``` 73 | 74 | Produces `?mypackage::options` 75 | 76 | ``` 77 | mypackage Options 78 | 79 | Description: 80 | 81 | Internally used, package-specific options. All options will 82 | prioritize R options() values, and fall back to environment 83 | variables if undefined. If neither the option nor the environment 84 | variable is set, a default value is used. 85 | 86 | Options: 87 | 88 | quiet 89 | This is an example of how a package author would document their 90 | internally used options. This option could make the package default to 91 | executing quietly. 92 | 93 | default: 94 | 95 | TRUE 96 | 97 | option: mypackage.quiet 98 | 99 | envvar: R_MYPACKAGE_QUIET (raw) 100 | ... 101 | ``` 102 | 103 | When your options are used as default values to parameters, you can use the 104 | option documentation to populate your function parameter docs. 105 | 106 | This is made simple when all of your parameters share the same names as your 107 | options. 108 | 109 | ```r 110 | #' @eval options::as_params() 111 | #' @name options_params 112 | #' 113 | NULL 114 | 115 | #' Count to Three 116 | #' 117 | #' @inheritParams option_params 118 | #' 119 | count_to_three <- function(quiet = opt("quiet")) { 120 | for (i in 1:3) if (!quiet) cat(i, "\n") 121 | } 122 | ``` 123 | 124 | In situations where you have identically named parameters where you _don't_ want 125 | to inherit the option documentation, you can provide their names to `as_params` 126 | to use just a subset of options. You can also reassign documentation for an 127 | option to a parameter of a different name. 128 | 129 | ```r 130 | #' Hello World! 131 | #' 132 | #' @eval options::as_params("silent" = "quiet") 133 | #' 134 | hello <- function(who, silent = opt("quiet")) { 135 | cat(paste0("Hello, ", who, "!"), "\n") 136 | } 137 | ``` 138 | 139 | ### Customizing Behaviors 140 | 141 | When using `define_option` you can set the `option_name` and `envvar_name` that 142 | will be used directly. 143 | 144 | But it can be tedious and typo-prone to write these out for each and every 145 | option. Instead, you might consider providing a function that sets the default 146 | format for your option and environment variable names. 147 | 148 | For this, you can use `set_option_name_fn` and `set_envvar_name_fn`, which each 149 | accept a function as an argument. This function accepts two arguments, a 150 | package name and internal option name, which it uses to produce and return the 151 | corresponding global option name or environment variable name. 152 | 153 | ```r 154 | options::set_option_name_fn(function(package, name) { 155 | tolower(paste0(package, ".", name)) 156 | }) 157 | 158 | options::set_envvar_name_fn(function(package, name) { 159 | gsub("[^A-Z0-9]", "_", toupper(paste0(package, "_", name))) 160 | }) 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /inst/options.example/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: options.example 2 | Version: 0.0.0.9000 3 | Title: Showcase of options package 4 | Description: Showcase of options package. 5 | License: file LICENSE 6 | Authors@R: 7 | person( 8 | "authors of `options`", 9 | email = "authors@optio.ns", 10 | role = c("aut", "cre") 11 | ) 12 | Imports: 13 | options 14 | Encoding: UTF-8 15 | Roxygen: list(markdown = TRUE) 16 | RoxygenNote: 7.2.3 17 | -------------------------------------------------------------------------------- /inst/options.example/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 authors of `options`. All rights reserved. 2 | -------------------------------------------------------------------------------- /inst/options.example/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(fizzbuzz) 4 | export(hello) 5 | export(show_option) 6 | import(options) 7 | -------------------------------------------------------------------------------- /inst/options.example/R/helpers.R: -------------------------------------------------------------------------------- 1 | #' Show an option value as it would be used within this package 2 | #' 3 | #' Explore what value is picked up. You can try setting environment variables or 4 | #' global options to see how they affect the internally used option value. 5 | #' 6 | #' @param x An option name to explore 7 | #' @inheritDotParams options::opt 8 | #' 9 | #' @return the option value as it would be discovered inside this package 10 | #' 11 | #' @examples 12 | #' show_option("quiet") 13 | #' # [1] FALSE 14 | #' 15 | #' Sys.setenv(R_OPTIONS_EXAMPLE_QUIET = 3) 16 | #' show_option("quiet") 17 | #' # [1] "3" 18 | #' 19 | #' options(options.example.quiet = TRUE) 20 | #' show_option("quiet") 21 | #' # [1] TRUE 22 | #' 23 | #' @export 24 | show_option <- function(x, ...) { 25 | opt(x, ...) 26 | } 27 | -------------------------------------------------------------------------------- /inst/options.example/R/options.R: -------------------------------------------------------------------------------- 1 | #' @import options 2 | 3 | 4 | # 5 | # Although it is nice to be consistent with the defaults, you may find that you 6 | # prefer to format your environment variables and option names differently. 7 | # 8 | # You can assign global callbacks that will be used to derive new option names. 9 | # 10 | 11 | options::set_envvar_name_fn(function(package, name) { 12 | gsub("[^a-z0-9]", "_", tolower(paste0(c("R", package, name), collapse = "_"))) 13 | }) 14 | 15 | options::set_option_name_fn(function(package, name) { 16 | tolower(paste0(package, ".", name)) 17 | }) 18 | 19 | 20 | 21 | options::define_options( 22 | "Define options within your package. Using `define` shorthand syntax 23 | will auto-generate global R option names and environment variables names to 24 | fall back to.", 25 | use_options = TRUE, 26 | 27 | "Additional options can be chained together. They're also only evaluated 28 | on first use, so you're free to use package functions without worrying about 29 | order of execution.", 30 | another_option = fizzbuzz(1:15), 31 | 32 | "Whether printing should be quiet or loud. See how it's used throughout this 33 | package!", 34 | quiet = TRUE 35 | ) 36 | 37 | options::define_option( 38 | "detailed", 39 | default = FALSE, 40 | desc = paste0( 41 | "An example of a more detailed mechanism of defining options, which gives ", 42 | "further control over option names, environment variable names and how ", 43 | "environment variables are internalized as values." 44 | ), 45 | envvar_fn = options::envvar_is_set() 46 | ) 47 | -------------------------------------------------------------------------------- /inst/options.example/R/roxygen2.R: -------------------------------------------------------------------------------- 1 | #' @eval options::as_roxygen_docs() 2 | NULL 3 | 4 | 5 | #' Generated Package Options as Params 6 | #' 7 | #' Here we create a small stub of a function. It's used to inherit parameters 8 | #' from throughout the package. With this function as a basis, you can use 9 | #' roxygen2's `@inheritParams` tag to document any parameters that share the 10 | #' name with an option. 11 | #' 12 | #' @eval options::as_params() 13 | #' @name options_params 14 | #' 15 | NULL 16 | 17 | #' Hello, World! 18 | #' 19 | #' We use the stub function above to inherit parameter definitions. 20 | #' 21 | #' @inheritParams options_params 22 | #' 23 | #' @export 24 | hello <- function(quiet = opt("quiet")) { 25 | str <- "hello, world!" 26 | if (!quiet) str <- toupper(str) 27 | cat(str, "\n") 28 | } 29 | 30 | 31 | #' FizzBuzz 32 | #' 33 | #' You can also cherry-pick options that you want to use, or rename them to 34 | #' suite your functions. 35 | #' 36 | #' @param xs a numeric vector of values to consider 37 | #' @param m1 "fizz" dividend 38 | #' @param m2 "buzz" dividend 39 | #' @eval options::as_params("silent" = "quiet") 40 | #' 41 | #' @export 42 | fizzbuzz <- function(xs, m1 = 3, m2 = 5, silent = opt("quiet")) { 43 | out <- paste0( 44 | ifelse(xs %% m1 == 0, "fizz", ""), 45 | ifelse(xs %% m2 == 0, "buzz", "") 46 | ) 47 | 48 | if (!silent) print(out) 49 | 50 | out 51 | } 52 | -------------------------------------------------------------------------------- /inst/options.example/man/fizzbuzz.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{fizzbuzz} 4 | \alias{fizzbuzz} 5 | \title{FizzBuzz} 6 | \usage{ 7 | fizzbuzz(xs, m1 = 3, m2 = 5, silent = opt("quiet")) 8 | } 9 | \arguments{ 10 | \item{xs}{a numeric vector of values to consider} 11 | 12 | \item{m1}{"fizz" dividend} 13 | 14 | \item{m2}{"buzz" dividend} 15 | 16 | \item{silent}{Whether printing should be quiet or loud. See how it's used throughout this 17 | package! (Defaults to \code{TRUE}, overwritable using option 'options.example.quiet' or environment variable 'r_options_example_quiet')} 18 | } 19 | \description{ 20 | You can also cherry-pick options that you want to use, or rename them to 21 | suite your functions. 22 | } 23 | -------------------------------------------------------------------------------- /inst/options.example/man/hello.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{hello} 4 | \alias{hello} 5 | \title{Hello, World!} 6 | \usage{ 7 | hello(quiet = opt("quiet")) 8 | } 9 | \arguments{ 10 | \item{quiet}{Whether printing should be quiet or loud. See how it's used throughout this 11 | package! (Defaults to \code{TRUE}, overwritable using option 'options.example.quiet' or environment variable 'r_options_example_quiet')} 12 | } 13 | \description{ 14 | We use the stub function above to inherit parameter definitions. 15 | } 16 | -------------------------------------------------------------------------------- /inst/options.example/man/options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{options} 4 | \alias{options} 5 | \title{options.example Options} 6 | \description{ 7 | Internally used, package-specific options. All options will prioritize R options() values, and fall back to environment variables if undefined. If neither the option nor the environment variable is set, a default value is used. 8 | } 9 | \section{Checking Option Values}{ 10 | 11 | Option values specific to \code{options.example} can be 12 | accessed by passing the package name to \code{env}. 13 | 14 | \if{html}{\out{}}\preformatted{options::opts(env = "options.example") 15 | 16 | options::opt(x, default, env = "options.example") 17 | }\if{html}{\out{}} 18 | } 19 | 20 | \section{Options}{ 21 | 22 | \describe{ 23 | \item{detailed}{\describe{ 24 | An example of a more detailed mechanism of defining options, which gives further control over option names, environment variable names and how environment variables are internalized as values.\item{default: }{\preformatted{FALSE}} 25 | \item{option: }{options.example.detailed} 26 | \item{envvar: }{r_options_example_detailed (TRUE if set, FALSE otherwise)} 27 | }} 28 | 29 | \item{use_options}{\describe{ 30 | Define options within your package. Using \code{define} shorthand syntax 31 | will auto-generate global R option names and environment variables names to 32 | fall back to.\item{default: }{\preformatted{TRUE}} 33 | \item{option: }{options.example.use_options} 34 | \item{envvar: }{r_options_example_use_options (evaluated if possible, raw string otherwise)} 35 | }} 36 | 37 | \item{another_option}{\describe{ 38 | Additional options can be chained together. They're also only evaluated 39 | on first use, so you're free to use package functions without worrying about 40 | order of execution.\item{default: }{\preformatted{fizzbuzz(1:15)}} 41 | \item{option: }{options.example.another_option} 42 | \item{envvar: }{r_options_example_another_option (evaluated if possible, raw string otherwise)} 43 | }} 44 | 45 | \item{quiet}{\describe{ 46 | Whether printing should be quiet or loud. See how it's used throughout this 47 | package!\item{default: }{\preformatted{TRUE}} 48 | \item{option: }{options.example.quiet} 49 | \item{envvar: }{r_options_example_quiet (evaluated if possible, raw string otherwise)} 50 | }} 51 | 52 | } 53 | } 54 | 55 | \seealso{ 56 | options getOption Sys.getenv Sys.getenv 57 | } 58 | -------------------------------------------------------------------------------- /inst/options.example/man/options_params.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/roxygen2.R 3 | \name{options_params} 4 | \alias{options_params} 5 | \title{Generated Package Options as Params} 6 | \arguments{ 7 | \item{detailed}{An example of a more detailed mechanism of defining options, which gives further control over option names, environment variable names and how environment variables are internalized as values. (Defaults to \code{FALSE}, overwritable using option 'options.example.detailed' or environment variable 'r_options_example_detailed')} 8 | 9 | \item{use_options}{Define options within your package. Using \code{define} shorthand syntax 10 | will auto-generate global R option names and environment variables names to 11 | fall back to. (Defaults to \code{TRUE}, overwritable using option 'options.example.use_options' or environment variable 'r_options_example_use_options')} 12 | 13 | \item{another_option}{Additional options can be chained together. They're also only evaluated 14 | on first use, so you're free to use package functions without worrying about 15 | order of execution. (Defaults to \code{fizzbuzz(1:15)}, overwritable using option 'options.example.another_option' or environment variable 'r_options_example_another_option')} 16 | 17 | \item{quiet}{Whether printing should be quiet or loud. See how it's used throughout this 18 | package! (Defaults to \code{TRUE}, overwritable using option 'options.example.quiet' or environment variable 'r_options_example_quiet')} 19 | } 20 | \description{ 21 | Here we create a small stub of a function. It's used to inherit parameters 22 | from throughout the package. With this function as a basis, you can use 23 | roxygen2's \verb{@inheritParams} tag to document any parameters that share the 24 | name with an option. 25 | } 26 | -------------------------------------------------------------------------------- /inst/options.example/man/show_option.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{show_option} 4 | \alias{show_option} 5 | \title{Show an option value as it would be used within this package} 6 | \usage{ 7 | show_option(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{An option name to explore} 11 | 12 | \item{...}{ 13 | Arguments passed on to \code{\link[options:opt]{options::opt}} 14 | \describe{ 15 | \item{\code{default}}{A default value if the option is not set} 16 | \item{\code{env}}{An environment, namespace or package name to pull options from} 17 | }} 18 | } 19 | \value{ 20 | the option value as it would be discovered inside this package 21 | } 22 | \description{ 23 | Explore what value is picked up. You can try setting environment variables or 24 | global options to see how they affect the internally used option value. 25 | } 26 | \examples{ 27 | show_option("quiet") 28 | # [1] FALSE 29 | 30 | Sys.setenv(R_OPTIONS_EXAMPLE_QUIET = 3) 31 | show_option("quiet") 32 | # [1] "3" 33 | 34 | options(options.example.quiet = TRUE) 35 | show_option("quiet") 36 | # [1] TRUE 37 | 38 | } 39 | -------------------------------------------------------------------------------- /man/as_params.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_roxygen.R 3 | \name{as_params} 4 | \alias{as_params} 5 | \title{Produce \verb{@param} roxygen sections for options} 6 | \usage{ 7 | as_params(...) 8 | } 9 | \arguments{ 10 | \item{...}{Character values of options to use. If named arguments are 11 | provided, the option description provided as the value is mapped to a 12 | parameter of the argument's name.} 13 | } 14 | \value{ 15 | A character vector of \code{roxygen2} \verb{@param} tags 16 | } 17 | \description{ 18 | Generate parameter documentation based on option behaviors. Especially useful 19 | for ubiquitous function parameters that default to option values. 20 | } 21 | \examples{ 22 | options::define_options( 23 | "whether messages should be written softly, or in all-caps", 24 | quiet = TRUE 25 | ) 26 | 27 | #' Hello, World 28 | #' 29 | #' @eval options::as_params("softly" = "quiet") 30 | #' 31 | hello <- function(who, softly = opt("quiet")) { 32 | say_what <- paste0("Hello, ", who, "!") 33 | if (quiet) say_what else toupper(say_what) 34 | } 35 | 36 | } 37 | \seealso{ 38 | Other options_roxygen2: 39 | \code{\link{as_roxygen_docs}()} 40 | } 41 | \concept{options_roxygen2} 42 | \keyword{roxygen2} 43 | -------------------------------------------------------------------------------- /man/as_roxygen_docs.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_roxygen.R 3 | \name{as_roxygen_docs} 4 | \alias{as_roxygen_docs} 5 | \title{Generate Standalone \code{?options} Documentation} 6 | \usage{ 7 | as_roxygen_docs( 8 | title = paste(pkgname(env), "Options"), 9 | desc = default_options_rd_desc(), 10 | env = parent.frame() 11 | ) 12 | } 13 | \arguments{ 14 | \item{title}{An optional, customized title (defaults to "Options")} 15 | 16 | \item{desc}{An optional, customized description of behaviors} 17 | 18 | \item{env}{An environemnt in which to find the associated options object} 19 | } 20 | \value{ 21 | A character vector of \code{roxygen2} tag segments 22 | } 23 | \description{ 24 | Produce a comprehensive documentation page outlining all your defined 25 | options' behaviors. 26 | } 27 | \examples{ 28 | #' @eval options::as_roxygen_docs() 29 | NULL 30 | 31 | } 32 | \seealso{ 33 | Other options_roxygen2: 34 | \code{\link{as_params}()} 35 | } 36 | \concept{options_roxygen2} 37 | \keyword{roxygen2} 38 | -------------------------------------------------------------------------------- /man/assert_naming_fn_signature.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/naming.R 3 | \name{assert_naming_fn_signature} 4 | \alias{assert_naming_fn_signature} 5 | \title{Assert signature for naming functions} 6 | \usage{ 7 | assert_naming_fn_signature(fn) 8 | } 9 | \arguments{ 10 | \item{fn}{A function to inspect} 11 | } 12 | \description{ 13 | Assert signature for naming functions 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/defining_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/define_option.R 3 | \name{defining_options} 4 | \alias{defining_options} 5 | \alias{define_option} 6 | \alias{define_options} 7 | \title{Defining Options} 8 | \usage{ 9 | define_option(option, ...) 10 | 11 | define_options(...) 12 | } 13 | \arguments{ 14 | \item{option}{An option name to use} 15 | 16 | \item{...}{Additional arguments passed to \code{\link[=option_spec]{option_spec()}}} 17 | } 18 | \value{ 19 | the package options environment 20 | } 21 | \description{ 22 | Define options which can be used throughout your package. 23 | } 24 | \details{ 25 | At their simplest, defining options lets you refer to a global option using a 26 | shorthand option name throughout your package, with the added benefit of 27 | looking for configurations in global options and environment variables. 28 | } 29 | \section{Functions}{ 30 | \itemize{ 31 | \item \code{define_option()}: Define an option. Unlike \code{\link[=define_options]{define_options()}}, this function allows detailed 32 | customization of all option behaviors. Accepts either an \code{\link[=option_spec]{option_spec()}} 33 | object, or an option named followed by arguments to provide to 34 | \code{\link[=option_spec]{option_spec()}}. 35 | 36 | \item \code{define_options()}: Define multiple options. This function provides a shorthand syntax for 37 | succinctly defining many options. Arguments are defined in groups, each 38 | starting with an unnamed description argument. For more details see Section 39 | \emph{Non-Standard Evaluation}. 40 | 41 | }} 42 | \section{Non-Standard Evaluation}{ 43 | 44 | 45 | \code{define_options()} accepts arguments in a \emph{non-standard} 46 | way, as groups of arguments which each are used to specify an option (See 47 | \code{options_spec()}). Groups of arguments must start with an unnamed argument, 48 | which provides the description for the argument, followed immediately by a 49 | named argument providing the name of option and default value, followed by 50 | any additional arguments to provie to \code{options_spec()}. 51 | 52 | The environment in which options are defined is always assumed to be the 53 | parent environment. If you'd prefer to specify options in a different 54 | environment, this is best done using \code{define_option()} or 55 | \verb{with(, define_options(...))}. 56 | 57 | Although \code{define_options()} provides all the functionality of 58 | \code{define_option()} in a succinct shorthand, it is only recommended in cases 59 | where the overwhelming majority of your options leverage default behaviors. 60 | It is encouraged to use \code{define_option()} if you repeatedly need more 61 | involved definitions to minimize non-standard evaluation bugs. 62 | } 63 | 64 | \examples{ 65 | define_options( 66 | "Whether execution should emit console output", 67 | quiet = FALSE, 68 | "Whether to use detailed console output (showcasing additional 69 | configuration parameters)", 70 | verbose = TRUE, 71 | envvar_fn = envvar_is_true() 72 | ) 73 | 74 | define_option( 75 | "deprecations", 76 | desc = "Whether deprecation warnings should be suppressed automatically", 77 | default = FALSE, 78 | option_name = "MypackageDeprecations", 79 | envvar_name = "MYPACKAGE_ENVVARS_DEPRECATIONS" 80 | ) 81 | 82 | } 83 | -------------------------------------------------------------------------------- /man/envvar_fns.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/envvars.R 3 | \name{envvar_fns} 4 | \alias{envvar_fns} 5 | \alias{envvar_is} 6 | \alias{envvar_is.NULL} 7 | \alias{envvar_is.character} 8 | \alias{envvar_is.numeric} 9 | \alias{envvar_is.logical} 10 | \alias{envvar_eval} 11 | \alias{envvar_eval_or_raw} 12 | \alias{envvar_is_one_of} 13 | \alias{envvar_choice_of} 14 | \alias{envvar_is_true} 15 | \alias{envvar_is_false} 16 | \alias{envvar_is_set} 17 | \alias{envvar_str_split} 18 | \title{Generator functions for environment variable processors} 19 | \usage{ 20 | envvar_is(value, ...) 21 | 22 | \method{envvar_is}{`NULL`}(value, case_sensitive = FALSE, ...) 23 | 24 | \method{envvar_is}{character}(value, case_sensitive = FALSE, ...) 25 | 26 | \method{envvar_is}{numeric}(value, ...) 27 | 28 | \method{envvar_is}{logical}(value, case_sensitive = FALSE, ...) 29 | 30 | envvar_eval(...) 31 | 32 | envvar_eval_or_raw(...) 33 | 34 | envvar_is_one_of(values, ...) 35 | 36 | envvar_choice_of(values, default = NULL, ...) 37 | 38 | envvar_is_true(...) 39 | 40 | envvar_is_false(...) 41 | 42 | envvar_is_set(...) 43 | 44 | envvar_str_split(delim = ";", ...) 45 | } 46 | \arguments{ 47 | \item{value}{A value to test against} 48 | 49 | \item{...}{Other arguments unused} 50 | 51 | \item{case_sensitive}{A logical value indicating whether string comparisons 52 | should be case-sensitive.} 53 | 54 | \item{values}{A list or vector of values to match} 55 | 56 | \item{default}{A default value used when a value cannot be coerced from the 57 | environment variable value} 58 | 59 | \item{delim}{A character value to use as a delimiter to use when splitting 60 | the environment variable value} 61 | } 62 | \value{ 63 | A function to be used for processing an environment variable value 64 | } 65 | \description{ 66 | These functions return environment variable processor functions. Arguments to 67 | them are used to specify behaviors. 68 | } 69 | \section{Functions}{ 70 | \itemize{ 71 | \item \code{envvar_is()}: Test for equality with handlers for most atomic R types, performing sensible 72 | interpretation of environment variable values. 73 | 74 | \item \code{envvar_is(`NULL`)}: environment variable has value \code{"null"} 75 | 76 | \item \code{envvar_is(character)}: environment variable is equal to string \code{value} 77 | 78 | \item \code{envvar_is(numeric)}: environment variable is equal to string representation of numeric \code{value} 79 | 80 | \item \code{envvar_is(logical)}: environment variable is equal to string representation of logical \code{value} 81 | 82 | \item \code{envvar_eval()}: Parse the environment variable value as R code and and evaluate it to 83 | produce a return value, emitting an error if the expression fails to parse 84 | or evaluate. This option is a sensible default for most R-specific 85 | environment variables, but may fail for string literals, and meaningful 86 | values that don't conform to R's syntax like \verb{"true}" (see 87 | \code{\link[=envvar_is_true]{envvar_is_true()}}), \code{"false"} (see \code{\link[=envvar_is_false]{envvar_is_false()}}) or \code{"null"}. 88 | 89 | \item \code{envvar_eval_or_raw()}: Parse the environment variable value as R code and and evaluate it to 90 | produce a return value, or falling back to the raw value as a string if an 91 | error occurs. 92 | 93 | \item \code{envvar_is_one_of()}: For meaningful string comparisons, check whether the environment variable is 94 | equal to some meaningful string. Optionally with case-sensitivity. 95 | 96 | \item \code{envvar_choice_of()}: Check whether environment variable can be coerced to match one of \code{values}, 97 | returning the value if it matches or \code{default} otherwise. 98 | 99 | \item \code{envvar_is_true()}: Test whether the environment variable is "truthy", that is whether it is 100 | case-insensitive \code{"true"} or \code{1} 101 | 102 | \item \code{envvar_is_false()}: Test whether the environment variable is "falsy", that is whether it is 103 | case-insensitive \code{"false"} or \code{0} 104 | 105 | \item \code{envvar_is_set()}: Test whether the environment variable is set. This is somewhat 106 | operating-system dependent, as not all operating systems can distinguish 107 | between an empty string as a value and an unset environment variable. For 108 | details see \code{\link[=Sys.getenv]{Sys.getenv()}}'s Details about its \code{unset} parameter. 109 | 110 | \item \code{envvar_str_split()}: Interpret the environment variable as a delimited list of strings, such as 111 | \code{PATH} variables. 112 | 113 | }} 114 | \keyword{envvar_parsers} 115 | -------------------------------------------------------------------------------- /man/err.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/errors.R 3 | \name{err} 4 | \alias{err} 5 | \title{Raise a package error} 6 | \usage{ 7 | err(title, issues = list(), which = 0) 8 | } 9 | \arguments{ 10 | \item{title}{A title for the error} 11 | 12 | \item{issues}{An optionally named list of issues to associate with the error. 13 | When named, the issues are first sorted by issue name.} 14 | 15 | \item{which}{A relative frame to use to build the associated call} 16 | } 17 | \value{ 18 | An options_error class 19 | } 20 | \description{ 21 | Raise a package error 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/format.option_spec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{format.option_spec} 4 | \alias{format.option_spec} 5 | \title{Format an option specification} 6 | \usage{ 7 | \method{format}{option_spec}(x, value, ..., fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{x}{An option specification ("option_spec") class object} 11 | 12 | \item{value}{Optionally, the current value to display for the option being 13 | specified} 14 | 15 | \item{...}{Additional arguments unused} 16 | 17 | \item{fmt}{A list of formats to use for formatting individual text elements} 18 | } 19 | \value{ 20 | A formatted character value 21 | } 22 | \description{ 23 | Format an option specification 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/format.options_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{format.options_env} 4 | \alias{format.options_env} 5 | \title{Format an options environment} 6 | \usage{ 7 | \method{format}{options_env}(x, ..., fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{x}{An option environment ("option_env") class object} 11 | 12 | \item{...}{Additional arguments unused} 13 | 14 | \item{fmt}{A list of formats to use for formatting individual text elements} 15 | } 16 | \value{ 17 | A formatted character value 18 | } 19 | \description{ 20 | Format an options environment 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/format.options_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{format.options_list} 4 | \alias{format.options_list} 5 | \title{Format an options list} 6 | \usage{ 7 | \method{format}{options_list}(x, ..., fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{x}{An option list ("option_list") class object} 11 | 12 | \item{...}{Additional arguments unused} 13 | 14 | \item{fmt}{A list of formats to use for formatting individual text elements} 15 | } 16 | \value{ 17 | A formatted character value 18 | } 19 | \description{ 20 | Format an options list 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/format_field.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{format_field} 4 | \alias{format_field} 5 | \title{Format a possible option source} 6 | \usage{ 7 | format_field(field, active, value, fmt = options_fmts()) 8 | } 9 | \arguments{ 10 | \item{field}{The field for the option source} 11 | 12 | \item{active}{Whether this source is the source of the option's value} 13 | 14 | \item{value}{The value from this source that was used} 15 | 16 | \item{fmt}{A list of formats to use for formatting individual text elements} 17 | } 18 | \value{ 19 | A formatted character value 20 | } 21 | \description{ 22 | Format a possible option source 23 | } 24 | \keyword{internal} 25 | -------------------------------------------------------------------------------- /man/format_value.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{format_value} 4 | \alias{format_value} 5 | \alias{format_value.default} 6 | \alias{format_value.S3} 7 | \alias{format_value.S4} 8 | \alias{format_value.function} 9 | \alias{format_value.environment} 10 | \alias{format_value.expression} 11 | \alias{format_value.quote} 12 | \alias{format_value.call} 13 | \alias{format_value.name} 14 | \alias{format_value.symbol} 15 | \title{Format value shorthands for command line display} 16 | \usage{ 17 | format_value(x, ..., fmt = NULL) 18 | 19 | \method{format_value}{default}(x, ..., fmt = options_fmts()) 20 | 21 | \method{format_value}{S3}(x, ..., fmt = options_fmts()) 22 | 23 | \method{format_value}{S4}(x, ..., fmt = options_fmts()) 24 | 25 | \method{format_value}{`function`}(x, ..., fmt = options_fmts()) 26 | 27 | \method{format_value}{environment}(x, ..., fmt = options_fmts()) 28 | 29 | \method{format_value}{expression}(x, ..., fmt = options_fmts()) 30 | 31 | \method{format_value}{quote}(x, ..., fmt = options_fmts()) 32 | 33 | \method{format_value}{call}(x, ..., fmt = options_fmts()) 34 | 35 | \method{format_value}{name}(x, ..., fmt = options_fmts()) 36 | 37 | \method{format_value}{symbol}(x, ..., fmt = options_fmts()) 38 | } 39 | \arguments{ 40 | \item{x}{An R object to display, attempting to show the actual value, but 41 | falling back to shorthands for more complex data types.} 42 | 43 | \item{...}{Additional arguments unused} 44 | 45 | \item{fmt}{A list of formats to use for formatting individual text elements} 46 | } 47 | \value{ 48 | A formatted character value 49 | } 50 | \description{ 51 | Format value shorthands for command line display 52 | } 53 | \keyword{internal} 54 | -------------------------------------------------------------------------------- /man/get_options_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{get_options_env} 4 | \alias{get_options_env} 5 | \alias{get_options_env.options_env} 6 | \alias{get_options_env.options_list} 7 | \alias{get_options_env.default} 8 | \title{Retrieve options environment (experimental)} 9 | \usage{ 10 | get_options_env(env, ...) 11 | 12 | \method{get_options_env}{options_env}(env, ...) 13 | 14 | \method{get_options_env}{options_list}(env, ...) 15 | 16 | \method{get_options_env}{default}( 17 | env = parent.frame(), 18 | ..., 19 | inherits = FALSE, 20 | ifnotfound = emptyenv() 21 | ) 22 | } 23 | \arguments{ 24 | \item{env}{An environment in which to search for an options environment} 25 | 26 | \item{...}{Additional arguments unused} 27 | 28 | \item{inherits}{Whether to search upward through parent environments} 29 | 30 | \item{ifnotfound}{A result to return of no options environment is found.} 31 | } 32 | \value{ 33 | An environment containing option specifications and default values, 34 | or \code{ifnotfound} if no environment is found. 35 | } 36 | \description{ 37 | The options environment stores metadata regarding the various options 38 | defined in the local scope - often the top environment of a package 39 | namespace. 40 | } 41 | \note{ 42 | This function's public interface is still under consideration. It is 43 | surfaced to provide access to option names, though the exact mechanism 44 | of retrieving these names should be considered experimental. 45 | } 46 | -------------------------------------------------------------------------------- /man/naming.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/naming.R 3 | \name{naming} 4 | \alias{naming} 5 | \alias{set_envvar_name_fn} 6 | \alias{set_option_name_fn} 7 | \title{Define Naming Conventions} 8 | \usage{ 9 | set_envvar_name_fn(fn, env = parent.frame()) 10 | 11 | set_option_name_fn(fn, env = parent.frame()) 12 | } 13 | \arguments{ 14 | \item{fn}{A callback function which expects two arguments, the package name 15 | and option name, and returns a single character value to use as an 16 | environment variable name.} 17 | 18 | \item{env}{An environment in which to search for options settings} 19 | } 20 | \value{ 21 | The callback function \code{fn} 22 | } 23 | \description{ 24 | Option naming conventions use sensible defaults so that you can get started 25 | quickly with minimal configuration. 26 | } 27 | \section{Functions}{ 28 | \itemize{ 29 | \item \code{set_envvar_name_fn()}: Set a callback function to use to format environment variable names. 30 | 31 | \item \code{set_option_name_fn()}: Set a callback function to use to format option names. 32 | 33 | }} 34 | \section{Defaults}{ 35 | 36 | 37 | Given a package \code{mypackage} and option \code{myoption}, the default settings 38 | will generate options and environment variables using the convention: 39 | 40 | option: 41 | 42 | \if{html}{\out{}}\preformatted{mypackage.myoption 43 | }\if{html}{\out{}} 44 | 45 | environment variable: 46 | 47 | \if{html}{\out{}}\preformatted{R_MYPACKAGE_MYOPTION 48 | }\if{html}{\out{}} 49 | 50 | This convention is intended to track closely with how options and environment 51 | variables are handled frequently in the wild. Perhaps in contrast to the 52 | community conventions, an \code{R_} prefix is tacked on to the default environment 53 | variables. This prefix helps to differentiate environment variables when 54 | similarly named tools exist outside of the R ecosystem. 55 | } 56 | 57 | \section{Setting Alternative Conventions}{ 58 | 59 | 60 | If you choose to use alternative naming conventions, you must set the 61 | callback function \emph{before} defining options. This is best achieved by 62 | altering these settings in the file where you define your options. 63 | 64 | If you choose to break up your options across multiple files, then it is best 65 | to define the collate order for your R scripts to ensure that the options are 66 | consistently configured across operating systems. 67 | } 68 | 69 | \examples{ 70 | set_envvar_name_fn(envvar_name_generic) 71 | 72 | set_envvar_name_fn(function(package, name) { 73 | toupper(paste("ENV", package, name, sep = "_")) 74 | }) 75 | 76 | } 77 | \seealso{ 78 | naming_formats 79 | } 80 | \keyword{naming} 81 | -------------------------------------------------------------------------------- /man/naming_formats.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/naming.R 3 | \name{naming_formats} 4 | \alias{naming_formats} 5 | \alias{option_name_default} 6 | \alias{envvar_name_default} 7 | \alias{envvar_name_generic} 8 | \title{Naming Convention Formatters} 9 | \usage{ 10 | option_name_default(package, option) # "package.option" 11 | 12 | envvar_name_default(package, option) # "R_PACKAGE_OPTION" 13 | 14 | envvar_name_generic(package, option) # "PACKAGE_OPTION" 15 | } 16 | \arguments{ 17 | \item{package, option}{The package name and internal option name used for 18 | generating a global R option and environment variable name. As these 19 | functions are often provided as values, their arguments rarely need to be 20 | provided by package authors directly.} 21 | } 22 | \value{ 23 | A character value to use as the global option name or environment 24 | variable name 25 | } 26 | \description{ 27 | This family of functions is used internally to generate global option and 28 | environment variable names from the package name and internal option name. 29 | } 30 | \section{Functions}{ 31 | \itemize{ 32 | \item \code{option_name_default()}: A default naming convention, producing a global R option name from the 33 | package name and internal option name (\code{mypackage.myoption}) 34 | 35 | \item \code{envvar_name_default()}: A default naming convention, producing an environment variable name from the 36 | package name and internal option name (\code{R_MYPACKAGE_MYOPTION}) 37 | 38 | \item \code{envvar_name_generic()}: A generic naming convention, producing an environment variable name from the 39 | package name and internal option name. Useful when a generic convention might 40 | be used to share environment variables with other tools of the same name, or 41 | when you're confident that your R package will not conflict with other tools. 42 | (\code{MYPACKAGE_MYOPTION}) 43 | 44 | }} 45 | \seealso{ 46 | naming 47 | } 48 | \concept{naming_formats} 49 | \keyword{naming_formats} 50 | -------------------------------------------------------------------------------- /man/opt.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_get.R 3 | \name{opt} 4 | \alias{opt} 5 | \alias{opt_set} 6 | \alias{opt<-} 7 | \alias{opt_source} 8 | \alias{opts} 9 | \alias{opt_set_local} 10 | \alias{opts_list} 11 | \title{Inspecting Option Values} 12 | \usage{ 13 | opt(x, default, env = parent.frame(), ...) 14 | 15 | opt_set(x, value, env = parent.frame(), ...) 16 | 17 | opt(x, ...) <- value 18 | 19 | opt_source(x, env = parent.frame()) 20 | 21 | opts(xs = NULL, env = parent.frame()) 22 | 23 | opt_set_local( 24 | x, 25 | value, 26 | env = parent.frame(), 27 | ..., 28 | add = TRUE, 29 | after = FALSE, 30 | scope = parent.frame() 31 | ) 32 | 33 | opts_list( 34 | ..., 35 | env = parent.frame(), 36 | check_names = c("asis", "warn", "error"), 37 | opts = list(...) 38 | ) 39 | } 40 | \arguments{ 41 | \item{x, xs}{An option name, vector of option names, or a named list of new 42 | option values} 43 | 44 | \item{default}{A default value if the option is not set} 45 | 46 | \item{env}{An environment, namespace or package name to pull options from} 47 | 48 | \item{...}{See specific functions to see behavior.} 49 | 50 | \item{value}{A new value to update the associated global option} 51 | 52 | \item{add, after, scope}{Passed to \link{on.exit}, with alternative defaults. 53 | \code{scope} is passed to the \link{on.exit} \code{envir} parameter to disambiguate it 54 | from \code{env}.} 55 | 56 | \item{check_names}{(experimental) A behavior used when checking option 57 | names against specified options. Expects one of \code{"asis"}, \code{"warn"} or 58 | \code{"stop"}.} 59 | 60 | \item{opts}{A \code{list} of values, for use in functions that accept \code{...} 61 | arguments. In rare cases where your argument names conflict with other 62 | named arguments to these functions, you can specify them directly using 63 | this parameter.} 64 | } 65 | \value{ 66 | For \code{opt()} and \code{opts()}; the result of the option (or a list of 67 | results), either the value from a global option, the result of processing 68 | the environment variable or the default value, depending on which of the 69 | alternative sources are defined. 70 | 71 | For modifying functions (\link{opt_set} and \link{opt<-}: the value of the 72 | option prior to modification 73 | 74 | For \link{opt_source}; the source that is used for a specific option, 75 | one of \code{"option"}, \code{"envvar"} or \code{"default"}. 76 | } 77 | \description{ 78 | Inspecting Option Values 79 | } 80 | \section{Functions}{ 81 | \itemize{ 82 | \item \code{opt()}: Retrieve an option. Additional \code{...} arguments passed to an optional 83 | \code{option_fn}. See \code{\link[=option_spec]{option_spec()}} for details. 84 | 85 | \item \code{opt_set()}: Set an option's value. Additional \code{...} arguments passed to 86 | \code{\link[=get_option_spec]{get_option_spec()}}. 87 | 88 | \item \code{opt(x, ...) <- value}: An alias for \code{\link[=opt_set]{opt_set()}} 89 | 90 | \item \code{opt_source()}: Determine source of option value. Primarily used for diagnosing options 91 | behaviors. 92 | 93 | \item \code{opts()}: Retrieve multiple options. When no names are provided, return a list 94 | containing all options from a given environment. Accepts a character 95 | vector of option names or a named list of new values to modify global 96 | option values. 97 | 98 | \item \code{opt_set_local()}: Set an option only in the local frame. Additional \code{...} arguments passed to 99 | \code{\link[=on.exit]{on.exit()}}. 100 | 101 | \item \code{opts_list()}: Produce a named list of namespaced option values, for use with \code{\link[=options]{options()}} 102 | and \code{\link[withr]{withr}}. Additional \code{...} arguments used to provide 103 | named option values. 104 | 105 | }} 106 | \note{ 107 | Local options are set with \link{on.exit}, which can be prone to error if 108 | subsequent calls are not called with \code{add = TRUE} (masking existing 109 | \link{on.exit} callbacks). A more rigorous alternative might make use of 110 | \code{\link[withr:defer]{withr::defer}}. 111 | 112 | \if{html}{\out{}}\preformatted{old <- opt_set("option", value) 113 | withr::defer(opt_set("option", old)) 114 | }\if{html}{\out{}} 115 | 116 | If you'd prefer to use this style, see \code{\link[=opts_list]{opts_list()}}, which is designed 117 | to work nicely with \code{\link[withr]{withr}}. 118 | } 119 | \examples{ 120 | define_options("Whether execution should emit console output", quiet = FALSE) 121 | opt("quiet") 122 | 123 | define_options("Whether execution should emit console output", quiet = FALSE) 124 | opt_source("quiet") 125 | 126 | Sys.setenv(R_GLOBALENV_QUIET = TRUE) 127 | opt_source("quiet") 128 | 129 | options(globalenv.quiet = FALSE) 130 | opt_source("quiet") 131 | 132 | define_options("Quietly", quiet = TRUE, "Verbosity", verbose = FALSE) 133 | 134 | # retrieve multiple options 135 | opts(c("quiet", "verbose")) 136 | 137 | # update multiple options, returns unmodified values 138 | opts(list(quiet = 42, verbose = TRUE)) 139 | 140 | # next time we check their values we'll see the modified values 141 | opts(c("quiet", "verbose")) 142 | 143 | define_options("print quietly", quiet = TRUE) 144 | 145 | print.example <- function(x, ...) if (!opt("quiet")) NextMethod() 146 | example <- structure("Hello, World!", class = "example") 147 | print(example) 148 | 149 | # using base R options to manage temporary options 150 | orig_opts <- options(opts_list(quiet = FALSE)) 151 | print(example) 152 | options(orig_opts) 153 | 154 | \dontshow{if (length(find.package("withr")) > 0L) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 155 | # using `withr` to manage temporary options 156 | withr::with_options(opts_list(quiet = FALSE), print(example)) 157 | \dontshow{\}) # examplesIf} 158 | } 159 | -------------------------------------------------------------------------------- /man/option_spec.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_spec.R 3 | \name{option_spec} 4 | \alias{option_spec} 5 | \title{Specify Option} 6 | \usage{ 7 | option_spec( 8 | name, 9 | default = bquote(), 10 | desc = NULL, 11 | option_name = get_option_name_fn(envir), 12 | envvar_name = get_envvar_name_fn(envir), 13 | option_fn = function(value, ...) value, 14 | envvar_fn = envvar_eval_or_raw(), 15 | quoted = FALSE, 16 | eager = FALSE, 17 | envir = parent.frame() 18 | ) 19 | } 20 | \arguments{ 21 | \item{name}{A string representing the internal name for the option. This is 22 | the short form \verb{} used within a namespace and relates to, for 23 | example, \verb{.} global R option.} 24 | 25 | \item{default}{Either a quoted expression (if parameter \code{quote == TRUE}) or 26 | default value for the option. Defaults to an empty expression, indicating 27 | that it is unset. The default value is lazily evaluated, evaluated only 28 | when the option is first requested unless parameter \code{eager == TRUE}.} 29 | 30 | \item{desc}{A written description of the option's effects} 31 | 32 | \item{option_name, envvar_name}{A character value or function. If a character 33 | value is provided it is used as the corresponding global option name or 34 | environment variable name. If a function is provided it is provided with 35 | the package name and internal option name to derive the global option name. 36 | For example, provided with package \code{"mypkg"} and option \code{"myoption"}, the 37 | function might return global option name \code{"mypkg.myoption"} or environment 38 | variable name \code{"R_MYPKG_MYOPTION"}. Defaults to configured default 39 | functions which fall back to \code{option_name_default} and 40 | \code{envvar_name_default}, and can be configured using \code{set_option_name_fn} 41 | and \code{set_envvar_name_fn}.} 42 | 43 | \item{option_fn}{A function to use for processing an option value before 44 | being returned from the \link{opt} accessor functions. For further details see 45 | section "Processing Functions".} 46 | 47 | \item{envvar_fn}{A function to use for parsing environment variable values. 48 | Defaults to \code{envvar_eval_or_raw()}. For further details see section 49 | "Processing Functions".} 50 | 51 | \item{quoted}{A logical value indicating whether the \code{default} argument 52 | should be treated as a quoted expression or as a value.} 53 | 54 | \item{eager}{A logical value indicating whether the \code{default} argument should 55 | be eagerly evaluated (upon call), or lazily evaluated (upon first use). 56 | This distinction will only affect default values that rely on evaluation of 57 | an expression, which may produce a different result depending on the 58 | context in which it is evaluated.} 59 | 60 | \item{envir}{An environment in which to search for an options envir object. 61 | It is rarely necessary to use anything but the default.} 62 | } 63 | \value{ 64 | An \code{option_spec} object, which is a simple S3 class wrapping a list 65 | containing these arguments. 66 | } 67 | \description{ 68 | An option specification outlines the various behaviors of an option. It's 69 | default value, related global R option, and related environment variable 70 | name, as well as a description. This information defines the operating 71 | behavior of the option. 72 | } 73 | \section{Processing Functions}{ 74 | Parameters \code{option_fn} and \code{envvar_fn} allow for customizing the way values 75 | are interpreted and processed before being returned by \code{\link{opt}} functions. 76 | \subsection{\code{envvar_fn}}{ 77 | 78 | When a value is retrieved from an environment variable, the string value 79 | contained in the environment variable is first processed by \code{envvar_fn}. 80 | 81 | An \code{envvar_fn} accepts only a single positional argument, and should have a 82 | signature such as: 83 | 84 | \if{html}{\out{}}\preformatted{function(value) 85 | }\if{html}{\out{}} 86 | } 87 | 88 | \subsection{\code{option_fn}}{ 89 | 90 | Regardless of how a value is produced - either retrieved from an environment 91 | variable, option, a stored default value or from a default provided to an 92 | \code{\link{opt}} accessor function - it is then further processed by \code{option_fn}. 93 | 94 | The first argument provided to \code{option_fn} will always be the retrieved 95 | value. The remaining parameters in the signature should be considered 96 | experimental. In addition to the value, the arguments provided to \code{\link[=opt]{opt()}}, 97 | as well as an additional \code{source} parameter from \code{\link[=opt_source]{opt_source()}} may be 98 | used. 99 | 100 | \strong{Stable} 101 | 102 | \if{html}{\out{}}\preformatted{function(value, ...) 103 | }\if{html}{\out{}} 104 | 105 | \strong{Experimental} 106 | 107 | \if{html}{\out{}}\preformatted{function(value, x, default, env, ..., source) 108 | }\if{html}{\out{}} 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /man/options_env.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/options_env.R 3 | \name{options_env} 4 | \alias{options_env} 5 | \alias{options_initialized} 6 | \alias{init_options_env} 7 | \alias{as_options_list} 8 | \alias{as_options_list.list} 9 | \alias{as_options_list.options_env} 10 | \alias{get_option_default_value} 11 | \alias{get_options_spec} 12 | \alias{get_option_spec} 13 | \alias{set_option_spec} 14 | \title{Options Environment Class} 15 | \usage{ 16 | options_initialized(env, inherits = FALSE) 17 | 18 | init_options_env(env = parent.frame()) 19 | 20 | as_options_list(x, ...) 21 | 22 | \method{as_options_list}{list}(x, ...) 23 | 24 | \method{as_options_list}{options_env}(x, ...) 25 | 26 | get_option_default_value(x, env = parent.frame()) 27 | 28 | get_options_spec(env = parent.frame()) 29 | 30 | get_option_spec( 31 | name, 32 | env = parent.frame(), 33 | inherits = FALSE, 34 | on_missing = warning 35 | ) 36 | 37 | set_option_spec(name, details, env = parent.frame()) 38 | } 39 | \arguments{ 40 | \item{env}{An environment in which to search for an options environment} 41 | 42 | \item{inherits}{Whether to search upward through parent environments} 43 | 44 | \item{...}{Additional arguments unused} 45 | } 46 | \description{ 47 | The options environment stores primarily, the default values for options. In 48 | addition, it stores metadata pertaining to each option in the form of 49 | attributes. 50 | } 51 | \section{Functions}{ 52 | \itemize{ 53 | \item \code{options_initialized()}: Test whether options is initialized in environment 54 | 55 | \item \code{init_options_env()}: Initialize an options object 56 | 57 | \item \code{as_options_list()}: Convert into an options list 58 | 59 | \item \code{get_option_default_value()}: Get the option's default value 60 | 61 | \item \code{get_options_spec()}: Get all options specifications as named list 62 | 63 | \item \code{get_option_spec()}: Get single option specification 64 | 65 | \item \code{set_option_spec()}: Set single option specification 66 | 67 | }} 68 | \section{Attributes}{ 69 | 70 | \itemize{ 71 | \item \code{spec}: A named list of option specifications 72 | \item \code{option_name_fn}: A function used to derive default option names for 73 | newly defined options. See \code{\link[=set_option_name_fn]{set_option_name_fn()}}. 74 | \item \code{envvar_name_fn}: A function used to derive default environment variable 75 | names for newly defined options. See \code{\link[=set_envvar_name_fn]{set_envvar_name_fn()}}. 76 | } 77 | } 78 | 79 | \concept{options_env} 80 | \keyword{internal} 81 | -------------------------------------------------------------------------------- /man/options_fmts.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/crayon.R 3 | \name{options_fmts} 4 | \alias{options_fmts} 5 | \title{Optional Crayon Handling} 6 | \usage{ 7 | options_fmts() 8 | } 9 | \value{ 10 | formatted text 11 | } 12 | \description{ 13 | Generate a list of styling functions using \code{crayon}, while safely falling 14 | back to non-\code{crayon} output when \code{crayon} is unavailable. 15 | } 16 | \keyword{internal} 17 | -------------------------------------------------------------------------------- /man/pkgname.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{pkgname} 4 | \alias{pkgname} 5 | \title{Grab package name, at runtime} 6 | \usage{ 7 | pkgname(env = parent.frame()) 8 | } 9 | \arguments{ 10 | \item{env}{An environment in which to search for a package name} 11 | } 12 | \value{ 13 | A package name or "globalenv" if not found 14 | } 15 | \description{ 16 | Lazily grab \code{packageName()} within calling environment, not within function 17 | environment. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/reflow_option_desc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/define_option.R 3 | \name{reflow_option_desc} 4 | \alias{reflow_option_desc} 5 | \title{Reflow multiline strings} 6 | \usage{ 7 | reflow_option_desc(x) 8 | } 9 | \arguments{ 10 | \item{x}{A vector of multiline strings to reflow} 11 | } 12 | \value{ 13 | The reflowed strings 14 | } 15 | \description{ 16 | A small helper function for allowing multiline strings to be collapsed into 17 | continuous lines, similar to markdown's paragraph handling. 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /pkgdown/_pkgdown.yml: -------------------------------------------------------------------------------- 1 | development: 2 | mode: auto 3 | 4 | url: ~ 5 | template: 6 | bootstrap: 5 7 | theme: arrow-light #atom-one-light 8 | bslib: 9 | fg: "#404040" 10 | bg: "#F4F4F4" 11 | primary: "#fa3939" 12 | navbar-dark-active-color: var(--bs-fg) 13 | navbar-dark-color: var(--bs-fg) 14 | navbar-dark-brand-color: var(--bs-fg) 15 | navbar-dark-brand-hover-color: white 16 | navbar-dark-hover-color: white 17 | 18 | navbar: 19 | bg: primary 20 | 21 | reference: 22 | - title: Accessing Options 23 | - contents: 24 | - opt 25 | - get_options_env 26 | - title: Defining Options 27 | - contents: 28 | - define_option 29 | - define_options 30 | - option_spec 31 | - title: "`roxygen2` Docs" 32 | - desc: Autogenerate documentation your package options 33 | - contents: 34 | - has_keyword("roxygen2") 35 | - title: Customize 36 | - subtitle: Naming Conventions 37 | - desc: Customize how global R options or environment variables are named 38 | - contents: 39 | - has_keyword("naming") 40 | - has_keyword("naming_formats") 41 | - subtitle: Environment Variable Parsing 42 | - desc: Control how environment variables are parsed into useful R data 43 | - contents: 44 | - has_keyword("envvar_parsers") 45 | -------------------------------------------------------------------------------- /pkgdown/extra.scss: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | --bs-code-color: rgba(var(--bs-primary-rgb), 0.8); 4 | } 5 | 6 | h1, h2, h3, h4, h5, h6 { 7 | @extend %headings !optional; 8 | } 9 | 10 | %headings { 11 | margin-top: 1rem; 12 | } 13 | 14 | pre { 15 | padding: 1rem; 16 | background: #fefbfa; 17 | border-style: none none none solid; 18 | border-color: rgba(var(--bs-primary-rgb), 0.6); 19 | border-radius: 0; 20 | border-width: 0.2em; 21 | } 22 | 23 | .btn-copy-ex { 24 | padding: 0.5em 1em; 25 | } 26 | 27 | // for dev version tag, which can be hard to read with bright navbars 28 | .nav-text.text-danger { 29 | background-color: rgba(255, 255, 255, 0.8); 30 | padding: 0.2em 0.8em; 31 | border-radius: 1em; 32 | } 33 | 34 | .navbar-dark { 35 | --bs-navbar-color: rgba(var(--bs-body-bg-rgb), 0.8); 36 | --bs-navbar-hover-color: rgba(var(--bs-body-bg-rgb), 1.0); 37 | font-weight: bolder; 38 | 39 | .navbar-nav { 40 | .active > .nav-link { 41 | background: rgba(255, 255, 255, 0.2); 42 | } 43 | } 44 | 45 | input[type="search"] { 46 | border: none; 47 | border-radius: 0.1em; 48 | background-color: rgba(var(--bs-body-bg-rgb), 0.8); 49 | color: rgba(var(--bs-primary-rgb), 0.6); 50 | &:focus { background-color: rgba(var(--bs-body-bg-rgb), 0.90); } 51 | } 52 | } 53 | 54 | .text-muted { 55 | color: rgba(var(--bs-body-bg-rgb), 0.6) !important; 56 | } 57 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/tests.html 7 | # * https://testthat.r-lib.org/reference/test_package.html#special-files 8 | 9 | library(testthat) 10 | library(options) 11 | 12 | test_check("options") 13 | -------------------------------------------------------------------------------- /tests/testthat/setup.R: -------------------------------------------------------------------------------- 1 | paths <- local({ 2 | sys_path <- system.file("options.example", package = "options") 3 | options.example_path <- if (dir.exists(sys_path)) { 4 | sys_path 5 | } else { 6 | path <- testthat::test_path() 7 | while (!file.exists(file.path(path, "DESCRIPTION"))) path <- dirname(path) 8 | if (dir.exists(pinst <- file.path(path, "inst"))) path <- pinst 9 | file.path(path, "options.example") 10 | } 11 | 12 | list( 13 | options.example = options.example_path 14 | ) 15 | }) 16 | 17 | test_env <- function() { 18 | new.env(parent = baseenv()) 19 | } 20 | 21 | # Configure a set of masking environment variables to allow R CMD check to run 22 | # as a test within R CMD check of `options`. Without masking these, they are 23 | # inherited in the child process which causes false positive R CMD check 24 | # errors. 25 | reset_envvars <- function() { 26 | envvars <- character() 27 | for (v in names(Sys.getenv())) { 28 | if (startsWith(v, "_R_CHECK") || 29 | startsWith(v, "RCMDCHECK") || 30 | startsWith(v, "R_TESTS") || 31 | startsWith(v, "TESTTHAT")) { 32 | envvars[v] <- "" 33 | } 34 | } 35 | envvars 36 | } 37 | 38 | remove_options_env <- function() { 39 | env <- get_options_env.default(parent.frame(), inherits = TRUE) 40 | specs <- get_options_spec(env) 41 | opt_names <- vcapply(specs, function(spec) spec$option_name) 42 | args <- rep_len(list(NULL), length(opt_names)) 43 | names(args) <- opt_names 44 | do.call(options, args) 45 | rm(".options", envir = parent.env(env)) 46 | } 47 | 48 | pkgload::load_all(paths$options.example, export_all = FALSE) 49 | -------------------------------------------------------------------------------- /tests/testthat/test-define-option.R: -------------------------------------------------------------------------------- 1 | test_that("define_options accepts multiple option definitions", { 2 | e <- test_env() 3 | 4 | expect_silent(with(e, { 5 | options::define_options( 6 | "option A", 7 | A = 1, 8 | "option B", 9 | B = 2 10 | ) 11 | })) 12 | 13 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 2) 14 | expect_equal(opt("A", env = e), 1) 15 | expect_equal(opt("B", env = e), 2) 16 | }) 17 | 18 | test_that("define_options accepts option_spec parameters", { 19 | e <- test_env() 20 | 21 | expect_silent(with(e, options::define_options( 22 | "this is option A", 23 | A = 1, 24 | option_name = "opt_a", 25 | envvar_name = "OPT_A" 26 | ))) 27 | 28 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 29 | expect_silent(spec <- get_option_spec("A", env = e)) 30 | expect_equal(spec$option_name, "opt_a") 31 | }) 32 | 33 | test_that("define_options errors when 'desc' argument is passed", { 34 | expect_error( 35 | with(test_env(), options::define_options( 36 | "this is option A", 37 | A = 1, 38 | desc = 10, 39 | option_name = "opt_a", 40 | envvar_name = "OPT_A" 41 | )), 42 | "desc" 43 | ) 44 | 45 | # but not when "desc" is the option name 46 | expect_no_error( 47 | with(test_env(), options::define_options( 48 | "this option is for descriptions", 49 | desc = 10, 50 | option_name = "opt_a", 51 | envvar_name = "OPT_A" 52 | )) 53 | ) 54 | }) 55 | 56 | test_that("define_options errors when 'name' argument is passed", { 57 | expect_error( 58 | with(test_env(), options::define_options( 59 | "this is option A", 60 | A = 1, 61 | name = "A", 62 | option_name = "opt_a", 63 | envvar_name = "OPT_A" 64 | )), 65 | "name" 66 | ) 67 | 68 | # but not when "name" is the option name 69 | expect_no_error( 70 | with(test_env(), options::define_options( 71 | "this is option 'name'", 72 | name = "bob", 73 | option_name = "opt_a", 74 | envvar_name = "OPT_A" 75 | )) 76 | ) 77 | }) 78 | 79 | test_that("define_options errors with empty last argument", { 80 | expect_error( 81 | with(test_env(), options::define_options( 82 | "this is option A", 83 | A = 1, 84 | )), 85 | "comma" 86 | ) 87 | }) 88 | 89 | test_that("define_options errors on multiple consecutive unnamed args", { 90 | expect_error( 91 | with(test_env(), options::define_options( 92 | "this isn't write", 93 | "this is option A", 94 | A = 1 95 | )), 96 | "follow.*description" 97 | ) 98 | }) 99 | 100 | test_that("define_options error when first argument is named", { 101 | expect_error( 102 | with(test_env(), options::define_options( 103 | A = 1 104 | )), 105 | "begin.*description" 106 | ) 107 | }) 108 | 109 | test_that("define_options errors report appropriate options", { 110 | expect_error( 111 | with(test_env(), options::define_options(A = 1, "B", B = 2)), 112 | "option \\(1\\)" 113 | ) 114 | 115 | expect_error( 116 | with(test_env(), options::define_options( 117 | "A", 118 | A = 1, 119 | "B", 120 | B = 2, 121 | desc = "b" 122 | )), 123 | "option \\(2\\)" 124 | ) 125 | }) 126 | 127 | test_that("define_option accepts parameterized options specification", { 128 | e <- test_env() 129 | 130 | expect_silent(with(e, { 131 | options::define_option( 132 | "A", 133 | default = 1, 134 | desc = "this is option A", 135 | option_name = "opt_a", 136 | envvar_name = "OPT_A" 137 | ) 138 | })) 139 | 140 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 141 | expect_equal(opt("A", env = e), 1) 142 | }) 143 | 144 | test_that("define_option accepts an option_spec object", { 145 | e <- test_env() 146 | 147 | expect_silent(with(e, { 148 | options::define_option(options::option_spec( 149 | "A", 150 | default = 1, 151 | desc = "this is option A", 152 | option_name = "opt_a", 153 | envvar_name = "OPT_A" 154 | )) 155 | })) 156 | 157 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 158 | expect_equal(opt("A", env = e), 1) 159 | }) 160 | 161 | test_that("option_spec option_fn processes option values", { 162 | e <- test_env() 163 | 164 | expect_silent(with(e, { 165 | options::define_option(options::option_spec( 166 | "A", 167 | default = 1, 168 | desc = "this is option A", 169 | option_name = "opt_a", 170 | option_fn = function(value, ..., source = source) { 171 | print(source) 172 | value + 1 173 | }, 174 | envvar_name = "OPT_A" 175 | )) 176 | })) 177 | 178 | expect_equal(length(e[[CONST_OPTIONS_ENV_NAME]]), 1) 179 | expect_output(expect_equal(opt("A", env = e), 2), "default") 180 | expect_output(withr::with_envvar( 181 | list(OPT_A = "10"), 182 | expect_equal(opt("A", env = e), 11) 183 | ), "envvar") 184 | }) 185 | -------------------------------------------------------------------------------- /tests/testthat/test-envvars.R: -------------------------------------------------------------------------------- 1 | test_that("envvar_is trys for complex data", { 2 | expect_true(envvar_is(list(1, 2))("list(1, 2)")) 3 | expect_false(envvar_is(list(1, 2))("c(1, 2)")) 4 | expect_false(envvar_is(list(1, 2))("1 + 'a'")) 5 | }) 6 | 7 | test_that("envvar_is works for numerics", { 8 | expect_true(envvar_is(1)("1")) 9 | expect_false(envvar_is(1)("0")) 10 | expect_false(envvar_is(1)("str")) 11 | }) 12 | 13 | test_that("envvar_is works for logicals", { 14 | expect_true(envvar_is(TRUE)("true")) 15 | expect_true(envvar_is(TRUE)("True")) 16 | expect_true(envvar_is(TRUE)("TRUE")) 17 | expect_true(envvar_is(FALSE)("false")) 18 | expect_true(envvar_is(FALSE)("False")) 19 | expect_true(envvar_is(FALSE)("FALSE")) 20 | expect_false(envvar_is(TRUE)("1")) 21 | expect_false(envvar_is(TRUE)("0")) 22 | expect_true(envvar_is(TRUE, case_sensitive = FALSE)("TrUe")) 23 | expect_false(envvar_is(TRUE, case_sensitive = TRUE)("TrUe")) 24 | }) 25 | 26 | test_that("envvar_is works for strings", { 27 | expect_true(envvar_is("test")("test")) 28 | expect_true(envvar_is("test")("Test")) 29 | expect_false(envvar_is("test", case_sensitive = TRUE)("Test")) 30 | expect_false(envvar_is("test")("1")) 31 | }) 32 | 33 | test_that("envvar_is works for nulls", { 34 | expect_true(envvar_is(NULL)("null")) 35 | expect_true(envvar_is(NULL)("NULL")) 36 | expect_false(envvar_is(NULL)("not null")) 37 | }) 38 | 39 | test_that("envvar_eval returns evaluated expression or error", { 40 | expect_equal(envvar_eval()("1 + 2", "VAR"), 3) 41 | expect_error( 42 | envvar_eval()("1 + 'a'", "VAR"), 43 | "Environment variable 'VAR' could not" 44 | ) 45 | }) 46 | 47 | test_that("envvar_eval_or_raw returns evaluated expression or error, or raw string", { 48 | expect_equal(envvar_eval_or_raw()("1 + 2"), 3) 49 | expect_equal(envvar_eval_or_raw()("1 + 'a'"), "1 + 'a'") 50 | }) 51 | 52 | test_that("envvar_is_one_of returns logical if in set", { 53 | expect_true(envvar_is_one_of(1:3)(1)) 54 | expect_false(envvar_is_one_of(1:3)(4)) 55 | expect_true(envvar_is_one_of(list(1, "a"))("a")) 56 | expect_false(envvar_is_one_of(list(1, "a"))("b")) 57 | }) 58 | 59 | test_that("envvar_choice_of returns value if in set, default otherwise", { 60 | expect_equal(envvar_choice_of(1:3)(1), 1) 61 | expect_equal(envvar_choice_of(1:3)(4), NULL) 62 | expect_equal(envvar_choice_of(1:3, default = 10)(4), 10) 63 | }) 64 | 65 | test_that("envvar_is_true returns TRUE for truthy values", { 66 | expect_true(envvar_is_true()("TRUE")) 67 | expect_true(envvar_is_true()("True")) 68 | expect_true(envvar_is_true()("true")) 69 | expect_true(envvar_is_true()("1")) 70 | expect_false(envvar_is_true()("other")) 71 | expect_false(envvar_is_true()("0")) 72 | }) 73 | 74 | test_that("envvar_is_false returns TRUE for falsy values", { 75 | expect_true(envvar_is_false()("FALSE")) 76 | expect_true(envvar_is_false()("False")) 77 | expect_true(envvar_is_false()("false")) 78 | expect_true(envvar_is_false()("0")) 79 | expect_false(envvar_is_false()("other")) 80 | expect_false(envvar_is_false()("1")) 81 | }) 82 | 83 | test_that("envvar_is_set always returns TRUE (unset condition handled elsewhere)", { 84 | expect_true(envvar_is_set()("anything"), TRUE) 85 | }) 86 | 87 | test_that("envvar_str_split splits raw string on delimiter", { 88 | expect_equal(envvar_str_split()("a;b;c"), c("a", "b", "c")) 89 | expect_equal(envvar_str_split(delim = "-")("a-b-c"), c("a", "b", "c")) 90 | expect_equal(envvar_str_split()("a"), c("a")) 91 | }) 92 | -------------------------------------------------------------------------------- /tests/testthat/test-naming.R: -------------------------------------------------------------------------------- 1 | test_that("changing naming functions affects future options", { 2 | e <- new.env() 3 | 4 | f_env <- function(pkg, name) toupper(name) 5 | f_opt <- function(pkg, name) tolower(name) 6 | 7 | expect_silent(set_envvar_name_fn(f_env, env = e)) 8 | expect_silent(set_option_name_fn(f_opt, env = e)) 9 | 10 | expect_identical(get_envvar_name_fn(env = e), f_env) 11 | expect_identical(get_option_name_fn(env = e), f_opt) 12 | 13 | expect_silent(with(e, define_options("option A", A = 1))) 14 | expect_silent(spec <- get_option_spec("A", env = e)) 15 | 16 | expect_identical(spec$envvar_name, "A") 17 | expect_identical(spec$option_name, "a") 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat/test-opt.R: -------------------------------------------------------------------------------- 1 | test_that("opts() fetches options by name if provided", { 2 | expect_silent(o <- opts("quiet", env = "options.example")) 3 | expect_length(o, 1) 4 | expect_named(o, "quiet") 5 | 6 | expect_silent(o <- opts(c("quiet", "use_options"), env = "options.example")) 7 | expect_length(o, 2) 8 | expect_named(o, c("quiet", "use_options")) 9 | }) 10 | 11 | test_that("opts() can be used to set options when provided a named list", { 12 | define_options("quietly", quiet = FALSE, "verbosity", verbose = FALSE) 13 | on.exit(remove_options_env()) 14 | 15 | # opts updates values and returns originals 16 | expect_silent(o <- opts(list(quiet = 42, verbose = TRUE))) 17 | expect_length(o, 2) 18 | expect_named(o, c("quiet", "verbose")) 19 | expect_equal(o$quiet, FALSE) 20 | expect_equal(o$verbose, FALSE) 21 | 22 | # retrieving values are modified 23 | expect_silent(o <- opts(c("quiet", "verbose"))) 24 | expect_length(o, 2) 25 | expect_named(o, c("quiet", "verbose")) 26 | expect_equal(o$quiet, 42) 27 | expect_equal(o$verbose, TRUE) 28 | }) 29 | 30 | test_that("opts() can be used to retrieve options when provided a unnamed list", { 31 | define_options("quietly", quiet = FALSE, "verbosity", verbose = FALSE) 32 | on.exit(remove_options_env()) 33 | 34 | expect_silent(o <- opts(list("quiet", "verbose"))) 35 | expect_length(o, 2) 36 | expect_named(o, c("quiet", "verbose")) 37 | expect_equal(o$quiet, FALSE) 38 | expect_equal(o$verbose, FALSE) 39 | }) 40 | 41 | test_that("opts() fetches options by name if provided", { 42 | expect_silent(o <- opts(env = "options.example")) 43 | expect_s3_class(o, "options_list") 44 | expect_true(length(o) > 2) 45 | expect_true(length(o) == length(unique(names(o)))) 46 | }) 47 | 48 | test_that("opts() throws error when mixing named and unnamed list values", { 49 | expect_error(opts(list(env = "options.example", "quiet"))) 50 | }) 51 | -------------------------------------------------------------------------------- /tests/testthat/test-opt_set.R: -------------------------------------------------------------------------------- 1 | test_that("opt_set modifies option value", { 2 | define_options("option quiet", quiet = FALSE) 3 | on.exit(remove_options_env()) 4 | 5 | expect_silent(opt_set("quiet", 42)) 6 | expect_equal(opt("quiet"), 42) 7 | }) 8 | 9 | test_that("opt_set returns original value", { 10 | define_options("option quiet", quiet = FALSE) 11 | on.exit(remove_options_env()) 12 | 13 | expect_equal(opt_set("quiet", 42), NULL) 14 | expect_equal(opt_set("quiet", 123), 42) 15 | }) 16 | 17 | test_that("opt_set raises on_missing callback when option not defined", { 18 | define_options("option quiet", quiet = FALSE) 19 | on.exit(remove_options_env()) 20 | 21 | expect_warning(opt_set("verbose", 42), "not defined") 22 | }) 23 | 24 | test_that("opt_set raises on_missing accepts callback functions", { 25 | define_options("option quiet", quiet = FALSE) 26 | on.exit(remove_options_env()) 27 | 28 | custom <- function(...) { 29 | cond <- simpleCondition("testing 1 2 3") 30 | class(cond) <- c("custom", "condition") 31 | signalCondition(cond) 32 | } 33 | 34 | expect_error(opt_set("verbose", 42, on_missing = stop), "not defined") 35 | expect_condition(opt_set("verbose", 42, on_missing = custom), class = "custom") 36 | }) 37 | 38 | test_that("opt_set raises on_missing accepts callback shorthand names", { 39 | define_options("option quiet", quiet = FALSE) 40 | on.exit(remove_options_env()) 41 | 42 | expect_error(opt_set("verbose", 42, on_missing = "error"), "not defined") 43 | }) 44 | -------------------------------------------------------------------------------- /tests/testthat/test-opt_set_local.R: -------------------------------------------------------------------------------- 1 | test_that("opt_set_local, modifying env options, restores original value when frame exits", { 2 | fn <- function() { 3 | opt_set_local("quiet", 42, env = "options.example") 4 | opt("quiet", env = "options.example") 5 | } 6 | 7 | orig <- opt("quiet", env = "options.example") 8 | expect_true(!identical(orig, 42)) 9 | expect_equal(fn(), 42) 10 | expect_equal(opt("quiet", env = "options.example"), orig) 11 | }) 12 | 13 | test_that("opt_set_local, modifying global options, restores original value when frame exits", { 14 | define_options("option quiet", quiet = FALSE) 15 | on.exit(remove_options_env()) 16 | 17 | fn <- function() { 18 | opt_set_local("quiet", 42) 19 | opt("quiet") 20 | } 21 | 22 | orig <- opt("quiet") 23 | expect_true(!identical(orig, 42)) 24 | expect_equal(fn(), 42) 25 | expect_equal(opt("quiet"), orig) 26 | }) 27 | -------------------------------------------------------------------------------- /tests/testthat/test-options_env.R: -------------------------------------------------------------------------------- 1 | test_that("get_options_env() returns options env if it exists", { 2 | e <- test_env() 3 | child_env <- new.env(parent = e) 4 | 5 | expect_silent(with(e, options::define_option( 6 | "A", 7 | default = 1, 8 | option_name = "opt.a", 9 | envvar_name = "OPT_A" 10 | ))) 11 | 12 | expect_equal(names(get_options_env(env = e)), "A") 13 | expect_equal(names(get_options_env(env = child_env, inherits = TRUE)), "A") 14 | expect_equal(get_options_env(), emptyenv()) 15 | }) 16 | -------------------------------------------------------------------------------- /tests/testthat/test-opts_list.R: -------------------------------------------------------------------------------- 1 | test_that("opts_list produces an option of namespaced option names", { 2 | e <- new.env(parent = baseenv()) 3 | 4 | expect_silent(with(e, options::define_options( 5 | "option quiet", 6 | quiet = TRUE 7 | ))) 8 | 9 | l <- expect_silent(with(e, options::opts_list(quiet = FALSE))) 10 | 11 | expect_match(names(l), ".+\\.quiet") 12 | expect_type(l, "list") 13 | expect_equal(l[[1]], FALSE) 14 | }) 15 | 16 | test_that("opts_list returns raw name if not part of namespaced option spec", { 17 | e <- new.env(parent = baseenv()) 18 | expect_silent(with(e, options::define_options( 19 | "option quiet", 20 | quiet = TRUE 21 | ))) 22 | 23 | l <- expect_silent(with(e, options::opts_list(quiet = FALSE, max.print = 10))) 24 | 25 | expect_match(names(l)[[1]], ".+\\.quiet") 26 | expect_true("max.print" %in% names(l)) 27 | }) 28 | 29 | test_that("opts_list emit warnings when names missing with check_names warn", { 30 | e <- new.env(parent = baseenv()) 31 | expect_silent(with(e, options::define_options( 32 | "option quiet", 33 | quiet = TRUE 34 | ))) 35 | 36 | expect_warning(with(e, { 37 | options::opts_list(quiet = FALSE, max.print = 10, check_names = "warn") 38 | })) 39 | }) 40 | 41 | test_that("opts_list emit error when names missing with check_names stop", { 42 | e <- new.env(parent = baseenv()) 43 | expect_silent(with(e, options::define_options( 44 | "option quiet", 45 | quiet = TRUE 46 | ))) 47 | 48 | expect_error(with(e, { 49 | options::opts_list(quiet = FALSE, max.print = 10, check_names = "stop") 50 | })) 51 | }) 52 | -------------------------------------------------------------------------------- /tests/testthat/test-output.R: -------------------------------------------------------------------------------- 1 | test_that("options objects pretty print", { 2 | e <- new.env(parent = baseenv()) 3 | 4 | expect_silent(with(e, options::define_option( 5 | "A", 6 | default = 1, 7 | option_name = "opt.A", 8 | envvar_name = "OPT_A" 9 | ))) 10 | 11 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 12 | 13 | # name and current value print 14 | expect_match(out, "A = 1") 15 | 16 | # sources print 17 | expect_match(out, "option\\s+:\\s*opt\\.A") 18 | expect_match(out, "envvar\\s+:\\s*OPT_A") 19 | expect_match(out, "default\\s+:\\s*1") 20 | 21 | # current source is flagged 22 | expect_match(out, " option") 23 | expect_match(out, " envvar") 24 | expect_match(out, "*default") 25 | 26 | # and updates when current source changes 27 | Sys.setenv("OPT_A" = 3) 28 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 29 | expect_match(out, " option") 30 | expect_match(out, "*envvar") 31 | expect_match(out, " default") 32 | Sys.unsetenv("OPT_A") 33 | }) 34 | 35 | test_that("options objects prints options in definition order", { 36 | e <- new.env(parent = baseenv()) 37 | 38 | expect_silent(with(e, options::define_option( 39 | "B", 40 | default = 2, 41 | envvar_name = "OPT_B" 42 | ))) 43 | 44 | expect_silent(with(e, options::define_option( 45 | "A", 46 | default = 1, 47 | envvar_name = "OPT_A" 48 | ))) 49 | 50 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 51 | expect_match(out, "OPT_B.*OPT_A") 52 | }) 53 | 54 | test_that("options objects print even without default", { 55 | e <- new.env(parent = baseenv()) 56 | expect_silent(with(e, options::define_option("B"))) 57 | expect_silent(out <- paste0(capture.output(e$.options), collapse = "\n")) 58 | expect_match(out, "") 59 | }) 60 | -------------------------------------------------------------------------------- /tests/testthat/test-precedence.R: -------------------------------------------------------------------------------- 1 | test_that("option, envvar, default precedence is used for option values", { 2 | withr::defer({ 3 | Sys.unsetenv("OPT_A") 4 | Sys.unsetenv("OPT_B") 5 | options(opt.A = NULL, opt.B = NULL) 6 | }) 7 | 8 | e <- new.env() 9 | expect_silent(with(e, define_option( 10 | "A", 11 | default = 1, 12 | option_name = "opt.A", 13 | envvar_name = "OPT_A" 14 | ))) 15 | 16 | expect_equal(opt("A", env = e), 1) 17 | 18 | Sys.setenv("OPT_A" = 2) 19 | expect_equal(opt("A", env = e), 2) 20 | 21 | options("opt.A" = 3) 22 | expect_equal(opt("A", env = e), 3) 23 | 24 | options("opt.A" = NULL) 25 | expect_equal(opt("A", env = e), 2) 26 | 27 | Sys.unsetenv("OPT_A") 28 | expect_equal(opt("A", env = e), 1) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/testthat/test-rcmdcheck.R: -------------------------------------------------------------------------------- 1 | test_that("Packages that use options pass R CMD check", { 2 | skip_on_os(c("windows", "solaris")) 3 | skip_on_covr() 4 | skip_if_not( 5 | "options" %in% rownames(installed.packages()), 6 | paste0( 7 | "Skipping R CMD check integration tests as package 'options' ", 8 | "is not installed and would be unavailable to satisfy dependency ", 9 | "requirements." 10 | ) 11 | ) 12 | 13 | results <- rcmdcheck::rcmdcheck( 14 | paths$options.example, 15 | build_args = "--no-manual", 16 | args = "--no-manual", 17 | env = reset_envvars(), 18 | quiet = TRUE 19 | ) 20 | 21 | expect_length(results$errors, 0) 22 | expect_length(results$warnings, 0) 23 | expect_length(results$notes, 0) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/testthat/test-roxygen2.R: -------------------------------------------------------------------------------- 1 | test_that("roxygen2-style block is generated from options env object", { 2 | e <- test_env() 3 | 4 | expect_silent(with(e, options::define_option( 5 | "A", 6 | default = 1, 7 | option_name = "opt.a", 8 | envvar_name = "OPT_A" 9 | ))) 10 | 11 | expect_silent(block <- paste0(as_roxygen_docs(env = e), collapse = "\n")) 12 | expect_match(block, "@title") 13 | expect_match(block, "\\{default: \\}\\{\\\\preformatted") 14 | expect_match(block, "\\{option: \\}\\{opt.a") 15 | expect_match(block, "\\{envvar: \\}\\{OPT_A") 16 | }) 17 | 18 | test_that("roxygen2 options documentation is in definition order", { 19 | e <- test_env() 20 | expect_silent(with(e, options::define_option("B"))) 21 | expect_silent(with(e, options::define_option("A"))) 22 | 23 | expect_silent(block <- paste0(as_roxygen_docs(env = e), collapse = "\n")) 24 | expect_match(block, "\\\\item\\{B\\}.*\\\\item\\{A\\}") 25 | }) 26 | 27 | test_that("roxygen2-style params block is generated from as_params", { 28 | e <- test_env() 29 | 30 | expect_silent(with(e, options::define_option( 31 | "A", 32 | default = 1, 33 | desc = "my description", 34 | option_name = "opt.a", 35 | envvar_name = "OPT_A" 36 | ))) 37 | 38 | expect_silent(block <- paste0(with(e, options::as_params()), collapse = "\n")) 39 | expect_match(block, "^@param A") 40 | expect_match(block, "my description") 41 | expect_match(block, "Defaults to `1`") 42 | expect_match(block, "'opt.a'") 43 | expect_match(block, "'OPT_A'") 44 | }) 45 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/envvars.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Environment Variables" 3 | subtitle: Customizing environment variable handling 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{environment_variables} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | collapse = TRUE, 14 | comment = "#>" 15 | ) 16 | ``` 17 | 18 | Environment variables can be a tremendously helpful alternative to options for 19 | behaviors that one might want to handle ubiquitously across R sessions, that 20 | might be administered, or that might be needed in a command-line context. 21 | 22 | They can provide a helpful interface for supporting tasks that might be called 23 | during continuous integration jobs, on a compute cluster, or within a 24 | containerized environment. Since use cases can be hard to predict, `options` 25 | will consider appropriately named environment variables by default. 26 | 27 | # Customizing names 28 | 29 | Perhaps the default environment names don't suit you. When defining your 30 | options, you can always use your own! However, this level of customization is 31 | only available through the `define_option()` interface. 32 | 33 | > By default environment variables look like `R__` 34 | 35 | ```{r setup} 36 | library(options) 37 | ``` 38 | 39 | ```{r custom_envvar_name} 40 | define_option( 41 | "volume", 42 | default = "shout", 43 | desc = "Print output in uppercase ('shout') or lowercase ('whisper')", 44 | option_name = "volume", 45 | envvar_name = "VOL" 46 | ) 47 | 48 | twist_and <- function(what = opt("volume")) { 49 | lyric <- paste( 50 | "Well, shake it up, baby, now (Shake it up, baby)", 51 | "Twist and shout (Twist and shout)", 52 | sep = "\n" 53 | ) 54 | 55 | cat(if (what == "shout") toupper(lyric) else tolower(lyric), "\n") 56 | } 57 | ``` 58 | 59 | ```{r custom_envvar_ex} 60 | twist_and() # by default, "shout" 61 | ``` 62 | 63 | We can now alter our behavior using the environment variable, `VOL`. 64 | 65 | ```{r custom_envvar_val, error = TRUE} 66 | Sys.setenv(VOL = "whisper") 67 | 68 | twist_and() # picks up our environment variable, "whisper" 69 | ``` 70 | 71 | That's better! 72 | 73 | # Setting Naming Rules 74 | 75 | Although individually mapping options to environment variables is handy for 76 | one-off options, it can be tedious to do throughout your package, especially if 77 | you want all your variables to follow some consistent naming scheme. 78 | 79 | For this we can provide a function which is used to name all future options' 80 | environment variables. 81 | 82 | ```{r envvar_name_convention} 83 | set_envvar_name_fn(function(package, name) { 84 | gsub("[^A-Z0-9]", "_", toupper(paste0(package, "_", name))) 85 | }) 86 | ``` 87 | 88 | Now any future option environment variables will use this convention when they 89 | are defined. Existing options will be unaffected until they are redefined, so 90 | it's often best to make sure this code runs before you start defining options. 91 | 92 | ```{r, echo = FALSE} 93 | Sys.unsetenv("VOL") 94 | ``` 95 | 96 | ```{r redefine_option} 97 | define_options( 98 | "Print output in uppercase ('shout') or lowercase ('whisper')", 99 | volume = "shout" 100 | ) 101 | ``` 102 | 103 | You'll notice that our redefined option now uses our custom naming scheme for 104 | its environment variable. 105 | 106 | You can always write your own function, or choose from some of the pre-built 107 | ones in `?naming_formats`. 108 | 109 | # Parsing Values 110 | 111 | So far we've just been using the environment variable's value as-is. Environment 112 | variable values, by default, will try to be parsed into R objects. If that 113 | fails, they'll deliver the raw string value. 114 | 115 | This can be a nice default behavior, handling many simple cases as expected 116 | 117 | +--------------------------------------+---------------------------------------+ 118 | | Environment Variable | Default `options` value | 119 | +======================================+=======================================+ 120 | | `whisper` | `[1] "whisper"` (_character_) | 121 | +--------------------------------------+---------------------------------------+ 122 | | `"shout"` | `[1] "shout"` (_character_) | 123 | +--------------------------------------+---------------------------------------+ 124 | | `12345` | `[1] 12345` (_numeric_) | 125 | +--------------------------------------+---------------------------------------+ 126 | | `TRUE` | `[1] TRUE` (_logical_) | 127 | +--------------------------------------+---------------------------------------+ 128 | | `NULL` | `NULL` | 129 | +--------------------------------------+---------------------------------------+ 130 | | `list(1, 'a')` | ``` | 131 | | | [[1]] | 132 | | | [1] 1 | 133 | | | | 134 | | | [[2]] | 135 | | | [1] "a" | 136 | | | | 137 | | | ``` | 138 | +--------------------------------------+---------------------------------------+ 139 | | `list(1, 'a',)` (_error!_) | `[1] "list(1, 'a',)"` (_character_) | 140 | +--------------------------------------+---------------------------------------+ 141 | 142 | But you'll notice that a typo in our last example completely changed the type of 143 | data that is read in. Depending on the way that you intend to use this variable, 144 | perhaps this default is more error-prone than necessary. 145 | 146 | To help with this, there is a whole family of functions that allow you to 147 | customize the way that environment variables are internalized as option values 148 | (`?envvar_fns`). Generally, it's best to keep these specific to the data type of 149 | the option that you intend to use - for example, using `envvar_is_true` to 150 | always coerce the value to a logical scalar. 151 | 152 | Just like before, these are used to specify your option's behaviors: 153 | 154 | ```{r, echo = FALSE} 155 | Sys.unsetenv("VOL") 156 | ``` 157 | 158 | ```{r define_envvar_fn} 159 | define_option( 160 | "volume", 161 | default = TRUE, 162 | desc = "Print output in uppercase (TRUE) or lowercase (FALSE)", 163 | envvar_fn = envvar_is_true() 164 | ) 165 | ``` 166 | 167 | Of course you can define this function however you like. 168 | 169 | # Example 170 | 171 | Let's put it all together. We'll customize our environment variable name, and 172 | provide a custom `envvar_fn` which handles how we interpret the raw environment 173 | variable value. 174 | 175 | ```{r, echo = FALSE} 176 | Sys.unsetenv("VOL") 177 | ``` 178 | 179 | ```{r define_envvar_fn_2} 180 | define_option( 181 | "volume", 182 | default = 1, 183 | desc = paste0( 184 | "Print output in uppercase (shout) or lowercase (whisper), or any ", 185 | "number from 1-10 for random uppercasing" 186 | ), 187 | envvar_name = "VOL", 188 | envvar_fn = function(raw, ...) { 189 | choice_of_nums <- envvar_choice_of(1:11) 190 | switch(raw, shout = 10, whisper = 1, choice_of_nums(raw)) 191 | } 192 | ) 193 | ``` 194 | 195 | > Note that the `?envvar_fns` family of functions, like `envvar_choice_of()` 196 | > return _functions_. Although this is a very powerful mechanism of customizing 197 | > behaviors, it can look odd at first glance. 198 | > 199 | > We first generate our function by giving it which values to choose from 200 | > (`envvar_choice_of(1:11)`), then use that function when we have our raw value 201 | > (`choice_of_nums(raw)`). 202 | 203 | Now we need to update our `twist_and` function to work with our newly consistent 204 | numeric volumes. 205 | 206 | ```{r twist_and_eleven} 207 | twist_and_shout <- function(vol = opt("volume")) { 208 | lyric <- c( 209 | "Well, shake it up, baby, now (Shake it up, baby)", 210 | "Twist and shout (Twist and shout)" 211 | ) 212 | 213 | # handle case where volume knob is broken 214 | if (is.null(vol)) stop("someone turned off the stereo") 215 | 216 | # randomly uppercase characters to match volume 217 | lyric <- strsplit(tolower(lyric), "") 218 | lyric <- lapply(lyric, function(line) { 219 | char_sample <- runif(nchar(line)) < (vol - 1) / 9 220 | line[char_sample] <- toupper(line[char_sample]) 221 | paste0(line, collapse = "") 222 | }) 223 | 224 | # in case someone turns it up to 11 225 | if (vol == 11) lyric <- gsub("(\\s*\\(|\\))", "!!!\\1", lyric) 226 | 227 | cat(paste(lyric, collapse = "\n"), "\n") 228 | } 229 | ``` 230 | 231 | Let's try it out! 232 | 233 | ```{r} 234 | Sys.setenv(VOL = "whisper") 235 | twist_and_shout() 236 | ``` 237 | 238 | ```{r} 239 | Sys.setenv(VOL = 5) 240 | twist_and_shout() 241 | ``` 242 | 243 | ```{r} 244 | Sys.setenv(VOL = "shout") 245 | twist_and_shout() 246 | ``` 247 | 248 | ```{r} 249 | Sys.setenv(VOL = 11) 250 | twist_and_shout() 251 | ``` 252 | 253 | ```{r, error = TRUE} 254 | Sys.setenv(VOL = "off") # parsed as NULL 255 | twist_and_shout() 256 | ``` 257 | 258 | Looks pretty good! We handle just the inputs we want, without having to worry 259 | about unexpected data slipping into our R code. 260 | -------------------------------------------------------------------------------- /vignettes/options.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "options" 3 | subtitle: _Simple, Consistent Package Options_ 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{options} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, include = FALSE} 12 | knitr::opts_chunk$set( 13 | eval = FALSE, 14 | collapse = TRUE, 15 | comment = "#>" 16 | ) 17 | ``` 18 | 19 | R's options are a useful tool for managing global settings. `options` aims to 20 | make them easy to configure and use, reduce boilerplate code and handle more 21 | involved options handling so you don't have to. 22 | 23 | # Defining Options 24 | 25 | While writing your package, we start by defining which options our package might 26 | use. 27 | 28 | Define your options using the `define_options()` shorthand. Interlace 29 | descriptions and default values to define multiple options at once. 30 | 31 | ```{r defining_options} 32 | options::define_options( 33 | "This is an example of how a package author would document their internally 34 | used options. This option could make the package default to executing 35 | quietly.", 36 | quiet = TRUE, 37 | 38 | "Multiple options can be defined, providing default values if a global option 39 | or environment variable isn't set.", 40 | second_example = FALSE, 41 | 42 | "Default values are lazily evaluated, so you are free to use package functions 43 | without worrying about build-time evaluation order", 44 | lazy_example = fn_not_defined_until_later() 45 | ) 46 | ``` 47 | 48 | When you want more control, you can use `define_option()` to declare all aspects 49 | of how your option behaves. 50 | 51 | ```{r defining_an_option} 52 | options::define_option( 53 | option = "concrete_example", 54 | default = TRUE, 55 | desc = paste0( 56 | "Or, if you prefer a more concrete constructor you can define each option ", 57 | "explicitly." 58 | ), 59 | option_name = "mypackage_concrete", # define custom option names 60 | envvar_name = "MYPACKAGE_CONCRETE", # and custom environment variable names 61 | envvar_fn = envvar_is_true() # and use helpers to handle envvar parsing 62 | ) 63 | ``` 64 | 65 | # Documentation 66 | 67 | As long as the options have been created as shown above, documenting your 68 | options is as easy as adding this small roxygen stub within your package. 69 | 70 | ```{r as_roxygen_docs} 71 | #' @eval options::as_roxygen_docs() 72 | NULL 73 | ``` 74 | 75 | Which produces a `?mypackage::options` help page. Moreover, any `options` page 76 | will show up in an index when using `?options` to search for documentation, 77 | making it easier to discover which packages have options for you to use. 78 | 79 | ``` 80 | mypackage Options 81 | 82 | Description: 83 | 84 | Internally used, package-specific options. All options will 85 | prioritize R options() values, and fall back to environment 86 | variables if undefined. If neither the option nor the environment 87 | variable is set, a default value is used. 88 | 89 | Options: 90 | 91 | quiet 92 | This is an example of how a package author would document their 93 | internally used options. This option could make the package default to 94 | executing quietly. 95 | 96 | default: 97 | 98 | TRUE 99 | 100 | option: mypackage.quiet 101 | 102 | envvar: R_MYPACKAGE_QUIET (raw) 103 | ... 104 | ``` 105 | 106 | When your options are used as default values to parameters, you can use the 107 | option documentation to populate your function parameter docs. 108 | 109 | This is made simple when all of your parameters share the same names as your 110 | options. 111 | 112 | ```{r as_params} 113 | #' @eval options::as_params() 114 | #' @name options_params 115 | #' 116 | NULL 117 | 118 | #' Count to Three 119 | #' 120 | #' @inheritParams option_params 121 | #' 122 | count_to_three <- function(quiet = opt("quiet")) { 123 | for (i in 1:3) if (!quiet) cat(i, "\n") 124 | } 125 | ``` 126 | 127 | In situations where you have identically named parameters where you _don't_ want 128 | to inherit the option documentation, you can provide their names to `as_params` 129 | to use just a subset of options. You can also reassign documentation for an 130 | option to a parameter of a different name. 131 | 132 | ```{r as_params_renamed} 133 | #' Hello World! 134 | #' 135 | #' @eval options::as_params("silent" = "quiet") 136 | #' 137 | hello <- function(who, silent = opt("quiet")) { 138 | cat(paste0("Hello, ", who, "!"), "\n") 139 | } 140 | ``` 141 | 142 | # Customizing Behaviors 143 | 144 | When using `define_option` you can set the `option_name` and `envvar_name` that 145 | will be used directly. 146 | 147 | But it can be tedious and typo-prone to write these out for each and every 148 | option. Instead, you might consider providing a function that sets the default 149 | format for your option and environment variable names. 150 | 151 | For this, you can use `set_option_name_fn` and `set_envvar_name_fn`, which each 152 | accept a function as an argument. This function accepts two arguments, a 153 | package name and internal option name, which it uses to produce and return the 154 | corresponding global option name or environment variable name. 155 | 156 | ```{r set_name_fn} 157 | options::set_option_name_fn(function(package, name) { 158 | tolower(paste0(package, ".", name)) 159 | }) 160 | 161 | options::set_envvar_name_fn(function(package, name) { 162 | gsub("[^A-Z0-9]", "_", toupper(paste0(package, "_", name))) 163 | }) 164 | ``` 165 | --------------------------------------------------------------------------------