A dictionary of messages used by the package. We separate these into its own file to avoid cluttering the R code with a multitude of strings.
messages <- list(
invalid_module_path = c("Invalid path passed to the ",
sQuote("path"), " parameter to the ", sQuote("module"),
" function: I received a ", crayon::red("{{{klass}}}"),
" but was expecting a ", crayon::yellow("character"), "."),
invalid_module_character_path = c("Invalid path passed to the ",
sQuote("path"), " parameter to the ", sQuote("module"),
" function: I expected a non-NA non-blank character vector
of length 1")
)
Cleanse the message a little after fetching it from the messages
list.
msg <- function(name) {
stopifnot(name %in% names(messages))
The gsub
will squish multiple spaces into a single space,
while the paste(collapse = "", ...)
usage will ensure we
can take vectors of characters in the above messages
list.
paste(collapse = "", gsub("[ ]+", " ", messages[[name]]))
}
We use the whisker templating engine to inject any additional values into the message string. For example,
m("invalid_module_path", klass = "bloop")
would return the appropriate error with the string “bloop” injected in the appropriate place.
m <- function(name, ...) {
Note the use of do.call
,
a very handy R metaprogramming tool when we do not know exactly which
arguments we will pass.
do.call(whisker::whisker.render, list(msg(name), list(...)))
}
# TODO: (RK) Dcoument modules more extensively.
Typical modularization of R code is done through packages. However, packages have a few design limitations that prevents their widespread use by developers.
Both of these limitations are legitimate problems because organizing large codebases managed by teams of dozens or hundreds of developers is difficult or unpleasant without a sane hierarchical namespacing system.
Modules aim to partially allay this problem by building a hierarchical namespacing mechanism on top of environments, a well-defined and well-understood primitive data structure in R.
#' Load and attach an R module in the current environment.
#'
#' Modules are light wrappers around environments that provide
#' better support for hierarching namespacing.
#'
#' @param path character. If \code{path} is a character vector giving
#' an installed package in the current library path, a module
#' encompassing that package will be returned. Otherwise, the
#' directory given by the path will be loaded as a module.
#' @return a \code{module} object.
module <- function(path) {
if (!is.character(path)) {
stop(m("invalid_module_path", klass = class(path)[1L]))
}
if (!is.simple_string(path)) {
stop(m("invalid_module_character_path"))
}
module_(path)
}
module_ <- function(path) {
if (package_exists(path)) {
object <- package(path)
} else {
object <- file(path)
}
module_new(object)
}
module_new <- function(object) {
UseMethod("module_new")
}
module_new.file <- function(object) {
NULL
}
module_new.package <- function(object) {
NULL
}
#' An R namespace system that supersedes packages by allowing nesting.
#'
#' @name module
#' @import module
#' @docType package
NULL
#' An S3 class representing metadata about a package.
#'
#' @param name character. The name of the package. It
#' must be installed in the user's currently active
#' library.
package <- function(name) {
# TODO: (RK) Find path using system.file, determine
# if installed.
structure(list(name = name), class = "package")
}
`%||%` <- function(x, y) if (is.null(x)) y else x
is.simple_string <- function(x) {
is.character(x) && length(x) == 1 &&
!is.na(x) && nzchar(x)
}
file <- function(path) {
}