knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
potions
is a package for easily storing and retrieving information via
options()
. It therefore provides functionality somewhat similar to
{settings}
, but with
syntax based more closely on
{here}
. The intended use
of potions
is for adding novel information to options()
for use within
single packages or workflows.
potions
has three basic functions:
brew()
to store datapour()
to retrieve datadrain()
to clear dataThe first step is to store data using brew()
, which accepts data in three
formats:
brew(x = 1)
list
, e.g. brew(list(x = 1))
brew(file = "my-config-file.yml")
Information stored using brew
can be retrieved using pour
:
library(potions) brew(x = 1) paste0("The value of x is ", pour("x")) drain()
Because potions
uses a novel S3
object for all data
storage, it never overwrites existing global options, and is therefore safe
to use without affecting existing workflows. For example, print.default
takes
it's default digits
argument from getOption("digits")
:
options("digits") # set to 7 by default print(pi)
If we use potions
to set digits
, we do not affect this behaviour. Instead,
the user must specifically retrieve data using pour
for these settings to be
applied:
library(potions) brew(digits = 3) print(pi, digits = pour("digits")) # using potions print(pi) # default is unaffected
This feature - i.e. storing data in a novel S3
object - means that potions
can distinguish between interactive use in the console versus being called
within a package. Data can be provided and used independently by multiple
packages, and in the console, without generating conflicts.
Options stored using potions
are not persistent across sessions; you will
need to reload options each time you open a new workspace. It is unlikely,
therefore, that you will need to 'clear' the data stored by potions
at any
point. If you do need to remove data, you can do so using drain()
(without
any further arguments).
config
filesOften it is necessary to share a script, but without sharing certain sensitive
information necessary to run the code. A common example is API keys or other
sensitive information required to download data from a web service. In such
cases, the default, interactive method of using brew()
is insufficient, i.e.
# start of script brew(list("my-secret-key" = "123456")) # shares secret information
To avoid this problem, you can instead supply the path to a file containing that information, i.e.
brew(file = "config.yml") # hides secret information
You can then simply add the corresponding file name to your gitignore
, and
your script will still run, without sharing sensitive information.
potions
in package developmentWhen weighing up architectural decisions about how packages should share information between functions, there are a few solutions that developers can choose between:
sysdata.rda
, which supports
internal use of named objects while avoiding options()
completely.options()
, and for which
there is no override, it is possible to temporarily reset options()
within a
function. In these cases, CRAN requires that the initial state be restored
after use, for which on.exit()
is a sensible choice (See Advanced R section 6.7.4).potions
or
settings can be valuable.To use potions
in a package development situation,
create a file in the R
directory called onLoad.R
, containing the following
code:
.onLoad <- function(libname, pkgname) { if(pkgname == "packagenamehere") { potions::brew(.pkg = "packagenamehere") } }
This is important because it tells potions
that you are developing a package,
what that package is called, and where future calls to brew()
from within that
package should place their data. It is also possible to add defaults here, e.g.
.onLoad <- function(libname, pkgname) { if(pkgname == "packagenamehere") { potions::brew( n_attempts == 5, verbose == TRUE, .pkg = "packagenamehere") } }
Often when developing a package, you will want users to call your own
configuration function, rather than call brew()
directly. This provides
greater control over the names & types of data stored by potions
, which in
turn gives you - the developer - greater certainty when calling those data
within your package via pour()
. For example, you might want to specify that
a specific argument is supplied as numeric:
packagename_config <- function(fontsize = 10){ if(!is.numeric(fontsize)){ rlang::abort("Argument `fontsize` must be a number") } brew(list(fontsize = fontsize)) }
An additional benefit of writing a wrapper function is to allow users to provide
their own config
file. The easiest way to do this is to support a file
argument within your own function, then pass this directly to brew()
:
packagename_config <- function(file = NULL){ if(!is.null(file)){ brew(file = file) } }
This approach is risky, however, as it doesn't allow any checks. An alternative
is to intercept the file, run your own checks, then pass the result to brew()
:
packagename_config <- function(file = NULL){ if(!is.null(file)){ config_data <- potions::read_config(x) # add any checks to `data` that are needed here if(length(names(data)) != length(data)){ rlang::abort("Not all entries are named!") } # pass to `brew` brew(config_data) } }
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.