Skip to content

Commit

Permalink
Assist with Quarto vignettes and articles (#2085)
Browse files Browse the repository at this point in the history
* Get use_vignette() working for qmd

* Another test

* Attempt to get "or" between 2 .vals

Approach taken from r-lib/cli#681 (comment)

* Catch up on the article side

* Add NEWS bullet

* Remove comment
  • Loading branch information
jennybc authored Nov 25, 2024
1 parent ee5b158 commit d7bf1cc
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 38 deletions.
7 changes: 5 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# usethis (development version)

* `use_vignette()` and `use_article()` now support Quarto. The `name` of the new
vignette or article can optionally include a file extension to signal whether
`.Rmd` or `.qmd` is desired, with `.Rmd` remaining the default for now. Thanks
to @olivroy for getting the ball rolling (#1997).

* `use_tidy_upkeep_issue()` now records the year it is being run in the
`Config/usethis/upkeep` field in DESCRIPTION. If this value exists it is
furthermore used to filter the checklist when making the issue.

## Bug fixes and minor improvements

* `use_package()` now decreases a package minimum version required when
`min_version` is lower than what is currently specified in the DESCRIPTION
file (@jplecavalier, #1957).
Expand Down
119 changes: 97 additions & 22 deletions R/vignette.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,85 @@
#' * Adds `inst/doc` to `.gitignore` so built vignettes aren't tracked.
#' * Adds `vignettes/*.html` and `vignettes/*.R` to `.gitignore` so
#' you never accidentally track rendered vignettes.
#' @param name Base for file name to use for new vignette. Should consist only
#' of numbers, letters, `_` and `-`. Lower case is recommended.
#' @param title The title of the vignette.
#' @seealso The [vignettes chapter](https://r-pkgs.org/vignettes.html) of
#' [R Packages](https://r-pkgs.org).
#' * For `*.qmd`, adds Quarto-related patterns to `.gitignore` and
#' `.Rbuildignore`.
#' @param name File name to use for new vignette. Should consist only of
#' numbers, letters, `_` and `-`. Lower case is recommended. Can include the
#' `".Rmd"` or `".qmd"` file extension, which also dictates whether to place
#' an R Markdown or Quarto vignette. R Markdown (`".Rmd"`) is the current
#' default, but it is anticipated that Quarto (`".qmd"`) will become the
#' default in the future.
#' @param title The title of the vignette. If not provided, a title is generated
#' from `name`.
#' @seealso
#' * The [vignettes chapter](https://r-pkgs.org/vignettes.html) of
#' [R Packages](https://r-pkgs.org)
#' * The pkgdown vignette on Quarto:
#' `vignette("quarto", package = "pkgdown")`
#' * The quarto (as in the R package) vignette on HTML vignettes:
#' `vignette("hello", package = "quarto")`
#' @export
#' @examples
#' \dontrun{
#' use_vignette("how-to-do-stuff", "How to do stuff")
#' use_vignette("r-markdown-is-classic.Rmd", "R Markdown is classic")
#' use_vignette("quarto-is-cool.qmd", "Quarto is cool")
#' }
use_vignette <- function(name, title = name) {
use_vignette <- function(name, title = NULL) {
check_is_package("use_vignette()")
check_required(name)
maybe_name(title)

ext <- get_vignette_extension(name)
if (ext == "qmd") {
check_installed("quarto")
check_installed("pkgdown", version = "2.1.0")
}

name <- path_ext_remove(name)
check_vignette_name(name)
title <- title %||% name

use_dependency("knitr", "Suggests")
use_dependency("rmarkdown", "Suggests")

proj_desc_field_update("VignetteBuilder", "knitr", overwrite = TRUE)
use_git_ignore("inst/doc")

use_vignette_template("vignette.Rmd", name, title)
if (tolower(ext) == "rmd") {
use_dependency("rmarkdown", "Suggests")
proj_desc_field_update("VignetteBuilder", "knitr", overwrite = TRUE, append = TRUE)
use_vignette_template("vignette.Rmd", name, title)
} else {
use_dependency("quarto", "Suggests")
proj_desc_field_update("VignetteBuilder", "quarto", overwrite = TRUE, append = TRUE)
use_vignette_template("vignette.qmd", name, title)
}

invisible()
}

#' @export
#' @rdname use_vignette
use_article <- function(name, title = name) {
use_article <- function(name, title = NULL) {
check_is_package("use_article()")
check_required(name)
maybe_name(title)

deps <- proj_deps()
if (!"rmarkdown" %in% deps$package) {
proj_desc_field_update("Config/Needs/website", "rmarkdown", append = TRUE)
ext <- get_vignette_extension(name)
if (ext == "qmd") {
check_installed("quarto")
check_installed("pkgdown", version = "2.1.0")
}

use_vignette_template("article.Rmd", name, title, subdir = "articles")
name <- path_ext_remove(name)
title <- title %||% name

if (tolower(ext) == "rmd") {
proj_desc_field_update("Config/Needs/website", "rmarkdown", overwrite = TRUE, append = TRUE)
use_vignette_template("article.Rmd", name, title, subdir = "articles")
} else {
use_dependency("quarto", "Suggests")
proj_desc_field_update("Config/Needs/website", "quarto", overwrite = TRUE, append = TRUE)
use_vignette_template("article.qmd", name, title, subdir = "articles")
}
use_build_ignore("vignettes/articles")

invisible()
Expand All @@ -58,18 +100,26 @@ use_vignette_template <- function(template, name, title, subdir = NULL) {
check_name(title)
maybe_name(subdir)

use_directory("vignettes")
if (!is.null(subdir)) {
use_directory(path("vignettes", subdir))
}
use_git_ignore(c("*.html", "*.R"), directory = "vignettes")
ext <- get_vignette_extension(template)

if (is.null(subdir)) {
path <- path("vignettes", asciify(name), ext = "Rmd")
target_dir <- "vignettes"
} else {
path <- path("vignettes", subdir, asciify(name), ext = "Rmd")
target_dir <- path("vignettes", subdir)
}

use_directory(target_dir)

use_git_ignore(c("*.html", "*.R"), directory = target_dir)
if (ext == "qmd") {
use_git_ignore("**/.quarto/")
use_git_ignore("*_files", target_dir)
use_build_ignore(path(target_dir, ".quarto"))
use_build_ignore(path(target_dir, "*_files"))
}

path <- path(target_dir, asciify(name), ext = ext)

data <- list(
Package = project_name(),
vignette_title = title,
Expand Down Expand Up @@ -102,3 +152,28 @@ check_vignette_name <- function(name) {
valid_vignette_name <- function(x) {
grepl("^[[:alpha:]][[:alnum:]_-]*$", x)
}

check_vignette_extension <- function(ext) {
# Quietly accept "rmd" here, tho we'll always write ".Rmd" in such a filepath
if (! ext %in% c("Rmd", "rmd", "qmd")) {
valid_exts_cli <- cli::cli_vec(
c("Rmd", "qmd"),
style = list("vec-sep2" = " or ")
)
ui_abort(c(
"Unsupported file extension: {.val {ext}}",
"usethis can only create a vignette or article with one of these
extensions: {.val {valid_exts_cli}}."
))
}
}

get_vignette_extension <- function(name) {
ext <- path_ext(name)
if (nzchar(ext)) {
check_vignette_extension(ext)
} else {
ext <- "Rmd"
}
ext
}
12 changes: 12 additions & 0 deletions inst/templates/article.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: "{{{ vignette_title }}}"
knitr:
opts_chunk:
collapse: true
comment: '#>'
---

```{r}
#| label: setup
library({{Package}})
```
16 changes: 16 additions & 0 deletions inst/templates/vignette.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: "{{{ vignette_title }}}"
vignette: >
%\VignetteIndexEntry{{{ braced_vignette_title }}}
%\VignetteEngine{quarto::html}
%\VignetteEncoding{UTF-8}
knitr:
opts_chunk:
collapse: true
comment: '#>'
---

```{r}
#| label: setup
library({{Package}})
```
29 changes: 22 additions & 7 deletions man/use_vignette.Rd

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

9 changes: 9 additions & 0 deletions tests/testthat/_snaps/vignette.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@
i Start with a letter.
i Contain only letters, numbers, '_', and '-'.

# we error informatively for bad vignette extension

Code
check_vignette_extension("Rnw")
Condition
Error in `check_vignette_extension()`:
x Unsupported file extension: "Rnw"
i usethis can only create a vignette or article with one of these extensions: "Rmd" or "qmd".

72 changes: 65 additions & 7 deletions tests/testthat/test-vignette.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ test_that("use_vignette() gives useful errors", {
})
})

test_that("use_vignette() does the promised setup", {
test_that("use_vignette() does the promised setup, Rmd", {
create_local_package()

use_vignette("name", "title")
Expand All @@ -32,32 +32,90 @@ test_that("use_vignette() does the promised setup", {
expect_identical(proj_desc()$get_field("VignetteBuilder"), "knitr")
})

# use_article -------------------------------------------------------------
test_that("use_vignette() does the promised setup, qmd", {
create_local_package()
local_check_installed()

use_vignette("name.qmd", "title")
expect_proj_file("vignettes/name.qmd")

ignores <- read_utf8(proj_path(".gitignore"))
expect_true("inst/doc" %in% ignores)

deps <- proj_deps()
expect_true(
all(c("knitr", "quarto") %in% deps$package[deps$type == "Suggests"])
)

expect_identical(proj_desc()$get_field("VignetteBuilder"), "quarto")
})

test_that("use_article goes in article subdirectory", {
test_that("use_vignette() does the promised setup, mix of Rmd and qmd", {
create_local_package()
local_check_installed()

use_vignette("older-vignette", "older Rmd vignette")
use_vignette("newer-vignette.qmd", "newer qmd vignette")
expect_proj_file("vignettes/older-vignette.Rmd")
expect_proj_file("vignettes/newer-vignette.qmd")

deps <- proj_deps()
expect_true(
all(c("knitr", "quarto", "rmarkdown") %in% deps$package[deps$type == "Suggests"])
)

use_article("test")
expect_proj_file("vignettes/articles/test.Rmd")
vignette_builder <- proj_desc()$get_field("VignetteBuilder")
expect_match(vignette_builder, "knitr", fixed = TRUE)
expect_match(vignette_builder, "quarto", fixed = TRUE)
})

test_that("use_article() adds rmarkdown to Config/Needs/website", {
# use_article -------------------------------------------------------------
test_that("use_article() does the promised setup, Rmd", {
create_local_package()
local_interactive(FALSE)

proj_desc_field_update("Config/Needs/website", "somepackage", append = TRUE)
# Let's have another package already in Config/Needs/website
proj_desc_field_update("Config/Needs/website", "somepackage")
use_article("name", "title")

expect_proj_file("vignettes/articles/name.Rmd")

expect_setequal(
proj_desc()$get_list("Config/Needs/website"),
c("rmarkdown", "somepackage")
)
})

# Note that qmd articles seem to cause problems for build_site() rn
# https://github.com/r-lib/pkgdown/issues/2821
test_that("use_article() does the promised setup, qmd", {
create_local_package()
local_check_installed()
local_interactive(FALSE)

# Let's have another package already in Config/Needs/website
proj_desc_field_update("Config/Needs/website", "somepackage")
use_article("name.qmd", "title")

expect_proj_file("vignettes/articles/name.qmd")

expect_setequal(
proj_desc()$get_list("Config/Needs/website"),
c("quarto", "somepackage")
)
})

# helpers -----------------------------------------------------------------

test_that("valid_vignette_name() works", {
expect_true(valid_vignette_name("perfectly-valid-name"))
expect_false(valid_vignette_name("01-test"))
expect_false(valid_vignette_name("test.1"))
})

test_that("we error informatively for bad vignette extension", {
expect_snapshot(
error = TRUE,
check_vignette_extension("Rnw")
)
})

0 comments on commit d7bf1cc

Please sign in to comment.