Skip to content

Commit 64ac559

Browse files
authored
Support README.qmd (#2677)
* Support README.qmd * Better skip re: quarto * Make sort order same across OSes * Empty commit to trigger CI * Don't use usethis::use_readme_qmd() (yet) I want to release devtools soon and, in particular, before I release usethis. * Move the cli::cli_inform() up * Use callr::r_safe(env =) * Work from a fixed list of executable README paths
1 parent 33448d6 commit 64ac559

7 files changed

Lines changed: 138 additions & 31 deletions

File tree

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Suggests:
5050
httr2 (>= 1.0.0),
5151
knitr (>= 1.39),
5252
lintr (>= 3.0.0),
53-
quarto,
53+
quarto (>= 1.5.1),
5454
remotes (>= 2.5.0),
5555
rmarkdown (>= 2.14),
5656
rstudioapi (>= 0.13),

NEWS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Deprecations
1313

1414
Other improvements
1515

16+
* `build_readme()` gains support for `README.qmd` and renders using Quarto (#2620).
1617
* `install()` now installs dependencies with `pak::local_install_deps()` instead of `remotes::install_deps()`. This lets us default to `upgrade = FALSE`, so that existing dependencies are only upgraded when a newer version is actually required (#2486). `keep_source` now defaults to `TRUE` when `build = FALSE`, so that source references are automatically preserved during development installs.
1718
* `build_manual()` reports more details on failure (#2586).
1819
* `build_site()` now just calls `pkgdown::build_site()`, meaning that you will get more (informative) output by default (#2578).
@@ -28,8 +29,7 @@ Other improvements
2829

2930
# devtools 2.4.6
3031

31-
* Functions that use httr now explicitly check that it is installed
32-
(@catalamarti, #2573).
32+
* Functions that use httr now explicitly check that it is installed (@catalamarti, #2573).
3333

3434
* `test_coverage()` now works if the package has not been installed.
3535

R/build-readme.R

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#' `r lifecycle::badge("deprecated")`
55
#'
66
#' `build_rmd()` is deprecated, as it is a low-level helper for internal use. To
7-
#' render your package's `README.Rmd` or `README.qmd`, use [build_readme()]. To
7+
#' render your package's `README.qmd` or `README.Rmd`, use [build_readme()]. To
88
#' preview a vignette or article, use functions like [pkgdown::build_site()] or
99
#' [pkgdown::build_article()].
1010
#'
@@ -64,9 +64,6 @@ build_rmd_impl <- function(
6464
output_options$html_preview <- FALSE
6565

6666
for (path in paths) {
67-
if (!quiet) {
68-
cli::cli_inform(c(i = "Building {.path {path}}"))
69-
}
7067
callr::r_safe(
7168
function(...) rmarkdown::render(...),
7269
args = list(
@@ -86,36 +83,77 @@ build_rmd_impl <- function(
8683

8784
#' Build README
8885
#'
89-
#' Renders an executable README, such as `README.Rmd`, to `README.md`.
90-
#' Specifically, `build_readme()`:
86+
#' Renders an executable README, i.e. `README.qmd` or `README.Rmd`, to
87+
#' `README.md`. Specifically, `build_readme()`:
9188
#' * Installs a copy of the package's current source to a temporary library
9289
#' * Renders the README in a clean R session
9390
#'
94-
#' @param path Path to the package to build the README.
91+
#' @param path Path to the top-level directory of the source package.
9592
#' @param quiet If `TRUE`, suppresses most output. Set to `FALSE`
9693
#' if you need to debug.
97-
#' @param ... Additional arguments passed to [rmarkdown::render()].
94+
#' @param ... Additional arguments passed to [rmarkdown::render()], in the
95+
#' case of `README.Rmd`. Not used for `README.qmd`
9896
#' @export
9997
build_readme <- function(path = ".", quiet = TRUE, ...) {
10098
pkg <- as.package(path)
10199

102-
regexp <- paste0(path_file(pkg$path), "/(inst/)?readme[.]rmd$")
103-
readme_path <- path_abs(dir_ls(
104-
pkg$path,
105-
ignore.case = TRUE,
106-
regexp = regexp,
107-
recurse = 1,
108-
type = "file"
109-
))
100+
readme_candidates <- c(
101+
path(pkg$path, "README.qmd"),
102+
path(pkg$path, "README.Rmd"),
103+
path(pkg$path, "inst", "README.qmd"),
104+
path(pkg$path, "inst", "README.Rmd")
105+
)
106+
readme_path <- readme_candidates[file_exists(readme_candidates)]
110107

111108
if (length(readme_path) == 0) {
112-
cli::cli_abort("Can't find {.file README.Rmd} or {.file inst/README.Rmd}.")
109+
cli::cli_abort(
110+
"Can't find {.file README.qmd} or {.file README.Rmd}, at the top-level or
111+
below {.file inst/}."
112+
)
113113
}
114114
if (length(readme_path) > 1) {
115+
rel_paths <- path_rel(readme_path, pkg$path)
115116
cli::cli_abort(
116-
"Can't have both {.file README.Rmd} and {.file inst/README.Rmd}."
117+
"Found multiple executable READMEs: {.file {rel_paths}}. There can only be
118+
one."
117119
)
118120
}
119121

120-
build_rmd_impl(readme_path, path = path, quiet = quiet, ...)
122+
if (!quiet) {
123+
cli::cli_inform(c(i = "Building {.path {readme_path}}"))
124+
}
125+
126+
if (path_ext(readme_path) == "qmd") {
127+
build_qmd_readme(readme_path, path = path, quiet = quiet)
128+
} else {
129+
build_rmd_impl(readme_path, path = path, quiet = quiet, ...)
130+
}
131+
}
132+
133+
build_qmd_readme <- function(readme_path, path = ".", quiet = TRUE) {
134+
pkg <- as.package(path)
135+
136+
check_installed("quarto")
137+
save_all()
138+
139+
local_install(pkg, quiet = TRUE)
140+
141+
# Quarto spawns its own R process for knitr, which won't inherit .libPaths().
142+
143+
# Pass library paths via R_LIBS_USER so the quarto subprocess finds the
144+
# temporarily installed package first, ahead of any user-installed version.
145+
lib_paths <- paste(.libPaths(), collapse = .Platform$path.sep)
146+
147+
callr::r_safe(
148+
function(input, quiet) {
149+
quarto::quarto_render(input = input, quiet = quiet)
150+
},
151+
args = list(input = readme_path, quiet = quiet),
152+
env = c(callr::rcmd_safe_env(), R_LIBS_USER = lib_paths),
153+
show = TRUE,
154+
spinner = FALSE,
155+
stderr = "2>&1"
156+
)
157+
158+
invisible(TRUE)
121159
}

man/build_readme.Rd

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/build_rmd.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/build-readme.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@
44
build_readme(pkg)
55
Condition
66
Error in `build_readme()`:
7-
! Can't find 'README.Rmd' or 'inst/README.Rmd'.
7+
! Can't find 'README.qmd' or 'README.Rmd', at the top-level or below 'inst/'.
88

99
---
1010

1111
Code
1212
build_readme(pkg)
1313
Condition
1414
Error in `build_readme()`:
15-
! Can't have both 'README.Rmd' and 'inst/README.Rmd'.
15+
! Found multiple executable READMEs: 'README.Rmd' and 'inst/README.Rmd'. There can only be one.
16+
17+
# errors if both README.qmd and README.Rmd exist
18+
19+
Code
20+
build_readme(pkg)
21+
Condition
22+
Error in `build_readme()`:
23+
! Found multiple executable READMEs: 'README.qmd' and 'README.Rmd'. There can only be one.
1624

1725
# build_rmd() is deprecated
1826

tests/testthat/test-build-readme.R

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
test_that("can build README in root directory", {
1+
test_that("can build README.Rmd in root directory", {
22
skip_on_cran()
33

44
pkg <- local_package_create()
@@ -14,7 +14,7 @@ test_that("can build README in root directory", {
1414
expect_false(file_exists(path(pkg, "README.html")))
1515
})
1616

17-
test_that("can build README in inst/", {
17+
test_that("can build README.Rmd in inst/", {
1818
skip_on_cran()
1919

2020
pkg <- local_package_create()
@@ -37,6 +37,59 @@ test_that("can build README in inst/", {
3737
expect_false(file_exists(path(pkg, "inst", "README.html")))
3838
})
3939

40+
test_that("can build README.qmd in root directory", {
41+
skip_on_cran()
42+
skip_if_not_installed("quarto")
43+
skip_if_not(quarto::quarto_available(), "quarto cli not available")
44+
45+
pkg <- local_package_create()
46+
# TODO: use usethis::use_readme_qmd() once it's in a usethis release
47+
# https://github.com/r-lib/usethis/pull/2219
48+
writeLines(
49+
c(
50+
"---",
51+
"format: gfm",
52+
"---",
53+
"",
54+
"# testpkg",
55+
"",
56+
"This is a test package."
57+
),
58+
path(pkg, "README.qmd")
59+
)
60+
61+
build_readme(pkg, quiet = TRUE)
62+
expect_true(file_exists(path(pkg, "README.md")))
63+
})
64+
65+
test_that("can build README.qmd in inst/", {
66+
skip_on_cran()
67+
skip_if_not_installed("quarto")
68+
skip_if_not(quarto::quarto_available(), "quarto cli not available")
69+
70+
pkg <- local_package_create()
71+
# TODO: use usethis::use_readme_qmd() once it's in a usethis release
72+
# https://github.com/r-lib/usethis/pull/2219
73+
dir_create(pkg, "inst")
74+
writeLines(
75+
c(
76+
"---",
77+
"format: gfm",
78+
"---",
79+
"",
80+
"# testpkg",
81+
"",
82+
"This is a test package."
83+
),
84+
path(pkg, "inst", "README.qmd")
85+
)
86+
87+
build_readme(pkg, quiet = TRUE)
88+
expect_true(file_exists(path(pkg, "inst", "README.md")))
89+
expect_false(file_exists(path(pkg, "README.qmd")))
90+
expect_false(file_exists(path(pkg, "README.md")))
91+
})
92+
4093
test_that("useful errors if too few or too many", {
4194
pkg <- local_package_create()
4295
expect_snapshot(build_readme(pkg), error = TRUE)
@@ -52,6 +105,13 @@ test_that("useful errors if too few or too many", {
52105
expect_snapshot(build_readme(pkg), error = TRUE)
53106
})
54107

108+
test_that("errors if both README.qmd and README.Rmd exist", {
109+
pkg <- local_package_create()
110+
file_create(path(pkg, "README.Rmd"))
111+
file_create(path(pkg, "README.qmd"))
112+
expect_snapshot(build_readme(pkg), error = TRUE)
113+
})
114+
55115
test_that("don't error for README in another directory", {
56116
skip_on_cran()
57117

0 commit comments

Comments
 (0)