---
title: "Quick start guide"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Quick start guide}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
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.
## Basic usage
`potions` has three basic functions:
- `brew()` to store data
- `pour()` to retrieve data
- `drain()` to clear data
The first step is to store data using `brew()`, which accepts data in three
formats:
- Named arguments, e.g. `brew(x = 1)`
- A `list`, e.g. `brew(list(x = 1))`
- A configuration file, e.g. `brew(file = "my-config-file.yml")`
Information stored using `brew` can be retrieved using `pour`:
```{r}
library(potions)
brew(x = 1)
paste0("The value of x is ", pour("x"))
drain()
```
## Interactions with global options
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")`:
```{r}
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:
```{r}
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).
## Using `config` files
Often 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.
```{r eval = FALSE}
# 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.
```{r eval = FALSE}
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.
## Using `potions` in package development
When weighing up architectural decisions about how packages should share
information between functions, there are a few solutions that developers
can choose between:
- Where a developer needs to be able to call static information across multiple
functions, an efficient solution is to use `sysdata.rda`, which supports
internal use of named objects while avoiding `options()` completely.
- Where a function relies on information stored in `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](https://adv-r.hadley.nz/functions.html#on-exit)).
- Finally, where there is a need for dynamic, package-wide options that can be
changed by the developer or the user, packages such as `potions` or
[settings](https://cran.r-project.org/package=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:
```{r, eval=FALSE}
.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.
```{r, eval=FALSE}
.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:
```{r, eval = FALSE}
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()`:
```{r, eval = FALSE}
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()`:
```{r, eval = FALSE}
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)
}
}
```