Skip to content

Commit

Permalink
Add use_import_from() (r-lib#1377)
Browse files Browse the repository at this point in the history
And generalise block_append() to uniquify and sort. Fixes r-lib#1361.

Co-authored-by: Hadley Wickham <[email protected]>
  • Loading branch information
malcolmbarrett and hadley authored Mar 3, 2021
1 parent aa81353 commit 4819d19
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 20 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Suggests:
knitr,
magick,
mockr,
pkgload,
rmarkdown,
roxygen2,
spelling (>= 1.2),
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export(use_github_release)
export(use_gitlab_ci)
export(use_gpl3_license)
export(use_gpl_license)
export(use_import_from)
export(use_jenkins)
export(use_latest_dependencies)
export(use_lgpl_license)
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

* `use_tidy_release_test_env()` has been deleted since we no longer recommend
including test environment in CRAN comments (#1365).

* Added `use_import_from()` to put `@importFrom pkg fun` into a package in a
consistent way (@malcolmbarrett, #1377)

# usethis 2.0.1

Expand Down
30 changes: 22 additions & 8 deletions R/block.R
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
block_append <- function(desc, value, path, block_start, block_end,
block_prefix = NULL, block_suffix = NULL) {
block_append <- function(desc, value, path,
block_start = "# <<<",
block_end = "# >>>",
block_prefix = NULL,
block_suffix = NULL,
sort = FALSE) {
if (!is.null(path) && file_exists(path)) {
lines <- read_utf8(path)
if (value %in% lines) {
if (all(value %in% lines)) {
return(FALSE)
}

Expand All @@ -24,18 +28,24 @@ block_append <- function(desc, value, path, block_start, block_end,
end <- block_lines[[2]]
block <- lines[seq2(start, end)]

new_lines <- union(block, value)
if (sort) {
new_lines <- sort(new_lines)
}

lines <- c(
lines[seq2(1, start - 1L)],
block,
value,
new_lines,
lines[seq2(end + 1L, length(lines))]
)
write_utf8(path, lines)

TRUE
}

block_replace <- function(desc, value, path, block_start, block_end) {
block_replace <- function(desc, value, path,
block_start = "# <<<",
block_end = "# >>>") {
if (!is.null(path) && file_exists(path)) {
lines <- read_utf8(path)
block_lines <- block_find(lines, block_start, block_end)
Expand Down Expand Up @@ -68,13 +78,13 @@ block_replace <- function(desc, value, path, block_start, block_end) {
}


block_show <- function(path, block_start, block_end) {
block_show <- function(path, block_start = "# <<<", block_end = "# >>>") {
lines <- read_utf8(path)
block <- block_find(lines, block_start, block_end)
lines[seq2(block[[1]], block[[2]])]
}

block_find <- function(lines, block_start, block_end) {
block_find <- function(lines, block_start = "# <<<", block_end = "# >>>") {
# No file
if (is.null(lines)) {
return(NULL)
Expand All @@ -97,3 +107,7 @@ block_find <- function(lines, block_start, block_end) {

c(start + 1L, end - 1L)
}

block_create <- function(lines = character(), block_start = "# <<<", block_end = "# >>>") {
c(block_start, unique(lines), block_end)
}
4 changes: 2 additions & 2 deletions R/pipe.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ use_pipe <- function(export = TRUE) {
use_dependency("magrittr", "Imports")

if (export) {
use_template("pipe.R", "R/utils-pipe.R") && roxygen_update()
use_template("pipe.R", "R/utils-pipe.R") && roxygen_remind()
return(invisible(TRUE))
}

if (has_package_doc()) {
roxygen_ns_append("@importFrom magrittr %>%") && roxygen_update()
roxygen_ns_append("@importFrom magrittr %>%") && roxygen_remind()
return(invisible(TRUE))
}

Expand Down
6 changes: 3 additions & 3 deletions R/rcpp.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use_rcpp <- function(name = NULL) {

use_dependency("Rcpp", "LinkingTo")
use_dependency("Rcpp", "Imports")
roxygen_ns_append("@importFrom Rcpp sourceCpp") && roxygen_update()
roxygen_ns_append("@importFrom Rcpp sourceCpp") && roxygen_remind()

use_src_example_script(name, "cpp")

Expand Down Expand Up @@ -55,7 +55,7 @@ use_rcpp_eigen <- function(name = NULL) {

use_dependency("RcppEigen", "LinkingTo")

roxygen_ns_append("@import RcppEigen") && roxygen_update()
roxygen_ns_append("@import RcppEigen") && roxygen_remind()

invisible()
}
Expand All @@ -77,7 +77,7 @@ use_src <- function() {
use_directory("src")
use_git_ignore(c("*.o", "*.so", "*.dll"), "src")
roxygen_ns_append(glue("@useDynLib {project_name()}, .registration = TRUE")) &&
roxygen_update()
roxygen_remind()

invisible()
}
Expand Down
19 changes: 17 additions & 2 deletions R/roxygen.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,30 @@ roxygen_ns_append <- function(tag) {
path = proj_path(package_doc_path()),
block_start = "## usethis namespace: start",
block_end = "## usethis namespace: end",
block_suffix = "NULL"
block_suffix = "NULL",
sort = TRUE
)
}
roxygen_ns_show <- function(tag) {
block_show(
path = proj_path(package_doc_path()),
block_start = "## usethis namespace: start",
block_end = "## usethis namespace: end"
)
}

roxygen_update <- function() {
roxygen_remind <- function() {
ui_todo("Run {ui_code('devtools::document()')} to update {ui_path('NAMESPACE')}")
TRUE
}

roxygen_update_ns <- function() {
ui_done("Writing {ui_path('NAMESPACE')}")
utils::capture.output(
suppressMessages(roxygen2::roxygenise(proj_get(), "namespace"))
)
TRUE
}

# Checkers ----------------------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion R/tibble.R
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use_tibble <- function() {
check_uses_roxygen("use_tibble()")

use_dependency("tibble", "Imports")
roxygen_ns_append("@importFrom tibble tibble") && roxygen_update()
roxygen_ns_append("@importFrom tibble tibble") && roxygen_remind()

ui_todo("Document a returned tibble like so:")
ui_code_block("#' @return a [tibble][tibble::tibble-package]", copy = FALSE)
Expand Down
79 changes: 79 additions & 0 deletions R/use_import_from.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#' Import a function from another package
#'
#' @description
#' `use_import_from()` imports a function from another package by adding the
#' roxygen2 `@importFrom` tag to the package-level documentation (which can be
#' created with [`use_package_doc()`]). Importing a function from another
#' package allows you to refer to it without a namespace (e.g., `fun()` instead
#' of `package::fun()`).
#'
#' `use_import_from()` also re-documents the NAMESPACE, and re-load the current
#' package. This ensures that `fun` is immediately available in your development
#' session.
#'
#' @param package Package name
#' @param fun A vector of function names
#' @param load Logical. Re-load with [`pkgload::load_all()`]?
#' @return
#' Invisibly, `TRUE` if the package document has changed, `FALSE` if not.
#' @export
#' @examples
#' \dontrun{
#' use_import_from("usethis", "ui_todo")
#' }
use_import_from <- function(package, fun, load = is_interactive()) {
if (!is_string(package)) {
ui_stop("{ui_code('package')} must be a single string")
}
check_is_package("use_import_from()")
check_uses_roxygen("use_import_from()")

if (!check_has_package_doc()) {
return(invisible(FALSE))
}
purrr::walk2(package, fun, check_fun_exists)

use_dependency(package, "Imports")
changed <- roxygen_ns_append(glue("@importFrom {package} {fun}"))

if (changed) {
roxygen_update_ns()

if (load) {
ui_done("Loading {project_name()}")
pkgload::load_all(quiet = TRUE)
}
}

invisible(changed)
}

check_fun_exists <- function(package, fun) {
if (exists(fun, envir = asNamespace(package))) {
return()
}
name <- paste0(package, "::", fun, "()")
ui_stop("Can't find {ui_code(name)}")
}

check_has_package_doc <- function() {
if (has_package_doc()) {
return(invisible(TRUE))
}

if (!is_interactive()) {
return(invisible(FALSE))
}

if (ui_yeah("
{ui_code('use_import_from()')} requires \\
package-level documentation. Would you like to add \\
it now?")) {
use_package_doc()
} else {
ui_todo("Run {ui_code('use_package_doc()')}")
return(invisible(FALSE))
}

invisible(TRUE)
}
2 changes: 0 additions & 2 deletions R/usethis-package.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
#' @import rlang
"_PACKAGE"

# The following block is used by usethis to automatically manage
# roxygen namespace tags. Modify with care!
## usethis namespace: start
#' @importFrom lifecycle deprecated
## usethis namespace: end
Expand Down
2 changes: 0 additions & 2 deletions inst/templates/packagename-package.R
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#' @keywords internal
"_PACKAGE"

# The following block is used by usethis to automatically manage
# roxygen namespace tags. Modify with care!
## usethis namespace: start
## usethis namespace: end
NULL
34 changes: 34 additions & 0 deletions man/use_import_from.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions tests/testthat/_snaps/use_import_from.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# use_import_from() adds one line for each function

Code
roxygen_ns_show()
Output
[1] "#' @importFrom tibble deframe" "#' @importFrom tibble enframe"
[3] "#' @importFrom tibble tibble"

# use_import_from() generates helpful errors

Code
use_import_from(1)
Error <usethis_error>
`package` must be a single string
Code
use_import_from(c("tibble", "rlang"))
Error <usethis_error>
`package` must be a single string
Code
use_import_from("tibble", "pool_noodle")
Error <usethis_error>
Can't find `tibble::pool_noodle()`

18 changes: 18 additions & 0 deletions tests/testthat/test-block.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
test_that("block_append() only writes unique lines", {

path <- withr::local_tempfile()
writeLines(block_create(), path)

block_append("---", c("x", "y"), path)
block_append("---", c("y", "x"), path)
expect_equal(block_show(path), c("x", "y"))
})

test_that("block_append() can sort, if requested", {
path <- withr::local_tempfile()
writeLines(block_create(), path)

block_append("---", c("z", "y"), path)
block_append("---", "x", path, sort = TRUE)
expect_equal(block_show(path), c("x", "y", "z"))
})
28 changes: 28 additions & 0 deletions tests/testthat/test-use_import_from.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
test_that("use_import_from() imports the related package & adds line to package doc", {
create_local_package()
use_package_doc()
use_import_from("tibble", "tibble")

expect_equal(trimws(desc::desc_get("Imports", proj_get()))[[1]], "tibble")
expect_equal(roxygen_ns_show(), "#' @importFrom tibble tibble")
})

test_that("use_import_from() adds one line for each function", {
create_local_package()
use_package_doc()
use_import_from("tibble", c("tibble", "enframe", "deframe"))

expect_snapshot(roxygen_ns_show())
})

test_that("use_import_from() generates helpful errors", {
create_local_package()
use_package_doc()

expect_snapshot(error = TRUE, {
use_import_from(1)
use_import_from(c("tibble", "rlang"))

use_import_from("tibble", "pool_noodle")
})
})

0 comments on commit 4819d19

Please sign in to comment.