Thinking of creating a new package? Dread the task of function documentation? Afraid to run devtools::check(build_args = '--as-cran') and get bombarded by Errors, Warnings, and Notes (oh my!) ? Wait…. breathe!

After feeling all that anxiety and following all of Hadley’s directions online, I felt I was doing a lot of manual labour, and that a package should be doing all lot of this for me. So we wrote one - sinew (sin-yu).

tl;dr: sinew is an automatic roxygen2 documentation creator

Quick example, run it in R:

 install.packages("sinew") # or devtools::install_github('metrumresearchgroup/sinew')
 
 # simple function
 
 myFun <- function(h=1){
   utils::head(rnorm(10),h)
 }
 
 library(sinew)
 makeOxygen(myFun)

This will produce the following output (which you can then append above your definition of myFun):

makeOxygen(myFun)

#' @title FUNCTION_TITLE
#' @description FUNCTION_DESCRIPTION
#' @param h PARAM_DESCRIPTION, Default: 1
#' @return OUTPUT_DESCRIPTION
#' @details DETAILS
#' @examples 
#' \dontrun{
#' if(interactive()){
#'  #EXAMPLE1
#'  }
#' }
#' @seealso 
#'  \code{\link[utils]{head}}
#' @rdname myFun
#' @export 
#' @importFrom utils head
myFun <- function(h=1){
  utils::head(h)
}

or document functions interactively with the shiny gadget:

For more details read the rest of the post, or skip straight to the gitbook: https://metrumresearchgroup.github.io/sinew/.

Why sinew?

Let me explain by example…

The current way to start down the documentation path is to create a function, I’ll use myFun defined above. Next I would use the skeleton provided by RStudio (In the toolbar Code=>Insert Roxygen Skeleton).

#' Title
#'
#' @param h 
#'
#' @return
#' @export
#'
#' @examples
myFun <- function(h=1){
  utils::head(h)
}

This gets me on my way, but there is information nested within the function itself that can be useful to document and manage the namespace

  • the default value of h
  • the function head needs to be imported from the package utils

I could just add that manually but this is just a toy example, actual functions have many parameters and you can import many functions from a number of different packages.

Trying to do the documentation bookkeeping as you develop the function will make you lose your train of thought, and make you forget that great idea you just had. Or you could write the full function and then at the end try to figure out what functions you need to import what packages they are from, should they get a seealso field, what parameters you used and so on…

Sinew will do this for you. It will parse your full function for relevant information that can fill in the blanks in the roxygen2 documentation and manage your import fields for you. Think of it as connecting the meat of your function to the Roxygen2 skeleton - or just the definition of the term sinew (again sin-yu). Added bonus it will help keep your package CRAN ready as you develop.

So how would that skeleton look after running it with sinew?

The workhorse of sinew is makeOxygen, it takes functions and returns Roxygen2 headers.

makeOxygen(myFun)

#' @title FUNCTION_TITLE
#' @description FUNCTION_DESCRIPTION
#' @param h PARAM_DESCRIPTION, Default: 1
#' @return OUTPUT_DESCRIPTION
#' @details DETAILS
#' @examples 
#' \dontrun{
#' if(interactive()){
#'  #EXAMPLE1
#'  }
#' }
#' @seealso 
#'  \code{\link[utils]{head}}
#' @rdname myFun
#' @export 
#' @importFrom utils head
myFun <- function(h=1){
  utils::head(h)
}

That little function had a lot of information embbeded in it to make your documentation well rounded.

  • Each field has it’s own placeholder that will guide you to what is expected.
  • param has the default value built in.
  • examples is populated with a more current layout to support htmlwidget and shiny examples.
  • utils was found and was added to importFrom automatically as was it’s seealso counterpart.
  • rdname was added with the name of the function.

Now lets take a real function - lm

makeOxygen(lm)

#' @title FUNCTION_TITLE
#' @description FUNCTION_DESCRIPTION
#' @param formula PARAM_DESCRIPTION
#' @param data PARAM_DESCRIPTION
#' @param subset PARAM_DESCRIPTION
#' @param weights PARAM_DESCRIPTION
#' @param na.action PARAM_DESCRIPTION
#' @param method PARAM_DESCRIPTION, Default: 'qr'
#' @param model PARAM_DESCRIPTION, Default: TRUE
#' @param x PARAM_DESCRIPTION, Default: FALSE
#' @param y PARAM_DESCRIPTION, Default: FALSE
#' @param qr PARAM_DESCRIPTION, Default: TRUE
#' @param singular.ok PARAM_DESCRIPTION, Default: TRUE
#' @param contrasts PARAM_DESCRIPTION, Default: NULL
#' @param offset PARAM_DESCRIPTION
#' @param ... PARAM_DESCRIPTION
#' @return OUTPUT_DESCRIPTION
#' @details DETAILS
#' @examples 
#' \dontrun{
#' if(interactive()){
#'  #EXAMPLE1
#'  }
#' }
#' @seealso 
#'  \code{\link[stats]{model.frame}}
#' @rdname lm
#' @export 
#' @importFrom stats model.frame
lm

That would of been a pain to do by hand, all that is left to do is replace the placeholders with relevant information and you’re done. Also notice the layout is consistent so other people (and you) can easily navigate the help across the package functions.

A few features and use cases:

  • Setting package options persistent during a session is done using sinew_opts (much like knitr::opts_chunk).

  • For keeping settings persistent across sessions during package development sinew reads from the working directory on load a file named _sinewconfig.yml.

  • To run makeOxygen in batch mode (like a whole ./R package subdirectory) use the function makeOxyFile.

  • To convert a long single file with multiple functions interwoven with a body script into multiple single function files, and keeping the script body intact, use untangle. If a function within the long file already has roxygen2 headers then they will be carried over to the new file.

  • To quickly run through a whole package and create the DESCRIPTION file Imports: field use makeImport('./R',format='description')

  • After updating a function the documentation gets out of synch and needs updating. moga (make oxygen great again!) will cross check the current documentation of an R script and the current function script itself and update/add any parameters/defaults/imports discrepancies.

  • Setting cut to an integer value in makeOxygen to return import package instead of importFrom package function1 [function2 …] for packages that call more than the value assigned to cut.

Interactive Documentation

A Shiny gadget was built to combine all this into an easy to use interface. Highlight text (preferably a function) in source editor of RStudio and then deploy the addin from the addins menu.

The gadget can read in almost any source you have the function in: unsourced functions, functions in the global environment, in a loaded package, in an installed package that you didn’t load yet.

The gadget will allow you to preview the roxygen header, add and remove roxygen2 fields with checkboxes and control the level of cut. When you’re ready insert what you see in the preview pane into the editor. When you’re done with one function highlight another one and continue to work, if you need to move to another file tab… that’s ok too!

If there is already documentation move into update mode, and touch up any changes that need to be made through moga.