diff --git a/NAMESPACE b/NAMESPACE index 3ec8b6f4b..2663acdaa 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -159,6 +159,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/NEWS.md b/NEWS.md index 7331c9d3a..39437a9d8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # usethis (development version) +* `use_readme_qmd()` creates a starter `README.qmd` with Quarto-flavored YAML frontmatter, analogous to `use_readme_rmd()` (#1671). Thanks @VisruthSK for getting the ball rolling. + * `use_pipe()` is now deprecated (@math-mcshane, #2124). * `use_claude_code()` is an experimental helper to set up Claude code in an R package in the same way as the tidyverse team (#2195). diff --git a/R/badge.R b/R/badge.R index 7dcf84464..21834ba26 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_qmd()], +#' [use_readme_rmd()], and [use_readme_md()] and include this block. #' #' @details #' @@ -53,7 +53,7 @@ use_badge <- function(badge_name, href, src) { if (is.null(path)) { ui_bullets(c( "!" = "Can't find a README for the current project.", - "i" = "See {.fun usethis::use_readme_rmd} for help creating this file.", + "i" = "See {.fun usethis::use_readme_qmd} or {.fun usethis::use_readme_rmd} for help creating this file.", "i" = "Badge link will only be printed to screen." )) path <- "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.qmd", "README.Rmd", "README.md"))) } diff --git a/R/readme.R b/R/readme.R index cf1a3651c..e63ce2dca 100644 --- a/R/readme.R +++ b/R/readme.R @@ -6,22 +6,25 @@ #' * 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`. -#' The resulting README is populated with default YAML frontmatter and R fenced -#' code blocks (`md`) or chunks (`Rmd`). +#' Use `qmd` or `Rmd` if you want a rich intermingling of code and output. +#' Use `md` for a basic README. `README.qmd` and `README.Rmd` will be +#' automatically added to `.Rbuildignore`. The resulting README is populated +#' with default YAML frontmatter and R fenced code blocks (`md`) or +#' chunks (`qmd`, `Rmd`). #' -#' If you use `Rmd`, 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. -#' An example workflow can be found in the `examples/` directory here: +#' If you use `qmd` or `Rmd`, 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.qmd` or +#' `README.Rmd` 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`, -#' synchronized. The hook creates friction if you try to commit when -#' `README.Rmd` has been edited more recently than `README.md`. If this hook -#' causes more problems than it solves for you, it is implemented in +#' If the current project is a Git repo, then `use_readme_qmd()` and +#' `use_readme_rmd()` automatically configure a pre-commit hook that helps +#' keep `README.md` synchronized with the source file. The hook creates +#' friction if you try to commit when `README.qmd` or `README.Rmd` 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. #' #' @inheritParams use_template @@ -31,6 +34,7 @@ #' @export #' @examples #' \dontrun{ +#' use_readme_qmd() #' use_readme_rmd() #' use_readme_md() #' } @@ -65,6 +69,63 @@ use_readme_rmd <- function(open = rlang::is_interactive()) { )) } + ui_bullets(c( + "_" = "Use {.fun devtools::build_readme} to render {.path {pth('README.Rmd')}}." + )) + + if (uses_git()) { + use_git_hook( + "pre-commit", + render_template("readme-rmd-pre-commit.sh") + ) + } + + invisible(TRUE) +} + +#' @export +#' @rdname use_readme_rmd +use_readme_qmd <- 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(), + on_github = !is.null(repo_spec), + github_spec = repo_spec + ) + + new <- use_template( + if (is_pkg) "package-README-qmd" else "project-README-qmd", + "README.qmd", + data = data, + ignore = is_pkg, + open = open + ) + if (!new) { + return(invisible(FALSE)) + } + + if (is_pkg && !data$on_github) { + ui_bullets(c( + "_" = "Update {.path {pth('README.qmd')}} to include installation instructions." + )) + } + + if (file_exists(proj_path("README.Rmd"))) { + ui_bullets(c( + "!" = "A pre-existing {.path {pth('README.Rmd')}} was found.", + "_" = "Migrate its content to {.path {pth('README.qmd')}}.", + "_" = "Delete {.path {pth('README.Rmd')}} when the migration is done." + )) + } + + ui_bullets(c( + "_" = "Use {.fun devtools::build_readme} to render {.path {pth('README.qmd')}}." + )) + if (uses_git()) { use_git_hook( "pre-commit", 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/tidyverse.R b/R/tidyverse.R index b75429cf4..910a6b47f 100644 --- a/R/tidyverse.R +++ b/R/tidyverse.R @@ -66,7 +66,7 @@ create_tidy_package <- function(path, copyright_holder = NULL) { use_mit_license(copyright_holder) use_tidy_description() - use_readme_rmd(open = FALSE) + use_readme_qmd(open = FALSE) use_lifecycle_badge("experimental") use_cran_badge() diff --git a/R/upkeep.R b/R/upkeep.R index 0beb7485a..a8dc1b169 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_qmd()` or `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()), @@ -161,7 +165,7 @@ tidy_upkeep_checklist <- function( bullets, "### Pre-history", "", - todo("`usethis::use_readme_rmd()`"), + todo("`usethis::use_readme_qmd()` or `usethis::use_readme_rmd()`"), todo("`usethis::use_roxygen_md()`"), todo("`usethis::use_github_links()`"), todo("`usethis::use_pkgdown_github_pages()`"), diff --git a/inst/templates/package-README-qmd b/inst/templates/package-README-qmd new file mode 100644 index 000000000..2c1c08955 --- /dev/null +++ b/inst/templates/package-README-qmd @@ -0,0 +1,71 @@ +--- +format: + gfm: + default-image-extension: "" +--- + + + +```{r} +#| include: false +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.path = "man/figures/README-", + out.width = "100%" +) +``` + +# {{{ Package }}} + + + + +The goal of {{{ Package }}} is to ... + +## Installation + +{{#on_github}} +You can install the development version of {{{ Package }}} from [GitHub](https://github.com/) with: + +``` r +# install.packages("pak") +pak::pak("{{{ github_spec }}}") +``` +{{/on_github}} +{{^on_github}} +You can install the development version of {{{ Package }}} like so: + +``` r +# FILL THIS IN! HOW CAN PEOPLE INSTALL YOUR DEV PACKAGE? +``` +{{/on_github}} + +## Example + +This is a basic example which shows you how to solve a common problem: + +```{r} +#| label: example +library({{Package}}) +## basic example code +``` + +What is special about using `README.qmd` instead of just `README.md`? You can include R chunks like so: + +```{r} +#| label: cars +summary(cars) +``` + +You'll still need to render `README.qmd` regularly, to keep `README.md` up-to-date. `devtools::build_readme()` is handy for this. + +You can also embed plots, for example: + +```{r} +#| label: pressure +#| echo: false +plot(pressure) +``` + +In that case, don't forget to commit and push the resulting figure files, so they display on GitHub and CRAN. diff --git a/inst/templates/project-README-qmd b/inst/templates/project-README-qmd new file mode 100644 index 000000000..0fdf6677f --- /dev/null +++ b/inst/templates/project-README-qmd @@ -0,0 +1,41 @@ +--- +format: + gfm: + default-image-extension: "" +--- + + + +```{r} +#| include: false +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +# {{{ Project }}} + + + + +The goal of {{{ Project }}} is to ... + +What is special about using `README.qmd` instead of just `README.md`? You can include R chunks like so: + +```{r} +#| label: cars +summary(cars) +``` + +You'll still need to render `README.qmd` regularly, to keep `README.md` up-to-date. + +You can also embed plots, for example: + +```{r} +#| label: pressure +#| echo: false +plot(pressure) +``` + +In that case, don't forget to commit and push the resulting figure files, so they display on GitHub. diff --git a/inst/templates/readme-rmd-pre-commit.sh b/inst/templates/readme-rmd-pre-commit.sh index d56327f36..fce1e3845 100644 --- a/inst/templates/readme-rmd-pre-commit.sh +++ b/inst/templates/readme-rmd-pre-commit.sh @@ -1,15 +1,15 @@ #!/bin/bash -README=($(git diff --cached --name-only | grep -Ei '^README\.[R]?md$')) +README=($(git diff --cached --name-only | grep -Ei '^README\.[qR]?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.qmd -nt README.md ]] || [[ README.Rmd -nt README.md ]]; then + echo -e "README.md is out of date; please re-render README.qmd/README.Rmd\n$MSG" exit 1 elif [[ ${#README[@]} -lt 2 ]]; then - echo -e "README.Rmd and README.md should be both staged\n$MSG" + echo -e "README.qmd/README.Rmd and README.md should be both staged\n$MSG" exit 1 fi diff --git a/man/badges.Rd b/man/badges.Rd index 8dcafbf09..962ede275 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_qmd]{use_readme_qmd()}}, +\code{\link[=use_readme_rmd]{use_readme_rmd()}}, and \code{\link[=use_readme_md]{use_readme_md()}} and include this block. } \details{ \itemize{ diff --git a/man/use_readme_rmd.Rd b/man/use_readme_rmd.Rd index a7d8e0570..80123babc 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,26 +24,30 @@ 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}. -The resulting README is populated with default YAML frontmatter and R fenced -code blocks (\code{md}) or chunks (\code{Rmd}). +Use \code{qmd} or \code{Rmd} if you want a rich intermingling of code and output. +Use \code{md} for a basic README. \code{README.qmd} and \code{README.Rmd} 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{qmd}, \code{Rmd}). -If you use \code{Rmd}, 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. -An example workflow can be found in the \verb{examples/} directory here: +If you use \code{qmd} or \code{Rmd}, 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.qmd} or +\code{README.Rmd} 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}, -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 -causes more problems than it solves for you, it is implemented in +If the current project is a Git repo, then \code{use_readme_qmd()} and +\code{use_readme_rmd()} automatically configure a pre-commit hook that helps +keep \code{README.md} synchronized with the source file. The hook creates +friction if you try to commit when \code{README.qmd} or \code{README.Rmd} 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_qmd() use_readme_rmd() use_readme_md() } diff --git a/tests/testthat/_snaps/badge.md b/tests/testthat/_snaps/badge.md index 9d249c1b8..de3eb82a5 100644 --- a/tests/testthat/_snaps/badge.md +++ b/tests/testthat/_snaps/badge.md @@ -23,7 +23,8 @@ use_r_universe_badge("OWNER_DIRECT/SCRUBBED") Message ! Can't find a README for the current project. - i See `usethis::use_readme_rmd()` for help creating this file. + i See `usethis::use_readme_qmd()` or `usethis::use_readme_rmd()` for help + creating this file. i Badge link will only be printed to screen. [ ] Copy and paste the following lines into 'README': @@ -36,7 +37,8 @@ use_r_universe_badge() Message ! Can't find a README for the current project. - i See `usethis::use_readme_rmd()` for help creating this file. + i See `usethis::use_readme_qmd()` or `usethis::use_readme_rmd()` for help + creating this file. i Badge link will only be printed to screen. [ ] Copy and paste the following lines into 'README': @@ -49,7 +51,8 @@ use_r_universe_badge() Message ! Can't find a README for the current project. - i See `usethis::use_readme_rmd()` for help creating this file. + i See `usethis::use_readme_qmd()` or `usethis::use_readme_rmd()` for help + creating this file. i Badge link will only be printed to screen. [ ] Copy and paste the following lines into 'README': diff --git a/tests/testthat/_snaps/coverage.md b/tests/testthat/_snaps/coverage.md index df963ad75..11cd9f86b 100644 --- a/tests/testthat/_snaps/coverage.md +++ b/tests/testthat/_snaps/coverage.md @@ -4,7 +4,8 @@ use_codecov_badge("OWNER/REPO") Message ! Can't find a README for the current project. - i See `usethis::use_readme_rmd()` for help creating this file. + i See `usethis::use_readme_qmd()` or `usethis::use_readme_rmd()` for help + creating this file. i Badge link will only be printed to screen. [ ] Copy and paste the following lines into 'README': diff --git a/tests/testthat/_snaps/readme.md b/tests/testthat/_snaps/readme.md index 631898991..5fcd9e357 100644 --- a/tests/testthat/_snaps/readme.md +++ b/tests/testthat/_snaps/readme.md @@ -182,3 +182,151 @@ In that case, don't forget to commit and push the resulting figure files, so they display on GitHub and CRAN. +# use_readme_qmd() notices a pre-existing README.Rmd + + Code + use_readme_qmd() + Message + v Writing 'README.qmd'. + v Adding "^README\\.qmd$" to '.Rbuildignore'. + [ ] Update 'README.qmd' to include installation instructions. + ! A pre-existing 'README.Rmd' was found. + [ ] Migrate its content to 'README.qmd'. + [ ] Delete 'README.Rmd' when the migration is done. + [ ] Use `devtools::build_readme()` to render 'README.qmd'. + +# use_readme_qmd() has expected form for a non-GitHub package + + Code + writeLines(read_utf8("README.qmd")) + Output + --- + format: + gfm: + default-image-extension: "" + --- + + + + ```{r} + #| include: false + knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.path = "man/figures/README-", + out.width = "100%" + ) + ``` + + # {TESTPKG} + + + + + The goal of {TESTPKG} is to ... + + ## Installation + + You can install the development version of {TESTPKG} like so: + + ``` r + # FILL THIS IN! HOW CAN PEOPLE INSTALL YOUR DEV PACKAGE? + ``` + + ## Example + + This is a basic example which shows you how to solve a common problem: + + ```{r} + #| label: example + library({TESTPKG}) + ## basic example code + ``` + + What is special about using `README.qmd` instead of just `README.md`? You can include R chunks like so: + + ```{r} + #| label: cars + summary(cars) + ``` + + You'll still need to render `README.qmd` regularly, to keep `README.md` up-to-date. `devtools::build_readme()` is handy for this. + + You can also embed plots, for example: + + ```{r} + #| label: pressure + #| echo: false + plot(pressure) + ``` + + In that case, don't forget to commit and push the resulting figure files, so they display on GitHub and CRAN. + +# use_readme_qmd() has expected form for a GitHub package + + Code + writeLines(read_utf8("README.qmd")) + Output + --- + format: + gfm: + default-image-extension: "" + --- + + + + ```{r} + #| include: false + knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.path = "man/figures/README-", + out.width = "100%" + ) + ``` + + # {TESTPKG} + + + + + The goal of {TESTPKG} is to ... + + ## Installation + + You can install the development version of {TESTPKG} from [GitHub](https://github.com/) with: + + ``` r + # install.packages("pak") + pak::pak("OWNER/TESTPKG") + ``` + + ## Example + + This is a basic example which shows you how to solve a common problem: + + ```{r} + #| label: example + library({TESTPKG}) + ## basic example code + ``` + + What is special about using `README.qmd` instead of just `README.md`? You can include R chunks like so: + + ```{r} + #| label: cars + summary(cars) + ``` + + You'll still need to render `README.qmd` regularly, to keep `README.md` up-to-date. `devtools::build_readme()` is handy for this. + + You can also embed plots, for example: + + ```{r} + #| label: pressure + #| echo: false + plot(pressure) + ``` + + In that case, don't forget to commit and push the resulting figure files, so they display on GitHub and CRAN. + diff --git a/tests/testthat/_snaps/upkeep.md b/tests/testthat/_snaps/upkeep.md index 33652db98..e21686061 100644 --- a/tests/testthat/_snaps/upkeep.md +++ b/tests/testthat/_snaps/upkeep.md @@ -9,7 +9,7 @@ ### Pre-history - * [ ] `usethis::use_readme_rmd()` + * [ ] `usethis::use_readme_qmd()` or `usethis::use_readme_rmd()` * [ ] `usethis::use_roxygen_md()` * [ ] `usethis::use_github_links()` * [ ] `usethis::use_pkgdown_github_pages()` @@ -122,7 +122,7 @@ Code writeLines(upkeep_checklist()) Output - * [ ] `usethis::use_readme_rmd()` + * [ ] `usethis::use_readme_qmd()` or `usethis::use_readme_rmd()` * [ ] `usethis::use_github_links()` * [ ] `usethis::use_pkgdown_github_pages()` * [ ] `usethis::use_tidy_description()` @@ -144,7 +144,7 @@ Code writeLines(checklist) Output - * [ ] `usethis::use_readme_rmd()` + * [ ] `usethis::use_readme_qmd()` or `usethis::use_readme_rmd()` * [ ] `usethis::use_github_links()` * [ ] `usethis::use_pkgdown_github_pages()` * [ ] `usethis::use_tidy_description()` diff --git a/tests/testthat/test-readme.R b/tests/testthat/test-readme.R index f2c8a0f4d..b28a23025 100644 --- a/tests/testthat/test-readme.R +++ b/tests/testthat/test-readme.R @@ -65,3 +65,52 @@ test_that("use_readme_rmd() has expected form for a GitHub package", { transform = scrub_testpkg ) }) + +test_that("use_readme_qmd() creates README.qmd", { + create_local_package() + use_readme_qmd() + expect_proj_file("README.qmd") +}) + +test_that("use_readme_qmd() sets up git pre-commit hook if pkg uses git", { + skip_if_no_git_user() + + create_local_package() + use_git() + use_readme_qmd(open = FALSE) + expect_proj_file(".git", "hooks", "pre-commit") +}) + +test_that("use_readme_qmd() notices a pre-existing README.Rmd", { + local_interactive(FALSE) + + create_local_package() + use_readme_rmd() + withr::local_options(usethis.quiet = FALSE) + expect_snapshot(use_readme_qmd(), transform = scrub_testpkg) + expect_proj_file("README.qmd") + expect_proj_file("README.Rmd") +}) + +test_that("use_readme_qmd() has expected form for a non-GitHub package", { + local_interactive(FALSE) + + create_local_package() + use_readme_qmd() + expect_snapshot( + writeLines(read_utf8("README.qmd")), + transform = scrub_testpkg + ) +}) + +test_that("use_readme_qmd() has expected form for a GitHub package", { + local_interactive(FALSE) + local_target_repo_spec("OWNER/TESTPKG") + + create_local_package() + use_readme_qmd() + expect_snapshot( + writeLines(read_utf8("README.qmd")), + transform = scrub_testpkg + ) +})