Title: | Write Reusable, Composable and Modular R Code |
Version: | 1.2.0 |
URL: | https://klmr.me/box/, https://github.com/klmr/box |
BugReports: | https://github.com/klmr/box/issues |
Description: | A modern module system for R. Organise code into hierarchical, composable, reusable modules, and use it effortlessly across projects via a flexible, declarative dependency loading syntax. |
Depends: | R (≥ 3.6.0) |
Imports: | tools |
License: | MIT + file LICENSE |
Encoding: | UTF-8 |
Suggests: | devtools, knitr (≥ 1.40), rmarkdown, R6, rlang, roxygen2 (≥ 7.2.1), shiny, stringr, testthat (≥ 3.1.7) |
Enhances: | rstudioapi |
VignetteBuilder: | knitr |
RoxygenNote: | 7.3.1 |
NeedsCompilation: | yes |
Packaged: | 2024-02-06 23:06:27 UTC; rudolpk2 |
Author: | Konrad Rudolph |
Maintainer: | Konrad Rudolph <konrad.rudolph@gmail.com> |
Repository: | CRAN |
Date/Publication: | 2024-02-06 23:50:02 UTC |
An alternative module system for R
Description
Use box::use(prefix/mod)
to import a module, or box::use(pkg)
to import a package. Fully qualified names are supported for nested modules,
reminiscent of module systems in many other modern languages.
Using modules & packages
Writing modules
Infrastructure and utility functions that are mainly used inside modules.
Interactive use
Functions for use in interactive sessions and for testing.
Author(s)
Maintainer: Konrad Rudolph konrad.rudolph@gmail.com (ORCID)
Other contributors:
Michael Schubert mschu.dev@gmail.com (ORCID) [contributor]
See Also
Useful links:
Extend code regions to include leading comments and whitespace
Description
Extend code regions to include leading comments and whitespace
Usage
add_comments(refs)
Arguments
refs |
a list of the code region |
Value
add_comment
returns a list of srcref
s corresponding to
srcref
, but extended to include the preceding comment block.
Collect export tag information
Description
Collect export tag information
Usage
create_export_block(expr, ref, info, mod_ns)
parse_object(info, expr, mod_ns)
roxygen2_object(alias, value, type)
Arguments
expr |
The unevaluated expression represented by the tag. |
ref |
The code reference |
alias |
The object name. |
value |
The object value. |
type |
The object type. |
Value
create_export_block
returns an object of type
roxy_block
represents an exported declaration expression, along with
its source code location.
Note
This could be represented much simpler but we keep compatibility with roxygen2 — at least for the time being — to make integration with the roxygen2 API easier, should it become necessary.
Retrieve a value or a default
Description
a %||% b
returns a
unless it is empty, in which case
b
is returned.
Usage
a %||% b
lhs %|% rhs
Arguments
a |
the value to return if non-empty |
b |
default value |
lhs |
vector with potentially missing values, or |
rhs |
vector with default values, same length as |
Value
a %||% b
returns a
, unless it is NULL
, empty,
FALSE
or ""
; in which case b
is returned.
lhs %|% rhs
returns a vector of the same length as
rhs
with all missing values in lhs
replaced by the
corresponding values in rhs
.
Explicitly declare module exports
Description
box::export
explicitly marks a source file as a box module. If
can be used as an alternative to the @export
tag comment to declare a
module’s exports.
Usage
box::export(...)
Arguments
... |
zero or more unquoted names that should be exported from the module. |
Details
box::export
can be called inside a module to specify the module’s
exports. If a module contains a call to box::export
, this call
overrides any declarations made via the @export
tag comment. When a
module contains multiple calls to box::export
, the union of all thus
defined names is exported.
A module can also contain an argument-less call to box::export
. This
ensures that the module does not export any names. Otherwise, a module that
defines names but does not mark them as exported would be treated as a
legacy module, and all default-visible names would be exported from
it. Default-visible names are names not starting with a dot (.
).
Another use of box::export()
is to enable a module without exports to
use module event hooks.
Value
box::export
has no return value. It is called for its
side effect.
Note
The preferred way of declaring exports is via the @export
tag
comment. The main purpose of box::export
is to explicitly prevent
exports, by being called without arguments.
See Also
box::use
for information on declaring exports via
@export
.
Find the full paths of files in modules
Description
Find the full paths of files in modules
Usage
box::file(...)
box::file(..., module)
Arguments
... |
character vectors of files or subdirectories inside a module; if none is given, return the root directory of the module |
module |
a module environment |
Value
A character vector containing the absolute paths to the files
specified in ...
.
Note
If called from outside a module, the current working directory is used.
This function is similar to system.file
for packages. Its semantics
differ in the presence of non-existent files: box::file
always returns
the requested paths, even for non-existent files; whereas system.file
returns empty strings for non-existent files, or fails (if requested via the
argument mustWork = TRUE
).
See Also
Find a module’s source location
Description
Find a module’s source location
Usage
find_in_path(spec, base_paths)
Arguments
spec |
a |
base_paths |
a character vector of paths to search the module in, in order of preference. |
Details
A module is physically represented in the file system either by ‘‹spec_name(spec)›.r’ or by ‘‹spec_name(spec)›/__init__.r’, in that order of preference in case both exist. File extensions are case insensitive to allow for R’s obsession with capital-R extensions (but lower-case are given preference, and upper-case file extensions are discouraged).
Value
find_in_path
returns a mod_info
that specifies the
module source location.
String formatting helpers
Description
Interpolate expressions in a string
Usage
fmt(..., envir = parent.frame())
chr(x)
html_escape(x)
interleave(a, b)
Arguments
... |
one or more unnamed character string arguments, followed optionally by named arguments |
x |
an object to convert |
a |
a character vector of length |
b |
a character vector of length |
Details
fmt
interpolates embedded expressions in a string.
chr
converts a value to a character vector; unlike
as.character
, it correctly deparses unevaluated names and expressions.
interleave
is a helper that interleaves two vectors a = c(a[1],
..., a[n])
and b = c(b[1], ..., b[n - 1])
.
The general format of an interpolation expression inside a fmt
string
is: {...}
interpolates the expression ...
. To insert
literal braces, double them (i.e. {{
, }}
). Interpolated
expressions can optionally be followed by a format modifier: if
present, it is specified via the syntax {...;modifier}
. The
following modifiers are supported:
\"
like
dQuote(...)
\'
like
sQuote(...)
‹fmt›f
like
sprintf('%‹fmt›f', ...)
Vectors of length > 1 will be concatenated as if using
toString
before interpolation.
Value
fmt(...)
concatenates any unnamed arguments, and
interpolates all embedded expressions as explained in the ‘Details’.
Named arguments are treated as locally defined variables, and are added to
(and override, in case of name reuse) names defined in the calling scope.
chr(x)
returns a string representation of a value or
unevaluated expression x
.
html_escape(x)
returns the HTML-escaped version of x
.
interleave(a, b)
returns a vector c(a[1], b[1], a[2],
b[2], ..., a[n - 1], b[n - 1], a[n])
.
Find @export
tags in code regions
Description
Find @export
tags in code regions
Usage
has_export_tag(ref)
Arguments
ref |
The code region |
Value
TRUE
if the given region is annotated with a @export
tag, FALSE
otherwise.
Display module documentation
Description
box::help
displays help on a module’s objects and functions in much
the same way help
does for package contents.
Usage
box::help(topic, help_type = getOption("help_type", "text"))
Arguments
topic |
either the fully-qualified name of the object or function to get
help for, in the format |
help_type |
character string specifying the output format; currently,
only |
Details
See the vignette at vignette('box', 'box')
for more information about
displaying help for modules.
Value
box::help
is called for its side effect when called directly
from the command prompt.
Helper functions for the help functionality
Description
help_topic_target
parses the expression being passed to the
help
function call to find the innermost module subset expression in
it.
find_env
acts similarly to find
, except that it
looks in the current environment’s parents rather than in the global
environment search list, it returns only one hit (or zero), and it returns
the environment rather than a character string.
call_help
invokes a help()
call expression for a package help
topic, finding the first help
function definition, ignoring the one
from this package.
Usage
help_topic_target(topic, caller)
find_env(name, caller)
call_help(call, caller)
Arguments
topic |
the unevaluated expression passed to |
caller |
the environment from which |
name |
the name to look for. |
call |
the patched |
Value
help_topic_target
returns a list of two elements containing
the innermost module of the help
call, as well as the name of the
object that’s the subject of the help
call. For help(a$b$c$d)
,
it returns list(c, quote(d))
.
Import a module or package
Description
Actual implementation of the import process
Usage
use_one(declaration, alias, caller, use_call)
load_and_register(spec, info, caller)
register_as_import(spec, info, mod_ns, caller)
defer_import_finalization(spec, info, mod_ns, caller)
finalize_deferred(info)
export_and_attach(spec, info, mod_ns, caller)
load_from_source(info, mod_ns)
load_mod(info)
mod_exports(info, spec, mod_ns)
mod_export_names(info, mod_ns)
attach_to_caller(spec, info, mod_exports, mod_ns, caller)
attach_list(spec, exports)
assign_alias(spec, mod_exports, caller)
assign_temp_alias(spec, caller)
Arguments
declaration |
an unevaluated use declaration expression without the
surrounding |
alias |
the use alias, if given, otherwise |
caller |
the client’s calling environment (parent frame) |
use_call |
the |
spec |
a module use declaration specification |
info |
the physical module information |
mod_ns |
the module namespace environment of the newly loaded module |
Details
use_one
performs the actual import. It is invoked by use
given
the calling context and unevaluated expressions as arguments, and only uses
standard evaluation.
load_and_register
performs the loading, attaching and exporting of a
module identified by its spec and info.
register_as_import
registers a use
declaration in the calling
module so that it can be found later on, if the declaration is reexported by
the calling module.
defer_import_finalization
is called by load_and_register
to
earmark a module for deferred initialization if it hasn’t been fully loaded
yet.
finalize_deferred
exports and attaches names from a module use
declaration which has been deferred due to being part of a cyclic loading
chain.
export_and_attach
exports and attaches names from a given module use
declaration.
load_from_source
loads a module source file into its newly created,
empty module namespace.
load_mod
tests whether a module or package was already loaded and, if
not, loads it.
mod_exports
returns an export environment containing a copy of the
module’s exported objects.
attach_to_caller
attaches the listed names of an attach specification
for a given use declaration to the calling environment.
assign_alias
creates a module/package object in calling environment,
unless it contains an attach declaration, and no explicit alias is given.
assign_temp_alias
creates a placeholder object for the module in the
calling environment, to be replaced by the actual module export environment
once the module is completely loaded (which happens in the case of cyclic
imports).
Value
use_one
does not currently return a value. — This might change
in the future.
load_mod
returns the module or package namespace environment
of the specified module or package info.
mod_exports
returns an export environment containing the
exported names of a given module.
mode_export_names
returns a vector containing the same names as
names(mod_exports(info, spec, mod_ns))
but does not create an export
environment.
attach_list
returns a named character vector of the names in
an attach specification. The vector’s names are the aliases, if provided, or
the attach specification names themselves otherwise.
Note
If a module is still being loaded (because it is part of a cyclic
import chain), load_and_register
earmarks the module for deferred
registration and holds off on attaching and exporting for now, since not all
its names are available yet.
Information about a physical module or package
Description
A mod_info
represents an existing, installed module and its runtime
physical location (usually in the file system).
Usage
mod_info(spec, source_path)
pkg_info(spec)
Arguments
spec |
a |
source_path |
character string full path to the physical module location. |
Value
mod_info
and pkg_info
return a structure representing
the module/package information for the given specification/source location.
Environment of loaded modules
Description
Each module is stored as an environment inside loaded_mods
with the
module’s code location path as its identifier. The path rather than the
module name is used because module names are not unique: two modules called
a
can exist nested inside modules b
and c
, respectively.
Yet these may be loaded at the same time and need to be distinguished.
Usage
loaded_mods
is_mod_loaded(info)
register_mod(info, mod_ns)
deregister_mod(info)
loaded_mod(info)
is_mod_still_loading(info)
mod_loading_finished(info, mod_ns)
Arguments
info |
the mod info of a module |
mod_ns |
module namespace environment |
Format
loaded_mods
is an environment of the loaded module and package
namespaces.
Details
is_mod_loaded
tests whether a module is already loaded.
register_mod
caches a module namespace and marks the module as loaded.
deregister_mod
removes a module namespace from the cache, unloading
the module from memory.
loaded_mod
retrieves a loaded module namespace given its info.
is_mod_still_loading
tests whether a module is still being loaded.
mod_loading_finished
signals that a module has been completely loaded.
Note
is_mod_still_loading
and mod_loading_finished
are used to
break cycles during the loading of modules with cyclic dependencies.
Return a list of function names in an environment
Description
Return a list of function names in an environment
Usage
lsf(envir)
Arguments
envir |
the environment to search in. |
Value
lsf
returns a vector of function names in the given environment.
Apply function to elements in list
Description
map
applies a function to lists of arguments, similar to Map
in
base R, with the argument USE.NAMES
set to FALSE
.
flatmap
performs a recursive map: the return type is always a vector
of some type given by the .default
, and if the return value of calling
.f
is a vector, it is flattened into the enclosing vector (see
‘Examples’).
transpose
is a special map
application that concatenates its
inputs to compute a transposed list.
Usage
map(.f, ...)
flatmap(.f, ..., .default)
flatmap_chr(.f, ...)
vmap(.f, .x, ..., .default)
map_int(.f, ...)
map_lgl(.f, ...)
map_chr(.f, ...)
transpose(...)
Arguments
.f |
an n-ary function where n is the number of further arguments given |
... |
lists of arguments to map over in parallel |
.default |
the default value returned by |
Value
map
returns a (potentially nested) list of values resulting
from applying .f
to the arguments.
flatmap
returns a vector with type given by .default
,
or .default
, if the input is empty.
transpose
returns a list of the element-wise concatenated
input vectors; that is, a “transposed list” of those elements.
Examples
flatmap_chr(identity, NULL) # character(0) flatmap_chr(identity, c('a', 'b')) # [1] "a" "b" flatmap_chr(identity, list(c('a', 'b'), 'c')) # [1] "a" "b" "c" transpose(1 : 2, 3 : 4) # [[1]] # [1] 1 3 # # [[2]] # [1] 2 4
Hooks for module events
Description
Modules can declare functions to be called when a module is first loaded.
Usage
.on_load(ns)
.on_unload(ns)
Arguments
ns |
the module namespace environment |
Details
To create module hooks, modules should define a function with the specified name and signature. Module hooks should not be exported.
When .on_load
is called, the unlocked module namespace environment is
passed to it via its parameter ns
. This means that code in
.on_load
is permitted to modify the namespace by adding names to,
replacing names in, or removing names from the namespace.
.on_unload
is called when modules are unloaded. The (locked) module
namespace is passed as an argument. It is primarily useful to clean up
resources used by the module. Note that, as for packages, .on_unload
is not necessarily called when R is shut down.
Legacy modules cannot use hooks. To use hooks, the module needs to
contain an export specification (if the module should not export any names,
specify an explicit, empty export list via
box::export()
.
Value
Any return values of the hook functions are ignored.
Note
The API for hook functions is still subject to change. In particular, there might in the future be a way to subscribe to module events of other modules and packages, equivalently to R package userhooks.
Get a module’s name
Description
Get a module’s name
Usage
box::name()
Value
box::name
returns a character string containing the name of
the module, or NULL
if called from outside a module.
Note
Because this function returns NULL
if not invoked inside a
module, the function can be used to check whether a code is being imported as
a module or called directly.
Module namespace handling
Description
make_namespace
creates a new module namespace.
Usage
make_namespace(info)
is_namespace(env)
namespace_info(ns, which, default = NULL)
namespace_info(ns, which) <- value
mod_topenv(env = parent.frame())
is_mod_topenv(env)
Arguments
info |
the module info. |
env |
an environment that may be a module namespace. |
ns |
the module namespace environment. |
which |
the key (as a length 1 character string) of the info to get/set. |
default |
default value to use if the key is not set. |
value |
the value to assign to the specified key. |
Details
The namespace contains a module’s content. This schema is very much like R package organisation. A good resource for this is: <http://obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/>
Value
make_namespace
returns the newly created module namespace for
the module described by info
.
Note
Module namespaces aren’t actual R package namespaces. This is intentional, since R makes strong assumptions about package namespaces that are violated here. In particular, such namespaces would have to be registered in R’s internal namespace registry, and their (de)serialisation is handled by R code which assumes that they belong to actual packges that can be loaded via 'loadNamespace'.
Parse a module’s documentation
Description
Parse a module’s documentation
Usage
parse_documentation(info, mod_ns)
parse_roxygen_tags(info, mod_ns)
patch_mod_doc(docs)
Arguments
info |
The module info. |
mod_ns |
The module namespace. |
docs |
the list of roxygen2 documentation objects. |
Value
parse_documentation
returns a list of character strings with
the Rd documentation source code for each documented name in a module.
Find exported names in parsed module source
Description
Find exported names in parsed module source
Usage
parse_export_specs(info, exprs, mod_ns)
use_call
static_assign_calls
assign_calls
is_static_assign_call(call)
is_assign_call(call)
block_is_assign(block)
block_is_use_call(block)
block_is_exported(block)
block_name(block)
Arguments
info |
The module info. |
exprs |
The list of expressions of the parsed module. |
mod_ns |
The module namespace after evaluating the expressions. |
call |
A call to test. |
block |
A roxygen2 block to inspect. |
Format
An object of class call
of length 3.
An object of class list
of length 4.
An object of class list
of length 6.
Value
parse_export_specs
returns a potentially empty character
vector of exported names.
Note
There are two situations in which the @export
tag can be applied:
When applied to assignments, the assigned name is exported.
When applied to a
box::use
call, the imported names are exported. This can be the module name itself, any attached names, or both. All names introduced by thebox::use
call are exported. Seeuse
for the rules governing what names are introduced into the scope, and thus exported.
In any other situation, applying the @export
tag is an error.
Extract comment tags from Roxygen block comments
Description
Extract comment tags from Roxygen block comments
Usage
parse_export_tags(info, exprs, mod_ns)
Arguments
exprs |
The unevaluated expressions to parse. |
Value
parse_export_tags
returns a list of roxy_block
s for all
exported declarations.
Note
The following code performs the same function as roxygen2 with a custom
@
tag roclet. Unfortunately roxygen2 itself pulls in many
dependencies, making it less suitable for an infrastructure package such as
this one. Furthermore, the code license of roxygen2 is incompatible with
ours, so we cannot simply copy and paste the relevant code out. Luckily the
logic is straightforward to reimplement.
Get a module’s path
Description
The following functions retrieve information about the path of the directory that a module or script is running in.
Usage
path(mod)
base_path(mod)
module_path(mod)
mod_path(mod)
explicit_path(...)
r_path(...)
knitr_path(...)
shiny_path(...)
testthat_path(...)
rstudio_path(...)
wd_path(...)
Arguments
mod |
a module environment or namespace |
Details
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:
Path explicitly set via
set_script_path
Path of a running document/application (knitr, Shiny)
Path of unit test cases (testthat)
Path of the currently opened source code file in RStudio
Code invoked as
Rscript script.r
Code invoked as
R CMD BATCH script.r
Code invoked as
R -f script.r
Script run interactively (use
getwd()
)
Value
path
returns a character string containing the module’s full
path.
base_path
returns a character string containing the module’s
base directory, or the current working directory if not invoked on a module.
module_path
returns a character string that contains the
directory in which the calling R code is run. See ‘Details’.
mod_path
returns the script path associated with a box
module
explicit_path
returns the script path explicitly set by the
user, if such a path was set.
r_path
returns the directory in which the current script is
run via Rscript
, R CMD BATCH
or R -f
.
knitr_path
returns the directory in which the currently knit
document is run, or NULL
if not called from within a knitr
document.
shiny_path
returns the directory in which a Shiny
application is running, or NULL
if not called from within a
Shiny application.
testthat_path
returns the directory in which testthat
code is being executed, or NULL
if not called from within a
testthat test case.
rstdio_path
returns the directory in which the currently
active RStudio script file is saved.
wd_path
returns the current working directory.
Path related functions
Description
mod_search_path
returns the character vector of paths where module
code can be located and will be found by box.
Usage
mod_search_path(caller)
calling_mod_path(caller)
split_path(path)
merge_path(components)
sanitize_path_fragment(path)
Arguments
caller |
the environment from which |
path |
the path |
components |
character string vector of path components to merge |
Value
calling_mod_path
the path of the source module that is calling
box::use
, or the script’s path if the calling code is not a module.
split_path
returns a character vector of path components that
logically represent path
.
merge_path
returns a single character string that is
logically equivalent to the path
passed to split_path
.
logically represent path
.
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,
getOption('box.path')
specifies the search path as a character vector.
Users can override its value by separately setting the environment variable
R_BOX_PATH to one or more paths, separated by the platform’s path
separator (“:” on UNIX-like systems, “;” on Windows).
merge_path
is the inverse function to 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.
Register S3 methods
Description
box::register_S3_method
makes an S3 method for a given generic and
class known inside a module.
Usage
box::register_S3_method(name, class, method)
Arguments
name |
the name of the generic as a character string. |
class |
the class name. |
method |
the method to register (optional). |
Details
If method
is missing, it defaults to a function named
name.class
in the calling module. If no such function exists, an error
is raised.
Methods for generics defined in the same module do not need to be registered
explicitly, and indeed should not be registered. However, if the user
wants to add a method for a known generic (defined outside the module, e.g.
print
), then this needs to be made known explicitly.
See the vignette at vignette('box', 'box')
for more information about
defining S3 methods inside modules.
Value
box::register_S3_method
is called for its side effect.
Note
Do not call registerS3method
inside a
module, only use box::register_S3_method
. This is important for the
module’s own book-keeping.
Internal S3 infrastructure helpers
Description
The following are internal S3 infrastructure helper functions.
Usage
is_S3_user_generic(function_name, envir = parent.frame())
make_S3_methods_known(module)
Arguments
function_name |
function name as character string. |
envir |
the environment this function is invoked from. |
module |
the module object for which to register S3 methods |
Details
is_S3_user_generic
checks whether a function given by name is a
user-defined generic. A user-defined generic is any function which, at some
point, calls UseMethod
.
make_S3_methods_known
finds and registers S3 methods inside a module.
Value
is_S3_user_generic
returns TRUE
if the specified
function is a user-defined S3 generic, FALSE
otherwise.
Set the base path of the script
Description
box::set_script_path(path)
explicitly tells box the path of a
given script from which it is called; box::script_path()
returns the
previously set path.
Usage
box::set_script_path(path)
box::script_path()
Arguments
path |
character string containing the relative or absolute path to the
currently executing R code file, or |
Details
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, box can
figure the path out automatically. However, in some cases third-party
packages load code in a way in which box cannot find the correct path
of the script any more. box::set_script_path
can be used in these
cases to set the path of the currently executing R script manually.
Value
Both box::script_path
and box::set_script_path
return
the previously set script path, or NULL
if none was explicitly set.
box::set_script_path
returns its value invisibly.
Note
box should be able to figure out the script path automatically.
Using box::set_script_path
should therefore never be necessary.
Please
file an issue if you encounter a situation that necessitates using
box::set_script_path
!
Examples
box::set_script_path('scripts/my_script.r')
Parse a mod or pkg spec expression passed to use
Description
Parse a mod or pkg spec expression passed to use
Usage
parse_spec(expr, alias)
mod_spec(spec, ...)
pkg_spec(spec, ...)
spec_name(spec)
Arguments
expr |
the mod or pkg spec expression to parse |
alias |
the mod or pkg spec alias as a character, or |
spec |
named list of information the parser constructed from a given spec expression |
... |
further information about a spec, not represented by the spec expression parse tree |
Value
parse_spec
returns a named list that contains information
about the parsed mod specification. Currently it contains:
name
the module or package name
prefix
the prefix, if the spec is a module
attach
a named vector of symbols to attach, or
TRUE
to attach all symbols, orNULL
to attach nothingalias
the module or package alias
explicit
a logical value indicating whether the caller provided an explicit alias
Throw informative error messages
Description
Helpers to generate readable and informative error messages for package users.
Usage
throw(..., call = sys.call(sys.parent()), subclass = NULL)
rethrow(error, call = sys.call(sys.parent()))
rethrow_on_error(expr, call = sys.call(sys.parent()))
box_error(message, call = NULL, subclass = NULL)
Arguments
... |
arguments to be passed to |
call |
the calling context from which the error is raised |
subclass |
an optional subclass name for the error condition to be raised |
error |
an object of class |
expr |
an expression to evaluate inside |
message |
the error message |
Details
For rethrow
, the call
argument overrides the rethrown error’s
own stored call.
Value
If it does not throw an error, rethrow_on_error
returns the
value of evaluating expr
.
box_error
returns a new ‘box’ error condition object
with a given message and call, and optionally a given subclass type.
Get a module’s namespace environment
Description
Called inside a module, box::topenv()
returns the module namespace
environment. Otherwise, it behaves similarly to topenv
.
Usage
box::topenv()
box::topenv(env)
Arguments
module |
a module environment |
Value
box::topenv()
returns the top-level module environment of the
module it is called from, or the nearest top-level non-module environment
otherwise; this is usually .GlobalEnv
.
box::topenv(env)
returns the nearest top-level environment that is a
direct or indirect parent of env
.
Unload or reload modules
Description
Given a module which has been previously loaded and is assigned to an alias
mod
, box::unload(mod)
unloads it; box::reload(mod)
unloads and reloads it from its source. box::purge_cache()
marks all
modules as unloaded.
Usage
box::unload(mod)
box::reload(mod)
box::purge_cache()
Arguments
mod |
a module object to be unloaded or reloaded |
Details
Unloading a module causes it to be removed from the internal cache such that
the next subsequent box::use
declaration will reload the module from
its source. box::reload
unloads and reloads the specified modules and
all its transitive module dependencies. box::reload
is not
merely a shortcut for calling box::unload
followed by box::use
,
because box::unload
only unloads the specified module itself, not any
dependent modules.
Value
These functions are called for their side effect. They do not return anything.
Note
Any other references to the loaded modules remain unchanged, and will
(usually) still work. Unloading and reloading modules is primarily useful for
testing during development, and should not be used in production code:
in particular, unloading may break other module references if the
.on_unload
hook unloaded any binary shared libraries which are still
referenced.
These functions come with a few restrictions.
box::unload
attempts to detach names attached by the corresponding
box::use
call.
box::reload
attempts to re-attach these same names. This only works if
the corresponding box::use
declaration is located in the same scope.
box::purge_cache
only removes the internal cache of modules, it does
not actually invalidate any module references or names attached from loaded
modules.
box::unload
will execute the .on_unload
hook of the module, if
it exists.
box::reload
will re-execute the .on_load
hook of the module and
of all dependent modules during loading (after executing the corresponding
.on_unload
hooks during unloading).
box::purge_cache
will execute any existing .on_unload
hooks in
all loaded modules.
See Also
Import a module or package
Description
box::use
imports one or more modules and/or packages, and makes them
available in the calling environment.
Usage
box::use(prefix/mod, ...)
box::use(pkg, ...)
box::use(alias = prefix/mod, ...)
box::use(alias = pkg, ...)
box::use(prefix/mod[attach_list], ...)
box::use(pkg[attach_list], ...)
Arguments
... |
further import declarations |
prefix/mod |
a qualified module name |
pkg |
a package name |
alias |
an alias name |
attach_list |
a list of names to attached, optionally witha aliases of
the form |
Details
box::use(...)
specifies a list of one or more import declarations,
given as individual arguments to box::use
, separated by comma.
box::use
permits using a trailing comma after the last import
declaration. Each import declaration takes one of the following forms:
prefix/mod
:-
Import a module given the qualified module name
prefix/mod
and make it available locally using the namemod
. Theprefix
itself can be a nested name to allow importing specific submodules. Local imports can be specified via the prefixes starting with.
and..
, to override the search path and use the local path instead. See the ‘Search path’ below for details. pkg
:-
Import a package
pkg
and make it available locally using its own package name. alias = prefix/mod
oralias = pkg
:-
Import a module or package, and make it available locally using the name
alias
instead of its regular module or package name. prefix/mod[attach_list]
orpkg[attach_list]
:-
Import a module or package and attach the exported symbols listed in
attach_list
locally. This declaration does not make the module/package itself available locally. To override this, provide an alias, that is, usealias = prefix/mod[attach_list]
oralias = pkg[attach_list]
.The
attach_list
is a comma-separated list of names, optionally with aliases assigned viaalias = name
. The list can also contain the special symbol...
, which causes all exported names of the module/package to be imported.
See the vignette at vignette('box', 'box')
for detailed examples of
the different types of use declarations listed above.
Value
box::use
has no return value. It is called for its
side effect.
Import semantics
Modules and packages are loaded into dedicated namespace environments. Names from a module or package can be selectively attached to the current scope as shown above.
Unlike with library
, attaching happens locally,
i.e. in the caller’s environment: if box::use
is executed in the
global environment, the effect is the same. Otherwise, the effect of
importing and attaching a module or package is limited to the caller’s local
scope (its environment()
). When used inside a module at module
scope, the newly imported module is only available inside the module’s scope,
not outside it (nor in other modules which might be loaded).
Member access of (non-attached) exported names of modules and packages
happens via the $
operator. This operator does not perform partial
argument matching, in contrast with the behavior of the $
operator in
base R, which matches partial names.
Note that replacement functions (i.e. functions of the form
fun<-
) must be attached to be usable, because R syntactically
does not allow assignment calls where the left-hand side of the assignment
contains $
.
Export specification
Names defined in modules can be marked as exported by prefixing them
with an @export
tag comment; that is, the name needs to be immediately
prefixed by a comment that reads, verbatim, #' @export
. That line may
optionally be part of a roxygen2 documentation for that name.
Alternatively, exports may be specified via the
box::export
function, but using declarative
@export
tags is generally preferred.
A module which has not declared any exports is treated as a legacy
module and exports all default-visible names (that is, all names that
do not start with a dot (.
). This usage is present only for backwards
compatibility with plain R scripts, and its usage is not recommended
when writing new modules.
To define a module that exports no names, call box::export()
without
arguments. This prevents the module from being treated as a legacy module.
Search path
Modules are searched in the module search path, given by
getOption('box.path')
. This is a character vector of paths to search,
from the highest to the lowest priority. The current directory is always
considered last. That is, if a file ‘a/b.r’ exists both locally in the
current directory and in a module search path, the local file ‘./a/b.r’
will not be loaded, unless the import is explicitly declared as
box::use(./a/b)
.
Modules in the module search path must be organised in subfolders, and
must be imported fully qualified. Keep in mind that box::use(name)
will never attempt to load a module; it always attempts to load a
package. A common module organisation is by project, company or user name;
for instance, fully qualified module names could mirror repository names on
source code sharing websites (such as GitHub).
Given a declaration box::use(a/b)
and a search path ‘p’, if
the file ‘p/a/b.r’ does not exist, box alternatively looks
for a nested file ‘p/a/b/__init__r’ to load. Module path names are
case sensitive (even on case insensitive file systems), but the file
extension can be spelled as either ‘.r’ or ‘.R’ (if both exist,
.r
is given preference).
The module search path can be overridden by the environment variable
R_BOX_PATH. If set, it may consist of one or more search paths,
separated by the platform’s path separator (i.e. ;
on Windows, and
:
on most other platforms).
Deprecation warning: in the next major version, box will read environment variables only once, at package load time. Modifying the value of R_BOX_PATH afterwards will have no effect, unless the package is unloaded and reloaded.
The current directory is context-dependent: inside a module, the
directory corresponds to the module’s directory. Inside an R code file
invoked from the command line, it corresponds to the directory containing
that file. If the code is running inside a Shiny application or a
knitr document, the directory of the execution is used. Otherwise (e.g.
in an interactive R session), the current working directory as given by
getwd()
is used.
Local import declarations (that is, module prefixes that start with ./
or ../
) never use the search path to find the module. Instead,
only the current module’s directory (for ./
) or the parent module’s
directory (for ../
) is looked at. ../
can be nested:
../../
denotes the grandparent module, etc.
S3 support
Modules can contain S3 generics and methods. To override known generics
(= those defined outside the module), methods inside a module need to be
registered using box::register_S3_method
.
See the documentation there for details.
Module names
A module’s full name consists of one or more R names separated by /
.
Since box::use
declarations contain R expressions, the names need to
be valid R names. Non-syntactic names need to be wrapped in backticks; see
Quotes.
Furthermore, since module names usually correspond to file or folder names, they should consist only of valid path name characters to ensure portability.
Encoding
All module source code files are assumed to be UTF-8 encoded.
See Also
box::name
and box::file
give
information about loaded modules.
box::help
displays help for a module’s exported names.
box::unload
and box::reload
aid
during module development by performing dynamic unloading and reloading of
modules in a running R session.
box::export
can be used as an alternative to
@export
comments inside a module to declare module exports.
Examples
# Set the module search path for the example module.
old_opts = options(box.path = system.file(package = 'box'))
# Basic usage
# The file `mod/hello_world.r` exports the functions `hello` and `bye`.
box::use(mod/hello_world)
hello_world$hello('Robert')
hello_world$bye('Robert')
# Using an alias
box::use(world = mod/hello_world)
world$hello('John')
# Attaching exported names
box::use(mod/hello_world[hello])
hello('Jenny')
# Exported but not attached, thus access fails:
try(bye('Jenny'))
# Attach everything, give `hello` an alias:
box::use(mod/hello_world[hi = hello, ...])
hi('Eve')
bye('Eve')
# Reset the module search path
on.exit(options(old_opts))
## Not run:
# The following code illustrates different import declaration syntaxes
# inside a single `box::use` declaration:
box::use(
global/mod,
mod2 = ./local/mod,
purrr,
tbl = tibble,
dplyr = dplyr[filter, select],
stats[st_filter = filter, ...],
)
# This declaration makes the following names available in the caller’s scope:
#
# 1. `mod`, which refers to the module environment for `global/mod`
# 2. `mod2`, which refers to the module environment for `./local/mod`
# 3. `purrr`, which refers to the package environment for ‘purrr’
# 4. `tbl`, which refers to the package environment for ‘tibble’
# 5. `dplyr`, which refers to the package environment for ‘dplyr’
# 6. `filter` and `select`, which refer to the names exported by ‘dplyr’
# 7. `st_filter`, which refers to `stats::filter`
# 8. all other exported names from the ‘stats’ package
## End(Not run)
Wrap “unsafe calls” functions
Description
wrap_unsafe_function
declares a function wrapper to a function that
causes an R CMD check
NOTE when called directly. We should usually
not call these functions, but we need some of them because we want to
explicitly support features they provide.
Usage
wrap_unsafe_function(ns, name)
Arguments
ns |
The namespace of the unsafe function. |
name |
The name of the unsafe function. |
Value
wrap_unsafe_calls
returns a wrapper function with the same
argument as the wrapped function that can be called without causing a NOTE.
Note
Using an implementation that simply aliases getExportedValue
does not work, since R CMD check
sees right through this
“ruse”.