Skip to content

Commit

Permalink
Resolve markdown links to external packages
Browse files Browse the repository at this point in the history
Closes #1612.
  • Loading branch information
gaborcsardi committed Jul 9, 2024
1 parent 12c3555 commit ab07b85
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 8 deletions.
67 changes: 61 additions & 6 deletions R/markdown-link.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,27 @@
#' ```
#' MARKDOWN LINK TEXT CODE RD
#' -------- --------- ---- --
#' [fun()] fun() T \\link[=fun]{fun()}
#' [obj] obj F \\link{obj}
#' [fun()] fun() T \\link[=fun]{fun()} or
#' \\link[pkg::file]{fun()}
#' [obj] obj F \\link{obj} or
#' \\link[pkg::obj]{obj}
#' [pkg::fun()] pkg::fun() T \\link[pkg:file]{pkg::fun()}
#' [pkg::obj] pkg::obj F \\link[pkg:file]{pkg::obj}
#' [text][fun()] text F \\link[=fun]{text}
#' [text][obj] text F \\link[=obj]{text}
#' [text][fun()] text F \\link[=fun]{text} or
#' \\link[pkg:file]{text}
#' [text][obj] text F \\link[=obj]{text} or
#' \\link[pkg::file]{text}
#' [text][pkg::fun()] text F \\link[pkg:file]{text}
#' [text][pkg::obj] text F \\link[pkg:file]{text}
#' [s4-class] s4 F \\linkS4class{s4}
#' [s4-class] s4 F \\linkS4class{s4} or
#' \\link[pkg:file]{s4}
#' [pkg::s4-class] pkg::s4 F \\link[pkg:file]{pkg::s4}
#' ```
#'
#' For the ones that have two RD variants the first version is used for
#' within-package links, and the second version is used for cross-package
#' links.
#'
#' The reference links will always look like `R:ref` for `[ref]` and
#' `[text][ref]`. These are explicitly tested in `test-rd-markdown-links.R`.
#'
Expand Down Expand Up @@ -131,13 +140,15 @@ parse_link <- function(destination, contents, state) {
is_code <- is_code || (grepl("[(][)]$", destination) && ! has_link_text)
pkg <- str_match(destination, "^(.*)::")[1,2]
pkg <- gsub("%", "\\\\%", pkg)
if (!is.na(pkg) && pkg == thispkg) pkg <- NA_character_
fun <- utils::tail(strsplit(destination, "::", fixed = TRUE)[[1]], 1)
fun <- gsub("%", "\\\\%", fun)
is_fun <- grepl("[(][)]$", fun)
obj <- sub("[(][)]$", "", fun)
s4 <- str_detect(destination, "-class$")
noclass <- str_match(fun, "^(.*)-class$")[1,2]

if (is.na(pkg)) pkg <- resolve_link_package(obj, thispkg)
if (!is.na(pkg) && pkg == thispkg) pkg <- NA_character_
file <- find_topic_filename(pkg, obj, state$tag)

## To understand this, look at the RD column of the table above
Expand Down Expand Up @@ -174,6 +185,50 @@ parse_link <- function(destination, contents, state) {
}
}

