Skip to content

Commit

Permalink
Add support for "external" articles (#2576)
Browse files Browse the repository at this point in the history
Fixes #2028
  • Loading branch information
hadley authored May 24, 2024
1 parent ba2596f commit be8bb7d
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 49 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pkgdown (development version)

* `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).
* `vignette("search")` has been removed since BS3 is deprecated and all the BS5 docs are also included in `build_search()` (#2564).
Expand Down
143 changes: 110 additions & 33 deletions R/build-articles.R
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
#' the navbar, it will link directly to the articles index instead of
#' providing a drop-down.
#'
#' # Get started
#' ## Get started
#' Note that a vignette with the same name as the package (e.g.,
#' `vignettes/pkgdown.Rmd` or `vignettes/articles/pkgdown.Rmd`) automatically
#' becomes a top-level "Get started" link, and will not appear in the articles
Expand All @@ -86,12 +86,29 @@
#' (If your package name includes a `.`, e.g. `pack.down`, use a `-` in the
#' vignette name, e.g. `pack-down.Rmd`.)
#'
#' ## Missing topics
#' ## Missing articles
#'
#' pkgdown will warn if there are (non-internal) articles that aren't listed
#' in the articles index. You can suppress such warnings by listing the
#' affected articles in a section with `title: internal` (case sensitive);
#' this section will not be displayed on the index page.
#'
#' ## External articles
#'
#' You can link to arbitrary additional articles by adding an
#' `external-articles` entry to `_pkgdown.yml`. It should contain an array
#' of objects with fields `name`, `title`, `href`, and `description`.
#'
#' ```yaml
#' external-articles:
#' - name: subsampling
#' title: Subsampling for Class Imbalances
#' description: Improve model performance in imbalanced data sets through undersampling or oversampling.
#' href: https://www.tidymodels.org/learn/models/sub-sampling/
#' ```
#'
#' If you've defined a custom articles index, you'll need to include the name
#' in one of the `contents` fields.
#'
#' # External files
#' pkgdown differs from base R in its handling of external files. When building
Expand Down Expand Up @@ -378,16 +395,16 @@ build_articles_index <- function(pkg = ".") {
data_articles_index <- function(pkg = ".", call = caller_env()) {
pkg <- as_pkgdown(pkg)

meta <- config_pluck_list(
pkg,
"articles",
default = default_articles_index(pkg),
articles <- data_articles(pkg, is_index = TRUE, call = call)
index <- config_pluck_list(pkg, "articles", call = call) %||%
default_articles_index(pkg)
sections <- unwrap_purrr_error(purrr::imap(
index,
data_articles_index_section,
articles = articles,
pkg = pkg,
call = call
)

sections <- unwrap_purrr_error(meta %>%
purrr::imap(data_articles_index_section, pkg = pkg, call = call) %>%
purrr::compact())
))

# Check for unlisted vignettes
listed <- sections %>%
Expand All @@ -396,7 +413,7 @@ data_articles_index <- function(pkg = ".", call = caller_env()) {
purrr::flatten_chr() %>%
unique()

missing <- setdiff(pkg$vignettes$name, listed)
missing <- setdiff(articles$name, listed)
# Exclude get started vignette or article #2150
missing <- missing[!article_is_intro(missing, package = pkg$package)]

Expand All @@ -417,10 +434,89 @@ data_articles_index <- function(pkg = ".", call = caller_env()) {
))
}

data_articles_index_section <- function(section, index, pkg, call = caller_env()) {
data_articles <- function(pkg = ".", is_index = FALSE, call = caller_env()) {
pkg <- as_pkgdown(pkg)

internal <- tibble::tibble(
name = pkg$vignettes$name,
title = pkg$vignettes$title,
href = pkg$vignettes$file_out,
description = pkg$vignettes$description,
)
if (is_index) {
internal$href <- path_rel(internal$href, "articles")
}

external <- config_pluck_external_articles(pkg, call = call)
articles <- rbind(internal, external)

articles$description <- lapply(articles$description, markdown_text_block)

# Hack data structure so we can use select_topics()
articles$alias <- as.list(articles$name)
articles$internal <- FALSE

articles
}

config_pluck_external_articles <- function(pkg, call = caller_env()) {
external <- config_pluck_list(pkg, "external-articles", call = call)
if (is.null(external)) {
return(tibble::tibble(
name = character(),
title = character(),
href = character(),
description = character()
))
}

for (i in seq_along(external)) {
config_check_list(
external[[i]],
has_names = c("name", "title", "href", "description"),
error_path = paste0("external-articles[", i, "]"),
error_pkg = pkg,
error_call = call
)
config_check_string(
external[[i]]$name,
error_path = paste0("external-articles[", i, "].name"),
error_pkg = pkg,
error_call = call
)
config_check_string(
external[[i]]$title,
error_path = paste0("external-articles[", i, "].title"),
error_pkg = pkg,
error_call = call
)
config_check_string(
external[[i]]$href,
error_path = paste0("external-articles[", i, "].href"),
error_pkg = pkg,
error_call = call
)
config_check_string(
external[[i]]$description,
error_path = paste0("external-articles[", i, "].description"),
error_pkg = pkg,
error_call = call
)
}

tibble::tibble(
name = purrr::map_chr(external, "name"),
title = purrr::map_chr(external, "title"),
href = purrr::map_chr(external, "href"),
description = purrr::map_chr(external, "description")
)
}

data_articles_index_section <- function(section, index, articles, pkg, call = caller_env()) {
config_check_list(
section,
error_path = paste0("articles[", index, "]"),
has_names = c("title", "contents"),
error_pkg = pkg,
error_call = call
)
Expand Down Expand Up @@ -452,15 +548,7 @@ data_articles_index_section <- function(section, index, pkg, call = caller_env()
)

# Match topics against any aliases
in_section <- select_vignettes(section$contents, pkg$vignettes)
section_vignettes <- pkg$vignettes[in_section, ]
contents <- tibble::tibble(
name = section_vignettes$name,
path = path_rel(section_vignettes$file_out, "articles"),
title = section_vignettes$title,
description = lapply(section_vignettes$description, markdown_text_block),
)

contents <- articles[select_topics(section$contents, articles), ]

list(
title = title,
Expand All @@ -470,17 +558,6 @@ data_articles_index_section <- function(section, index, pkg, call = caller_env()
)
}

# Quick hack: create the same structure as for topics so we can use
# the existing select_topics()
select_vignettes <- function(match_strings, vignettes) {
topics <- tibble::tibble(
name = vignettes$name,
alias = as.list(vignettes$name),
internal = FALSE
)
select_topics(match_strings, topics)
}

default_articles_index <- function(pkg = ".") {
pkg <- as_pkgdown(pkg)

Expand Down
13 changes: 7 additions & 6 deletions R/navbar.R
Original file line number Diff line number Diff line change
Expand Up @@ -187,24 +187,25 @@ navbar_articles <- function(pkg = ".") {
menu_links(vignettes$title, vignettes$file_out)
)
} else {
articles <- config_pluck(pkg, "articles")
articles_index <- config_pluck(pkg, "articles")
articles <- data_articles(pkg)

navbar <- purrr::keep(articles, ~ has_name(.x, "navbar"))
navbar <- purrr::keep(articles_index, ~ has_name(.x, "navbar"))
if (length(navbar) == 0) {
# No articles to be included in navbar so just link to index
menu$articles <- menu_link(tr_("Articles"), "articles/index.html")
} else {
sections <- lapply(navbar, function(section) {
vig <- pkg$vignettes[select_vignettes(section$contents, pkg$vignettes), , drop = FALSE]
vig <- articles[select_topics(section$contents, articles), , drop = FALSE]
vig <- vig[vig$name != pkg$package, , drop = FALSE]
c(
if (!is.null(section$navbar)) list(menu_separator(), menu_heading(section$navbar)),
menu_links(vig$title, vig$file_out)
menu_links(vig$title, vig$href)
)
})
children <- unlist(sections, recursive = FALSE, use.names = FALSE)

if (length(navbar) != length(articles)) {
if (length(navbar) != length(articles_index)) {
children <- c(
children,
list(
Expand Down Expand Up @@ -243,7 +244,7 @@ pkg_navbar_vignettes <- function(name = character(),
title <- title %||% paste0("Title ", name)
file_out <- file_out %||% paste0(name, ".html")

tibble::tibble(name = name, title = title, file_out)
tibble::tibble(name = name, title = title, file_out, description = "desc")
}


Expand Down
2 changes: 1 addition & 1 deletion R/package.R
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ package_vignettes <- function(path = ".") {
check_unique_article_paths(file_in, file_out)

out <- tibble::tibble(
name = path_ext_remove(vig_path),
name = as.character(path_ext_remove(vig_path)),
file_in = file_in,
file_out = file_out,
title = title,
Expand Down
2 changes: 1 addition & 1 deletion inst/BS3/templates/content-article-index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ <h3>{{{title}}}</h3>

<dl>
{{#contents}}
<dt><a href="{{path}}">{{title}}</a></dt>
<dt><a href="{{href}}">{{title}}</a></dt>
<dd>{{{description}}}</dt>
{{/contents}}
</dl>
Expand Down
2 changes: 1 addition & 1 deletion inst/BS5/templates/content-article-index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ <h3>{{{title}}}</h3>

<dl>
{{#contents}}
<dt><a href="{{path}}">{{title}}</a></dt>
<dt><a href="{{href}}">{{title}}</a></dt>
<dd>{{{description}}}</dt>
{{/contents}}
</dl>
Expand Down
24 changes: 21 additions & 3 deletions man/build_articles.Rd

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

57 changes: 55 additions & 2 deletions tests/testthat/_snaps/build-articles.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,20 @@
! articles[1] must be a list, not the number 1.
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_index_(list(list(title = 1)))
data_articles_index_(list(list()))
Condition
Error in `data_articles_index_()`:
! articles[1] must have components "title" and "contents".
2 missing components: "title" and "contents".
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_index_(list(list(title = 1, contents = 1)))
Condition
Error in `data_articles_index_()`:
! articles[1].title must be a string, not the number 1.
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_index_(list(list(title = "a\n\nb")))
data_articles_index_(list(list(title = "a\n\nb", contents = 1)))
Condition
Error in `data_articles_index_()`:
! articles[1].title must be inline markdown.
Expand All @@ -73,6 +80,52 @@
i You might need to add '' around special YAML values like 'N' or 'off'
i Edit _pkgdown.yml to fix the problem.

# validates external-articles

Code
data_articles_(1)
Condition
Error in `data_articles_()`:
! external-articles must be a list, not the number 1.
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_(list(1))
Condition
Error in `data_articles_()`:
! external-articles[1] must be a list, not the number 1.
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_(list(list(name = "x")))
Condition
Error in `data_articles_()`:
! external-articles[1] must have components "name", "title", "href", and "description".
3 missing components: "title", "href", and "description".
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_(list(list(name = 1, title = "x", href = "x", description = "x")))
Condition
Error in `data_articles_()`:
! external-articles[1].name must be a string, not the number 1.
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_(list(list(name = "x", title = 1, href = "x", description = "x")))
Condition
Error in `data_articles_()`:
! external-articles[1].title must be a string, not the number 1.
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_(list(list(name = "x", title = "x", href = 1, description = "x")))
Condition
Error in `data_articles_()`:
! external-articles[1].href must be a string, not the number 1.
i Edit _pkgdown.yml to fix the problem.
Code
data_articles_(list(list(name = "x", title = "x", href = "x", description = 1)))
Condition
Error in `data_articles_()`:
! external-articles[1].description must be a string, not the number 1.
i Edit _pkgdown.yml to fix the problem.

# articles in vignettes/articles/ are unnested into articles/

Code
Expand Down
Loading

0 comments on commit be8bb7d

Please sign in to comment.