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/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/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/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()) +})