This vignette summarizes how the internals of boomer work.
An important principle of {boomer} is that we don’t modify the body of the function we rig.
rigged_file_ext <- boomer::rig(tools::file_ext)
tools::file_ext
#> function (x)
#> {
#> x <- as.character(x)
#> if (!length(x))
#> return(character())
#> ifelse(grepl("^(.*[^.]+.*)[.]([[:alnum:]]+)$", basename(x)),
#> sub(".*[.]([[:alnum:]]+)$", "\\1", x), "")
#> }
#> <bytecode: 0x55ed9ea0f7b8>
#> <environment: namespace:tools>
rigged_file_ext
#> function (x)
#> {
#> x <- as.character(x)
#> if (!length(x))
#> return(character())
#> ifelse(grepl("^(.*[^.]+.*)[.]([[:alnum:]]+)$", basename(x)),
#> sub(".*[.]([[:alnum:]]+)$", "\\1", x), "")
#> }
#> <environment: 0x55ed9cea3880>
#> attr(,"boomer.rigged")
#> [1] TRUEInstead we copy the original function but give it a new environment. This new environment is a child of the original environment, and is populated with shims of the functions called by the original function. We call this environment the mask.
# the original environment
environment(tools::file_ext)
#> <environment: namespace:tools>
# our new environment
env <- environment(rigged_file_ext)
env
#> <environment: 0x55ed9cea3880>
# its parent
parent.env(env)
#> <environment: namespace:tools>
# its content
ls(env)
#> [1] "::" ":::" "!" "(" "{"
#> [6] "<-" "=" "as.character" "basename" "character"
#> [11] "grepl" "if" "ifelse" "length" "return"
#> [16] "sub"rig_impl() does this job and is the main function of the
package. It calls wrap(), the other important function,
whose mission is to build verbose shims of functions used in the rigged
function. Both are detailed thereafter.
boom() and rig()boom() is a wrapper around rig_impl() , it
rigs the calling function and runs the call, it also has some hacky code
so we can pipe to boom() with {magrittr} (The hack is not
needed for the base pipe)
rig_impl()Here’s the diagram of dependencies of rig_impl()
flow::flow_view_deps(boomer:::rig_impl, show_imports = "packages")
#> `google-chrome`, `chromium-browser` and `chrome` were not found. Try setting the `CHROMOTE_CHROME` environment variable to the executable of a Chromium-based browser, such as Google Chrome, Chromium or Brave or adding one of these executables to your PATH.
#> Error in `initialize()`:
#> ! Invalid path to Chromerig_impl() :
:: and ::: so
when we find pkg::fun or pkg:::fun in the
original function we don’t wrap their output and not the ::
or ::: operator<- and = so
functions created in the original function, and thus impossible to shim
at “rig time”, can be made verbose too.wrap()wrap()wrap() builds verbose wrapper functions.
Its main aim is to print information directly related to the wrapped function (e.g. argument values and execution time).
However it does a couple more things:
..EVALED_ARGS.. variable in the rigged
function’s execution environment, which it creates on that
occasion...EVALED_ARGS.. variable, a named logical vector that
keeps track of which arguments have been evaled.rig_in_place()rig_in_place() calls rig_impl() on its
inputs but unlike rig() we want the rigged functions to be
bound in the namespace instead of the original functions.
To do this we unlock the namespace and assign the rigged functions there. We also replace the other copies a caller might reach: the one in the attached package environment, the one in the S3 methods table (so S3 dispatch is verbose too), and the ones imported by other already loaded packages.
Since rig_in_place() accepts several functions as
arguments, and that they might call each other, we also make sure we
include wrapped versions of our rigged functions in all the masks.