The core of this tutorial gist lies in bestPracticesWalkThrough.R. Running assumes you have the following packages at versions equal (or above) those specified
library('devtools') # 1.9.1
library('testthat') # 0.11.0
library('stringr') # 1.0.0
library('git2r') # 0.12.1
If you want to run the tutorial in one shot, from the R interpreter (say):
devtools::source_gist('https://gist.github.com/stevenpollack/141b14437c6c4b071fff')
Though I've provided a few references below, reading the testthat
docs, and copying the roxygen code in
extractPhoneNumbers.R
as a template should get you up-and-running.
It doesn't matter what you read in a blog, or elsewhere, the letter of the law when it comes to
R packages is (and CRAN packages, in particular) is
Writing R Extensions
(the CRAN manual on packages). For cryptic errors that result from R CMD check
, all the way to
protocol for adding data to your package, you're gonna want to consult that resource. It's not an easy
read (at first), but you eventually get used to it, and will develop a muscle memory for where/how to
look certain things up.
That being said, writing packages to the letter of the law is a particularly onerous task, hence
why tools like roxygen2
, devtools
, and testthat
have been developed. The devtools
endorsed
check()
, build()
, install()
workflow is pretty well documented.
While roxygen2
has lots of documentation scattered throughout various source (e.g.
R Packages and
roxygen2
Vignettes,
there is one particular feature that I find shabbily documented: templates.
Roxygen templates are immensely powerful. The most basic way of using a template would be to have static content, maybe something like:
#' @param x a character or numeric vector
#' @param y a character or numeric vector
and you'd save this chunk of text in the man-roxygen/
of your package, under the filename
xyParam.R
. Then, if you had a function,
plot <- function(x, y, ...) {
# put real code here
}
you could skimp on the roxumentation by using the following roxygen block:
#' Plot Y vs. X
#'
#' @description Create a basic scatter plot that puts \code{y} on the
#' y-axis, and \code{x} on the x-axis. Categorical and numeric variables
#' are both valid input.
#'
#' @template xyParam
#' @param ... extra parameters to be passed onto \code{\link{someOtherFunction}}
#'
#' @return plot of y vs. x that's sent to the X11 (or XQuartz or whatever) server.
And you might content yourself to work like this for a while, but then you may find yourself writing a function,
threeWayDistance <- function(x, y, z) {
# more code
}
whose roxygen block looks like
#' Calculate the three-way distance between variables
#'
#' @description Some sort of mumbo-jumbo
#'
#' @template xyParam
#' @param z a character or numeric vector
#'
#' @return a numeric value indicating the distance between
#' \code{x}, \code{y}, and \code{z}.
Now we've hit a weird violation of the DRY-principle: the documentation
for z
is nearly identical to that of x
or y
. If only there was
a way to abstract the documentation so we didn't have to go through
all of this silliness!?!
The answer lies in an innocent paragraph in the Roxygen templates section of the intro vignette -- I'll quote the whole section, it's deceptively shallow:
Roxygen templates are R files containing only roxygen comments that live in the man-roxygen directory. Use
@template file-name
(without extension) to insert the contents of a template into the current documentation.You can make templates more flexible by using template variables defined with
@templateVar name value
. Template files are run with brew, so you can retrieve values (or execute any other arbitrary R code) with<%= name %>
.Note that templates are parsed a little differently to regular blocks, so you’ll need to explicitly set the title, description and details with
@title
,@description
and@details
.
You'll notice the second paragraph makes a very quick intro to
brew
. Understanding brew
(a templating language
that is not unlike knitr
) is paramount to successfully wielding template variables. The help for
brew::brew()
contains syntax explanations and examples in the details section; I won't repeat them, here,
but I will go through a practical example of using template variables that necessarily uses brew
capabilities.
We identified, above, that there seems to be some repetition above, with our @param
tag. What if we could
make the name of the parameter variable, so we could use a roxumentation block like:
#' @paramName x
#' @someTemplate
and the processed help file would look identical to what would've happened if we had written
#' @param x a character or numeric vector
We cannot achieve something exactly like above, BUT we can come close: if we build a template like
<%
# helper function to evaluate stringified R-code
evalString <- function(str) {
eval.parent(parse(text=str))
}
# helper constants
roxygenBlock <- c("@param", "a numeric or character vector")
# reassign paramName to the its passed in value
evalString(paste0("params <- ", paramNames))
# for each parameter, pump out the appropriate roxygen block;
# be sure to use `cat` since we are in a <\% and not a <\%= brew-chunk
sapply(params,
function(param) {
cat(paste(roxygenBlock[1], param, roxygenBlock[2]), fill = TRUE)
})
%>
and save this as man-roxygen/vectorParams.R
, then we could modify
threeWayDistance
's (and similarly plot
's) roxygen block to look like:
#' Calculate the three-way distance between variables
#'
#' @description Some sort of mumbo-jumbo
#'
#' @templateVar paramNames c('x', 'y', 'z')
#' @template vectorParams
#'
#' @return a numeric value indicating the distance between
#' \code{x}, \code{y}, and \code{z}.
To see this in action, with an extra layer of abstraction, I've included
vectorParams.R
and
threeWayDistance.R
in this gist, and in the package built during
bestPracticesWalkThrough.R.
Templates (in particular, brew
) can feel a bit awkward in the beginning -- brew
is a bit fussy when
you try and write a brew chunk (e.g. <% ... %>
or <%= ... %>
) inside roxygen's #'
. When your chunks
are sitting in open air (i.e. not proceeding any #
's), you can write R code like normal. When they're sitting
inside roxygen blocks, you need to be sure to wrap everyline. E.g.,
#' <%
#' stringFun <- function(str) {
#' paste0(str, collapse="8")
#' }
#' %>
will NOT initialize stringFun
... brew
will pretend as if you didn't even make it. Insteand, you'd
have to write
#' <% stringFun <- function(str) { %>
#' <% paste0(str, collapse="8") %>
#' <% } %>
... Don't think too hard about it, it's just a consequence of the #
's...
A good (production) usage of templates is the R-Shopify interface. For example, Shop.R and man-roxygen/api.r.