Ubiquity of Vignettes
Few things to keep in mind about vignettes in the eyes of an everyday R user...
- Available on the CRAN package page (it's a great quality check on the package if the authors have one!)
- Added to the package binaries and show up on the vignette help section via
browseVignettes(package = "pkgname")
[as you have pointed out]
- Embedded within the R package source
- Possibility to use embedded code in vignette as a separate script demo in
/demos
via demo()
- The most important part of all: None of the users really know about vignettes at all.
Thus, if you spend a considerable amount of time on the package documentation, you probably should at least indicate on startup such a feature exists.
Exploiting packageStartupMessage()
within .onAttach()
for the users benefit.
On package load, the console is relatively clear and the user will definitely see the red text as the contrast is relatively high between normal blue R text (assuming no crayon). However, there are a few cases where it does not make sense to alert the user to the presence of vignettes.
- If the session is non-interactive (e.g. bash script).
- If the user is an R 'pro'
- If the user has been using your package for a long, long, long time leading the user to disregard messages.
Thus, there is negative to a poorly implemented packageStartupMessage()
.
As a result, I would recommend introducing the startup message under four different conditions.
- (All) Check to see if a human is present by querying
interactive()
. If so, then proceed:
- (Standard) Have a list of at least 3 different startup messages and randomly select one per startup to display;
- (Normal) Randomly generate a number to determine if a start up message should be displayed;
- (Pro) Count package loads, after
x
package loads kill the startup message until the user reinstalls the package.
I'll present two solutions next that follows the tenet of (1) - (3) and then just (1) and (4) for simplicity sake.
Randomly Selecting Package Hints
Within this version we seek to simply check for humans, provide a break from adding a package load message, and randomly picking one hint to display. Only one dependency is needed outside of the scope of base
, the stats::runif
command to generate a probability between [0,1].
#' @importFrom stats runif
.onAttach <- function(...) {
# If interactive, hide message
# o.w. check against rng seed.
if (!interactive() || stats::runif(1) > 0.5){
return()
}
# Create a list of helpful tips
pkg_hints = c(
"Check for updates and report bugs at https://cran.r-project.org/web/packages/pkgname/.",
"Use `suppressPackageStartupMessages()` to remove package startup messages.",
"To see the user guides use `browseVignettes('pkgname')`"
)
# Randomly pick one hint
startup_hint = sample(pkg_hints, 1)
# Display hint
packageStartupMessage(paste(strwrap(startup_hint), collapse = "\n"))
}
Counting Package Loads
To use a counter, we make use of the ability to save into the package's install directory and not the "working" directory / user space. This has a few problems associated with it that I'll briefly mention:
- The users might only have the rights to view but not modify the
system
library (user libraries are okay).
- Thus, they will always receive package hints under this method as they cannot increment the counter.
- The counter might be shared if the package is in a
system
library.
- This would cause all the hints to be used quickly.
- Error logs may increase in size.
Of course, you could create your own folder within the user space via R_USER
or HOME
environment variables to negate these problems. (Exercise left to the reader, hint: Use Sys.getenv()
and dir.create()
.)
Regardless, one of the beauties of this function is at a later time you could probably include a "send package usage statistics" function within the package. This would actual give a reasonably accurate - phone home - statistic vs. the current RStudio CRAN mirror package download info. But, I digress.
For this method to work, we need to do a bit more prep work on the initial package supplied to CRAN. Specifically, we should probably pre-setup a counter via:
# Set working directory to package
setwd("package_dir")
# Create the inst directory if it does not exist
if(!dir.exists("inst")){
dir.create("inst")
}
# Create a counter variable
pkg_count = 0
# Save it into a .rda file
save(pkg_count, file="inst/counter.rda")
And now, onto the .onAttach()
counter implementation!
.onAttach <- function(...){
if (!interactive()) return()
# Get the install directory location of the counter
path_count = system.file("counter.rda", package = "pkgname")
# Suppress messages by default
displayMsg = FALSE
# Are we able to load the counter?
a = tryCatch(load(path_count), error = function(e){"error"}, warning = function(e){"warning"})
# Set error variable
count_error = a %in% c("error","warning")
# Check if the count is loaded
if(!count_error){
# Check if load count is low...
if(pkg_count < 10){
pkg_count = pkg_count + 1
# Try to save out
try(save(pkg_count, file = path_count), silent=TRUE)
displayMsg = T
}
}
# Display message (can be suppressed with suppressPackageStartupMessages)
if(displayMsg){
packageStartupMessage("use `browseVignettes('packagename')` to see vignettes")
}
}
A last thought
Please keep in mind that if you have any package dependencies (e.g. Depend:
in DESCRIPTION
), they may have their own set of startup messages that will be displayed before the ones written in your package.
library(packagename)
loads? – Fairydata.table
uses github.com/Rdatatable/data.table/blob/… – Fairy