├── .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 | [](https://CRAN.R-project.org/package=import)
9 | [ `-success)](https://github.com/rticulate/import)
10 | [](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 | [](https://CRAN.R-project.org/package=import)
8 | [](https://github.com/rticulate/import)
10 | [](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 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-defunct.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-deprecated.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-experimental.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-maturing.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-questioning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-stable.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-superseded.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | 
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 |
--------------------------------------------------------------------------------