From 1e078c1bdad239c8d8e8945db383f11ee474ffb2 Mon Sep 17 00:00:00 2001 From: Hadley Wickham Date: Fri, 24 May 2024 10:59:04 -0500 Subject: [PATCH] Extract lifecycle badges and display on reference index (#2579) Fixes #2123 --- NEWS.md | 1 + R/autolink_html.R | 5 +- R/build-reference.R | 2 + R/package.R | 41 +++++- R/topics-external.R | 3 +- R/topics.R | 5 + inst/BS5/assets/pkgdown.scss | 10 ++ .../templates/content-reference-index.html | 1 + man/autolink_html.Rd | 4 +- man/build_reference.Rd | 2 + pkgdown/_pkgdown.yml | 4 + po/R-pkgdown.pot | 134 +++++++++++------- .../testthat/_snaps/build-reference-index.md | 14 ++ tests/testthat/_snaps/topics-external.md | 26 ++-- tests/testthat/test-package.R | 16 +++ tests/testthat/test-topics.R | 16 ++- 16 files changed, 214 insertions(+), 70 deletions(-) diff --git a/NEWS.md b/NEWS.md index c778abd70..947cf3e70 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # pkgdown (development version) +* `build_reference_index()` now displays lifecycle badges next to the function name (#2123). You can now also use `has_lifecycle()` to select functions by their lifecycle status. * `build_articles()` now recognises a new `external-articles` top-level field that allows you to define articles that live in other packages (#2028). * New light switch makes it easy for users to switch between light and dark themes for the website (based on work in bslib by @gadenbuie). For now this behaviour is opt-in with `template.light-switch: true` but in the future we may turn it on automatically. See the customization vignette for details (#1696). * The search dropdown has been tweaked to look more like the other navbar menu items (#2338). diff --git a/R/autolink_html.R b/R/autolink_html.R index 7ff2709d9..a03c5ebcd 100644 --- a/R/autolink_html.R +++ b/R/autolink_html.R @@ -1,6 +1,9 @@ #' Automatically link references and articles in an HTML page #' -#' Deprecated: please use [downlit::downlit_html_path] instead. +#' @description +#' `r lifecycle::badge("deprecated")` +#' +#' Please use [downlit::downlit_html_path] instead. #' #' @param input,output Input and output paths for HTML file #' @param local_packages A named character vector providing relative paths diff --git a/R/build-reference.R b/R/build-reference.R index 9d5f94664..6ae346c00 100644 --- a/R/build-reference.R +++ b/R/build-reference.R @@ -80,6 +80,8 @@ #' captured by `has_concepts()`. #' * Topics from other installed packages, e.g. `rlang::is_installed()` (function name) #' or `sass::font_face` (topic name). +#' * `has_lifecycle("deprecated")` will select all topics with lifecycle +#' deprecated. #' #' All functions (except for `has_keywords()`) automatically exclude internal #' topics (i.e. those with `\keyword{internal}`). You can choose to include diff --git a/R/package.R b/R/package.R index aba4d02a4..baa61970e 100644 --- a/R/package.R +++ b/R/package.R @@ -217,6 +217,7 @@ package_topics <- function(path = ".", package = "pkgdown") { keywords <- unname(purrr::map(rd, extract_tag, "tag_keyword")) internal <- purrr::map_lgl(keywords, ~ "internal" %in% .) source <- purrr::map(rd, extract_source) + lifecycle <- unname(purrr::map(rd, extract_lifecycle)) file_in <- names(rd) file_out <- rd_output_path(file_in) @@ -234,7 +235,8 @@ package_topics <- function(path = ".", package = "pkgdown") { source = source, keywords = keywords, concepts = concepts, - internal = internal + internal = internal, + lifecycle = lifecycle ) } @@ -285,6 +287,43 @@ extract_source <- function(x) { regmatches(text, m)[[1]] } +extract_lifecycle <- function(x) { + fig <- extract_figure(x) + if (!is.null(fig) && length(fig) > 0 && length(fig[[1]]) > 0) { + path <- as.character(fig[[1]][[1]]) + if (grepl("lifecycle", path)) { + name <- gsub("lifecycle-", "", path) + name <- path_ext_remove(name) + + # Translate the most common lifecylce names + name <- switch(name, + deprecated = tr_("deprecated"), + superseded = tr_("superseded"), + experimental = tr_("experimental"), + stable = tr_("stable"), + name + ) + + return(name) + } + } + NULL +} + +extract_figure <- function(elements) { + for (element in elements) { + if (inherits(element, "tag_figure")) { + return(element) + } else if (inherits(element, "tag")) { + child <- extract_figure(element) + if (!is.null(child)) { + return(child) + } + } + } + NULL +} + # Vignettes --------------------------------------------------------------- package_vignettes <- function(path = ".") { diff --git a/R/topics-external.R b/R/topics-external.R index 0bcdd9ae9..9c873f105 100644 --- a/R/topics-external.R +++ b/R/topics-external.R @@ -21,7 +21,8 @@ ext_topics <- function(match_strings) { source = NA_character_, keywords = list(character()), # used for has_keyword() concepts = list(character()), # used for has_concept() - internal = FALSE + internal = FALSE, + lifecycle = list(NULL) # used for has_lifecycle ) } diff --git a/R/topics.R b/R/topics.R index a26631240..b43c75091 100644 --- a/R/topics.R +++ b/R/topics.R @@ -132,6 +132,10 @@ match_env <- function(topics) { check_character(x) which(purrr::map_lgl(topics$keywords, ~ any(. %in% x))) } + fns$has_lifecycle <- function(x) { + check_string(x) + which(purrr::map_lgl(topics$lifecycle, ~ any(. %in% x))) + } fns$has_concept <- function(x, internal = FALSE) { check_string(x) check_bool(internal) @@ -224,6 +228,7 @@ section_topics <- function(match_strings, topics, src_path) { name = selected$name, path = selected$file_out, title = selected$title, + lifecycle = selected$lifecycle, aliases = purrr::map2(selected$funs, selected$alias, ~ if (length(.x) > 0) .x else .y), icon = find_icons(selected$alias, path(src_path, "icons")) ) diff --git a/inst/BS5/assets/pkgdown.scss b/inst/BS5/assets/pkgdown.scss index c2dfb96c8..5e0a32531 100644 --- a/inst/BS5/assets/pkgdown.scss +++ b/inst/BS5/assets/pkgdown.scss @@ -345,6 +345,16 @@ a[href='#main'] { z-index: 2000; } +.lifecycle { + color: var(--bs-secondary-color); + background-color: var(--bs-secondary-bg); // backup just in case we don't know the name + border-radius: 5px; +} +.lifecycle-stable { background-color: rgb(16, 128, 1); color: var(--bs-white);} +.lifecycle-superseded { background-color: rgb(7, 64, 128); color: var(--bs-white);} +.lifecycle-experimental, +.lifecycle-deprecated { background-color: rgb(253, 128, 8); color: var(--bs-black);} + /* Footnotes ---------------------------------------------------------------- */ a.footnote-ref { diff --git a/inst/BS5/templates/content-reference-index.html b/inst/BS5/templates/content-reference-index.html index 3cacfc730..d9a981066 100644 --- a/inst/BS5/templates/content-reference-index.html +++ b/inst/BS5/templates/content-reference-index.html @@ -14,6 +14,7 @@

{{{pagetitle}}}

{{#has_icons}}{{#icon}}{{/icon}}{{/has_icons}} {{#aliases}}{{{.}}} {{/aliases}} + {{#lifecycle}}{{.}}{{/lifecycle}}
{{{title}}}
{{/topics}} diff --git a/man/autolink_html.Rd b/man/autolink_html.Rd index 0aae23e7f..0bed63ca7 100644 --- a/man/autolink_html.Rd +++ b/man/autolink_html.Rd @@ -14,7 +14,9 @@ autolink_html(input, output = input, local_packages = character()) from the target HTML document.} } \description{ -Deprecated: please use \link[downlit:downlit_html_path]{downlit::downlit_html_path} instead. +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} + +Please use \link[downlit:downlit_html_path]{downlit::downlit_html_path} instead. } \examples{ \dontrun{ diff --git a/man/build_reference.Rd b/man/build_reference.Rd index 980512c70..064a413fb 100644 --- a/man/build_reference.Rd +++ b/man/build_reference.Rd @@ -129,6 +129,8 @@ without those concepts. This is useful to capture topics not otherwise captured by \code{has_concepts()}. \item Topics from other installed packages, e.g. \code{rlang::is_installed()} (function name) or \code{sass::font_face} (topic name). +\item \code{has_lifecycle("deprecated")} will select all topics with lifecycle +deprecated. } All functions (except for \code{has_keywords()}) automatically exclude internal diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 5aa1ae809..cc3a5f52e 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -83,6 +83,10 @@ reference: contents: - starts_with("test", internal = TRUE) +- title: Deprecated functions + contents: + - has_lifecycle("deprecated") + redirects: - ["articles/search.html", "reference/build_search.html"] diff --git a/po/R-pkgdown.pot b/po/R-pkgdown.pot index 533a48959..d886b2c46 100644 --- a/po/R-pkgdown.pot +++ b/po/R-pkgdown.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: pkgdown 2.0.9.9000\n" -"POT-Creation-Date: 2024-05-10 10:43-0500\n" +"POT-Creation-Date: 2024-05-24 09:36-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,19 +18,19 @@ msgstr "" msgid "Content not found. Please use links in the navbar." msgstr "" -#: build-articles.R:411 navbar.R:163 navbar.R:170 navbar.R:185 +#: build-articles.R:432 navbar.R:186 navbar.R:196 navbar.R:217 msgid "Articles" msgstr "" -#: build-articles.R:462 +#: build-articles.R:570 msgid "All vignettes" msgstr "" -#: build-footer.R:23 +#: build-footer.R:34 msgid "Developed by" msgstr "" -#: build-footer.R:28 +#: build-footer.R:41 msgid "Site built with pkgdown %s." msgstr "" @@ -38,63 +38,63 @@ msgstr "" msgid "Authors and Citation" msgstr "" -#: build-home-authors.R:96 +#: build-home-authors.R:101 msgid "More about authors..." msgstr "" -#: build-home-authors.R:99 +#: build-home-authors.R:104 msgid "Developers" msgstr "" -#: build-home-authors.R:185 +#: build-home-authors.R:190 msgid "author" msgstr "" -#: build-home-authors.R:186 +#: build-home-authors.R:191 msgid "compiler" msgstr "" -#: build-home-authors.R:187 +#: build-home-authors.R:192 msgid "contractor" msgstr "" -#: build-home-authors.R:188 +#: build-home-authors.R:193 msgid "contributor" msgstr "" -#: build-home-authors.R:189 +#: build-home-authors.R:194 msgid "copyright holder" msgstr "" -#: build-home-authors.R:190 +#: build-home-authors.R:195 msgid "maintainer" msgstr "" -#: build-home-authors.R:191 +#: build-home-authors.R:196 msgid "data contributor" msgstr "" -#: build-home-authors.R:192 +#: build-home-authors.R:197 msgid "funder" msgstr "" -#: build-home-authors.R:193 +#: build-home-authors.R:198 msgid "reviewer" msgstr "" -#: build-home-authors.R:194 +#: build-home-authors.R:199 msgid "thesis advisor" msgstr "" -#: build-home-authors.R:195 +#: build-home-authors.R:200 msgid "translator" msgstr "" -#: build-home-authors.R:249 render.R:101 +#: build-home-authors.R:254 render.R:103 msgid "Citation" msgstr "" -#: build-home-authors.R:250 +#: build-home-authors.R:255 msgid "Citing %s" msgstr "" @@ -114,27 +114,27 @@ msgstr "" msgid "Community" msgstr "" -#: build-home-index.R:88 +#: build-home-index.R:120 msgid "Dev Status" msgstr "" -#: build-home-index.R:161 +#: build-home-index.R:162 msgid "View on %s" msgstr "" -#: build-home-index.R:162 +#: build-home-index.R:163 msgid "Browse source code" msgstr "" -#: build-home-index.R:163 +#: build-home-index.R:164 msgid "Report a bug" msgstr "" -#: build-home-index.R:167 +#: build-home-index.R:168 msgid "Links" msgstr "" -#: build-home-index.R:172 render.R:103 +#: build-home-index.R:173 render.R:105 msgid "Table of contents" msgstr "" @@ -146,23 +146,23 @@ msgstr "" msgid "Full license" msgstr "" -#: build-news.R:101 build-news.R:259 build-news.R:264 +#: build-news.R:102 build-news.R:259 build-news.R:263 msgid "Changelog" msgstr "" -#: build-news.R:128 +#: build-news.R:129 msgid "Version %s" msgstr "" -#: build-news.R:140 build-news.R:253 +#: build-news.R:141 build-news.R:254 msgid "News" msgstr "" -#: build-news.R:255 +#: build-news.R:256 msgid "Releases" msgstr "" -#: build-news.R:311 +#: build-news.R:310 msgid "CRAN release: %s" msgstr "" @@ -170,15 +170,15 @@ msgstr "" msgid "Package index" msgstr "" -#: build-reference-index.R:127 +#: build-reference-index.R:158 msgid "All functions" msgstr "" -#: build-reference.R:369 +#: build-reference.R:371 msgid "Usage" msgstr "" -#: build-tutorials.R:47 navbar.R:129 +#: build-tutorials.R:47 navbar.R:152 msgid "Tutorials" msgstr "" @@ -194,24 +194,56 @@ msgstr "" msgid "Unreleased version" msgstr "" -#: navbar.R:121 +#: navbar-menu.R:173 +msgid "Search site" +msgstr "" + +#: navbar-menu.R:174 +msgid "Search for" +msgstr "" + +#: navbar.R:129 msgid "Reference" msgstr "" -#: navbar.R:157 +#: navbar.R:140 +msgid "Light switch" +msgstr "" + +#: navbar.R:143 +msgid "Light" +msgstr "" + +#: navbar.R:144 +msgid "Dark" +msgstr "" + +#: navbar.R:145 +msgid "Auto" +msgstr "" + +#: navbar.R:180 msgid "Get started" msgstr "" -#: navbar.R:183 +#: navbar.R:213 msgid "More articles..." msgstr "" -#: navbar.R:224 -msgid "Search site" +#: package.R:300 +msgid "deprecated" msgstr "" -#: navbar.R:225 -msgid "Search for" +#: package.R:301 +msgid "superseded" +msgstr "" + +#: package.R:302 +msgid "experimental" +msgstr "" + +#: package.R:303 +msgid "stable" msgstr "" #: rd-data.R:23 @@ -226,7 +258,7 @@ msgstr "" msgid "References" msgstr "" -#: rd-data.R:35 render.R:96 +#: rd-data.R:35 render.R:98 msgid "Source" msgstr "" @@ -254,39 +286,39 @@ msgstr "" msgid "Value" msgstr "" -#: render.R:93 +#: render.R:95 msgid "Skip to contents" msgstr "" -#: render.R:94 +#: render.R:96 msgid "Toggle navigation" msgstr "" -#: render.R:95 +#: render.R:97 msgid "On this page" msgstr "" -#: render.R:97 +#: render.R:99 msgid "Abstract" msgstr "" -#: render.R:98 +#: render.R:100 msgid "Authors" msgstr "" -#: render.R:99 +#: render.R:101 msgid "Version" msgstr "" -#: render.R:100 +#: render.R:102 msgid "Examples" msgstr "" -#: render.R:102 +#: render.R:104 msgid "Additional details" msgstr "" -#: render.R:104 +#: render.R:106 msgid "Site navigation" msgstr "" diff --git a/tests/testthat/_snaps/build-reference-index.md b/tests/testthat/_snaps/build-reference-index.md index ec11c0118..3554d0a65 100644 --- a/tests/testthat/_snaps/build-reference-index.md +++ b/tests/testthat/_snaps/build-reference-index.md @@ -16,22 +16,27 @@ - topics: - path: a.html title: A + lifecycle: ~ aliases: a() icon: ~ - path: b.html title: B + lifecycle: ~ aliases: b() icon: ~ - path: c.html title: C + lifecycle: ~ aliases: c() icon: ~ - path: e.html title: E + lifecycle: ~ aliases: e icon: ~ - path: help.html title: D + lifecycle: ~ aliases: '`?`()' icon: ~ names: @@ -143,32 +148,39 @@ - topics: - path: a.html title: A + lifecycle: ~ aliases: a() icon: ~ - path: b.html title: B + lifecycle: ~ aliases: b() icon: ~ - path: c.html title: C + lifecycle: ~ aliases: c() icon: ~ - path: e.html title: E + lifecycle: ~ aliases: e icon: ~ - path: help.html title: D + lifecycle: ~ aliases: '`?`()' icon: ~ - path: https://rlang.r-lib.org/reference/is_installed.html title: Are packages installed in any of the libraries? (from rlang) + lifecycle: ~ aliases: - is_installed() - check_installed() icon: ~ - path: https://rdrr.io/pkg/bslib/man/bs_bundle.html title: Add low-level theming customizations (from bslib) + lifecycle: ~ aliases: - bs_add_variables() - bs_add_rules() @@ -203,10 +215,12 @@ - topics: - path: matches.html title: matches + lifecycle: ~ aliases: matches() icon: ~ - path: A.html title: A + lifecycle: ~ aliases: A() icon: ~ names: diff --git a/tests/testthat/_snaps/topics-external.md b/tests/testthat/_snaps/topics-external.md index dd07cdb31..87f333c56 100644 --- a/tests/testthat/_snaps/topics-external.md +++ b/tests/testthat/_snaps/topics-external.md @@ -3,23 +3,25 @@ Code str(ext_topics("base::mean")) Output - tibble [1 x 11] (S3: tbl_df/tbl/data.frame) - $ name : chr "base::mean" - $ file_in : chr NA - $ file_out: chr "https://rdrr.io/r/base/mean.html" - $ alias :List of 1 + tibble [1 x 12] (S3: tbl_df/tbl/data.frame) + $ name : chr "base::mean" + $ file_in : chr NA + $ file_out : chr "https://rdrr.io/r/base/mean.html" + $ alias :List of 1 ..$ : chr(0) - $ funs :List of 1 + $ funs :List of 1 ..$ : chr "mean()" - $ title : chr "Arithmetic Mean (from base)" - $ rd :List of 1 + $ title : chr "Arithmetic Mean (from base)" + $ rd :List of 1 ..$ : chr(0) - $ source : chr NA - $ keywords:List of 1 + $ source : chr NA + $ keywords :List of 1 ..$ : chr(0) - $ concepts:List of 1 + $ concepts :List of 1 ..$ : chr(0) - $ internal: logi FALSE + $ internal : logi FALSE + $ lifecycle:List of 1 + ..$ : NULL # fails if documentation not available diff --git a/tests/testthat/test-package.R b/tests/testthat/test-package.R index a016207a9..5a262a6d1 100644 --- a/tests/testthat/test-package.R +++ b/tests/testthat/test-package.R @@ -95,3 +95,19 @@ test_that("read_meta() errors gracefully if _pkgdown.yml failed to parse", { transform = function(x) gsub(pkg$src_path, "", x, fixed = TRUE) ) }) + +# lifecycle --------------------------------------------------------------- + +test_that("can extract lifecycle badges", { + expect_equal( + extract_lifecycle(rd_text(lifecycle::badge("deprecated"))), + "deprecated" + ) +}) + +test_that("malformed figures fail gracefully", { + rd_lifecycle <- function(x) extract_lifecycle(rd_text(x)) + + expect_null(rd_lifecycle("{\\figure{deprecated.svg}}")) + expect_null(rd_lifecycle("{\\figure{}}")) +}) diff --git a/tests/testthat/test-topics.R b/tests/testthat/test-topics.R index 00222a433..ee8be76f6 100644 --- a/tests/testthat/test-topics.R +++ b/tests/testthat/test-topics.R @@ -119,6 +119,16 @@ test_that("can select by keyword", { expect_equal(select_topics("has_keyword('c')", topics), integer()) }) +test_that("can select by lifecycle", { + topics <- tibble::tribble( + ~name, ~alias, ~internal, ~keywords, ~lifecycle, + "b1", "b1", FALSE, "a", list("stable"), + "b2", "b2", FALSE, c("a", "b"), NULL + ) + expect_equal(select_topics("has_lifecycle('stable')", topics), 1) + expect_equal(select_topics("has_lifecycle('deprecated')", topics), integer()) +}) + test_that("can combine positive and negative selections", { topics <- tibble::tribble( ~name, ~alias, ~internal, @@ -152,9 +162,9 @@ test_that("an unmatched selection generates a warning", { test_that("uses funs or aliases", { topics <- tibble::tribble( - ~name, ~funs, ~alias, ~file_out, ~title, - "x", character(), c("x1", "x2"), "x.html", "X", - "y", c("y1", "y2"), "y3", "y.html", "Y" + ~name, ~funs, ~alias, ~file_out, ~title, ~lifecycle, + "x", character(), c("x1", "x2"), "x.html", "X", NULL, + "y", c("y1", "y2"), "y3", "y.html", "Y", NULL ) out <- section_topics(c("x", "y"), topics, ".")