-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathquick-start-guide.Rmd
More file actions
195 lines (157 loc) · 6.4 KB
/
quick-start-guide.Rmd
File metadata and controls
195 lines (157 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
---
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
<a href="https://cran.r-project.org/package=settings">`{settings}`</a>, but with
syntax based more closely on
<a href="https://cran.r-project.org/package=here">`{here}`</a>. 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)
}
}
```