diff --git a/NAMESPACE b/NAMESPACE index 91a15d28e..87c0e2d9b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -158,6 +158,7 @@ export(use_rcpp) export(use_rcpp_armadillo) export(use_rcpp_eigen) export(use_readme_md) +export(use_readme_qmd) export(use_readme_rmd) export(use_release_issue) export(use_reprex) diff --git a/R/badge.R b/R/badge.R index 7dcf84464..18f794fe5 100644 --- a/R/badge.R +++ b/R/badge.R @@ -5,8 +5,8 @@ #' and link out to relevant external resources. To allow badges to be added #' automatically, ensure your badge block starts with a line containing only #' `` and ends with a line containing only -#' ``. The templates used by [use_readme_md()] and -#' [use_readme_rmd()] include this block. +#' ``. The templates used by [use_readme_md()], +#' [use_readme_rmd()], and [use_readme_qmd()] include this block. #' #' @details #' @@ -66,7 +66,7 @@ use_badge <- function(badge_name, href, src) { block_end = badge_end ) - if (changed && path_ext(path) == "Rmd") { + if (changed && path_ext(path) %in% c("Rmd", "qmd")) { ui_bullets(c( "_" = "Re-knit {.path {pth(path)}} with {.run devtools::build_readme()}." )) @@ -224,5 +224,5 @@ badge_start <- "" badge_end <- "" find_readme <- function() { - path_first_existing(proj_path(c("README.Rmd", "README.md"))) + path_first_existing(proj_path(c("README.Rmd", "README.qmd", "README.md"))) } diff --git a/R/readme.R b/R/readme.R index cf1a3651c..b42fafbd7 100644 --- a/R/readme.R +++ b/R/readme.R @@ -6,21 +6,21 @@ #' * R code to install from GitHub, if GitHub usage detected #' * a basic example #' -#' Use `Rmd` if you want a rich intermingling of code and output. Use `md` for a -#' basic README. `README.Rmd` will be automatically added to `.Rbuildignore`. +#' Use `qmd` or `Rmd` if you want a rich intermingling of code and output. Use `md` for a +#' basic README. `README.[R,q]md` will be automatically added to `.Rbuildignore`. #' The resulting README is populated with default YAML frontmatter and R fenced -#' code blocks (`md`) or chunks (`Rmd`). +#' code blocks (`md`) or chunks (`Rmd`/`qmd`). #' -#' If you use `Rmd`, you'll still need to render it regularly, to keep +#' If you use `[R,q]md`, you'll still need to render it regularly, to keep #' `README.md` up-to-date. `devtools::build_readme()` is handy for this. You -#' could also use GitHub Actions to re-render `README.Rmd` every time you push. +#' could also use GitHub Actions to re-render `README.[R,q]md` every time you push. #' An example workflow can be found in the `examples/` directory here: #' . #' -#' If the current project is a Git repo, then `use_readme_rmd()` automatically -#' configures a pre-commit hook that helps keep `README.Rmd` and `README.md`, +#' If the current project is a Git repo, then `use_readme_[r,q]md()` automatically +#' configures a pre-commit hook that helps keep `README.[R,q]md` and `README.md`, #' synchronized. The hook creates friction if you try to commit when -#' `README.Rmd` has been edited more recently than `README.md`. If this hook +#' `README.[R,q]md` has been edited more recently than `README.md`. If this hook #' causes more problems than it solves for you, it is implemented in #' `.git/hooks/pre-commit`, which you can modify or even delete. #' @@ -32,75 +32,108 @@ #' @examples #' \dontrun{ #' use_readme_rmd() +#' use_readme_qmd() #' use_readme_md() #' } use_readme_rmd <- function(open = rlang::is_interactive()) { + use_readme("Rmd", open = open) +} + +#' @export +#' @rdname use_readme_rmd +use_readme_qmd <- function(open = rlang::is_interactive()) { + use_readme("qmd", open = open) +} + +#' @export +#' @rdname use_readme_rmd +use_readme_md <- function(open = rlang::is_interactive()) { + use_readme("md", open = open) +} + +#' Helper to create README files +#' +#' @description +#' This function switches between the three file formats supported for READMEs, +#' and neatly handles file creation for all of them. +#' +#' @noRd +use_readme <- function( + fmt = c("Rmd", "md", "qmd"), + open = rlang::is_interactive() +) { check_is_project() - check_installed("rmarkdown") + fmt <- rlang::arg_match(fmt) + # Check dependencies and conflicts. + if (fmt == "Rmd") { + check_installed("rmarkdown") + if (fs::file_exists(proj_path("README.qmd"))) { + cli::cli_abort( + "Can't have both {.file README.Rmd} and {.file README.qmd}. Delete {.file README.qmd} if you want to generate {.file README.Rmd}." + ) + } + } else if (fmt == "qmd") { + check_installed("quarto") + if (fs::file_exists(proj_path("README.Rmd"))) { + cli::cli_abort( + "Can't have both {.file README.Rmd} and {.file README.qmd}. Delete {.file README.Rmd} if you want to generate {.file README.qmd}." + ) + } + } + + # Get some info about the package/project function was called from is_pkg <- is_package() repo_spec <- tryCatch(target_repo_spec(ask = FALSE), error = function(e) NULL) nm <- if (is_pkg) "Package" else "Project" + + # build out arguments for creating template + args <- switch( + fmt, + Rmd = list(Rmd = TRUE, filename = "README.Rmd", needs_render = TRUE), + md = list(needs_render = FALSE), + qmd = list(quarto = TRUE, filename = "README.qmd", needs_render = TRUE) + ) data <- list2( !!nm := project_name(), - Rmd = TRUE, on_github = !is.null(repo_spec), - github_spec = repo_spec + github_spec = repo_spec, + !!!args ) + # Create the template, interpolating values from `data` as specified new <- use_template( if (is_pkg) "package-README" else "project-README", - "README.Rmd", + glue::glue("README.", fmt), data = data, - ignore = is_pkg, + ignore = if (fmt %in% c("Rmd", "qmd")) is_pkg else FALSE, open = open ) - if (!new) { - return(invisible(FALSE)) - } + # Make some checks if (is_pkg && !data$on_github) { - ui_bullets(c( - "_" = "Update {.path {pth('README.Rmd')}} to include installation instructions." - )) + msg <- switch( + fmt, + rmd = "Update {.path {pth('README.Rmd')}} to include installation instructions.", + md = "Update {.path {pth('README.md')}} to include installation instructions.", + qmd = "Update {.path {pth('README.qmd')}} to include installation instructions." + ) + ui_bullets(c("_" = msg)) } - if (uses_git()) { + # More checks, specific to renderable README + if (fmt %in% c("Rmd", "qmd") && uses_git()) { + if (!new) { + return(invisible(FALSE)) + } + use_git_hook( "pre-commit", render_template("readme-rmd-pre-commit.sh") ) - } - invisible(TRUE) -} - -#' @export -#' @rdname use_readme_rmd -use_readme_md <- function(open = rlang::is_interactive()) { - check_is_project() - is_pkg <- is_package() - repo_spec <- tryCatch(target_repo_spec(ask = FALSE), error = function(e) NULL) - nm <- if (is_pkg) "Package" else "Project" - data <- list2( - !!nm := project_name(), - Rmd = FALSE, - on_github = !is.null(repo_spec), - github_spec = repo_spec - ) - - new <- use_template( - if (is_pkg) "package-README" else "project-README", - "README.md", - data = data, - open = open - ) - - if (is_pkg && !data$on_github) { - ui_bullets(c( - "_" = "Update {.path {pth('README.md')}} to include installation instructions." - )) + invisible(TRUE) + } else { + invisible(new) } - - invisible(new) } diff --git a/R/release.R b/R/release.R index 89722e90e..e56f3154f 100644 --- a/R/release.R +++ b/R/release.R @@ -75,7 +75,8 @@ release_checklist <- function(version, on_cran, target_repo = NULL) { has_news <- file_exists(proj_path("NEWS.md")) has_pkgdown <- uses_pkgdown() has_lifecycle <- proj_desc()$has_dep("lifecycle") - has_readme <- file_exists(proj_path("README.Rmd")) + has_readme <- file_exists(proj_path("README.Rmd")) || + file_exists(proj_path("README.qmd")) has_github_links <- has_github_links(target_repo) is_posit_pkg <- is_posit_pkg() milestone_num <- gh_milestone_number(target_repo, version) diff --git a/R/upkeep.R b/R/upkeep.R index 0beb7485a..ba9e68d0f 100644 --- a/R/upkeep.R +++ b/R/upkeep.R @@ -62,7 +62,11 @@ upkeep_checklist <- function(target_repo = NULL) { has_github_links <- has_github_links(target_repo) bullets <- c( - todo("`usethis::use_readme_rmd()`", !file_exists(proj_path("README.Rmd"))), + todo( + "`usethis::use_readme_rmd()`", + !file_exists(proj_path("README.Rmd")) && + !file_exists(proj_path("README.qmd")) + ), todo("`usethis::use_roxygen_md()`", !is_true(uses_roxygen_md())), todo("`usethis::use_github_links()`", !has_github_links), todo("`usethis::use_pkgdown_github_pages()`", !uses_pkgdown()), diff --git a/inst/templates/package-README b/inst/templates/package-README index cde93f606..37dbd360f 100644 --- a/inst/templates/package-README +++ b/inst/templates/package-README @@ -15,7 +15,23 @@ knitr::opts_chunk$set( ``` {{/Rmd}} +{{#quarto}} +--- +title: {{{ Package }}} +knitr: + opts_chunk: + collapse: true + comment: '#>' + fig.path: "man/figures/README-" + out.width: "100%" +format: gfm +--- + + +{{/quarto}} +{{^quarto}} # {{{ Package }}} +{{/quarto}} @@ -44,23 +60,23 @@ You can install the development version of {{{ Package }}} like so: This is a basic example which shows you how to solve a common problem: -{{#Rmd}} +{{#needs_render}} ```{r example} -{{/Rmd}} -{{^Rmd}}``` r -{{/Rmd}} +{{/needs_render}} +{{^needs_render}}``` r +{{/needs_render}} library({{Package}}) ## basic example code ``` -{{#Rmd}} -What is special about using `README.Rmd` instead of just `README.md`? You can include R chunks like so: +{{#needs_render}} +What is special about using `{{{ filename }}}` instead of just `README.md`? You can include R chunks like so: ```{r cars} summary(cars) ``` -You'll still need to render `README.Rmd` regularly, to keep `README.md` up-to-date. `devtools::build_readme()` is handy for this. +You'll still need to render `{{{ filename }}}` regularly, to keep `README.md` up-to-date. `devtools::build_readme()` is handy for this. You can also embed plots, for example: @@ -69,4 +85,4 @@ plot(pressure) ``` In that case, don't forget to commit and push the resulting figure files, so they display on GitHub and CRAN. -{{/Rmd}} +{{/needs_render}} diff --git a/inst/templates/project-README b/inst/templates/project-README index 955d05f3d..ed606c8e0 100644 --- a/inst/templates/project-README +++ b/inst/templates/project-README @@ -12,22 +12,36 @@ knitr::opts_chunk$set( ) ``` {{/Rmd}} +{{#quarto}} +--- +title: {{{ Project }}} +knitr: + opts_chunk: + collapse: true + comment: '#>' + fig.path: "man/figures/README-" + out.width: "100%" +format: gfm +--- + +{{/quarto}} +{{^quarto}} # {{{ Project }}} - +{{/quarto}} The goal of {{{ Project }}} is to ... -{{#Rmd}} -What is special about using `README.Rmd` instead of just `README.md`? You can include R chunks like so: +{{#needs_render}} +What is special about using `{{{ filename }}}` instead of just `README.md`? You can include R chunks like so: ```{r cars} summary(cars) ``` -You'll still need to render `README.Rmd` regularly, to keep `README.md` up-to-date. +You'll still need to render `{{{ filename }}}` regularly, to keep `README.md` up-to-date. You can also embed plots, for example: @@ -36,4 +50,4 @@ plot(pressure) ``` In that case, don't forget to commit and push the resulting figure files, so they display on GitHub. -{{/Rmd}} +{{/needs_render}} diff --git a/inst/templates/readme-rmd-pre-commit.sh b/inst/templates/readme-rmd-pre-commit.sh index d56327f36..46febb345 100644 --- a/inst/templates/readme-rmd-pre-commit.sh +++ b/inst/templates/readme-rmd-pre-commit.sh @@ -1,13 +1,13 @@ #!/bin/bash -README=($(git diff --cached --name-only | grep -Ei '^README\.[R]?md$')) +README=($(git diff --cached --name-only | grep -Ei '^README\.([R,q]?md)$')) MSG="use 'git commit --no-verify' to override this check" if [[ ${#README[@]} == 0 ]]; then exit 0 fi -if [[ README.Rmd -nt README.md ]]; then - echo -e "README.md is out of date; please re-knit README.Rmd\n$MSG" +if [[ README.Rmd -nt README.md || README.qmd -nt README.md ]]; then + echo -e "README.md is out of date; please re-knit README.Rmd or README.qmd\n$MSG" exit 1 elif [[ ${#README[@]} -lt 2 ]]; then echo -e "README.Rmd and README.md should be both staged\n$MSG" diff --git a/man/badges.Rd b/man/badges.Rd index 8dcafbf09..c21b648ec 100644 --- a/man/badges.Rd +++ b/man/badges.Rd @@ -52,8 +52,8 @@ badges that report information, such as the CRAN version or test coverage, and link out to relevant external resources. To allow badges to be added automatically, ensure your badge block starts with a line containing only \verb{} and ends with a line containing only -\verb{}. The templates used by \code{\link[=use_readme_md]{use_readme_md()}} and -\code{\link[=use_readme_rmd]{use_readme_rmd()}} include this block. +\verb{}. The templates used by \code{\link[=use_readme_md]{use_readme_md()}}, +\code{\link[=use_readme_rmd]{use_readme_rmd()}}, and \code{\link[=use_readme_qmd]{use_readme_qmd()}} include this block. } \details{ \itemize{ diff --git a/man/use_readme_rmd.Rd b/man/use_readme_rmd.Rd index a7d8e0570..591f45e7b 100644 --- a/man/use_readme_rmd.Rd +++ b/man/use_readme_rmd.Rd @@ -2,11 +2,14 @@ % Please edit documentation in R/readme.R \name{use_readme_rmd} \alias{use_readme_rmd} +\alias{use_readme_qmd} \alias{use_readme_md} \title{Create README files} \usage{ use_readme_rmd(open = rlang::is_interactive()) +use_readme_qmd(open = rlang::is_interactive()) + use_readme_md(open = rlang::is_interactive()) } \arguments{ @@ -21,27 +24,28 @@ Creates skeleton README files with possible stubs for \item a basic example } -Use \code{Rmd} if you want a rich intermingling of code and output. Use \code{md} for a -basic README. \code{README.Rmd} will be automatically added to \code{.Rbuildignore}. +Use \code{qmd} or \code{Rmd} if you want a rich intermingling of code and output. Use \code{md} for a +basic README. \verb{README.[R,q]md} will be automatically added to \code{.Rbuildignore}. The resulting README is populated with default YAML frontmatter and R fenced -code blocks (\code{md}) or chunks (\code{Rmd}). +code blocks (\code{md}) or chunks (\code{Rmd}/\code{qmd}). -If you use \code{Rmd}, you'll still need to render it regularly, to keep +If you use \verb{[R,q]md}, you'll still need to render it regularly, to keep \code{README.md} up-to-date. \code{devtools::build_readme()} is handy for this. You -could also use GitHub Actions to re-render \code{README.Rmd} every time you push. +could also use GitHub Actions to re-render \verb{README.[R,q]md} every time you push. An example workflow can be found in the \verb{examples/} directory here: \url{https://github.com/r-lib/actions/}. -If the current project is a Git repo, then \code{use_readme_rmd()} automatically -configures a pre-commit hook that helps keep \code{README.Rmd} and \code{README.md}, +If the current project is a Git repo, then \verb{use_readme_[r,q]md()} automatically +configures a pre-commit hook that helps keep \verb{README.[R,q]md} and \code{README.md}, synchronized. The hook creates friction if you try to commit when -\code{README.Rmd} has been edited more recently than \code{README.md}. If this hook +\verb{README.[R,q]md} has been edited more recently than \code{README.md}. If this hook causes more problems than it solves for you, it is implemented in \code{.git/hooks/pre-commit}, which you can modify or even delete. } \examples{ \dontrun{ use_readme_rmd() +use_readme_qmd() use_readme_md() } } diff --git a/tests/testthat/test-readme.R b/tests/testthat/test-readme.R index f2c8a0f4d..e9f91c09a 100644 --- a/tests/testthat/test-readme.R +++ b/tests/testthat/test-readme.R @@ -12,6 +12,14 @@ test_that("use_readme_rmd() creates README.Rmd", { expect_proj_file("README.Rmd") }) +test_that("use_readme_qmd() creates README.qmd", { + skip_if_not_installed("quarto") + + create_local_package() + use_readme_qmd() + expect_proj_file("README.qmd") +}) + test_that("use_readme_rmd() sets up git pre-commit hook if pkg uses git", { skip_if_no_git_user() skip_if_not_installed("rmarkdown") @@ -65,3 +73,19 @@ test_that("use_readme_rmd() has expected form for a GitHub package", { transform = scrub_testpkg ) }) + +test_that("Disallow creation of README.Rmd if README.qmd exists", { + skip_if_not_installed("quarto") + create_local_package() + use_readme_qmd() + expect_proj_file("README.qmd") + expect_error(use_readme_rmd()) +}) + +test_that("Disallow creation of README.qmd if README.Rmd exists", { + skip_if_not_installed("rmarkdown") + create_local_package() + use_readme_rmd() + expect_proj_file("README.Rmd") + expect_error(use_readme_qmd()) +})