├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ └── pkgdown.yaml ├── .gitignore ├── CRAN-SUBMISSION ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R ├── S3.R ├── badge.R ├── detect_bad_recursion.R ├── file_path.R ├── from.R ├── here.R ├── import.R ├── import_aliases.R ├── into.R ├── is_script.R ├── make_import_call.R ├── modified.R ├── package_specs.R ├── safe_assign.R ├── scripts.R ├── suppress_output.R ├── symbol_as_character.R ├── symbol_list.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── build └── build_and_release_process.R ├── cran-comments.md ├── import.Rproj ├── import.svg ├── inst └── WORDLIST ├── man ├── examples │ ├── plusone_module.R │ ├── sequence_module.R │ └── some_module.R ├── figures │ ├── lifecycle-archived.svg │ ├── lifecycle-defunct.svg │ ├── lifecycle-deprecated.svg │ ├── lifecycle-experimental.svg │ ├── lifecycle-maturing.svg │ ├── lifecycle-questioning.svg │ ├── lifecycle-stable.svg │ ├── lifecycle-superseded.svg │ └── logo.png ├── import.Rd └── importfunctions.Rd ├── pkgdown └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── tests ├── test_import │ ├── cleanup_environment.R │ ├── module_S3.R │ ├── module_base.R │ ├── module_chdir │ │ └── module_chdir.R │ ├── module_hidden_objects.R │ ├── module_recursive │ │ └── src │ │ │ ├── run_me.R │ │ │ ├── text.R │ │ │ ├── title_text.R │ │ │ ├── title_text_here.R │ │ │ └── to_title.R │ ├── module_recursive_inner.R │ ├── module_recursive_library.R │ ├── module_recursive_outer_from.R │ ├── module_recursive_outer_here.R │ ├── module_recursive_package_from.R │ ├── module_recursive_package_here.R │ ├── module_script_error.R │ ├── module_subsequent.R │ ├── packageToTest │ │ ├── DESCRIPTION │ │ ├── NAMESPACE │ │ ├── R │ │ │ ├── get.R │ │ │ └── hello.R │ │ └── man │ │ │ ├── get.Rd │ │ │ └── hello.Rd │ ├── packageToTest_0.1.0.tar.gz │ ├── skipped_test_module_urls.R │ ├── test_S3.R │ ├── test_basetemplate.R │ ├── test_from.R │ ├── test_hidden_objects.R │ ├── test_into_and_here.R │ ├── test_into_param.R │ ├── test_module_directories.R │ └── test_module_recursive.R ├── testthat.R └── testthat │ └── test_import.R └── vignettes ├── .gitignore ├── import.Rmd └── lifecycle-experimental.svg /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.md 4 | ^README\.Rmd 5 | ^cran-comments\.md$ 6 | ^import\.svg 7 | ^import\.png 8 | ^\.github$ 9 | ^_pkgdown\.yml$ 10 | ^docs$ 11 | ^pkgdown$ 12 | ^doc$ 13 | ^Meta$ 14 | ^CRAN-RELEASE$ 15 | ^revdep$ 16 | ^CRAN-SUBMISSION$ 17 | ^scratch$ 18 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: R-CMD-check 4 | 5 | jobs: 6 | R-CMD-check: 7 | runs-on: ${{ matrix.config.os }} 8 | 9 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | config: 15 | - {os: macOS-latest, r: 'release'} 16 | - {os: windows-latest, r: 'release'} 17 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 18 | - {os: ubuntu-latest, r: 'release'} 19 | - {os: ubuntu-latest, r: 'oldrel-1'} 20 | 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | R_KEEP_PKG_SOURCE: yes 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | r-version: ${{ matrix.config.r }} 33 | http-user-agent: ${{ matrix.config.http-user-agent }} 34 | use-public-rspm: true 35 | 36 | - uses: r-lib/actions/setup-r-dependencies@v2 37 | with: 38 | extra-packages: rcmdcheck 39 | 40 | - uses: r-lib/actions/check-r-package@v2 41 | 42 | - name: Show testthat output 43 | if: always() 44 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 45 | shell: bash 46 | 47 | - name: Upload check results 48 | if: failure() 49 | uses: actions/upload-artifact@main 50 | with: 51 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 52 | path: check 53 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main, docs, x/docs] 4 | 5 | name: pkgdown 6 | 7 | jobs: 8 | pkgdown: 9 | runs-on: macOS-latest 10 | env: 11 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - uses: r-lib/actions/setup-r@v2 16 | 17 | - uses: r-lib/actions/setup-pandoc@v2 18 | 19 | - name: Query dependencies 20 | run: | 21 | install.packages('remotes') 22 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 23 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 24 | shell: Rscript {0} 25 | 26 | - name: Cache R packages 27 | uses: actions/cache@v3 28 | with: 29 | path: ${{ env.R_LIBS_USER }} 30 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 31 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 32 | 33 | - name: Install dependencies 34 | run: | 35 | remotes::install_deps(dependencies = TRUE) 36 | install.packages("pkgdown") 37 | shell: Rscript {0} 38 | 39 | - name: Install package 40 | run: R CMD INSTALL . 41 | 42 | - name: Deploy package 43 | run: | 44 | git config --local user.email "actions@github.com" 45 | git config --local user.name "GitHub Actions" 46 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | inst/doc/ 5 | inst/doc 6 | docs 7 | revdep 8 | doc 9 | Meta 10 | /doc/ 11 | /Meta/ 12 | scratch 13 | -------------------------------------------------------------------------------- /CRAN-SUBMISSION: -------------------------------------------------------------------------------- 1 | Version: 1.3.2 2 | Date: 2024-01-20 10:09:37 UTC 3 | SHA: 14636772a57c66d67d6fcdbb97afc7ae447dec1f 4 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: import 2 | Type: Package 3 | Title: An Import Mechanism for R 4 | Version: 1.3.2.9001 5 | Authors@R: 6 | c(person(given = "Stefan Milton", 7 | family = "Bache", 8 | role = c("aut"), 9 | email = "stefan@stefanbache.dk"), 10 | person(given = "Magnus Thor", 11 | family = "Torfason", 12 | role = c("aut", "cre"), 13 | email = "m@zulutime.net")) 14 | Description: Alternative mechanism for importing objects from packages 15 | and R modules. The syntax allows for importing multiple objects with a single 16 | command in an expressive way. The import package bridges some of the gap 17 | between using library (or require) and direct (single-object) imports. 18 | Furthermore the imported objects are not placed in the current environment. 19 | License: MIT + file LICENSE 20 | ByteCompile: TRUE 21 | URL: https://import.rticulate.org/, https://github.com/rticulate/import 22 | BugReports: https://github.com/rticulate/import/issues 23 | Suggests: 24 | knitr, 25 | rmarkdown, 26 | magrittr, 27 | testthat 28 | Language: en-US 29 | VignetteBuilder: knitr 30 | RoxygenNote: 7.3.2 31 | Encoding: UTF-8 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2017 2 | COPYRIGHT HOLDER: Stefan Milton Bache 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(from) 4 | export(here) 5 | export(into) 6 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 2 | Version 1.3.2.900x 3 | ============= 4 | 5 | * Minor bugfixes 6 | 7 | 8 | Version 1.3.2 9 | ============= 10 | 11 | * This is a minor bugfix release addressing an issue in the way method names 12 | were constructed for S3 imports. 13 | 14 | 15 | 16 | Version 1.3.1 17 | ============= 18 | 19 | * The `import` package will now by default use the full current set of library 20 | paths, i.e. the result of `.libPaths()`, while in versions up to and including 21 | `1.3.0` this defaulted to use only the *first* entry in the library paths, i.e. 22 | `.library=.libPaths()[1L]`. 23 | 24 | * The order of `loadNamespace()` and `getNamespaceExports()` has been changed in 25 | `import::from()`. While this is intended to be a bug fix, it is possible that 26 | it affects usage in situations where specificity about the library path is 27 | important. 28 | 29 | * The two aforementioned changes had to be implemented jointly, because the bug 30 | that is addressed by the second change was masking a bug that then needed to 31 | be fixed by the second change. A detailed analysis of this can be found in 32 | issue [#56](https://github.com/rticulate/import/issues/56) on GitHub. 33 | 34 | * A regression introduced in 1.3.0, that prevented importing multiple 35 | scripts into the same environment in some situations, has now been fixed. 36 | 37 | * Errors that occur while importing modules are now handled more effectively. 38 | 39 | * An issue with package documentation, correctly documenting `"_PACKAGE"` to 40 | ensure a `-package` alias is added. 41 | 42 | 43 | 44 | Version 1.3.0 45 | ============= 46 | 47 | * import::from/into now support importing `.into` `symbol`s, regardless of 48 | `.character_only` value, and NSE is never performed on this parameter. In 49 | other words, the `.into` parameter should now always be a regular variable, 50 | and can be of type `character` (indicating a named environment) or 51 | `environment` (indicating an unnamed environment). Curly brackets `{}` are no 52 | longer needed, and are simply ignored if present. 53 | 54 | * import::from/here/into now have a new `.S3` parameter, setting it to `TRUE` 55 | which allows automatic detection and registration of S3 generics and methods. 56 | This is an initial experimental implementation, and is disabled by default. 57 | 58 | * import::from/here/into now support importing hidden objects (those with names 59 | prefixed by a period). Users should be aware that importing objects with the 60 | same names as named function parameters may cause issues (and this could 61 | happen as well with any new parameters that may be added to the `import` 62 | package in the future) 63 | 64 | * Minor patch to import fixes a bug when importing from a library not defined in 65 | the `libPaths`. The namespace was fixed to be imported earlier in the function 66 | definition so that later functions that do not use a `lib.loc` parameter (such 67 | as `getNamespaceExports`) can successfully reference the namespace. 68 | 69 | * Minor patch to import fixes a bug where function `get` from namespaces other 70 | than `package:base` can be incorrectly substituted in `make_import_call`. This 71 | fix also applies to `getExportedValue`, even though this function is less 72 | likely to be masked. 73 | 74 | * Several documentation improvements. 75 | 76 | 77 | 78 | Version 1.2.0 79 | ============= 80 | 81 | * import is now more strict and won't allow an import from a different 82 | environment to replace an existing import of that name and location. 83 | * One can now import directly into an environment (which may not be attached) 84 | by wrapping it in braces, e.g. import::from(pkg, symbol, .into = {environment()}) 85 | * import::from/here/into now include a new option, `.character_only`, that 86 | suppresses non-standard evaluation and thus allows passing object names 87 | or locations in variables (character strings). 88 | * import::from/here/into now include new options, `.all` and `.except`, that 89 | allow the user to import all functions, or all functions except a few, from a 90 | package or module. 91 | * import::from/here/into now include a new option, "`.chdir`", specifying whether 92 | the working directory is changed before sourcing modules to import. 93 | * import::from/here/into now include a new option, "`.directory`", 94 | * import::here() has been fixed to use environment() explicitly to import into 95 | the current environment, rather than importing into "" (the empty string), 96 | which no longer works because of upstream changes. Passing "" to "`.into`" 97 | parameter is now only a shorthand for .into = {environment()}. 98 | * Using import::from(), import::into(), or library() inside a module that is 99 | being sourced from the import::from/here/into 100 | * The parameter order for import::here() has been changed to be consistent 101 | with import::from(). The change is backwards compatible because the moved 102 | parameter (`.from`) was previously behind the ellipsis, requiring the use of 103 | named parameters. 104 | * Unit tests have been added and various issues fixed. 105 | 106 | 107 | Version 1.1.0 108 | ============= 109 | 110 | * There is now support to import objects from script files, i.e. a kind of 111 | "module". Scripts meant to expose objects for import should ideally be 112 | side-effect free, but this is not enforced. Any attachments are detached 113 | after import, but loaded namespaces remain loaded. 114 | 115 | 116 | Version 1.0.2 117 | ============= 118 | 119 | * You can now specify which library to use, and only one library is ever 120 | used in a single call: there is no ambiguity about where imports come from. 121 | * There is a distinction between using double- and triple colon syntax; 122 | analogously to using :: and ::: operators. 123 | * If the package is attached (e.g. via library) there is a startup message 124 | informing the user that the package is not meant to be attached. 125 | * It is only possible to use the functions with the `import::` / `import:::` 126 | syntax. 127 | -------------------------------------------------------------------------------- /R/S3.R: -------------------------------------------------------------------------------- 1 | #' Register S3 method 2 | #' 3 | #' Detect if `x` is an S3 method and if so, register it in an environment. 4 | #' 5 | #' Given the ambiguity in class and method names, here we detect only the 6 | #' canonical form `generic.class` methods (or `generic.class.name` to allow for 7 | #' `data.frame` methods). This ambiguity cannot be sufficiently resolved given 8 | #' that both class and generic can contain dots in their names. Take for an 9 | #' example `t.data.frame` and `t.test.matrix` and a hypothetical (but valid) `t` 10 | #' generics for class `test.data.frame`. Users should resolve possible ambiguity 11 | #' by registering the S3 manually. 12 | #' 13 | #' @param x A potential S3 method. 14 | #' @param env An environemnt in which `x` will be registered if `x` is an S3 15 | #' method. 16 | #' 17 | #' @return A four-element vector of mode `character` containing information on 18 | #' whether the object `x` is S3 class and name of itself derived generic and 19 | #' class names. Note that this vector contains a boolean value that is 20 | #' represented as a `character`, so care must be taken in handling this value. 21 | #' 22 | #' @md 23 | #' @noRd 24 | register_s3_method <- function(x, env){ 25 | method <- base::get(x, envir=env) 26 | 27 | if(!is.function(method) || !utils::isS3method(x, envir=env)) 28 | return(c(x, FALSE, NA, NA)) 29 | 30 | parts <- unlist(strsplit(x, split=".", fixed=TRUE)) 31 | generic <- parts[1] 32 | class <- paste0(parts[-1], collapse=".") 33 | 34 | registerS3method(generic, class, method, env) 35 | 36 | c(x, TRUE, generic, class) 37 | } 38 | 39 | #' Register S3 methods 40 | #' 41 | #' Find S3 methods in `x` and register them in an environment `env`. 42 | #' 43 | #' @param x A vector of objects that might contain S3 methods. 44 | #' @param env An environment in which detected S3 methods from `x` 45 | #' will be registered. 46 | #' 47 | #' @return A data.frame containing information whether the objects in `x` are 48 | #' S3 methods and if so, the detected class and generics. 49 | #' 50 | #' @md 51 | #' @noRd 52 | register_s3_methods <- function(x, env){ 53 | methods <- lapply(x, register_s3_method, env=env) 54 | methods <- data.frame(do.call(rbind, methods)) 55 | names(methods) <- c("function", "isS3", "generic", "class") 56 | methods 57 | } 58 | -------------------------------------------------------------------------------- /R/badge.R: -------------------------------------------------------------------------------- 1 | 2 | #' Add a lifecycle badge to html help versions only 3 | #' 4 | #' @param stage A character representing the stage 5 | #' @return Badge code appropriate for inclusion in .Rd file 6 | #' 7 | #' @md 8 | #' @noRd 9 | badge <- function(stage) 10 | { 11 | # Utility function for capital case 12 | upcase1 <- function (x) 13 | { 14 | substr(x, 1, 1) <- toupper(substr(x, 1, 1)) 15 | x 16 | } 17 | 18 | # Check arguments 19 | stages <- c("experimental", "stable", "superseded", "deprecated") 20 | stopifnot(stage %in% stages) 21 | 22 | # Construct .Rd code 23 | url <- paste0("https://lifecycle.r-lib.org/articles/stages.html#", stage) 24 | html <- sprintf("\\href{%s}{\\figure{%s}{options: alt='[%s]'}}", 25 | url, file.path(sprintf("lifecycle-%s.svg", stage)), upcase1(stage)) 26 | text <- sprintf("\\strong{[%s]}", upcase1(stage)) 27 | sprintf("\\ifelse{html}{%s}{%s}", html, text) 28 | } 29 | -------------------------------------------------------------------------------- /R/detect_bad_recursion.R: -------------------------------------------------------------------------------- 1 | #' Detect bad recursion in function call 2 | #' 3 | #' Examines the stack trace that is passed as an argument to determine if 4 | #' `import::*()` has been called recursively in a way that is not allowed (i.e. 5 | #' bad). 6 | #' 7 | #' Any recursive use of `import::*()` within a module that is itself being 8 | #' imported should only be done with `import::here()`, never with 9 | #' `import::from()` or `import::into()`. 10 | #' 11 | #' @param stack_trace A list object containing the value of a `.traceback()` call. 12 | #' 13 | #' @return logical indicating whether a bad recursion call was detected 14 | #' 15 | #' @md 16 | #' @noRd 17 | detect_bad_recursion <- function(stack_trace) { 18 | 19 | # Extract all lines correxponding to calls to import::* 20 | x <- grep("^import::", unlist(stack_trace), value = TRUE) 21 | 22 | # If more than one line starts with import:::?from, we are inside 23 | # a recursive call. That is OK, as long as the last call originates+ 24 | # in a call to import here (i.e. the second element in the vector 25 | # starts with import:::?here) 26 | if ( length(grep("^import:::?from", x, value=TRUE)) > 1 ) { 27 | if ( !grepl("^import:::?here", x[2])) { 28 | # Ouch, we have detected a bad recursion 29 | return(TRUE) 30 | } 31 | } 32 | return(FALSE) 33 | } 34 | -------------------------------------------------------------------------------- /R/file_path.R: -------------------------------------------------------------------------------- 1 | #' Return a file path given file name and directory 2 | #' 3 | #' Given a directory and a file name (path), return the combined path. For 4 | #' relative file names, this is identical to `file.path()`, for absolute file 5 | #' names, it simply returns the absolute file name. 6 | #' 7 | #' @param working_directory The working directory to use. 8 | #' @param file_name The name of a possible R script file. 9 | #' 10 | #' @return The resulting file name of the file. 11 | #' 12 | #' @md 13 | #' @noRd 14 | file_path <- function(working_directory, file_name) { 15 | 16 | # Credit to Henrik Bengtson and the R.utils package 17 | # https://cran.r-project.org/web/packages/R.utils/index.html 18 | isAbsolutePath <- function(pathname, ...) { 19 | 20 | # Argument 'pathname': 21 | pathname <- as.character(pathname) 22 | # BACKWARD COMPATIBILITY: Treat empty path specially? 23 | #pathname <- .getPathIfEmpty(pathname, where="isAbsolutePath") 24 | 25 | nPathnames <- length(pathname) 26 | 27 | # Nothing to do? 28 | if (nPathnames == 0L) return(logical(0L)) 29 | 30 | # Multiple path to be checked? 31 | if (nPathnames > 1L) { 32 | res <- sapply(pathname, FUN=isAbsolutePath, ...) 33 | return(res) 34 | } 35 | 36 | # A missing pathname? 37 | if (is.na(pathname)) return(FALSE) 38 | 39 | # Recognize '~' paths 40 | if (regexpr("^~", pathname) != -1L) 41 | return(TRUE) 42 | 43 | # Windows paths 44 | if (regexpr("^.:(/|\\\\)", pathname) != -1L) 45 | return(TRUE) 46 | 47 | # Split pathname... 48 | components <- strsplit(pathname, split="[/\\]")[[1L]] 49 | if (length(components) == 0L) 50 | return(FALSE) 51 | 52 | (components[1L] == "") 53 | } 54 | 55 | 56 | if ( isAbsolutePath(file_name) ) { 57 | file_name 58 | } else { 59 | file.path(working_directory, file_name) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /R/from.R: -------------------------------------------------------------------------------- 1 | #' Import Objects From a Package. 2 | #' 3 | #' The `import::from` and `import::into` functions provide an alternative way to 4 | #' import objects (e.g. functions) from packages. It is sometimes preferred over 5 | #' using `library` (or `require`) which will import all objects exported by the 6 | #' package. The benefit over `obj <- pkg::obj` is that the imported objects will 7 | #' (by default) be placed in a separate entry in the search path (which can be 8 | #' specified), rather in the global/current environment. Also, it is a more 9 | #' succinct way of importing several objects. Note that the two functions are 10 | #' symmetric, and usage is a matter of preference and whether specifying the 11 | #' `.into` argument is desired. The function `import::here` imports into the 12 | #' current environment. 13 | #' 14 | #' The function arguments can be quoted or unquoted as with e.g. `library`. In 15 | #' any case, the character representation is used when unquoted arguments are 16 | #' provided (and not the value of objects with matching names). The period in 17 | #' the argument names `.into` and `.from` are there to avoid name clash with 18 | #' package objects. However, while importing of hidden objects (those with names 19 | #' prefixed by a period) is supported, care should be taken not to conflict with 20 | #' the argument names. The double-colon syntax `import::from` allows for imports 21 | #' of exported objects (and lazy data) only. To import objects that are not 22 | #' exported, use triple-colon syntax, e.g. `import:::from`. The two ways of 23 | #' calling the `import` functions analogue the `::` and `:::` operators 24 | #' themselves. 25 | #' 26 | #' Note that the `import` functions usually have the (intended) side-effect of 27 | #' altering the search path, as they (by default) import objects into the 28 | #' "imports" search path entry rather than the global environment. 29 | #' 30 | #' The `import` package is not meant to be loaded with `library` (and will 31 | #' output a message about this if attached), but rather it is named to make the 32 | #' function calls expressive without the need to loading before use, i.e. it is 33 | #' designed to be used explicitly with the `::` syntax, e.g. `import::from(pkg, 34 | #' x, y)`. 35 | #' 36 | #' @section Packages vs. modules: 37 | #' `import` can either be used to import objects either from R packages or from 38 | #' `R` source files. If the `.from` parameter ends with '.R' or '.r', `import` 39 | #' will look for a source file to import from. A source file in this context is 40 | #' referred to as a `module` in the documentation. 41 | #' 42 | #' @section Package Versions: 43 | #' With `import` you can specify package version requirements. To do this add a 44 | #' requirement in parentheses to the package name (which then needs to be 45 | #' quoted), e.g `import::from("parallel (>= 3.2.0)", ...)`. You can use the 46 | #' operators `<`, `>`, `<=`, `>=`, `==`, `!=`. Whitespace in the specification 47 | #' is irrelevant. 48 | #' 49 | #' @rdname importfunctions 50 | #' @param .from The package from which to import. 51 | #' @param ... Names or name-value pairs specifying objects to import. If 52 | #' arguments are named, then the imported object will have this new name. 53 | #' @param .into The environment into which the imported objects should be 54 | #' assigned. If the value is of mode `character`, it is treated as referring 55 | #' to a named environment on the search path. If it is of mode `environment`, 56 | #' the objects are assigned directly to that environment. Using 57 | #' `.into=environment()` causes imports to be made into the current 58 | #' environment; `.into=""` is an equivalent shorthand value. 59 | #' @param .library character specifying the library to use when importing from 60 | #' packages. Defaults to the current set of library paths (note that the 61 | #' default value was different in versions up to and including `1.3.0`). 62 | #' @param .directory character specifying the directory to use when importing 63 | #' from modules. Defaults to the current working directory. If .from is a 64 | #' module specified using an absolute path (i.e. starting with `/`), this 65 | #' parameter is ignored. 66 | #' @param .all logical specifying whether all available objects in a package or 67 | #' module should be imported. It defaults to FALSE unless .exclude is being 68 | #' used to omit particular functions. 69 | #' @param .except character vector specifying any objects that should not be 70 | #' imported. Any values specified here override both values provided in `...` 71 | #' and objects included because of the `.all` parameter 72 | #' @param .chdir logical specifying whether to change directories before 73 | #' sourcing a module (this parameter is ignored for libraries) 74 | #' @param .character_only A logical indicating whether `.from` and `...` can be 75 | #' assumed to be character strings. (Note that this parameter does not apply 76 | #' to how the `.into` parameter is handled). 77 | #' @param .S3 `r badge("experimental")` A logical indicating whether an 78 | #' automatic detection and registration of S3 methods should be performed. The 79 | #' S3 methods are assumed to be in the standard form `generic.class`. Methods 80 | #' can also be registered manually instead using be registered manually 81 | #' instead using the `.S3method(generic, class, method)` call. *This is an 82 | #' experimental feature. We think it should work well and you are encouraged 83 | #' to use it and report back – but the syntax and semantics may change in the 84 | #' future to improve the feature.* 85 | #' 86 | #' @return a reference to the environment containing the imported objects. 87 | #' 88 | #' @export 89 | #' @examples 90 | #' import::from(parallel, makeCluster, parLapply) 91 | #' import::into("imports:parallel", makeCluster, parLapply, .from = parallel) 92 | #' 93 | #' @seealso 94 | #' Helpful links: 95 | #' * [https://import.rticulate.org](https://import.rticulate.org) 96 | #' * [https://github.com/rticulate/import](https://github.com/rticulate/import) 97 | #' * [https://github.com/rticulate/import/issues](https://github.com/rticulate/import/issues) 98 | #' 99 | #' @md 100 | from <- function(.from, ..., .into = "imports", 101 | .library = .libPaths(), .directory=".", 102 | .all=(length(.except) > 0), .except=character(), 103 | .chdir = TRUE, .character_only = FALSE, .S3 = FALSE) 104 | { 105 | # Capture the relevant part of the call to see if 106 | # the import function is used as intended. 107 | cl <- match.call()[[1L]] 108 | 109 | # Check if only exported objects are considered valid, 110 | # i.e. when called as import::from 111 | exports_only <- identical(cl, call("::", quote(import), quote(from))) 112 | 113 | # If not, the only other valid way of calling the function is import:::from 114 | # which will allow non-exported values too. 115 | if (!exports_only && !identical(cl, call(":::", quote(import), quote(from)))) 116 | stop("Use `import::` or `import:::` when importing objects.", call. = FALSE) 117 | 118 | # Ensure that .from is specified. 119 | if (missing(.from)) 120 | stop("Argument `.from` must be specified for import::from.", call. = FALSE) 121 | 122 | # .all or .except must not be used in conjunction with ::: notation 123 | if (identical(cl, call(":::", quote(import), quote(from))) && 124 | (.all!=FALSE || length(.except)!=0)) 125 | stop("`import:::` must not be used in conjunction with .all or .except", call. = FALSE) 126 | 127 | # Extract the arguments 128 | symbols <- symbol_list(..., .character_only = .character_only, .all = .all) 129 | 130 | # If .character_only==FALSE, we substitute the symbol with its string representation 131 | if (!isTRUE(.character_only)) 132 | .from <- symbol_as_character(substitute(.from)) 133 | 134 | # .into =="" is a special case, indicating that objects should be imported directly 135 | # into the calling environment (as in import::here()). So we set .into<-parent.frame() 136 | if (is.character(.into) && .into=="") 137 | .into <- parent.frame() 138 | 139 | # If we are inside a bad recursion call, warn and set .into to the only 140 | # acceptable value for an inner recursive call, which is parent.frame() (the calling environment) 141 | if (detect_bad_recursion(.traceback(0))) { 142 | .into <- parent.frame() 143 | warning(paste0("import::from() or import::into() was used recursively, to import \n", 144 | " a module from within a module. Please rely on import::here() \n", 145 | " when using the import package in this way.\n", 146 | " See vignette(import) for further details.")) 147 | } 148 | 149 | # .into is either a character or an environment. Check which it is 150 | into_is_env <- is.environment(.into) 151 | 152 | # .into handling. Check whether assignment should be done in a named entry in the search path. 153 | use_into <- !exists(".packageName", parent.frame(), inherits = TRUE) && 154 | !into_is_env 155 | 156 | # Check whether the name already exists in the search path. 157 | into_exists <- !into_is_env && (.into %in% search()) 158 | 159 | # Create the entry if needed. 160 | make_attach <- attach # Make R CMD check happy. 161 | if (use_into && !into_exists) 162 | make_attach(NULL, 2L, name = .into) 163 | 164 | # Determine whether the source is a script or package. 165 | from_is_script <- is_script(.from, .directory) 166 | 167 | if (from_is_script) { 168 | from_created <- .from %in% ls(scripts, all.names = TRUE) 169 | if (!from_created || modified(.from, .directory) > modified(scripts[[.from]])) { 170 | 171 | # Find currently attachments 172 | attached <- search() 173 | 174 | # Create a new environment to manage the script module if it does not exist 175 | if (!from_created) 176 | assign(.from, new.env(parent = parent.frame()), scripts) 177 | 178 | # Make modification time stamp 179 | modified(scripts[[.from]]) <- modified(.from, .directory) 180 | 181 | # Make behaviour match that of a package, i.e. import::from won't use "imports" 182 | scripts[[.from]][[".packageName"]] <- .from 183 | 184 | # Source the file into the new environment. 185 | packages_before <- .packages() 186 | tryCatch( 187 | expr = suppress_output(sys.source(file_path(.directory, .from), scripts[[.from]], chdir = .chdir)), 188 | error = function(cnd) { 189 | rm(list = .from, envir = scripts) 190 | stop("Failed to import ", .from, " due to the following error:\n", cnd$message, call. = FALSE) 191 | } 192 | ) 193 | 194 | # If sourcing the script loaded new packages, raise error 195 | packages_after <- .packages() 196 | if ( !identical(packages_before,packages_after) ) { 197 | warning("A package was loaded using 'library(...)' from within an import::*() module.\n", 198 | " Please rely on import::here() to load objects from packages within an \n", 199 | " import::*() module. See vignette(import) for further details." ) 200 | } 201 | 202 | # Make sure to detach any new attachments. 203 | on.exit({ 204 | to_deattach <- Filter(function(.) !. %in% attached, search()) 205 | for (d in to_deattach) 206 | detach(d, character.only = TRUE) 207 | }) 208 | } 209 | pkg <- scripts[[.from]] 210 | pkg_name <- .from 211 | 212 | # Create list of all available objects (for use with the .all parameter) 213 | all_objects <- ls(scripts[[.from]], all.names = TRUE) 214 | 215 | # Only for scripts: Register S3 methods, if needed. 216 | if (.S3) 217 | register_s3_methods(all_objects, env=pkg) 218 | 219 | } else { 220 | # Load the package namespace, which is passed to the import calls. 221 | spec <- package_specs(.from) 222 | pkg <- tryCatch( 223 | loadNamespace(spec$pkg, lib.loc = .library, 224 | versionCheck = spec$version_check), 225 | error = function(e) stop(conditionMessage(e), call. = FALSE) 226 | ) 227 | all_objects <- getNamespaceExports(spec$pkg) 228 | pkg_name <- spec$pkg 229 | } 230 | # If .all parameter was specified, override with list of all objects 231 | # (excluding internal variable __last_modified__) 232 | # Take care not to lose the names of any manually specified parameters 233 | if (.all) { 234 | all_objects <- setdiff(all_objects, c("__last_modified__", ".packageName")) 235 | names(all_objects) <- all_objects 236 | symbols <- c(symbols,all_objects) 237 | symbols <- symbols[!duplicated(symbols)] 238 | } 239 | 240 | # If .except parameter was specified, any object specified there 241 | # should be omitted from the import 242 | if (length(.except)>0) { 243 | symbols <- symbols[!(symbols %in% .except)] # Fancy setdiff() to preserve names 244 | } 245 | 246 | # import each object specified in the argument list. 247 | for (s in seq_along(symbols)) { 248 | import_call <- 249 | make_import_call( 250 | list(new = names(symbols)[s], 251 | nm = symbols[s], 252 | ns = pkg, 253 | inh = !exports_only, 254 | pos = if (use_into || into_is_env) .into else -1), 255 | exports_only && !from_is_script) 256 | 257 | if (!from_is_script) 258 | import_aliases[[names(symbols)[s]]] <- 259 | call("::", as.symbol(pkg_name), as.symbol(symbols[s])) 260 | 261 | # Evaluate the import call. 262 | tryCatch(eval.parent(import_call), 263 | error = function(e) stop(e$message, call. = FALSE)) 264 | } 265 | 266 | if (!into_is_env && !exists("?", .into, mode = "function", inherits = FALSE)) { 267 | assign("?", `?redirect`, .into) 268 | } 269 | 270 | invisible(as.environment(.into)) 271 | } 272 | -------------------------------------------------------------------------------- /R/here.R: -------------------------------------------------------------------------------- 1 | #' @rdname importfunctions 2 | #' @export 3 | here <- function(.from, ..., 4 | .library = .libPaths()[1L], .directory=".", 5 | .all=(length(.except) > 0), .except=character(), 6 | .chdir = TRUE, .character_only = FALSE, .S3 = FALSE) 7 | { 8 | # Capture the call and check that it is valid. 9 | cl <- match.call() 10 | if (!identical(cl[[1L]], call( "::", quote(import), quote(here))) && 11 | !identical(cl[[1L]], call(":::", quote(import), quote(here)))) 12 | stop("Use `import::` or `import:::` when importing objects.", call. = FALSE) 13 | 14 | # Ensure the needed arguments are provided. 15 | if (missing(.from)) 16 | stop("Argument `.from` must be specified.", call. = FALSE) 17 | 18 | # Rewrite the call to import::from syntax and evaluate in parent frame. 19 | cl <- match.call() 20 | cl[[1L]][[3L]] <- quote(from) 21 | cl[[".into"]] <- quote({environment()}) 22 | 23 | eval.parent(cl) 24 | } 25 | -------------------------------------------------------------------------------- /R/import.R: -------------------------------------------------------------------------------- 1 | #' This package is not intended for use with `library`. It is named to make 2 | #' calls like `import::from(pkg, fun1, fun2)` expressive. Using the `import` 3 | #' functions complements the standard use of `library(pkg)`(when most objects 4 | #' are needed, and context is clear) and `obj <- pkg::obj` (when only a single 5 | #' object is needed). 6 | #' 7 | #' @docType package 8 | #' @name import 9 | #' @title An Import Mechanism for R 10 | #' @author Stefan Milton Bache 11 | #' @description This is an alternative mechanism for importing objects from 12 | #' packages. The syntax allows for importing multiple objects from a package 13 | #' with a single command in an expressive way. The `import` package bridges 14 | #' some of the gap between using `library` (or `require`) and direct 15 | #' (single-object) imports. Furthermore the imported objects are not placed in 16 | #' the current environment (although possible), but in a named entry in the 17 | #' search path. 18 | #' @seealso For usage instructions and examples, see [`from`], [`into`], or 19 | #' [`here`]. 20 | #' 21 | #' Helpful links: 22 | #' * [https://import.rticulate.org](https://import.rticulate.org) 23 | #' * [https://github.com/rticulate/import](https://github.com/rticulate/import) 24 | #' * [https://github.com/rticulate/import/issues](https://github.com/rticulate/import/issues) 25 | #' 26 | #' @md 27 | "_PACKAGE" 28 | -------------------------------------------------------------------------------- /R/import_aliases.R: -------------------------------------------------------------------------------- 1 | #' Environment with Mappings from Import- and Original Names 2 | #' 3 | #' This is used to redirect documentation requests on imported names. 4 | #' 5 | #' @md 6 | #' @noRd 7 | import_aliases <- new.env() 8 | 9 | 10 | `?redirect` <- function(e1, e2) 11 | { 12 | redirect <- FALSE 13 | cl <- match.call() 14 | 15 | sym <- substitute(e1) 16 | if (missing(e2) && 17 | (is.symbol(sym) || (is.character(sym) && length(sym) == 1L))) { 18 | sym <- import_aliases[[symbol_as_character(sym)]] 19 | if (!is.null(sym)) { 20 | 21 | cl[[2L]] <- sym 22 | redirect <- TRUE 23 | msg <- paste0("import: showing documentation for ", 24 | deparse(sym, nlines = 1)) 25 | message(msg) 26 | } 27 | } 28 | 29 | if (!redirect) { 30 | cl[[1]] <- call("::", "utils", "?") 31 | } 32 | 33 | eval(cl, parent.frame(), parent.frame()) 34 | } 35 | -------------------------------------------------------------------------------- /R/into.R: -------------------------------------------------------------------------------- 1 | #' @rdname importfunctions 2 | #' @export 3 | into <- function(.into, ..., .from, 4 | .library = .libPaths()[1L], .directory=".", 5 | .all=(length(.except) > 0), .except=character(), 6 | .chdir = TRUE, .character_only = FALSE, .S3 = FALSE) 7 | { 8 | # Capture the call and check that it is valid. 9 | cl <- match.call() 10 | if (!identical(cl[[1L]], call( "::", quote(import), quote(into))) && 11 | !identical(cl[[1L]], call(":::", quote(import), quote(into)))) 12 | stop("Use `import::` or `import:::` when importing objects.", call. = FALSE) 13 | 14 | # Ensure the needed arguments are provided. 15 | if (missing(.into) || missing(.from)) 16 | stop("Arguments .into and .from must be specified.", 17 | call. = FALSE) 18 | 19 | # Rewrite the call to import::from syntax and evaluate in parent frame. 20 | cl[[1L]][[3L]] <- quote(from) 21 | 22 | eval.parent(cl) 23 | } 24 | -------------------------------------------------------------------------------- /R/is_script.R: -------------------------------------------------------------------------------- 1 | #' Determine if File is an R Script. 2 | #' 3 | #' Given an import source, this function will infer whether it is a script (as 4 | #' opposed to a package). It will be treated as a file if there exists a file 5 | #' with the provided name, and it ends in .R or .r. 6 | #' 7 | #' @param file_name The name of a possible R script file. 8 | #' 9 | #' @return A logical indicating whether `file_name` is a script (as opposed to a 10 | #' package). 11 | #' 12 | #' @md 13 | #' @noRd 14 | is_script <- function(file_name, .directory) 15 | { 16 | is.character(file_name) && 17 | is.character(.directory) && 18 | length(file_name) == 1L && 19 | isTRUE(grepl(".+\\.[rR]$", file_name)) && 20 | file.exists(file_path(.directory,file_name)) 21 | } 22 | -------------------------------------------------------------------------------- /R/make_import_call.R: -------------------------------------------------------------------------------- 1 | #' Make an import call object. 2 | #' 3 | #' The import call constructed by this function can be evaluated with `eval` to 4 | #' perform the actual import. 5 | #' 6 | #' @param params A list of parameters to substitute in the call. 7 | #' @param exports_only A logical indicating whether only exported objects are 8 | #' allowed. 9 | #' 10 | #' @return A call object. 11 | #' 12 | #' @md 13 | #' @noRd 14 | make_import_call <- function(params, exports_only) 15 | { 16 | cl <- 17 | if (exports_only) 18 | substitute(safe_assign(new, base::getExportedValue(nm, ns = ns), pos = pos), 19 | params) 20 | else 21 | substitute(safe_assign(new, base::get(nm, envir = ns, inherits = inh), pos = pos), 22 | params) 23 | 24 | cl[[1]] <- safe_assign 25 | 26 | cl 27 | } 28 | -------------------------------------------------------------------------------- /R/modified.R: -------------------------------------------------------------------------------- 1 | #' Get Modification Time of Script Module. 2 | #' 3 | #' When a script is loaded to allow imports, a modification time is stored to 4 | #' allow re-sourcing the script if modified after it was loaded. 5 | #' 6 | #' @param e A script environment or the file name of a script file. 7 | #' @param .directory A directory to use in the case where e is a character 8 | #' variable, to be treated as a file path. The file path to use is then 9 | #' constructed using `file.path(.directory, e)` 10 | #' @return POSIXct indicating modification time of the file when it was sourced 11 | #' into `e` (within `scripts`). 12 | #' 13 | #' @md 14 | #' @noRd 15 | modified <- function(e, .directory=NULL) 16 | { 17 | if (is.environment(e)) { 18 | e[["__last_modified__"]] 19 | } else if (is.character(e) && is.character(.directory) && 20 | file.exists(file_path(.directory,e)) ) { 21 | file.info(file_path(.directory,e))[["mtime"]] 22 | } else { 23 | NULL 24 | } 25 | } 26 | 27 | #' Set Modification Time of Script Module. 28 | #' 29 | #' When a script is loaded to allow imports, a modification time is stored to 30 | #' allow re-sourcing the script if modified after it was loaded. 31 | #' 32 | #' @param e A script environment. 33 | #' @param value POSIXct indicating modification time. 34 | #' 35 | #' @md 36 | #' @noRd 37 | `modified<-` <- function(e, value) 38 | { 39 | if (!inherits(value, "POSIXct")) 40 | stop("Modification time must be a POSIXct timestamp.") 41 | 42 | assign("__last_modified__", value, envir = e) 43 | invisible(e) 44 | } 45 | -------------------------------------------------------------------------------- /R/package_specs.R: -------------------------------------------------------------------------------- 1 | #' Package Specifications 2 | #' 3 | #' Decompose package specification into a name and a list for `checkVersion` in 4 | #' `loadNamespace`. 5 | #' 6 | #' @param pkg A `character` with the name of the package, optionally with a 7 | #' version requirement in parentheses (as in Depends/Imports fields in package 8 | #' DESCRIPTION files). 9 | #' 10 | #' @return A `list` with `name` and `version_check`, the latter which can be 11 | #' passed to `loadNamespace`. 12 | #' 13 | #' @details One can specify the package version using the operators `>`, `<`, 14 | #' `>=`, `<=`, `==`, and `!=`. 15 | #' 16 | #' @examples 17 | #' package_specs("magrittr (>= 1.0.1)") 18 | #' package_specs("magrittr(1.5)") 19 | #' 20 | #' @md 21 | #' @noRd 22 | package_specs <- function(pkg) 23 | { 24 | if (!is.character(pkg) || length(pkg) != 1L) 25 | stop("`from` must be character with length 1.", call. = FALSE) 26 | 27 | contains_version_spec <- grepl("\\(.+\\)", pkg) 28 | 29 | if (contains_version_spec) { 30 | spec <- gsub("(.*\\()|(\\).*)", "", pkg) 31 | op <- gsub(".*((>)|(<)|(>=)|(<=)|(==)|(!=)).*", "\\1", spec) 32 | if (identical(spec, op)) 33 | op <- "==" 34 | version <- gsub(sprintf("( )|(%s)|(\\()|(\\))", op), "", spec) 35 | nm <- gsub("( )|(\\(.*\\))", "", pkg) 36 | list(pkg = nm, 37 | version_check = list(name = nm, op = op, version = version)) 38 | } else { 39 | list(pkg = gsub(" ", "", pkg), 40 | version_check = NULL) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /R/safe_assign.R: -------------------------------------------------------------------------------- 1 | #' Safe Assignment 2 | #' 3 | #' Wrapper for \code{assign} which will not allow assigning a name to a 4 | #' different value in the given environment. See [`base::assign()`] for 5 | #' information about the role of each parameter. 6 | #' 7 | #' @param x A variable name. 8 | #' @param value The value. 9 | #' @param pos Where to do assignment. 10 | #' @param envir The environment. 11 | #' @param inherits Should the enclosing environment be inspected? 12 | #' 13 | #' @md 14 | #' @noRd 15 | safe_assign <- function(x, value, pos = -1, envir = as.environment(pos), inherits = FALSE) 16 | { 17 | if (x %in% ls(envir, all.names = TRUE) && 18 | !identical(environment(envir[[x]]), environment(value))) 19 | stop(sprintf("Cannot assign name `%s` to different value in the given environment. Name already in use.", 20 | x), 21 | call. = FALSE) 22 | 23 | assign(x, value, pos, envir, inherits) 24 | } 25 | -------------------------------------------------------------------------------- /R/scripts.R: -------------------------------------------------------------------------------- 1 | #' Script Module Environment. 2 | #' 3 | #' In addition to importing from packages, `import` allows imports from `.R` 4 | #' scripts (serving as modules). The scripts will be sourced into this 5 | #' environment, from which values can be fetcthed. 6 | #' 7 | #' @md 8 | #' @noRd 9 | scripts <- new.env() 10 | -------------------------------------------------------------------------------- /R/suppress_output.R: -------------------------------------------------------------------------------- 1 | #' Suppress the output of a command 2 | #' 3 | #' This function simply calls [`base::suppressPackageStartupMessages()`]. 4 | #' 5 | #' @param e An expression that is passed to `suppressPackageStartupMessages` 6 | #' 7 | #' @md 8 | #' @noRd 9 | suppress_output <- function(e) suppressPackageStartupMessages(e) 10 | -------------------------------------------------------------------------------- /R/symbol_as_character.R: -------------------------------------------------------------------------------- 1 | #' Convert a possible symbol to character. 2 | #' 3 | #' @param symbol A `symbol` or `character` (of length one). 4 | #' 5 | #' @return A `character` representation of the input. 6 | #' 7 | #' @md 8 | #' @noRd 9 | symbol_as_character <- function(symbol) 10 | { 11 | if (is.symbol(symbol) || 12 | (is.character(symbol) && length(symbol) == 1L)) { 13 | as.character(symbol) 14 | } else { 15 | stop(sprintf("%s is not a valid symbol.", 16 | paste(as.character(symbol), collapse = " ")), 17 | call. = FALSE) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /R/symbol_list.R: -------------------------------------------------------------------------------- 1 | #' Process a symbol list provided passed as ellipses 2 | #' 3 | #' This function is used within a function that receives dot-arguments, and will 4 | #' create a named character vector. It ensures that all entries are named, and 5 | #' will use the value as name when it is missing. 6 | #' 7 | #' @param ... The ellipsis arguments to be converted. 8 | #' @param .character_only A `logical` indicating whether `...` can be assumed to 9 | #' be charater strings. 10 | #' @param .all A `logical` indicating whether to process all objects found in a 11 | #' given package or module. This variable is actually not used in this 12 | #' function and should probably either be removed or this function updated to 13 | #' utilize the value in a reasonable manner. 14 | #' 15 | #' @return A named character vector. 16 | #' 17 | #' @md 18 | #' @noRd 19 | symbol_list <- function(..., .character_only = FALSE, .all=FALSE) 20 | { 21 | if (isTRUE(.character_only)) { 22 | dots <- unlist(list(...)) 23 | } else { 24 | dots <- eval(substitute(alist(...)), parent.frame(), parent.frame()) 25 | } 26 | 27 | if (length(dots)==0) { 28 | # If .all was true, empty dots should no longer error 29 | return(character()) 30 | } 31 | 32 | names <- names(dots) 33 | unnamed <- if (is.null(names)) 1:length(dots) else which(names == "") 34 | dots <- vapply(dots, symbol_as_character, character(1)) 35 | 36 | names(dots)[unnamed] <- dots[unnamed] 37 | 38 | dots 39 | } 40 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | # Output a message for the user if they attach the package rather than use the 2 | # colon-syntax. 3 | .onAttach <- function(libname, pkgname) 4 | { 5 | msg <- 6 | paste0("The import package should not be attached.\n", 7 | "Use \"colon syntax\" instead, e.g. import::from, or import:::from.") 8 | 9 | packageStartupMessage(msg) 10 | 11 | invisible() 12 | } 13 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | # import 6 | 7 | 8 | [![CRAN status](https://www.r-pkg.org/badges/version/import)](https://CRAN.R-project.org/package=import) 9 | [![CRAN status shields](https://img.shields.io/badge/Git-`r desc::desc_get_version() `-success)](https://github.com/rticulate/import) 10 | [![R build status](https://github.com/rticulate/import/workflows/R-CMD-check/badge.svg)](https://github.com/rticulate/import/actions) 11 | 12 | 13 | # An Import Mechanism For R 14 | 15 | The import package is intended to simplify the way in which functions from 16 | external packages or modules are made available for use in R scripts. Learn more 17 | on the [package website](https://import.rticulate.org/), by reading 18 | [`vignette("import")`](https://import.rticulate.org/articles/import.html), or 19 | using the help (`?import::from`). 20 | 21 | ## Introduction 22 | 23 | The typical way of using functionality exposed by a package in R scripts is to 24 | load (and attach) the entire package with `library()` (or `require()`). This can 25 | have the **undesirable effect of masking objects** in the user's search path and 26 | can also make it difficult and **confusing to identify** what functionality 27 | comes from which package when using several `library` statements. 28 | 29 | The `import` package provides a simple alternative, allowing the user specify in 30 | a concise way exactly which objects. For example, the `Hmisc` package exposes 31 | over four hundred functions. Instead of exposing all of those functions, someone 32 | who only needs access to, say the `impute()` and the `nomiss()` functions, can 33 | import those functions only: 34 | 35 | ```R 36 | import::from(Hmisc, impute, nomiss) 37 | ``` 38 | 39 | For more on the motivation behind the package, see 40 | [vignette("import")](https://import.rticulate.org/articles/import.html) 41 | 42 | 43 | ## Installation 44 | 45 | Install the release version of `import` from CRAN using `pak` or 46 | `install.packages()`: 47 | 48 | ```R 49 | pak::pak("import") 50 | # or 51 | install.packages("import") 52 | ``` 53 | 54 | Install the development version of `import` from GitHub using `pak` or 55 | `devtools`: 56 | 57 | ```R 58 | pak::pak("rticulate/import") 59 | # or 60 | devtools::install_github("rticulate/import") 61 | ``` 62 | 63 | ## Usage 64 | 65 | ### Importing functions from R packages 66 | 67 | The most basic use case is to import a few functions from package (here the 68 | `psych` package): 69 | 70 | ```R 71 | import::from(psych, geometric.mean, harmonic.mean) 72 | geometric.mean(trees$Volume) 73 | ``` 74 | 75 | If one of the function names conflicts with an existing function (such as 76 | `filter` from the `dplyr` package) it is simple to rename it: 77 | 78 | ```R 79 | import::from(dplyr, select, arrange, keep_when = filter) 80 | keep_when(mtcars, hp>250) 81 | ``` 82 | 83 | Use `.all=TRUE` to import all functions from a package. If you want to rename 84 | one of them, you can still do that: 85 | 86 | ```R 87 | import::from(dplyr, keep_when = filter, .all=TRUE) 88 | ``` 89 | 90 | To omit a function from the import, use `.except` (which takes a character 91 | vector): 92 | 93 | ```R 94 | import::from(dplyr, .except=c("filter", "lag")) 95 | ``` 96 | 97 | Note that `import` tries to be smart about this and assumes that if you are 98 | using the `.except` parameter, you probably want to import everything you are 99 | _not_ explicitly omitting, and sets the `.all` parameter to `TRUE`. You can 100 | still override this in exceptional cases, but you seldom need to. 101 | 102 | These and other examples are discussed in more detail in the 103 | [Importing from Packages](https://import.rticulate.org/articles/import.html#importing-from-packages) 104 | section of the package vignette. 105 | 106 | ### Importing Functions from "Module" Scripts 107 | 108 | The `import` package allows R files to be used as "modules" from which functions 109 | are loaded. For example, the file 110 | [sequence_module.R](https://raw.githubusercontent.com/rticulate/import/master/man/examples/sequence_module.R) 111 | contains several functions calculating terms of mathematical sequences. It is 112 | possible to import from such files, just as one imports from packages: 113 | 114 | ```R 115 | import::from(sequence_module.R, fibonacci, square, triangular) 116 | ``` 117 | 118 | Renaming, as well as the `.all` and `.except` parameters, work in the same way 119 | as for packages: 120 | 121 | ```R 122 | import::from(sequence_module.R, fib=fibonacci, .except="square") 123 | ``` 124 | 125 | These and other examples are discussed in more detail in the 126 | [Importing from Modules](https://import.rticulate.org/articles/import.html#importing-functions-from-module-scripts) 127 | section of the package vignette. 128 | 129 | ### Choosing where import looks for packages or modules 130 | 131 | The `import` package will by default use the current set of library paths, i.e. 132 | the result of `.libPaths()`. It is, however, possible to specify a different set 133 | of library paths using the `.library` argument in any of the `import` functions, 134 | for example to import packages installed in a custom location, or to remove any 135 | ambiguity as to where imports come from. 136 | 137 | Note that in versions up to and including `1.3.0` this defaulted to use only the 138 | *first* entry in the library paths, i.e. `.library=.libPaths()[1L]`. We believe 139 | the new default is applicable in a broader set of circumstances, but if this 140 | change causes any issues, we would very much appreciate hearing about it. 141 | 142 | When importing from a module (.R file), the directory where `import` looks for 143 | the module script can be specified with the with `.directory` parameter. The 144 | default is `.` (the current working directory). 145 | 146 | ### Choosing where the imported functions are placed 147 | 148 | By default, imported objects are placed in a separate entity in the search path 149 | called "imports". One can also specify which names to use in the search path and 150 | use several to group imports: 151 | 152 | ```R 153 | import::from(magrittr, "%>%", "%$%", .into = "operators") 154 | import::from(dplyr, arrange, .into = "datatools") 155 | ``` 156 | 157 | If using custom search path entities actively, one might prefer the alternative 158 | syntax (which does the same but reverses the argument order): 159 | 160 | ```R 161 | import::into("operators", "%>%", "%$%", .from = magrittr) 162 | import::into("datatools", arrange, .from = dplyr) 163 | ``` 164 | 165 | If it is desired to place imported objects in the current environment, use 166 | `import::here()`: 167 | 168 | ### More advanced usage 169 | 170 | The `import` package is designed to be simple to use for basic cases, so it uses 171 | symbolic evaluation to allow the names of packages, modules and functions to be 172 | entered without quotes (except for operators, such as `"%>%"` which must be 173 | quoted). However, this means that it calling a variable containing the name of a 174 | module, or a vector of functions to import, will not work. For this use case, 175 | you can use the `.character_only` parameter: 176 | 177 | ```R 178 | module_name <- "../utils/my_module.R" 179 | 180 | # Will not work (import will look for a package called "module_name") 181 | import::from(module_name, foo, bar) 182 | 183 | # This will correctly import the foo() and bar() functions from "../utils/my_module.R" 184 | import::from(module_name, foo, bar, .character_only=TRUE) 185 | ``` 186 | 187 | The `.character_only` parameter is covered in more detail in the 188 | [Advanced Usage](https://import.rticulate.org/articles/import.html#advanced-usage) 189 | section of the package vignette, which also describes how you can import from 190 | module scripts stored online with the help of the `pins` package, or achieve 191 | python-like imports with the help of `{}` notation for environments in the 192 | `.into` parameter. 193 | 194 | # Contributing 195 | 196 | Contributions to this project are welcome. Please start by opening an issue or 197 | discussion thread. New features are added conservatively based on supply (is 198 | anyone willing to contribute an implementation of the feature?), demand (how 199 | many people seem to need a new feature?), and last, but not least, by whether a 200 | feature can be implemented without breaking backwards compatibility. 201 | 202 | - Created and authored by [@smbache](https://github.com/smbache) 203 | - Currently maintained by [@torfason](https://github.com/torfason) 204 | - Code contributions by 205 | [@awong234](https://github.com/awong234), 206 | [@brshallo](https://github.com/brshallo), 207 | [@flying-sheep](https://github.com/flying-sheep), 208 | [@hutch3232](https://github.com/hutch3232), 209 | [@J-Moravec](https://github.com/J-Moravec), 210 | [@klmr](https://github.com/klmr), 211 | [@mschilli87](https://github.com/mschilli87), 212 | [@olivroy](https://github.com/olivroy) 213 | [@aramirezreyes](https://github.com/aramirezreyes) 214 | 215 | *(Did we forget to add you? If so, please let us know!)* 216 | 217 | # See also: 218 | 219 | - Some of the use cases for `import` can now be handled directly in base R using 220 | the new `exclude` and `include.only` arguments of `library()` and `require()` 221 | - For an interesting but slightly different idea of Python-like modules for R, 222 | see the [box](https://klmr.me/box/) package by 223 | [@klmr](https://github.com/klmr) (previously called 224 | [modules](https://github.com/klmr/modules)). 225 | - Another approach, focused on treating the use of functions with naming 226 | conflicts as explicit errors is the 227 | [conflicted](https://github.com/r-lib/conflicted) package by 228 | [@hadley](https://github.com/hadley). 229 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # import 3 | 4 | 5 | 6 | [![CRAN 7 | status](https://www.r-pkg.org/badges/version/import)](https://CRAN.R-project.org/package=import) 8 | [![CRAN status 9 | shields](https://img.shields.io/badge/Git-1.3.2.9001-success)](https://github.com/rticulate/import) 10 | [![R build 11 | status](https://github.com/rticulate/import/workflows/R-CMD-check/badge.svg)](https://github.com/rticulate/import/actions) 12 | 13 | 14 | # An Import Mechanism For R 15 | 16 | The import package is intended to simplify the way in which functions 17 | from external packages or modules are made available for use in R 18 | scripts. Learn more on the [package 19 | website](https://import.rticulate.org/), by reading 20 | [`vignette("import")`](https://import.rticulate.org/articles/import.html), 21 | or using the help (`?import::from`). 22 | 23 | ## Introduction 24 | 25 | The typical way of using functionality exposed by a package in R scripts 26 | is to load (and attach) the entire package with `library()` (or 27 | `require()`). This can have the **undesirable effect of masking 28 | objects** in the user’s search path and can also make it difficult and 29 | **confusing to identify** what functionality comes from which package 30 | when using several `library` statements. 31 | 32 | The `import` package provides a simple alternative, allowing the user 33 | specify in a concise way exactly which objects. For example, the `Hmisc` 34 | package exposes over four hundred functions. Instead of exposing all of 35 | those functions, someone who only needs access to, say the `impute()` 36 | and the `nomiss()` functions, can import those functions only: 37 | 38 | ``` r 39 | import::from(Hmisc, impute, nomiss) 40 | ``` 41 | 42 | For more on the motivation behind the package, see 43 | [vignette(“import”)](https://import.rticulate.org/articles/import.html) 44 | 45 | ## Installation 46 | 47 | Install the release version of `import` from CRAN using `pak` or 48 | `install.packages()`: 49 | 50 | ``` r 51 | pak::pak("import") 52 | # or 53 | install.packages("import") 54 | ``` 55 | 56 | Install the development version of `import` from GitHub using `pak` or 57 | `devtools`: 58 | 59 | ``` r 60 | pak::pak("rticulate/import") 61 | # or 62 | devtools::install_github("rticulate/import") 63 | ``` 64 | 65 | ## Usage 66 | 67 | ### Importing functions from R packages 68 | 69 | The most basic use case is to import a few functions from package (here 70 | the `psych` package): 71 | 72 | ``` r 73 | import::from(psych, geometric.mean, harmonic.mean) 74 | geometric.mean(trees$Volume) 75 | ``` 76 | 77 | If one of the function names conflicts with an existing function (such 78 | as `filter` from the `dplyr` package) it is simple to rename it: 79 | 80 | ``` r 81 | import::from(dplyr, select, arrange, keep_when = filter) 82 | keep_when(mtcars, hp>250) 83 | ``` 84 | 85 | Use `.all=TRUE` to import all functions from a package. If you want to 86 | rename one of them, you can still do that: 87 | 88 | ``` r 89 | import::from(dplyr, keep_when = filter, .all=TRUE) 90 | ``` 91 | 92 | To omit a function from the import, use `.except` (which takes a 93 | character vector): 94 | 95 | ``` r 96 | import::from(dplyr, .except=c("filter", "lag")) 97 | ``` 98 | 99 | Note that `import` tries to be smart about this and assumes that if you 100 | are using the `.except` parameter, you probably want to import 101 | everything you are *not* explicitly omitting, and sets the `.all` 102 | parameter to `TRUE`. You can still override this in exceptional cases, 103 | but you seldom need to. 104 | 105 | These and other examples are discussed in more detail in the [Importing 106 | from 107 | Packages](https://import.rticulate.org/articles/import.html#importing-from-packages) 108 | section of the package vignette. 109 | 110 | ### Importing Functions from “Module” Scripts 111 | 112 | The `import` package allows R files to be used as “modules” from which 113 | functions are loaded. For example, the file 114 | [sequence_module.R](https://raw.githubusercontent.com/rticulate/import/master/man/examples/sequence_module.R) 115 | contains several functions calculating terms of mathematical sequences. 116 | It is possible to import from such files, just as one imports from 117 | packages: 118 | 119 | ``` r 120 | import::from(sequence_module.R, fibonacci, square, triangular) 121 | ``` 122 | 123 | Renaming, as well as the `.all` and `.except` parameters, work in the 124 | same way as for packages: 125 | 126 | ``` r 127 | import::from(sequence_module.R, fib=fibonacci, .except="square") 128 | ``` 129 | 130 | These and other examples are discussed in more detail in the [Importing 131 | from 132 | Modules](https://import.rticulate.org/articles/import.html#importing-functions-from-module-scripts) 133 | section of the package vignette. 134 | 135 | ### Choosing where import looks for packages or modules 136 | 137 | The `import` package will by default use the current set of library 138 | paths, i.e. the result of `.libPaths()`. It is, however, possible to 139 | specify a different set of library paths using the `.library` argument 140 | in any of the `import` functions, for example to import packages 141 | installed in a custom location, or to remove any ambiguity as to where 142 | imports come from. 143 | 144 | Note that in versions up to and including `1.3.0` this defaulted to use 145 | only the *first* entry in the library paths, 146 | i.e. `.library=.libPaths()[1L]`. We believe the new default is 147 | applicable in a broader set of circumstances, but if this change causes 148 | any issues, we would very much appreciate hearing about it. 149 | 150 | When importing from a module (.R file), the directory where `import` 151 | looks for the module script can be specified with the with `.directory` 152 | parameter. The default is `.` (the current working directory). 153 | 154 | ### Choosing where the imported functions are placed 155 | 156 | By default, imported objects are placed in a separate entity in the 157 | search path called “imports”. One can also specify which names to use in 158 | the search path and use several to group imports: 159 | 160 | ``` r 161 | import::from(magrittr, "%>%", "%$%", .into = "operators") 162 | import::from(dplyr, arrange, .into = "datatools") 163 | ``` 164 | 165 | If using custom search path entities actively, one might prefer the 166 | alternative syntax (which does the same but reverses the argument 167 | order): 168 | 169 | ``` r 170 | import::into("operators", "%>%", "%$%", .from = magrittr) 171 | import::into("datatools", arrange, .from = dplyr) 172 | ``` 173 | 174 | If it is desired to place imported objects in the current environment, 175 | use `import::here()`: 176 | 177 | ### More advanced usage 178 | 179 | The `import` package is designed to be simple to use for basic cases, so 180 | it uses symbolic evaluation to allow the names of packages, modules and 181 | functions to be entered without quotes (except for operators, such as 182 | `"%>%"` which must be quoted). However, this means that it calling a 183 | variable containing the name of a module, or a vector of functions to 184 | import, will not work. For this use case, you can use the 185 | `.character_only` parameter: 186 | 187 | ``` r 188 | module_name <- "../utils/my_module.R" 189 | 190 | # Will not work (import will look for a package called "module_name") 191 | import::from(module_name, foo, bar) 192 | 193 | # This will correctly import the foo() and bar() functions from "../utils/my_module.R" 194 | import::from(module_name, foo, bar, .character_only=TRUE) 195 | ``` 196 | 197 | The `.character_only` parameter is covered in more detail in the 198 | [Advanced 199 | Usage](https://import.rticulate.org/articles/import.html#advanced-usage) 200 | section of the package vignette, which also describes how you can import 201 | from module scripts stored online with the help of the `pins` package, 202 | or achieve python-like imports with the help of `{}` notation for 203 | environments in the `.into` parameter. 204 | 205 | # Contributing 206 | 207 | Contributions to this project are welcome. Please start by opening an 208 | issue or discussion thread. New features are added conservatively based 209 | on supply (is anyone willing to contribute an implementation of the 210 | feature?), demand (how many people seem to need a new feature?), and 211 | last, but not least, by whether a feature can be implemented without 212 | breaking backwards compatibility. 213 | 214 | - Created and authored by [@smbache](https://github.com/smbache) 215 | - Currently maintained by [@torfason](https://github.com/torfason) 216 | - Code contributions by [@awong234](https://github.com/awong234), 217 | [@brshallo](https://github.com/brshallo), 218 | [@flying-sheep](https://github.com/flying-sheep), 219 | [@hutch3232](https://github.com/hutch3232), 220 | [@J-Moravec](https://github.com/J-Moravec), 221 | [@klmr](https://github.com/klmr), 222 | [@mschilli87](https://github.com/mschilli87), 223 | [@olivroy](https://github.com/olivroy) 224 | [@aramirezreyes](https://github.com/aramirezreyes) 225 | 226 | *(Did we forget to add you? If so, please let us know!)* 227 | 228 | # See also: 229 | 230 | - Some of the use cases for `import` can now be handled directly in base 231 | R using the new `exclude` and `include.only` arguments of `library()` 232 | and `require()` 233 | - For an interesting but slightly different idea of Python-like modules 234 | for R, see the [box](https://klmr.me/box/) package by 235 | [@klmr](https://github.com/klmr) (previously called 236 | [modules](https://github.com/klmr/modules)). 237 | - Another approach, focused on treating the use of functions with naming 238 | conflicts as explicit errors is the 239 | [conflicted](https://github.com/r-lib/conflicted) package by 240 | [@hadley](https://github.com/hadley). 241 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | template: 2 | bootstrap: 5 3 | -------------------------------------------------------------------------------- /build/build_and_release_process.R: -------------------------------------------------------------------------------- 1 | 2 | ## Build package, documentation, and readme (before commits) 3 | { 4 | devtools::document() 5 | devtools::build_readme() 6 | devtools::spell_check() 7 | devtools::build() 8 | devtools::test() 9 | message("Pre-commit tasks completed") 10 | } 11 | 12 | ## Build vignettes and site 13 | { 14 | devtools::build_vignettes() 15 | devtools::build_manual() 16 | devtools::build_site() 17 | } 18 | 19 | ## Final checks (before release) 20 | { 21 | system("R CMD INSTALL --preclean --no-multiarch --with-keep.source .") 22 | devtools::spell_check() 23 | devtools::check() 24 | devtools::release_checks() 25 | devtools:::git_checks() 26 | } 27 | 28 | 29 | ## Remote/long-running checks (copy to terminal and run manually) 30 | # devtools::check_rhub() 31 | # devtools::check_win_devel() 32 | # revdepcheck::revdep_check(num_workers = 4) 33 | 34 | ## Finally submit to cran (copy to terminal and run manually) 35 | # devtools::release() 36 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## General 2 | 3 | Version 1.3.2 is a bug fix release with the following fixes: 4 | 5 | * Fix an issue in the way method names are constructed for S3 imports 6 | 7 | More info in `NEWS.md` 8 | 9 | 10 | ## Test environments 11 | 12 | * local Mac OS X (R 4.3.1) 13 | * r-hub 14 | * (Windows Server 2022, R-devel, 64 bit) 15 | * (Ubuntu Linux 20.04.1 LTS, R-release, GCC) 16 | * (Fedora Linux, R-devel, clang, gfortran) 17 | * win-builder (devel R R-4.1.0 and release R-4.0.2) 18 | * GitHub CE (macos, linux and windows) 19 | 20 | 21 | ## R CMD check results 22 | 23 | There were no ERRORs or WARNINGs. 24 | 25 | On winbuilder, there were no NOTEs: 26 | 27 | On r-hub.io, some platforms raies one or the other of the following notes: 28 | 29 | +--- 30 | ❯ checking for non-standard things in the check directory ... NOTE 31 | Found the following files/directories: 32 | ''NULL'' 33 | 34 | ❯ checking for detritus in the temp directory ... NOTE 35 | Found the following files/directories: 36 | 'lastMiKTeXException' 37 | +--- 38 | 39 | These notes are not reproducible locally or on all platforms, and seem not to affect the output. The check reports no errors related to the PDF version of the manual: 40 | 41 | #> * checking PDF version of manual ... [12s] OK 42 | 43 | 44 | ## revdepcheck results 45 | 46 | We checked 13 reverse dependencies (12 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package. 47 | 48 | * We saw 0 new problems 49 | * We failed to check 0 packages 50 | 51 | 52 | ## Other Notes (unchanged since last release): 53 | 54 | The package's functionality may alter the search path, but this is intended and should be clear for the user, as the main functionality is an alternative to using `library`. For example the call: 55 | 56 | `import::from(parallel, makeCluster, parLapply)` 57 | 58 | will make an "imports" entry in the search path and place the imported there. 59 | 60 | -------------------------------------------------------------------------------- /import.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: b4e69012-19bc-4db9-9560-8861fe0600bc 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace,vignette 23 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | NSE 2 | https 3 | img 4 | io 5 | bugfix 6 | -------------------------------------------------------------------------------- /man/examples/plusone_module.R: -------------------------------------------------------------------------------- 1 | myfunc <- function(x) { 2 | x + 1 3 | } 4 | -------------------------------------------------------------------------------- /man/examples/sequence_module.R: -------------------------------------------------------------------------------- 1 | 2 | # Return the nth term in the fibonacci sequence (starting with 1) 3 | # This is not a very efficient implementation :-) 4 | fibonacci <- function(n) { 5 | stopifnot(n>=1 && all.equal(n, as.integer(n))) 6 | ifelse(n>2, fibonacci(n-1) + fibonacci(n-2), 1) 7 | } 8 | 9 | # Return the nth square number 10 | square <- function(n) { 11 | stopifnot(n>=1 && all.equal(n, as.integer(n))) 12 | n^2 13 | } 14 | 15 | # Return the nth triangular number 16 | triangular <- function(n) { 17 | stopifnot(n>=1 && all.equal(n, as.integer(n))) 18 | n*(n+1)/2 19 | } 20 | -------------------------------------------------------------------------------- /man/examples/some_module.R: -------------------------------------------------------------------------------- 1 | ## Do not use library() inside a module. This results in a warning, 2 | ## and functions relying on ggplot2 will not work. 3 | #library(ggplot2) 4 | 5 | ## This is also not recommended, because it is not clear wether recursively 6 | ## imported functions should be available after the module is imported 7 | #import::from(ggplot2, qplot) 8 | 9 | ## This is the recommended way to recursively import functions on which 10 | ## module functions depend. The qplot function will be available to 11 | ## module functions, but will not itself be available after import 12 | import::here(ggplot2, qplot) 13 | 14 | ## Note this operator overload is not something you want to `source`! 15 | `+` <- function(e1, e2) 16 | paste(e1, e2) 17 | 18 | ## Some function relying on the above overload: 19 | a <- function(s1, s2) 20 | s1 + rep(s2, 3) 21 | 22 | ## Another value. 23 | b <- head(iris, 10) 24 | 25 | ## A value created using a recursively imported function 26 | p <- qplot(Sepal.Length, Sepal.Width, data = iris, color = Species) 27 | 28 | ## A function relying on a function exposed through attachment: 29 | plot_it <- function() 30 | qplot(Sepal.Length, Sepal.Width, data = iris, color = Species) 31 | -------------------------------------------------------------------------------- /man/figures/lifecycle-archived.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclearchivedarchived -------------------------------------------------------------------------------- /man/figures/lifecycle-defunct.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycledefunctdefunct -------------------------------------------------------------------------------- /man/figures/lifecycle-deprecated.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycledeprecateddeprecated -------------------------------------------------------------------------------- /man/figures/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycleexperimentalexperimental -------------------------------------------------------------------------------- /man/figures/lifecycle-maturing.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclematuringmaturing -------------------------------------------------------------------------------- /man/figures/lifecycle-questioning.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclequestioningquestioning -------------------------------------------------------------------------------- /man/figures/lifecycle-stable.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclestablestable -------------------------------------------------------------------------------- /man/figures/lifecycle-superseded.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclesupersededsuperseded -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/man/figures/logo.png -------------------------------------------------------------------------------- /man/import.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/import.R 3 | \docType{package} 4 | \name{import} 5 | \alias{import-package} 6 | \alias{import} 7 | \title{An Import Mechanism for R} 8 | \description{ 9 | This is an alternative mechanism for importing objects from 10 | packages. The syntax allows for importing multiple objects from a package 11 | with a single command in an expressive way. The \code{import} package bridges 12 | some of the gap between using \code{library} (or \code{require}) and direct 13 | (single-object) imports. Furthermore the imported objects are not placed in 14 | the current environment (although possible), but in a named entry in the 15 | search path. 16 | } 17 | \details{ 18 | This package is not intended for use with \code{library}. It is named to make 19 | calls like \code{import::from(pkg, fun1, fun2)} expressive. Using the \code{import} 20 | functions complements the standard use of \code{library(pkg)}(when most objects 21 | are needed, and context is clear) and \code{obj <- pkg::obj} (when only a single 22 | object is needed). 23 | } 24 | \seealso{ 25 | For usage instructions and examples, see \code{\link{from}}, \code{\link{into}}, or 26 | \code{\link{here}}. 27 | 28 | Helpful links: 29 | \itemize{ 30 | \item \url{https://import.rticulate.org} 31 | \item \url{https://github.com/rticulate/import} 32 | \item \url{https://github.com/rticulate/import/issues} 33 | } 34 | } 35 | \author{ 36 | Stefan Milton Bache 37 | } 38 | -------------------------------------------------------------------------------- /man/importfunctions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/from.R, R/here.R, R/into.R 3 | \name{from} 4 | \alias{from} 5 | \alias{here} 6 | \alias{into} 7 | \title{Import Objects From a Package.} 8 | \usage{ 9 | from( 10 | .from, 11 | ..., 12 | .into = "imports", 13 | .library = .libPaths(), 14 | .directory = ".", 15 | .all = (length(.except) > 0), 16 | .except = character(), 17 | .chdir = TRUE, 18 | .character_only = FALSE, 19 | .S3 = FALSE 20 | ) 21 | 22 | here( 23 | .from, 24 | ..., 25 | .library = .libPaths()[1L], 26 | .directory = ".", 27 | .all = (length(.except) > 0), 28 | .except = character(), 29 | .chdir = TRUE, 30 | .character_only = FALSE, 31 | .S3 = FALSE 32 | ) 33 | 34 | into( 35 | .into, 36 | ..., 37 | .from, 38 | .library = .libPaths()[1L], 39 | .directory = ".", 40 | .all = (length(.except) > 0), 41 | .except = character(), 42 | .chdir = TRUE, 43 | .character_only = FALSE, 44 | .S3 = FALSE 45 | ) 46 | } 47 | \arguments{ 48 | \item{.from}{The package from which to import.} 49 | 50 | \item{...}{Names or name-value pairs specifying objects to import. If 51 | arguments are named, then the imported object will have this new name.} 52 | 53 | \item{.into}{The environment into which the imported objects should be 54 | assigned. If the value is of mode \code{character}, it is treated as referring 55 | to a named environment on the search path. If it is of mode \code{environment}, 56 | the objects are assigned directly to that environment. Using 57 | \code{.into=environment()} causes imports to be made into the current 58 | environment; \code{.into=""} is an equivalent shorthand value.} 59 | 60 | \item{.library}{character specifying the library to use when importing from 61 | packages. Defaults to the current set of library paths (note that the 62 | default value was different in versions up to and including \verb{1.3.0}).} 63 | 64 | \item{.directory}{character specifying the directory to use when importing 65 | from modules. Defaults to the current working directory. If .from is a 66 | module specified using an absolute path (i.e. starting with \code{/}), this 67 | parameter is ignored.} 68 | 69 | \item{.all}{logical specifying whether all available objects in a package or 70 | module should be imported. It defaults to FALSE unless .exclude is being 71 | used to omit particular functions.} 72 | 73 | \item{.except}{character vector specifying any objects that should not be 74 | imported. Any values specified here override both values provided in \code{...} 75 | and objects included because of the \code{.all} parameter} 76 | 77 | \item{.chdir}{logical specifying whether to change directories before 78 | sourcing a module (this parameter is ignored for libraries)} 79 | 80 | \item{.character_only}{A logical indicating whether \code{.from} and \code{...} can be 81 | assumed to be character strings. (Note that this parameter does not apply 82 | to how the \code{.into} parameter is handled).} 83 | 84 | \item{.S3}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} A logical indicating whether an 85 | automatic detection and registration of S3 methods should be performed. The 86 | S3 methods are assumed to be in the standard form \code{generic.class}. Methods 87 | can also be registered manually instead using be registered manually 88 | instead using the \code{.S3method(generic, class, method)} call. \emph{This is an 89 | experimental feature. We think it should work well and you are encouraged 90 | to use it and report back – but the syntax and semantics may change in the 91 | future to improve the feature.}} 92 | } 93 | \value{ 94 | a reference to the environment containing the imported objects. 95 | } 96 | \description{ 97 | The \code{import::from} and \code{import::into} functions provide an alternative way to 98 | import objects (e.g. functions) from packages. It is sometimes preferred over 99 | using \code{library} (or \code{require}) which will import all objects exported by the 100 | package. The benefit over \code{obj <- pkg::obj} is that the imported objects will 101 | (by default) be placed in a separate entry in the search path (which can be 102 | specified), rather in the global/current environment. Also, it is a more 103 | succinct way of importing several objects. Note that the two functions are 104 | symmetric, and usage is a matter of preference and whether specifying the 105 | \code{.into} argument is desired. The function \code{import::here} imports into the 106 | current environment. 107 | } 108 | \details{ 109 | The function arguments can be quoted or unquoted as with e.g. \code{library}. In 110 | any case, the character representation is used when unquoted arguments are 111 | provided (and not the value of objects with matching names). The period in 112 | the argument names \code{.into} and \code{.from} are there to avoid name clash with 113 | package objects. However, while importing of hidden objects (those with names 114 | prefixed by a period) is supported, care should be taken not to conflict with 115 | the argument names. The double-colon syntax \code{import::from} allows for imports 116 | of exported objects (and lazy data) only. To import objects that are not 117 | exported, use triple-colon syntax, e.g. \code{import:::from}. The two ways of 118 | calling the \code{import} functions analogue the \code{::} and \code{:::} operators 119 | themselves. 120 | 121 | Note that the \code{import} functions usually have the (intended) side-effect of 122 | altering the search path, as they (by default) import objects into the 123 | "imports" search path entry rather than the global environment. 124 | 125 | The \code{import} package is not meant to be loaded with \code{library} (and will 126 | output a message about this if attached), but rather it is named to make the 127 | function calls expressive without the need to loading before use, i.e. it is 128 | designed to be used explicitly with the \code{::} syntax, e.g. \code{import::from(pkg, x, y)}. 129 | } 130 | \section{Packages vs. modules}{ 131 | 132 | \code{import} can either be used to import objects either from R packages or from 133 | \code{R} source files. If the \code{.from} parameter ends with '.R' or '.r', \code{import} 134 | will look for a source file to import from. A source file in this context is 135 | referred to as a \code{module} in the documentation. 136 | } 137 | 138 | \section{Package Versions}{ 139 | 140 | With \code{import} you can specify package version requirements. To do this add a 141 | requirement in parentheses to the package name (which then needs to be 142 | quoted), e.g \code{import::from("parallel (>= 3.2.0)", ...)}. You can use the 143 | operators \code{<}, \code{>}, \code{<=}, \code{>=}, \code{==}, \code{!=}. Whitespace in the specification 144 | is irrelevant. 145 | } 146 | 147 | \examples{ 148 | import::from(parallel, makeCluster, parLapply) 149 | import::into("imports:parallel", makeCluster, parLapply, .from = parallel) 150 | 151 | } 152 | \seealso{ 153 | Helpful links: 154 | \itemize{ 155 | \item \url{https://import.rticulate.org} 156 | \item \url{https://github.com/rticulate/import} 157 | \item \url{https://github.com/rticulate/import/issues} 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/test_import/cleanup_environment.R: -------------------------------------------------------------------------------- 1 | 2 | #' Cleans up any objects in the environment left over from 3 | #' a given test sequence 4 | #' @param environments A list (by name) of environments to cleanup 5 | #' @param cleanup_here Should the calling environment be cleaned up? (for tests with here()) 6 | #' @param exclude_self Should the cleanup_environment function itself be excluded from cleanup? 7 | #' 8 | cleanup_environment <- function(environments=c("imports"), 9 | cleanup_here=FALSE, exclude_self=TRUE) { 10 | for (env_name in environments) { 11 | if(env_name %in% search()) 12 | rm(list=ls(n=env_name, all.names=TRUE), pos=env_name) 13 | } 14 | # Cleanup the local environment, if applicable 15 | if (cleanup_here) { 16 | obj_list <- ls(pos=parent.env(environment()), all.names=TRUE) 17 | if (exclude_self) 18 | obj_list <- setdiff(obj_list,"cleanup_environment") 19 | rm(list=obj_list, pos=parent.env(environment())) 20 | } 21 | # The import:::scripts environment must always be cleaned up 22 | rm(list=ls(env=import:::scripts), pos=import:::scripts) 23 | } 24 | -------------------------------------------------------------------------------- /tests/test_import/module_S3.R: -------------------------------------------------------------------------------- 1 | # a sample generic with two different methods (that should behave differently) 2 | test_fun <- function(x){ 3 | UseMethod("test_fun", x) 4 | } 5 | 6 | test_fun.character <- function(x){ 7 | structure("character", class = "test_class") 8 | } 9 | 10 | 11 | test_fun.numeric <- function(x){ 12 | structure("numeric", class = "test_class") 13 | } 14 | 15 | # a method for existing (base) generic. 16 | print.test_class <- function(x){ 17 | print(as.character(x)) 18 | } 19 | 20 | # a method for class with dot in name 21 | test_fun.data.frame = function(x){ 22 | structure("data.frame", class = "test_class") 23 | } 24 | -------------------------------------------------------------------------------- /tests/test_import/module_base.R: -------------------------------------------------------------------------------- 1 | 2 | fun1 <- function() {"fun1"} 3 | fun2 <- function() {"fun2"} 4 | fun3 <- function() {"fun3"} 5 | fun4 <- function() {"fun4"} 6 | fun5 <- function() {"fun5"} 7 | fun6 <- function() {"fun6"} 8 | -------------------------------------------------------------------------------- /tests/test_import/module_chdir/module_chdir.R: -------------------------------------------------------------------------------- 1 | 2 | # This variable is set when the script is sourced, so that 3 | # the .chdir parameter can be tested. 4 | directory_of_sourcing <- getwd() 5 | 6 | fun_chdir_report <- function() { 7 | paste0("Module was sourced in: ",directory_of_sourcing) 8 | } 9 | -------------------------------------------------------------------------------- /tests/test_import/module_hidden_objects.R: -------------------------------------------------------------------------------- 1 | 2 | fun1 <- function() {"fun1"} 3 | .script_version <- "v1.0" 4 | # formals from import::from 5 | .from <- "a script" 6 | .into <- "an env" 7 | .directory <- "my_dir" 8 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive/src/run_me.R: -------------------------------------------------------------------------------- 1 | import::from("text.R", print_text) 2 | import::from("title_text.R", print_title_text) 3 | 4 | text = "hi friend, how are you" 5 | print_text(text) 6 | print_title_text(text) 7 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive/src/text.R: -------------------------------------------------------------------------------- 1 | print_text = function(text){ 2 | print(text) 3 | } 4 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive/src/title_text.R: -------------------------------------------------------------------------------- 1 | # If import::from does not change the path to the dirname(path) 2 | # (like the chdir=TRUE in sys.source), this shouldn't work. 3 | import::from("to_title.R", to_title) 4 | 5 | print_title_text = function(text){ 6 | print(to_title(text)) 7 | } 8 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive/src/title_text_here.R: -------------------------------------------------------------------------------- 1 | import::here(.from=to_title.R, to_title) 2 | 3 | print_title_text = function(text){ 4 | print(to_title(text)) 5 | } 6 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive/src/to_title.R: -------------------------------------------------------------------------------- 1 | to_title = function(text) { 2 | tools::toTitleCase(text) 3 | } 4 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive_inner.R: -------------------------------------------------------------------------------- 1 | to_title = function(text) { 2 | tools::toTitleCase(text) 3 | } 4 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive_library.R: -------------------------------------------------------------------------------- 1 | # Import utility function or package using import::here() is the recommended usage 2 | library(knitr) 3 | 4 | standalone_fun = function(text){ 5 | print(text) 6 | } 7 | 8 | dependent_fun = function(text){ 9 | normal_print(text) 10 | } 11 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive_outer_from.R: -------------------------------------------------------------------------------- 1 | # Import utility function using import::from() is not recommended usage 2 | import::from(module_recursive_inner.R, to_title) 3 | 4 | print_title_text = function(text){ 5 | print(to_title(text)) 6 | } 7 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive_outer_here.R: -------------------------------------------------------------------------------- 1 | # Import utility function using import::here() is the recommended usage 2 | import::here(module_recursive_inner.R, to_title) 3 | 4 | print_title_text = function(text){ 5 | print(to_title(text)) 6 | } 7 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive_package_from.R: -------------------------------------------------------------------------------- 1 | # Import utility function or package using import::from() is not recommended usage 2 | import::from(knitr, normal_print) 3 | import::from(module_recursive_inner.R, to_title) 4 | 5 | print_title_text = function(text){ 6 | normal_print(to_title(text)) 7 | } 8 | -------------------------------------------------------------------------------- /tests/test_import/module_recursive_package_here.R: -------------------------------------------------------------------------------- 1 | # Import utility function or package using import::here() is the recommended usage 2 | import::here(knitr, normal_print) 3 | import::here(module_recursive_inner.R, to_title) 4 | 5 | print_title_text = function(text){ 6 | normal_print(to_title(text)) 7 | } 8 | -------------------------------------------------------------------------------- /tests/test_import/module_script_error.R: -------------------------------------------------------------------------------- 1 | if(is.na(Sys.getenv("SOMECONFIG", NA))) stop("SOMECONFIG not set!") 2 | foo <- function(){ 3 | "foo" 4 | } 5 | -------------------------------------------------------------------------------- /tests/test_import/module_subsequent.R: -------------------------------------------------------------------------------- 1 | fun7 <- function() {"fun7"} 2 | -------------------------------------------------------------------------------- /tests/test_import/packageToTest/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: packageToTest 2 | Type: Package 3 | Title: What the Package Does (Title Case) 4 | Version: 0.1.0 5 | Author: Who wrote it 6 | Maintainer: The package maintainer 7 | Description: More about what it does (maybe more than one line) 8 | Use four spaces when indenting paragraphs within the Description. 9 | License: What license is it under? 10 | Encoding: UTF-8 11 | LazyData: true 12 | RoxygenNote: 7.1.2 13 | -------------------------------------------------------------------------------- /tests/test_import/packageToTest/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(get) 4 | export(hello) 5 | -------------------------------------------------------------------------------- /tests/test_import/packageToTest/R/get.R: -------------------------------------------------------------------------------- 1 | #' Function named get 2 | #' 3 | #' Used to test imports when a function named `get` is before the base package 4 | #' on the search path. 5 | #' 6 | #' @export 7 | get <- function(...) { 8 | stop("import incorrectly used function `get` exported from package: packageToTest") 9 | } 10 | -------------------------------------------------------------------------------- /tests/test_import/packageToTest/R/hello.R: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | # 3 | # This is an example function named 'hello' 4 | # which prints 'Hello, world!'. 5 | # 6 | # You can learn more about package authoring with RStudio at: 7 | # 8 | # http://r-pkgs.had.co.nz/ 9 | # 10 | # Some useful keyboard shortcuts for package authoring: 11 | # 12 | # Install Package: 'Ctrl + Shift + B' 13 | # Check Package: 'Ctrl + Shift + E' 14 | # Test Package: 'Ctrl + Shift + T' 15 | #' @export 16 | hello <- function() { 17 | print("Hello, world!") 18 | } 19 | -------------------------------------------------------------------------------- /tests/test_import/packageToTest/man/get.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/get.R 3 | \name{get} 4 | \alias{get} 5 | \title{Function named get} 6 | \usage{ 7 | get(x) 8 | } 9 | \description{ 10 | Used to test imports when a function named `get` is before the base package 11 | on the search path. 12 | } 13 | -------------------------------------------------------------------------------- /tests/test_import/packageToTest/man/hello.Rd: -------------------------------------------------------------------------------- 1 | \name{hello} 2 | \alias{hello} 3 | \title{Hello, World!} 4 | \usage{ 5 | hello() 6 | } 7 | \description{ 8 | Prints 'Hello, world!'. 9 | } 10 | \examples{ 11 | hello() 12 | } 13 | -------------------------------------------------------------------------------- /tests/test_import/packageToTest_0.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticulate/import/827e26f0d102da4d13ad0516013ef817933e1cde/tests/test_import/packageToTest_0.1.0.tar.gz -------------------------------------------------------------------------------- /tests/test_import/skipped_test_module_urls.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | 25 | # Test a very basic scenario. The first test in a test sequence should test 26 | # that relevant functions have not been imported before the sequence starts. 27 | test_that("Basic scenario works", { 28 | expect_error ( normal_print("OK") ) 29 | expect_silent( import::from(knitr, normal_print) ) 30 | expect_output( normal_print("OK"), "OK" ) 31 | cleanup_environment() 32 | }) 33 | 34 | # Test a very basic scenario. The first test in a test sequence should test 35 | # that relevant functions have not been imported before the sequence starts. 36 | test_that("Loading URL modules with pins works", { 37 | skip_if_offline(host="raw.githubusercontent.com") 38 | url <- "https://raw.githubusercontent.com/smbache/import/master/tests/test_import/module_base.R" 39 | expect_silent( import::from(pins::pin(url), "fun1", "fun2", .character_only=TRUE) ) 40 | expect_equal( fun1(), "fun1" ) 41 | cleanup_environment() 42 | }) 43 | 44 | 45 | ## Tests end 46 | 47 | 48 | ## IMPORTANT: 49 | ## The following line must be printed exactly as is, 50 | ## it is used by the custom harness to check if the tests worked: 51 | print("Import tests completed successfully ...") 52 | 53 | -------------------------------------------------------------------------------- /tests/test_import/test_S3.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | 25 | # Test a very basic scenario. The first test in a test sequence should test 26 | # that relevant functions have not been imported before the sequence starts. 27 | test_that("S3 methods are registered", { 28 | expect_error( test_fun("OK") ) 29 | expect_silent( import::from("module_S3.R", "test_fun", .S3=TRUE) ) 30 | expect_identical( test_fun(1), structure("numeric", class="test_class") ) 31 | expect_identical( test_fun("1"), structure("character", class="test_class") ) 32 | expect_identical( test_fun(data.frame()), structure("data.frame", class="test_class")) 33 | expect_output( print(test_fun(1)), "numeric" ) 34 | expect_output( print(test_fun("OK")), "character" ) 35 | cleanup_environment() 36 | }) 37 | 38 | test_that("S3 methods are not registered when .S3=FALSE", { 39 | expect_error( test_fun("OK") ) 40 | expect_silent( import::from("module_S3.R", "test_fun", .S3=FALSE) ) 41 | expect_error( test_fun("OK") ) 42 | cleanup_environment() 43 | }) 44 | 45 | 46 | ## Test import::here and import::into 47 | test_that("S3 methods are registered when import::here is used ", { 48 | expect_error( test_fun(1) ) 49 | expect_silent( import::here("module_S3.R", "test_fun", .S3=TRUE) ) 50 | expect_identical( test_fun(1), structure("numeric", class="test_class") ) 51 | expect_output( print(test_fun(1)), "numeric" ) 52 | cleanup_environment(cleanup_here=TRUE) 53 | }) 54 | 55 | test_that("S3 methods are registered when import::into is used ", { 56 | expect_error( test_fun(1) ) 57 | expect_silent( import::into("custom_env", "test_fun", .from="module_S3.R", .S3=TRUE) ) 58 | expect_identical( test_fun(1), structure("numeric", class="test_class") ) 59 | expect_output( print(test_fun(1)), "numeric" ) 60 | cleanup_environment("custom_env") 61 | }) 62 | 63 | ## Tests end 64 | 65 | 66 | ## IMPORTANT: 67 | ## The following line must be printed exactly as is, 68 | ## it is used by the custom harness to check if the tests worked: 69 | print("Import tests completed successfully ...") 70 | 71 | -------------------------------------------------------------------------------- /tests/test_import/test_basetemplate.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | 25 | # Test a very basic scenario. The first test in a test sequence should test 26 | # that relevant functions have not been imported before the sequence starts. 27 | test_that("Basic scenario works", { 28 | expect_error ( normal_print("OK") ) 29 | expect_silent( import::from(knitr, normal_print) ) 30 | expect_output( normal_print("OK"), "OK" ) 31 | cleanup_environment() 32 | }) 33 | 34 | 35 | ## Tests end 36 | 37 | 38 | ## IMPORTANT: 39 | ## The following line must be printed exactly as is, 40 | ## it is used by the custom harness to check if the tests worked: 41 | print("Import tests completed successfully ...") 42 | 43 | -------------------------------------------------------------------------------- /tests/test_import/test_from.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | 25 | test_that("Imports from libraries work", { 26 | expect_error ( normal_print("OK") ) 27 | expect_silent( import::from(knitr, normal_print) ) 28 | expect_output( normal_print("OK"), "OK" ) 29 | cleanup_environment() 30 | }) 31 | 32 | test_that("Imports from modules work", { 33 | expect_error ( fun1() ) 34 | expect_silent( import::from(module_base.R, fun1) ) 35 | expect_equal ( fun1(), "fun1" ) 36 | cleanup_environment() 37 | }) 38 | 39 | test_that("Conflicting imports throw error containing conflicting name", { 40 | expect_error ( fun1() ) 41 | expect_silent( import::from(module_base.R, fun1) ) 42 | expect_error ( import::from(module_hidden_objects.R, fun1) ) 43 | expect_error ( import::from(module_hidden_objects.R, fun1), ".*fun1.*" ) 44 | cleanup_environment() 45 | }) 46 | 47 | test_that("Subsequent imports from modules work", { 48 | expect_error ( fun1() ) 49 | expect_error ( fun7() ) 50 | expect_silent( import::from(module_base.R, .all=TRUE) ) 51 | expect_silent( import::from(module_subsequent.R, .all=TRUE) ) 52 | expect_equal ( fun1(), "fun1" ) 53 | expect_equal ( fun7(), "fun7" ) 54 | cleanup_environment() 55 | }) 56 | 57 | test_that("Passing values as characters works", { 58 | char_package <- "knitr" 59 | char_functions <- c("normal_print","knit_print") 60 | expect_error ( normal_print("OK") ) 61 | expect_silent( import::from(char_package, char_functions, .character_only=TRUE) ) 62 | expect_output( normal_print("OK"), "OK" ) 63 | expect_output( knit_print("OK"), "OK" ) 64 | cleanup_environment() 65 | }) 66 | 67 | test_that("Importing modules by absolute path works", { 68 | abs_module <- file.path(tempdir(),"module_base.R") 69 | file.copy("module_base.R",abs_module) 70 | expect_error ( fun1() ) 71 | expect_silent( import::from(abs_module, "fun1", .character_only=TRUE) ) 72 | expect_equal ( fun1(), "fun1" ) 73 | cleanup_environment() 74 | }) 75 | 76 | test_that("The .all parameter works", { 77 | expect_error ( fun1() ) 78 | expect_silent( import::from(module_base.R, .all=TRUE) ) 79 | expect_equal ( fun1(), "fun1" ) 80 | expect_equal ( fun2(), "fun2" ) 81 | cleanup_environment() 82 | }) 83 | 84 | # The .all parameter should work with individual objects for renaming 85 | # Renamed functions should not be found under their original names 86 | test_that("The .all parameter works with renamings", { 87 | expect_error ( fun1() ) 88 | expect_silent( import::from(module_base.R, hansolo=fun1, luke=fun6, .all=TRUE) ) 89 | expect_equal ( hansolo(), "fun1" ) 90 | expect_equal ( fun2(), "fun2" ) 91 | expect_equal ( luke(), "fun6" ) 92 | expect_error ( fun1() ) 93 | expect_error ( fun6() ) 94 | cleanup_environment() 95 | }) 96 | 97 | test_that("The .all and .except parameters work together", { 98 | expect_error ( fun1() ) 99 | expect_silent( import::from(module_base.R, .all=TRUE, .except=c("fun2","fun3")) ) 100 | expect_equal ( fun1(), "fun1" ) 101 | expect_error ( fun2() ) 102 | expect_error ( fun3() ) 103 | expect_equal ( fun4(), "fun4" ) 104 | cleanup_environment() 105 | }) 106 | 107 | test_that("The .all parmeter is smart about whether .except is being used", { 108 | expect_error ( fun1() ) 109 | expect_silent( import::from(module_base.R, .except=c("fun1","fun4")) ) 110 | expect_error ( fun1() ) 111 | expect_equal ( fun2(), "fun2" ) 112 | expect_equal ( fun3(), "fun3" ) 113 | expect_error ( fun4() ) 114 | expect_equal ( fun5(), "fun5" ) 115 | expect_equal ( fun6(), "fun6" ) 116 | cleanup_environment() 117 | }) 118 | 119 | test_that("Using .all or .except with import::: should throw an error", { 120 | expect_error( import:::from(module_base.R, .all=TRUE) ) 121 | expect_error( import:::from(module_base.R, .except=c("fun1","fun4")) ) 122 | expect_error( import:::from(module_base.R, .all=TRUE, .except=c("fun2","fun3")) ) 123 | cleanup_environment() 124 | }) 125 | 126 | test_that("The .into paremeter is honored", { 127 | expect_error ( normal_print("OK") ) 128 | expect_silent( import::from(knitr, normal_print, .into="custom_env") ) 129 | expect_output( normal_print("OK"), "OK" ) 130 | cleanup_environment("custom_env") 131 | }) 132 | 133 | test_that("The .into paremeter is honored w/ .character_only=TRUE", { 134 | expect_error ( normal_print("OK") ) 135 | expect_silent( import::from('knitr', "normal_print", .into="custom_env", .character_only=TRUE) ) 136 | expect_output( normal_print("OK"), "OK" ) 137 | cleanup_environment("custom_env") 138 | }) 139 | 140 | test_that("Importing .into={....} (curly brackets) works", { 141 | expect_error ( normal_print("OK") ) 142 | expect_false ( "normal_print" %in% ls() ) 143 | expect_silent( import::from(knitr, normal_print, .into={environment()}) ) 144 | expect_output( normal_print("OK"), "OK" ) 145 | expect_true ("normal_print" %in% ls() ) 146 | cleanup_environment(cleanup_here=TRUE) 147 | }) 148 | 149 | test_that("Importing .into={....} (curly brackets) works w/ .character_only=TRUE", { 150 | expect_error ( normal_print("OK") ) 151 | expect_false ( "normal_print" %in% ls() ) 152 | expect_silent( import::from("knitr", "normal_print", .into={environment()}, .character_only=TRUE) ) 153 | expect_output( normal_print("OK"), "OK" ) 154 | expect_true ("normal_print" %in% ls() ) 155 | cleanup_environment(cleanup_here=TRUE) 156 | }) 157 | 158 | test_that("Importing .into=\"\" (empty string) works", { 159 | expect_error ( normal_print("OK") ) 160 | expect_false ( "normal_print" %in% ls() ) 161 | expect_silent( import::from(knitr, normal_print, .into="") ) 162 | expect_output( normal_print("OK"), "OK" ) 163 | expect_true ("normal_print" %in% ls() ) 164 | cleanup_environment(cleanup_here=TRUE) 165 | }) 166 | 167 | test_that("Importing .into=\"\" (empty string) works w/ .character_only=TRUE", { 168 | expect_error ( normal_print("OK") ) 169 | expect_false ( "normal_print" %in% ls() ) 170 | expect_silent( import::from("knitr", "normal_print", .into="", .character_only=TRUE) ) 171 | expect_output( normal_print("OK"), "OK" ) 172 | expect_true ("normal_print" %in% ls() ) 173 | cleanup_environment(cleanup_here=TRUE) 174 | }) 175 | 176 | test_that("Importing .into a symbol works", { 177 | expect_error ( normal_print("OK") ) 178 | expect_false ( "normal_print" %in% ls() ) 179 | symbol_env <- "custom_env" 180 | expect_silent( import::from(knitr, normal_print, .into=symbol_env) ) 181 | expect_output( normal_print("OK"), "OK" ) 182 | expect_true ("normal_print" %in% ls(name = symbol_env) ) 183 | cleanup_environment(environments = symbol_env) 184 | }) 185 | 186 | test_that("Importing .into a symbol works w/ .character_only=TRUE", { 187 | expect_error ( normal_print("OK") ) 188 | expect_false ( "normal_print" %in% ls() ) 189 | symbol_env <- "custom_env" 190 | expect_silent( import::from("knitr", "normal_print", .into=symbol_env, .character_only=TRUE) ) 191 | expect_output( normal_print("OK"), "OK" ) 192 | expect_true ("normal_print" %in% ls(name = symbol_env) ) 193 | cleanup_environment(environments = symbol_env) 194 | }) 195 | 196 | test_that("Imports from libraries NOT defined in .libPaths work", { 197 | tmp_install_dir <- tempdir() 198 | if (!file.exists("packageToTest_0.1.0.tar.gz")) { 199 | system("R CMD build packageToTest") 200 | } 201 | install.packages("packageToTest_0.1.0.tar.gz", 202 | lib = tmp_install_dir, 203 | repos = NULL, 204 | type = "source", 205 | quiet = TRUE 206 | ) 207 | expect_true("packageToTest" %in% list.files(tmp_install_dir)) 208 | expect_silent(import::from(.from = packageToTest, .library = tmp_install_dir, hello)) 209 | expect_equal(hello(), "Hello, world!") 210 | }) 211 | 212 | test_that("Functions named `get` in the calling environment do not mask base::get", { 213 | get <- function(...) stop("import incorrectly used function `get` defined in calling env.") 214 | expect_silent(import::from(module_base.R, fun1)) 215 | expect_silent(import::from(knitr, normal_print)) 216 | cleanup_environment() 217 | }) 218 | 219 | test_that("Functions named `get` in arbitrary environment on search path do not mask base::get", { 220 | attach(what = new.env(parent = emptyenv()), pos = 2, name = "custom") 221 | assign(x = "get", value = function(...) stop("import incorrectly used function `get` defined in custom env on search path."), pos = "custom") 222 | expect_silent(import::from(module_base.R, fun1)) 223 | expect_silent(import::from(knitr, normal_print)) 224 | cleanup_environment() 225 | detach("custom") 226 | }) 227 | 228 | test_that("Functions named `get` exported from packages do not mask base::get", { 229 | tmp_install_dir <- tempdir() 230 | library(packageToTest, lib.loc = tmp_install_dir) 231 | expect_true("get" %in% getNamespaceExports("packageToTest")) 232 | expect_silent(import::from(module_base.R, fun1)) 233 | expect_silent(import::from(knitr, normal_print)) 234 | cleanup_environment() 235 | detach("package:packageToTest", unload = TRUE) 236 | }) 237 | 238 | 239 | test_that("Imports from specific version work",{ 240 | 241 | # Base case, no version 242 | expect_silent( import::from(magrittr, "%>%") )# no version, quotes are unnecessary. 243 | 244 | # Variable spacing before parenthesis or around comparator operator 245 | expect_silent( import::from("magrittr (>=1.5)", "%>%") ) 246 | expect_silent( import::from("magrittr(>= 1.5)", "%>%") ) 247 | expect_silent( import::from("magrittr( >=1.5)", "%>%") ) 248 | expect_silent( import::from("magrittr ( >=1.5 )", "%>%") ) 249 | 250 | # Variable specificity 251 | expect_silent( import::from("magrittr(>=1)", "%>%") ) 252 | expect_silent( import::from("magrittr(>=1.5)", "%>%") ) 253 | expect_silent( import::from("magrittr(>=1.5.0)", "%>%") ) 254 | 255 | # Maximum version 256 | expect_silent( import::from("magrittr(<= 100.0.1)", "%>%") ) 257 | 258 | # Non-equality 259 | expect_silent( import::from("magrittr(!= 1.0.1)", "%>%") ) 260 | expect_silent( import::from("magrittr(!= 1.0.1)", "%>%") ) 261 | 262 | # Cleanup before testing failures 263 | cleanup_environment() 264 | 265 | # If the version is not available, the import should fail with erro 266 | # Variable spacing before parenthesis or around comparator operator 267 | expect_error( import::from("magrittr (>=100.5)", "%>%") ) 268 | expect_error( import::from("magrittr(>= 100.5)", "%>%") ) 269 | expect_error( import::from("magrittr( >=100.5)", "%>%") ) 270 | expect_error( import::from("magrittr ( >=100.5 )", "%>%") ) 271 | 272 | # Variable specificity 273 | expect_error( import::from("magrittr(>=100)", "%>%") ) 274 | expect_error( import::from("magrittr(>=100.5)", "%>%") ) 275 | expect_error( import::from("magrittr(>=100.5.0)", "%>%") ) 276 | 277 | # Maximum version 278 | expect_error( import::from("magrittr(<= 0.0.1)", "%>%") ) 279 | 280 | 281 | # Test equality/non-equality with the version currently installed 282 | # (1.5.0 at time of writing. The test could be updated to choose an 283 | # available version automatically, but we'll put that on the backburner 284 | # for now. So we skip these tests when running automatically.) 285 | skip("It is too fragile to test specific versions") 286 | 287 | # Non-equality 288 | expect_error( import::from("magrittr(!= 1.5)", "%>%") ) 289 | expect_error( import::from("magrittr(!= 1.5.0)", "%>%") ) 290 | 291 | # Equality 292 | expect_silent( import::from("magrittr(== 1.5)", "%>%") ) 293 | expect_silent( import::from("magrittr(== 1.5.0)", "%>%") ) 294 | expect_silent( import::from("magrittr(1.5)", "%>%") ) # same as (==1.5) 295 | expect_silent( import::from("magrittr(1.5.0)", "%>%") ) 296 | }) 297 | 298 | ## .chdir parameter is tested in a separate file (test_param_chdir.R) 299 | 300 | test_that("Script errors are caught and allow manual retry", { 301 | expect_error ( foo() ) 302 | expect_true ( is.na(Sys.getenv("SOMECONFIG", NA)) ) 303 | expect_error ( import::from(module_script_error.R, foo) ) 304 | expect_error ( foo() ) 305 | Sys.setenv ( "SOMECONFIG"="any" ) 306 | expect_silent( import::from(module_script_error.R, foo) ) 307 | expect_equal ( foo(), "foo" ) 308 | Sys.unsetenv ( "SOMECONFIG" ) 309 | cleanup_environment() 310 | }) 311 | 312 | ## Tests end 313 | 314 | 315 | ## IMPORTANT: 316 | ## The following line must be printed exactly as is, 317 | ## it is used by the custom harness to check if the tests worked: 318 | print("Import tests completed successfully ...") 319 | -------------------------------------------------------------------------------- /tests/test_import/test_hidden_objects.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | 25 | test_that("Standard import from module works", { 26 | abs_module <- file.path(tempdir(),"module_hidden_objects.R") 27 | file.copy("module_hidden_objects.R",abs_module) 28 | expect_error ( fun1() ) 29 | expect_silent( import::from(abs_module, "fun1", .character_only=TRUE) ) 30 | expect_equal ( fun1(), "fun1" ) 31 | cleanup_environment() 32 | }) 33 | 34 | test_that("Import hidden object with .character_only=TRUE", { 35 | abs_module <- file.path(tempdir(),"module_hidden_objects.R") 36 | file.copy("module_hidden_objects.R",abs_module) 37 | expect_error ( .script_version ) 38 | expect_silent( import::from(abs_module, ".script_version", .character_only=TRUE) ) 39 | expect_equal ( .script_version, "v1.0" ) 40 | cleanup_environment() 41 | }) 42 | 43 | test_that("Import hidden objects with argument name conflicts", { 44 | abs_module <- file.path(tempdir(),"module_hidden_objects.R") 45 | file.copy("module_hidden_objects.R",abs_module) 46 | expect_error ( .from ) 47 | expect_error ( .into ) 48 | expect_error ( .directory ) 49 | expect_silent( import::from(abs_module, ".from", ".into", ".directory", .character_only=TRUE) ) 50 | expect_equal ( .from, "a script" ) 51 | expect_equal ( .into, "an env" ) 52 | expect_equal ( .directory, "my_dir" ) 53 | cleanup_environment() 54 | }) 55 | 56 | test_that("Import hidden object with argument name conflict and both specified", { 57 | abs_module <- file.path(tempdir(),"module_hidden_objects.R") 58 | file.copy("module_hidden_objects.R",abs_module) 59 | expect_error ( .directory ) 60 | expect_silent( import::from("module_hidden_objects.R", ".directory", .character_only=TRUE, .directory = tempdir()) ) 61 | expect_equal ( .directory, "my_dir" ) 62 | cleanup_environment() 63 | }) 64 | 65 | test_that("Import hidden object with argument name conflict and both specified and .character_only=FALSE", { 66 | expect_error ( .from ) 67 | expect_error ( .into ) 68 | expect_error ( .directory ) 69 | expect_silent( import::from(module_hidden_objects.R, .from, .directory, .into, .directory = ".", .into = "new env") ) 70 | expect_equal ( .from, "a script" ) 71 | expect_equal ( .into, "an env" ) 72 | expect_equal ( .directory, "my_dir" ) 73 | expect_true (all(c(".from", ".into", ".directory") %in% ls(n = "new env", all.names = TRUE))) 74 | cleanup_environment(environments = "new env") 75 | }) 76 | 77 | test_that("Import hidden object with .character_only=FALSE", { 78 | expect_error ( .script_version ) 79 | expect_silent( import::from(module_hidden_objects.R, .script_version) ) 80 | expect_equal ( .script_version, "v1.0" ) 81 | cleanup_environment() 82 | }) 83 | 84 | test_that("Import hidden (unexported) object", { 85 | expect_error ( .packageName ) 86 | expect_silent( import:::from(rmarkdown, .packageName) ) 87 | expect_equal ( .packageName, "rmarkdown" ) 88 | cleanup_environment() 89 | }) 90 | 91 | test_that("Import all objects, including hidden, .character_only=TRUE", { 92 | abs_module <- file.path(tempdir(),"module_hidden_objects.R") 93 | file.copy("module_hidden_objects.R",abs_module) 94 | expect_error ( .script_version ) 95 | expect_error ( fun1() ) 96 | expect_error ( .directory ) 97 | expect_silent( import::from(abs_module, .all=TRUE, .character_only=TRUE) ) 98 | expect_equal ( .script_version, "v1.0" ) 99 | expect_equal ( fun1(), "fun1" ) 100 | expect_equal ( .directory, "my_dir" ) 101 | cleanup_environment() 102 | }) 103 | 104 | test_that("Import all objects, including hidden, .character_only=FALSE", { 105 | expect_error ( .script_version ) 106 | expect_error ( fun1() ) 107 | expect_error ( .directory ) 108 | expect_silent( import::from(module_hidden_objects.R, .all=TRUE) ) 109 | expect_equal ( .script_version, "v1.0" ) 110 | expect_equal ( fun1(), "fun1" ) 111 | expect_equal ( .directory, "my_dir" ) 112 | cleanup_environment() 113 | }) 114 | 115 | ## Tests end 116 | 117 | 118 | ## IMPORTANT: 119 | ## The following line must be printed exactly as is, 120 | ## it is used by the custom harness to check if the tests worked: 121 | print("Import tests completed successfully ...") 122 | 123 | -------------------------------------------------------------------------------- /tests/test_import/test_into_and_here.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | 25 | ## into() 26 | 27 | test_that("Imports from libraries work with into()", { 28 | expect_error ( normal_print("OK") ) 29 | expect_silent( import::into("custom_env", normal_print, .from=knitr) ) 30 | expect_output( normal_print("OK"), "OK" ) 31 | cleanup_environment("custom_env") 32 | }) 33 | 34 | test_that("Imports from libraries work with .into as a symbol", { 35 | expect_error ( normal_print("OK") ) 36 | symbol_env <- "custom_env" 37 | expect_silent( import::into(symbol_env, normal_print, .from=knitr) ) 38 | expect_output( normal_print("OK"), "OK" ) 39 | expect_true ("normal_print" %in% ls(name = symbol_env) ) 40 | cleanup_environment("custom_env") 41 | }) 42 | 43 | test_that("Imports from modules work with into()", { 44 | expect_error ( fun1() ) 45 | expect_silent( import::into("custom_env", fun1, .from=module_base.R) ) 46 | expect_equal ( fun1(), "fun1" ) 47 | cleanup_environment("custom_env") 48 | }) 49 | 50 | 51 | test_that("The .all parmeter with into() is smart about whether .except is being used", { 52 | expect_error ( fun1() ) 53 | expect_silent( import::into("custom_env", .from=module_base.R, .except=c("fun1","fun4")) ) 54 | expect_error ( fun1() ) 55 | expect_equal ( fun2(), "fun2" ) 56 | expect_equal ( fun3(), "fun3" ) 57 | expect_error ( fun4() ) 58 | expect_equal ( fun5(), "fun5" ) 59 | expect_equal ( fun6(), "fun6" ) 60 | cleanup_environment("custom_env") 61 | }) 62 | 63 | ## here() 64 | 65 | test_that("Imports from libraries work with here()", { 66 | expect_error ( normal_print("OK") ) 67 | expect_silent( import::here(normal_print, .from=knitr) ) 68 | expect_output( normal_print("OK"), "OK" ) 69 | cleanup_environment(cleanup_here=TRUE) 70 | }) 71 | 72 | test_that("Imports from modules work with here()", { 73 | expect_error ( fun1() ) 74 | expect_silent( import::here(fun1, .from=module_base.R) ) 75 | expect_equal ( fun1(), "fun1" ) 76 | cleanup_environment(cleanup_here=TRUE) 77 | }) 78 | 79 | test_that("Importing modules with here() by absolute path works", { 80 | abs_module <- file.path(tempdir(),"module_base.R") 81 | file.copy("module_base.R",abs_module) 82 | expect_error ( fun1() ) 83 | expect_silent( import::here(abs_module, "fun1", .character_only=TRUE) ) 84 | expect_equal ( fun1(), "fun1" ) 85 | cleanup_environment(cleanup_here=TRUE) 86 | }) 87 | 88 | 89 | 90 | ## Tests end 91 | 92 | ## IMPORTANT: 93 | ## The following line must be printed exactly as is, 94 | ## it is used by the custom harness to check if the tests worked: 95 | print("Import tests completed successfully ...") 96 | 97 | -------------------------------------------------------------------------------- /tests/test_import/test_into_param.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | 25 | # Test every combination of into parameters 26 | # Demonstrates that .character_only has no effect on .into processing 27 | test_that(".into all combinations", { 28 | 29 | # Abbreviations 30 | ce <- cleanup_environment 31 | ee <- expect_error 32 | es <- expect_silent 33 | eo <- expect_output 34 | 35 | aaa <- "my_named_env" 36 | 37 | ne1 <- new.env(); ne2 <- new.env() 38 | ee(normal_print("OK")); es(import::from( knitr , normal_print , .into="a", .character_only=FALSE)); eo(normal_print("OK")); ce("a") 39 | ee(normal_print("OK")); es(import::from( knitr , normal_print , .into=aaa, .character_only=FALSE)); eo(normal_print("OK")); ce(aaa) 40 | ee(ne1$normal_print("OK")); es(import::from( knitr , normal_print , .into={ne1}, .character_only=FALSE)); eo(ne1$normal_print("OK")); rm(ne1) 41 | ee(ne2$normal_print("OK")); es(import::from( knitr , normal_print , .into=ne2, .character_only=FALSE)); eo(ne2$normal_print("OK")); rm(ne2) 42 | 43 | ne1 <- new.env(); ne2 <- new.env() 44 | ee(normal_print("OK")); es(import::from("knitr", "normal_print", .into="a", .character_only=TRUE)); eo(normal_print("OK")); ce("a") 45 | ee(normal_print("OK")); es(import::from("knitr", "normal_print", .into=aaa, .character_only=TRUE)); eo(normal_print("OK")); ce(aaa) 46 | ee(ne1$normal_print("OK")); es(import::from("knitr", "normal_print", .into={ne1}, .character_only=TRUE)); eo(ne1$normal_print("OK")); rm(ne1) 47 | ee(ne2$normal_print("OK")); es(import::from("knitr", "normal_print", .into=ne2, .character_only=TRUE)); eo(ne2$normal_print("OK")); rm(ne2) 48 | 49 | }) 50 | 51 | test_that("Import .into works well with .character_only", { 52 | 53 | expect_error ( normal_print("OK") ) 54 | 55 | # With 56 | expect_error( import::from(knitr, normal_print, .into=test_pkg) ) 57 | expect_silent( import::from(knitr, normal_print, .into="test_pkg") ) 58 | 59 | # Importing to named environment without quotes fails, even without .character_only 60 | expect_error(import::from(module_base.R, fun1, .into=test_pkg)) 61 | 62 | # It succeeds if quoted 63 | expect_silent(import::from(module_base.R, fun1, .into="test_pkg")) 64 | 65 | # With import-symbols fix, it also succeeds with pasted strings 66 | expect_silent(import::from(module_base.R, fun2, .into=paste0("test_pkg"))) 67 | 68 | # And regardless of whether one uses .character_only=TRUE/FALSE 69 | expect_silent(import::from(paste0("module_base.R"), paste0("fun3"), 70 | .into=paste0("test_pkg"), .character_only=TRUE)) 71 | 72 | 73 | expect_output( normal_print("OK"), "OK" ) 74 | cleanup_environment("test_pkg") 75 | cleanup_environment() 76 | }) 77 | 78 | test_that("Const-character named environments work", { 79 | expect_error ( normal_print("OK") ) 80 | expect_silent( import::from(knitr, normal_print, .into="custom_named") ) 81 | expect_output( normal_print("OK"), "OK" ) 82 | cleanup_environment("custom_named") 83 | }) 84 | 85 | test_that("Symbol-character environments work", { 86 | expect_error ( normal_print("OK") ) 87 | expect_silent( import::from(knitr, normal_print, .into=paste0("custom_named", "_dynamic") )) 88 | expect_output( normal_print("OK"), "OK" ) 89 | cleanup_environment("custom_named_dynamic") 90 | }) 91 | 92 | test_that("Unnamed environments work with curlies", { 93 | my_env <- new.env() 94 | expect_error ( my_env$normal_print("OK") ) 95 | expect_silent( import::from(knitr, normal_print, .into={my_env})) 96 | expect_output( my_env$normal_print("OK"), "OK" ) 97 | rm(my_env) 98 | }) 99 | 100 | # After making .into a regular parameter, unnamed environments DO work with curlies 101 | test_that("Unnamed environments DO work without curlies", { 102 | my_env <- new.env() 103 | expect_error ( my_env$normal_print("OK") ) 104 | expect_silent( import::from(knitr, normal_print, .into=my_env)) 105 | expect_output( my_env$normal_print("OK"), "OK" ) 106 | rm(my_env) 107 | }) 108 | 109 | 110 | ## Tests end 111 | 112 | 113 | ## IMPORTANT: 114 | ## The following line must be printed exactly as is, 115 | ## it is used by the custom harness to check if the tests worked: 116 | print("Import tests completed successfully ...") 117 | 118 | -------------------------------------------------------------------------------- /tests/test_import/test_module_directories.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | # The .directory parameter should be respected, default should be "." 25 | test_that("The .directory parameter is respected", { 26 | expect_error ( fun_chdir_report() ) 27 | expect_error ( import::from(module_chdir.R, fun_chdir_report) ) 28 | expect_error ( fun_chdir_report() ) # Nothing should have been imported 29 | expect_silent( import::from(module_chdir.R, fun_chdir_report, .directory="module_chdir") ) 30 | expect_match ( fun_chdir_report(), "module_chdir$" ) 31 | expect_error ( import::from(module_chdir.R, fun_chdir_report, .directory="nonexisting_directory") ) 32 | cleanup_environment() 33 | }) 34 | 35 | 36 | 37 | # The .chdir parameter should be respected, default should be true 38 | # fun_chdir_report() returns the directory where its definition was originally sourced 39 | test_that("The .chdir parameter is respected (defaulting to TRUE)", { 40 | expect_error ( fun_chdir_report() ) 41 | expect_silent( import::from(module_chdir.R, fun_chdir_report, .directory="module_chdir") ) 42 | expect_match ( fun_chdir_report(), "module_chdir$" ) 43 | cleanup_environment() 44 | }) 45 | 46 | # The .chdir parameter should be respecte, if TRUE is explicitly passed 47 | test_that("The .chdir parameter is respected when passed TRUE", { 48 | expect_error ( fun_chdir_report() ) 49 | expect_silent( import::from(module_chdir.R, fun_chdir_report, .directory="module_chdir", .chdir=TRUE) ) 50 | expect_match ( fun_chdir_report(), "module_chdir$" ) 51 | cleanup_environment() 52 | }) 53 | 54 | # The .chdir parameter should be respected, if set to false no chdir performed, 55 | # so the report should NOT match the name of the module_chdir directory 56 | test_that("The .chdir parameter is respected when passed FALSE", { 57 | expect_error ( fun_chdir_report() ) 58 | expect_silent ( import::from(module_chdir.R, fun_chdir_report, .directory="module_chdir", .chdir=FALSE) ) 59 | expect_failure( expect_match(fun_chdir_report(),"module_chdir$") ) 60 | cleanup_environment() 61 | }) 62 | 63 | 64 | ## Tests end 65 | 66 | 67 | ## IMPORTANT: 68 | ## The following line must be printed exactly as is, 69 | ## it is used by the custom harness to check if the tests worked: 70 | print("Import tests completed successfully ...") 71 | 72 | -------------------------------------------------------------------------------- /tests/test_import/test_module_recursive.R: -------------------------------------------------------------------------------- 1 | 2 | # We use the testthat library to evaluate expectations. 3 | # Tests must nevertheless be run outside the standard testthat 4 | # test harness, because import::from changes environments that 5 | # are protected when run inside the harness. See the contents 6 | # of the testthat directory to examine the custom test harness 7 | # that is used instead. These tests can also be run manually. 8 | library(testthat) 9 | 10 | # Printing the directory helps with fixing any problems if tests fail 11 | print(getwd()) 12 | 13 | # Source cleanup script 14 | # (we do not use import::from because then the script would clean itself up) 15 | source("cleanup_environment.R") 16 | 17 | ## IMPORTANT: 18 | ## Remember to run the cleanup script after each test sequence with: 19 | ## > cleanup_environment() 20 | 21 | 22 | ## Tests begin 23 | 24 | test_that("Recursive import::here() works with modules", { 25 | text = "hi friend, how are you" 26 | text_title_case = "Hi Friend, How are you" 27 | expect_error(print_title_text(text)) 28 | expect_error(to_title(text)) # Inner utility function should not be available 29 | 30 | expect_silent(import::from(module_recursive_outer_here.R, print_title_text)) 31 | expect_output(print_title_text(text), text_title_case) 32 | expect_error(to_title(text)) # Inner utility function should not be available 33 | cleanup_environment() 34 | }) 35 | 36 | test_that("Recursive import::from() works with modules (with warning)", { 37 | text = "hi friend, how are you" 38 | text_title_case = "Hi Friend, How are you" 39 | expect_error(print_title_text(text)) 40 | expect_error(to_title(text)) # Inner utility function should not be available 41 | 42 | expect_warning(import::from(module_recursive_outer_from.R, print_title_text)) 43 | expect_output(print_title_text(text), text_title_case) 44 | expect_error(to_title(text)) # Inner utility function should not be available 45 | cleanup_environment() 46 | }) 47 | 48 | test_that("Recursive import::here() works with modules and packages", { 49 | text = "hi friend, how are you" 50 | text_title_case = "Hi Friend, How are you" 51 | expect_error(normal_print("OK")) # This test actually relies on knitr::normal_print() 52 | expect_error(print_title_text(text)) 53 | expect_error(to_title(text)) # Inner utility function should not be available 54 | 55 | expect_silent(import::from(module_recursive_package_here.R, print_title_text)) 56 | expect_output(print_title_text(text), text_title_case) 57 | expect_error(to_title(text)) # Inner utility function should not be available 58 | expect_error(normal_print("OK")) # Inner utility function should not be available 59 | cleanup_environment() 60 | }) 61 | 62 | test_that("Recursive import::from() works with modules and packages (with warning)", { 63 | text = "hi friend, how are you" 64 | text_title_case = "Hi Friend, How are you" 65 | expect_error(normal_print("OK")) # This test actually relies on knitr::normal_print() 66 | expect_error(print_title_text(text)) 67 | expect_error(to_title(text)) # Inner utility function should not be available 68 | 69 | expect_warning(import::from(module_recursive_package_from.R, print_title_text)) 70 | expect_output(print_title_text(text), text_title_case) 71 | expect_error(to_title(text)) # Inner utility function should not be available 72 | expect_error(normal_print("OK")) # Inner utility function should not be available 73 | cleanup_environment() 74 | }) 75 | 76 | # Combines recursive tests with chdir functionality 77 | test_that("Recursive module imports in subdirs work (with warning)", { 78 | skip_on_os("windows") # Test relies on using forward slashes in paths 79 | text = "hi friend, how are you" 80 | text_title_case = "Hi Friend, How are you" 81 | expect_error(print_text(text)) 82 | expect_error(print_title_text(text)) 83 | 84 | expect_silent(import::from("module_recursive/src/text.R", print_text)) 85 | expect_output(print_text(text), text) 86 | expect_warning(import::from("module_recursive/src/title_text.R", print_title_text)) 87 | expect_output(print_title_text(text), text_title_case) 88 | cleanup_environment() 89 | }) 90 | 91 | test_that("Recursive module imports in subdirs work with here()", { 92 | skip_on_os("windows") # Test relies on using forward slashes in paths 93 | text = "hi friend, how are you" 94 | text_title_case = "Hi Friend, How are you" 95 | expect_error(print_text(text)) 96 | expect_error(print_title_text(text)) 97 | expect_silent(import::from("module_recursive/src/text.R", print_text)) 98 | expect_output(print_text(text), text) 99 | expect_silent(import::from("module_recursive/src/title_text_here.R", print_title_text)) 100 | expect_output(print_title_text(text), text_title_case) 101 | cleanup_environment() 102 | }) 103 | 104 | 105 | # Using library() inside a module is not recommended and should throw a warning. 106 | # The reason is that although imported standalone functions will work, 107 | # imported functions that rely on the library will not. 108 | test_that("Using library() inside a module throws a warning", { 109 | text = "hi friend, how are you" 110 | expect_error(standalone_fun(text)) 111 | expect_error(dependent_fun(text)) 112 | expect_warning(import::from(module_recursive_library.R, standalone_fun, dependent_fun)) 113 | expect_output(standalone_fun(text), text) 114 | expect_error(dependent_fun(text)) 115 | cleanup_environment() 116 | }) 117 | 118 | 119 | 120 | ## Tests end 121 | 122 | 123 | ## IMPORTANT: 124 | ## The following line must be printed exactly as is, 125 | ## it is used by the custom harness to check if the tests worked: 126 | print("Import tests completed successfully ...") 127 | 128 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | 3 | # Import should never be attached 4 | #library(import) 5 | 6 | test_check("import") 7 | -------------------------------------------------------------------------------- /tests/testthat/test_import.R: -------------------------------------------------------------------------------- 1 | 2 | # Manual setup to duplicate testthat environment 3 | #library(testthat) 4 | #setwd("tests/testthat") 5 | 6 | # Set context 7 | context("Setting up custom test harness") 8 | 9 | # We start with some sanity checks to ensure that 10 | # testthat is correctly set up. 11 | test_that("Harness works", { 12 | 13 | # knitr::normal_print() should not be found 14 | expect_error(normal_print("x")) 15 | 16 | }) 17 | 18 | # testthat does not work well with packages that manipulate 19 | # namespaces, so tests need to be run manually. 20 | # (see $package_root/tests/test_import/.R) 21 | 22 | # testthat starts in $package_root/tests/testthat 23 | # test_import tests should start in $package_root/tests/test_import 24 | # Change to the right directory, then change back again at the end. 25 | testthat_dir <- setwd(file.path("..","test_import")) 26 | 27 | # Look for any files in test_import dir that start with "test" 28 | # and run each of them in a new process. See test_import/test_template 29 | # for an example of required parts for each test file 30 | test_files <- list.files(".","^test") 31 | 32 | for ( test_file in test_files ) { 33 | 34 | # Set context according to the file name 35 | context(test_file) 36 | 37 | # Setup a test sequence 38 | test_that(paste(test_file," works"), { 39 | 40 | # Skip on windows CI for now 41 | if ( isTRUE(as.logical(Sys.getenv("CI"))) & 42 | tolower(Sys.info()[["sysname"]]) == "windows" ) { 43 | skip("Skipping on CI Windows Action") 44 | } 45 | 46 | # Set up a new Rscript process to source the manual tests, 47 | # then check the output to examine if they ran correctly. 48 | rscript_file <- ifelse(Sys.info()['sysname']=="Windows","Rscript.exe","Rscript") 49 | rscript_path <- paste0("\"",file.path(R.home(),"bin",rscript_file),"\"") 50 | test_output <- system(paste(rscript_path, test_file), intern=TRUE) 51 | expect_match( 52 | test_output, 53 | "Import tests completed successfully ...", 54 | all=FALSE, 55 | label=paste(test_output,collapse="\n") 56 | ) 57 | 58 | }) 59 | } 60 | 61 | # Skipped tests in the test_import directory are not automatically reported. 62 | # Any skipped tests can be noted here 63 | context("List any skipped tests") 64 | # No tests are currently skipped 65 | 66 | # Switch back to the original directory, just to be safe 67 | setwd(testthat_dir) 68 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/import.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "The import package" 3 | output: 4 | rmarkdown::html_vignette: 5 | toc: true 6 | vignette: > 7 | %\VignetteIndexEntry{The import package} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | editor_options: 11 | chunk_output_type: console 12 | --- 13 | 14 | * This version: May, 2022 15 | * Latest version at: https://import.rticulate.org/articles/import.html 16 | * A briefer intro at: https://import.rticulate.org/ 17 | 18 | # Introduction 19 | 20 | One of the most important aspects of the R ecosystem is the ease with which 21 | extensions and new features can be developed and distributed in the form of 22 | *packages.* The main distribution channel is the *Comprehensive R 23 | Archive Network*, from which packages can be installed directly from 24 | R. Another popular option is using GitHub repositories from which packages can 25 | also be painlessly installed, e.g. using `install_github` from the 26 | `devtools` package. 27 | 28 | The `import` package provides an alternative approach to using external 29 | functionality in R scripts; first however, it is useful to describe the standard 30 | approach to clarify how `import` may serve as improvement. 31 | The most common way to include the functionality provided by a package is to use 32 | the `library` function: 33 | 34 | ```{r, eval = FALSE} 35 | library(PackageA) 36 | library(PackageB) 37 | 38 | value1 <- function_a(...) # Supposedly this comes from PackageA, 39 | value2 <- function_b(...) # and this from PackageB, but who knows?! 40 | ... 41 | ``` 42 | 43 | In some situations this is fine; however there are some subtle shortcomings: 44 | 45 | 1. Packages are *attached* and *all* of their exported objects are exposed, 46 | 2. When using more packages this way, the *order* in which they are attached 47 | can be important, 48 | 3. It quickly becomes *unclear* to the reader of a script which package 49 | certain functionality comes from, and 50 | 4. the terms "library" and "package" are often used incorrectly 51 | (although a minor point, it seems to confuse somewhat). 52 | 53 | The problem with (1) is that the search path is populated with more 54 | objects than are needed and it is not immediately clear whether name clashes 55 | will occur. Problem (2) refers to the case where packages export 56 | different objects with the same names, say if `function_b` is exported in both 57 | `PackageA` and `PackageB` above. In this case the name will 58 | point to the object from the package attached *last*. The earlier exposed 59 | objects are said to *masked*. Even if this is not a problem 60 | when *writing* the script, an update of packages may cause this problem 61 | later on when *executing* the script; and tracking down the resulting 62 | errors may be tough and time consuming. Problem (3) may appear unimportant, 63 | but it is not to be underestimated. Code snippets are very commonly shared 64 | and spending time figuring out where functionality comes from is not 65 | a very satisfying nor value-adding activity. 66 | 67 | It is possible to unambiguously specify where a function comes from by 68 | prefixing it with `::` every time it used, but this is often 69 | overly verbose and does not provide an easily accessible overview of what 70 | external functionality is used in a script. One may also import single 71 | exported objects, one at a time, using the (double) "colon syntax", 72 | 73 | ```{r, eval = FALSE} 74 | function_a <- PackageA::function_a 75 | function_b <- PackageB::function_b 76 | ``` 77 | 78 | The downside of this approach is that the object is **placed 79 | in the user's global work space**, rather than being encapsulated somewhere else 80 | in the search path (when using `library` to load `pkg`, a namespace `package:pkg` 81 | will be attached in the search path which will contain the exported functions 82 | from `pkg`). Another minor point is that one can **only import one object at a 83 | time** using this approach. 84 | 85 | While packages form the backbone of code distribution, another option comes 86 | in the form of *scripts*, but these are usually task specific and not 87 | commonly used to "bundle" functionality for use in *other* scripts. In 88 | particular, when `source` is used to include contents from one script in 89 | another, once again *all* objects produced by the script will be "exposed" 90 | and may "over populate" the working environment, masking other objects, 91 | if not only producing some mental clutter. Scope management is therefore not too 92 | comfortable when splitting functionality across files in a modular way. 93 | 94 | The `import` package sets out to improve the way external functionality 95 | is included in your code by alleviating some of the concerns raised above by 96 | providing an expressive way of importing object from both packages and 97 | scripts. The latter provides a bridge between the *package* approach to 98 | distribution and simple stand-alone script files. This allows for the use of scripts 99 | as *modules*, a collection of related object definitions, each of which 100 | may be used at different places without exposing more than necessary. 101 | 102 | The package is inspired in part by Python's 103 | `from some_module import some_function` syntax, and solves 104 | the two issues raised above. It is also similar to `roxygen2`s 105 | `@importFrom package function1 function2` for packages. While `import` will 106 | also work for package development, the intended use case is when using 107 | external functions `R` scripts. 108 | 109 | In addition to being able to import objects from packages, `import` also allows 110 | you to import objects from other *scripts* (i.e. a kind of *module*). This allows 111 | a simple way to distribute and use functionality without the need to write 112 | a full package. One example is a Shiny app, where one can place definitions 113 | in a script and import only the needed objects where they are used. This 114 | avoids workspace clutter and name clashes. For more details see below. 115 | 116 | 117 | # Basic Usage 118 | 119 | ## Importing from Packages 120 | 121 | The most basic use case is to import a few functions from package 122 | (here the `psych` package): 123 | 124 | ```{r, eval=FALSE} 125 | import::from(psych, geometric.mean, harmonic.mean) 126 | geometric.mean(trees$Volume) 127 | ``` 128 | 129 | The imported objects are placed in a separate entity in the search path which by 130 | default is named "imports". It is therefore also easy to get rid of them again 131 | with `detach("imports")`. The main point is that it is **clear which functions 132 | will be used and where they come from**. It's noteworthy that there is nothing 133 | special going on: the `import::from` function is only a convenient wrapper 134 | around `getExportedValue` (as is `::` itself) and `assign`. 135 | 136 | The `import` package itself should not to be attached 137 | (don't include it via `library`, you will get a warning). Rather, it is designed 138 | to be expressive when using the colon syntax. To import non-exported objects 139 | one must use triple-colon syntax: `import:::from(pkg, obj)`. 140 | If any of the `import` functions are called regularly, i.e. without preceding 141 | `import::` or `import:::`, an error is raised. If `import` is attached, a 142 | startup message will inform that `import` *should not* be attached. 143 | 144 | If one of the function names conflicts with an existing function (such as `filter` 145 | from the `dplyr` package) it is simple to rename it: 146 | 147 | ```{r, eval=FALSE} 148 | import::from(dplyr, select, arrange, keep_when = filter) 149 | keep_when(mtcars, hp>250) 150 | ``` 151 | 152 | This does pretty much what it says: three functions are imported from `dplyr`, 153 | two of which will keep their original name, and one which is renamed, e.g. to 154 | avoid name clash with `stats::filter`. 155 | 156 | You can use `.all=TRUE` to import all functions from a package, but rename one of them: 157 | 158 | ```{r, eval=FALSE} 159 | import::from(dplyr, keep_when = filter, .all=TRUE) 160 | ``` 161 | 162 | To omit a function from the import, use `.except` (which takes a character vector): 163 | 164 | ```{r, eval=FALSE} 165 | import::from(dplyr, .except=c("filter", "lag")) 166 | ``` 167 | 168 | Note that `import` tries to be smart about this and assumes that if you are using the 169 | `.except` parameter, you probably want to import everything you are _not_ explicitly omitting, 170 | and sets the `.all` parameter to `TRUE`. You can override this in exceptional cases, but you 171 | seldom need to. 172 | 173 | Finally, a more complex example, combining a few different import statements: 174 | 175 | ```{r, eval = FALSE} 176 | import::from(magrittr, "%>%") 177 | import::from(dplyr, starwars, select, mutate, keep_when = filter) 178 | import::from(tidyr, unnest) 179 | import::from(broom, tidy) 180 | 181 | ready_data <- 182 | starwars %>% 183 | keep_when(mass < 100) %>% 184 | select(name, height, mass, films) %>% 185 | unnest(films) %>% 186 | mutate( log_mass = log(mass), films=factor(films)) 187 | 188 | linear_model <- 189 | lm(log_mass ~ height + films, data = ready_data) %>% 190 | tidy 191 | ``` 192 | 193 | In the above, it is clear *which* package provides *which* functions 194 | (one could e.g. otherwise be tempted to think that `tidy` belonged to 195 | `tidyr`). Note that ordering is irrelevant, even if `tidyr` at some point 196 | exposes a function `tidy` after an update, as `import` is *explicit* about 197 | importing. 198 | 199 | It also shows that one can import multiple objects in a single 200 | statement, and even rename objects if desired; for example, in the above 201 | one can imagine that `filter` from `stats` is needed later on, and so 202 | `dplyr`'s `filter` is renamed to avoid confusion. Sometimes, it is 203 | not at all clear what purpose a package has; e.g. the name `magrittr` does 204 | not immediately reveal that it's main purpose is to provide the pipe 205 | operator, `%>%`. 206 | 207 | 208 | ### Importing Functions from "Module" Scripts {#module} 209 | 210 | 211 | The `import` package allows for importing objects defined in script files, 212 | which we will here refer to as "modules". 213 | The module will be fully evaluated by `import` when an import is requested, 214 | after which objects such as functions or data can be imported. 215 | Such modules should be side-effect free, but this is 216 | not enforced. 217 | 218 | Attachments are detached (e.g. packages attached by `library`) 219 | but loaded namespaces remain loaded. This means that *values* created 220 | by functions in an attached namespace will work with `import`, but 221 | functions to be exported *should not* rely on such functions (use function 222 | importing in the modules instead). 223 | 224 | For example, the file 225 | [sequence_module.R](https://raw.githubusercontent.com/rticulate/import/master/man/examples/sequence_module.R) 226 | contains several functions calculating terms of mathematical sequences. 227 | It is possible to import from such files, just as one imports from packages: 228 | 229 | ```{r, eval=FALSE} 230 | import::from(sequence_module.R, fibonacci, square, triangular) 231 | ``` 232 | 233 | Renaming, the `.all`, and the `.except` parameters work in the same way as for packages: 234 | 235 | ```{r, eval=FALSE} 236 | import::from(sequence_module.R, fib=fibonacci, .except="square") 237 | ``` 238 | 239 | If a module is modified, `import` will 240 | realize this and reload the script if further imports are executed or 241 | re-executed; otherwise additional imports will not cause the script to be 242 | reloaded for efficiency. As the script is loaded in its own environment 243 | (maintained by `import`) dependencies are kept (except those exposed through 244 | attachment), as the following small example shows. 245 | 246 | **Contents of "[some_module.R](https://raw.githubusercontent.com/rticulate/import/master/man/examples/some_module.R)":** 247 | ```{r, eval=FALSE} 248 | ## Do not use library() inside a module. This results in a warning, 249 | ## and functions relying on ggplot2 will not work. 250 | #library(ggplot2) 251 | 252 | ## This is also not recommended, because it is not clear wether recursively 253 | ## imported functions should be available after the module is imported 254 | #import::here(qplot, .from = ggplot2) 255 | 256 | ## This is the recommended way to recursively import functions on which 257 | ## module functions depend. The qplot function will be available to 258 | ## module functions, but will not itself be available after import 259 | import::here(qplot, .from = ggplot2) 260 | 261 | ## Note this operator overload is not something you want to `source`! 262 | `+` <- function(e1, e2) 263 | paste(e1, e2) 264 | 265 | ## Some function relying on the above overload: 266 | a <- function(s1, s2) 267 | s1 + rep(s2, 3) 268 | 269 | ## Another value. 270 | b <- head(iris, 10) 271 | 272 | ## A value created using a recursively imported function 273 | p <- qplot(Sepal.Length, Sepal.Width, data = iris, color = Species) 274 | 275 | ## A function relying on a function exposed through attachment: 276 | plot_it <- function() 277 | qplot(Sepal.Length, Sepal.Width, data = iris, color = Species) 278 | ``` 279 | 280 | **Usage:** 281 | ```{r, eval=FALSE} 282 | import::from(some_module.R, a, b, p, plot_it) 283 | 284 | ## Works: 285 | a("cool", "import") 286 | 287 | ## The `+` is not affecting anything here, so this won't work: 288 | # "cool" + "import" 289 | 290 | # Works: 291 | b 292 | p 293 | plot_it() 294 | ``` 295 | 296 | Suppose that you have some related functionality that you wish to bundle, and 297 | that authoring a full package seems excessive or inappropriate 298 | for the specific task, for example bundling related user interface components for a `shiny` 299 | application. One option with `import` is to author a module (script), say as outlined 300 | below: 301 | 302 | ```{r, eval=FALSE} 303 | # File: foo.R 304 | # Desc: Functionality related to foos. 305 | # Imports from other_resources.R 306 | # When recursively importing from another module or package for use by 307 | # your module functions, you should always use import::here() rather 308 | # than import::from() or library() 309 | import::here(fun_a, fun_b, .from = "other_resources.R") 310 | 311 | internal_fun <- function(...) ... 312 | 313 | fun_c <- function(...) 314 | { 315 | ... 316 | a <- fun_a(...) 317 | i <- internal_fun(...) 318 | ... 319 | } 320 | 321 | fun_d <- function(...) ... 322 | ``` 323 | 324 | Then in another file we need `fun_c`: 325 | 326 | ```{r, eval = FALSE} 327 | # File: bar.R 328 | # Desc: Functionality related to bars. 329 | # Imports from foo.R 330 | import::here(fun_c, .from = "foo.R") 331 | ... 332 | ``` 333 | 334 | In the above, *only* `fun_c` is visible inside `bar.R`. The 335 | functions on which it depends exist, but are not exposed. 336 | Also, note that imported scripts may themselves import. 337 | 338 | Since the desired effect of `import::from` inside a module script 339 | is ambiguous, this results in a warning (but the functions will 340 | still be imported into the local environment of the script, just 341 | as with `import::here` which only imports are only exposed to 342 | the module itself. 343 | 344 | When importing from a module, it is sourced into an environment 345 | managed by `import`, and will not be sourced again upon subsequent 346 | imports (unless the file has changed). For example, in a `shiny` 347 | application, importing some 348 | objects in `server.R` and others in `ui.R` from the same module will not 349 | cause it to be sourced twice. 350 | 351 | 352 | ### Choosing where import looks for packages or modules 353 | 354 | The `import` package will by default use the current set of library paths, i.e. 355 | the result of `.libPaths()`. It is, however, possible to specify a different set 356 | of library paths using the `.library` argument in any of the `import` functions, 357 | for example to import packages installed in a custom location, or to remove any 358 | ambiguity as to where imports come from. 359 | 360 | Note that in versions up to and including `1.3.0` this defaulted to use only the 361 | *first* entry in the library paths, i.e. `.library=.libPaths()[1L]`. We believe 362 | the new default is applicable in a broader set of circumstances, but if this 363 | change causes any issues, we would very much appreciate hearing about it. 364 | 365 | When importing from a module (.R file), the directory where `import` looks for 366 | the module script can be specified with the with `.directory` parameter. 367 | The default is `.` (the current working directory). 368 | 369 | ### Choosing where the imported functions are placed 370 | 371 | One can also specify which names to use in the search path and use several to 372 | group imports. Names can be specified either as character literals or as 373 | variables of type `character` (for example if the environment needs to be 374 | determined dynamically). 375 | 376 | ```{r, eval = FALSE} 377 | import::from(magrittr, "%>%", "%$%", .into = "operators") 378 | import::from(dplyr, arrange, .into = "datatools") 379 | import::from(psych, describe, .into=month.name[1]) # Uses env: "January" 380 | ``` 381 | 382 | The `import::into` and `import::from` accept the same parameters and achieve the 383 | same result. The the choice between them a matter of preference). If using 384 | custom search path entities actively, one might prefer the alternative syntax 385 | (which does the same but reverses the argument order): 386 | 387 | ```{r, eval = FALSE} 388 | import::into("operators", "%>%", "%$%", .from = magrittr) 389 | import::into("datatools", arrange, .from = dplyr) 390 | import::into(month.name[1], describe, .from=psych) 391 | ``` 392 | 393 | Be aware that beginning in version `1.3.0` hidden objects (those with names 394 | prefixed by a period) are supported. Take care to avoid name clashes with 395 | argument names. 396 | 397 | If it is desired to import objects directly into the current environment, 398 | this can be accomplished by `import::here`. This is particularly 399 | useful when importing inside a function definition, or module scripts as 400 | described [here](#module). 401 | 402 | ```{r, eval = FALSE} 403 | import::here("%>%", "%$%", .from = magrittr) 404 | import::here(arrange, .from = dplyr) 405 | ``` 406 | 407 | Instead of specifying a named environment on the search path, by passing a 408 | `character` to the `.into` parameter, it is possible to directly specify an 409 | environment. The function automatically determines which use case is involved, 410 | based on the `mode()` of the `.into` parameter (either `character` or 411 | `environment`). 412 | 413 | Prior to version `1.3.0`, non-standard evaluation (NSE) was applied to the 414 | `.into` parameter, and it was necessary to surround it with `{}`, in order for 415 | it to be treated as an `environment`. This is no longer needed, although it is 416 | still allowed (curly brackets are simply ignored). 417 | 418 | Examples include: 419 | 420 | ```{r, eval = FALSE} 421 | # Import into the local environment 422 | import::into(environment(), "%>%", .from = magrittr) 423 | 424 | # Import into the global environment, curlies are optional 425 | import::into({.GlobalEnv}, "%>%", "%$%", .from = magrittr) 426 | 427 | # Import into a new environment, mainly useful for python-style imports 428 | # (see below) 429 | x = import::into(new.env(), "%<>%", .from = magrittr) 430 | ``` 431 | 432 | 433 | # Advanced usage 434 | 435 | ## Advanced usage and the .character_only parameter 436 | 437 | The `import` package uses non-standard evaluation (NSE) on the `.from` and `...` 438 | parameters, allowing the names of packages and functions to be listed without 439 | quoting them. This makes some common use-cases very straightforward, but can get 440 | in the way of more programmatic usages. 441 | 442 | This is where the `.character_only` parameter comes in handy. By setting 443 | `.character_only=TRUE`, the non-standard evaluation of the `.from` and the `...` 444 | parameters is disabled. Instead, the parameters are processed as character 445 | vectors containing the relevant values. 446 | 447 | (Previously, NSE was also applied to the `.into` parameter, but as of version 448 | `1.3.0` this is no longer the case, and all parameters except `.from` and `...` 449 | are always evaluated in a standard way.) 450 | 451 | It is useful to examine some examples of how specifying `.character_only=TRUE` 452 | can be helpful. 453 | 454 | 455 | ## Programmatic selection of objects to import 456 | 457 | It is not always know in advance which objects to import from a given 458 | package. For example, assume we have a list of objects from the 459 | `broom` package that we need to import, we can do it as follows: 460 | 461 | ```{r, eval = FALSE} 462 | objects <- c("tidy", "glance", "augment") 463 | import::from("broom", objects, .character_only=TRUE) 464 | ``` 465 | 466 | This will import the three functions specified in the `objects` vector. 467 | It is worth noting that because `.character_only` disables non-standard 468 | evaluation on *all* parameters, the name of the package must now be quoted. 469 | 470 | One common use case is when one wants to import all objects except one 471 | or a few, because of conflicts with other packages. Should one, for 472 | example, want to use the `stats` versions of the `filter()` and `lag()` 473 | functions, but import all the other functions in the `dplyr` package, 474 | one could do it like this: 475 | 476 | ```{r, eval = FALSE} 477 | objects <- setdiff(getNamespaceExports("dplyr"), c("filter","lag")) 478 | import::from("dplyr", objects, .character_only=TRUE) 479 | ``` 480 | 481 | 482 | ## Programmatic selection of module location 483 | 484 | The same approach can be used when the directory of the source 485 | file for a module is not known in advance. This can be useful 486 | when the original source file is not always run with the original 487 | working directory, but one still does not want to specify a 488 | hard-coded absolute path, but to determine it at run time: 489 | 490 | ```{r, eval = FALSE} 491 | mymodule <- file.path(mypath, "module.R") 492 | import::from(mymodule, "myfunction", .character_only=TRUE) 493 | ``` 494 | 495 | Again, note that now the name of the function must be quoted because 496 | non-standard evaluation is disabled on all parameters. 497 | 498 | The `here` package is useful in many circumstances like this; it 499 | allows the setting of a "root" directory for a project and by using 500 | the `here::here()` function to figure out the correct directory, 501 | regardless of the working directory. 502 | 503 | ```{r, eval = FALSE} 504 | import::from(here::here("src/utils/module.R")), "myfunction", .character_only=TRUE) 505 | ``` 506 | 507 | Alternatively, if the file name is always the same and it is only the directory 508 | that differs, you could use the `.directory` parameter, which always expects standard 509 | evaluation arguments. 510 | 511 | ```{r, eval = FALSE} 512 | import::from(module.R, "myfunction", here::here("src/utils")) 513 | ``` 514 | 515 | Note that `here::here()` has no relation to `import::here()` despite 516 | the similarity in names. 517 | 518 | 519 | ## Importing from a URL 520 | 521 | Another case where `.character_only` comes in handy is when one 522 | wants to import some functions from a URL. While `import` does 523 | not allow direct importing from a URL (because of difficult 524 | questions about when a URL target has changes, whether to 525 | download a file and other things), it easy to achieve the desired 526 | result by using the `pins` package (whose main purpose is to 527 | resolve such difficult questions). A simple example follows, which 528 | directly imports the `myfunc()` function, which is defined in the 529 | [`plusone_module.R`](https://raw.githubusercontent.com/rticulate/import/master/man/examples/plusone_module.R): 530 | 531 | ```{r, eval = FALSE} 532 | url <- "https://raw.githubusercontent.com/rticulate/import/master/man/examples/plusone_module.R" 533 | import::from(pins::pin(url), "myfunc", .character_only=TRUE) 534 | myfunc(3) 535 | #> [1] 4 536 | ``` 537 | 538 | 539 | ## Python-like imports 540 | 541 | A frequent pattern in python imports packages under an alias; all subsequent use of the imported objects then explicitly includes the alias: 542 | 543 | ```python 544 | import pandas as pd 545 | import numpy as np 546 | import math as m 547 | 548 | print(m.pi) 549 | print(m.e) 550 | ``` 551 | 552 | In order to achieve this functionality with the import package, use `.into={new.env()}` which assign to a new environment without attaching it. `import::from()` returns this environment, so it can be assigned to a variable: 553 | 554 | ```{r, eval = FALSE} 555 | # Import into a new namespace, use $ to access 556 | td <- import::from(tidyr, spread, pivot_wider, .into={new.env()}) 557 | dp <- import::from(dplyr, .all=TRUE, .into={new.env()}) 558 | dp$select(head(cars),dist) 559 | #> dist 560 | #> 1 2 561 | #> 2 10 562 | #> 3 4 563 | #> 4 22 564 | #> 5 16 565 | #> 6 10 566 | 567 | # Note that functions are not visible without dp$ prefix 568 | select(head(cars),dist) 569 | #> Error in select(head(cars), dist): could not find function "select" 570 | ``` 571 | 572 | 573 | ## Importing S3 methods 574 | 575 | ![](lifecycle-experimental.svg) 576 | 577 | S3 methods work well in local context, but when the method is called from a different environment, it must be registered (in packages, this is done in the `NAMESPACE` file). `import` can now register methods of form `generic.class` or `generic.class.name` automatically using the new `.S3` argument. By specifying `.S3=TRUE`, `import` will automatically detect methods for existing or new generics. No need to export and/or register them manually! 578 | 579 | Consider following script `foo.r` with a generic and two methods: 580 | ```{r, eval = FALSE} 581 | # foo.r 582 | # functions with great foonctionality 583 | foo = function(x){ 584 | UseMethod("foo", x) 585 | } 586 | 587 | foo.numeric <- function(x){ 588 | x + 1 589 | } 590 | 591 | foo.character <- function(x){ 592 | paste0("_", x, "_") 593 | } 594 | ``` 595 | 596 | Now, all we need is to import the `foo` generic: 597 | ```{r, eval = FALSE} 598 | import::from("foo.r", foo, .S3=TRUE) 599 | 600 | foo(0) # 1 601 | foo("bar") # _bar_ 602 | ``` 603 | 604 | *This is an experimental feature. We think it should work well and you are 605 | encouraged to use it and report back – but the syntax and semantics may change 606 | in the future to improve the feature.* 607 | -------------------------------------------------------------------------------- /vignettes/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycleexperimentalexperimental --------------------------------------------------------------------------------