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 ORCID iD [aut, cre], Michael Schubert ORCID iD [ctb]
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:

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 srcrefs to extend.

Value

add_comment returns a list of srcrefs 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 srcref represented by the tag.

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 NULL

rhs

vector with default values, same length as lhs unless that is NULL

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

system.file


Find a module’s source location

Description

Find a module’s source location

Usage

find_in_path(spec, base_paths)

Arguments

spec

a mod_spec.

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 n

b

a character vector of length n - 1

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 srcref to search.

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 module$function; or a name that was exported and attached from an imported module or package.

help_type

character string specifying the output format; currently, only 'text' is supported.

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 help.

caller

the environment from which help was called.

name

the name to look for.

call

the patched help call expression.

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 use call

alias

the use alias, if given, otherwise NULL

caller

the client’s calling environment (parent frame)

use_call

the use call which is invoking this code

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 mod_spec or pkg_spec (for mod_info and pkg_info, respectively)

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 flatmap for an empty input

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:

  1. When applied to assignments, the assigned name is exported.

  2. 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 the box::use call are exported. See use 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_blocks 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:

  1. Path explicitly set via set_script_path

  2. Path of a running document/application (knitr, Shiny)

  3. Path of unit test cases (testthat)

  4. Path of the currently opened source code file in RStudio

  5. Code invoked as Rscript script.r

  6. Code invoked as R CMD BATCH script.r

  7. Code invoked as R -f script.r

  8. 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 box::use was invoked.

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 NULL to reset the path.

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 NULL

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, or NULL to attach nothing

alias

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 fmt

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 c("error", "condition") to rethrow

expr

an expression to evaluate inside tryCatch

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

box::use, module hooks


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 alias = name; or the special placeholder name ...

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 name mod. The prefix 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 or alias = 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] or pkg[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, use alias = prefix/mod[attach_list] or alias = pkg[attach_list].

The attach_list is a comma-separated list of names, optionally with aliases assigned via alias = 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”.