From ec8eaaa0dc2d6cd5b9139e98dfeeeefa41688882 Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Mon, 17 Jun 2024 16:29:26 +0800 Subject: [PATCH 1/8] functionally working for packages --- R/get_module_exports.R | 27 ++++++++++++++ tests/testthat/test-get_module_exports.R | 45 ++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 R/get_module_exports.R create mode 100644 tests/testthat/test-get_module_exports.R diff --git a/R/get_module_exports.R b/R/get_module_exports.R new file mode 100644 index 00000000..9d79f420 --- /dev/null +++ b/R/get_module_exports.R @@ -0,0 +1,27 @@ +#' @export +get_module_exports = function (...) { + caller = parent.frame() + call = match.call() + imports = call[-1L] + aliases = names(imports) %||% character(length(imports)) + map(get_one, imports, aliases, list(caller), use_call = list(sys.call())) +} + +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) +} diff --git a/tests/testthat/test-get_module_exports.R b/tests/testthat/test-get_module_exports.R new file mode 100644 index 00000000..0b8a887a --- /dev/null +++ b/tests/testthat/test-get_module_exports.R @@ -0,0 +1,45 @@ +context("get exports") + +test_that("returns all functions of a whole package attached", { + results = get_module_exports(stringr) + + expected_output = getNamespaceExports("stringr") + + expect_contains(results[[1]][["stringr"]], expected_output) +}) + +test_that("returns all functions of a whole packaged attached and aliased", { + results = get_module_exports(alias = stringr) + + expected_output = getNamespaceExports("stringr") + + expect_named(results[[1]], c("alias")) + expect_contains(results[[1]][["alias"]], expected_output) +}) + +test_that("return all functions of a package attached by three dots", { + results = get_module_exports(stringr[...]) + expected_output = getNamespaceExports("stringr") + + expect_contains(unname(results[[1]]), expected_output) +}) + +test_that("returns attached functions from packages", { + results = get_module_exports(stringr[str_pad, str_trim]) + expected_output = c("str_pad", "str_trim") + names(expected_output) = c("str_pad", "str_trim") + expect_named(results[[1]], names(expected_output)) + expect_setequal(unname(results[[1]]), unname(expected_output)) +}) + +test_that("returns aliased attached functions from packages", { + results = get_module_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[[1]], names(expected_output)) + expect_setequal(unname(results[[1]]), unname(expected_output)) +}) + +test_that("throws an error on unknown function attached", { + expect_error(get_module_exports(stringr[unknown_function])) +}) From 969b3b5ca6ae3bc788327d8296d2da3580bdebcd Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Mon, 17 Jun 2024 16:32:42 +0800 Subject: [PATCH 2/8] follow box style guide --- R/get_module_exports.R | 34 +++++++------- tests/testthat/test-get_module_exports.R | 56 ++++++++++++------------ 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/R/get_module_exports.R b/R/get_module_exports.R index 9d79f420..2a8a8489 100644 --- a/R/get_module_exports.R +++ b/R/get_module_exports.R @@ -1,27 +1,27 @@ #' @export get_module_exports = function (...) { - caller = parent.frame() - call = match.call() - imports = call[-1L] - aliases = names(imports) %||% character(length(imports)) - map(get_one, imports, aliases, list(caller), use_call = list(sys.call())) + caller = parent.frame() + call = match.call() + imports = call[-1L] + aliases = names(imports) %||% character(length(imports)) + map(get_one, imports, aliases, list(caller), use_call = list(sys.call())) } -get_one = function(declaration, alias, caller, use_call) { - if (declaration %==% quote(expr =) && alias %==% '') return() +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) + spec = parse_spec(declaration, alias) + info = find_mod(spec, caller) + mod_ns = load_mod(info) - mod_exports = mod_exports(info, spec, mod_ns) + mod_exports = mod_exports(info, spec, mod_ns) - exports = attach_list(spec, names(mod_exports)) + exports = attach_list(spec, names(mod_exports)) - if (is.null(exports)) { - exports = list(names(mod_exports)) - names(exports) = spec$alias - } + if (is.null(exports)) { + exports = list(names(mod_exports)) + names(exports) = spec$alias + } - return(exports) + return(exports) } diff --git a/tests/testthat/test-get_module_exports.R b/tests/testthat/test-get_module_exports.R index 0b8a887a..96c39ccf 100644 --- a/tests/testthat/test-get_module_exports.R +++ b/tests/testthat/test-get_module_exports.R @@ -1,45 +1,45 @@ -context("get exports") +context('get exports') -test_that("returns all functions of a whole package attached", { - results = get_module_exports(stringr) +test_that('returns all functions of a whole package attached', { + results = get_module_exports(stringr) - expected_output = getNamespaceExports("stringr") + expected_output = getNamespaceExports('stringr') - expect_contains(results[[1]][["stringr"]], expected_output) + expect_contains(results[[1]][['stringr']], expected_output) }) -test_that("returns all functions of a whole packaged attached and aliased", { - results = get_module_exports(alias = stringr) +test_that('returns all functions of a whole packaged attached and aliased', { + results = get_module_exports(alias = stringr) - expected_output = getNamespaceExports("stringr") + expected_output = getNamespaceExports('stringr') - expect_named(results[[1]], c("alias")) - expect_contains(results[[1]][["alias"]], expected_output) + expect_named(results[[1]], c('alias')) + expect_contains(results[[1]][['alias']], expected_output) }) -test_that("return all functions of a package attached by three dots", { - results = get_module_exports(stringr[...]) - expected_output = getNamespaceExports("stringr") +test_that('return all functions of a package attached by three dots', { + results = get_module_exports(stringr[...]) + expected_output = getNamespaceExports('stringr') - expect_contains(unname(results[[1]]), expected_output) + expect_contains(unname(results[[1]]), expected_output) }) -test_that("returns attached functions from packages", { - results = get_module_exports(stringr[str_pad, str_trim]) - expected_output = c("str_pad", "str_trim") - names(expected_output) = c("str_pad", "str_trim") - expect_named(results[[1]], names(expected_output)) - expect_setequal(unname(results[[1]]), unname(expected_output)) +test_that('returns attached functions from packages', { + results = get_module_exports(stringr[str_pad, str_trim]) + expected_output = c('str_pad', 'str_trim') + names(expected_output) = c('str_pad', 'str_trim') + expect_named(results[[1]], names(expected_output)) + expect_setequal(unname(results[[1]]), unname(expected_output)) }) -test_that("returns aliased attached functions from packages", { - results = get_module_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[[1]], names(expected_output)) - expect_setequal(unname(results[[1]]), unname(expected_output)) +test_that('returns aliased attached functions from packages', { + results = get_module_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[[1]], names(expected_output)) + expect_setequal(unname(results[[1]]), unname(expected_output)) }) -test_that("throws an error on unknown function attached", { - expect_error(get_module_exports(stringr[unknown_function])) +test_that('throws an error on unknown function attached', { + expect_error(get_module_exports(stringr[unknown_function])) }) From a59f6160d0be056892e24714389d21f5a2c41cb8 Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Mon, 17 Jun 2024 17:10:13 +0800 Subject: [PATCH 3/8] works with modules. adds initial documentation --- R/get_module_exports.R | 54 ++++++++++- tests/testthat/test-get_module_exports.R | 115 +++++++++++++++++++++-- 2 files changed, 160 insertions(+), 9 deletions(-) diff --git a/R/get_module_exports.R b/R/get_module_exports.R index 2a8a8489..2e709ecb 100644 --- a/R/get_module_exports.R +++ b/R/get_module_exports.R @@ -1,5 +1,47 @@ +#' 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_module_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_module_exports = function (...) { +get_exports = function (...) { caller = parent.frame() call = match.call() imports = call[-1L] @@ -7,13 +49,21 @@ get_module_exports = function (...) { map(get_one, imports, aliases, list(caller), use_call = list(sys.call())) } +#' 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)) diff --git a/tests/testthat/test-get_module_exports.R b/tests/testthat/test-get_module_exports.R index 96c39ccf..03b5ce8e 100644 --- a/tests/testthat/test-get_module_exports.R +++ b/tests/testthat/test-get_module_exports.R @@ -1,7 +1,7 @@ context('get exports') test_that('returns all functions of a whole package attached', { - results = get_module_exports(stringr) + results = get_exports(stringr) expected_output = getNamespaceExports('stringr') @@ -9,7 +9,7 @@ test_that('returns all functions of a whole package attached', { }) test_that('returns all functions of a whole packaged attached and aliased', { - results = get_module_exports(alias = stringr) + results = get_exports(alias = stringr) expected_output = getNamespaceExports('stringr') @@ -18,14 +18,14 @@ test_that('returns all functions of a whole packaged attached and aliased', { }) test_that('return all functions of a package attached by three dots', { - results = get_module_exports(stringr[...]) + results = get_exports(stringr[...]) expected_output = getNamespaceExports('stringr') expect_contains(unname(results[[1]]), expected_output) }) test_that('returns attached functions from packages', { - results = get_module_exports(stringr[str_pad, str_trim]) + 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[[1]], names(expected_output)) @@ -33,13 +33,114 @@ test_that('returns attached functions from packages', { }) test_that('returns aliased attached functions from packages', { - results = get_module_exports(stringr[alias_1 = str_pad, alias_2 = str_trim]) + 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[[1]], names(expected_output)) expect_setequal(unname(results[[1]]), unname(expected_output)) }) -test_that('throws an error on unknown function attached', { - expect_error(get_module_exports(stringr[unknown_function])) +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[[1]][['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[[1]], c('alias')) + expect_setequal(results[[1]][['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[[1]]), 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[[1]], names(expected_output)) + expect_setequal(unname(results[[1]]), 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[[1]], names(expected_output)) + expect_setequal(unname(results[[1]]), unname(expected_output)) +}) + +test_that('throws an error on unknown function attached from module', { + expect_error(get_exports(mod/a[unknown_function])) }) From 575ec94de48d9c2d8ebe24e04d6932a0f812d814 Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Tue, 18 Jun 2024 14:21:07 +0800 Subject: [PATCH 4/8] unlist get_exports result one level --- R/get_module_exports.R | 5 +++- tests/testthat/test-get_module_exports.R | 32 ++++++++++++------------ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/R/get_module_exports.R b/R/get_module_exports.R index 2e709ecb..4a28d221 100644 --- a/R/get_module_exports.R +++ b/R/get_module_exports.R @@ -46,7 +46,10 @@ get_exports = function (...) { call = match.call() imports = call[-1L] aliases = names(imports) %||% character(length(imports)) - map(get_one, imports, aliases, list(caller), use_call = list(sys.call())) + 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 diff --git a/tests/testthat/test-get_module_exports.R b/tests/testthat/test-get_module_exports.R index 03b5ce8e..9cad41ef 100644 --- a/tests/testthat/test-get_module_exports.R +++ b/tests/testthat/test-get_module_exports.R @@ -5,7 +5,7 @@ test_that('returns all functions of a whole package attached', { expected_output = getNamespaceExports('stringr') - expect_contains(results[[1]][['stringr']], expected_output) + expect_contains(results[['stringr']], expected_output) }) test_that('returns all functions of a whole packaged attached and aliased', { @@ -13,31 +13,31 @@ test_that('returns all functions of a whole packaged attached and aliased', { expected_output = getNamespaceExports('stringr') - expect_named(results[[1]], c('alias')) - expect_contains(results[[1]][['alias']], expected_output) + 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[[1]]), expected_output) + 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[[1]], names(expected_output)) - expect_setequal(unname(results[[1]]), unname(expected_output)) + 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[[1]], names(expected_output)) - expect_setequal(unname(results[[1]]), unname(expected_output)) + expect_named(results, names(expected_output)) + expect_setequal(unname(results), unname(expected_output)) }) test_that('throws an error on unknown function attached from package', { @@ -68,7 +68,7 @@ test_that('returns all functions of a whole module attached', { '%a%.class%' ) - expect_setequal(results[[1]][['a']], expected_output) + expect_setequal(results[['a']], expected_output) }) test_that('returns all functions of a whole module attached', { @@ -95,8 +95,8 @@ test_that('returns all functions of a whole module attached', { '%a%.class%' ) - expect_named(results[[1]], c('alias')) - expect_setequal(results[[1]][['alias']], expected_output) + expect_named(results, c('alias')) + expect_setequal(results[['alias']], expected_output) }) test_that('return all functions of a module attached by three dots', { @@ -122,23 +122,23 @@ test_that('return all functions of a module attached by three dots', { '%a%.class%' ) - expect_contains(unname(results[[1]]), expected_output) + 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[[1]], names(expected_output)) - expect_setequal(unname(results[[1]]), unname(expected_output)) + 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[[1]], names(expected_output)) - expect_setequal(unname(results[[1]]), unname(expected_output)) + expect_named(results, names(expected_output)) + expect_setequal(unname(results), unname(expected_output)) }) test_that('throws an error on unknown function attached from module', { From 0d93c9b1d471175f978be33265cecf088a13b2fa Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Tue, 18 Jun 2024 14:22:25 +0800 Subject: [PATCH 5/8] rename files --- R/{get_module_exports.R => get_exports.R} | 0 tests/testthat/{test-get_module_exports.R => test-get_exports.R} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename R/{get_module_exports.R => get_exports.R} (100%) rename tests/testthat/{test-get_module_exports.R => test-get_exports.R} (100%) diff --git a/R/get_module_exports.R b/R/get_exports.R similarity index 100% rename from R/get_module_exports.R rename to R/get_exports.R diff --git a/tests/testthat/test-get_module_exports.R b/tests/testthat/test-get_exports.R similarity index 100% rename from tests/testthat/test-get_module_exports.R rename to tests/testthat/test-get_exports.R From 0ab31096c87e741826a4586bf15f06038119b7e5 Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Mon, 1 Jul 2024 15:31:21 +0800 Subject: [PATCH 6/8] rename files to match project standard --- R/{get_exports.R => get-exports.r} | 0 tests/testthat/{test-get_exports.R => test-get-exports.r} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename R/{get_exports.R => get-exports.r} (100%) rename tests/testthat/{test-get_exports.R => test-get-exports.r} (100%) diff --git a/R/get_exports.R b/R/get-exports.r similarity index 100% rename from R/get_exports.R rename to R/get-exports.r diff --git a/tests/testthat/test-get_exports.R b/tests/testthat/test-get-exports.r similarity index 100% rename from tests/testthat/test-get_exports.R rename to tests/testthat/test-get-exports.r From 06eee543e4f8de0a985b958295525c86c49b5ce6 Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Mon, 1 Jul 2024 15:35:26 +0800 Subject: [PATCH 7/8] added news item --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index de80f730..37ee5954 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 From 428c2a7cc5e16ecdb28f94ab0e336af77c67069d Mon Sep 17 00:00:00 2001 From: Rodrigo Basa Date: Mon, 1 Jul 2024 15:37:18 +0800 Subject: [PATCH 8/8] error in roxygen documentation --- R/get-exports.r | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/get-exports.r b/R/get-exports.r index 4a28d221..c427633f 100644 --- a/R/get-exports.r +++ b/R/get-exports.r @@ -16,7 +16,7 @@ #' @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_module_exports} returns a list of attached packages, modules, and functions. +#' @return \code{box::get_exports} returns a list of attached packages, modules, and functions. #' #' @examples #' # Set the module search path for the example module