Skip to content

Commit d7bf1cc

Browse files
authored
Assist with Quarto vignettes and articles (#2085)
* 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
1 parent ee5b158 commit d7bf1cc

File tree

7 files changed

+226
-38
lines changed

7 files changed

+226
-38
lines changed

NEWS.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# usethis (development version)
22

3+
* `use_vignette()` and `use_article()` now support Quarto. The `name` of the new
4+
vignette or article can optionally include a file extension to signal whether
5+
`.Rmd` or `.qmd` is desired, with `.Rmd` remaining the default for now. Thanks
6+
to @olivroy for getting the ball rolling (#1997).
7+
38
* `use_tidy_upkeep_issue()` now records the year it is being run in the
49
`Config/usethis/upkeep` field in DESCRIPTION. If this value exists it is
510
furthermore used to filter the checklist when making the issue.
611

7-
## Bug fixes and minor improvements
8-
912
* `use_package()` now decreases a package minimum version required when
1013
`min_version` is lower than what is currently specified in the DESCRIPTION
1114
file (@jplecavalier, #1957).

R/vignette.R

+97-22
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,85 @@
1010
#' * Adds `inst/doc` to `.gitignore` so built vignettes aren't tracked.
1111
#' * Adds `vignettes/*.html` and `vignettes/*.R` to `.gitignore` so
1212
#' you never accidentally track rendered vignettes.
13-
#' @param name Base for file name to use for new vignette. Should consist only
14-
#' of numbers, letters, `_` and `-`. Lower case is recommended.
15-
#' @param title The title of the vignette.
16-
#' @seealso The [vignettes chapter](https://r-pkgs.org/vignettes.html) of
17-
#' [R Packages](https://r-pkgs.org).
13+
#' * For `*.qmd`, adds Quarto-related patterns to `.gitignore` and
14+
#' `.Rbuildignore`.
15+
#' @param name File name to use for new vignette. Should consist only of
16+
#' numbers, letters, `_` and `-`. Lower case is recommended. Can include the
17+
#' `".Rmd"` or `".qmd"` file extension, which also dictates whether to place
18+
#' an R Markdown or Quarto vignette. R Markdown (`".Rmd"`) is the current
19+
#' default, but it is anticipated that Quarto (`".qmd"`) will become the
20+
#' default in the future.
21+
#' @param title The title of the vignette. If not provided, a title is generated
22+
#' from `name`.
23+
#' @seealso
24+
#' * The [vignettes chapter](https://r-pkgs.org/vignettes.html) of
25+
#' [R Packages](https://r-pkgs.org)
26+
#' * The pkgdown vignette on Quarto:
27+
#' `vignette("quarto", package = "pkgdown")`
28+
#' * The quarto (as in the R package) vignette on HTML vignettes:
29+
#' `vignette("hello", package = "quarto")`
1830
#' @export
1931
#' @examples
2032
#' \dontrun{
2133
#' use_vignette("how-to-do-stuff", "How to do stuff")
34+
#' use_vignette("r-markdown-is-classic.Rmd", "R Markdown is classic")
35+
#' use_vignette("quarto-is-cool.qmd", "Quarto is cool")
2236
#' }
23-
use_vignette <- function(name, title = name) {
37+
use_vignette <- function(name, title = NULL) {
2438
check_is_package("use_vignette()")
2539
check_required(name)
40+
maybe_name(title)
41+
42+
ext <- get_vignette_extension(name)
43+
if (ext == "qmd") {
44+
check_installed("quarto")
45+
check_installed("pkgdown", version = "2.1.0")
46+
}
47+
48+
name <- path_ext_remove(name)
2649
check_vignette_name(name)
50+
title <- title %||% name
2751

2852
use_dependency("knitr", "Suggests")
29-
use_dependency("rmarkdown", "Suggests")
30-
31-
proj_desc_field_update("VignetteBuilder", "knitr", overwrite = TRUE)
3253
use_git_ignore("inst/doc")
3354

34-
use_vignette_template("vignette.Rmd", name, title)
55+
if (tolower(ext) == "rmd") {
56+
use_dependency("rmarkdown", "Suggests")
57+
proj_desc_field_update("VignetteBuilder", "knitr", overwrite = TRUE, append = TRUE)
58+
use_vignette_template("vignette.Rmd", name, title)
59+
} else {
60+
use_dependency("quarto", "Suggests")
61+
proj_desc_field_update("VignetteBuilder", "quarto", overwrite = TRUE, append = TRUE)
62+
use_vignette_template("vignette.qmd", name, title)
63+
}
3564

3665
invisible()
3766
}
3867

3968
#' @export
4069
#' @rdname use_vignette
41-
use_article <- function(name, title = name) {
70+
use_article <- function(name, title = NULL) {
4271
check_is_package("use_article()")
72+
check_required(name)
73+
maybe_name(title)
4374

44-
deps <- proj_deps()
45-
if (!"rmarkdown" %in% deps$package) {
46-
proj_desc_field_update("Config/Needs/website", "rmarkdown", append = TRUE)
75+
ext <- get_vignette_extension(name)
76+
if (ext == "qmd") {
77+
check_installed("quarto")
78+
check_installed("pkgdown", version = "2.1.0")
4779
}
4880

49-
use_vignette_template("article.Rmd", name, title, subdir = "articles")
81+
name <- path_ext_remove(name)
82+
title <- title %||% name
83+
84+
if (tolower(ext) == "rmd") {
85+
proj_desc_field_update("Config/Needs/website", "rmarkdown", overwrite = TRUE, append = TRUE)
86+
use_vignette_template("article.Rmd", name, title, subdir = "articles")
87+
} else {
88+
use_dependency("quarto", "Suggests")
89+
proj_desc_field_update("Config/Needs/website", "quarto", overwrite = TRUE, append = TRUE)
90+
use_vignette_template("article.qmd", name, title, subdir = "articles")
91+
}
5092
use_build_ignore("vignettes/articles")
5193

5294
invisible()
@@ -58,18 +100,26 @@ use_vignette_template <- function(template, name, title, subdir = NULL) {
58100
check_name(title)
59101
maybe_name(subdir)
60102

61-
use_directory("vignettes")
62-
if (!is.null(subdir)) {
63-
use_directory(path("vignettes", subdir))
64-
}
65-
use_git_ignore(c("*.html", "*.R"), directory = "vignettes")
103+
ext <- get_vignette_extension(template)
66104

67105
if (is.null(subdir)) {
68-
path <- path("vignettes", asciify(name), ext = "Rmd")
106+
target_dir <- "vignettes"
69107
} else {
70-
path <- path("vignettes", subdir, asciify(name), ext = "Rmd")
108+
target_dir <- path("vignettes", subdir)
109+
}
110+
111+
use_directory(target_dir)
112+
113+
use_git_ignore(c("*.html", "*.R"), directory = target_dir)
114+
if (ext == "qmd") {
115+
use_git_ignore("**/.quarto/")
116+
use_git_ignore("*_files", target_dir)
117+
use_build_ignore(path(target_dir, ".quarto"))
118+
use_build_ignore(path(target_dir, "*_files"))
71119
}
72120

121+
path <- path(target_dir, asciify(name), ext = ext)
122+
73123
data <- list(
74124
Package = project_name(),
75125
vignette_title = title,
@@ -102,3 +152,28 @@ check_vignette_name <- function(name) {
102152
valid_vignette_name <- function(x) {
103153
grepl("^[[:alpha:]][[:alnum:]_-]*$", x)
104154
}
155+
156+
check_vignette_extension <- function(ext) {
157+
# Quietly accept "rmd" here, tho we'll always write ".Rmd" in such a filepath
158+
if (! ext %in% c("Rmd", "rmd", "qmd")) {
159+
valid_exts_cli <- cli::cli_vec(
160+
c("Rmd", "qmd"),
161+
style = list("vec-sep2" = " or ")
162+
)
163+
ui_abort(c(
164+
"Unsupported file extension: {.val {ext}}",
165+
"usethis can only create a vignette or article with one of these
166+
extensions: {.val {valid_exts_cli}}."
167+
))
168+
}
169+
}
170+
171+
get_vignette_extension <- function(name) {
172+
ext <- path_ext(name)
173+
if (nzchar(ext)) {
174+
check_vignette_extension(ext)
175+
} else {
176+
ext <- "Rmd"
177+
}
178+
ext
179+
}

inst/templates/article.qmd

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: "{{{ vignette_title }}}"
3+
knitr:
4+
opts_chunk:
5+
collapse: true
6+
comment: '#>'
7+
---
8+
9+
```{r}
10+
#| label: setup
11+
library({{Package}})
12+
```

inst/templates/vignette.qmd

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
title: "{{{ vignette_title }}}"
3+
vignette: >
4+
%\VignetteIndexEntry{{{ braced_vignette_title }}}
5+
%\VignetteEngine{quarto::html}
6+
%\VignetteEncoding{UTF-8}
7+
knitr:
8+
opts_chunk:
9+
collapse: true
10+
comment: '#>'
11+
---
12+
13+
```{r}
14+
#| label: setup
15+
library({{Package}})
16+
```

man/use_vignette.Rd

+22-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/vignette.md

+9
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@
1313
i Start with a letter.
1414
i Contain only letters, numbers, '_', and '-'.
1515

16+
# we error informatively for bad vignette extension
17+
18+
Code
19+
check_vignette_extension("Rnw")
20+
Condition
21+
Error in `check_vignette_extension()`:
22+
x Unsupported file extension: "Rnw"
23+
i usethis can only create a vignette or article with one of these extensions: "Rmd" or "qmd".
24+

tests/testthat/test-vignette.R

+65-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ test_that("use_vignette() gives useful errors", {
1515
})
1616
})
1717

18-
test_that("use_vignette() does the promised setup", {
18+
test_that("use_vignette() does the promised setup, Rmd", {
1919
create_local_package()
2020

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

35-
# use_article -------------------------------------------------------------
35+
test_that("use_vignette() does the promised setup, qmd", {
36+
create_local_package()
37+
local_check_installed()
38+
39+
use_vignette("name.qmd", "title")
40+
expect_proj_file("vignettes/name.qmd")
41+
42+
ignores <- read_utf8(proj_path(".gitignore"))
43+
expect_true("inst/doc" %in% ignores)
44+
45+
deps <- proj_deps()
46+
expect_true(
47+
all(c("knitr", "quarto") %in% deps$package[deps$type == "Suggests"])
48+
)
49+
50+
expect_identical(proj_desc()$get_field("VignetteBuilder"), "quarto")
51+
})
3652

37-
test_that("use_article goes in article subdirectory", {
53+
test_that("use_vignette() does the promised setup, mix of Rmd and qmd", {
3854
create_local_package()
55+
local_check_installed()
56+
57+
use_vignette("older-vignette", "older Rmd vignette")
58+
use_vignette("newer-vignette.qmd", "newer qmd vignette")
59+
expect_proj_file("vignettes/older-vignette.Rmd")
60+
expect_proj_file("vignettes/newer-vignette.qmd")
61+
62+
deps <- proj_deps()
63+
expect_true(
64+
all(c("knitr", "quarto", "rmarkdown") %in% deps$package[deps$type == "Suggests"])
65+
)
3966

40-
use_article("test")
41-
expect_proj_file("vignettes/articles/test.Rmd")
67+
vignette_builder <- proj_desc()$get_field("VignetteBuilder")
68+
expect_match(vignette_builder, "knitr", fixed = TRUE)
69+
expect_match(vignette_builder, "quarto", fixed = TRUE)
4270
})
4371

44-
test_that("use_article() adds rmarkdown to Config/Needs/website", {
72+
# use_article -------------------------------------------------------------
73+
test_that("use_article() does the promised setup, Rmd", {
4574
create_local_package()
4675
local_interactive(FALSE)
4776

48-
proj_desc_field_update("Config/Needs/website", "somepackage", append = TRUE)
77+
# Let's have another package already in Config/Needs/website
78+
proj_desc_field_update("Config/Needs/website", "somepackage")
4979
use_article("name", "title")
5080

81+
expect_proj_file("vignettes/articles/name.Rmd")
82+
5183
expect_setequal(
5284
proj_desc()$get_list("Config/Needs/website"),
5385
c("rmarkdown", "somepackage")
5486
)
5587
})
5688

89+
# Note that qmd articles seem to cause problems for build_site() rn
90+
# https://github.com/r-lib/pkgdown/issues/2821
91+
test_that("use_article() does the promised setup, qmd", {
92+
create_local_package()
93+
local_check_installed()
94+
local_interactive(FALSE)
95+
96+
# Let's have another package already in Config/Needs/website
97+
proj_desc_field_update("Config/Needs/website", "somepackage")
98+
use_article("name.qmd", "title")
99+
100+
expect_proj_file("vignettes/articles/name.qmd")
101+
102+
expect_setequal(
103+
proj_desc()$get_list("Config/Needs/website"),
104+
c("quarto", "somepackage")
105+
)
106+
})
107+
57108
# helpers -----------------------------------------------------------------
58109

59110
test_that("valid_vignette_name() works", {
60111
expect_true(valid_vignette_name("perfectly-valid-name"))
61112
expect_false(valid_vignette_name("01-test"))
62113
expect_false(valid_vignette_name("test.1"))
63114
})
115+
116+
test_that("we error informatively for bad vignette extension", {
117+
expect_snapshot(
118+
error = TRUE,
119+
check_vignette_extension("Rnw")
120+
)
121+
})

0 commit comments

Comments
 (0)