Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assist with Quarto vignettes and articles #2085

Merged
merged 6 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 98 additions & 22 deletions R/vignette.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,86 @@
#' * 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 {
# check this dependency stuff
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 +101,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/")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will gitignore .quarto at the top-level (relevant to README.qmd, although usethis isn't helping to place such a file just yet), vignettes/.quarto (normal vignettes), and vignettes/articles/.quarto (articles). It's probably redundant with something Quarto itself is doing, but I think this is better than what Quarto does and redundancy is not a big deal in this context.

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 +153,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")
)
})
Loading