Skip to content
Open
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

* Suppress a spurious internal warning upon reloading a module, caused by a dependent module being imported more than once (#363).

## New feature

* Add `box::get_exports()` function to provide functionality similar to `base::getNamespaceExports()`.

# box 1.2.0

Expand Down
80 changes: 80 additions & 0 deletions R/get-exports.r
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#' List exports of a module or package
#'
#' \code{box::get_exports} supports reflection on {box} modules. This is the {box} version of
#' {base::getNamespaceExports}.
#'
#' @usage \special{box::get_exports(prefix/mod, \dots)}
#' @usage \special{box::get_exports(pkg, \dots)}
#' @usage \special{box::get_exports(alias = prefix/mod, \dots)}
#' @usage \special{box::get_exports(alias = pkg, \dots)}
#' @usage \special{box::get_exports(prefix/mod[attach_list], \dots)}
#' @usage \special{box::get_exports(pkg[attach_list], \dots)}
#'
#' @param prefix/mod a qualified module name
#' @param pkg a package name
#' @param alias an alias name
#' @param attach_list a list of names to attached, optionally witha aliases of
#' the form \code{alias = name}; or the special placeholder name \code{\dots}
#' @param \dots further import declarations
#' @return \code{box::get_exports} returns a list of attached packages, modules, and functions.
#'
#' @examples
#' # Set the module search path for the example module
#' old_opts = options(box.path = system.file(package = 'box'))
#'
#' # Basic usage
#' box::get_exports(mod/hello_world)
#'
#' # Using an alias
#' box::get_exports(world = mod/hello_world)
#'
#' # Attaching exported names
#' box::get_exports(mod/hello_world[hello])
#'
#' # Attach everything, give `hello` an alias:
#' box::get_exports(mod/hello_world[hi = hello, ...])
#'
#' # Reset the module search path
#' on.exit(options(old_opts))
#'
#' @seealso
#' \code{\link[=use]{box::use}} give information about importing modules or packages
#'
#' @export
get_exports = function (...) {
caller = parent.frame()
call = match.call()
imports = call[-1L]
aliases = names(imports) %||% character(length(imports))
unlist(
map(get_one, imports, aliases, list(caller), use_call = list(sys.call())),
recursive = FALSE
)
}

#' Get a module or package's exports without loading into the environment
#'
#' @param declaration an unevaluated use declaration expression without the
#' surrounding \code{use} call
#' @param alias the use alias, if given, otherwise \code{NULL}
#' @param caller the client’s calling environment (parent frame)
#' @param use_call the \code{use} call which is invoking this code
#' @return \code{get_one} return a list of functions exported.
#' @keywords internal
get_one = function (declaration, alias, caller, use_call) {
if (declaration %==% quote(expr =) && alias %==% '') return()

spec = parse_spec(declaration, alias)
info = find_mod(spec, caller)
mod_ns = load_mod(info)
mod_exports = mod_exports(info, spec, mod_ns)

exports = attach_list(spec, names(mod_exports))

if (is.null(exports)) {
exports = list(names(mod_exports))
names(exports) = spec$alias
}

return(exports)
}
146 changes: 146 additions & 0 deletions tests/testthat/test-get-exports.r
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
context('get exports')

test_that('returns all functions of a whole package attached', {
results = get_exports(stringr)

expected_output = getNamespaceExports('stringr')

expect_contains(results[['stringr']], expected_output)
})

test_that('returns all functions of a whole packaged attached and aliased', {
results = get_exports(alias = stringr)

expected_output = getNamespaceExports('stringr')

expect_named(results, c('alias'))
expect_contains(results[['alias']], expected_output)
})

test_that('return all functions of a package attached by three dots', {
results = get_exports(stringr[...])
expected_output = getNamespaceExports('stringr')

expect_contains(unname(results), expected_output)
})

test_that('returns attached functions from packages', {
results = get_exports(stringr[str_pad, str_trim])
expected_output = c('str_pad', 'str_trim')
names(expected_output) = c('str_pad', 'str_trim')
expect_named(results, names(expected_output))
expect_setequal(unname(results), unname(expected_output))
})

test_that('returns aliased attached functions from packages', {
results = get_exports(stringr[alias_1 = str_pad, alias_2 = str_trim])
expected_output = c('str_pad', 'str_trim')
names(expected_output) = c('alias_1', 'alias_2')
expect_named(results, names(expected_output))
expect_setequal(unname(results), unname(expected_output))
})

test_that('throws an error on unknown function attached from package', {
expect_error(get_exports(stringr[unknown_function]))
})

test_that('returns all functions of a whole module attached', {
results = get_exports(mod/a)

expected_output = c(
'double',
'modname',
'get_modname',
'get_modname2',
'get_counter',
'inc',
'%or%',
'+.string',
'which',
'encoding_test',
'.hidden',
'%.%',
'%x.%',
'%.x%',
'%x.x%',
'%foo.bar',
'%%.%%',
'%a%.class%'
)

expect_setequal(results[['a']], expected_output)
})

test_that('returns all functions of a whole module attached', {
results = get_exports(alias = mod/a)

expected_output = c(
'double',
'modname',
'get_modname',
'get_modname2',
'get_counter',
'inc',
'%or%',
'+.string',
'which',
'encoding_test',
'.hidden',
'%.%',
'%x.%',
'%.x%',
'%x.x%',
'%foo.bar',
'%%.%%',
'%a%.class%'
)

expect_named(results, c('alias'))
expect_setequal(results[['alias']], expected_output)
})

test_that('return all functions of a module attached by three dots', {
results = get_exports(mod/a[...])
expected_output = c(
'double',
'modname',
'get_modname',
'get_modname2',
'get_counter',
'inc',
'%or%',
'+.string',
'which',
'encoding_test',
'.hidden',
'%.%',
'%x.%',
'%.x%',
'%x.x%',
'%foo.bar',
'%%.%%',
'%a%.class%'
)

expect_contains(unname(results), expected_output)
})

test_that('returns attached functions from modules', {
results = get_exports(mod/a[get_modname, `%or%`])
expected_output = c('get_modname', '%or%')
names(expected_output) = c('get_modname', '%or%')
expect_named(results, names(expected_output))
expect_setequal(unname(results), unname(expected_output))
})

test_that('returns attached aliased functions from modules', {
results = get_exports(mod/a[alias_1 = get_modname, alias_2 = `%or%`])
expected_output = c('get_modname', '%or%')
names(expected_output) = c('alias_1', 'alias_2')
expect_named(results, names(expected_output))
expect_setequal(unname(results), unname(expected_output))
})

test_that('throws an error on unknown function attached from module', {
expect_error(get_exports(mod/a[unknown_function]))
})