resolve_link_package <- function(topic, me = NULL, pkgdir = NULL) {
me <- me %||% roxy_meta_get("current_package")
# this is probably from the roxygen2 tests
if (me == "") return(NA_character_)

# if it is in the current package, then no need for package name, right?
if (has_topic(topic, me)) return(NA_character_)

Check warning on line 194 in R/markdown-link.R

View check run for this annotation

Codecov / codecov/patch

R/markdown-link.R#L194

Added line #L194 was not covered by tests

# otherwise check depends, imports, suggests and base packages
pkgdir <- pkgdir %||% roxy_meta_get("current_package_dir")
base <- base_packages()
deps <- desc::desc_get_deps(pkgdir)
deps <- deps[deps$package != "R", ]
deps <- deps[deps$type %in% c("Depeneds", "Imports", "Suggests"), ]
pkgs <- deps$package

Check warning on line 202 in R/markdown-link.R

View check run for this annotation

Codecov / codecov/patch

R/markdown-link.R#L197-L202

Added lines #L197 - L202 were not covered by tests

pkg_has_topic <- pkgs[map_lgl(pkgs, has_topic, topic = topic)]
if (length(pkg_has_topic) == 1) {
if (pkg_has_topic %in% base_packages()) {
return(NA_character_)

Check warning on line 207 in R/markdown-link.R

View check run for this annotation

Codecov / codecov/patch

R/markdown-link.R#L204-L207

Added lines #L204 - L207 were not covered by tests
} else {
return(pkg_has_topic)

Check warning on line 209 in R/markdown-link.R

View check run for this annotation

Codecov / codecov/patch

R/markdown-link.R#L209

Added line #L209 was not covered by tests
}
} else if (length(pkg_has_topic) > 1) {
cli::cli_abort(c(
"Topic {.val {topic}} is available in multiple packages: {.pkg {pkgs}}.",
i = "Qualify topic explicitly with a package name."
))

Check warning on line 215 in R/markdown-link.R

View check run for this annotation

Codecov / codecov/patch

R/markdown-link.R#L211-L215

Added lines #L211 - L215 were not covered by tests
}

# try base packages as well
for (bp in base) {
if (has_topic(topic, bp)) return(NA_character_)

Check warning on line 220 in R/markdown-link.R

View check run for this annotation

Codecov / codecov/patch

R/markdown-link.R#L219-L220

Added lines #L219 - L220 were not covered by tests
}

cli::cli_abort(c(
"Could not resolve link to topic {.val {topic}} in the dependent
and base packages.",
"i" = "Make sure that the name of the topic is spelled correctly.",
"i" = "Always list the linked package as a dependency.",
"i" = "Alternatively, you can fully qualify the link with a package name."
))

Check warning on line 229 in R/markdown-link.R

View check run for this annotation

Codecov / codecov/patch

R/markdown-link.R#L223-L229

Added lines #L223 - L229 were not covered by tests
}

#' Dummy page to test roxygen's markdown formatting
#'
#' Links are very tricky, so I'll put in some links here:
Expand Down
2 changes: 2 additions & 0 deletions R/options.R
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ load_options <- function(base_path = ".") {
markdown = FALSE,
r6 = TRUE,
current_package = NA_character_,
current_package_dir = NA_character_,
rd_family_title = list(),
knitr_chunk_options = NULL,
restrict_image_formats = TRUE
Expand Down Expand Up @@ -94,6 +95,7 @@ load_options_description <- function(base_path = ".") {
}

opts$current_package <- dcf[[1, 2]]
opts$current_package_dir <- normalizePath(base_path)
opts
}

Expand Down
11 changes: 11 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,14 @@ auto_quote <- function(x) {
is_syntactic <- function(x) make.names(x) == x
has_quotes <- function(x) str_detect(x, "^(`|'|\").*\\1$")
strip_quotes <- function(x) str_replace(x, "^(`|'|\")(.*)\\1$", "\\2")

base_packages <- function() {
if (getRversion() >= "4.4.0") {
tools::standard_package_names()[["base"]]

Check warning on line 188 in R/utils.R

View check run for this annotation

Codecov / codecov/patch

R/utils.R#L187-L188

Added lines #L187 - L188 were not covered by tests
} else {
c("base", "compiler", "datasets",
"graphics", "grDevices", "grid", "methods", "parallel",
"splines", "stats", "stats4", "tcltk", "tools", "utils"
)

Check warning on line 193 in R/utils.R

View check run for this annotation

Codecov / codecov/patch

R/utils.R#L190-L193

Added lines #L190 - L193 were not covered by tests
}
}
1 change: 0 additions & 1 deletion tests/testthat/test-rd-describe-in.R
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,3 @@ test_that("complains about bad usage", {
"
expect_snapshot(. <- roc_proc_text(rd_roclet(), block))
})

3 changes: 3 additions & 0 deletions tests/testthat/test-rd-include-rmd.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
skip_if_not_installed("rmarkdown")

# clear some state
roxy_meta_clear()

test_that("invalid syntax gives useful warning", {
block <- "
#' @includeRmd
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/testRawNamespace/DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ Author: Hadley <hadley@rstudio.com>
Maintainer: Hadley <hadley@rstudio.com>
Encoding: UTF-8
Version: 0.1
RoxygenNote: 7.3.0.9000
RoxygenNote: 7.3.2.9000

0 comments on commit ab07b85

Please sign in to comment.