#' Set the base path of the script
#'
#' \code{box::set_script_path(path)} explicitly tells \pkg{box} the path of a
#' given script from which it is called; \code{box::script_path()} returns the
#' previously set path.
#' @usage \special{box::set_script_path(path)}
#' @param path character string containing the relative or absolute path to the
#' currently executing \R code file, or \code{NULL} to reset the path.
#' @return Both \code{box::script_path} and \code{box::set_script_path} return
#' the previously set script path, or \code{NULL} if none was explicitly set.
#' \code{box::set_script_path} returns its value invisibly.
#'
#' @details
#' \pkg{box} needs to know the base path of the topmost calling \R context (i.e.
#' the script) to find relative import locations. In most cases, \pkg{box} can
#' figure the path out automatically. However, in some cases third-party
#' packages load code in a way in which \pkg{box} cannot find the correct path
#' of the script any more. \code{box::set_script_path} can be used in these
#' cases to set the path of the currently executing \R script manually.
#'
#' @note
#' \pkg{box} \emph{should} be able to figure out the script path automatically.
#' Using \code{box::set_script_path} should therefore never be necessary.
#' \href{https://github.com/klmr/box/issues/new?assignees=&labels=\%E2\%9A\%A0\%EF\%B8\%8F+bug&title=\%5Bset_script_path\%5D\%20&template=bug-report.yml}{Please
#' file an issue} if you encounter a situation that necessitates using
#' \code{box::set_script_path}!
#'
#' @examples
#' box::set_script_path('scripts/my_script.r')
#' @name script_path
#' @export
set_script_path = function (path = NULL) {
old_path = script_path()
script_path_env$script_path = path
script_path_env$value = if (is.null(path)) NULL else dirname(path)
invisible(old_path)
}
#' @usage \special{box::script_path()}
#' @rdname script_path
#' @export
script_path = function () {
script_path_env$script_path
}
#' Get a module’s path
#'
#' The following functions retrieve information about the path of the directory
#' that a module or script is running in.
#' @param mod a module environment or namespace
#' @return \code{path} returns a character string containing the module’s full
#' path.
#' @keywords internal
path = function (mod) {
UseMethod('path')
}
#' @export
`path.box$mod` = function (mod) {
attr(mod, 'info')$source_path
}
#' @export
`path.box$ns` = function (mod) {
namespace_info(mod, 'info')$source_path
}
#' @return \code{base_path} returns a character string containing the module’s
#' base directory, or the current working directory if not invoked on a module.
#' @rdname path
base_path = function (mod) {
normalizePath(module_path(mod), winslash = '/', mustWork = FALSE)
}
script_path_env = new.env(parent = emptyenv())
#' @return \code{module_path} returns a character string that contains the
#' directory in which the calling R code is run. See \sQuote{Details}.
#' @details
#' \code{module_path} takes a best guess at a script’s path, since R does not
#' provide a sure-fire way for determining the path of the currently executing
#' code. The following calling situations are covered:
#'
#' \enumerate{
#' \item Path explicitly set via \code{set_script_path}
#' \item Path of a running document/application (\pkg{knitr}, \pkg{Shiny})
#' \item Path of unit test cases (\pkg{testthat})
#' \item Path of the currently opened source code file in RStudio
#' \item Code invoked as \command{Rscript script.r}
#' \item Code invoked as \command{R CMD BATCH script.r}
#' \item Code invoked as \command{R -f script.r}
#' \item Script run interactively (use \code{getwd()})
#' }
#' @rdname path
module_path = function (mod) {
for (test in path_tests) {
path = test(mod)
if (! is.null(path)) {
# Don’t cache result, since it might change suddenly, e.g. due to
# knitr or Shiny running in the same process.
return(path)
}
}
throw('unreachable code')
}
#' @return \code{mod_path} returns the script path associated with a \pkg{box}
#' module
#' @rdname path
mod_path = function (mod) {
tryCatch(dirname(path(mod)), error = function (e) NULL)
}
#' @return \code{explicit_path} returns the script path explicitly set by the
#' user, if such a path was set.
#' @rdname path
explicit_path = function (...) {
script_path_env$value
}
#' @return \code{r_path} returns the directory in which the current script is
#' run via \command{Rscript}, \command{R CMD BATCH} or \command{R -f}.
#' @rdname path
r_path = function (...) {
args = commandArgs()
if (length((file_arg = grep('^--file=', args))) != 0L) {
unescape_path_arg(dirname(sub('--file=', '', args[file_arg])))
} else if (length((f_arg = grep('^-f$', args))) != 0L) {
unescape_path_arg(dirname(args[f_arg + 1L]))
}
}
unescape_path_arg = if (tolower(Sys.info()[['sysname']]) == 'windows') {
identity
} else {
function (path) {
# Translated from src/unix/system.c:unescape_arg
# But only unescape spaces, not newlines, since the latter are only escaped
# when passing code via `-e`, never in file paths.
gsub('~+~', ' ', path, fixed = TRUE)
}
}
#' @return \code{knitr_path} returns the directory in which the currently knit
#' document is run, or \code{NULL} if not called from within a \pkg{knitr}
#' document.
#' @rdname path
knitr_path = function (...) {
if (! 'knitr' %in% loadedNamespaces()) return(NULL)
knitr_input = suppressWarnings(knitr::current_input(dir = TRUE))
if (! is.null(knitr_input)) dirname(knitr_input)
}
#' @return \code{shiny_path} returns the directory in which a \pkg{Shiny}
#' application is running, or \code{NULL} if not called from within a
#' \pkg{Shiny} application.
#' @rdname path
shiny_path = function (...) {
if (! 'shiny' %in% loadedNamespaces()) return()
in_shiny =
(utils::packageVersion('shiny') < '1.6.0' && shiny::isRunning()) || {
# `isRunning` no longer works in Shiny 1.6.0:
# <https://github.com/rstudio/shiny/issues/3499>
shiny_ns = getNamespace('shiny')
any(map_lgl(identical, lapply(sys.frames(), base::topenv), shiny_ns))
}
if (in_shiny) getwd()
}
#' @return \code{testthat_path} returns the directory in which \pkg{testthat}
#' code is being executed, or \code{NULL} if not called from within a
#' \pkg{testthat} test case.
#' @rdname path
testthat_path = function (...) {
if (Sys.getenv('TESTTHAT') %==% 'true') getwd()
}
#' @return \code{rstdio_path} returns the directory in which the currently
#' active RStudio script file is saved.
#' @rdname path
rstudio_path = function (...) {
# .Platform$GUI is not yet set to "RStudio" during startup, so checking for
# it *might* be insufficient in corner cases.
# However, we cannot merely check whether the `RSTUDIO` environment
# variable is set, because it is also set inside the RStudio terminal; and
# when ‘box’ is invoked from a script that is run in the terminal, we do
# *not* want to use RStudio’s active document, since that isn’t the script
# from which we are called.
# See also comments at <https://stackoverflow.com/a/35849779/1968>.
if (.Platform$GUI %!=% 'RStudio') return(NULL)
document_path = if (requireNamespace('rstudioapi', quietly = TRUE)) {
rstudioapi::getActiveDocumentContext()$path
} else {
# ‘rstudioapi’ might not be installed. Attempt to use the internal API
# as a fallback.
tryCatch(
as.environment('tools:rstudio')$.rs.api.getActiveDocumentContext()$path,
error = function (.) {
bug_reports_url = utils::packageDescription(.packageName)$BugReports
warning(fmt(
'It looks like the code is run from inside RStudio but ',
'{.packageName;\'} is unable to identify the calling ',
'document. This should not happen. Please consider filing ',
'a bug report at <{bug_reports_url}>.'
))
''
}
)
}
if (document_path %==% '') {
# The active document wasn’t saved yet, or the code is invoked from the
# R REPL/console.
getwd()
} else {
dirname(document_path)
}
}
#' @return \code{wd_path} returns the current working directory.
#' @rdname path
wd_path = function (...) {
# Fallback
getwd()
}
path_test_hooks = c('mod', 'explicit', 'knitr', 'shiny', 'testthat', 'rstudio', 'r', 'wd')
path_tests = mget(paste0(path_test_hooks, '_path'))
#' Path related functions
#'
#' \code{mod_search_path} returns the character vector of paths where module
#' code can be located and will be found by \pkg{box}.
#'
#' @note The search paths are ordered from highest to lowest priority.
#' The current module’s path always has the lowest priority.
#'
#' There are two ways of modifying the module search path: by default,
#' \code{getOption('box.path')} specifies the search path as a character vector.
#' Users can override its value by separately setting the environment variable
#' \env{R_BOX_PATH} to one or more paths, separated by the platform’s path
#' separator (\dQuote{:} on UNIX-like systems, \dQuote{;} on Windows).
#' @keywords internal
#' @name paths
mod_search_path = function (caller) {
env_value = strsplit(Sys.getenv('R_BOX_PATH'), .Platform$path.sep)[[1L]]
c(
env_value %||% getOption('box.path'),
system_mod_path,
calling_mod_path(caller)
)
}
#' \code{calling_mod_path} determines the path of the module code that is
#' currently calling into the \pkg{box} package.
#'
#' @param caller the environment from which \code{box::use} was invoked.
#' @return \code{calling_mod_path} the path of the source module that is calling
#' \code{box::use}, or the script’s path if the calling code is not a module.
#' @rdname paths
calling_mod_path = function (caller) {
calling_ns = mod_topenv(caller)
# FIXME: Make work for modules imported inside package, if necessary.
module_path(calling_ns)
}
#' \code{split_path(path)} is a platform independent and filesystem logic
#' aware alternative to \code{strsplit(path, '/')[[1L]]}.
#' @param path the path
#' @return \code{split_path} returns a character vector of path components that
#' logically represent \code{path}.
#' @rdname paths
split_path = function (path) {
if (path %==% dirname(path)) {
path
} else {
c(Recall(dirname(path)), basename(path))
}
}
#' \code{merge_path(split_path(path))} is equivalent to \code{path}.
#' @param components character string vector of path components to merge
#' @return \code{merge_path} returns a single character string that is
#' logically equivalent to the \code{path} passed to \code{split_path}.
#' logically represent \code{path}.
#' @note \code{merge_path} is the inverse function to \code{split_path}.
#' However, this does not mean that its result will be identical to the
#' original path. Instead, it is only guaranteed that it will refer to the same
#' logical path given the same working directory.
#' @rdname paths
merge_path = function (components) {
do.call('file.path', as.list(components))
}
#' \code{sanitize_path_fragment(path)} replaces invalid characters in the given
#' \emph{relative} path, making the result a valid Windows path.
#' @rdname paths
sanitize_path_fragment = function (path) {
win32_reserved_path_chars = '[<>:"/\\|?*]'
# Replace invalid chars with `-`, which is unlikely to appear in R names.
# This isn’t particularly important, but it decreases the risk of name
# clashes e.g. for path names of the interactive HTML help.
gsub(win32_reserved_path_chars, '-', path)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.