pkgconfig/0000755000175100001440000000000013064242264012242 5ustar hornikuserspkgconfig/inst/0000755000175100001440000000000013063734634013225 5ustar hornikuserspkgconfig/inst/README.Rmd0000644000175100001440000000713313063726637014636 0ustar hornikusers # Private configuration for R packages [![Linux Build Status](https://travis-ci.org/gaborcsardi/pkgconfig.svg?branch=master)](https://travis-ci.org/gaborcsardi/pkgconfig) [![Windows Build status](https://ci.appveyor.com/api/projects/status/github/gaborcsardi/pkgconfig?svg=true)](https://ci.appveyor.com/project/gaborcsardi/pkgconfig) [![](http://www.r-pkg.org/badges/version/pkgconfig)](http://www.r-pkg.org/pkg/pkgconfig) [![](http://cranlogs.r-pkg.org/badges/pkgconfig)](http://www.r-pkg.org/pkg/pkgconfig) [![Coverage Status](https://img.shields.io/codecov/c/github/gaborcsardi/pkgconfig/master.svg)](https://codecov.io/github/gaborcsardi/pkgconfig?branch=master) Easy way to create configuration parameters in your R package. Configuration values set in different packages are independent. Call `set_config()` to set a configuration parameter. Call `get_config()` to query it. ## Installation Use the `devtools` package: ```r devtools::install_github("gaborcsardi/pkgconfig") ``` ## Typical usage > Note: this is a real example, but it is not yet implemented in > the CRAN version of the `igraph` package. The igraph package has two ways of returning a set of vertices. Before version 1.0.0, it simply returned a numeric vector. From version 1.0.0 it sets an S3 class on this vector by default, but it has an option called `return.vs.es` that can be set to `FALSE` to request the old behavior. The problem with the `return.vs.es` option is that it is global. Once set to `FALSE` (interactively or from a package), R will use that setting in all packages, which breaks packages that expect the new behavior. `pkgconfig` solves this problem, by providing configuration settings that are private to packages. Setting a configuration key from a given package will only apply to that package. ## Workflow Let's assume that two packages, `pkgA` and `pkgB`, both set the igraph option `return.vs.es`, but `pkgA` sets it to `TRUE`, and `pkgB` sets it to `FALSE`. Here is how their code will look. ### `pkgA` `pkgA` imports `set_config` from the `pkgconfig` package, and sets the `return.vs.es` option from it's `.onLoad` function: ```r .onLoad <- function(lib, pkg) { pkgconfig::set_config("igraph::return.vs.es" = TRUE) } ``` ### `pkgB` `pkgB` is similar, but it sets the option to `FALSE`: ```r .onLoad <- function(lib, pkg) { pkgconfig::set_config("igraph::return.vs.es" = FALSE) } ``` ### `igraph` The igraph package will use `get_config` to query the option, and will supply a fallback value for the cases when it is not set: ```r return_vs_es_default <- TRUE # ... igraph_func <- function() { # ... pkgconfig::get_config("igraph::return.vs.es", return_vs_es_default) # ... } ``` If `igraph_func` is called from `pkgA` (maybe through other packages), `get_config` will return `TRUE`, and if it is called from `pkgB`, `get_config` will return `FALSE`. For all other packages the `igraph::return.vs.es` option is not set, and the default value is used, as specified in `igraph`. ## What if `pkgA` calls `pkgB`? It might happen that both `pkgA` and `pkgB` set an option, and `pkgA` also calls functions from `pkgB`, which in turn, might call `igraph`. In this case the package that is further down the call stack wins. In other words, if the call sequence looks like this: ``` ... -> pkgA -> ... -> pkgB -> ... -> igraph ``` then `pkgB`'s value is used in `igraph`. (Assuming the last `...` does not contain a call to `pkgA` of course.) ## Feedback Please comment in the [Github issue tracker](https://github.com/gaborcsardi/pkgconfig/issues) of the project. ## License MIT © [Gábor Csárdi](https://github.com/gaborcsardi) pkgconfig/inst/README.markdown0000644000175100001440000000713313063726645015735 0ustar hornikusers # Private configuration for R packages [![Linux Build Status](https://travis-ci.org/gaborcsardi/pkgconfig.svg?branch=master)](https://travis-ci.org/gaborcsardi/pkgconfig) [![Windows Build status](https://ci.appveyor.com/api/projects/status/github/gaborcsardi/pkgconfig?svg=true)](https://ci.appveyor.com/project/gaborcsardi/pkgconfig) [![](http://www.r-pkg.org/badges/version/pkgconfig)](http://www.r-pkg.org/pkg/pkgconfig) [![](http://cranlogs.r-pkg.org/badges/pkgconfig)](http://www.r-pkg.org/pkg/pkgconfig) [![Coverage Status](https://img.shields.io/codecov/c/github/gaborcsardi/pkgconfig/master.svg)](https://codecov.io/github/gaborcsardi/pkgconfig?branch=master) Easy way to create configuration parameters in your R package. Configuration values set in different packages are independent. Call `set_config()` to set a configuration parameter. Call `get_config()` to query it. ## Installation Use the `devtools` package: ```r devtools::install_github("gaborcsardi/pkgconfig") ``` ## Typical usage > Note: this is a real example, but it is not yet implemented in > the CRAN version of the `igraph` package. The igraph package has two ways of returning a set of vertices. Before version 1.0.0, it simply returned a numeric vector. From version 1.0.0 it sets an S3 class on this vector by default, but it has an option called `return.vs.es` that can be set to `FALSE` to request the old behavior. The problem with the `return.vs.es` option is that it is global. Once set to `FALSE` (interactively or from a package), R will use that setting in all packages, which breaks packages that expect the new behavior. `pkgconfig` solves this problem, by providing configuration settings that are private to packages. Setting a configuration key from a given package will only apply to that package. ## Workflow Let's assume that two packages, `pkgA` and `pkgB`, both set the igraph option `return.vs.es`, but `pkgA` sets it to `TRUE`, and `pkgB` sets it to `FALSE`. Here is how their code will look. ### `pkgA` `pkgA` imports `set_config` from the `pkgconfig` package, and sets the `return.vs.es` option from it's `.onLoad` function: ```r .onLoad <- function(lib, pkg) { pkgconfig::set_config("igraph::return.vs.es" = TRUE) } ``` ### `pkgB` `pkgB` is similar, but it sets the option to `FALSE`: ```r .onLoad <- function(lib, pkg) { pkgconfig::set_config("igraph::return.vs.es" = FALSE) } ``` ### `igraph` The igraph package will use `get_config` to query the option, and will supply a fallback value for the cases when it is not set: ```r return_vs_es_default <- TRUE # ... igraph_func <- function() { # ... pkgconfig::get_config("igraph::return.vs.es", return_vs_es_default) # ... } ``` If `igraph_func` is called from `pkgA` (maybe through other packages), `get_config` will return `TRUE`, and if it is called from `pkgB`, `get_config` will return `FALSE`. For all other packages the `igraph::return.vs.es` option is not set, and the default value is used, as specified in `igraph`. ## What if `pkgA` calls `pkgB`? It might happen that both `pkgA` and `pkgB` set an option, and `pkgA` also calls functions from `pkgB`, which in turn, might call `igraph`. In this case the package that is further down the call stack wins. In other words, if the call sequence looks like this: ``` ... -> pkgA -> ... -> pkgB -> ... -> igraph ``` then `pkgB`'s value is used in `igraph`. (Assuming the last `...` does not contain a call to `pkgA` of course.) ## Feedback Please comment in the [Github issue tracker](https://github.com/gaborcsardi/pkgconfig/issues) of the project. ## License MIT © [Gábor Csárdi](https://github.com/gaborcsardi) pkgconfig/inst/NEWS.markdown0000644000175100001440000000102413063726471015542 0ustar hornikusers # 2.0.1 No changes in functionality, only internal cleanup. # 2.0.0 * Can also be used from the global environment, not only from packages. * `set_config_in()` function, to allow custom APIs. This means that packages does not have to use `set_config()` and `get_config()`, but they can provide their own API. * Fix a `get_config()` bug, for composite values only the first element was returned. * Fix a bug when key was not set at all. In these cases `fallback` was ignored in `get_config()`. # 1.0.0 Initial release. pkgconfig/tests/0000755000175100001440000000000013063724415013406 5ustar hornikuserspkgconfig/tests/testthat.R0000644000175100001440000000033413063724415015371 0ustar hornikusers if (require(testthat, quietly = TRUE) && require(disposables, quietly = TRUE)) { library(pkgconfig) test_check("pkgconfig") } else { cat("The testthat and disposables packages are required for unit tests") } pkgconfig/tests/testthat/0000755000175100001440000000000013064242264015244 5ustar hornikuserspkgconfig/tests/testthat/test-globalenv.R0000644000175100001440000000115613063730714020321 0ustar hornikusers context("Global env") test_that("Global env does not bother packages", { evalq(set_config(key3 = "value"), .GlobalEnv) on.exit(try(evalq(set_config(key3 = NULL), .GlobalEnv)), add = TRUE) on.exit(try(disposables::dispose_packages(pkgs)), add = TRUE) pkgs <- disposables::make_packages( pkgA = { setter <- function() { set_config(key3 = "value2") } getter <- function() { utility::getter() } }, utility = { getter <- function() { get_config("key3", "fallback") } } ) pkgA::setter() expect_equal(get_config("key3"), "value") expect_equal(pkgA::getter(), "value2") }) pkgconfig/tests/testthat/test-api.R0000644000175100001440000000167613063531351017124 0ustar hornikusers context("Creating a custom API") test_that("We can create a custom API", { on.exit(try(disposables::dispose_packages(pkgs))) pkgs <- disposables::make_packages( utility = { set_opt <- function(...) { pars <- list(...) names(pars) <- paste0("utility::", names(pars)) do.call(pkgconfig::set_config_in, c(pars, list(.in = parent.frame()))) } get_opt <- function(key) { real_key <- paste0("utility::", key) pkgconfig::get_config(real_key) } }, pkgA = { setter <- function() { utility::set_opt(key4 = "value_A") } getter <- function() { utility::get_opt("key4") } }, pkgB = { setter <- function() { utility::set_opt(key4 = "value_B") } getter <- function() { utility::get_opt("key4") } } ) pkgA::setter() pkgB::setter() expect_equal(pkgA::getter(), "value_A") expect_equal(pkgB::getter(), "value_B") }) pkgconfig/tests/testthat/test-errors.R0000644000175100001440000000024013063731625017657 0ustar hornikusers context("Errors") test_that("Arguments must be named", { expect_error( set_config("foo" = "bar", "foobar"), "Some parameters are not named" ) }) pkgconfig/tests/testthat/tests.R0000644000175100001440000000532413064176463016544 0ustar hornikusers context("Session") test_that("Session variables", { on.exit(try(disposables::dispose_packages(pkgs))) pkgs <- disposables::make_packages( pkgconfigtest = { f <- function() { set_config(foo = "bar") get_config("foo") } g <- function() { get_config("foo") } h <- function() { get_config("foobar") } } ) expect_equal(f(), "bar") expect_equal(g(), "bar") expect_null(h()) }) test_that("Composite values", { on.exit(try(disposables::dispose_packages(pkgs))) pkgs <- disposables::make_packages( pkgconfigtest = { f <- function() { set_config(foo = list(1,2,3)) get_config("foo") } g <- function() { get_config("foo") } h <- function() { get_config("foobar") } } ) expect_equal(f(), list(1,2,3)) expect_equal(g(), list(1,2,3)) expect_null(h()) }) context("Keys are private") test_that("Two packages do not interfere", { on.exit(try(disposables::dispose_packages(pkgs))) pkgs <- disposables::make_packages( pkgA = { setter <- function() { pkgconfig::set_config(key = "A") } getter <- function() { utility::getter() } }, pkgB = { setter <- function() { pkgconfig::set_config(key = "B") } getter <- function() { utility::getter() } }, utility = { getter <- function() { pkgconfig::get_config("key") } } ) pkgA::setter() pkgB::setter() expect_equal(pkgA::getter(), "A") expect_equal(pkgB::getter(), "B") }) test_that("Cannot get if set by another package", { on.exit(try(disposables::dispose_packages(pkgs))) pkgs <- disposables::make_packages( pkgconfigtest1 = { getter <- function() { get_config("foo") } getter_parent <- function() { getter() } }, pkgconfigtest2 = { setter <- function() { set_config(foo = "bar") } } ) pkgconfigtest2::setter() expect_null(pkgconfigtest1::getter_parent()) }) test_that("Setting from .onLoad works fine", { if (utils::packageVersion("disposables") < "1.0.3") { skip("test needs disposables >= 1.0.3") } on.exit(try(disposables::dispose_packages(pkgs)), add = TRUE) pkgs <- disposables::make_packages( utility = { getter <- function() { pkgconfig::get_config("key", "fallback") } }, pkgA = { .onLoad <- function(lib, pkg) { pkgconfig::set_config(key = "A") } getter <- function() { utility::getter() } }, pkgB = { .onLoad <- function(lib, pkg) { pkgconfig::set_config(key = "B") } getter <- function() { utility::getter() } }, pkgC = { getter <- function() { utility::getter() } } ) expect_equal(pkgA::getter(), "A") expect_equal(pkgB::getter(), "B") expect_equal(pkgC::getter(), "fallback") }) pkgconfig/NAMESPACE0000644000175100001440000000022012550764310013453 0ustar hornikusers# Generated by roxygen2 (4.1.1): do not edit by hand export(get_config) export(set_config) export(set_config_in) importFrom(utils,packageName) pkgconfig/R/0000755000175100001440000000000013063730224012440 5ustar hornikuserspkgconfig/R/utils.R0000644000175100001440000000011413063730224013717 0ustar hornikusers `%||%` <- function(lhs, rhs) { if (!is.null(lhs)) { lhs } else { rhs } } pkgconfig/R/pkgconfig-package.R0000644000175100001440000000061312546331036016126 0ustar hornikusers #' Persistent configuration for R packages #' #' This package is meant to be used in other packages, and provides #' configuration options for them. #' #' It is currently very minimal, and has two main functions: one #' for setting configuration options (\code{\link{set_config}}), and one #' for querying them (\code{\link{get_config}}). #' #' @docType package #' @name pkgconfig-package NULL pkgconfig/R/getset.R0000644000175100001440000000631312551001503014051 0ustar hornikusers ## This is the environment that stores all parameters config <- new.env() ## ---------------------------------------------------------------------- #' Query a configuration parameter key #' #' Query a configuration parameter key, and return the value #' set in the calling package(s). #' #' @details #' This function is meant to be called from the package whose #' behavior depends on it. It searches for the given configuration #' key, and if it exists, it checks which package(s) it was called #' from and returns the configuration setting for that package. #' #' If the key is not set in any calling package, but it is set in #' the global environment (i.e. at the R prompt), then it returns that #' setting. #' #' If the key is not set anywhere, then it returns \code{NULL}. #' #' @param key The name of the parameter to query. #' @param fallback Fallback if the parameter id not found anywhere. #' @return The value of the parameter, or the fallback value if not found. #' #' @export get_config <- function(key, fallback = NULL) { result <- get_from_session(key) if (is.null(result)) fallback else result[[1]] } get_from_session <- function(key) { value <- config[[key]] if (is.null(value)) return(NULL) pkgs <- sys.frames() pkgs <- lapply(pkgs, parent.env) pkgs <- Filter(pkgs, f = isNamespace) pkgs <- vapply(pkgs, environmentName, "") pkgs <- unique(pkgs) for (p in rev(pkgs)) { if (p %in% names(value)) return(value[p]) } if ("R_GlobalEnv" %in% names(value)) { return(value["R_GlobalEnv"]) } NULL } ## ---------------------------------------------------------------------- #' Set a configuration parameter #' #' Set a configuration parameter, for the package we are calling from. #' If called from the R prompt and not from a package, then it sets #' the parameter for global environment. #' #' @param ... Parameters to set, they should be all named. #' @return Nothing. #' #' @export #' @seealso \code{\link{set_config_in}} set_config <- function(...) { set_config_in(..., .in = parent.frame()) } check_named_args <- function(...) { nn <- names(list(...)) if (is.null(nn) || any(nn == "")) { stop("Some parameters are not named") } } #' Set a configuration parameter for a package #' #' This is a more flexible variant of \code{link{set_config}}, #' and it allows creating an custom API in the package that #' uses \code{pkgconfig} for its configuration. #' #' @details #' This function is identical to \code{\link{set_config}}, but it allows #' supplying the environment that is used as the package the configuration #' is set for. This makes it possible to create an API for setting #' (and getting) configuration parameters. #' #' @param ... Parameters to set, they should be all named. #' @param .in An environment, typically belonging to a package. #' @return Nothing. #' #' @export #' @seealso \code{\link{set_config}} #' @importFrom utils packageName set_config_in <- function(..., .in = parent.frame()) { check_named_args(...) who <- packageName(env = .in) %||% "R_GlobalEnv" set_config_session(who = who, ...) } set_config_session <- function(who, ...) { l <- list(...) for (n in names(l)) { key <- config[[n]] %||% list() key[[who]] <- l[[n]] config[[n]] <- key } } pkgconfig/MD50000644000175100001440000000166613064242264012563 0ustar hornikusers0866c2f4d8ce2f24b8d1ffe424e12f84 *DESCRIPTION 4b7ff7c348e978f33981b2237200878d *LICENSE cd8e0bb63bd54833fe1e01ad1a8647d6 *NAMESPACE 9f75eb47d58b82e003713579356c2af6 *R/getset.R 1531da19568f0512ff768360cb78540f *R/pkgconfig-package.R 72539c3bc185b91ce53a54f2bc8336b2 *R/utils.R 126f0602cc011b89c24915b2cf4af4e2 *inst/NEWS.markdown 650807f1f0cac8233dfd54cfdf0d6454 *inst/README.Rmd 650807f1f0cac8233dfd54cfdf0d6454 *inst/README.markdown df949f83f265547f97085e9ad1891a6b *man/get_config.Rd e97c9e9fa3c680d05d3758f07448ef2b *man/pkgconfig-package.Rd 006950b03f96ba261abaeb6f225df012 *man/set_config.Rd d80ab36e7a5e1b8863b79fbbd50466b9 *man/set_config_in.Rd 0138a95afd3fe43ad69771aa955b910a *tests/testthat.R 98b113f008c249e31b7035ba76077f84 *tests/testthat/test-api.R da73dc4a696ec2066812ab8c467ab361 *tests/testthat/test-errors.R 909e8b0e2558c28ffef1cfae84b8139d *tests/testthat/test-globalenv.R a196e36e881f8baf7306dd633ada68e2 *tests/testthat/tests.R pkgconfig/DESCRIPTION0000644000175100001440000000124013064242264013745 0ustar hornikusersPackage: pkgconfig Title: Private Configuration for 'R' Packages Version: 2.0.1 Author: Gábor Csárdi Maintainer: Gábor Csárdi Description: Set configuration options on a per-package basis. Options set by a given package only apply to that package, other packages are unaffected. License: MIT + file LICENSE LazyData: true Imports: utils Suggests: covr, testthat, disposables (>= 1.0.3) URL: https://github.com/gaborcsardi/pkgconfig BugReports: https://github.com/gaborcsardi/pkgconfig/issues Encoding: UTF-8 NeedsCompilation: no Packaged: 2017-03-21 10:15:26 UTC; gaborcsardi Repository: CRAN Date/Publication: 2017-03-21 15:20:20 UTC pkgconfig/man/0000755000175100001440000000000012550764310013015 5ustar hornikuserspkgconfig/man/set_config_in.Rd0000644000175100001440000000154412550764363016126 0ustar hornikusers% Generated by roxygen2 (4.1.1): do not edit by hand % Please edit documentation in R/getset.R \name{set_config_in} \alias{set_config_in} \title{Set a configuration parameter for a package} \usage{ set_config_in(..., .in = parent.frame()) } \arguments{ \item{...}{Parameters to set, they should be all named.} \item{.in}{An environment, typically belonging to a package.} } \value{ Nothing. } \description{ This is a more flexible variant of \code{link{set_config}}, and it allows creating an custom API in the package that uses \code{pkgconfig} for its configuration. } \details{ This function is identical to \code{\link{set_config}}, but it allows supplying the environment that is used as the package the configuration is set for. This makes it possible to create an API for setting (and getting) configuration parameters. } \seealso{ \code{\link{set_config}} } pkgconfig/man/set_config.Rd0000644000175100001440000000100212550764363015425 0ustar hornikusers% Generated by roxygen2 (4.1.1): do not edit by hand % Please edit documentation in R/getset.R \name{set_config} \alias{set_config} \title{Set a configuration parameter} \usage{ set_config(...) } \arguments{ \item{...}{Parameters to set, they should be all named.} } \value{ Nothing. } \description{ Set a configuration parameter, for the package we are calling from. If called from the R prompt and not from a package, then it sets the parameter for global environment. } \seealso{ \code{\link{set_config_in}} } pkgconfig/man/get_config.Rd0000644000175100001440000000173712550747602015425 0ustar hornikusers% Generated by roxygen2 (4.1.1): do not edit by hand % Please edit documentation in R/getset.R \name{get_config} \alias{get_config} \title{Query a configuration parameter key} \usage{ get_config(key, fallback = NULL) } \arguments{ \item{key}{The name of the parameter to query.} \item{fallback}{Fallback if the parameter id not found anywhere.} } \value{ The value of the parameter, or the fallback value if not found. } \description{ Query a configuration parameter key, and return the value set in the calling package(s). } \details{ This function is meant to be called from the package whose behavior depends on it. It searches for the given configuration key, and if it exists, it checks which package(s) it was called from and returns the configuration setting for that package. If the key is not set in any calling package, but it is set in the global environment (i.e. at the R prompt), then it returns that setting. If the key is not set anywhere, then it returns \code{NULL}. } pkgconfig/man/pkgconfig-package.Rd0000644000175100001440000000101712546335012016641 0ustar hornikusers% Generated by roxygen2 (4.1.1): do not edit by hand % Please edit documentation in R/pkgconfig-package.R \docType{package} \name{pkgconfig-package} \alias{pkgconfig-package} \title{Persistent configuration for R packages} \description{ This package is meant to be used in other packages, and provides configuration options for them. } \details{ It is currently very minimal, and has two main functions: one for setting configuration options (\code{\link{set_config}}), and one for querying them (\code{\link{get_config}}). } pkgconfig/LICENSE0000644000175100001440000000006213063724603013246 0ustar hornikusersYEAR: 2014--2017 COPYRIGHT HOLDER: Gábor Csárdi