Build a package with a few demo functions using common Software Engineering Practices:
- use
devtoolsto create and check/build - use
testthatfor unit tests - use
roxygen2for (in-source) documentation
28 January 2016
Build a package with a few demo functions using common Software Engineering Practices:
devtools to create and check/buildtestthat for unit testsroxygen2 for (in-source) documentationThe real question is, "why not?"
extractPhoneNumbers() – we want to get this in a package:
extractPhoneNumbers <- function(inputStr) {
# imports
`%>%` <- stringr::`%>%`
inputStr %>% stringr::str_replace_all(pattern = "[()+\\-_. ]",
replacement = "") %>%
stringr::str_extract_all(pattern = "[:digit:]{10}")
}
We're need:
bestPracticesWalkThroughL6-L21:
# set the package's info -- the first two settings should
# probably go in your .Rprofile
options(
devtools.desc.author = '"Guy Fawkes <guy@fawkes.net> [aut,cre]"',
devtools.desc.license = "GPL-3",
devtools.desc = list( # set other package info:
Version = "0.0.1",
Title = "Demo Package",
Description = "This package is supposed to demonstrate \
the basics of package development with devtools & roxygen2...")
)
devtools::create(
path = pkgName <- "demoPkg", # TERRIBLE FORM...
rstudio = FALSE, # remove auxiliary files
check = TRUE) # validate your DESCRIPTION parameters
demoPkg/
+ DESCRIPTION # properly filled
+ NAMESPACE # (properly) empty
+ R/ # empty
+ man/ # empty
demoPkg/DESCRIPTION:
Package: demoPkg
Title: Demo Package
Version: 0.0.1
Authors@R: "Guy Fawkes <guy@fawkes.net> [aut,cre]"
Description: This package is supposed to demonstrate
the basics of package development with devtools & roxygen2...
Depends:
R (>= 3.2.3)
License: GPL-3
LazyData: true
RoxygenNote: 5.0.1
This is how R knows:
demoPkg depends on (either Depends, Imports, or Suggests – devtool's DESCRIPTION is a good example of all three.R (>= 3.2.3)) but this is best practice.# Generated by roxygen2: do not edit by hand
This is how R knows:
::. E.g. devtools::create. (@export)@import[From])roxygen2From the DESCRIPTION:
A 'Doxygen'-like in-source documentation system for Rd, collation, and 'NAMESPACE' files.
Really does marry source code and documentation:
#' @title short description
#' @description longer description
#' @param x describe parameter x here
#' @returns describe the function's output
#' @export
#' @importFrom pkgX fxnY
#' @examples
#' # put your example code here
someFunction <- function(x) {
return(NULL)
}roxygen2 won't recognize "roxumentation" unless it starts with #'.
# (without the ') is just a regular R comment!!!roxygen2 looks for "tags" (e.g. @title, @param, etc.) and processes everything after the tag (inside the #') until another tag is found.@title, and @description:
@description cannot be the same as @titleroxygen2::roxygenise() will convert roxumentation in R/.*\.R into valid .Rd files.roxument your script's functions!@template[Var] (power-user tags).extractPhoneNumbers.RL1-L27 snippet:
#' @title Extract Phone Numbers
#'
#' @description Search for and extract all 10-digit phone numbers
#' in a string, provided the phone numbers are visually delimited
#' with the typical characters (i.e. '(', ')', '.', '+', '-', or ' ').
#'
#' @param inputStr a character vector (supposedly) containing phone
#' numbers to be extracted.
#'
#' @return a list of length equal to \code{inputStr} whose entries
#' contain character vectors whose entries are all extracted phone
#' numbers from the individual entries in \code{inputStr} (if any).
#'
#' @importFrom stringr %>% str_replace_all str_extract_all
#' @export
devtools::document() to update .Rd's and NAMESPACE…# Generated by roxygen2: do not edit by hand export(extractPhoneNumbers) importFrom(stringr,"%>%") importFrom(stringr,str_extract_all) importFrom(stringr,str_replace_all)
extractPhoneNumbers() imports some functions from stringr – let's make those explicit:
# demoPkg/R/extractPhoneNumbers.R
# ... SNIP ...
extractPhoneNumbers <- function(inputStr) {
# imports
`%>%` <- stringr::`%>%`
str_replace_all <- stringr::str_replace_all
str_extract_all <- stringr::str_extract_all
# ... SNIP ...We need to update our package dependencies accordingly:
devtools::use_package('stringr', type='Imports', pkg=pkgName) use_package is idempotent!
Package: demoPkg
Title: Demo Package
Version: 0.0.1
Authors@R: "Guy Fawkes <guy@fawkes.net> [aut,cre]"
Description: This package is supposed to demonstrate
the basics of package development with devtools & roxygen2...
Depends:
R (>= 3.2.3)
License: GPL-3
LazyData: true
RoxygenNote: 5.0.1
Imports: stringr
We're going to want to explicitly state which version of stringr:
Imports: stringr (>= 1.0.0)
A lot has been written on (unit) testing:
We can setup the testthat infrastructure with:
devtools::use_testthat(pkg = pkgName)
# create empty test file, test-extractPhoneNumbers
devtools::use_test("extractPhoneNumbers.R", pkg = pkgName)Fill test-extractPhoneNumbers.R:
# demoPkg/tests/testthat/test-extractPhoneNumbers.R
context("extractPhoneNumbers")
test_that("expected cases are properly extracted", {
testStrings <- c("1234567890", "123 456 7890")
expectedOutput <- list("1234567890", "1234567890")
expect_equal(extractPhoneNumbers(testStrings), expectedOutput)
})We can check:
devtools::dev_help()devtools::test()devtools::check()devtools::install()
You can execute the entire gist inside the interpreter:
gistURL <- 'https://gist.github.com/stevenpollack/' gistURL <- paste0(gistURL, '141b14437c6c4b071fff') devtools::source_gist(gistURL)
Don't be shy!
devtools is setup in a such a way that you'll find yourself going through the following pattern/workflow:
dev_mode().setwd())load_all())test())document())DESCRIPTION (e.g. version numbers and dependencies)R CMD check (check())install())dev_mode() and install updated package into original library.