From 1dc845cbb4b5ce1502022fe4fed9896db67bc91e Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 12 Feb 2024 15:28:01 -0800 Subject: [PATCH 001/111] Init article on the conversion task --- vignettes/articles/ui-cli-conversion.Rmd | 184 +++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 vignettes/articles/ui-cli-conversion.Rmd diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd new file mode 100644 index 000000000..88e24646b --- /dev/null +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -0,0 +1,184 @@ +--- +title: "Converting usethis's UI to use cli" +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r setup} +library(usethis) +``` + +# Review of the *status quo* + +I'll start with a review of the current (soon to be legacy) `usethis::ui_*()` functions. + +## Block styles + +The block styles mostly exist to produce bulleted output with a specific symbol, using a specific color. + +```{r} +f <- function() { + ui_todo("ui_todo(): red bullet") + ui_done("ui_done(): green check") + ui_oops("ui_oops(): red x") + ui_info("ui_info(): yellow i") + ui_line("ui_line(): (no symbol)") +} +f() +``` + +These styles are very close to what can be done with `cli::cli_bullets()` and the way it responds to the names of its input `text`. + +```{r} +cli::cli_bullets(c( + "noindent", + " " = "indent", + "*" = "bullet", + ">" = "arrow", + "v" = "success", + "x" = "danger", + "!" = "warning", + "i" = "info" +)) +``` + +A direct translation looks something like this: + +| `ui_*()` | `cli_bullets()` shortcode | tweaks needed | +|-----------------|------------------------|-------------------------------| +| `ui_todo()` | `*` | blue (default) -\> red | +| `ui_done()` | `v` | perfect match | +| `ui_oops()` | `x` | perfect match | +| `ui_info()` | `i` | blue (default) -\> yellow | +| `ui_line()` | (unnamed) | might be perfect match? although sometimes ui_line is used just to get a blank line | + +I'm experimenting with a few more tweaks related to TODOs: + +- Use `cli::symbol$checkbox_off` as the symbol (instead of a generic bullet). I guess it will continue to be red. +- Introduce a new bullet shortcode for a TODO. Proposal: `_` (instead of `*`), which seems to be the best single ascii character that evokes a place to check something off. + +That just leaves `code_block()`, which is pretty different. +It exists to put some code on the screen and optionally place it on the clipboard. + +```{r} +ui_code_block(" + options( + warnPartialMatchArgs = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchAttr = TRUE + )") +``` + +I think I should build around `cli::code_block()` for this. + +## Utility functions + +The block style functions all route through some unexported utility functions. + +`is_quiet()` just consults the `usethis.quiet` option and implements the default of `FALSE`. + +```{r} +#| eval: false +is_quiet <- function() { + isTRUE(getOption("usethis.quiet", default = FALSE)) +} +``` + +`ui_todo()`, `ui_done()`, `ui_oops()`, `ui_info()` all route through `ui_bullet()`, which does some hygiene related to indentation (using the `indent()` utility function), then calls `ui_inform()`. +`ui_line()` and `ui_code()` call `ui_inform()` directly. + +`ui_inform()` is just a wrapper around `rlang::inform()` that is guarded by a call to `is_quiet()` + +```{r} +#| eval: false +ui_inform <- function(...) { + if (!is_quiet()) { + inform(paste0(...)) + } + invisible() +} +``` + +Other than `is_quiet()`, which will continue to play the same role, I anticipate that we no longer need these utilities (`indent()`, `ui_bullet()`, `ui_inform()`). + +Let's cover `ui_silence()` while we're here, which *is* exported. +It's just a `withr::with_*()` function for executing `code` with `usethis.quiet = TRUE`. + +```{r} +#| eval: false +ui_silence <- function(code) { + withr::with_options(list(usethis.quiet = TRUE), code) +} +``` + +## Inline styles + + + +- `ui_field()` becomes `{.field blah}` +- `ui_value()` becomes `{.val value}` +- `ui_path()` will draw on `{.file path/to/thing}` but some of `ui_path()`'s logic is still needed (e.g. making path relative to project root, ensuring a directory has a trailing `/`). Should also think about hyperlink behaviour. +- `ui_code()` probably becomes one of: + - `{.arg some_argument}` + + - `{.cls some_class}` + + - `{.code some_text}` + + - `{.envvar SOME_ENV_VAR}` + + - `{.fun devtools::build_readme}` + + - `{.help some_function}` + + - `{.run some_code}` + + - `{.topic some_topic}` + + - `{.var variable_name}` + +(I'll talk about `ui_unset()` below.) + +## Conditions + +```{r} +ui_stop <- function(x, .envir = parent.frame()) { + x <- glue_collapse(x, "\n") + x <- glue(x, .envir = .envir) + + cnd <- structure( + class = c("usethis_error", "error", "condition"), + list(message = x) + ) + + stop(cnd) +} + +ui_warn <- function(x, .envir = parent.frame()) { + x <- glue_collapse(x, "\n") + x <- glue(x, .envir = .envir) + + warning(x, call. = FALSE, immediate. = TRUE) +} +``` + +I think `ui_stop()`, becomes a wrapper around `cli::cli_abort()` that applies the `usethis_error` class. + +I suppose `ui_warn() probably just becomes `cli::cli_warn()`. + +## Sitrep and format helpers + +This is a small clump of functions that support sitrep-type output. + +- `hd_line()` +- `kv_line()` +- `ui_unset()` + +## Questions + +There's currently no cli alternative to `ui_yeah()` and `ui_nope()`, so I won't make changes here. From f7b906a8852b3289e8cea75e42c1a7873a7c7e33 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 13 Feb 2024 12:32:04 -0800 Subject: [PATCH 002/111] Build up the cli-based UI in one place --- R/ui.R | 10 ---------- R/utils-ui.R | 7 +++++++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/R/ui.R b/R/ui.R index e1a11405e..d7a062049 100644 --- a/R/ui.R +++ b/R/ui.R @@ -321,13 +321,3 @@ kv_line <- function(key, value, .envir = parent.frame()) { key <- glue(key, .envir = .envir) ui_inform(glue("{cli::symbol$bullet} {key}: {value}")) } - - -# cli wrappers ------------------------------------------------------------ - -ui_cli_inform <- function(..., .envir = parent.frame()) { - if (!is_quiet()) { - cli::cli_inform(..., .envir = .envir) - } - invisible() -} diff --git a/R/utils-ui.R b/R/utils-ui.R index e57d7c14f..709956602 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -1,5 +1,12 @@ # opening act of an eventual transition away from the ui_*() functions and towards # the cli-mediated UI we're using in other packages +# TODO: get rid of this because it doesn't honor usethis.quiet +ui_cli_inform <- function(..., .envir = parent.frame()) { + if (!is_quiet()) { + cli::cli_inform(..., .envir = .envir) + } + invisible() +} usethis_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { #cli::cli_div(theme = usethis_theme()) From 21946234509f102ea39d2f0199851835fb23e651 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 13 Feb 2024 13:00:31 -0800 Subject: [PATCH 003/111] Introduce ui_cli_bullets() --- R/release.R | 6 ++-- R/utils-ui.R | 29 +++++++++++----- tests/testthat/_snaps/ui.md | 4 +-- tests/testthat/_snaps/utils-ui.md | 32 +++++++++++++++++ tests/testthat/test-ui.R | 4 +-- tests/testthat/test-utils-ui.R | 57 +++++++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 tests/testthat/_snaps/utils-ui.md create mode 100644 tests/testthat/test-utils-ui.R diff --git a/R/release.R b/R/release.R index 6114dde9b..6772f6265 100644 --- a/R/release.R +++ b/R/release.R @@ -262,7 +262,7 @@ use_github_release <- function(publish = TRUE, gh <- gh_tr(tr) - ui_cli_inform("Publishing {tag_name} release to GitHub") + ui_cli_bullets("Publishing {tag_name} release to GitHub") release <- gh( "POST /repos/{owner}/{repo}/releases", name = release_name, @@ -271,10 +271,10 @@ use_github_release <- function(publish = TRUE, body = news, draft = !publish ) - ui_cli_inform("Release at {.url {release$html_url}}") + ui_cli_bullets("Release at {.url {release$html_url}}") if (!is.null(dat$file)) { - ui_cli_inform("Deleting {.path {dat$file}}") + ui_cli_bullets("Deleting {.path {dat$file}}") file_delete(dat$file) } diff --git a/R/utils-ui.R b/R/utils-ui.R index 709956602..01e39e70f 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -1,15 +1,28 @@ -# opening act of an eventual transition away from the ui_*() functions and towards -# the cli-mediated UI we're using in other packages -# TODO: get rid of this because it doesn't honor usethis.quiet -ui_cli_inform <- function(..., .envir = parent.frame()) { - if (!is_quiet()) { - cli::cli_inform(..., .envir = .envir) +usethis_theme <- function() { + list( + # add a "todo" bullet, which is intended to be seen as an unchecked checkbox + ".bullets .bullet-_" = list( + "text-exdent" = 2, + before = function(x) paste0(cli::col_red(cli::symbol$checkbox_off), " ") + ), + # historically, usethis has used yellow for this + ".bullets .bullet-i" = list( + "text-exdent" = 2, + before = function(x) paste0(cli::col_yellow(cli::symbol$info), " ") + ) + ) +} + +ui_cli_bullets <- function(text, .envir = parent.frame()) { + if (is_quiet()) { + return(invisible()) } - invisible() + cli::cli_div(theme = usethis_theme()) + cli::cli_bullets(text, .envir = .envir) } usethis_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { - #cli::cli_div(theme = usethis_theme()) + cli::cli_div(theme = usethis_theme()) cli::cli_abort( message, class = c(class, "usethis_error"), diff --git a/tests/testthat/_snaps/ui.md b/tests/testthat/_snaps/ui.md index dfb8fea2e..37c1c6149 100644 --- a/tests/testthat/_snaps/ui.md +++ b/tests/testthat/_snaps/ui.md @@ -1,4 +1,4 @@ -# basic UI actions behave as expected +# basic legacy UI actions behave as expected Code ui_line("line") @@ -31,7 +31,7 @@ Warning: a warning -# UI actions respect usethis.quiet = TRUE +# legacy UI actions respect usethis.quiet = TRUE Code ui_line("line") diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md new file mode 100644 index 000000000..94c85e20b --- /dev/null +++ b/tests/testthat/_snaps/utils-ui.md @@ -0,0 +1,32 @@ +# ui_cli_bullets() look as expected + + Code + ui_cli_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", + ` ` = "indent", `*` = "bullet", `>` = "arrow", `!` = "warning")) + Message + [ ] todo + v done + x oops + i info + noindent + indent + * bullet + > arrow + ! warning + +# ui_cli_bullets() respect usethis.quiet = TRUE + + Code + ui_cli_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", + ` ` = "indent", `*` = "bullet", `>` = "arrow", `!` = "warning")) + +# ui_cli_bullets() does glue interpolation and inline markup + + Code + ui_cli_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", + x = "Scary {.code code} or {.fun function}")) + Message + i Hello, world! + v Updated the BugReports field + x Scary `code` or `function()` + diff --git a/tests/testthat/test-ui.R b/tests/testthat/test-ui.R index d1bf0b2cc..f2f67f17b 100644 --- a/tests/testthat/test-ui.R +++ b/tests/testthat/test-ui.R @@ -1,4 +1,4 @@ -test_that("basic UI actions behave as expected", { +test_that("basic legacy UI actions behave as expected", { # suppress test silencing withr::local_options(list(usethis.quiet = FALSE)) @@ -13,7 +13,7 @@ test_that("basic UI actions behave as expected", { }) }) -test_that("UI actions respect usethis.quiet = TRUE", { +test_that("legacy UI actions respect usethis.quiet = TRUE", { withr::local_options(list(usethis.quiet = TRUE)) expect_snapshot({ diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R new file mode 100644 index 000000000..fe7ca8f82 --- /dev/null +++ b/tests/testthat/test-utils-ui.R @@ -0,0 +1,57 @@ +test_that("ui_cli_bullets() look as expected", { + # suppress test silencing + withr::local_options(list(usethis.quiet = FALSE)) + + expect_snapshot( + ui_cli_bullets(c( + # relate to legacy functions + "_" = "todo", # ui_todo() + "v" = "done", # ui_done() + "x" = "oops", # ui_oops() + "i" = "info", # ui_info() + "noindent", # ui_line() + + # other cli bullets that have no special connection to usethis history + " " = "indent", + "*" = "bullet", + ">" = "arrow", + "!" = "warning" + )) + ) +}) + +test_that("ui_cli_bullets() respect usethis.quiet = TRUE", { + withr::local_options(list(usethis.quiet = TRUE)) + + expect_snapshot( + ui_cli_bullets(c( + # relate to legacy functions + "_" = "todo", # ui_todo() + "v" = "done", # ui_done() + "x" = "oops", # ui_oops() + "i" = "info", # ui_info() + "noindent", # ui_line() + + # other cli bullets that have no special connection to usethis history + " " = "indent", + "*" = "bullet", + ">" = "arrow", + "!" = "warning" + )) + ) +}) + +test_that("ui_cli_bullets() does glue interpolation and inline markup", { + # suppress test silencing + withr::local_options(list(usethis.quiet = FALSE)) + + x <- "world" + + expect_snapshot( + ui_cli_bullets(c( + "i" = "Hello, {x}!", + "v" = "Updated the {.field BugReports} field", + "x" = "Scary {.code code} or {.fun function}" + )) + ) +}) From 26f1d31de9da6432310b4edde2828481962e20e3 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 13 Feb 2024 13:25:38 -0800 Subject: [PATCH 004/111] author.R --- R/author.R | 47 ++++++++++++++++++--------------- tests/testthat/_snaps/author.md | 10 +++---- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/R/author.R b/R/author.R index b31221dd5..31c672bf4 100644 --- a/R/author.R +++ b/R/author.R @@ -60,13 +60,15 @@ use_author <- function(given = NULL, family = NULL, ..., role = "ctb") { author <- utils::person(given = given, family = family, role = role, ...) aut_fmt <- format(author, style = 'text') if (authors_at_r_already) { - ui_done(" - Adding to {ui_field('Authors@R')} in DESCRIPTION: - {aut_fmt}") + ui_cli_bullets(c( + "v" = "Adding to {.field Authors@R} in DESCRIPTION:", + " " = "{aut_fmt}" + )) } else { - ui_done(" - Creating {ui_field('Authors@R')} field in DESCRIPTION and adding: - {aut_fmt}") + ui_cli_bullets(c( + "v" = "Creating {.field Authors@R} field in DESCRIPTION and adding:", + " " = "{aut_fmt}" + )) } d$add_author(given = given, family = family, role = role, ...) @@ -84,17 +86,17 @@ challenge_legacy_author_fields <- function(d = proj_desc()) { return(invisible()) } - ui_oops(" - Found legacy {ui_field('Author')} and/or {ui_field('Maintainer')} field \\ - in DESCRIPTION. - usethis only supports modification of the {ui_field('Authors@R')} field.") - ui_info(" - We recommend one of these paths forward: - * Delete these fields and rebuild with {ui_code('use_author()')}. - * Convert to {ui_field('Authors@R')} with {ui_code('desc::desc_coerce_authors_at_r()')}, - then delete the legacy fields.") + ui_cli_bullets(c( + "x" = "Found legacy {.field Author} and/or {.field Maintainer} field in + DESCRIPTION.", + " " = "usethis only supports modification of the {.field Authors@R} field.", + "i" = "We recommend one of these paths forward:", + "_" = "Delete the legacy fields and rebuild with {.fun use_author}; or", + "_" = "Convert to {.field Authors@R} with + {.fun desc::desc_coerce_authors_at_r}, then delete the legacy fields." + )) if (ui_yeah("Do you want to cancel this operation and sort that out first?")) { - ui_stop("Cancelling.") + usethis_abort("Cancelling.") } invisible() } @@ -109,9 +111,9 @@ check_author_is_novel <- function(given = NULL, family = NULL, d = proj_desc()) if (any(m)) { aut_name <- glue("{given %||% ''} {family %||% ''}") usethis_abort(c( - "{.val {aut_name}} already appears in {.val Authors@R}.", - "Please make the desired change directly in DESCRIPTION or call the \\ - desc package directly." + "x" = "{.val {aut_name}} already appears in {.field Authors@R}.", + " " = "Please make the desired change directly in DESCRIPTION or call the + {.pkg desc} package directly." )) } invisible() @@ -129,9 +131,10 @@ challenge_default_author <- function(d = proj_desc()) { ) if (any(m)) { - ui_info(" - {ui_field('Authors@R')} appears to include a placeholder author: - {format(default_author, style = 'text')}") + ui_cli_bullets(c( + "i" = "{.field Authors@R} appears to include a placeholder author:", + " " = "{format(default_author, style = 'text')}" + )) if(is_interactive() && ui_yeah("Would you like to remove it?")) { # TODO: Do I want to suppress this output? # Authors removed: First Last, NULL NULL. diff --git a/tests/testthat/_snaps/author.md b/tests/testthat/_snaps/author.md index 188af7201..b79592a99 100644 --- a/tests/testthat/_snaps/author.md +++ b/tests/testthat/_snaps/author.md @@ -6,9 +6,9 @@ x Found legacy Author and/or Maintainer field in DESCRIPTION. usethis only supports modification of the Authors@R field. i We recommend one of these paths forward: - * Delete these fields and rebuild with `use_author()`. - * Convert to Authors@R with `desc::desc_coerce_authors_at_r()`, - then delete the legacy fields. + [ ] Delete the legacy fields and rebuild with `use_author()`; or + [ ] Convert to Authors@R with `desc::desc_coerce_authors_at_r()`, then delete + the legacy fields. Condition Error: ! User input required, but session is not interactive. @@ -20,8 +20,8 @@ use_author("Jennifer", "Bryan", role = "cph") Condition Error in `check_author_is_novel()`: - ! "Jennifer Bryan" already appears in "Authors@R". - Please make the desired change directly in DESCRIPTION or call the desc package directly. + x "Jennifer Bryan" already appears in Authors@R. + Please make the desired change directly in DESCRIPTION or call the desc package directly. # Placeholder author is challenged From 7c96412add443e62da7a72aa44a995f3a6b4f3ea Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 15 Feb 2024 14:58:30 -0800 Subject: [PATCH 005/111] Factor out the path-processing logic into ui_path_impl() --- R/ui.R | 13 +------------ R/utils-ui.R | 19 +++++++++++++++++++ tests/testthat/test-ui.R | 17 ----------------- tests/testthat/test-utils-ui.R | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/R/ui.R b/R/ui.R index d7a062049..13afab6f4 100644 --- a/R/ui.R +++ b/R/ui.R @@ -252,18 +252,7 @@ ui_value <- function(x) { #' @export #' @param base If specified, paths will be displayed relative to this path. ui_path <- function(x, base = NULL) { - is_directory <- is_dir(x) | grepl("/$", x) - if (is.null(base)) { - x <- proj_rel_path(x) - } else if (!identical(base, NA)) { - x <- path_rel(x, base) - } - - # rationalize trailing slashes - x <- path_tidy(x) - x <- ifelse(is_directory, paste0(x, "/"), x) - - ui_value(x) + ui_value(ui_path_impl(x, base = base)) } #' @rdname ui diff --git a/R/utils-ui.R b/R/utils-ui.R index 01e39e70f..a558c526e 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -30,3 +30,22 @@ usethis_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { ... ) } + +ui_path_impl <- function(x, base = NULL) { + is_directory <- is_dir(x) | grepl("/$", x) + if (is.null(base)) { + x <- proj_rel_path(x) + } else if (!identical(base, NA)) { + x <- path_rel(x, base) + } + + # rationalize trailing slashes + x <- path_tidy(x) + x[is_directory] <- paste0(x[is_directory], "/") + + unclass(x) +} + +# shorter form for compactness, because this is typical usage: +# ui_bullets("blah blah {.path {pth(some_path)}}") +pth <- ui_path_impl diff --git a/tests/testthat/test-ui.R b/tests/testthat/test-ui.R index f2f67f17b..e3640e265 100644 --- a/tests/testthat/test-ui.R +++ b/tests/testthat/test-ui.R @@ -36,20 +36,3 @@ test_that("ui_silence() suppresses output", { expect_output(ui_silence(ui_line()), NA) }) - -test_that("trailing slash behaviour of ui_path()", { - withr::local_options(list(crayon.enabled = FALSE)) - # target doesn't exist so no empirical evidence that it's a directory - expect_match(ui_path("abc"), "abc'$") - - # path suggests it's a directory - expect_match(ui_path("abc/"), "abc/'$") - expect_match(ui_path("abc//"), "abc/'$") - - # path is known to be a directory - tmpdir <- withr::local_tempdir(pattern = "ui_path") - - expect_match(ui_path(tmpdir), "/'$") - expect_match(ui_path(paste0(tmpdir, "/")), "[^/]/'$") - expect_match(ui_path(paste0(tmpdir, "//")), "[^/]/'$") -}) diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index fe7ca8f82..5fdb617e7 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -55,3 +55,19 @@ test_that("ui_cli_bullets() does glue interpolation and inline markup", { )) ) }) + +test_that("trailing slash behaviour of ui_path_impl()", { + # target doesn't exist so no empirical evidence that it's a directory + expect_match(ui_path_impl("abc"), "abc$") + + # path suggests it's a directory + expect_match(ui_path_impl("abc/"), "abc/$") + expect_match(ui_path_impl("abc//"), "abc/$") + + # path is known to be a directory + tmpdir <- withr::local_tempdir(pattern = "ui_path_impl") + + expect_match(ui_path_impl(tmpdir), "/$") + expect_match(ui_path_impl(paste0(tmpdir, "/")), "[^/]/$") + expect_match(ui_path_impl(paste0(tmpdir, "//")), "[^/]/$") +}) From 1a6322389347fc23c80a374ae4250032efb52b1c Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 13 Feb 2024 14:55:19 -0800 Subject: [PATCH 006/111] addin.R --- R/addin.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/R/addin.R b/R/addin.R index 1072d7115..0f7b429c2 100644 --- a/R/addin.R +++ b/R/addin.R @@ -15,13 +15,15 @@ use_addin <- function(addin = "new_addin", open = rlang::is_interactive()) { if (!file_exists(addin_dcf_path)) { create_directory(proj_path("inst", "rstudio")) file_create(addin_dcf_path) - ui_done("Creating {ui_path(addin_dcf_path)}") + ui_cli_bullets(c("v" = "Creating {.path {pth(addin_dcf_path)}}")) } addin_info <- render_template("addins.dcf", data = list(addin = addin)) addin_info[length(addin_info) + 1] <- "" write_utf8(addin_dcf_path, addin_info, append = TRUE) - ui_done("Adding binding to {ui_code(addin)} to addins.dcf.") + ui_cli_bullets(c( + "v" = "Adding binding to {.fun {addin}} to {.path addins.dcf}" + )) if (open) { edit_file(addin_dcf_path) From de288ca437c812f4d6256632e41427574d0f497e Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 13 Feb 2024 15:05:25 -0800 Subject: [PATCH 007/111] ui_bullets() is better because shorter --- R/addin.R | 4 ++-- R/author.R | 8 ++++---- R/release.R | 6 +++--- R/utils-ui.R | 2 +- tests/testthat/_snaps/utils-ui.md | 17 ++++++++--------- tests/testthat/test-utils-ui.R | 12 ++++++------ 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/R/addin.R b/R/addin.R index 0f7b429c2..6f4a5e905 100644 --- a/R/addin.R +++ b/R/addin.R @@ -15,13 +15,13 @@ use_addin <- function(addin = "new_addin", open = rlang::is_interactive()) { if (!file_exists(addin_dcf_path)) { create_directory(proj_path("inst", "rstudio")) file_create(addin_dcf_path) - ui_cli_bullets(c("v" = "Creating {.path {pth(addin_dcf_path)}}")) + ui_bullets(c("v" = "Creating {.path {pth(addin_dcf_path)}}")) } addin_info <- render_template("addins.dcf", data = list(addin = addin)) addin_info[length(addin_info) + 1] <- "" write_utf8(addin_dcf_path, addin_info, append = TRUE) - ui_cli_bullets(c( + ui_bullets(c( "v" = "Adding binding to {.fun {addin}} to {.path addins.dcf}" )) diff --git a/R/author.R b/R/author.R index 31c672bf4..6e6f06e69 100644 --- a/R/author.R +++ b/R/author.R @@ -60,12 +60,12 @@ use_author <- function(given = NULL, family = NULL, ..., role = "ctb") { author <- utils::person(given = given, family = family, role = role, ...) aut_fmt <- format(author, style = 'text') if (authors_at_r_already) { - ui_cli_bullets(c( + ui_bullets(c( "v" = "Adding to {.field Authors@R} in DESCRIPTION:", " " = "{aut_fmt}" )) } else { - ui_cli_bullets(c( + ui_bullets(c( "v" = "Creating {.field Authors@R} field in DESCRIPTION and adding:", " " = "{aut_fmt}" )) @@ -86,7 +86,7 @@ challenge_legacy_author_fields <- function(d = proj_desc()) { return(invisible()) } - ui_cli_bullets(c( + ui_bullets(c( "x" = "Found legacy {.field Author} and/or {.field Maintainer} field in DESCRIPTION.", " " = "usethis only supports modification of the {.field Authors@R} field.", @@ -131,7 +131,7 @@ challenge_default_author <- function(d = proj_desc()) { ) if (any(m)) { - ui_cli_bullets(c( + ui_bullets(c( "i" = "{.field Authors@R} appears to include a placeholder author:", " " = "{format(default_author, style = 'text')}" )) diff --git a/R/release.R b/R/release.R index 6772f6265..4f8c2eac5 100644 --- a/R/release.R +++ b/R/release.R @@ -262,7 +262,7 @@ use_github_release <- function(publish = TRUE, gh <- gh_tr(tr) - ui_cli_bullets("Publishing {tag_name} release to GitHub") + ui_bullets("Publishing {tag_name} release to GitHub") release <- gh( "POST /repos/{owner}/{repo}/releases", name = release_name, @@ -271,10 +271,10 @@ use_github_release <- function(publish = TRUE, body = news, draft = !publish ) - ui_cli_bullets("Release at {.url {release$html_url}}") + ui_bullets("Release at {.url {release$html_url}}") if (!is.null(dat$file)) { - ui_cli_bullets("Deleting {.path {dat$file}}") + ui_bullets("Deleting {.path {dat$file}}") file_delete(dat$file) } diff --git a/R/utils-ui.R b/R/utils-ui.R index a558c526e..364e0e409 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -13,7 +13,7 @@ usethis_theme <- function() { ) } -ui_cli_bullets <- function(text, .envir = parent.frame()) { +ui_bullets <- function(text, .envir = parent.frame()) { if (is_quiet()) { return(invisible()) } diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index 94c85e20b..6f49da53a 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -1,8 +1,8 @@ -# ui_cli_bullets() look as expected +# ui_bullets() look as expected Code - ui_cli_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", - ` ` = "indent", `*` = "bullet", `>` = "arrow", `!` = "warning")) + ui_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", ` ` = "indent", + `*` = "bullet", `>` = "arrow", `!` = "warning")) Message [ ] todo v done @@ -14,17 +14,16 @@ > arrow ! warning -# ui_cli_bullets() respect usethis.quiet = TRUE +# ui_bullets() respect usethis.quiet = TRUE Code - ui_cli_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", - ` ` = "indent", `*` = "bullet", `>` = "arrow", `!` = "warning")) + ui_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", ` ` = "indent", + `*` = "bullet", `>` = "arrow", `!` = "warning")) -# ui_cli_bullets() does glue interpolation and inline markup +# ui_bullets() does glue interpolation and inline markup Code - ui_cli_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", - x = "Scary {.code code} or {.fun function}")) + ui_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", x = "Scary {.code code} or {.fun function}")) Message i Hello, world! v Updated the BugReports field diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 5fdb617e7..3d0e54aa5 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -1,9 +1,9 @@ -test_that("ui_cli_bullets() look as expected", { +test_that("ui_bullets() look as expected", { # suppress test silencing withr::local_options(list(usethis.quiet = FALSE)) expect_snapshot( - ui_cli_bullets(c( + ui_bullets(c( # relate to legacy functions "_" = "todo", # ui_todo() "v" = "done", # ui_done() @@ -20,11 +20,11 @@ test_that("ui_cli_bullets() look as expected", { ) }) -test_that("ui_cli_bullets() respect usethis.quiet = TRUE", { +test_that("ui_bullets() respect usethis.quiet = TRUE", { withr::local_options(list(usethis.quiet = TRUE)) expect_snapshot( - ui_cli_bullets(c( + ui_bullets(c( # relate to legacy functions "_" = "todo", # ui_todo() "v" = "done", # ui_done() @@ -41,14 +41,14 @@ test_that("ui_cli_bullets() respect usethis.quiet = TRUE", { ) }) -test_that("ui_cli_bullets() does glue interpolation and inline markup", { +test_that("ui_bullets() does glue interpolation and inline markup", { # suppress test silencing withr::local_options(list(usethis.quiet = FALSE)) x <- "world" expect_snapshot( - ui_cli_bullets(c( + ui_bullets(c( "i" = "Hello, {x}!", "v" = "Updated the {.field BugReports} field", "x" = "Scary {.code code} or {.fun function}" From 1db32b41f2fac5ce6cdb9052d957ba33afee0ee6 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 15 Feb 2024 14:57:09 -0800 Subject: [PATCH 008/111] badge.R --- R/badge.R | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/R/badge.R b/R/badge.R index b1317cf5b..fdbdee643 100644 --- a/R/badge.R +++ b/R/badge.R @@ -43,15 +43,15 @@ NULL use_badge <- function(badge_name, href, src) { path <- find_readme() if (is.null(path)) { - ui_oops(" - Can't find a README for the current project. - See {ui_code('usethis::use_readme_rmd()')} for help creating this file. - Badge link can only be printed to screen. - ") + 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" = "Badge link will only be printed to screen." + )) path <- "README" } changed <- block_append( - glue("{ui_field(badge_name)} badge"), + glue("{badge_name} badge"), glue("[![{badge_name}]({src})]({href})"), path = path, block_start = badge_start, @@ -59,7 +59,9 @@ use_badge <- function(badge_name, href, src) { ) if (changed && path_ext(path) == "Rmd") { - ui_todo("Re-knit {ui_path(path)} with {ui_code('devtools::build_readme()')}") + ui_bullets(c( + "_" = "Re-knit {.path {pth(path)}} with {.fun devtools::build_readme}." + )) } invisible(changed) } @@ -154,9 +156,9 @@ use_posit_cloud_badge <- function(url) { use_badge("Launch Posit Cloud", url, img) } else { usethis_abort(" - {.fun usethis::use_posit_cloud_badge} requires a link to an \\ - existing Posit Cloud project of the form \\ - {.val https://posit.cloud/content/} or \\ + {.fun usethis::use_posit_cloud_badge} requires a link to an + existing Posit Cloud project of the form + {.val https://posit.cloud/content/} or {.val https://posit.cloud/spaces//content/}.") } From 4ab8df7adc85e6ce64e81478acfcde9ad00b4ce8 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 13 Feb 2024 15:32:46 -0800 Subject: [PATCH 009/111] ui_abort() feels more consistent with everything else --- R/author.R | 4 ++-- R/badge.R | 2 +- R/git-default-branch.R | 6 +++--- R/utils-ui.R | 2 +- tests/testthat/test-utils-ui.R | 4 ++++ 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/R/author.R b/R/author.R index 6e6f06e69..9fb567e61 100644 --- a/R/author.R +++ b/R/author.R @@ -96,7 +96,7 @@ challenge_legacy_author_fields <- function(d = proj_desc()) { {.fun desc::desc_coerce_authors_at_r}, then delete the legacy fields." )) if (ui_yeah("Do you want to cancel this operation and sort that out first?")) { - usethis_abort("Cancelling.") + ui_abort("Cancelling.") } invisible() } @@ -110,7 +110,7 @@ check_author_is_novel <- function(given = NULL, family = NULL, d = proj_desc()) }) if (any(m)) { aut_name <- glue("{given %||% ''} {family %||% ''}") - usethis_abort(c( + ui_abort(c( "x" = "{.val {aut_name}} already appears in {.field Authors@R}.", " " = "Please make the desired change directly in DESCRIPTION or call the {.pkg desc} package directly." diff --git a/R/badge.R b/R/badge.R index fdbdee643..cba764169 100644 --- a/R/badge.R +++ b/R/badge.R @@ -155,7 +155,7 @@ use_posit_cloud_badge <- function(url) { img <- "https://img.shields.io/badge/launch-posit%20cloud-447099?style=flat" use_badge("Launch Posit Cloud", url, img) } else { - usethis_abort(" + ui_abort(" {.fun usethis::use_posit_cloud_badge} requires a link to an existing Posit Cloud project of the form {.val https://posit.cloud/content/} or diff --git a/R/git-default-branch.R b/R/git-default-branch.R index 2f7abf9ec..265bc29b1 100644 --- a/R/git-default-branch.R +++ b/R/git-default-branch.R @@ -142,7 +142,7 @@ git_default_branch <- function() { if (is.na(db_local_with_source) ) { if (length(db_source)) { - usethis_abort(c( + ui_abort(c( "Default branch mismatch between local repo and remote.", "The default branch of the {.val {db_source$name}} remote is {.val {db_source$default_branch}}.", @@ -154,7 +154,7 @@ git_default_branch <- function() { db_source = db_source ) } else { - usethis_abort( + ui_abort( "Can't determine the local repo's default branch.", class = "error_default_branch" ) @@ -170,7 +170,7 @@ git_default_branch <- function() { # we learned a default branch from the source repo and it doesn't match # the local default branch - usethis_abort(c( + ui_abort(c( "Default branch mismatch between local repo and remote.", "The default branch of the {.val {db_source$name}} remote is {.val {db_source$default_branch}}.", diff --git a/R/utils-ui.R b/R/utils-ui.R index 364e0e409..d94e41d8a 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -21,7 +21,7 @@ ui_bullets <- function(text, .envir = parent.frame()) { cli::cli_bullets(text, .envir = .envir) } -usethis_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { +ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { cli::cli_div(theme = usethis_theme()) cli::cli_abort( message, diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 3d0e54aa5..d09a7b5b2 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -71,3 +71,7 @@ test_that("trailing slash behaviour of ui_path_impl()", { expect_match(ui_path_impl(paste0(tmpdir, "/")), "[^/]/$") expect_match(ui_path_impl(paste0(tmpdir, "//")), "[^/]/$") }) + +test_that("ui_abort() works", { + expect_usethis_error(ui_abort("an error"), "an error") +}) From 5827019224630d91a2d23f0deba7a4078ec08d70 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 15 Feb 2024 15:00:27 -0800 Subject: [PATCH 010/111] Introduce ui_code_snippet() --- R/utils-ui.R | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/R/utils-ui.R b/R/utils-ui.R index d94e41d8a..9c6ce3979 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -49,3 +49,32 @@ ui_path_impl <- function(x, base = NULL) { # shorter form for compactness, because this is typical usage: # ui_bullets("blah blah {.path {pth(some_path)}}") pth <- ui_path_impl + +ui_code_snippet <- function(x, + copy = rlang::is_interactive(), + language = c("R", ""), + interpolate = TRUE, + .envir = parent.frame()) { + language <- arg_match(language) + + x <- glue_collapse(x, "\n") + if (interpolate) { + x <- glue(x, .envir = .envir) + } + + if (!is_quiet()) { + cli::cli_code(indent(x), language = language, .envir = .envir) + } + + if (copy && clipr::clipr_available()) { + x_no_ansi <- cli::ansi_strip(x) + clipr::write_clip(x_no_ansi) + style_subtle <- cli::combine_ansi_styles( + cli::make_ansi_style("grey"), + cli::style_italic + ) + ui_bullets(c(" " = style_subtle("[Copied to clipboard]"))) + } + + invisible(x) +} From fa943c202b8116be6a1dde5a306711ce662bb2f0 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 15 Feb 2024 15:13:03 -0800 Subject: [PATCH 011/111] block.R --- R/block.R | 26 ++++++++++++++----------- tests/testthat/_snaps/github-actions.md | 2 +- tests/testthat/_snaps/lifecycle.md | 3 ++- tests/testthat/_snaps/tibble.md | 2 +- tests/testthat/_snaps/tidyverse.md | 7 ++++--- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/R/block.R b/R/block.R index df7987393..35d35de63 100644 --- a/R/block.R +++ b/R/block.R @@ -16,13 +16,14 @@ block_append <- function(desc, value, path, } if (is.null(block_lines)) { - ui_todo(" - Copy and paste the following lines into {ui_path(path)}:") - ui_code_block(c(block_prefix, block_start, value, block_end, block_suffix)) + ui_bullets(c( + "_" = "Copy and paste the following lines into {.path {pth(path)}}:" + )) + ui_code_snippet(c(block_prefix, block_start, value, block_end, block_suffix)) return(FALSE) } - ui_done("Adding {desc} to {ui_path(path)}") + ui_bullets(c("v" = "Adding {desc} to {.path {pth(path)}}.")) start <- block_lines[[1]] end <- block_lines[[2]] @@ -54,8 +55,10 @@ block_replace <- function(desc, value, path, } if (is.null(block_lines)) { - ui_todo("Copy and paste the following lines into {ui_value(path)}:") - ui_code_block(c(block_start, value, block_end)) + ui_bullets(c( + "_" = "Copy and paste the following lines into {.path {pth(path)}}:" + )) + ui_code_snippet(c(block_start, value, block_end)) return(invisible(FALSE)) } @@ -67,7 +70,7 @@ block_replace <- function(desc, value, path, return(invisible(FALSE)) } - ui_done("Replacing {desc} in {ui_path(path)}") + ui_bullets(c("v" = "Replacing {desc} in {.path {pth(path)}}.")) lines <- c( lines[seq2(1, start - 1L)], @@ -99,10 +102,11 @@ block_find <- function(lines, block_start = "# <<<", block_end = "# >>>") { } if (!(length(start) == 1 && length(end) == 1 && start < end)) { - ui_stop( - "Invalid block specification. - Must start with {ui_code(block_start)} and end with {ui_code(block_end)}" - ) + ui_abort(c( + "x" = "Invalid block specification.", + "i" = "Must start with {.code {block_start}} and end with + {.code {block_end}}." + )) } c(start + 1L, end - 1L) diff --git a/tests/testthat/_snaps/github-actions.md b/tests/testthat/_snaps/github-actions.md index 6f4083899..d5ce82073 100644 --- a/tests/testthat/_snaps/github-actions.md +++ b/tests/testthat/_snaps/github-actions.md @@ -10,7 +10,7 @@ v Creating '.github/workflows/' v Saving 'r-lib/actions/examples/check-full.yaml@v2' to '.github/workflows/R-CMD-check.yaml' * Learn more at . - v Adding R-CMD-check badge to 'README.md' + v Adding R-CMD-check badge to 'README.md'. # use_github_action() still errors in non-interactive environment diff --git a/tests/testthat/_snaps/lifecycle.md b/tests/testthat/_snaps/lifecycle.md index b19b30936..f3fe64d87 100644 --- a/tests/testthat/_snaps/lifecycle.md +++ b/tests/testthat/_snaps/lifecycle.md @@ -5,7 +5,8 @@ Message v Adding 'lifecycle' to Imports field in DESCRIPTION * Refer to functions with `lifecycle::fun()` - v Adding '@importFrom lifecycle deprecated' to 'R/{TESTPKG}-package.R' + v Adding '@importFrom lifecycle deprecated' to + 'R/{TESTPKG}-package.R'. v Writing 'NAMESPACE' v Creating 'man/figures/' v Copied SVG badges to 'man/figures/' diff --git a/tests/testthat/_snaps/tibble.md b/tests/testthat/_snaps/tibble.md index ac0695750..f0152f654 100644 --- a/tests/testthat/_snaps/tibble.md +++ b/tests/testthat/_snaps/tibble.md @@ -4,7 +4,7 @@ use_tibble() Message v Adding 'tibble' to Imports field in DESCRIPTION - v Adding '@importFrom tibble tibble' to 'R/{TESTPKG}-package.R' + v Adding '@importFrom tibble tibble' to 'R/{TESTPKG}-package.R'. * Document a returned tibble like so: #' @return a [tibble][tibble::tibble-package] diff --git a/tests/testthat/_snaps/tidyverse.md b/tests/testthat/_snaps/tidyverse.md index b70cd5aa3..21c23cb0d 100644 --- a/tests/testthat/_snaps/tidyverse.md +++ b/tests/testthat/_snaps/tidyverse.md @@ -8,9 +8,10 @@ v Adding 'cli' to Imports field in DESCRIPTION v Adding 'glue' to Imports field in DESCRIPTION v Adding 'withr' to Imports field in DESCRIPTION - v Adding '@import rlang' to 'R/{TESTPKG}-package.R' - v Adding '@importFrom glue glue' to 'R/{TESTPKG}-package.R' - v Adding '@importFrom lifecycle deprecated' to 'R/{TESTPKG}-package.R' + v Adding '@import rlang' to 'R/{TESTPKG}-package.R'. + v Adding '@importFrom glue glue' to 'R/{TESTPKG}-package.R'. + v Adding '@importFrom lifecycle deprecated' to + 'R/{TESTPKG}-package.R'. v Writing 'NAMESPACE' v Writing 'R/import-standalone-purrr.R' From d4b4009932992ce231542f017172e6787748519a Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 15 Feb 2024 16:30:07 -0800 Subject: [PATCH 012/111] Some testing improvements --- tests/testthat/_snaps/ui.md | 10 ----- tests/testthat/_snaps/utils-ui.md | 75 +++++++++++++++++++++++++++++-- tests/testthat/test-ui.R | 2 +- tests/testthat/test-utils-ui.R | 13 ++++-- 4 files changed, 82 insertions(+), 18 deletions(-) diff --git a/tests/testthat/_snaps/ui.md b/tests/testthat/_snaps/ui.md index 37c1c6149..7dbebf21a 100644 --- a/tests/testthat/_snaps/ui.md +++ b/tests/testthat/_snaps/ui.md @@ -31,13 +31,3 @@ Warning: a warning -# legacy UI actions respect usethis.quiet = TRUE - - Code - ui_line("line") - ui_todo("to do") - ui_done("done") - ui_oops("oops") - ui_info("info") - ui_code_block(c("x <- 1", "y <- 2")) - diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index 6f49da53a..b322beadc 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -1,4 +1,4 @@ -# ui_bullets() look as expected +# ui_bullets() look as expected [plain] Code ui_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", ` ` = "indent", @@ -14,13 +14,55 @@ > arrow ! warning -# ui_bullets() respect usethis.quiet = TRUE +# ui_bullets() look as expected [ansi] Code ui_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", ` ` = "indent", `*` = "bullet", `>` = "arrow", `!` = "warning")) + Message + [ ] todo + v done + x oops + i info + noindent + indent + * bullet + > arrow + ! warning + +# ui_bullets() look as expected [unicode] + + Code + ui_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", ` ` = "indent", + `*` = "bullet", `>` = "arrow", `!` = "warning")) + Message + ☐ todo + ✔ done + ✖ oops + ℹ info + noindent + indent + • bullet + → arrow + ! warning + +# ui_bullets() look as expected [fancy] + + Code + ui_bullets(c(`_` = "todo", v = "done", x = "oops", i = "info", "noindent", ` ` = "indent", + `*` = "bullet", `>` = "arrow", `!` = "warning")) + Message + ☐ todo + ✔ done + ✖ oops + ℹ info + noindent + indent + • bullet + → arrow + ! warning -# ui_bullets() does glue interpolation and inline markup +# ui_bullets() does glue interpolation and inline markup [plain] Code ui_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", x = "Scary {.code code} or {.fun function}")) @@ -29,3 +71,30 @@ v Updated the BugReports field x Scary `code` or `function()` +# ui_bullets() does glue interpolation and inline markup [ansi] + + Code + ui_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", x = "Scary {.code code} or {.fun function}")) + Message + i Hello, world! + v Updated the BugReports field + x Scary `code` or `function()` + +# ui_bullets() does glue interpolation and inline markup [unicode] + + Code + ui_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", x = "Scary {.code code} or {.fun function}")) + Message + ℹ Hello, world! + ✔ Updated the BugReports field + ✖ Scary `code` or `function()` + +# ui_bullets() does glue interpolation and inline markup [fancy] + + Code + ui_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", x = "Scary {.code code} or {.fun function}")) + Message + ℹ Hello, world! + ✔ Updated the BugReports field + ✖ Scary `code` or `function()` + diff --git a/tests/testthat/test-ui.R b/tests/testthat/test-ui.R index e3640e265..6874e640a 100644 --- a/tests/testthat/test-ui.R +++ b/tests/testthat/test-ui.R @@ -16,7 +16,7 @@ test_that("basic legacy UI actions behave as expected", { test_that("legacy UI actions respect usethis.quiet = TRUE", { withr::local_options(list(usethis.quiet = TRUE)) - expect_snapshot({ + expect_no_message({ ui_line("line") ui_todo("to do") ui_done("done") diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index d09a7b5b2..3a422e826 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -1,4 +1,4 @@ -test_that("ui_bullets() look as expected", { +cli::test_that_cli("ui_bullets() look as expected", { # suppress test silencing withr::local_options(list(usethis.quiet = FALSE)) @@ -23,7 +23,7 @@ test_that("ui_bullets() look as expected", { test_that("ui_bullets() respect usethis.quiet = TRUE", { withr::local_options(list(usethis.quiet = TRUE)) - expect_snapshot( + expect_no_message( ui_bullets(c( # relate to legacy functions "_" = "todo", # ui_todo() @@ -41,7 +41,7 @@ test_that("ui_bullets() respect usethis.quiet = TRUE", { ) }) -test_that("ui_bullets() does glue interpolation and inline markup", { +cli::test_that_cli("ui_bullets() does glue interpolation and inline markup", { # suppress test silencing withr::local_options(list(usethis.quiet = FALSE)) @@ -73,5 +73,10 @@ test_that("trailing slash behaviour of ui_path_impl()", { }) test_that("ui_abort() works", { - expect_usethis_error(ui_abort("an error"), "an error") + expect_usethis_error(ui_abort("spatula"), "spatula") + + # usethis.quiet should have no effect on this + withr::local_options(list(usethis.quiet = TRUE)) + expect_usethis_error(ui_abort("whisk"), "whisk") + }) From c8b7c624c0c7c461dcf375c3506cfa1ddbee353b Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 09:31:03 -0800 Subject: [PATCH 013/111] Test ui_code_snippet() --- R/utils-ui.R | 10 +++- tests/testthat/_snaps/utils-ui.md | 98 +++++++++++++++++++++++++++++++ tests/testthat/test-utils-ui.R | 60 +++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) diff --git a/R/utils-ui.R b/R/utils-ui.R index 9c6ce3979..71011e5ca 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -60,10 +60,18 @@ ui_code_snippet <- function(x, x <- glue_collapse(x, "\n") if (interpolate) { x <- glue(x, .envir = .envir) + # what about literal `{` or `}`? + # use `interpolate = FALSE`, if appropriate + # double them, i.e. `{{` or `}}` + # open issue/PR about adding `.open` and `.close` } if (!is_quiet()) { - cli::cli_code(indent(x), language = language, .envir = .envir) + # the inclusion of `.envir = .envir` leads to test failure + # I'm consulting with Gabor on this + # leaving it out seems fine for my use case + # cli::cli_code(indent(x), language = language, .envir = .envir) + cli::cli_code(indent(x), language = language) } if (copy && clipr::clipr_available()) { diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index b322beadc..10c75a1e1 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -98,3 +98,101 @@ ✔ Updated the BugReports field ✖ Scary `code` or `function()` +# ui_code_snippet() with scalar input [plain] + + Code + ui_code_snippet( + "\n options(\n warnPartialMatchArgs = TRUE,\n warnPartialMatchDollar = TRUE,\n warnPartialMatchAttr = TRUE\n )") + Message + options( + warnPartialMatchArgs = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchAttr = TRUE + ) + +# ui_code_snippet() with scalar input [ansi] + + Code + ui_code_snippet( + "\n options(\n warnPartialMatchArgs = TRUE,\n warnPartialMatchDollar = TRUE,\n warnPartialMatchAttr = TRUE\n )") + Message + options( + warnPartialMatchArgs = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchAttr = TRUE + ) + +# ui_code_snippet() with vector input [plain] + + Code + ui_code_snippet(c("options(", " warnPartialMatchArgs = TRUE,", + " warnPartialMatchDollar = TRUE,", " warnPartialMatchAttr = TRUE", ")")) + Message + options( + warnPartialMatchArgs = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchAttr = TRUE + ) + +# ui_code_snippet() with vector input [ansi] + + Code + ui_code_snippet(c("options(", " warnPartialMatchArgs = TRUE,", + " warnPartialMatchDollar = TRUE,", " warnPartialMatchAttr = TRUE", ")")) + Message + options( + warnPartialMatchArgs = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchAttr = TRUE + ) + +# ui_code_snippet() when langauge is not R [plain] + + Code + ui_code_snippet("#include <{h}>", language = "") + Message + #include + +# ui_code_snippet() when langauge is not R [ansi] + + Code + ui_code_snippet("#include <{h}>", language = "") + Message + #include + +# ui_code_snippet() can interpolate [plain] + + Code + ui_code_snippet("if (1) {true_val} else {false_val}") + Message + if (1) TRUE else 'FALSE' + +# ui_code_snippet() can interpolate [ansi] + + Code + ui_code_snippet("if (1) {true_val} else {false_val}") + Message + if (1) TRUE else 'FALSE' + +# ui_code_snippet() can NOT interpolate [plain] + + Code + ui_code_snippet("foo <- function(x){x}", interpolate = FALSE) + Message + foo <- function(x){x} + Code + ui_code_snippet("foo <- function(x){{x}}", interpolate = TRUE) + Message + foo <- function(x){x} + +# ui_code_snippet() can NOT interpolate [ansi] + + Code + ui_code_snippet("foo <- function(x){x}", interpolate = FALSE) + Message + foo <- function(x){x} + Code + ui_code_snippet("foo <- function(x){{x}}", interpolate = TRUE) + Message + foo <- function(x){x} + diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 3a422e826..0a86ef4e0 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -80,3 +80,63 @@ test_that("ui_abort() works", { expect_usethis_error(ui_abort("whisk"), "whisk") }) + +cli::test_that_cli("ui_code_snippet() with scalar input", { + withr::local_options(list(usethis.quiet = FALSE)) + + expect_snapshot( + ui_code_snippet(" + options( + warnPartialMatchArgs = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchAttr = TRUE + )") + ) +}, configs = c("plain", "ansi")) + +cli::test_that_cli("ui_code_snippet() with vector input", { + withr::local_options(list(usethis.quiet = FALSE)) + + expect_snapshot( + ui_code_snippet(c( + "options(", + " warnPartialMatchArgs = TRUE,", + " warnPartialMatchDollar = TRUE,", + " warnPartialMatchAttr = TRUE", + ")" + )) + ) +}, configs = c("plain", "ansi")) + +cli::test_that_cli("ui_code_snippet() when langauge is not R", { + withr::local_options(list(usethis.quiet = FALSE)) + h <- "blah.h" + expect_snapshot( + ui_code_snippet("#include <{h}>", language = "") + ) +}, configs = c("plain", "ansi")) + +cli::test_that_cli("ui_code_snippet() can interpolate", { + withr::local_options(list(usethis.quiet = FALSE)) + + true_val <- "TRUE" + false_val <- "'FALSE'" + + expect_snapshot( + ui_code_snippet("if (1) {true_val} else {false_val}") + ) +}, configs = c("plain", "ansi")) + +cli::test_that_cli("ui_code_snippet() can NOT interpolate", { + withr::local_options(list(usethis.quiet = FALSE)) + expect_snapshot({ + ui_code_snippet( + "foo <- function(x){x}", + interpolate = FALSE + ) + ui_code_snippet( + "foo <- function(x){{x}}", + interpolate = TRUE + ) + }) +}, configs = c("plain", "ansi")) From 2fb1a4c7d67b26d8216ac1405620204dc9ee1aeb Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 10:00:35 -0800 Subject: [PATCH 014/111] Replace all calls to ui_code_block() --- R/code-of-conduct.R | 5 +++-- R/logo.R | 10 ++++++++-- R/package.R | 2 +- R/rcpp.R | 5 +++-- R/rprofile.R | 14 ++++---------- R/tibble.R | 6 +++++- R/tidyverse.R | 5 ++++- 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/R/code-of-conduct.R b/R/code-of-conduct.R index 51f38ad6e..d42b51337 100644 --- a/R/code-of-conduct.R +++ b/R/code-of-conduct.R @@ -36,12 +36,13 @@ use_code_of_conduct <- function(contact, path = NULL) { href <- paste0(href, "/CODE_OF_CONDUCT.html") ui_todo("You may also want to describe the code of conduct in your README:") - ui_code_block(" + ui_code_snippet(" ## Code of Conduct Please note that the {project_name()} project is released with a \\ [Contributor Code of Conduct]({href}). By contributing to this project, \\ - you agree to abide by its terms." + you agree to abide by its terms.", + language = "" ) invisible(new) diff --git a/R/logo.R b/R/logo.R index d6199f536..f0e0661b2 100644 --- a/R/logo.R +++ b/R/logo.R @@ -51,8 +51,14 @@ use_logo <- function(img, geometry = "240x278", retina = TRUE) { ui_todo("Add logo to your README with the following html:") pd_link <- pkgdown_url(pedantic = TRUE) if (is.null(pd_link)) { - ui_code_block("# {pkg} \"\"") + ui_code_snippet( + "# {pkg} \"\"", + language = "" + ) } else { - ui_code_block("# {pkg} \"{pkg}") + ui_code_snippet( + "# {pkg} \"{pkg}", + language = "" + ) } } diff --git a/R/package.R b/R/package.R index 910d99f0e..bde7f0e40 100644 --- a/R/package.R +++ b/R/package.R @@ -188,5 +188,5 @@ show_includes <- function(package) { } ui_todo("Possible includes are:") - ui_code_block("#include <{path_file(h)}>", copy = FALSE) + ui_code_snippet("#include <{path_file(h)}>", copy = FALSE, language = "") } diff --git a/R/rcpp.R b/R/rcpp.R index 6229f8308..c82ade1f4 100644 --- a/R/rcpp.R +++ b/R/rcpp.R @@ -97,8 +97,9 @@ use_makevars <- function(settings = NULL) { } else { ui_todo("Ensure the following Makevars compilation settings are set for both \\ {ui_path(makevars_path)} and {ui_path(makevars_win_path)}:") - ui_code_block( - makevars_content + ui_code_snippet( + makevars_content, + language = "" ) edit_file(makevars_path) edit_file(makevars_win_path) diff --git a/R/rprofile.R b/R/rprofile.R index cd3f549a4..4395fa1aa 100644 --- a/R/rprofile.R +++ b/R/rprofile.R @@ -43,13 +43,10 @@ use_rprofile_package <- function(package) { "Include this code in {ui_value('.Rprofile')} to make \\ {ui_field(package)} available in all interactive sessions." ) - ui_code_block( - " + ui_code_snippet(" if (interactive()) {{ suppressMessages(require({package})) - }} - " - ) + }}") edit_r_profile("user") } @@ -59,14 +56,11 @@ use_partial_warnings <- function() { ui_todo( "Include this code in {ui_path('.Rprofile')} to warn on partial matches." ) - ui_code_block( - " + ui_code_snippet(" options( warnPartialMatchArgs = TRUE, warnPartialMatchDollar = TRUE, warnPartialMatchAttr = TRUE - ) - " - ) + )") edit_r_profile("user") } diff --git a/R/tibble.R b/R/tibble.R index 2e127a203..a0f8e4e07 100644 --- a/R/tibble.R +++ b/R/tibble.R @@ -34,7 +34,11 @@ use_tibble <- function() { created <- use_import_from("tibble", "tibble") ui_todo("Document a returned tibble like so:") - ui_code_block("#' @return a [tibble][tibble::tibble-package]", copy = FALSE) + ui_code_snippet( + "#' @return a [tibble][tibble::tibble-package]", + language = "", + copy = FALSE + ) invisible(created) } diff --git a/R/tidyverse.R b/R/tidyverse.R index faa9f58eb..bdf7fc726 100644 --- a/R/tidyverse.R +++ b/R/tidyverse.R @@ -332,7 +332,10 @@ use_tidy_thanks <- function(repo_spec = NULL, contrib_link <- glue("[@{contributors}](https://github.com/{contributors})") ui_done("Found {length(contributors)} contributors:") - ui_code_block(glue_collapse(contrib_link, sep = ", ", last = ", and ") + glue(".")) + ui_code_snippet( + glue_collapse(contrib_link, sep = ", ", last = ", and ") + glue("."), + language = "" + ) invisible(contributors) } From 53777884788a996efb7c3e2c5e9b1f832ee0e2bc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 12:10:52 -0800 Subject: [PATCH 015/111] Typo --- tests/testthat/test-utils-ui.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 0a86ef4e0..59ed7496d 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -108,7 +108,7 @@ cli::test_that_cli("ui_code_snippet() with vector input", { ) }, configs = c("plain", "ansi")) -cli::test_that_cli("ui_code_snippet() when langauge is not R", { +cli::test_that_cli("ui_code_snippet() when language is not R", { withr::local_options(list(usethis.quiet = FALSE)) h <- "blah.h" expect_snapshot( From 4251e5c87e58b7bc14d7cbac857ca6098fa77a1c Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 12:11:07 -0800 Subject: [PATCH 016/111] Update conversion article --- vignettes/articles/ui-cli-conversion.Rmd | 89 +++++++++++++++++------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 88e24646b..349112f41 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -19,7 +19,7 @@ I'll start with a review of the current (soon to be legacy) `usethis::ui_*()` fu ## Block styles -The block styles mostly exist to produce bulleted output with a specific symbol, using a specific color. +The block styles mostly exist to produce bulleted output with a specific symbol, using a specific color, that can be shutdown package-wide via the `usethis.quiet` option. ```{r} f <- function() { @@ -49,12 +49,12 @@ cli::cli_bullets(c( A direct translation looks something like this: -| `ui_*()` | `cli_bullets()` shortcode | tweaks needed | -|-----------------|------------------------|-------------------------------| -| `ui_todo()` | `*` | blue (default) -\> red | -| `ui_done()` | `v` | perfect match | -| `ui_oops()` | `x` | perfect match | -| `ui_info()` | `i` | blue (default) -\> yellow | +| `ui_*()` | `cli_bullets()` shortcode | tweaks needed | +|-------------|---------------------------|-------------------------------| +| `ui_todo()` | `*` | blue (default) -\> red | +| `ui_done()` | `v` | perfect match | +| `ui_oops()` | `x` | perfect match | +| `ui_info()` | `i` | blue (default) -\> yellow | | `ui_line()` | (unnamed) | might be perfect match? although sometimes ui_line is used just to get a blank line | I'm experimenting with a few more tweaks related to TODOs: @@ -64,17 +64,35 @@ I'm experimenting with a few more tweaks related to TODOs: That just leaves `code_block()`, which is pretty different. It exists to put some code on the screen and optionally place it on the clipboard. +I have created a new function, `ui_code_snippet()` that is built around `cli::code_block()`. +Main observations: +* `cli::code_block(language = "R")` applies syntax highlighting and hyperlinks (e.g. to function help topics) to R code, which is cool. Therefore the `language` argument is also exposed in `ui_code_snippet()`, defaulting to `"R"`. Use `""` for anything that's not R code: ```{r} -ui_code_block(" +#| eval: false +ui_code_snippet("x <- 1 + 2") +ui_code_snippet("#include ", language = "") +``` +* `ui_code_snippet()` takes a scalar glue-type template string or a vector of lines. +```{r} +ui_code_snippet(" options( warnPartialMatchArgs = TRUE, warnPartialMatchDollar = TRUE, warnPartialMatchAttr = TRUE )") +# produces same result as +ui_code_snippet(c( + "options(", + " warnPartialMatchArgs = TRUE,", + " warnPartialMatchDollar = TRUE,", + " warnPartialMatchAttr = TRUE", + ")")) ``` - -I think I should build around `cli::code_block()` for this. +* `ui_code_snippet()` does glue interpolation, by default, before calling `cli::cli_code()`, which means you have to think about your use of `{` and `}`. If you want literal `{` or `}`: + - Use `interpolate = FALSE`, if you don't need interpolation. + - Do the usual glue thing and double them, i.e. `{{` or `}}`. + - If this becomes a real pain, open an issue/PR about adding `.open` and `.close` as arguments to `ui_code_snippet()`. ## Utility functions @@ -105,6 +123,7 @@ ui_inform <- function(...) { ``` Other than `is_quiet()`, which will continue to play the same role, I anticipate that we no longer need these utilities (`indent()`, `ui_bullet()`, `ui_inform()`). +Update: `indent()` turns out to still be useful in `ui_code_snippet()`. Let's cover `ui_silence()` while we're here, which *is* exported. It's just a `withr::with_*()` function for executing `code` with `usethis.quiet = TRUE`. @@ -122,30 +141,35 @@ ui_silence <- function(code) { - `ui_field()` becomes `{.field blah}` - `ui_value()` becomes `{.val value}` -- `ui_path()` will draw on `{.file path/to/thing}` but some of `ui_path()`'s logic is still needed (e.g. making path relative to project root, ensuring a directory has a trailing `/`). Should also think about hyperlink behaviour. -- `ui_code()` probably becomes one of: +- `ui_path()` is connected to `{.path path/to/thing}`, but `ui_path()` also has some legitimately useful logic, such as making a path relative to project root or ensuring a directory has a trailing `/`. Therefore, this has been abstracted into `ui_path_impl()`, which is aliased to `pth()` for compactness. Here's a typical conversion: +```{r} +#| eval: false +# using legacy functions +ui_done("Setting {ui_field('LazyData')} to \\ + {ui_value('true')} in {ui_path('DESCRIPTION')}") +# using new cli-based ui +ui_bullets(c( + "v" = "Setting {.field LazyData} to {.val true} in {.path {pth('DESCRIPTION')}}." +)) +``` +It would be nice to create a custom inline class, e.g. `{.usethis_path {some_path}}`, which I have done in, e.g., googledrive. But it's not easy to this *while also inheriting cli's `file:` hyperlink behaviour*, which is very desirable. So that leads to the somewhat clunk pattern above, but it gives a nice result. +- `ui_code()` gets replaced by various inline styles, depending on what the actual goal is: - `{.arg some_argument}` - - `{.cls some_class}` - - `{.code some_text}` - - `{.envvar SOME_ENV_VAR}` - - `{.fun devtools::build_readme}` - - `{.help some_function}` - - `{.run some_code}` - - `{.topic some_topic}` - - `{.var variable_name}` (I'll talk about `ui_unset()` below.) ## Conditions +I'm moving from `ui_stop()`: + ```{r} ui_stop <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -158,7 +182,28 @@ ui_stop <- function(x, .envir = parent.frame()) { stop(cnd) } +``` + +to `ui_abort()`: + +```{r} +ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { + cli::cli_div(theme = usethis_theme()) + cli::cli_abort( + message, + class = c(class, "usethis_error"), + .envir = .envir, + ... + ) +} +``` + +At this point, the `usethis_theme()` doesn't do anything that is perceivable in an error. +So the main point of `ui_abort()` is to switch to `cli_abort()`. + +Say something about `ui_warn()` once I get there: +```{r} ui_warn <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") x <- glue(x, .envir = .envir) @@ -167,10 +212,6 @@ ui_warn <- function(x, .envir = parent.frame()) { } ``` -I think `ui_stop()`, becomes a wrapper around `cli::cli_abort()` that applies the `usethis_error` class. - -I suppose `ui_warn() probably just becomes `cli::cli_warn()`. - ## Sitrep and format helpers This is a small clump of functions that support sitrep-type output. From 8a7a2506d07b120eec292a0ba4a848c41f310612 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 13:19:22 -0800 Subject: [PATCH 017/111] browse.R --- R/browse.R | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/R/browse.R b/R/browse.R index 9f60298f3..baf045ba7 100644 --- a/R/browse.R +++ b/R/browse.R @@ -77,7 +77,10 @@ browse_package <- function(package = NULL) { grl <- set_names(grl$url, nm = grl$remote) parsed <- parse_github_remotes(grl) urls <- c(urls, glue_data(parsed, "https://{host}/{repo_owner}/{repo_name}")) - details <- c(details, map(parsed$name, ~ glue("{ui_value(.x)} remote"))) + details <- c( + details, + map(parsed$name, ~ cli::cli_fmt(cli::cli_text("{.val {.x}} remote"))) + ) } desc_urls_dat <- desc_urls(package, include_cran = TRUE) @@ -86,11 +89,11 @@ browse_package <- function(package = NULL) { details, map( desc_urls_dat$desc_field, - ~ if (is.na(.x)) "CRAN" else glue("{ui_field(.x)} field in DESCRIPTION") + ~ if (is.na(.x)) "CRAN" else cli::cli_fmt(cli::cli_text("{.field {.x}} field in DESCRIPTION")) ) ) if (length(urls) == 0) { - ui_oops("Can't find any URLs") + ui_bullets(c(x = "Can't find any URLs.")) return(invisible(character())) } @@ -171,15 +174,15 @@ github_url <- function(package = NULL) { if (is.null(desc_urls_dat)) { if (is.null(package)) { - ui_stop(" - Project {ui_value(project_name())} has no DESCRIPTION file and \\ - has no GitHub remotes configured - No way to discover URLs") + ui_abort(c( + "Project {.val {project_name()}} has no DESCRIPTION file and + has no GitHub remotes configured.", + x = "No way to discover URLs.")) } else { - ui_stop(" - Can't find DESCRIPTION for package {ui_value(package)} locally \\ - or on CRAN - No way to discover URLs") + ui_abort(c( + "Can't find DESCRIPTION for package {.val {package}} locally + or on CRAN.", + x = "No way to discover URLs.")) } } @@ -191,13 +194,13 @@ github_url <- function(package = NULL) { } if (is.null(package)) { - ui_stop(" - Project {ui_value(project_name())} has no GitHub remotes configured \\ - and has no GitHub URLs in DESCRIPTION") + ui_abort(" + Project {.val {project_name()}} has no GitHub remotes configured + and has no GitHub URLs in DESCRIPTION.") } - ui_warn(" - Package {ui_value(package)} has no GitHub URLs in DESCRIPTION - Trying the GitHub CRAN mirror") + cli::cli_warn(c( + "!" = "Package {.val {package}} has no GitHub URLs in DESCRIPTION.", + " " = "Trying the GitHub CRAN mirror.")) glue_chr("https://github.com/cran/{package}") } From 46a9f229f4c94cf1cf3bf66956afe22ac4b029af Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 13:20:04 -0800 Subject: [PATCH 018/111] Eliminate usage of ui_warn() --- R/data-table.R | 8 +++++--- R/data.R | 4 +++- R/helpers.R | 8 ++++---- R/pkgdown.R | 7 ++++--- tests/testthat/_snaps/data-table.md | 14 ++++++++++++++ tests/testthat/_snaps/helpers.md | 7 +++++++ tests/testthat/_snaps/package.md | 5 ++--- tests/testthat/_snaps/pkgdown.md | 20 ++++++++++++++++++++ tests/testthat/_snaps/utils-ui.md | 4 ++-- tests/testthat/test-data-table.R | 7 ++++--- tests/testthat/test-helpers.R | 7 +++++-- tests/testthat/test-pkgdown.R | 9 +++++++-- vignettes/articles/ui-cli-conversion.Rmd | 11 +++++++---- 13 files changed, 84 insertions(+), 27 deletions(-) diff --git a/R/data-table.R b/R/data-table.R index 20ae78e96..1353590d2 100644 --- a/R/data-table.R +++ b/R/data-table.R @@ -20,9 +20,11 @@ use_data_table <- function() { deps <- desc$get_deps() if (any(deps$type == "Depends" & deps$package == "data.table")) { - ui_warn("data.table should be in Imports or Suggests, not Depends") - ui_done("Deleting data.table from {ui_field('Depends')}") - + ui_bullets(c( + "!" = "{.pkg data.table} should be in {.field Imports} or + {.field Suggests}, not {.field Depends}!", + "v" = "Removing {.pkg data.table} from {.field Depends}." + )) desc$del_dep("data.table", "Depends") desc$write() } diff --git a/R/data.R b/R/data.R index 9eca4db20..2094cf3fc 100644 --- a/R/data.R +++ b/R/data.R @@ -95,7 +95,9 @@ get_objs_from_dots <- function(.dots) { duplicated_objs <- which(stats::setNames(duplicated(objs), objs)) if (length(duplicated_objs) > 0L) { objs <- unique(objs) - ui_warn("Saving duplicates only once: {ui_value(names(duplicated_objs))}") + ui_bullets(c( + "!" = "Saving duplicates only once: {.val {names(duplicated_objs)}}." + )) } objs } diff --git a/R/helpers.R b/R/helpers.R index fb2b9803e..c539133c9 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -56,10 +56,10 @@ use_dependency <- function(package, type, min_version = NULL) { delta <- sign(match(existing_type, types) - match(type, types)) if (delta < 0) { # don't downgrade - ui_warn( - "Package {ui_value(package)} is already listed in \\ - {ui_value(existing_type)} in DESCRIPTION, no change made." - ) + ui_bullets(c( + "!" = "Package {.pkg {package}} is already listed in + {.field {existing_type}} in DESCRIPTION; no change made." + )) } else if (delta == 0 && !is.null(min_version)) { # change version upgrade <- existing_version == "*" || diff --git a/R/pkgdown.R b/R/pkgdown.R index 534b05f1c..ca3530320 100644 --- a/R/pkgdown.R +++ b/R/pkgdown.R @@ -175,9 +175,10 @@ pkgdown_url <- function(pedantic = FALSE) { } if (pedantic) { - ui_warn(" - pkgdown config does not specify the site's {ui_field('url')}, \\ - which is optional but recommended") + ui_bullets(c( + "!" = "{.pkg pkgdown} config does not specify the site's {.field url}, + which is optional but recommended." + )) } NULL } diff --git a/tests/testthat/_snaps/data-table.md b/tests/testthat/_snaps/data-table.md index f1aa70eaa..55ac13130 100644 --- a/tests/testthat/_snaps/data-table.md +++ b/tests/testthat/_snaps/data-table.md @@ -15,6 +15,20 @@ # use_data_table() blocks use of Depends + Code + use_data_table() + Message + ! data.table should be in Imports or Suggests, not Depends! + v Removing data.table from Depends. + v Adding 'data.table' to Imports field in DESCRIPTION + v Adding '@importFrom data.table data.table', '@importFrom data.table :=', + '@importFrom data.table .SD', '@importFrom data.table .BY', '@importFrom + data.table .N', '@importFrom data.table .I', '@importFrom data.table .GRP', + '@importFrom data.table .NGRP', '@importFrom data.table .EACHI' to + 'R/{TESTPKG}-package.R'. + +--- + Code roxygen_ns_show() Output diff --git a/tests/testthat/_snaps/helpers.md b/tests/testthat/_snaps/helpers.md index dac7b271b..55bf8c5c9 100644 --- a/tests/testthat/_snaps/helpers.md +++ b/tests/testthat/_snaps/helpers.md @@ -1,3 +1,10 @@ +# use_dependency() declines to downgrade a dependency + + Code + use_dependency("usethis", "Suggests") + Message + ! Package usethis is already listed in Imports in DESCRIPTION; no change made. + # can add LinkingTo dependency if other dependency already exists Code diff --git a/tests/testthat/_snaps/package.md b/tests/testthat/_snaps/package.md index dbf92e275..9e5c4988e 100644 --- a/tests/testthat/_snaps/package.md +++ b/tests/testthat/_snaps/package.md @@ -8,9 +8,8 @@ Code use_package("withr") use_package("withr", "Suggests") - Condition - Warning: - Package 'withr' is already listed in 'Imports' in DESCRIPTION, no change made. + Message + ! Package withr is already listed in Imports in DESCRIPTION; no change made. # use_package() handles R versions with aplomb diff --git a/tests/testthat/_snaps/pkgdown.md b/tests/testthat/_snaps/pkgdown.md index d1ff23874..30b923024 100644 --- a/tests/testthat/_snaps/pkgdown.md +++ b/tests/testthat/_snaps/pkgdown.md @@ -8,3 +8,23 @@ v Writing '_pkgdown.yml' * Edit '_pkgdown.yml' +# pkgdown_url() returns correct data, warns if pedantic + + Code + pkgdown_url(pedantic = TRUE) + Message + ! pkgdown config does not specify the site's url, which is optional but + recommended. + Output + NULL + +--- + + Code + pkgdown_url(pedantic = TRUE) + Message + ! pkgdown config does not specify the site's url, which is optional but + recommended. + Output + NULL + diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index 10c75a1e1..3edf821df 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -146,14 +146,14 @@ warnPartialMatchAttr = TRUE ) -# ui_code_snippet() when langauge is not R [plain] +# ui_code_snippet() when language is not R [plain] Code ui_code_snippet("#include <{h}>", language = "") Message #include -# ui_code_snippet() when langauge is not R [ansi] +# ui_code_snippet() when language is not R [ansi] Code ui_code_snippet("#include <{h}>", language = "") diff --git a/tests/testthat/test-data-table.R b/tests/testthat/test-data-table.R index 88dbdfd29..0a55f8482 100644 --- a/tests/testthat/test-data-table.R +++ b/tests/testthat/test-data-table.R @@ -26,11 +26,12 @@ test_that("use_data_table() blocks use of Depends", { local_roxygen_update_ns() local_check_fun_exists() - expect_warning( + withr::local_options(list(usethis.quiet = FALSE)) + expect_snapshot( use_data_table(), - "data.table should be in Imports or Suggests, not Depends" + transform = scrub_testpkg ) - expect_match(proj_desc()$get("Imports"), "data.table") + expect_match(desc::desc_get("Imports"), "data.table") expect_snapshot(roxygen_ns_show()) }) diff --git a/tests/testthat/test-helpers.R b/tests/testthat/test-helpers.R index 22ceb5cf1..745eb22ea 100644 --- a/tests/testthat/test-helpers.R +++ b/tests/testthat/test-helpers.R @@ -80,9 +80,12 @@ test_that("use_dependency() declines to downgrade a dependency", { expect_message(use_dependency("usethis", "Imports")) expect_match(desc::desc_get("Imports"), "usethis") - expect_warning(use_dependency("usethis", "Suggests"), "no change") + withr::local_options(list(usethis.quiet = FALSE)) + expect_snapshot( + use_dependency("usethis", "Suggests") + ) expect_match(desc::desc_get("Imports"), "usethis") - expect_no_match( desc::desc_get("Suggests"), "usethis") + expect_no_match(desc::desc_get("Suggests"), "usethis") }) test_that("can add LinkingTo dependency if other dependency already exists", { diff --git a/tests/testthat/test-pkgdown.R b/tests/testthat/test-pkgdown.R index 7344c0eea..9ea3f5932 100644 --- a/tests/testthat/test-pkgdown.R +++ b/tests/testthat/test-pkgdown.R @@ -55,13 +55,18 @@ test_that("pkgdown_url() returns correct data, warns if pedantic", { # empty config expect_null(pkgdown_url()) expect_silent(pkgdown_url()) - expect_warning(pkgdown_url(pedantic = TRUE), "url") + withr::local_options(list(usethis.quiet = FALSE)) + expect_snapshot( + pkgdown_url(pedantic = TRUE) + ) # nonempty config, but no url writeLines(c("home:", " strip_header: true"), pkgdown_config_path()) expect_null(pkgdown_url()) expect_silent(pkgdown_url()) - expect_warning(pkgdown_url(pedantic = TRUE), "url") + expect_snapshot( + pkgdown_url(pedantic = TRUE) + ) # config has url writeLines("url: https://usethis.r-lib.org", pkgdown_config_path()) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 349112f41..6a891d2f8 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -139,7 +139,7 @@ ui_silence <- function(code) { -- `ui_field()` becomes `{.field blah}` +- `ui_field()` becomes `{.field blah}`. TO THINK ABOUT: I'm not seeing single quotes in the 'plain' snapshots, but I think we will want quoting if there's no color. - `ui_value()` becomes `{.val value}` - `ui_path()` is connected to `{.path path/to/thing}`, but `ui_path()` also has some legitimately useful logic, such as making a path relative to project root or ensuring a directory has a trailing `/`. Therefore, this has been abstracted into `ui_path_impl()`, which is aliased to `pth()` for compactness. Here's a typical conversion: ```{r} @@ -198,10 +198,10 @@ ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { } ``` -At this point, the `usethis_theme()` doesn't do anything that is perceivable in an error. -So the main point of `ui_abort()` is to switch to `cli_abort()`. +At this point, the `usethis_theme()` doesn't do anything that affects the styling of an error. +So the main point of `ui_abort()` is to use to `cli_abort()` (and to continue applying the `"usethis_error"` class). -Say something about `ui_warn()` once I get there: +The legacy functions include `ui_warn()`: ```{r} ui_warn <- function(x, .envir = parent.frame()) { @@ -212,6 +212,9 @@ ui_warn <- function(x, .envir = parent.frame()) { } ``` +It has very little usage and, instead of converting it, I'm going to eliminate its use altogether in favor of `ui_bullets(c("!" = "Thing I'm warning about.")) +`. + ## Sitrep and format helpers This is a small clump of functions that support sitrep-type output. From 87783e89e31ee36e2f356e2d6a0ed49e1ab2fb30 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 13:30:12 -0800 Subject: [PATCH 019/111] ci.R --- R/ci.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/ci.R b/R/ci.R index 273046ff1..86c7edec9 100644 --- a/R/ci.R +++ b/R/ci.R @@ -82,7 +82,9 @@ use_circleci_badge <- function(repo_spec = NULL) { circleci_activate <- function(owner, browse = is_interactive()) { url <- glue("https://circleci.com/add-projects/gh/{owner}") - ui_todo("Turn on CircleCI for your repo at {url}") + ui_bullets(c( + "_" = "Turn on CircleCI for your repo at {.url {url}}." + )) if (browse) { utils::browseURL(url) } From d500a8fa25618eaa3f5fd363e12c9ecb24d25ecc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 13:33:39 -0800 Subject: [PATCH 020/111] code_of_conduct.R --- R/code-of-conduct.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/code-of-conduct.R b/R/code-of-conduct.R index d42b51337..95ecd02aa 100644 --- a/R/code-of-conduct.R +++ b/R/code-of-conduct.R @@ -23,9 +23,8 @@ #' @export use_code_of_conduct <- function(contact, path = NULL) { if (missing(contact)) { - ui_stop(" - {ui_code('use_code_of_conduct()')} requires contact details in \\ - first argument") + ui_abort(" + {.fun use_code_of_conduct} requires contact details in first argument.") } new <- use_coc(contact = contact, path = path) @@ -35,7 +34,9 @@ use_code_of_conduct <- function(contact, path = NULL) { href <- sub("/$", "", href) href <- paste0(href, "/CODE_OF_CONDUCT.html") - ui_todo("You may also want to describe the code of conduct in your README:") + ui_bullets(c( + "_" = "You may also want to describe the code of conduct in your README:" + )) ui_code_snippet(" ## Code of Conduct From da160a9f497aa4d75c382bc448ea4f7ffbbe01d4 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 13:51:47 -0800 Subject: [PATCH 021/111] course.R --- R/course.R | 63 ++++++++++++++++++--------------- tests/testthat/_snaps/course.md | 16 ++++----- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/R/course.R b/R/course.R index ab3a1ed8f..2cf665080 100644 --- a/R/course.R +++ b/R/course.R @@ -63,19 +63,20 @@ use_course <- function(url, destdir = getOption("usethis.destdir")) { check_path_is_directory(destdir) if (destdir_not_specified && is_interactive()) { - ui_line(c( - "Downloading into {ui_path(destdir)}.", - "Prefer a different location? Cancel, try again, and specify {ui_code('destdir')}" + ui_bullets(c( + "i" = "Downloading into {.path {pth(destdir)}}.", + "_" = "Prefer a different location? Cancel, try again, and specify + {.arg destdir}." )) if (ui_nope("OK to proceed?")) { - ui_oops("Cancelling download.") + ui_bullets(c(x = "Cancelling download.")) return(invisible()) } } - ui_done("Downloading from {ui_value(url)}") + ui_bullets(c("v" = "Downloading from {.url {url}}.")) zipfile <- tidy_download(url, destdir) - ui_done("Download stored in {ui_path(zipfile)}") + ui_bullets(c("v" = "Download stored in {.path {pth(zipfile)}}.")) check_is_zip(attr(zipfile, "content-type")) tidy_unzip(zipfile, cleanup = NA) } @@ -90,9 +91,9 @@ use_zip <- function(url, cleanup = if (rlang::is_interactive()) NA else FALSE) { url <- normalize_url(url) check_path_is_directory(destdir) - ui_done("Downloading from {ui_value(url)}") + ui_bullets(c("v" = "Downloading from {.url {url}}.")) zipfile <- tidy_download(url, destdir) - ui_done("Download stored in {ui_path(zipfile)}") + ui_bullets(c("v" = "Download stored in {.path {pth(zipfile)}}.")) check_is_zip(attr(zipfile, "content-type")) tidy_unzip(zipfile, cleanup) } @@ -255,14 +256,15 @@ tidy_download <- function(url, destdir = getwd()) { tmp <- file_temp("tidy-download-") h <- download_url(url, destfile = tmp) - ui_line() + cli::cat_line() cd <- content_disposition(h) base_name <- make_filename(cd, fallback = path_file(url)) full_path <- path(destdir, base_name) if (!can_overwrite(full_path)) { - ui_stop("Cancelling download, to avoid overwriting {ui_path(full_path)}") + ui_abort( + "Cancelling download, to avoid overwriting {.path {pth(full_path)}}.") } attr(full_path, "content-type") <- content_type(h) attr(full_path, "content-disposition") <- cd @@ -303,7 +305,7 @@ download_url <- function(url, status <- try_download(url, destfile, handle = handle) if (inherits(status, "error") && is_interactive()) { - ui_oops(status$message) + ui_bullets(c("x" = status$message)) if (ui_nope(" Download failed :( See above for everything we know about why it failed. @@ -323,7 +325,7 @@ download_url <- function(url, ) } i <- i + 1 - ui_info("Retrying download ... attempt {i}") + ui_bullets(c("i" = "Retrying download ... attempt {i}.")) status <- try_download(url, destfile, handle = handle) } @@ -368,10 +370,10 @@ tidy_unzip <- function(zipfile, cleanup = FALSE) { target <- path(base_path, td) utils::unzip(zipfile, files = filenames, exdir = base_path) } - ui_done( - "Unpacking ZIP file into {ui_path(target, base_path)} \\ - ({length(filenames)} files extracted)" - ) + ui_bullets(c( + "v" = "Unpacking ZIP file into {.path {pth(target, base_path)}} + ({length(filenames)} file{?s} extracted)." + )) if (isNA(cleanup)) { cleanup <- is_interactive() && @@ -379,17 +381,19 @@ tidy_unzip <- function(zipfile, cleanup = FALSE) { } if (isTRUE(cleanup)) { - ui_done("Deleting {ui_path(zipfile, base_path)}") + ui_bullets(c("v" = "Deleting {.path {pth(zipfile, base_path)}}.")) file_delete(zipfile) } if (is_interactive()) { rproj_path <- rproj_paths(target) if (length(rproj_path) == 1 && rstudioapi::hasFun("openProject")) { - ui_done("Opening project in RStudio") + ui_bullets(c("v" = "Opening project in RStudio.")) rstudioapi::openProject(target, newSession = TRUE) } else if (!in_rstudio_server()) { - ui_done("Opening {ui_path(target, base_path)} in the file manager") + ui_bullets(c( + "v" = "Opening {.path {pth(target, base_path)}} in the file manager." + )) utils::browseURL(path_real(target)) } } @@ -462,10 +466,10 @@ modify_github_url <- function(url) { } hopeless_url <- function(url) { - ui_info( - "URL does not match a recognized form for Google Drive or DropBox. \\ - No change made." - ) + ui_bullets(c( + "!" = "URL does not match a recognized form for Google Drive or DropBox; + no change made." + )) url } @@ -552,9 +556,9 @@ check_is_zip <- function(ct) { # see https://github.com/r-lib/usethis/issues/573 allowed <- c("application/zip", "application/x-zip-compressed") if (!ct %in% allowed) { - ui_stop(c( - "Download does not have MIME type {ui_value('application/zip')}.", - "Instead it's {ui_value(ct)}." + ui_abort(c( + "x" = "Download does not have MIME type {.val application/zip}.", + "i" = "Instead it's {.val {ct}}." )) } invisible(ct) @@ -568,9 +572,10 @@ check_is_zip <- function(ct) { # http://test.greenbytes.de/tech/tc2231/ parse_content_disposition <- function(cd) { if (!grepl("^attachment;", cd)) { - ui_stop(c( - "{ui_code('Content-Disposition')} header doesn't start with {ui_value('attachment')}.", - "Actual header: {ui_value(cd)}" + ui_abort(c( + "x" = "{.code Content-Disposition} header doesn't start with + {.val attachment}.", + "i" = "Actual header: {.val cd}" )) } diff --git a/tests/testthat/_snaps/course.md b/tests/testthat/_snaps/course.md index eb684aa7e..34784853a 100644 --- a/tests/testthat/_snaps/course.md +++ b/tests/testthat/_snaps/course.md @@ -8,23 +8,23 @@ Code out <- download_url(url = "URL", destfile = "destfile") Message - i Retrying download ... attempt 2 + i Retrying download ... attempt 2. --- Code out <- download_url(url = "URL", destfile = "destfile") Message - i Retrying download ... attempt 2 - i Retrying download ... attempt 3 + i Retrying download ... attempt 2. + i Retrying download ... attempt 3. --- Code out <- download_url(url = "URL", destfile = "destfile", n_tries = 3) Message - i Retrying download ... attempt 2 - i Retrying download ... attempt 3 + i Retrying download ... attempt 2. + i Retrying download ... attempt 3. Condition Error: ! try 3 @@ -34,9 +34,9 @@ Code out <- download_url(url = "URL", destfile = "destfile", n_tries = 10) Message - i Retrying download ... attempt 2 - i Retrying download ... attempt 3 - i Retrying download ... attempt 4 + i Retrying download ... attempt 2. + i Retrying download ... attempt 3. + i Retrying download ... attempt 4. # normalize_url() prepends https:// (or not) From cd7e298c605274aa78c898ee1a5f1e1dffe404e5 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 13:54:49 -0800 Subject: [PATCH 022/111] coverage.R --- R/coverage.R | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/R/coverage.R b/R/coverage.R index 1af822d92..dc9dd5f2a 100644 --- a/R/coverage.R +++ b/R/coverage.R @@ -16,7 +16,9 @@ use_coverage <- function(type = c("codecov", "coveralls"), repo_spec = NULL) { return(invisible(FALSE)) } } else if (type == "coveralls") { - ui_todo("Turn on coveralls for this repo at https://coveralls.io/repos/new") + ui_bullets(c( + "_" = "Turn on coveralls for this repo at {.url https://coveralls.io/repos/new}." + )) } switch( @@ -25,9 +27,10 @@ use_coverage <- function(type = c("codecov", "coveralls"), repo_spec = NULL) { coveralls = use_coveralls_badge(repo_spec) ) - ui_todo(" - Call {ui_code('use_github_action(\"test-coverage\")')} to continuously \\ - monitor test coverage.") + ui_bullets(c( + "_" = "Call {.code use_github_action(\"test-coverage\")} to continuously + monitor test coverage." + )) invisible(TRUE) } From fb2e179489183e7a1d8bb9f321cd77958f7f955f Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 13:59:58 -0800 Subject: [PATCH 023/111] cpp11.R --- R/cpp11.R | 4 +++- tests/testthat/_snaps/cpp11.md | 2 +- vignettes/articles/ui-cli-conversion.Rmd | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/R/cpp11.R b/R/cpp11.R index 0d93fd075..9162b917c 100644 --- a/R/cpp11.R +++ b/R/cpp11.R @@ -37,6 +37,8 @@ check_cpp_register_deps <- function() { installed <- map_lgl(cpp_register_deps, is_installed) if (!all(installed)) { - ui_todo("Now install {ui_value(cpp_register_deps[!installed])} to use cpp11.") + ui_bullets(c( + "_" = "Now install {.pkg {cpp_register_deps[!installed]}} to use {.pkg cpp11}." + )) } } diff --git a/tests/testthat/_snaps/cpp11.md b/tests/testthat/_snaps/cpp11.md index 44c084def..81bfeb151 100644 --- a/tests/testthat/_snaps/cpp11.md +++ b/tests/testthat/_snaps/cpp11.md @@ -3,5 +3,5 @@ Code check_cpp_register_deps() Message - * Now install 'decor', 'vctrs' to use cpp11. + [ ] Now install decor and vctrs to use cpp11. diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 6a891d2f8..242203161 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -164,6 +164,11 @@ It would be nice to create a custom inline class, e.g. `{.usethis_path {some_pat - `{.topic some_topic}` - `{.var variable_name}` +Inline styles that don't get quoted in the absence of color and that I'm worried about: + +* `.field` +* `.pkg` + (I'll talk about `ui_unset()` below.) ## Conditions From c9ef28c97a29563f51ac3590367b1d38d1847229 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 18:29:50 -0800 Subject: [PATCH 024/111] Single quote inline .field if no color --- R/utils-ui.R | 19 ++++++++++++++++++- tests/testthat/_snaps/author.md | 12 ++++++------ tests/testthat/_snaps/data-table.md | 4 ++-- tests/testthat/_snaps/helpers.md | 3 ++- tests/testthat/_snaps/package.md | 2 +- tests/testthat/_snaps/pkgdown.md | 4 ++-- tests/testthat/_snaps/utils-ui.md | 4 ++-- vignettes/articles/ui-cli-conversion.Rmd | 2 +- 8 files changed, 34 insertions(+), 16 deletions(-) diff --git a/R/utils-ui.R b/R/utils-ui.R index 71011e5ca..3317f199a 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -9,10 +9,27 @@ usethis_theme <- function() { ".bullets .bullet-i" = list( "text-exdent" = 2, before = function(x) paste0(cli::col_yellow(cli::symbol$info), " ") - ) + ), + span.field = list(transform = single_quote_if_no_color) ) } +single_quote_if_no_color <- function(x) quote_if_no_color(x, "'") + +quote_if_no_color <- function(x, quote = "'") { + # copied from googledrive + # TODO: if a better way appears in cli, use it + # @gabor says: "if you want to have before and after for the no-color case + # only, we can have a selector for that, such as: + # span.field::no-color + # (but, at the time I write this, cli does not support this yet) + if (cli::num_ansi_colors() > 1) { + x + } else { + paste0(quote, x, quote) + } +} + ui_bullets <- function(text, .envir = parent.frame()) { if (is_quiet()) { return(invisible()) diff --git a/tests/testthat/_snaps/author.md b/tests/testthat/_snaps/author.md index b79592a99..c226a8d95 100644 --- a/tests/testthat/_snaps/author.md +++ b/tests/testthat/_snaps/author.md @@ -3,11 +3,11 @@ Code challenge_legacy_author_fields() Message - x Found legacy Author and/or Maintainer field in DESCRIPTION. - usethis only supports modification of the Authors@R field. + x Found legacy 'Author' and/or 'Maintainer' field in DESCRIPTION. + usethis only supports modification of the 'Authors@R' field. i We recommend one of these paths forward: [ ] Delete the legacy fields and rebuild with `use_author()`; or - [ ] Convert to Authors@R with `desc::desc_coerce_authors_at_r()`, then delete + [ ] Convert to 'Authors@R' with `desc::desc_coerce_authors_at_r()`, then delete the legacy fields. Condition Error: @@ -20,7 +20,7 @@ use_author("Jennifer", "Bryan", role = "cph") Condition Error in `check_author_is_novel()`: - x "Jennifer Bryan" already appears in Authors@R. + x "Jennifer Bryan" already appears in 'Authors@R'. Please make the desired change directly in DESCRIPTION or call the desc package directly. # Placeholder author is challenged @@ -28,8 +28,8 @@ Code use_author("Charlie", "Brown") Message - v Adding to Authors@R in DESCRIPTION: + v Adding to 'Authors@R' in DESCRIPTION: Charlie Brown [ctb] - i Authors@R appears to include a placeholder author: + i 'Authors@R' appears to include a placeholder author: First Last [aut, cre] (YOUR-ORCID-ID) diff --git a/tests/testthat/_snaps/data-table.md b/tests/testthat/_snaps/data-table.md index 55ac13130..8e1f9ab3b 100644 --- a/tests/testthat/_snaps/data-table.md +++ b/tests/testthat/_snaps/data-table.md @@ -18,8 +18,8 @@ Code use_data_table() Message - ! data.table should be in Imports or Suggests, not Depends! - v Removing data.table from Depends. + ! data.table should be in 'Imports' or 'Suggests', not 'Depends'! + v Removing data.table from 'Depends'. v Adding 'data.table' to Imports field in DESCRIPTION v Adding '@importFrom data.table data.table', '@importFrom data.table :=', '@importFrom data.table .SD', '@importFrom data.table .BY', '@importFrom diff --git a/tests/testthat/_snaps/helpers.md b/tests/testthat/_snaps/helpers.md index 55bf8c5c9..62d6a65b5 100644 --- a/tests/testthat/_snaps/helpers.md +++ b/tests/testthat/_snaps/helpers.md @@ -3,7 +3,8 @@ Code use_dependency("usethis", "Suggests") Message - ! Package usethis is already listed in Imports in DESCRIPTION; no change made. + ! Package usethis is already listed in 'Imports' in DESCRIPTION; no change + made. # can add LinkingTo dependency if other dependency already exists diff --git a/tests/testthat/_snaps/package.md b/tests/testthat/_snaps/package.md index 9e5c4988e..f59128b88 100644 --- a/tests/testthat/_snaps/package.md +++ b/tests/testthat/_snaps/package.md @@ -9,7 +9,7 @@ use_package("withr") use_package("withr", "Suggests") Message - ! Package withr is already listed in Imports in DESCRIPTION; no change made. + ! Package withr is already listed in 'Imports' in DESCRIPTION; no change made. # use_package() handles R versions with aplomb diff --git a/tests/testthat/_snaps/pkgdown.md b/tests/testthat/_snaps/pkgdown.md index 30b923024..bc399452c 100644 --- a/tests/testthat/_snaps/pkgdown.md +++ b/tests/testthat/_snaps/pkgdown.md @@ -13,7 +13,7 @@ Code pkgdown_url(pedantic = TRUE) Message - ! pkgdown config does not specify the site's url, which is optional but + ! pkgdown config does not specify the site's 'url', which is optional but recommended. Output NULL @@ -23,7 +23,7 @@ Code pkgdown_url(pedantic = TRUE) Message - ! pkgdown config does not specify the site's url, which is optional but + ! pkgdown config does not specify the site's 'url', which is optional but recommended. Output NULL diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index 3edf821df..9357a492c 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -68,7 +68,7 @@ ui_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", x = "Scary {.code code} or {.fun function}")) Message i Hello, world! - v Updated the BugReports field + v Updated the 'BugReports' field x Scary `code` or `function()` # ui_bullets() does glue interpolation and inline markup [ansi] @@ -86,7 +86,7 @@ ui_bullets(c(i = "Hello, {x}!", v = "Updated the {.field BugReports} field", x = "Scary {.code code} or {.fun function}")) Message ℹ Hello, world! - ✔ Updated the BugReports field + ✔ Updated the 'BugReports' field ✖ Scary `code` or `function()` # ui_bullets() does glue interpolation and inline markup [fancy] diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 242203161..a72f90a0f 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -166,7 +166,7 @@ It would be nice to create a custom inline class, e.g. `{.usethis_path {some_pat Inline styles that don't get quoted in the absence of color and that I'm worried about: -* `.field` +* ~`.field`~ I've fixed this in the theme * `.pkg` (I'll talk about `ui_unset()` below.) From 681ad466ea993862437d6db910c165cce2f41922 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 16 Feb 2024 18:46:48 -0800 Subject: [PATCH 025/111] create.R --- R/create.R | 100 +++++++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/R/create.R b/R/create.R index 4616a9cc6..45d984f4e 100644 --- a/R/create.R +++ b/R/create.R @@ -87,9 +87,11 @@ create_project <- function(path, if (rstudio) { use_rstudio() } else { - ui_done("Writing a sentinel file {ui_path('.here')}") - ui_todo("Build robust paths within your project via {ui_code('here::here()')}") - ui_todo("Learn more at ") + ui_bullets(c( + "v" = "Writing a sentinel file {.path {pth('.here')}}.", + "_" = "Build robust paths within your project via {.fun here::here}.", + "i" = "Learn more at {.url https://here.r-lib.org}." + )) file_create(proj_path(".here")) } @@ -206,26 +208,27 @@ create_from_github <- function(repo_spec, hint <- code_hint_with_host("gh_token_help", host) if (no_auth && is.na(fork)) { - ui_stop(" - Unable to discover a GitHub personal access token - Therefore, can't determine your permissions on {ui_value(repo_spec)} - Therefore, can't decide if `fork` should be `TRUE` or `FALSE` - - You have two choices: - 1. Make your token available (if in doubt, DO THIS): - - Call {ui_code(hint)} for directions - 2. Call {ui_code('create_from_github()')} again, but with \\ - {ui_code('fork = FALSE')} - - Only do this if you are absolutely sure you don't want to fork - - Note you will NOT be in a position to make a pull request") + ui_abort(c( + "x" = "Unable to discover a GitHub personal access token.", + "x" = "Therefore, can't determine your permissions on {.val {repo_spec}}.", + "x" = "Therefore, can't decide if {.arg fork} should be {.code TRUE} or {.code FALSE}.", + "", + "i" = "You have two choices:", + "_" = "Make your token available (if in doubt, DO THIS):", + " " = "Call {.code {hint}} for instructions that should help.", + "_" = "Call {.fun create_from_github} again, but with {.code fork = FALSE}.", + " " = "Only do this if you are absolutely sure you don't want to fork.", + " " = "Note you will NOT be in a position to make a pull request." + )) } if (no_auth && isTRUE(fork)) { - ui_stop(" - Unable to discover a GitHub personal access token - A token is required in order to fork {ui_value(repo_spec)} - - Call {ui_code(hint)} for help configuring a token") + ui_abort(c( + "x" = "Unable to discover a GitHub personal access token.", + "i" = "A token is required in order to fork {.val {repo_spec}}.", + "", + "_" = "Call {.code {hint}} for help configuring a token." + )) } # one of these is true: # - gh is discovering a token for `host` @@ -246,14 +249,15 @@ create_from_github <- function(repo_spec, if (is.na(fork)) { fork <- !isTRUE(repo_info$permissions$push) fork_status <- glue("fork = {fork}") - ui_done("Setting {ui_code(fork_status)}") + ui_bullets(c("v" = "Setting {.code {fork_status}}.")) } # fork is either TRUE or FALSE if (fork && identical(user, repo_info$owner$login)) { - ui_stop(" - Can't fork, because the authenticated user {ui_value(user)} \\ - already owns the source repo {ui_value(repo_info$full_name)}") + ui_abort(c( + "x" = "Can't fork, because the authenticated user {.val {user}} + already owns the source repo {.vale {repo_info$full_name}}." + )) } destdir <- user_path_prep(destdir %||% conspicuous_place()) @@ -265,14 +269,14 @@ create_from_github <- function(repo_spec, if (fork) { ## https://developer.github.com/v3/repos/forks/#create-a-fork - ui_done("Forking {ui_value(repo_info$full_name)}") + ui_bullets(c("v" = "Forking {.val {repo_info$full_name}}.")) upstream_url <- switch( protocol, https = repo_info$clone_url, ssh = repo_info$ssh_url ) repo_info <- gh("POST /repos/{owner}/{repo}/forks") - ui_done("Waiting for the fork to finalize before cloning") + ui_bullets(c("i" = "Waiting for the fork to finalize before cloning...")) Sys.sleep(3) } @@ -282,7 +286,9 @@ create_from_github <- function(repo_spec, ssh = repo_info$ssh_url ) - ui_done("Cloning repo from {ui_value(origin_url)} into {ui_value(repo_path)}") + ui_bullets(c( + "v" = "Cloning repo from {.val {origin_url}} into {.path {repo_path}}." + )) gert::git_clone(origin_url, repo_path, verbose = FALSE) proj_path <- find_rstudio_root(repo_path) @@ -291,16 +297,19 @@ create_from_github <- function(repo_spec, # 2023-01-28 again, it would be more natural to trust the default branch of # the fork, but that cannot always be trusted. For now, we're still using # the default branch learned from the source repo. - ui_info("Default branch is {ui_value(default_branch)}") + ui_bullets(c("i" = "Default branch is {.val {default_branch}}.")) if (fork) { - ui_done("Adding {ui_value('upstream')} remote: {ui_value(upstream_url)}") + ui_bullets(c( + "v" = "Adding {.val upstream} remote: {.val {upstream_url}}" + )) use_git_remote("upstream", upstream_url) pr_merge_main() upstream_remref <- glue("upstream/{default_branch}") - ui_done(" - Setting remote tracking branch for local {ui_value(default_branch)} \\ - branch to {ui_value(upstream_remref)}") + ui_bullets(c( + "v" = "Setting remote tracking branch for local {.val {default_branch}} + branch to {.val {upstream_remref}}." + )) gert::git_branch_set_upstream(upstream_remref, repo = git_repo()) config_key <- glue("remote.upstream.created-by") gert::git_config_set(config_key, "usethis::create_from_github", repo = git_repo()) @@ -344,15 +353,15 @@ challenge_nested_project <- function(path, name) { return(invisible()) } - ui_line( - "New project {ui_value(name)} is nested inside an existing project \\ - {ui_path(path)}, which is rarely a good idea. - If this is unexpected, the here package has a function, \\ - {ui_code('here::dr_here()')} that reveals why {ui_path(path)} \\ - is regarded as a project." - ) + ui_bullets(c( + "!" = "New project {.val {name}} is nested inside an existing project + {.path {pth(path)}}, which is rarely a good idea.", + "i" = "If this is unexpected, the {.pkg here} package has a function, + {.fun here::dr_here} that reveals why {.path {pth(path)}} is regarded + as a project." + )) if (ui_nope("Do you want to create anyway?")) { - ui_stop("Cancelling project creation.") + ui_abort("Cancelling project creation.") } invisible() } @@ -368,12 +377,13 @@ challenge_home_directory <- function(path) { } else { "" } - ui_line(" - {ui_path(path)} is {qualification}your home directory. - It is generally a bad idea to create a new project here. - You should probably create your new project in a subdirectory.") + ui_bullets(c( + "!" = "{.path {pth(path)}} is {qualification}your home directory.", + "i" = "It is generally a bad idea to create a new project here.", + "i" = "You should probably create your new project in a subdirectory." + )) if (ui_nope("Do you want to create anyway?")) { - ui_stop("Good move! Cancelling project creation.") + ui_abort("Good move! Cancelling project creation.") } invisible() } From 582d2710ea71bab3bf9fd9ce5ba7ac05fb1b5ce4 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 21:59:03 -0800 Subject: [PATCH 026/111] data.R --- R/data.R | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/R/data.R b/R/data.R index 2094cf3fc..c2dfc2fd2 100644 --- a/R/data.R +++ b/R/data.R @@ -59,16 +59,21 @@ use_data <- function(..., desc <- proj_desc() if (!desc$has_fields("LazyData")) { - ui_done("Setting {ui_field('LazyData')} to \\ - {ui_value('true')} in {ui_path('DESCRIPTION')}") + ui_bullets(c( + "v" = "Setting {.field LazyData} to {.val true} in {.path DESCRIPTION}.")) desc$set(LazyData = "true") desc$write() } } check_files_absent(proj_path(paths), overwrite = overwrite) - ui_done("Saving {ui_value(unlist(objs))} to {ui_value(paths)}") - if (!internal) ui_todo("Document your data (see {ui_value('https://r-pkgs.org/data.html')})") + ui_bullets(c( + "v" = "Saving {.val {unlist(objs)}} to {.val {paths}}.")) + if (!internal) { + ui_bullets(c( + "_" = "Document your data (see {.url https://r-pkgs.org/data.html})." + )) + } envir <- parent.frame() mapply( @@ -142,6 +147,8 @@ use_data_raw <- function(name = "DATASET", open = rlang::is_interactive()) { open = open ) - ui_todo("Finish the data preparation script in {ui_value(r_path)}") - ui_todo("Use {ui_code('usethis::use_data()')} to add prepared data to package") + ui_bullets(c( + "_" = "Finish writing the data preparation script in {.path {pth(r_path)}}.", + "_" = "Use {.code usethis::use_data()} to add prepared data to package." + )) } From 818632378b18d69b231d87c94b08c325ede4bf23 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 17 Feb 2024 15:10:15 -0800 Subject: [PATCH 027/111] description.R --- R/description.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/R/description.R b/R/description.R index bf62ca184..6c11d860d 100644 --- a/R/description.R +++ b/R/description.R @@ -131,12 +131,12 @@ usethis_description_defaults <- function(package = NULL) { check_package_name <- function(name) { if (!valid_package_name(name)) { - ui_stop(c( - "{ui_value(name)} is not a valid package name. To be allowed on CRAN, it should:", - "* Contain only ASCII letters, numbers, and '.'", - "* Have at least two characters", - "* Start with a letter", - "* Not end with '.'" + ui_abort(c( + "x" = "{.val {name}} is not a valid package name. To be allowed on CRAN, it should:", + "*" = "Contain only ASCII letters, numbers, and '.'.", + "*" = "Have at least two characters.", + "*" = "Start with a letter.", + "*" = "Not end with '.'." )) } } From bf88fdec499831d67afd4b17bcb2a44749b46bf2 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 17 Feb 2024 15:14:25 -0800 Subject: [PATCH 028/111] directory.R --- R/directory.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/directory.R b/R/directory.R index 239b8a67a..479797a11 100644 --- a/R/directory.R +++ b/R/directory.R @@ -26,7 +26,7 @@ create_directory <- function(path) { if (dir_exists(path)) { return(invisible(FALSE)) } else if (file_exists(path)) { - ui_stop("{ui_path(path)} exists but is not a directory.") + ui_abort(c("x" = "{.path {pth(path)}} exists but is not a directory.")) } dir_create(path, recurse = TRUE) @@ -36,7 +36,7 @@ create_directory <- function(path) { check_path_is_directory <- function(path) { if (!file_exists(path)) { - ui_stop("Directory {ui_path(path)} does not exist.") + ui_abort(c("x" = "Directory {.path {pth(path)}} does not exist.")) } if (is_link(path)) { @@ -44,7 +44,7 @@ check_path_is_directory <- function(path) { } if (!is_dir(path)) { - ui_stop("{ui_path(path)} is not a directory.") + ui_abort(c("x" = "{.path {pth(path)}} is not a directory.")) } } @@ -58,7 +58,7 @@ directory_has_files <- function(x) { check_directory_is_empty <- function(x) { if (directory_has_files(x)) { - ui_stop("{ui_path(x)} exists and is not an empty directory.") + ui_abort(c("x" = "{.path {pth(path)}} exists and is not an empty directory.")) } invisible(x) } From a468a4aee1376303b4ec3c40a1284e779ded1dc0 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 17 Feb 2024 17:51:06 -0800 Subject: [PATCH 029/111] edit.R --- R/edit.R | 28 ++++++++++++++++------------ tests/testthat/_snaps/pkgdown.md | 2 +- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/R/edit.R b/R/edit.R index 84de7ae59..58b025d12 100644 --- a/R/edit.R +++ b/R/edit.R @@ -24,11 +24,11 @@ edit_file <- function(path, open = rlang::is_interactive()) { file_create(path) if (!open) { - ui_todo("Edit {ui_path(path)}") + ui_bullets(c("_" = "Edit {.path {pth(path)}}.")) return(invisible(path)) } - ui_todo("Modify {ui_path(path)}") + ui_bullets(c("_" = "Modify {.path {pth(path)}}.")) if (rstudio_available() && rstudioapi::hasFun("navigateToFile")) { rstudioapi::navigateToFile(path) } else { @@ -45,7 +45,9 @@ edit_template <- function(template = NULL, open = rlang::is_interactive()) { check_is_package("edit_template()") if (is.null(template)) { - ui_info("No template specified... checking {ui_path('inst/templates')}") + ui_bullets(c( + "!" = "No template specified ... checking {.path {pth('inst/templates')}}." + )) template <- choose_template() } @@ -110,7 +112,7 @@ NULL edit_r_profile <- function(scope = c("user", "project")) { path <- scoped_path_r(scope, ".Rprofile", envvar = "R_PROFILE_USER") edit_file(path) - ui_todo("Restart R for changes to take effect") + ui_bullets(c("_" = "Restart R for changes to take effect.")) invisible(path) } @@ -119,7 +121,7 @@ edit_r_profile <- function(scope = c("user", "project")) { edit_r_environ <- function(scope = c("user", "project")) { path <- scoped_path_r(scope, ".Renviron", envvar = "R_ENVIRON_USER") edit_file(path) - ui_todo("Restart R for changes to take effect") + ui_bullets(c("_" = "Restart R for changes to take effect.")) invisible(path) } @@ -159,15 +161,17 @@ edit_rstudio_snippets <- function(type = c( if (new_rstudio && file_exists(old_path) && !file_exists(new_path)) { create_directory(path_dir(new_path)) file_copy(old_path, new_path) - ui_done("Copying snippets file to {ui_path(new_path)}") + ui_bullets(c( + "v" = "Copying snippets file to {.path {pth(new_path)}}." + )) } path <- if (new_rstudio) new_path else old_path if (!file_exists(path)) { - ui_done("New snippet file at {ui_path(path)}") - ui_info(c( - "This masks the default snippets for {ui_field(type)}.", - "Delete this file and restart RStudio to restore the default snippets." + ui_bullets(c( + "v" = "New snippet file at {.path {pth(path)}}.", + "i" = "This masks the default snippets for {.field {type}}.", + "i" = "Delete this file and restart RStudio to restore the default snippets." )) } edit_file(path) @@ -179,7 +183,7 @@ edit_rstudio_prefs <- function() { path <- rstudio_config_path("rstudio-prefs.json") edit_file(path) - ui_todo("Restart RStudio for changes to take effect") + ui_bullets(c("_" = "Restart RStudio for changes to take effect.")) invisible(path) } @@ -248,7 +252,7 @@ git_ignore_path <- function(scope = c("user", "project")) { edit_pkgdown_config <- function() { path <- pkgdown_config_path() if (is.null(path)) { - ui_oops("No pkgdown config file found in current Project.") + ui_bullets(c("x" = "No pkgdown config file found in current Project.")) } else { invisible(edit_file(path)) } diff --git a/tests/testthat/_snaps/pkgdown.md b/tests/testthat/_snaps/pkgdown.md index bc399452c..d9e7863bf 100644 --- a/tests/testthat/_snaps/pkgdown.md +++ b/tests/testthat/_snaps/pkgdown.md @@ -6,7 +6,7 @@ v Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore' v Adding 'docs' to '.gitignore' v Writing '_pkgdown.yml' - * Edit '_pkgdown.yml' + [ ] Edit '_pkgdown.yml'. # pkgdown_url() returns correct data, warns if pedantic From 2530f03991494716d9ebda92f227b55f9715c865 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 12:10:27 -0800 Subject: [PATCH 030/111] git-default-branch.R --- R/git-default-branch.R | 192 +++++++++++--------- tests/testthat/_snaps/git-default-branch.md | 20 +- 2 files changed, 120 insertions(+), 92 deletions(-) diff --git a/R/git-default-branch.R b/R/git-default-branch.R index 265bc29b1..f79e51eee 100644 --- a/R/git-default-branch.R +++ b/R/git-default-branch.R @@ -107,15 +107,16 @@ git_default_branch <- function() { # 404s critique_remote <- function(remote) { if (remote$is_configured && is.na(remote$default_branch)) { - ui_oops(" - The {ui_value(remote$name)} remote is configured, but we can't \\ - determine its default branch. - Possible reasons: - - The remote repo no longer exists, suggesting the local remote should - be deleted. - - We are offline or that specific Git server is down. - - You don't have the necessary permission or something is wrong with - your credentials.") + ui_bullets(c( + "x" = "The {.val {remote$name}} remote is configured, but we can't + determine its default branch.", + " " = "Possible reasons:", + "*" = "The remote repo no longer exists, suggesting the local remote + should be deleted.", + "*" = "We are offline or that specific Git server is down.", + "*" = "You don't have the necessary permission or something is wrong + with your credentials." + )) } } @@ -143,19 +144,19 @@ git_default_branch <- function() { if (is.na(db_local_with_source) ) { if (length(db_source)) { ui_abort(c( - "Default branch mismatch between local repo and remote.", - "The default branch of the {.val {db_source$name}} remote is - {.val {db_source$default_branch}}.", - "But the local repo has no branch named - {.val {db_source$default_branch}}.", - "Call {.code git_default_branch_rediscover()} to resolve this." + "x" = "Default branch mismatch between local repo and remote.", + "i" = "The default branch of the {.val {db_source$name}} remote is + {.val {db_source$default_branch}}.", + " " = "But the local repo has no branch named + {.val {db_source$default_branch}}.", + "_" = "Call {.fun git_default_branch_rediscover} to resolve this." ), class = "error_default_branch", db_source = db_source ) } else { ui_abort( - "Can't determine the local repo's default branch.", + c("x" = "Can't determine the local repo's default branch."), class = "error_default_branch" ) } @@ -171,12 +172,12 @@ git_default_branch <- function() { # the local default branch ui_abort(c( - "Default branch mismatch between local repo and remote.", - "The default branch of the {.val {db_source$name}} remote is - {.val {db_source$default_branch}}.", - "But the default branch of the local repo appears to be - {.val {db_local_with_source}}.", - "Call {.code git_default_branch_rediscover()} to resolve this." + "x" = "Default branch mismatch between local repo and remote.", + "i" = "The default branch of the {.val {db_source$name}} remote is + {.val {db_source$default_branch}}.", + " " = "But the default branch of the local repo appears to be + {.val {db_local_with_source}}.", + "_" = "Call {.fun git_default_branch_rediscover} to resolve this." ), class = "error_default_branch", db_source = db_source, db_local = db_local_with_source @@ -244,9 +245,10 @@ guess_local_default_branch <- function(prefer = NULL, verbose = FALSE) { gb <- gert::git_branch_list(local = TRUE, repo = repo)[["name"]] if (length(gb) == 0) { - ui_stop(" - Can't find any local branches. - Do you need to make your first commit?") + ui_abort(c( + "x" = "Can't find any local branches.", + " " = "Do you need to make your first commit?" + )) } candidates <- c(prefer, default_branch_candidates()) @@ -258,17 +260,19 @@ guess_local_default_branch <- function(prefer = NULL, verbose = FALSE) { db <- first_matched(gb, candidates) } else { # TODO: perhaps this should be classed, so I can catch it and distinguish - # from the ui_stop() above, where there are no local branches. - ui_stop(" - Unable to guess which existing local branch plays the role of the default.") + # from the ui_abort() above, where there are no local branches. + ui_abort(c( + "x" = "Unable to guess which existing local branch plays the role of the + default." + )) } if (verbose) { - ui_info(" - Local branch {ui_value(db)} appears to play the role of \\ - the default branch.") + ui_bullets(c( + "i" = "Local branch {.val {db}} appears to play the role of the default + branch." + )) } - db } @@ -281,8 +285,10 @@ guess_local_default_branch <- function(prefer = NULL, verbose = FALSE) { #' } git_default_branch_configure <- function(name = "main") { check_string(name) - ui_done("Configuring {ui_field('init.defaultBranch')} as {ui_value(name)}.") - ui_info("Remember: this only affects repos you create in the future.") + ui_bullets(c( + "v" = "Configuring {.field init.defaultBranch} as {.val {name}}.", + "i" = "Remember: this only affects repos you create in the future!" + )) use_git_config(scope = "user", `init.defaultBranch` = name) invisible(name) } @@ -323,7 +329,7 @@ git_default_branch_rename <- function(from = NULL, to = "main") { if (!is.null(from) && !gert::git_branch_exists(from, local = TRUE, repo = repo)) { - ui_stop("Can't find existing branch named {ui_value(from)}.") + ui_abort(c("x" = "Can't find existing branch named {.val {from}}.")) } cfg <- github_remote_config(github_get = TRUE) @@ -332,9 +338,13 @@ git_default_branch_rename <- function(from = NULL, to = "main") { if (cfg$type == "no_github") { from <- from %||% guess_local_default_branch(verbose = TRUE) if (from == to) { - ui_info("Local repo already has {ui_value(from)} as its default branch.") + ui_bullets(c( + "i" = "Local repo already has {.val {from}} as its default branch." + )) } else { - ui_done("Moving local {ui_value(from)} branch to {ui_value(to)}.") + ui_bullets(c( + "v" = "Moving local {.val {from}} branch to {.val {to}}." + )) gert::git_branch_move(branch = from, new_branch = to, repo = repo) rstudio_git_tickle() report_fishy_files(old_name = from, new_name = to) @@ -347,24 +357,26 @@ git_default_branch_rename <- function(from = NULL, to = "main") { old_source_db <- tr$default_branch if (!isTRUE(tr$can_admin)) { - ui_stop(" - You don't seem to have {ui_field('admin')} permissions for the source \\ - repo {ui_value(tr$repo_spec)}, which is required to rename the default \\ - branch.") + ui_abort(c( + "x" = "You don't seem to have {.field admin} permissions for the source + repo {.val {tr$repo_spec}}, which is required to rename the default + branch." + )) } old_local_db <- from %||% guess_local_default_branch(old_source_db, verbose = FALSE) if (old_local_db != old_source_db) { - ui_oops(" - It's weird that the current default branch for your local repo and \\ - the source repo are different: - {ui_value(old_local_db)} (local) != {ui_value(old_source_db)} (source)") + ui_bullets(c( + "!" = "It's weird that the current default branch for your local repo and + the source repo are different:", + " " = "{.val {old_local_db}} (local) != {.val {old_source_db}} (source)" + )) if (ui_nope( "Are you sure you want to proceed?", yes = "yes", no = "no", shuffle = FALSE)) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } @@ -380,13 +392,15 @@ git_default_branch_rename <- function(from = NULL, to = "main") { } if (source_update) { - ui_done(" - Default branch of the source repo {ui_value(tr$repo_spec)} has moved: \\ - {ui_value(old_source_db)} --> {ui_value(to)}") + ui_bullets(c( + "v" = "Default branch of the source repo {.val {tr$repo_spec}} has moved:", + " " = "{.val {old_source_db}} {cli::symbol$arrow_right} {.val {to}}" + )) } else { - ui_done(" - Default branch of source repo {ui_value(tr$repo_spec)} is \\ - {ui_value(to)}. Nothing to be done.") + ui_bullets(c( + "i" = "Default branch of source repo {.val {tr$repo_spec}} is + {.val {to}}. Nothing to be done." + )) } report_fishy_files(old_name = old_local_db, new_name = to) @@ -414,7 +428,9 @@ rediscover_default_branch <- function(old_name = NULL, report_on_source = TRUE) repo <- git_repo() if (!is.null(old_name) && !gert::git_branch_exists(old_name, local = TRUE, repo = repo)) { - ui_stop("Can't find existing local branch named {ui_value(old_name)}.") + ui_abort(c( + "x" = "Can't find existing local branch named {.val {old_name}}." + )) } cfg <- github_remote_config(github_get = TRUE) @@ -484,27 +500,33 @@ rediscover_default_branch <- function(old_name = NULL, report_on_source = TRUE) } if (report_on_source) { - ui_info(" - Default branch of the source repo {ui_value(tr$repo_spec)}: {ui_value(db)}") + ui_bullets(c( + "i" = "Default branch of the source repo {.val {tr$repo_spec}} is + {.val {db}}." + )) } if (local_update) { - ui_done(" - Default branch of local repo has moved: \\ - {ui_value(old_name)} --> {ui_value(db)}") + ui_bullets(c( + "v" = "Default branch of local repo has moved: + {.val {old_name}} {cli::symbol$arrow_right} {.val {db}}" + )) } else { - ui_done(" - Default branch of local repo is {ui_value(db)}. Nothing to be done.") + ui_bullets(c( + "i" = "Default branch of local repo is {.val {db}}. Nothing to be done." + )) } if (cfg$type == "fork") { if (fork_update) { - ui_done(" - Default branch of your fork has moved: \\ - {ui_value(old_name_fork)} --> {ui_value(db)}") + ui_bullets(c( + "v" = "Default branch of your fork has moved: + {.val {old_name_fork}} {cli::symbol$arrow_right} {.val {db}}" + )) } else { - ui_done(" - Default branch of your fork is {ui_value(db)}. Nothing to be done.") + ui_bullets(c( + "i" = "Default branch of your fork is {.val {db}}. Nothing to be done." + )) } } @@ -522,16 +544,17 @@ challenge_non_default_branch <- function(details = "Are you sure you want to pro if (ui_nope(" Current branch ({ui_value(actual)}) is not repo's default \\ branch ({ui_value(default_branch)}).{details}")) { - ui_stop("Cancelling. Not on desired branch.") + ui_abort("Cancelling. Not on desired branch.") } } invisible() } report_fishy_files <- function(old_name = "master", new_name = "main") { - ui_todo(" - Be sure to update files that refer to the default branch by name. - Consider searching within your project for {ui_value(old_name)}.") + ui_bullets(c( + "_" = "Be sure to update files that refer to the default branch by name.", + " " = "Consider searching within your project for {.val {old_name}}." + )) # I don't want failure of a fishy file check to EVER cause # git_default_branch_rename() to fail and prevent the call to # git_default_branch_rediscover() @@ -566,11 +589,14 @@ fishy_github_actions <- function(new_name = "main") { } paths <- sort(paths) - ui_paths <- map_chr(paths, ui_path) - - ui_oops(c( - "These GitHub Action files don't mention the new default branch {ui_value(new_name)}:", - paste0("- ", ui_paths) + ui_paths <- map_chr(paths, ui_path_impl) + + # TODO: the ui_paths used to be a nested bullet list + # if that ever becomes possible/easier with cli, go back to that + ui_bullets(c( + "x" = "{cli::qty(length(ui_paths))}{?No/This/These} GitHub Action file{?s} + {?/doesn't/don't} mention the new default branch {.val {new_name}}:", + " " = "{.path {ui_paths}}" )) invisible(paths) @@ -597,10 +623,11 @@ fishy_badges <- function(old_name = "master") { return(invisible(character())) } - ui_path <- ui_path(proj_rel_path(path)) - ui_oops(c( - "Some badges may refer to the old default branch {ui_value(old_name)}:", - paste0("- ", ui_path) + ui_path <- ui_path_impl(proj_rel_path(path)) + ui_bullets(c( + "x" = "Some badges appear to refer to the old default branch + {.val {old_name}}.", + "_" = "Check and correct, if needed, in this file: {.file {ui_path}}" )) invisible(path) @@ -630,10 +657,11 @@ fishy_bookdown_config <- function(old_name = "master") { return(invisible(character())) } - ui_path <- ui_path(proj_rel_path(bookdown_config)) - ui_oops(c( - "The bookdown configuration file may refer to the old default branch {ui_value(old_name)}:", - paste0("- ", ui_path) + ui_path <- ui_path_impl(proj_rel_path(bookdown_config)) + ui_bullets(c( + "x" = "The bookdown configuration file may refer to the old default branch + {.val {old_name}}.", + "_" = "Check and correct, if needed, in this file: {.file {ui_path}}" )) invisible(path) diff --git a/tests/testthat/_snaps/git-default-branch.md b/tests/testthat/_snaps/git-default-branch.md index d3853d54d..9c24be952 100644 --- a/tests/testthat/_snaps/git-default-branch.md +++ b/tests/testthat/_snaps/git-default-branch.md @@ -3,14 +3,14 @@ Code git_default_branch_rename() Message - i Local branch 'master' appears to play the role of the default branch. - v Moving local 'master' branch to 'main'. - * Be sure to update files that refer to the default branch by name. - Consider searching within your project for 'master'. - x These GitHub Action files don't mention the new default branch 'main': - - '.github/workflows/blah.yml' - x Some badges may refer to the old default branch 'master': - - 'README.md' - x The bookdown configuration file may refer to the old default branch 'master': - - 'whatever/foo/_bookdown.yaml' + i Local branch "master" appears to play the role of the default branch. + v Moving local "master" branch to "main". + [ ] Be sure to update files that refer to the default branch by name. + Consider searching within your project for "master". + x This GitHub Action file doesn't mention the new default branch "main": + '.github/workflows/blah.yml' + x Some badges appear to refer to the old default branch "master". + [ ] Check and correct, if needed, in this file: 'README.md' + x The bookdown configuration file may refer to the old default branch "master". + [ ] Check and correct, if needed, in this file: 'whatever/foo/_bookdown.yaml' From 73b986c242a4ff5df2b41e14e856a6296c55e2af Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 15:47:20 -0800 Subject: [PATCH 031/111] git.R --- NAMESPACE | 3 + R/git.R | 111 ++++++++++++----------- R/utils-ui.R | 46 ++++++++++ vignettes/articles/ui-cli-conversion.Rmd | 13 +++ 4 files changed, 118 insertions(+), 55 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index b6bcb9a93..3648d5db7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,9 @@ S3method(format,github_remote_config) S3method(print,github_remote_config) S3method(print,sitrep) +S3method(usethis_map_cli,"NULL") +S3method(usethis_map_cli,character) +S3method(usethis_map_cli,default) export(browse_circleci) export(browse_cran) export(browse_github) diff --git a/R/git.R b/R/git.R index 80681423d..06b130cae 100644 --- a/R/git.R +++ b/R/git.R @@ -13,7 +13,7 @@ use_git <- function(message = "Initial commit") { needs_init <- !uses_git() if (needs_init) { - ui_done("Initialising Git repo") + ui_bullets(c("v" = "Initialising Git repo.")) git_init() } @@ -159,7 +159,7 @@ use_git_config <- function(scope = c("user", "project"), ...) { git_protocol <- function() { protocol <- tolower(getOption("usethis.protocol", "unset")) if (identical(protocol, "unset")) { - ui_info("Defaulting to {ui_value('https')} Git protocol") + ui_bullets(c("i" = "Defaulting to {.val https} Git protocol.")) protocol <- "https" } else { check_protocol(protocol) @@ -179,9 +179,7 @@ check_protocol <- function(protocol) { if (!is_string(protocol) || !(tolower(protocol) %in% c("https", "ssh"))) { options(usethis.protocol = NULL) - ui_stop(" - {ui_code('protocol')} must be either {ui_value('https')} or \\ - {ui_value('ssh')}") + ui_abort(c("x" = "{.arg protocol} must be either {.val https} or {.val ssh}.")) } invisible() } @@ -251,9 +249,10 @@ use_git_remote <- function(name = "origin", url, overwrite = FALSE) { repo <- git_repo() if (name %in% names(remotes) && !overwrite) { - ui_stop(" - Remote {ui_value(name)} already exists. Use \\ - {ui_code('overwrite = TRUE')} to edit it anyway.") + ui_abort(c( + "x" = "Remote {.val {name}} already exists.", + "i" = "Use {.code overwrite = TRUE} to edit it anyway." + )) } if (name %in% names(remotes)) { @@ -289,32 +288,22 @@ git_clean <- function() { paths <- st[st$status == "new", ][["file"]] n <- length(paths) if (n == 0) { - ui_info("Found no untracked files") + ui_bullets(c("i" = "Found no untracked files.")) return(invisible()) } paths <- sort(paths) - ui_paths <- map_chr(paths, ui_path) - if (n > 10) { - ui_paths <- c(ui_paths[1:10], "...") - } - - if (n == 1) { - file_hint <- "There is 1 untracked file:" - } else { - file_hint <- "There are {n} untracked files:" - } - ui_line(c( - file_hint, - paste0("* ", ui_paths) + ui_paths <- map_chr(paths, ui_path_impl) + ui_bullets(c( + "i" = "{cli::qty(n)}There {?is/are} {n} untracked file{?s}:", + bulletize(usethis_map_cli(ui_paths, template = "{.file <>}")) )) if (ui_yeah(" - Do you want to remove {if (n == 1) 'it' else 'them'}?", yes = "yes", no = "no", shuffle = FALSE)) { file_delete(paths) - ui_done("{n} file(s) deleted") + ui_bullets(c("v" = "{n} file{?s} deleted.")) } rstudio_git_tickle() invisible() @@ -356,7 +345,7 @@ git_sitrep <- function(tool = c("git", "github"), vaccinated <- git_vaccinated() kv_line("Vaccinated", vaccinated) if (!vaccinated) { - ui_info("See {ui_code('?git_vaccinate')} to learn more") + ui_bullets(c("i" = "See {.fun git_vaccinate} to learn more.")) } kv_line("Default Git protocol", git_protocol()) kv_line("Default initial branch name", init_default_branch) @@ -376,13 +365,13 @@ git_sitrep <- function(tool = c("git", "github"), } if (!proj_active()) { - ui_info("No active usethis project") + ui_bullets(c("i" = "No active usethis project.")) return(invisible()) } - cli::cli_h2(glue("Active usethis project: {ui_value(proj_get())}")) + cli::cli_h2("Active usethis project: {.val {proj_get()}}") if (!uses_git()) { - ui_info("Active project is not a Git repo") + ui_bullets(c("i" = "Active project is not a Git repo.")) return(invisible()) } @@ -402,9 +391,11 @@ git_sitrep <- function(tool = c("git", "github"), default_branch_sitrep() # vertical alignment would make this nicer, but probably not worth it - ui_bullet(glue(" - Current local branch -> remote tracking branch: - {ui_value(branch)} -> {ui_value(tracking_branch)}")) + ui_bullets(c( + "*" = "Current local branch {cli::symbol$arrow_right} remote tracking + branch:", + " " = "{.val {branch}} {cli::symbol$arrow_right} {.val {tracking_branch}}" + )) } # GitHub remote config ------------------------------------------------------- @@ -414,7 +405,7 @@ git_sitrep <- function(tool = c("git", "github"), cfg <- github_remote_config() if (cfg$type == "no_github") { - ui_info("Project does not use GitHub") + ui_bullets(c("i" = "Project does not use GitHub.")) return(invisible()) } @@ -441,7 +432,7 @@ git_user_sitrep <- function(scope = c("user", "project")) { user_local <- git_user_get("local") if (scope == "project" && !all(map_lgl(user_local, is.null))) { - ui_info("This repo has a locally configured user") + ui_bullets(c("i" = "This repo has a locally configured user.")) } kv_line("Name", user$name) @@ -456,47 +447,55 @@ git_user_check <- function(user) { if (all(map_lgl(user, is.null))) { hint <- 'use_git_config(user.name = "", user.email = "")' - ui_oops( - "Git user's name and email are not set. Configure using {ui_code(hint)}." - ) + ui_bullets(c( + "x" = "Git user's name and email are not set.", + "i" = "Configure using {.code {hint}}." + )) return(invisible(NULL)) } if (is.null(user$name)) { hint <- 'use_git_config(user.name = "")' - ui_oops("Git user's name is not set. Configure using {ui_code(hint)}.") + ui_bullets(c( + "x" = "Git user's name is not set.", + "i" = "Configure using {.code {hint}}." + )) } if (is.null(user$email)) { hint <- 'use_git_config(user.email = "")' - ui_oops("Git user's email is not set. Configure using {ui_code(hint)}.") + ui_bullets(c( + "x" = "Git user's email is not set.", + "i" = "Configure using {.code {hint}}." + )) } } -# TODO: when I really overhaul the UI, determine if I can just re-use the -# git_default_branch() error messages in the sitrep -# the main point is converting an error to an "oops" type of message default_branch_sitrep <- function() { tryCatch( kv_line("Default branch", git_default_branch()), error_default_branch = function(e) { if (has_name(e, "db_local")) { # FYI existence of db_local implies existence of db_source - ui_oops(" - Default branch mismatch between local repo and remote! - {ui_value(e$db_source$name)} remote default branch: \\ - {ui_value(e$db_source$default_branch)} - Local default branch: {ui_value(e$db_local)} - Call {ui_code('git_default_branch_rediscover()')} to resolve this.") + ui_bullets(c( + "x" = "Default branch mismatch between local repo and remote.", + "i" = "The default branch of the {.val {e$db_source$name}} remote is + {.val {e$db_source$default_branch}}.", + "!" = "The local repo has no branch named + {.val {e$db_source$default_branch}}.", + "_" = "Call {.fun git_default_branch_rediscover} to resolve this." + )) } else if (has_name(e, "db_source")) { - ui_oops(" - Default branch mismatch between local repo and remote! - {ui_value(e$db_source$name)} remote default branch: \\ - {ui_value(e$db_source$default_branch)} - Local repo has no branch by that name nor any other obvious candidates. - Call {ui_code('git_default_branch_rediscover()')} to resolve this.") + ui_bullets(c( + "x" = "Default branch mismatch between local repo and remote.", + "i" = "The default branch of the {.val {e$db_source$name}} remote is + {.val {e$db_source$default_branch}}.", + "!" = "The local repo has no branch by that name, nor any other + obvious candidates.", + "_" = "Call {.fun git_default_branch_rediscover} to resolve this." + )) } else { - ui_oops("Default branch cannot be determined.") + ui_bullets(c("Default branch cannot be determined.")) } } ) @@ -517,7 +516,9 @@ git_vaccinate <- function() { ensure_core_excludesFile() path <- git_ignore_path(scope = "user") if (!file_exists(path)) { - ui_done("Creating the global (user-level) gitignore: {ui_path(path)}") + ui_bullets(c( + "v" = "Creating the global (user-level) gitignore: {.path {pth(path)}}" + )) } write_union(path, git_ignore_lines) } diff --git a/R/utils-ui.R b/R/utils-ui.R index 3317f199a..46a873966 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -103,3 +103,49 @@ ui_code_snippet <- function(x, invisible(x) } + +# inspired by gargle::gargle_map_cli() and gargle::bulletize() +usethis_map_cli <- function(x, ...) UseMethod("usethis_map_cli") + +#' @export +usethis_map_cli.default <- function(x, ...) { + ui_abort(c( + "x" = "Don't know how to {.fun usethis_map_cli} an object of class + {.obj_type_friendlys {x}}." + )) +} + +#' @export +usethis_map_cli.NULL <- function(x, ...) NULL + +#' @export +usethis_map_cli.character <- function(x, + template = "{.field <>}", + .open = "<<", .close = ">>", + ...) { + as.character(glue(template, .open = .open, .close = .close)) +} + +bulletize <- function(x, bullet = "*", n_show = 5, n_fudge = 2) { + n <- length(x) + n_show_actual <- compute_n_show(n, n_show, n_fudge) + out <- utils::head(x, n_show_actual) + n_not_shown <- n - n_show_actual + + out <- set_names(out, rep_along(out, bullet)) + + if (n_not_shown == 0) { + out + } else { + c(out, " " = glue("{cli::symbol$ellipsis} and {n_not_shown} more")) + } +} + +# I don't want to do "... and x more" if x is silly, i.e. 1 or 2 +compute_n_show <- function(n, n_show_nominal = 5, n_fudge = 2) { + if (n > n_show_nominal && n - n_show_nominal > n_fudge) { + n_show_nominal + } else { + n + } +} diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index a72f90a0f..d2207a554 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -231,3 +231,16 @@ This is a small clump of functions that support sitrep-type output. ## Questions There's currently no cli alternative to `ui_yeah()` and `ui_nope()`, so I won't make changes here. + +TO RECONSIDER: If I keep using `ui_yeah()` and `ui_nope()`, that means I retain usage of legacy inline styling. For example: + +```{r} +#| eval: false +if (ui_nope(" + Current branch ({ui_value(actual)}) is not repo's default \\ + branch ({ui_value(default_branch)}).{details}")) { + ui_abort("Cancelling. Not on desired branch.") + } +``` + +If I create new versions of `ui_yeah()` and `ui_nope()`, then I could still switch over to cli's inline styling. Otherwise, I won't be able to remove all internal usage of legacy `ui_*()` functions. From a64ecf46b378e73755c66a71685cc99f098f40bb Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 15:56:44 -0800 Subject: [PATCH 032/111] Test bulletize() and usethis_map_cli() --- tests/testthat/_snaps/utils-ui.md | 96 +++++++++++++++++++++++++++++++ tests/testthat/test-utils-ui.R | 20 +++++++ 2 files changed, 116 insertions(+) diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index 9357a492c..bffe059bb 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -196,3 +196,99 @@ Message foo <- function(x){x} +# bulletize() works + + Code + ui_bullets(bulletize(letters)) + Message + * a + * b + * c + * d + * e + ... and 21 more + +--- + + Code + ui_bullets(bulletize(letters, bullet = "x")) + Message + x a + x b + x c + x d + x e + ... and 21 more + +--- + + Code + ui_bullets(bulletize(letters, n_show = 2)) + Message + * a + * b + ... and 24 more + +--- + + Code + ui_bullets(bulletize(letters[1:6])) + Message + * a + * b + * c + * d + * e + * f + +--- + + Code + ui_bullets(bulletize(letters[1:7])) + Message + * a + * b + * c + * d + * e + * f + * g + +--- + + Code + ui_bullets(bulletize(letters[1:8])) + Message + * a + * b + * c + * d + * e + ... and 3 more + +--- + + Code + ui_bullets(bulletize(letters[1:6], n_fudge = 0)) + Message + * a + * b + * c + * d + * e + ... and 1 more + +--- + + Code + ui_bullets(bulletize(letters[1:8], n_fudge = 3)) + Message + * a + * b + * c + * d + * e + * f + * g + * h + diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 59ed7496d..1483feaa0 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -140,3 +140,23 @@ cli::test_that_cli("ui_code_snippet() can NOT interpolate", { ) }) }, configs = c("plain", "ansi")) + +test_that("bulletize() works", { + withr::local_options(list(usethis.quiet = FALSE)) + expect_snapshot(ui_bullets(bulletize(letters))) + expect_snapshot(ui_bullets(bulletize(letters, bullet = "x"))) + expect_snapshot(ui_bullets(bulletize(letters, n_show = 2))) + expect_snapshot(ui_bullets(bulletize(letters[1:6]))) + expect_snapshot(ui_bullets(bulletize(letters[1:7]))) + expect_snapshot(ui_bullets(bulletize(letters[1:8]))) + expect_snapshot(ui_bullets(bulletize(letters[1:6], n_fudge = 0))) + expect_snapshot(ui_bullets(bulletize(letters[1:8], n_fudge = 3))) +}) + +test_that("usethis_map_cli() works", { + x <- c("aaa", "bbb", "ccc") + expect_equal( + usethis_map_cli(x, template = "{.file <>}"), + c("{.file aaa}", "{.file bbb}", "{.file ccc}") + ) +}) From bcb5f6b604c11892535caf68580a46b6a1fec38a Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 16:18:04 -0800 Subject: [PATCH 033/111] github-actions.R --- R/github-actions.R | 13 +++++++------ tests/testthat/_snaps/github-actions.md | 9 +++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/R/github-actions.R b/R/github-actions.R index 846c3a508..b99545e15 100644 --- a/R/github-actions.R +++ b/R/github-actions.R @@ -121,7 +121,7 @@ use_github_action <- function(name = NULL, } if (!is.null(readme)) { - ui_todo("Learn more at <{readme}>.") + ui_bullets(c("_" = "Learn more at {.url {readme}}.")) } badge <- badge %||% is_check_action(url) @@ -231,7 +231,7 @@ use_tidy_github_actions <- function(ref = NULL) { "Remove existing {ui_path('.travis.yml')} and {ui_path('appveyor.yml')}?" )) { file_delete(old_configs[has_appveyor_travis]) - ui_todo("Remove old badges from README") + ui_bullets(c("_" = "Remove old badges from README.")) } } @@ -250,10 +250,11 @@ check_uses_github_actions <- function() { return(invisible()) } - ui_stop(" - Cannot detect that package {ui_value(project_name())} already \\ - uses GitHub Actions. - Do you need to run {ui_code('use_github_actions()')}?") + ui_abort(c( + "x" = "Cannot detect that package {.pkg {project_name()}} already uses + GitHub Actions.", + "i" = "Do you need to run {.fun use_github_actions}?" + )) } latest_release <- function(repo_spec = "https://github.com/r-lib/actions") { diff --git a/tests/testthat/_snaps/github-actions.md b/tests/testthat/_snaps/github-actions.md index d5ce82073..018ecf6bb 100644 --- a/tests/testthat/_snaps/github-actions.md +++ b/tests/testthat/_snaps/github-actions.md @@ -9,7 +9,8 @@ v Adding '*.html' to '.github/.gitignore' v Creating '.github/workflows/' v Saving 'r-lib/actions/examples/check-full.yaml@v2' to '.github/workflows/R-CMD-check.yaml' - * Learn more at . + [ ] Learn more at + . v Adding R-CMD-check badge to 'README.md'. # use_github_action() still errors in non-interactive environment @@ -32,7 +33,7 @@ Code check_uses_github_actions() Condition - Error: - ! Cannot detect that package '{TESTPKG}' already uses GitHub Actions. - Do you need to run `use_github_actions()`? + Error in `check_uses_github_actions()`: + x Cannot detect that package {TESTPKG} already uses GitHub Actions. + i Do you need to run `use_github_actions()`? From 8de285e3a5878ee81e84824c04c31fdd1f24f139 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 18:22:02 -0800 Subject: [PATCH 034/111] .val seems like a better default for usethis --- R/utils-ui.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils-ui.R b/R/utils-ui.R index 46a873966..f5a212a87 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -120,7 +120,7 @@ usethis_map_cli.NULL <- function(x, ...) NULL #' @export usethis_map_cli.character <- function(x, - template = "{.field <>}", + template = "{.val <>}", .open = "<<", .close = ">>", ...) { as.character(glue(template, .open = .open, .close = .close)) From 7e9aeb933545f6eaec427f6a1a7fe667cb508cbe Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 18:22:12 -0800 Subject: [PATCH 035/111] github-labels.R --- R/github-labels.R | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/R/github-labels.R b/R/github-labels.R index 2da10a7e7..cb5a37e3b 100644 --- a/R/github-labels.R +++ b/R/github-labels.R @@ -109,11 +109,16 @@ use_github_labels <- function(repo_spec = deprecated(), cur_label_names <- label_attr("name", cur_labels) to_rename <- intersect(cur_label_names, names(rename)) if (length(to_rename) > 0) { - delta <- purrr::map2_chr( - to_rename, rename[to_rename], - ~ paste0(ui_value(.x), " -> ", ui_value(.y)) + dat <- data.frame(from = to_rename, to = rename[to_rename]) + delta <- glue_data( + dat, + "{.val <>} {cli::symbol$arrow_right} {.val <>}", + .open = "<<", .close = ">>" ) - ui_done("Renaming labels: {paste0(delta, collapse = '\n')}") + ui_bullets(c( + "v" = "Renaming labels:", + bulletize(delta) + )) # Can't do this at label level, i.e. "old_label_name --> new_label_name" # Fails if "new_label_name" already exists @@ -153,7 +158,7 @@ use_github_labels <- function(repo_spec = deprecated(), ) labels <- union(labels, setdiff(rename, cur_label_names)) } else { - ui_info("No labels need renaming") + ui_bullets(c("i" = "No labels need renaming.")) } cur_labels <- gh("GET /repos/{owner}/{repo}/labels") @@ -161,10 +166,13 @@ use_github_labels <- function(repo_spec = deprecated(), # Add missing labels if (all(labels %in% cur_label_names)) { - ui_info("No new labels needed") + ui_bullets(c("i" = "No new labels needed.")) } else { to_add <- setdiff(labels, cur_label_names) - ui_done("Adding missing labels: {ui_value(to_add)}") + ui_bullets(c( + "v" = "Adding missing labels:", + bulletize(usethis_map_cli(to_add)) + )) for (label in to_add) { gh( @@ -184,10 +192,13 @@ use_github_labels <- function(repo_spec = deprecated(), label_attr("color", cur_labels), cur_label_names ) if (identical(cur_label_colours[names(colours)], colours)) { - ui_info("Label colours are up-to-date") + ui_bullets(c("i" = "Label colours are up-to-date.")) } else { to_update <- intersect(cur_label_names, names(colours)) - ui_done("Updating colours: {ui_value(to_update)}") + ui_bullets(c( + "v" = "Updating colours:", + bulletize(usethis_map_cli(to_update)) + )) for (label in to_update) { gh( @@ -203,10 +214,13 @@ use_github_labels <- function(repo_spec = deprecated(), label_attr("description", cur_labels), cur_label_names ) if (identical(cur_label_descriptions[names(descriptions)], descriptions)) { - ui_info("Label descriptions are up-to-date") + ui_bullets(c("i" = "Label descriptions are up-to-date.")) } else { to_update <- intersect(cur_label_names, names(descriptions)) - ui_done("Updating descriptions: {ui_value(to_update)}") + ui_bullets(c( + "v" = "Updating descriptions:", + bulletize(usethis_map_cli(to_update)) + )) for (label in to_update) { gh( @@ -223,12 +237,17 @@ use_github_labels <- function(repo_spec = deprecated(), to_remove <- setdiff(cur_label_names[default], labels) if (length(to_remove) > 0) { - ui_done("Removing default labels: {ui_value(to_remove)}") + ui_bullets(c( + "v" = "Removing default labels:", + bulletize(usethis_map_cli(to_remove)) + )) for (label in to_remove) { issues <- gh("GET /repos/{owner}/{repo}/issues", labels = label) if (length(issues) > 0) { - ui_todo("Delete {ui_value(label)} label manually; it has associated issues") + ui_bullets(c( + "_" = "Delete {.val {label}} label manually; it has associated issues." + )) } else { gh("DELETE /repos/{owner}/{repo}/labels/{name}", name = label) } From 776b4a0da2e8f0363800d39ecd5c536bc3854ffc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 18:25:46 -0800 Subject: [PATCH 036/111] github-pages.R --- R/github-pages.R | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/R/github-pages.R b/R/github-pages.R index 0b43a8687..e5e68a14c 100644 --- a/R/github-pages.R +++ b/R/github-pages.R @@ -71,7 +71,9 @@ use_github_pages <- function(branch = "gh-pages", path = "/", cname = NA) { site <- safe_gh("GET /repos/{owner}/{repo}/pages")[["result"]] if (is.null(site)) { - ui_done("Activating GitHub Pages for {ui_value(tr$repo_spec)}") + ui_bullets(c( + "v" = "Activating GitHub Pages for {.val {tr$repo_spec}}." + )) site <- gh( "POST /repos/{owner}/{repo}/pages", source = list(branch = branch, path = path), @@ -103,7 +105,7 @@ use_github_pages <- function(branch = "gh-pages", path = "/", cname = NA) { site <- safe_gh("GET /repos/{owner}/{repo}/pages")[["result"]] } - ui_done("GitHub Pages is publishing from:") + ui_bullets(c("v" = "GitHub Pages is publishing from:")) if (!is.null(site$cname)) { kv_line("Custom domain", site$cname) } @@ -129,9 +131,10 @@ create_gh_pages_branch <- function(tr, branch = "gh-pages") { return(FALSE) } - ui_done(" - Initializing empty, orphan {ui_value(branch)} branch in GitHub repo \\ - {ui_value(tr$repo_spec)}") + ui_bullets(c( + "v" = "Initializing empty, orphan branch {.val {branch}} in GitHub repo + {.val {tr$repo_spec}}." + )) # GitHub no longer allows you to directly create an empty tree # hence this roundabout method of getting an orphan branch with no files From bd730c13e5e7a4469c786dd4d1ae1e9377dc7314 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 21 Feb 2024 19:29:34 -0800 Subject: [PATCH 037/111] github.R + check_current_branch() --- R/github.R | 47 +++++++++++++----------- R/pr.R | 13 ++++--- R/utils-git.R | 17 +++++---- vignettes/articles/ui-cli-conversion.Rmd | 3 ++ 4 files changed, 46 insertions(+), 34 deletions(-) diff --git a/R/github.R b/R/github.R index 818366202..ae0b3dbd9 100644 --- a/R/github.R +++ b/R/github.R @@ -80,7 +80,7 @@ use_github <- function(organisation = NULL, is = default_branch, # glue-ing happens inside check_current_branch(), where `gb` gives the # current branch - "Must be on the default branch ({ui_value(is)}), not {ui_value(gb)}." + message = c("x" = "Must be on the default branch {.val {is}}, not {.val {gb}}.") ) challenge_uncommitted_changes(msg = " There are uncommitted changes and we're about to create and push to a new \\ @@ -89,10 +89,11 @@ use_github <- function(organisation = NULL, if (is.null(organisation)) { if (visibility_specified) { - ui_stop(" - The {ui_code('visibility')} setting is only relevant for - organisation-owned repos, within the context of certain \\ - GitHub Enterprise products.") + ui_abort(c( + "x" = "The {.arg visibility} setting is only relevant for + organisation-owned repos, within the context of certain GitHub + Enterprise products." + )) } visibility <- if (private) "private" else "public" } @@ -103,15 +104,15 @@ use_github <- function(organisation = NULL, whoami <- suppressMessages(gh::gh_whoami(.api_url = host)) if (is.null(whoami)) { - ui_stop(" - Unable to discover a GitHub personal access token - A token is required in order to create and push to a new repo - - Call {ui_code('gh_token_help()')} for help configuring a token") + ui_abort(c( + "x" = "Unable to discover a GitHub personal access token.", + "i" = "A token is required in order to create and push to a new repo.", + "_" = "Call {.run gh_token_help()} for help configuring a token." + )) } empirical_host <- parse_github_remotes(glue("{whoami$html_url}/REPO"))$host if (empirical_host != "github.com") { - ui_info("Targeting the GitHub host {ui_value(empirical_host)}") + ui_bullets(c("i" = "Targeting the GitHub host {.val {empirical_host}}.")) } owner <- organisation %||% whoami$login @@ -123,7 +124,9 @@ use_github <- function(organisation = NULL, repo_spec <- glue("{owner}/{repo_name}") visibility_string <- if (visibility == "public") "" else glue("{visibility} ") - ui_done("Creating {visibility_string}GitHub repository {ui_value(repo_spec)}") + ui_bullets(c( + "v" = "Creating {visibility_string}GitHub repository {.val {repo_spec}}." + )) if (is.null(organisation)) { create <- gh::gh( "POST /user/repos", @@ -153,7 +156,7 @@ use_github <- function(organisation = NULL, ) withr::defer(view_url(create$html_url)) - ui_done("Setting remote {ui_value('origin')} to {ui_value(origin_url)}") + ui_bullets(c("v" = "Setting remote {.val origin} to {.val {origin_url}}.")) use_git_remote("origin", origin_url) if (is_package()) { @@ -171,8 +174,7 @@ use_github <- function(organisation = NULL, repo <- git_repo() gbl <- gert::git_branch_list(local = TRUE, repo = repo) if (nrow(gbl) > 1) { - ui_done(" - Setting {ui_value(default_branch)} as default branch on GitHub") + ui_bullets(c("v" = "Setting {.val {default_branch}} as default branch on GitHub.")) gh::gh( "PATCH /repos/{owner}/{repo}", owner = owner, repo = repo_name, @@ -258,11 +260,12 @@ has_github_links <- function() { check_no_origin <- function() { remotes <- git_remotes() if ("origin" %in% names(remotes)) { - ui_stop(" - This repo already has an {ui_value('origin')} remote, \\ - with value {ui_value(remotes[['origin']])}. - You can remove this setting with: - {ui_code('usethis::use_git_remote(\"origin\", url = NULL, overwrite = TRUE)')}") + ui_abort(c( + "x" = "This repo already has an {.val origin} remote, with value + {.val {remotes[['origin']]}}.", + "i" = "You can remove this setting with:", + " " = '{.code usethis::use_git_remote("origin", url = NULL, overwrite = TRUE)}' + )) } invisible() } @@ -284,5 +287,7 @@ check_no_github_repo <- function(owner, repo, host) { } spec <- glue("{owner}/{repo}") empirical_host <- parse_github_remotes(repo_info$html_url)$host - ui_stop("Repo {ui_value(spec)} already exists on {ui_value(empirical_host)}") + ui_abort(c( + "x" = "Repo {.val {spec}} already exists on {.val {empirical_host}}." + )) } diff --git a/R/pr.R b/R/pr.R index 26de04689..9341f1945 100644 --- a/R/pr.R +++ b/R/pr.R @@ -958,11 +958,12 @@ check_pr_branch <- function(default_branch = git_default_branch()) { # current git branch check_current_branch( is_not = default_branch, - message = " - The {ui_code('pr_*()')} functions facilitate pull requests. - The current branch ({ui_value(gb)}) is this repo's default \\ - branch, but pull requests should NOT come from the default branch. - Do you need to call {ui_code('pr_init()')} (new PR)? - Or {ui_code('pr_resume()')} or {ui_code('pr_fetch()')} (existing PR)?" + message = c( + "i" = "The {.code pr_*()} functions facilitate pull requests.", + "i" = "The current branch ({.val {gb}}) is this repo's default branch, but + pull requests should NOT come from the default branch.", + "i" = "Do you need to call {.fun pr_init} (new PR)? Or {.fun pr_resume} or + {.fun pr_fetch} (existing PR)?" + ) ) } diff --git a/R/utils-git.R b/R/utils-git.R index 234c64444..adeedcbb0 100644 --- a/R/utils-git.R +++ b/R/utils-git.R @@ -371,7 +371,8 @@ git_push_first <- function(branch = git_branch(), remote = "origin", verbose = T # Checks ------------------------------------------------------------------ -check_current_branch <- function(is = NULL, is_not = NULL, +check_current_branch <- function(is = NULL, + is_not = NULL, message = NULL) { gb <- git_branch() @@ -380,9 +381,10 @@ check_current_branch <- function(is = NULL, is_not = NULL, if (gb == is) { return(invisible()) } else { - msg <- message %||% - "Must be on branch {ui_value(is)}, not {ui_value(gb)}." - ui_stop(msg) + if (is.null(message)) { + message <- c("x" = "Must be on branch {.val {is}}, not {.val {gb}}.") + } + ui_abort(message) } } @@ -391,9 +393,10 @@ check_current_branch <- function(is = NULL, is_not = NULL, if (gb != is_not) { return(invisible()) } else { - msg <- message %||% - "Can't be on branch {ui_value(gb)}." - ui_stop(msg) + if (is.null(message)) { + message <- c("x" = "Can't be on branch {.val {gb}}.") + } + ui_abort(message) } } diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index d2207a554..642dbc94a 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -220,6 +220,9 @@ ui_warn <- function(x, .envir = parent.frame()) { It has very little usage and, instead of converting it, I'm going to eliminate its use altogether in favor of `ui_bullets(c("!" = "Thing I'm warning about.")) `. +Now that I'm looking at a lot of the new errors with `ui_abort()` I realize that usethis also needs to be passing the `call` argument along. +I'm not going to tackle this in the main conversion. + ## Sitrep and format helpers This is a small clump of functions that support sitrep-type output. From 2e97e4f38b0c1c7f69f4046a842e49c047076687 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 08:31:20 -0800 Subject: [PATCH 038/111] github_token.R --- R/github_token.R | 129 +++++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/R/github_token.R b/R/github_token.R index bc9281fc8..b51046b0f 100644 --- a/R/github_token.R +++ b/R/github_token.R @@ -67,11 +67,12 @@ create_github_token <- function(scopes = c("repo", "user", "gist", "workflow"), withr::defer(view_url(url)) hint <- code_hint_with_host("gitcreds::gitcreds_set", host) - ui_todo(" - Call {ui_code(hint)} to register this token in the \\ - local Git credential store - It is also a great idea to store this token in any password-management \\ - software that you use") + ui_bullets(c( + "_" = "Call {.code {hint}} to register this token in the local Git + credential store.", + "i" = "It is also a great idea to store this token in any + password-management software that you use." + )) invisible() } @@ -118,8 +119,10 @@ pat_sitrep <- function(host = "https://github.com", maybe_pat <- purrr::safely(gh::gh_token)(api_url = host) if (is.null(maybe_pat$result)) { - ui_oops("The PAT discovered for {ui_path(host)} has the wrong structure.") - ui_inform(maybe_pat$error) + ui_bullets(c( + "x" = "The PAT discovered for {.url {host}} has the wrong structure." + )) + ui_bullets(c("i" = maybe_pat$error)) return(invisible(FALSE)) } pat <- maybe_pat$result @@ -128,36 +131,44 @@ pat_sitrep <- function(host = "https://github.com", if (!have_pat) { kv_line("Personal access token for {ui_value(host)}", NULL) hint <- code_hint_with_host("create_github_token", host, "host") - ui_todo("To create a personal access token, call {ui_code(hint)}") + ui_bullets(c( + "_" = "To create a personal access token, call {.code {hint}}." + )) hint <- code_hint_with_host("gitcreds::gitcreds_set", host) - ui_todo("To store a token for current and future use, call {ui_code(hint)}") - ui_info(" - Read more in the {ui_value('Managing Git(Hub) Credentials')} article: - https://usethis.r-lib.org/articles/articles/git-credentials.html") + url <- "https://usethis.r-lib.org/articles/articles/git-credentials.html" + ui_bullets(c( + "_" = "To store a token for current and future use, call {.code {hint}}.", + "i" = "Read more in the {.href [Managing Git(Hub) Credentials]({url})} article." + )) return(invisible(FALSE)) } kv_line("Personal access token for {ui_value(host)}", "") online <- is_online(host) if (!online) { - ui_oops(" - Host is not reachable. - No further vetting of the personal access token is possible. - Try again when {ui_value(host)} can be reached.") + ui_bullets(c( + "x" = "Host is not reachable.", + " " = "No further vetting of the personal access token is possible.", + "_" = "Try again when {.val {host}} can be reached." + )) return(invisible()) } maybe_who <- purrr::safely(gh::gh_whoami)(.token = pat, .api_url = host) if (is.null(maybe_who$result)) { - message <- "Can't get user information for this token." + message <- c("x" = "Can't get user information for this token.") if (inherits(maybe_who$error, "http_error_401")) { - message <- " - Can't get user information for this token. - The token may no longer be valid or perhaps it lacks the \\ - {ui_value('user')} scope." + message <- c( + message, + "i" = "The token may no longer be valid or perhaps it lacks the + {.val user} scope." + ) } - ui_oops(message) - ui_inform(maybe_who$error) + message <- c( + message, + "i" = maybe_who$error + ) + ui_bullets(message) return(invisible(FALSE)) } who <- maybe_who$result @@ -171,10 +182,11 @@ pat_sitrep <- function(host = "https://github.com", maybe_emails <- purrr::safely(gh::gh)("/user/emails", .token = pat, .api_url = host) if (is.null(maybe_emails$result)) { - ui_oops(" - Can't retrieve registered email addresses from GitHub. - Consider re-creating your PAT with the {ui_value('user')} \\ - or at least {ui_value('user:email')} scope.") + ui_bullets(c( + "x" = "Can't retrieve registered email addresses from GitHub.", + "i" = "Consider re-creating your PAT with the {.val user} (or at least + {.val user:email}) scope." + )) } else { emails <- maybe_emails$result addresses <- map_chr( @@ -187,9 +199,10 @@ pat_sitrep <- function(host = "https://github.com", ) git_user_check(user) if (!is.null(user$email) && !any(grepl(user$email, addresses))) { - ui_oops(" - Git user's email ({ui_value(user$email)}) doesn't appear to \\ - be registered with GitHub host.") + ui_bullets(c( + "x" = "Git user's email (.val {user$email}}) doesn't appear to be + registered with GitHub host." + )) } } @@ -211,29 +224,30 @@ scold_for_renviron <- function() { fishy_keys <- re_match(fishy_lines, "^(?.+)=.+")$key # TODO: when I switch to cli, this is a good place for `!` # in general, lots below is suboptimal, but good enough for now - ui_info(c( - "{ui_path(renviron_path)} defines environment variable(s):", - paste0("- ", fishy_keys), - "This can prevent your PAT from being retrieved from the Git credential store." + ui_bullets(c( + "!" = "{.path {pth(renviron_path)}} defines{cli::qty(length(fishy_keys))} + the environment variable{?s}:", + bulletize(fishy_keys), + "!" = "This can prevent your PAT from being retrieved from the Git + credential store.", + "i" = "If you are troubleshooting PAT problems, the root cause may be an + old, invalid PAT defined in {.path {pth(renviron_path)}}.", + "i" = "For most use cases, it is better to NOT define the PAT in + {.file .Renviron}.", + "_" = "Call {.fun edit_r_environ} to edit that file.", + "_" = "Then call {.run gitcreds::gitcreds_set()} to put the PAT into + the Git credential store." )) - ui_info(" - If you are troubleshooting PAT problems, the root cause may be an old, \\ - invalid PAT defined in {ui_path(renviron_path)}.") - ui_todo("Call {ui_code('edit_r_environ()')} to edit that file.") - ui_info(" - For most use cases, it is better to NOT define the PAT in \\ - {ui_code('.Renviron')}. - Instead, call {ui_code('gitcreds::gitcreds_set()')} to put the PAT into \\ - the Git credential store.") - invisible() } scold_for_scopes <- function(scopes) { if (length(scopes) == 0) { - ui_oops(" - Token has no scopes! - {ui_code('create_github_token()')} defaults to the recommended scopes.") + ui_bullets(c( + "x" = "Token has no scopes!", + "i" = "Tokens initiated with {.fun create_github_token} default to the + recommended scopes." + )) return(invisible()) } @@ -252,21 +266,16 @@ scold_for_scopes <- function(scopes) { # current design of the ui_*() functions makes this pretty hard :( suggestions <- c( - if (!has_repo) { - "- {ui_value('repo')}: needed to fully access user's repos" - }, - if (!has_workflow) { - "- {ui_value('workflow')}: needed to manage GitHub Actions workflow files" - }, - if (!has_user_email) { - "- {ui_value('user:email')}: needed to read user's email addresses" - } + "*" = if (!has_repo) "{.val repo}: needed to fully access user's repos", + "*" = if (!has_workflow) "{.val workflow}: needed to manage GitHub Actions workflow files", + "*" = if (!has_user_email) "{.val user:email}: needed to read user's email addresses" ) message <- c( - "Token lacks recommended scopes:", + "!" = "Token lacks recommended scopes:", suggestions, - "Consider re-creating your PAT with the missing scopes.", - "{ui_code('create_github_token()')} defaults to the recommended scopes." + "i" = "Consider re-creating your PAT with the missing scopes.", + "i" = "Tokens initiated with {.fun create_github_token} default to the + recommended scopes." ) - ui_oops(glue_collapse(message, sep = "\n")) + ui_bullets(message) } From ab2120d8586d27217c63e3c086b36a72d05eb02c Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 10:39:45 -0800 Subject: [PATCH 039/111] Remove hd_line() which is not used anywhere at this point --- R/ui.R | 4 ---- vignettes/articles/ui-cli-conversion.Rmd | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/R/ui.R b/R/ui.R index 13afab6f4..12ec58078 100644 --- a/R/ui.R +++ b/R/ui.R @@ -301,10 +301,6 @@ is_quiet <- function() { # Sitrep helpers --------------------------------------------------------------- -hd_line <- function(name) { - ui_inform(crayon::bold(name)) -} - kv_line <- function(key, value, .envir = parent.frame()) { value <- if (is.null(value)) ui_unset() else ui_value(value) key <- glue(key, .envir = .envir) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 642dbc94a..30f3c5d8b 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -225,11 +225,11 @@ I'm not going to tackle this in the main conversion. ## Sitrep and format helpers -This is a small clump of functions that support sitrep-type output. +This is a small clump of functions that support sitrep-type output. -- `hd_line()` -- `kv_line()` -- `ui_unset()` +- `hd_line()` *unexported and, apparently, unused! now removed* +- `kv_line()` *unexported* +- `ui_unset()` *exported* ## Questions From 0ece8f4d641a0063cb8bb5f4b811e3b9debc0ddd Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 17:34:06 -0800 Subject: [PATCH 040/111] Notes --- vignettes/articles/ui-cli-conversion.Rmd | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 30f3c5d8b..1f1245e5c 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -247,3 +247,21 @@ if (ui_nope(" ``` If I create new versions of `ui_yeah()` and `ui_nope()`, then I could still switch over to cli's inline styling. Otherwise, I won't be able to remove all internal usage of legacy `ui_*()` functions. + +## Miscellany + +* I've been adding a period to the end of messages, as a general rule. +* In terms of whitespace and indentation, I've settled on some conventions: +```{r} +#| eval: false +ui_bullets(c( + "i" = "Downloading into {.path {pth(destdir)}}.", + "_" = "Prefer a different location? Cancel, try again, and specify + {.arg destdir}." +)) +... +ui_bullets(c("x" = "Things are very wrong.")) +``` + Key points: + - Put `ui_bullets(c(` on its own line, then all of the bullet items, followed by `))` on its own line. Sometimes I make an exception for a bullet list with exactly one, short bullet item. + - Use hard line breaks inside bullet text to comply with surrounding line length. In subsequent lines, use indentation to get you just past the opening `"`. This extraneous white space is later rationalized by cli, which handles wrapping. From e6d365cafa6698785db9e625e4237df2b7252fba Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 17:36:57 -0800 Subject: [PATCH 041/111] Modernize kv_line() + other changes to improve git_sitrep() --- R/git.R | 30 +++--- R/github.R | 2 +- R/github_token.R | 9 +- R/ui.R | 8 -- R/utils-github.R | 14 +-- R/utils-ui.R | 24 +++++ man/git_vaccinate.Rd | 11 ++- tests/testthat/_snaps/utils-github.md | 2 +- tests/testthat/_snaps/utils-ui.md | 130 +++++++++++++++++++++++++- tests/testthat/test-utils-ui.R | 49 +++++++++- 10 files changed, 239 insertions(+), 40 deletions(-) diff --git a/R/git.R b/R/git.R index 06b130cae..5d8836526 100644 --- a/R/git.R +++ b/R/git.R @@ -345,7 +345,7 @@ git_sitrep <- function(tool = c("git", "github"), vaccinated <- git_vaccinated() kv_line("Vaccinated", vaccinated) if (!vaccinated) { - ui_bullets(c("i" = "See {.fun git_vaccinate} to learn more.")) + ui_bullets(c("i" = "See {.fun usethis::git_vaccinate} to learn more.")) } kv_line("Default Git protocol", git_protocol()) kv_line("Default initial branch name", init_default_branch) @@ -378,9 +378,16 @@ git_sitrep <- function(tool = c("git", "github"), # current branch ------------------------------------------------------------- branch <- tryCatch(git_branch(), error = function(e) NULL) tracking_branch <- if (is.null(branch)) NA_character_ else git_branch_tracking() - # TODO: can't really express with kv_line() helper - branch <- if (is.null(branch)) "" else branch - tracking_branch <- if (is.na(tracking_branch)) "" else tracking_branch + if (is.null(branch)) { + branch <- cli::format_inline(ui_special()) + } else { + branch <- cli::format_inline("{.val {branch}}") + } + if (is.na(tracking_branch)) { + tracking_branch <- cli::format_inline(ui_special()) + } else { + tracking_branch <- cli::format_inline("{.val {tracking_branch}}") + } # local git config ----------------------------------------------------------- if ("git" %in% tool) { @@ -392,9 +399,9 @@ git_sitrep <- function(tool = c("git", "github"), # vertical alignment would make this nicer, but probably not worth it ui_bullets(c( - "*" = "Current local branch {cli::symbol$arrow_right} remote tracking + "*" = "Current local branch {cli::symbol$arrow_right} remote tracking branch:", - " " = "{.val {branch}} {cli::symbol$arrow_right} {.val {tracking_branch}}" + " " = "{branch} {cli::symbol$arrow_right} {tracking_branch}" )) } @@ -505,11 +512,12 @@ default_branch_sitrep <- function() { #' Vaccinate your global gitignore file #' -#' Adds `.Rproj.user`, `.Rhistory`, `.Rdata`, `.httr-oauth`, `.DS_Store`, and `.quarto` to -#' your global (a.k.a. user-level) `.gitignore`. This is good practice as it -#' decreases the chance that you will accidentally leak credentials to GitHub. -#' `git_vaccinate()` also tries to detect and fix the situation where you have a -#' global gitignore file, but it's missing from your global Git config. +#' Adds `.Rproj.user`, `.Rhistory`, `.Rdata`, `.httr-oauth`, `.DS_Store`, and +#' `.quarto` to your global (a.k.a. user-level) `.gitignore`. This is good +#' practice as it decreases the chance that you will accidentally leak +#' credentials to GitHub. `git_vaccinate()` also tries to detect and fix the +#' situation where you have a global gitignore file, but it's missing from your +#' global Git config. #' #' @export git_vaccinate <- function() { diff --git a/R/github.R b/R/github.R index ae0b3dbd9..369d62408 100644 --- a/R/github.R +++ b/R/github.R @@ -107,7 +107,7 @@ use_github <- function(organisation = NULL, ui_abort(c( "x" = "Unable to discover a GitHub personal access token.", "i" = "A token is required in order to create and push to a new repo.", - "_" = "Call {.run gh_token_help()} for help configuring a token." + "_" = "Call {.run usethis::gh_token_help()} for help configuring a token." )) } empirical_host <- parse_github_remotes(glue("{whoami$html_url}/REPO"))$host diff --git a/R/github_token.R b/R/github_token.R index b51046b0f..52b9811f4 100644 --- a/R/github_token.R +++ b/R/github_token.R @@ -129,7 +129,7 @@ pat_sitrep <- function(host = "https://github.com", have_pat <- pat != "" if (!have_pat) { - kv_line("Personal access token for {ui_value(host)}", NULL) + kv_line("Personal access token for {.val {host}}", NULL) hint <- code_hint_with_host("create_github_token", host, "host") ui_bullets(c( "_" = "To create a personal access token, call {.code {hint}}." @@ -142,7 +142,7 @@ pat_sitrep <- function(host = "https://github.com", )) return(invisible(FALSE)) } - kv_line("Personal access token for {ui_value(host)}", "") + kv_line("Personal access token for {.val {host}}", ui_special("discovered")) online <- is_online(host) if (!online) { @@ -174,9 +174,8 @@ pat_sitrep <- function(host = "https://github.com", who <- maybe_who$result kv_line("GitHub user", who$login) - scopes <- who$scopes - kv_line("Token scopes", who$scopes) - scopes <- strsplit(scopes, ", ")[[1]] + scopes <- strsplit(who$scopes, ", ")[[1]] + kv_line("Token scopes", scopes) scold_for_scopes(scopes) maybe_emails <- diff --git a/R/ui.R b/R/ui.R index 12ec58078..47807a1bf 100644 --- a/R/ui.R +++ b/R/ui.R @@ -298,11 +298,3 @@ ui_inform <- function(...) { is_quiet <- function() { isTRUE(getOption("usethis.quiet", default = FALSE)) } - -# Sitrep helpers --------------------------------------------------------------- - -kv_line <- function(key, value, .envir = parent.frame()) { - value <- if (is.null(value)) ui_unset() else ui_value(value) - key <- glue(key, .envir = .envir) - ui_inform(glue("{cli::symbol$bullet} {key}: {value}")) -} diff --git a/R/utils-github.R b/R/utils-github.R index ed53766af..7a9e213ea 100644 --- a/R/utils-github.R +++ b/R/utils-github.R @@ -673,15 +673,17 @@ all_configs <- function() { } read_more <- function() { - glue(" - Read more about the GitHub remote configurations that usethis supports at: - {ui_value('https://happygitwithr.com/common-remote-setups.html')}") + cli::format_inline( + "Read more about the GitHub remote configurations that usethis supports at: +{.url https://happygitwithr.com/common-remote-setups.html}." + ) } read_more_maybe <- function() { - glue(" - Read more about what this GitHub remote configurations means at: - {ui_value('https://happygitwithr.com/common-remote-setups.html')}") + cli::format_inline( + "Read more about what this GitHub remote configurations means at: +{.url https://happygitwithr.com/common-remote-setups.html}." + ) } cfg_no_github <- function(cfg) { diff --git a/R/utils-ui.R b/R/utils-ui.R index f5a212a87..61e81c2e7 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -10,6 +10,11 @@ usethis_theme <- function() { "text-exdent" = 2, before = function(x) paste0(cli::col_yellow(cli::symbol$info), " ") ), + # we have enough color going on already, let's have black bullets + ".bullets .bullet-*" = list( + "text-exdent" = 2, + before = function(x) paste0(cli::symbol$bullet, " ") + ), span.field = list(transform = single_quote_if_no_color) ) } @@ -149,3 +154,22 @@ compute_n_show <- function(n, n_show_nominal = 5, n_fudge = 2) { n } } + +kv_line <- function(key, value, .envir = parent.frame()) { + key <- cli::format_inline(key, .envir = .envir) + + value <- value %||% ui_special() + + if (inherits(value, "AsIs")) { + value <- cli::format_inline(value, .envir = .envir) + } else { + value <- cli::format_inline("{.val {value}}") + } + + ui_bullets(c("*" = "{key}: {value}")) +} + +ui_special <- function(x = "unset") { + I(glue("{cli::col_grey('<[x]>')}", .open = "[", .close = "]")) +} + diff --git a/man/git_vaccinate.Rd b/man/git_vaccinate.Rd index 79f938a36..4d7a894b5 100644 --- a/man/git_vaccinate.Rd +++ b/man/git_vaccinate.Rd @@ -7,9 +7,10 @@ git_vaccinate() } \description{ -Adds \code{.Rproj.user}, \code{.Rhistory}, \code{.Rdata}, \code{.httr-oauth}, \code{.DS_Store}, and \code{.quarto} to -your global (a.k.a. user-level) \code{.gitignore}. This is good practice as it -decreases the chance that you will accidentally leak credentials to GitHub. -\code{git_vaccinate()} also tries to detect and fix the situation where you have a -global gitignore file, but it's missing from your global Git config. +Adds \code{.Rproj.user}, \code{.Rhistory}, \code{.Rdata}, \code{.httr-oauth}, \code{.DS_Store}, and +\code{.quarto} to your global (a.k.a. user-level) \code{.gitignore}. This is good +practice as it decreases the chance that you will accidentally leak +credentials to GitHub. \code{git_vaccinate()} also tries to detect and fix the +situation where you have a global gitignore file, but it's missing from your +global Git config. } diff --git a/tests/testthat/_snaps/utils-github.md b/tests/testthat/_snaps/utils-github.md index 64a22bc7c..5132700e1 100644 --- a/tests/testthat/_snaps/utils-github.md +++ b/tests/testthat/_snaps/utils-github.md @@ -22,5 +22,5 @@ * The 'origin' GitHub remote is a fork, but its parent is not configured as the 'upstream' remote. Read more about the GitHub remote configurations that usethis supports at: - 'https://happygitwithr.com/common-remote-setups.html' + . diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index bffe059bb..ff3620359 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -26,7 +26,7 @@ i info noindent indent - * bullet + * bullet > arrow ! warning @@ -58,7 +58,7 @@ ℹ info noindent indent - • bullet + • bullet → arrow ! warning @@ -292,3 +292,129 @@ * g * h +# ui_special() works [plain] + + Code + cli::cli_text(ui_special()) + Message + + +--- + + Code + cli::cli_text(ui_special("whatever")) + Message + + +# ui_special() works [ansi] + + Code + cli::cli_text(ui_special()) + Message +  + +--- + + Code + cli::cli_text(ui_special("whatever")) + Message +  + +# kv_line() looks as expected in basic use [plain] + + Code + kv_line("CHARACTER", "VALUE") + Message + * CHARACTER: "VALUE" + Code + kv_line("NUMBER", 1) + Message + * NUMBER: 1 + Code + kv_line("LOGICAL", TRUE) + Message + * LOGICAL: TRUE + +# kv_line() looks as expected in basic use [fancy] + + Code + kv_line("CHARACTER", "VALUE") + Message + • CHARACTER: "VALUE" + Code + kv_line("NUMBER", 1) + Message + • NUMBER: 1 + Code + kv_line("LOGICAL", TRUE) + Message + • LOGICAL: TRUE + +# kv_line() can interpolate and style inline in key [plain] + + Code + kv_line("Personal access token for {.val {value}}", "some_secret") + Message + * Personal access token for "SOME_HOST": "some_secret" + +# kv_line() can interpolate and style inline in key [fancy] + + Code + kv_line("Personal access token for {.val {value}}", "some_secret") + Message + • Personal access token for "SOME_HOST": "some_secret" + +# kv_line() can treat value in different ways [plain] + + Code + kv_line("Key", value) + Message + * Key: "some value" + Code + kv_line("Something we don't have", NULL) + Message + * Something we don't have: + Code + kv_line("Key", ui_special("discovered")) + Message + * Key: + Code + kv_line("Key", "something {.emph important}") + Message + * Key: "something {.emph important}" + Code + kv_line("Key", I("something {.emph important}")) + Message + * Key: something important + Code + kv_line("Key", I("something {.emph {adjective}}")) + Message + * Key: something great + +# kv_line() can treat value in different ways [fancy] + + Code + kv_line("Key", value) + Message + • Key: "some value" + Code + kv_line("Something we don't have", NULL) + Message + • Something we don't have:  + Code + kv_line("Key", ui_special("discovered")) + Message + • Key:  + Code + kv_line("Key", "something {.emph important}") + Message + • Key: "something {.emph important}" + Code + kv_line("Key", I("something {.emph important}")) + Message + • Key: something important + Code + kv_line("Key", I("something {.emph {adjective}}")) + Message + • Key: something great + diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 1483feaa0..ad2f635fd 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -78,7 +78,6 @@ test_that("ui_abort() works", { # usethis.quiet should have no effect on this withr::local_options(list(usethis.quiet = TRUE)) expect_usethis_error(ui_abort("whisk"), "whisk") - }) cli::test_that_cli("ui_code_snippet() with scalar input", { @@ -160,3 +159,51 @@ test_that("usethis_map_cli() works", { c("{.file aaa}", "{.file bbb}", "{.file ccc}") ) }) + +cli::test_that_cli("ui_special() works", { + expect_snapshot(cli::cli_text(ui_special())) + expect_snapshot(cli::cli_text(ui_special("whatever"))) +}, configs = c("plain", "ansi")) + +cli::test_that_cli("kv_line() looks as expected in basic use", { + withr::local_options(list(usethis.quiet = FALSE)) + + expect_snapshot({ + kv_line("CHARACTER", "VALUE") + kv_line("NUMBER", 1) + kv_line("LOGICAL", TRUE) + }) +}, configs = c("plain", "fancy")) + +cli::test_that_cli("kv_line() can interpolate and style inline in key", { + withr::local_options(list(usethis.quiet = FALSE)) + + value <- "SOME_HOST" + expect_snapshot( + kv_line("Personal access token for {.val {value}}", "some_secret") + ) +}, configs = c("plain", "fancy")) + +cli::test_that_cli("kv_line() can treat value in different ways", { + withr::local_options(list(usethis.quiet = FALSE)) + + value <- "some value" + adjective <- "great" + + expect_snapshot({ + # evaluation in .envir + kv_line("Key", value) + + # NULL is special + kv_line("Something we don't have", NULL) + # explicit special + kv_line("Key", ui_special("discovered")) + + # value taken at face value + kv_line("Key", "something {.emph important}") + + # I() indicates value has markup + kv_line("Key", I("something {.emph important}")) + kv_line("Key", I("something {.emph {adjective}}")) + }) +}, configs = c("plain", "fancy")) From e1b2d97bcd3723dc454106971cc5bf12acb029a6 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 19:00:35 -0800 Subject: [PATCH 042/111] github_token.R --- R/github_token.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/github_token.R b/R/github_token.R index 52b9811f4..f6c68bc0d 100644 --- a/R/github_token.R +++ b/R/github_token.R @@ -263,7 +263,6 @@ scold_for_scopes <- function(scopes) { return(invisible()) } - # current design of the ui_*() functions makes this pretty hard :( suggestions <- c( "*" = if (!has_repo) "{.val repo}: needed to fully access user's repos", "*" = if (!has_workflow) "{.val workflow}: needed to manage GitHub Actions workflow files", @@ -273,7 +272,7 @@ scold_for_scopes <- function(scopes) { "!" = "Token lacks recommended scopes:", suggestions, "i" = "Consider re-creating your PAT with the missing scopes.", - "i" = "Tokens initiated with {.fun create_github_token} default to the + "i" = "Tokens initiated with {.fun usethis::create_github_token} default to the recommended scopes." ) ui_bullets(message) From 5e44d179effb5ddc7a995081d45c2b29e188e295 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 19:19:35 -0800 Subject: [PATCH 043/111] helpers.R --- R/helpers.R | 30 ++++++++++------ tests/testthat/_snaps/data-table.md | 2 +- tests/testthat/_snaps/helpers.md | 53 +++++++++++++++++++++++++++-- tests/testthat/_snaps/lifecycle.md | 2 +- tests/testthat/_snaps/package.md | 18 +++++----- tests/testthat/_snaps/tibble.md | 2 +- tests/testthat/_snaps/tidyverse.md | 10 +++--- tests/testthat/test-helpers.R | 45 +++++++++++------------- 8 files changed, 106 insertions(+), 56 deletions(-) diff --git a/R/helpers.R b/R/helpers.R index c539133c9..c1190353e 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -7,9 +7,13 @@ use_dependency <- function(package, type, min_version = NULL) { } if (package == "R" && tolower(type) != "depends") { - ui_stop("Set {ui_code('type = \"Depends\"')} when specifying an R version") + ui_abort(c( + "x" = 'Set {.code type = "Depends"} when specifying an R version.' + )) } else if (package == "R" && is.null(min_version)) { - ui_stop("Specify {ui_code('min_version')} when {ui_code('package = \"R\"')}") + ui_abort(c( + "x" = 'Specify {.arg min_version} when {.code package = "R"}.' + )) } if (isTRUE(min_version) && package == "R") { @@ -38,7 +42,9 @@ use_dependency <- function(package, type, min_version = NULL) { # * First use of a LinkingTo package as a non-LinkingTo dependency # In all cases, we can can simply make the change. if (nrow(deps) == 0 || new_linking_to || new_non_linking_to) { - ui_done("Adding {ui_value(package)} to {ui_field(type)} field in DESCRIPTION") + ui_bullets(c( + "v" = "Adding {.pkg {package}} to {.field {type}} field in DESCRIPTION." + )) desc$set_dep(package, type, version = version) desc$write() changed <- TRUE @@ -65,18 +71,20 @@ use_dependency <- function(package, type, min_version = NULL) { upgrade <- existing_version == "*" || numeric_version(min_version) > version_spec(existing_version) if (upgrade) { - ui_done( - "Increasing {ui_value(package)} version to {ui_value(version)} in \\ - DESCRIPTION") + ui_bullets(c( + "v" = "Increasing {.pkg {package}} version to {.val {version}} in + DESCRIPTION." + )) desc$set_dep(package, type, version = version) desc$write() changed <- TRUE } } else if (delta > 0) { # moving from, e.g., Suggests to Imports - ui_done( - "Moving {ui_value(package)} from {ui_field(existing_type)} to \\ - {ui_field(type)} field in DESCRIPTION") + ui_bullets(c( + "v" = "Moving {.pkg {package}} from {.field {existing_type}} to + {.field {type}} field in DESCRIPTION." + )) desc$del_dep(package, existing_type) desc$set_dep(package, type, version = version) desc$write() @@ -99,10 +107,10 @@ version_spec <- function(x) { view_url <- function(..., open = is_interactive()) { url <- paste(..., sep = "/") if (open) { - ui_done("Opening URL {ui_value(url)}") + ui_bullets(c("v" = "Opening URL {.url {url}}.")) utils::browseURL(url) } else { - ui_todo("Open URL {ui_value(url)}") + ui_bullets(c("_" = "Open URL {.url {url}}.")) } invisible(url) } diff --git a/tests/testthat/_snaps/data-table.md b/tests/testthat/_snaps/data-table.md index 8e1f9ab3b..517edd4ac 100644 --- a/tests/testthat/_snaps/data-table.md +++ b/tests/testthat/_snaps/data-table.md @@ -20,7 +20,7 @@ Message ! data.table should be in 'Imports' or 'Suggests', not 'Depends'! v Removing data.table from 'Depends'. - v Adding 'data.table' to Imports field in DESCRIPTION + v Adding data.table to 'Imports' field in DESCRIPTION. v Adding '@importFrom data.table data.table', '@importFrom data.table :=', '@importFrom data.table .SD', '@importFrom data.table .BY', '@importFrom data.table .N', '@importFrom data.table .I', '@importFrom data.table .GRP', diff --git a/tests/testthat/_snaps/helpers.md b/tests/testthat/_snaps/helpers.md index 62d6a65b5..570af239e 100644 --- a/tests/testthat/_snaps/helpers.md +++ b/tests/testthat/_snaps/helpers.md @@ -1,5 +1,54 @@ +# we message for new type and are silent for same type + + Code + use_dependency("crayon", "Imports") + Message + v Adding crayon to 'Imports' field in DESCRIPTION. + +# we message for version change and are silent for same version + + Code + use_dependency("crayon", "Imports") + Message + v Adding crayon to 'Imports' field in DESCRIPTION. + +--- + + Code + use_dependency("crayon", "Imports", min_version = "1.0.0") + Message + v Increasing crayon version to ">= 1.0.0" in DESCRIPTION. + +--- + + Code + use_dependency("crayon", "Imports", min_version = "2.0.0") + Message + v Increasing crayon version to ">= 2.0.0" in DESCRIPTION. + +# use_dependency() upgrades a dependency + + Code + use_dependency("usethis", "Suggests") + Message + v Adding usethis to 'Suggests' field in DESCRIPTION. + +--- + + Code + use_dependency("usethis", "Imports") + Message + v Moving usethis from 'Suggests' to 'Imports' field in DESCRIPTION. + # use_dependency() declines to downgrade a dependency + Code + use_dependency("usethis", "Imports") + Message + v Adding usethis to 'Imports' field in DESCRIPTION. + +--- + Code use_dependency("usethis", "Suggests") Message @@ -11,7 +60,7 @@ Code use_dependency("rlang", "LinkingTo") Message - v Adding 'rlang' to LinkingTo field in DESCRIPTION + v Adding rlang to 'LinkingTo' field in DESCRIPTION. # use_dependency() does not fall over on 2nd LinkingTo request @@ -23,6 +72,6 @@ Code use_package("rlang") Message - v Moving 'rlang' from Suggests to Imports field in DESCRIPTION + v Moving rlang from 'Suggests' to 'Imports' field in DESCRIPTION. * Refer to functions with `rlang::fun()` diff --git a/tests/testthat/_snaps/lifecycle.md b/tests/testthat/_snaps/lifecycle.md index f3fe64d87..710674bd1 100644 --- a/tests/testthat/_snaps/lifecycle.md +++ b/tests/testthat/_snaps/lifecycle.md @@ -3,7 +3,7 @@ Code use_lifecycle() Message - v Adding 'lifecycle' to Imports field in DESCRIPTION + v Adding lifecycle to 'Imports' field in DESCRIPTION. * Refer to functions with `lifecycle::fun()` v Adding '@importFrom lifecycle deprecated' to 'R/{TESTPKG}-package.R'. diff --git a/tests/testthat/_snaps/package.md b/tests/testthat/_snaps/package.md index f59128b88..c2175eccb 100644 --- a/tests/testthat/_snaps/package.md +++ b/tests/testthat/_snaps/package.md @@ -3,7 +3,7 @@ Code use_package("withr") Message - v Adding 'withr' to Imports field in DESCRIPTION + v Adding withr to 'Imports' field in DESCRIPTION. * Refer to functions with `withr::fun()` Code use_package("withr") @@ -16,37 +16,37 @@ Code use_package("R") Condition - Error: - ! Set `type = "Depends"` when specifying an R version + Error in `use_dependency()`: + x Set `type = "Depends"` when specifying an R version. --- Code use_package("R", type = "Depends") Condition - Error: - ! Specify `min_version` when `package = "R"` + Error in `use_dependency()`: + x Specify `min_version` when `package = "R"`. --- Code use_package("R", type = "Depends", min_version = "3.6") Message - v Adding 'R' to Depends field in DESCRIPTION + v Adding R to 'Depends' field in DESCRIPTION. --- Code use_package("R", type = "Depends", min_version = TRUE) Message - v Increasing 'R' version to '>= 4.1' in DESCRIPTION + v Increasing R version to ">= 4.1" in DESCRIPTION. # use_package(type = 'Suggests') guidance w/o and w/ rlang Code use_package("withr", "Suggests") Message - v Adding 'withr' to Suggests field in DESCRIPTION + v Adding withr to 'Suggests' field in DESCRIPTION. * Use `requireNamespace("withr", quietly = TRUE)` to test if package is installed * Then directly refer to functions with `withr::fun()` @@ -55,7 +55,7 @@ Code use_package("purrr", "Suggests") Message - v Adding 'purrr' to Suggests field in DESCRIPTION + v Adding purrr to 'Suggests' field in DESCRIPTION. * In your package code, use `rlang::is_installed("purrr")` or `rlang::check_installed("purrr")` to test if purrr is installed * Then directly refer to functions with `purrr::fun()` diff --git a/tests/testthat/_snaps/tibble.md b/tests/testthat/_snaps/tibble.md index f0152f654..cdfd223c0 100644 --- a/tests/testthat/_snaps/tibble.md +++ b/tests/testthat/_snaps/tibble.md @@ -3,7 +3,7 @@ Code use_tibble() Message - v Adding 'tibble' to Imports field in DESCRIPTION + v Adding tibble to 'Imports' field in DESCRIPTION. v Adding '@importFrom tibble tibble' to 'R/{TESTPKG}-package.R'. * Document a returned tibble like so: #' @return a [tibble][tibble::tibble-package] diff --git a/tests/testthat/_snaps/tidyverse.md b/tests/testthat/_snaps/tidyverse.md index 21c23cb0d..7a5de4d09 100644 --- a/tests/testthat/_snaps/tidyverse.md +++ b/tests/testthat/_snaps/tidyverse.md @@ -3,11 +3,11 @@ Code use_tidy_dependencies() Message - v Adding 'rlang' to Imports field in DESCRIPTION - v Adding 'lifecycle' to Imports field in DESCRIPTION - v Adding 'cli' to Imports field in DESCRIPTION - v Adding 'glue' to Imports field in DESCRIPTION - v Adding 'withr' to Imports field in DESCRIPTION + v Adding rlang to 'Imports' field in DESCRIPTION. + v Adding lifecycle to 'Imports' field in DESCRIPTION. + v Adding cli to 'Imports' field in DESCRIPTION. + v Adding glue to 'Imports' field in DESCRIPTION. + v Adding withr to 'Imports' field in DESCRIPTION. v Adding '@import rlang' to 'R/{TESTPKG}-package.R'. v Adding '@importFrom glue glue' to 'R/{TESTPKG}-package.R'. v Adding '@importFrom lifecycle deprecated' to diff --git a/tests/testthat/test-helpers.R b/tests/testthat/test-helpers.R index 745eb22ea..3b063325d 100644 --- a/tests/testthat/test-helpers.R +++ b/tests/testthat/test-helpers.R @@ -30,31 +30,27 @@ test_that("valid_file_name() enforces valid file names", { test_that("we message for new type and are silent for same type", { create_local_package() - withr::local_options(list(usethis.quiet = FALSE, crayon.enabled = FALSE)) + withr::local_options(usethis.quiet = FALSE) - expect_message( - use_dependency("crayon", "Imports"), - "Adding 'crayon' to Imports field" + expect_snapshot( + use_dependency("crayon", "Imports") ) expect_silent(use_dependency("crayon", "Imports")) }) test_that("we message for version change and are silent for same version", { create_local_package() - withr::local_options(list(usethis.quiet = FALSE, crayon.enabled = FALSE)) + withr::local_options(usethis.quiet = FALSE) - expect_message( - use_dependency("crayon", "Imports"), - "Adding 'crayon" + expect_snapshot( + use_dependency("crayon", "Imports") ) - expect_message( - use_dependency("crayon", "Imports", min_version = "1.0.0"), - "Increasing 'crayon'" + expect_snapshot( + use_dependency("crayon", "Imports", min_version = "1.0.0") ) expect_silent(use_dependency("crayon", "Imports", min_version = "1.0.0")) - expect_message( - use_dependency("crayon", "Imports", min_version = "2.0.0"), - "Increasing 'crayon'" + expect_snapshot( + use_dependency("crayon", "Imports", min_version = "2.0.0") ) expect_silent(use_dependency("crayon", "Imports", min_version = "1.0.0")) }) @@ -62,12 +58,12 @@ test_that("we message for version change and are silent for same version", { ## https://github.com/r-lib/usethis/issues/99 test_that("use_dependency() upgrades a dependency", { create_local_package() - withr::local_options(list(usethis.quiet = FALSE, crayon.enabled = FALSE)) + withr::local_options(usethis.quiet = FALSE) - expect_message(use_dependency("usethis", "Suggests")) + expect_snapshot(use_dependency("usethis", "Suggests")) expect_match(desc::desc_get("Suggests"), "usethis") - expect_message(use_dependency("usethis", "Imports"), "Moving 'usethis'") + expect_snapshot(use_dependency("usethis", "Imports")) expect_match(desc::desc_get("Imports"), "usethis") expect_no_match(desc::desc_get("Suggests"), "usethis") }) @@ -75,15 +71,12 @@ test_that("use_dependency() upgrades a dependency", { ## https://github.com/r-lib/usethis/issues/99 test_that("use_dependency() declines to downgrade a dependency", { create_local_package() - withr::local_options(list(usethis.quiet = FALSE, crayon.enabled = FALSE)) + withr::local_options(usethis.quiet = FALSE) - expect_message(use_dependency("usethis", "Imports")) + expect_snapshot(use_dependency("usethis", "Imports")) expect_match(desc::desc_get("Imports"), "usethis") - withr::local_options(list(usethis.quiet = FALSE)) - expect_snapshot( - use_dependency("usethis", "Suggests") - ) + expect_snapshot(use_dependency("usethis", "Suggests")) expect_match(desc::desc_get("Imports"), "usethis") expect_no_match(desc::desc_get("Suggests"), "usethis") }) @@ -92,7 +85,7 @@ test_that("can add LinkingTo dependency if other dependency already exists", { create_local_package() use_dependency("rlang", "Imports") - withr::local_options(list(usethis.quiet = FALSE)) + withr::local_options(usethis.quiet = FALSE) expect_snapshot( use_dependency("rlang", "LinkingTo") ) @@ -107,7 +100,7 @@ test_that("use_dependency() does not fall over on 2nd LinkingTo request", { use_dependency("rlang", "LinkingTo") - withr::local_options(list(usethis.quiet = FALSE)) + withr::local_options(usethis.quiet = FALSE) expect_snapshot(use_dependency("rlang", "LinkingTo")) }) @@ -119,7 +112,7 @@ test_that("use_dependency() can level up a LinkingTo dependency", { use_dependency("rlang", "LinkingTo") use_dependency("rlang", "Suggests") - withr::local_options(list(usethis.quiet = FALSE)) + withr::local_options(usethis.quiet = FALSE) expect_snapshot(use_package("rlang")) deps <- proj_deps() From bfd87d7e9ece0a1147cf0535a4238f3733d115bc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 21:42:38 -0800 Subject: [PATCH 044/111] issue.R --- R/issue.R | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/R/issue.R b/R/issue.R index 16181f8c9..fecc20318 100644 --- a/R/issue.R +++ b/R/issue.R @@ -41,23 +41,25 @@ issue_close_community <- function(number, reprex = FALSE) { # https://docs.github.com/en/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level # I have not found a way to detect triage permission via API. # It seems you just have to try? - ui_line(" - You don't seem to have push access for {ui_value(tr$repo_spec)}. - Unless you have triage permissions, you won't be allowed to close an \\ - issue.") + ui_bullets(c( + "!" = "You don't seem to have push access for {.val {tr$repo_spec}}.", + "i" = "Unless you have triage permissions, you won't be allowed to close + an issue." + )) if (ui_nope("Do you want to try anyway?")) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } info <- issue_info(number, tr) issue <- issue_details(info) - ui_done(" - Closing issue {ui_value(issue$shorthand)} \\ - ({ui_field(issue$author)}): {ui_value(issue$title)}") + ui_bullets(c( + "v" = "Closing issue {.val {issue$shorthand}} ({.field {issue$author}}): + {.val {issue$title}}." + )) if (info$state == "closed") { - ui_stop("Issue {number} is already closed") + ui_abort(c("x" ="Issue {.val {number}} is already closed.")) } reprex_insert <- glue(" @@ -88,12 +90,13 @@ issue_reprex_needed <- function(number) { # https://docs.github.com/en/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level # I can't find anyway to detect triage permission via API. # It seems you just have to try? - ui_line(" - You don't seem to have push access for {ui_value(tr$repo_spec)}. - Unless you have triage permissions, you won't be allowed to label an \\ - issue.") + ui_bullets(c( + "!" = "You don't seem to have push access for {.val {tr$repo_spec}}.", + "i" = "Unless you have triage permissions, you won't be allowed to label + an issue." + )) if (ui_nope("Do you want to try anyway?")) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } @@ -102,12 +105,13 @@ issue_reprex_needed <- function(number) { labels <- map_chr(info$labels, "name") issue <- issue_details(info) if ("reprex" %in% labels) { - ui_stop("Issue {number} already has 'reprex' label") + ui_abort(c("x" = "Issue {.val {number}} already has {.val reprex} label.")) } - ui_done(" - Labelling and commenting on issue {ui_value(issue$shorthand)} \\ - ({ui_field(issue$author)}): {ui_value(issue$title)}") + ui_bullets(c( + "v" = "Labelling and commenting on issue {.val {issue$shorthand}} + ({.field {issue$author}}): {.val {issue$title}}." + )) message <- glue(" Can you please provide a minimal reproducible example using the \\ From 142c1fa0e2fa6464a36518d4b940263a7240dab7 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 21:54:27 -0800 Subject: [PATCH 045/111] license.R --- R/license.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/license.R b/R/license.R index 2d668f000..8243f560f 100644 --- a/R/license.R +++ b/R/license.R @@ -186,8 +186,7 @@ check_license_version <- function(version, possible) { version <- as.double(version) if (!version %in% possible) { - possible <- glue_collapse(possible, sep = ", ", last = ", or ") - ui_stop("`version` must be {possible}") + ui_abort(c("x" = "{.arg version} must be {.or {possible}}.")) } version From 48346b04787896678881fd39c0823cbb46f3ca86 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 21:59:19 -0800 Subject: [PATCH 046/111] lifecycle.R --- R/lifecycle.R | 19 ++++++++++--------- tests/testthat/_snaps/lifecycle.md | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/R/lifecycle.R b/R/lifecycle.R index f87419034..0a6e72796 100644 --- a/R/lifecycle.R +++ b/R/lifecycle.R @@ -18,8 +18,10 @@ use_lifecycle <- function() { check_is_package("use_lifecycle()") check_uses_roxygen("use_lifecycle()") if (!uses_roxygen_md()) { - ui_stop(" - Turn on roxygen2 markdown support {ui_code('use_roxygen_md()')}") + ui_abort(c( + "x" = "Turn on roxygen2 markdown support with + {.run usethis::use_roxygen_md()}, then try again." + )) } use_package("lifecycle") @@ -32,13 +34,12 @@ use_lifecycle <- function() { templ_files <- dir_ls(templ_dir, glob = "*/lifecycle-*.svg") purrr::walk(templ_files, file_copy, dest_dir, overwrite = TRUE) - ui_done("Copied SVG badges to {ui_path(dest_dir)}") - - ui_todo(c( - "Add badges in documentation topics by inserting one of:", - "#' `r lifecycle::badge('experimental')`", - "#' `r lifecycle::badge('superseded')`", - "#' `r lifecycle::badge('deprecated')`" + ui_bullets(c( + "v" = "Copied SVG badges to {.path {pth(dest_dir)}}.", + "_" = "Add badges in documentation topics by inserting a line like this:", + " " = "#' `r lifecycle::badge('experimental')`", + " " = "#' `r lifecycle::badge('superseded')`", + " " = "#' `r lifecycle::badge('deprecated')`" )) invisible(TRUE) diff --git a/tests/testthat/_snaps/lifecycle.md b/tests/testthat/_snaps/lifecycle.md index 710674bd1..bc3f0adc1 100644 --- a/tests/testthat/_snaps/lifecycle.md +++ b/tests/testthat/_snaps/lifecycle.md @@ -9,8 +9,8 @@ 'R/{TESTPKG}-package.R'. v Writing 'NAMESPACE' v Creating 'man/figures/' - v Copied SVG badges to 'man/figures/' - * Add badges in documentation topics by inserting one of: + v Copied SVG badges to 'man/figures/'. + [ ] Add badges in documentation topics by inserting a line like this: #' `r lifecycle::badge('experimental')` #' `r lifecycle::badge('superseded')` #' `r lifecycle::badge('deprecated')` From c85f779af9938be71f550c232b9725022bac36a5 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 22:04:44 -0800 Subject: [PATCH 047/111] logo.R --- R/logo.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/logo.R b/R/logo.R index f0e0661b2..e5d90584e 100644 --- a/R/logo.R +++ b/R/logo.R @@ -29,7 +29,7 @@ use_logo <- function(img, geometry = "240x278", retina = TRUE) { if (path_ext(img) == "svg") { logo_path <- path("man", "figures", "logo.svg") file_copy(img, proj_path(logo_path), overwrite = TRUE) - ui_done("Copied {ui_path(img)} to {ui_path(logo_path)}") + ui_bullets(c("v" = "Copied {.path {pth(img)}} to {.path {logo_path}}.")) height <- as.integer(sub(".*x", "", geometry)) } else { @@ -38,7 +38,7 @@ use_logo <- function(img, geometry = "240x278", retina = TRUE) { img_data <- magick::image_read(img) img_data <- magick::image_resize(img_data, geometry) magick::image_write(img_data, logo_path) - ui_done("Resized {ui_path(img)} to {geometry}") + ui_bullets(c("v" = "Resized {.path {pth(img)}} to {geometry}.")) height <- magick::image_info(magick::image_read(logo_path))$height } @@ -48,7 +48,7 @@ use_logo <- function(img, geometry = "240x278", retina = TRUE) { height <- round(height / 2) } - ui_todo("Add logo to your README with the following html:") + ui_bullets(c("_" = "Add logo to your README with the following html:")) pd_link <- pkgdown_url(pedantic = TRUE) if (is.null(pd_link)) { ui_code_snippet( From 0e8252c30921c23dabed38487df9ec3fc9523a8d Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 22:06:07 -0800 Subject: [PATCH 048/111] news.R --- R/news.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/news.R b/R/news.R index cf82197e2..548377c4a 100644 --- a/R/news.R +++ b/R/news.R @@ -50,10 +50,10 @@ use_news_heading <- function(version) { if (development_title == news[[1]]) { news[[1]] <- title - ui_done("Replacing development heading in NEWS.md") + ui_bullets(c("v" = "Replacing development heading in {.path NEWS.md}.")) return(write_utf8(news_path, news)) } - ui_done("Adding new heading to NEWS.md") + ui_bullets(c("v" = "Adding new heading to {.path NEWS.md}.")) write_utf8(news_path, c(title, "", news)) } From 55f0819f72ec3251cafdf8da65d59d6f287bd83c Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 22:23:35 -0800 Subject: [PATCH 049/111] package.R --- R/package.R | 61 +++++++++++++++++--------------- tests/testthat/_snaps/package.md | 12 ++++--- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/R/package.R b/R/package.R index bde7f0e40..a5af07361 100644 --- a/R/package.R +++ b/R/package.R @@ -83,9 +83,10 @@ use_remote <- function(package, package_remote = NULL) { package_remote <- package_remote(package_desc) } - ui_done(" - Adding {ui_value(package_remote)} to {ui_field('Remotes')} field in \\ - DESCRIPTION") + ui_bullets(c( + "v" = "Adding {.val {package_remote}} to {.field Remotes} field in + DESCRIPTION." + )) remotes <- c(remotes, package_remote) desc$set_remotes(remotes) @@ -113,7 +114,7 @@ package_remote <- function(desc) { urls <- desc_urls(package, desc = desc) urls <- urls[urls$is_github, ] if (nrow(urls) < 1) { - ui_stop("Cannot determine remote for {ui_value(package)}") + ui_abort(c("x" = "Cannot determine remote for {.pkg {package}}.")) } parsed <- parse_github_remotes(urls$url[[1]]) remote <- paste0(parsed$repo_owner, "/", parsed$repo_name) @@ -123,22 +124,23 @@ package_remote <- function(desc) { Is this OK?")) { remote } else { - ui_stop("Cannot determine remote for {ui_value(package)}") + ui_abort(c("x" = "Cannot determine remote for {.pkg {package}}.")) } } refuse_package <- function(package, verboten) { if (package %in% verboten) { - code <- glue("use_package(\"{package}\", type = \"depends\")") - ui_stop( - "{ui_value(package)} is a meta-package and it is rarely a good idea to \\ - depend on it. Please determine the specific underlying package(s) that \\ - offer the function(s) you need and depend on that instead. \\ - For data analysis projects that use a package structure but do not implement \\ - a formal R package, adding {ui_value(package)} to Depends is a \\ - reasonable compromise. Call {ui_code(code)} to achieve this. - " - ) + code <- glue('use_package("{package}", type = "depends")') + ui_abort(c( + "x" = "{.pkg {package}} is a meta-package and it is rarely a good idea to + depend on it.", + "_" = "Please determine the specific underlying package(s) that provide + the function(s) you need and depend on that instead.", + "i" = "For data analysis projects that use a package structure but do not + implement a formal R package, adding {.pkg {package}} to + {.field Depends} is a reasonable compromise.", + "_" = "Call {.code {code}} to achieve this." + )) } invisible(package) } @@ -151,11 +153,13 @@ how_to_use <- function(package, type) { } switch(type, - imports = ui_todo("Refer to functions with {ui_code(paste0(package, '::fun()'))}"), - depends = ui_todo( - "Are you sure you want {ui_field('Depends')}? \\ - {ui_field('Imports')} is almost always the better choice." - ), + imports = ui_bullets(c( + "_" = "Refer to functions with {.code {paste0(package, '::fun()')}}." + )), + depends = ui_bullets(c( + "!" = "Are you sure you want {.field Depends}? + {.field Imports} is almost always the better choice." + )), suggests = suggests_usage_hint(package), enhances = "", linkingto = show_includes(package) @@ -167,16 +171,17 @@ suggests_usage_hint <- function(package) { if (imports_rlang) { code1 <- glue('rlang::is_installed("{package}")') code2 <- glue('rlang::check_installed("{package}")') - ui_todo(" - In your package code, use {ui_code(code1)} or {ui_code(code2)} to test \\ - if {package} is installed") + ui_bullets(c( + "_" = "In your package code, use {.code {code1}} or {.code {code2}} to + test if {.pkg {package}} is installed." + )) code <- glue("{package}::fun()") - ui_todo("Then directly refer to functions with {ui_code(code)}") + ui_bullets(c("_" = "Then directly refer to functions with {.code {code}}.")) } else { - code <- glue("requireNamespace(\"{package}\", quietly = TRUE)") - ui_todo("Use {ui_code(code)} to test if package is installed") + code <- glue('requireNamespace("{package}", quietly = TRUE)') + ui_bullets(c("_" = "Use {.code {code}} to test if package is installed.")) code <- glue("{package}::fun()") - ui_todo("Then directly refer to functions with {ui_code(code)}") + ui_bullets(c("_" = "Then directly refer to functions with {.code {code}}.")) } } @@ -187,6 +192,6 @@ show_includes <- function(package) { return() } - ui_todo("Possible includes are:") + ui_bullets(c("Possible includes are:")) ui_code_snippet("#include <{path_file(h)}>", copy = FALSE, language = "") } diff --git a/tests/testthat/_snaps/package.md b/tests/testthat/_snaps/package.md index c2175eccb..96501fa5e 100644 --- a/tests/testthat/_snaps/package.md +++ b/tests/testthat/_snaps/package.md @@ -4,7 +4,7 @@ use_package("withr") Message v Adding withr to 'Imports' field in DESCRIPTION. - * Refer to functions with `withr::fun()` + [ ] Refer to functions with `withr::fun()`. Code use_package("withr") use_package("withr", "Suggests") @@ -47,8 +47,9 @@ use_package("withr", "Suggests") Message v Adding withr to 'Suggests' field in DESCRIPTION. - * Use `requireNamespace("withr", quietly = TRUE)` to test if package is installed - * Then directly refer to functions with `withr::fun()` + [ ] Use `requireNamespace("withr", quietly = TRUE)` to test if package is + installed. + [ ] Then directly refer to functions with `withr::fun()`. --- @@ -56,6 +57,7 @@ use_package("purrr", "Suggests") Message v Adding purrr to 'Suggests' field in DESCRIPTION. - * In your package code, use `rlang::is_installed("purrr")` or `rlang::check_installed("purrr")` to test if purrr is installed - * Then directly refer to functions with `purrr::fun()` + [ ] In your package code, use `rlang::is_installed("purrr")` or + `rlang::check_installed("purrr")` to test if purrr is installed. + [ ] Then directly refer to functions with `purrr::fun()`. From 1e3f5c810afe7849778847e215476437ed2262c1 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 22 Feb 2024 22:28:39 -0800 Subject: [PATCH 050/111] pkgdown.R --- R/pkgdown.R | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/R/pkgdown.R b/R/pkgdown.R index ca3530320..b2b7f1151 100644 --- a/R/pkgdown.R +++ b/R/pkgdown.R @@ -86,9 +86,10 @@ use_pkgdown_url <- function(url, tr = NULL) { tr <- tr %||% target_repo(github_get = TRUE) config_path <- pkgdown_config_path() - ui_done(" - Recording {ui_value(url)} as site's {ui_field('url')} in \\ - {ui_path(config_path)}") + ui_bullets(c( + "v" = "Recording {.url {url}} as site's {.field url} in + {.path {pth(config_path)}}." + )) config <- pkgdown_config_meta() if (has_name(config, "url")) { config$url <- url @@ -99,15 +100,17 @@ use_pkgdown_url <- function(url, tr = NULL) { proj_desc_field_update("URL", url, append = TRUE) if (has_package_doc()) { - ui_todo(" - Run {ui_code('devtools::document()')} to update package-level documentation.") + ui_bullets(c( + "_" = "Run {.run devtools::document()} to update package-level documentation." + )) } gh <- gh_tr(tr) homepage <- gh("GET /repos/{owner}/{repo}")[["homepage"]] if (is.null(homepage) || homepage != url) { - ui_done("Setting {ui_value(url)} as homepage of GitHub repo \\ - {ui_value(tr$repo_spec)}") + ui_bullets(c( + "v" = "Setting {.url {url}} as homepage of GitHub repo {.val {tr$repo_spec}}." + )) gh("PATCH /repos/{owner}/{repo}", homepage = url) } From bba1926090fc37bc467ea128f86b06baca50c059 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 09:18:57 -0800 Subject: [PATCH 051/111] Provide default bullets in ui_abort() --- R/block.R | 6 +++--- R/browse.R | 8 ++++--- R/course.R | 13 ++++++------ R/create.R | 8 +++---- R/directory.R | 8 +++---- R/git-default-branch.R | 22 +++++++------------ R/git.R | 6 +++--- R/github-actions.R | 5 ++--- R/github.R | 12 ++++------- R/helpers.R | 8 ++----- R/issue.R | 4 ++-- R/license.R | 2 +- R/lifecycle.R | 7 +++--- R/package.R | 4 ++-- R/utils-ui.R | 7 ++++++ tests/testthat/_snaps/badge.md | 6 +++--- tests/testthat/_snaps/helpers.md | 2 +- tests/testthat/_snaps/lifecycle.md | 2 +- tests/testthat/_snaps/utils-ui.md | 27 ++++++++++++++++++++++++ tests/testthat/test-utils-ui.R | 20 ++++++++++++++++++ vignettes/articles/ui-cli-conversion.Rmd | 25 ++++++++++++++++++++++ 21 files changed, 132 insertions(+), 70 deletions(-) diff --git a/R/block.R b/R/block.R index 35d35de63..5f66476dc 100644 --- a/R/block.R +++ b/R/block.R @@ -103,9 +103,9 @@ block_find <- function(lines, block_start = "# <<<", block_end = "# >>>") { if (!(length(start) == 1 && length(end) == 1 && start < end)) { ui_abort(c( - "x" = "Invalid block specification.", - "i" = "Must start with {.code {block_start}} and end with - {.code {block_end}}." + "Invalid block specification.", + "Must start with {.code {block_start}} and end with + {.code {block_end}}." )) } diff --git a/R/browse.R b/R/browse.R index baf045ba7..726a97950 100644 --- a/R/browse.R +++ b/R/browse.R @@ -177,12 +177,14 @@ github_url <- function(package = NULL) { ui_abort(c( "Project {.val {project_name()}} has no DESCRIPTION file and has no GitHub remotes configured.", - x = "No way to discover URLs.")) + "No way to discover URLs." + )) } else { ui_abort(c( - "Can't find DESCRIPTION for package {.val {package}} locally + "Can't find DESCRIPTION for package {.pkg {package}} locally or on CRAN.", - x = "No way to discover URLs.")) + "No way to discover URLs." + )) } } diff --git a/R/course.R b/R/course.R index 2cf665080..371fadcf1 100644 --- a/R/course.R +++ b/R/course.R @@ -263,8 +263,8 @@ tidy_download <- function(url, destdir = getwd()) { full_path <- path(destdir, base_name) if (!can_overwrite(full_path)) { - ui_abort( - "Cancelling download, to avoid overwriting {.path {pth(full_path)}}.") + ui_abort(" + Cancelling download, to avoid overwriting {.path {pth(full_path)}}.") } attr(full_path, "content-type") <- content_type(h) attr(full_path, "content-disposition") <- cd @@ -557,8 +557,8 @@ check_is_zip <- function(ct) { allowed <- c("application/zip", "application/x-zip-compressed") if (!ct %in% allowed) { ui_abort(c( - "x" = "Download does not have MIME type {.val application/zip}.", - "i" = "Instead it's {.val {ct}}." + "Download does not have MIME type {.val application/zip}.", + "Instead it's {.val {ct}}." )) } invisible(ct) @@ -573,9 +573,8 @@ check_is_zip <- function(ct) { parse_content_disposition <- function(cd) { if (!grepl("^attachment;", cd)) { ui_abort(c( - "x" = "{.code Content-Disposition} header doesn't start with - {.val attachment}.", - "i" = "Actual header: {.val cd}" + "{.code Content-Disposition} header doesn't start with {.val attachment}.", + "Actual header: {.val cd}" )) } diff --git a/R/create.R b/R/create.R index 45d984f4e..3cf6a93b4 100644 --- a/R/create.R +++ b/R/create.R @@ -226,7 +226,6 @@ create_from_github <- function(repo_spec, ui_abort(c( "x" = "Unable to discover a GitHub personal access token.", "i" = "A token is required in order to fork {.val {repo_spec}}.", - "", "_" = "Call {.code {hint}} for help configuring a token." )) } @@ -254,10 +253,9 @@ create_from_github <- function(repo_spec, # fork is either TRUE or FALSE if (fork && identical(user, repo_info$owner$login)) { - ui_abort(c( - "x" = "Can't fork, because the authenticated user {.val {user}} - already owns the source repo {.vale {repo_info$full_name}}." - )) + ui_abort(" + Can't fork, because the authenticated user {.val {user}} already owns the + source repo {.val {repo_info$full_name}}.") } destdir <- user_path_prep(destdir %||% conspicuous_place()) diff --git a/R/directory.R b/R/directory.R index 479797a11..3e465a43d 100644 --- a/R/directory.R +++ b/R/directory.R @@ -26,7 +26,7 @@ create_directory <- function(path) { if (dir_exists(path)) { return(invisible(FALSE)) } else if (file_exists(path)) { - ui_abort(c("x" = "{.path {pth(path)}} exists but is not a directory.")) + ui_abort("{.path {pth(path)}} exists but is not a directory.") } dir_create(path, recurse = TRUE) @@ -36,7 +36,7 @@ create_directory <- function(path) { check_path_is_directory <- function(path) { if (!file_exists(path)) { - ui_abort(c("x" = "Directory {.path {pth(path)}} does not exist.")) + ui_abort("Directory {.path {pth(path)}} does not exist.") } if (is_link(path)) { @@ -44,7 +44,7 @@ check_path_is_directory <- function(path) { } if (!is_dir(path)) { - ui_abort(c("x" = "{.path {pth(path)}} is not a directory.")) + ui_abort("{.path {pth(path)}} is not a directory.") } } @@ -58,7 +58,7 @@ directory_has_files <- function(x) { check_directory_is_empty <- function(x) { if (directory_has_files(x)) { - ui_abort(c("x" = "{.path {pth(path)}} exists and is not an empty directory.")) + ui_abort("{.path {pth(path)}} exists and is not an empty directory.") } invisible(x) } diff --git a/R/git-default-branch.R b/R/git-default-branch.R index f79e51eee..d06ea4ae3 100644 --- a/R/git-default-branch.R +++ b/R/git-default-branch.R @@ -156,7 +156,7 @@ git_default_branch <- function() { ) } else { ui_abort( - c("x" = "Can't determine the local repo's default branch."), + "Can't determine the local repo's default branch.", class = "error_default_branch" ) } @@ -261,10 +261,8 @@ guess_local_default_branch <- function(prefer = NULL, verbose = FALSE) { } else { # TODO: perhaps this should be classed, so I can catch it and distinguish # from the ui_abort() above, where there are no local branches. - ui_abort(c( - "x" = "Unable to guess which existing local branch plays the role of the - default." - )) + ui_abort(" + Unable to guess which existing local branch plays the role of the default.") } if (verbose) { @@ -329,7 +327,7 @@ git_default_branch_rename <- function(from = NULL, to = "main") { if (!is.null(from) && !gert::git_branch_exists(from, local = TRUE, repo = repo)) { - ui_abort(c("x" = "Can't find existing branch named {.val {from}}.")) + ui_abort("Can't find existing branch named {.val {from}}.") } cfg <- github_remote_config(github_get = TRUE) @@ -357,11 +355,9 @@ git_default_branch_rename <- function(from = NULL, to = "main") { old_source_db <- tr$default_branch if (!isTRUE(tr$can_admin)) { - ui_abort(c( - "x" = "You don't seem to have {.field admin} permissions for the source - repo {.val {tr$repo_spec}}, which is required to rename the default - branch." - )) + ui_abort(" + You don't seem to have {.field admin} permissions for the source repo + {.val {tr$repo_spec}}, which is required to rename the default branch.") } old_local_db <- from %||% @@ -428,9 +424,7 @@ rediscover_default_branch <- function(old_name = NULL, report_on_source = TRUE) repo <- git_repo() if (!is.null(old_name) && !gert::git_branch_exists(old_name, local = TRUE, repo = repo)) { - ui_abort(c( - "x" = "Can't find existing local branch named {.val {old_name}}." - )) + ui_abort("Can't find existing local branch named {.val {old_name}}.") } cfg <- github_remote_config(github_get = TRUE) diff --git a/R/git.R b/R/git.R index 5d8836526..040486778 100644 --- a/R/git.R +++ b/R/git.R @@ -179,7 +179,7 @@ check_protocol <- function(protocol) { if (!is_string(protocol) || !(tolower(protocol) %in% c("https", "ssh"))) { options(usethis.protocol = NULL) - ui_abort(c("x" = "{.arg protocol} must be either {.val https} or {.val ssh}.")) + ui_abort("{.arg protocol} must be either {.val https} or {.val ssh}.") } invisible() } @@ -250,8 +250,8 @@ use_git_remote <- function(name = "origin", url, overwrite = FALSE) { if (name %in% names(remotes) && !overwrite) { ui_abort(c( - "x" = "Remote {.val {name}} already exists.", - "i" = "Use {.code overwrite = TRUE} to edit it anyway." + "Remote {.val {name}} already exists.", + "Use {.code overwrite = TRUE} to edit it anyway." )) } diff --git a/R/github-actions.R b/R/github-actions.R index b99545e15..e401ffd38 100644 --- a/R/github-actions.R +++ b/R/github-actions.R @@ -251,9 +251,8 @@ check_uses_github_actions <- function() { } ui_abort(c( - "x" = "Cannot detect that package {.pkg {project_name()}} already uses - GitHub Actions.", - "i" = "Do you need to run {.fun use_github_actions}?" + "Cannot detect that package {.pkg {project_name()}} already uses GitHub Actions.", + "Do you need to run {.fun use_github_actions}?" )) } diff --git a/R/github.R b/R/github.R index 369d62408..f1f2a615c 100644 --- a/R/github.R +++ b/R/github.R @@ -89,11 +89,9 @@ use_github <- function(organisation = NULL, if (is.null(organisation)) { if (visibility_specified) { - ui_abort(c( - "x" = "The {.arg visibility} setting is only relevant for - organisation-owned repos, within the context of certain GitHub - Enterprise products." - )) + ui_abort(" + The {.arg visibility} setting is only relevant for organisation-owned + repos, within the context of certain GitHub Enterprise products.") } visibility <- if (private) "private" else "public" } @@ -287,7 +285,5 @@ check_no_github_repo <- function(owner, repo, host) { } spec <- glue("{owner}/{repo}") empirical_host <- parse_github_remotes(repo_info$html_url)$host - ui_abort(c( - "x" = "Repo {.val {spec}} already exists on {.val {empirical_host}}." - )) + ui_abort("Repo {.val {spec}} already exists on {.val {empirical_host}}.") } diff --git a/R/helpers.R b/R/helpers.R index c1190353e..edbdbdd87 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -7,13 +7,9 @@ use_dependency <- function(package, type, min_version = NULL) { } if (package == "R" && tolower(type) != "depends") { - ui_abort(c( - "x" = 'Set {.code type = "Depends"} when specifying an R version.' - )) + ui_abort('Set {.code type = "Depends"} when specifying an R version.') } else if (package == "R" && is.null(min_version)) { - ui_abort(c( - "x" = 'Specify {.arg min_version} when {.code package = "R"}.' - )) + ui_abort('Specify {.arg min_version} when {.code package = "R"}.') } if (isTRUE(min_version) && package == "R") { diff --git a/R/issue.R b/R/issue.R index fecc20318..4e6dc907b 100644 --- a/R/issue.R +++ b/R/issue.R @@ -59,7 +59,7 @@ issue_close_community <- function(number, reprex = FALSE) { {.val {issue$title}}." )) if (info$state == "closed") { - ui_abort(c("x" ="Issue {.val {number}} is already closed.")) + ui_abort("Issue {.val {number}} is already closed.") } reprex_insert <- glue(" @@ -105,7 +105,7 @@ issue_reprex_needed <- function(number) { labels <- map_chr(info$labels, "name") issue <- issue_details(info) if ("reprex" %in% labels) { - ui_abort(c("x" = "Issue {.val {number}} already has {.val reprex} label.")) + ui_abort("Issue {.val {number}} already has {.val reprex} label.") } ui_bullets(c( diff --git a/R/license.R b/R/license.R index 8243f560f..0e3340eab 100644 --- a/R/license.R +++ b/R/license.R @@ -186,7 +186,7 @@ check_license_version <- function(version, possible) { version <- as.double(version) if (!version %in% possible) { - ui_abort(c("x" = "{.arg version} must be {.or {possible}}.")) + ui_abort("{.arg version} must be {.or {possible}}.") } version diff --git a/R/lifecycle.R b/R/lifecycle.R index 0a6e72796..cd4f13c5a 100644 --- a/R/lifecycle.R +++ b/R/lifecycle.R @@ -18,10 +18,9 @@ use_lifecycle <- function() { check_is_package("use_lifecycle()") check_uses_roxygen("use_lifecycle()") if (!uses_roxygen_md()) { - ui_abort(c( - "x" = "Turn on roxygen2 markdown support with - {.run usethis::use_roxygen_md()}, then try again." - )) + ui_abort(" + Turn on roxygen2 markdown support with {.run usethis::use_roxygen_md()}, + then try again.") } use_package("lifecycle") diff --git a/R/package.R b/R/package.R index a5af07361..b621b97e9 100644 --- a/R/package.R +++ b/R/package.R @@ -114,7 +114,7 @@ package_remote <- function(desc) { urls <- desc_urls(package, desc = desc) urls <- urls[urls$is_github, ] if (nrow(urls) < 1) { - ui_abort(c("x" = "Cannot determine remote for {.pkg {package}}.")) + ui_abort("Cannot determine remote for {.pkg {package}}.") } parsed <- parse_github_remotes(urls$url[[1]]) remote <- paste0(parsed$repo_owner, "/", parsed$repo_name) @@ -124,7 +124,7 @@ package_remote <- function(desc) { Is this OK?")) { remote } else { - ui_abort(c("x" = "Cannot determine remote for {.pkg {package}}.")) + ui_abort("Cannot determine remote for {.pkg {package}}.") } } diff --git a/R/utils-ui.R b/R/utils-ui.R index 61e81c2e7..0d274b9f5 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -45,6 +45,13 @@ ui_bullets <- function(text, .envir = parent.frame()) { ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { cli::cli_div(theme = usethis_theme()) + + nms <- names2(message) + default_nms <- rep_along(message, "i") + default_nms[1] <- "x" + nms <- ifelse(nzchar(nms), nms, default_nms) + names(message) <- nms + cli::cli_abort( message, class = c(class, "usethis_error"), diff --git a/tests/testthat/_snaps/badge.md b/tests/testthat/_snaps/badge.md index ec4f8af43..a212bab6b 100644 --- a/tests/testthat/_snaps/badge.md +++ b/tests/testthat/_snaps/badge.md @@ -29,7 +29,7 @@ use_posit_cloud_badge("http://posit.cloud/123") Condition Error in `use_posit_cloud_badge()`: - ! `usethis::use_posit_cloud_badge()` requires a link to an existing Posit Cloud project of the form "https://posit.cloud/content/" or "https://posit.cloud/spaces//content/". + x `usethis::use_posit_cloud_badge()` requires a link to an existing Posit Cloud project of the form "https://posit.cloud/content/" or "https://posit.cloud/spaces//content/". --- @@ -37,7 +37,7 @@ use_rscloud_badge("https://rstudio.cloud/content/123") Condition Error in `use_posit_cloud_badge()`: - ! `usethis::use_posit_cloud_badge()` requires a link to an existing Posit Cloud project of the form "https://posit.cloud/content/" or "https://posit.cloud/spaces//content/". + x `usethis::use_posit_cloud_badge()` requires a link to an existing Posit Cloud project of the form "https://posit.cloud/content/" or "https://posit.cloud/spaces//content/". --- @@ -45,5 +45,5 @@ use_rscloud_badge("https://posit.cloud/project/123") Condition Error in `use_posit_cloud_badge()`: - ! `usethis::use_posit_cloud_badge()` requires a link to an existing Posit Cloud project of the form "https://posit.cloud/content/" or "https://posit.cloud/spaces//content/". + x `usethis::use_posit_cloud_badge()` requires a link to an existing Posit Cloud project of the form "https://posit.cloud/content/" or "https://posit.cloud/spaces//content/". diff --git a/tests/testthat/_snaps/helpers.md b/tests/testthat/_snaps/helpers.md index 570af239e..807c258c3 100644 --- a/tests/testthat/_snaps/helpers.md +++ b/tests/testthat/_snaps/helpers.md @@ -73,5 +73,5 @@ use_package("rlang") Message v Moving rlang from 'Suggests' to 'Imports' field in DESCRIPTION. - * Refer to functions with `rlang::fun()` + [ ] Refer to functions with `rlang::fun()`. diff --git a/tests/testthat/_snaps/lifecycle.md b/tests/testthat/_snaps/lifecycle.md index bc3f0adc1..d93e9c737 100644 --- a/tests/testthat/_snaps/lifecycle.md +++ b/tests/testthat/_snaps/lifecycle.md @@ -4,7 +4,7 @@ use_lifecycle() Message v Adding lifecycle to 'Imports' field in DESCRIPTION. - * Refer to functions with `lifecycle::fun()` + [ ] Refer to functions with `lifecycle::fun()`. v Adding '@importFrom lifecycle deprecated' to 'R/{TESTPKG}-package.R'. v Writing 'NAMESPACE' diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index ff3620359..5dd999bab 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -98,6 +98,33 @@ ✔ Updated the BugReports field ✖ Scary `code` or `function()` +# ui_abort() defaults to 'x' for first bullet + + Code + ui_abort("no explicit bullet") + Condition + Error: + x no explicit bullet + +# ui_abort() can take explicit first bullet + + Code + ui_abort(c(v = "success bullet")) + Condition + Error: + v success bullet + +# ui_abort() defaults to 'i' for non-first bullet + + Code + ui_abort(c("oops", ` ` = "space bullet", "info bullet", v = "success bullet")) + Condition + Error: + x oops + space bullet + i info bullet + v success bullet + # ui_code_snippet() with scalar input [plain] Code diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index ad2f635fd..eef5429b2 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -80,6 +80,26 @@ test_that("ui_abort() works", { expect_usethis_error(ui_abort("whisk"), "whisk") }) +test_that("ui_abort() defaults to 'x' for first bullet", { + expect_snapshot(error = TRUE, ui_abort("no explicit bullet")) +}) + +test_that("ui_abort() can take explicit first bullet", { + expect_snapshot(error = TRUE, ui_abort(c("v" = "success bullet"))) +}) + +test_that("ui_abort() defaults to 'i' for non-first bullet", { + expect_snapshot( + error = TRUE, + ui_abort(c( + "oops", + " " = "space bullet", + "info bullet", + "v" = "success bullet" + )) + ) +}) + cli::test_that_cli("ui_code_snippet() with scalar input", { withr::local_options(list(usethis.quiet = FALSE)) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 1f1245e5c..3da8bbe09 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -194,6 +194,7 @@ to `ui_abort()`: ```{r} ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { cli::cli_div(theme = usethis_theme()) + # bullet naming stuff, see below cli::cli_abort( message, class = c(class, "usethis_error"), @@ -206,6 +207,30 @@ ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { At this point, the `usethis_theme()` doesn't do anything that affects the styling of an error. So the main point of `ui_abort()` is to use to `cli_abort()` (and to continue applying the `"usethis_error"` class). +I also use `ui_abort()` to apply different default bullet styling. +Starting with `"x"` and then defaulting to `"i"` seems to fit best with usethis's existing errors. + +```{r} +block_start = "# <<<" +block_end = "# >>>" +ui_abort(c( + "Invalid block specification.", + "Must start with {.code {block_start}} and end with {.code {block_end}}." +)) +``` + +Any bullets that are explicitly given are honored. + +```{r} +ui_abort(c("v" = "Weird thing to do in an error, but whatever.")) +ui_abort(c( + "!" = "Things are not ideal.", + ">" = "Look at me!" +)) +``` + +`rlang::abort()` and `cli::cli_abort()` start with `"!"` by default, then use `"*"` and `" "`, respectively. + The legacy functions include `ui_warn()`: ```{r} From 89b8a1bf3f89b72f54843787043e712ff12f47dc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 12:27:23 -0800 Subject: [PATCH 052/111] pr.R --- R/pr.R | 193 +++++++++++++---------- vignettes/articles/ui-cli-conversion.Rmd | 11 ++ 2 files changed, 119 insertions(+), 85 deletions(-) diff --git a/R/pr.R b/R/pr.R index 9341f1945..ed8348133 100644 --- a/R/pr.R +++ b/R/pr.R @@ -155,9 +155,10 @@ pr_init <- function(branch) { repo <- git_repo() if (gert::git_branch_exists(branch, local = TRUE, repo = repo)) { - code <- glue("pr_resume(\"{branch}\")") - ui_info(" - Branch {ui_value(branch)} already exists, calling {ui_code(code)}") + code <- glue('pr_resume("{branch}")') + ui_bullets(c( + "i" = "Branch {.val {branch}} already exists, calling {.code {code}}." + )) return(pr_resume(branch)) } @@ -169,14 +170,16 @@ pr_init <- function(branch) { maybe_good_configs <- c("maybe_ours_or_theirs", "maybe_fork") if (cfg$type %in% maybe_good_configs) { - ui_line(' - Unable to confirm the GitHub remote configuration is "pull request ready". - You probably need to configure a personal access token for \\ - {ui_value(tr$host)}. - See {ui_code("gh_token_help()")} for help. - (Or maybe we\'re just offline?)') + ui_bullets(c( + "x" = 'Unable to confirm the GitHub remote configuration is + "pull request ready".', + "i" = "You probably need to configure a personal access token for + {.val {tr$host}}.", + "i" = "See {.run usethis::gh_token_help()} for help with that.", + "i" = "(Or maybe we're just offline?)" + )) if (ui_github_remote_config_wat(cfg)) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } @@ -207,20 +210,21 @@ pr_init <- function(branch) { if (comparison$remote_only > 0) { challenge_uncommitted_changes() } - ui_done("Pulling changes from {ui_value(remref)}.") + ui_bullets(c("v" = "Pulling changes from {.val {remref}}.")) git_pull(remref = remref, verbose = FALSE) } } else { - ui_info(" - Unable to pull changes for current branch, since we are offline.") + ui_bullets(c( + "!" = "Unable to pull changes for current branch, since we are offline." + )) } - ui_done("Creating and switching to local branch {ui_value(branch)}.") + ui_bullets(c("v" = "Creating and switching to local branch {.val {branch}}.")) gert::git_branch_create(branch, repo = repo) config_key <- glue("branch.{branch}.created-by") gert::git_config_set(config_key, value = "usethis::pr_init", repo = repo) - ui_todo("Use {ui_code('pr_push()')} to create a PR.") + ui_bullets(c("_" = "Use {.fun pr_push} to create a PR.")) invisible() } @@ -230,35 +234,37 @@ pr_resume <- function(branch = NULL) { repo <- git_repo() if (is.null(branch)) { - ui_info(" - No branch specified ... looking up local branches and associated PRs.") + ui_bullets(c( + "i" = "No branch specified ... looking up local branches and associated PRs." + )) default_branch <- git_default_branch() branch <- choose_branch(exclude = default_branch) if (is.null(branch)) { - ui_oops("Repo doesn't seem to have any non-default branches.") + ui_bullets(c("x" = "Repo doesn't seem to have any non-default branches.")) return(invisible()) } if (length(branch) == 0) { - ui_oops("No branch selected, exiting.") + ui_bullets(c("x" = "No branch selected, exiting.")) return(invisible()) } } check_string(branch) if (!gert::git_branch_exists(branch, local = TRUE, repo = repo)) { - code <- glue("pr_init(\"{branch}\")") - ui_stop(" - No branch named {ui_value(branch)} exists. - Call {ui_code(code)} to create a new PR branch.") + code <- glue('pr_init("{branch}")') + ui_abort(c( + "x" = "No branch named {.val {branch}} exists.", + "_" = "Call {.code {code}} to create a new PR branch." + )) } challenge_uncommitted_changes() - ui_done("Switching to branch {ui_value(branch)}.") + ui_bullets(c("v" = "Switching to branch {.val {branch}}.")) gert::git_branch_checkout(branch, repo = repo) git_pull() - ui_todo("Use {ui_code('pr_push()')} to create or update PR.") + ui_bullets(c("_" = "Use {.fun usethis::pr_push} to create or update PR.")) invisible() } @@ -280,14 +286,14 @@ pr_fetch <- function(number = NULL, target = c("source", "primary")) { challenge_uncommitted_changes() if (is.null(number)) { - ui_info("No PR specified ... looking up open PRs.") + ui_bullets(c("i" = "No PR specified ... looking up open PRs.")) pr <- choose_pr(tr = tr) if (is.null(pr)) { - ui_oops("No open PRs found for {ui_value(tr$repo_spec)}.") + ui_bullets(c("x" = "No open PRs found for {.val {tr$repo_spec}}.")) return(invisible()) } if (min(lengths(pr)) == 0) { - ui_oops("No PR selected, exiting.") + ui_bullets(c("x" = "No PR selected, exiting.")) return(invisible()) } } else { @@ -295,26 +301,28 @@ pr_fetch <- function(number = NULL, target = c("source", "primary")) { } if (is.na(pr$pr_repo_owner)) { - ui_stop(" - The repo or branch where PR #{pr$pr_number} originates seems to have been \\ - deleted.") + ui_abort(" + The repo or branch where PR #{pr$pr_number} originates seems to have been + deleted.") } pr_user <- glue("@{pr$pr_user}") - ui_done(" - Checking out PR {ui_value(pr$pr_string)} ({ui_field(pr_user)}): \\ - {ui_value(pr$pr_title)}.") + ui_bullets(c( + "v" = "Checking out PR {.val {pr$pr_string}} ({.field {pr_user}}): + {.val {pr$pr_title}}." + )) if (pr$pr_from_fork && isFALSE(pr$maintainer_can_modify)) { - ui_info(" - Note that user does NOT allow maintainer to modify this PR at this \\ - time, although this can be changed.") + ui_bullets(c( + "!" = "Note that user does NOT allow maintainer to modify this PR at this + time, although this can be changed." + )) } remote <- github_remote_list(pr$pr_remote) if (nrow(remote) == 0) { url <- switch(tr$protocol, https = pr$pr_https_url, ssh = pr$pr_ssh_url) - ui_done("Adding remote {ui_value(pr$pr_remote)} as {ui_value(url)}.") + ui_bullets(c("v" = "Adding remote {.val {pr$pr_remote}} as {.val {url}}.")) gert::git_remote_add(url = url, name = pr$pr_remote, repo = repo) config_key <- glue("remote.{pr$pr_remote}.created-by") gert::git_config_set(config_key, "usethis::pr_fetch", repo = repo) @@ -334,9 +342,10 @@ pr_fetch <- function(number = NULL, target = c("source", "primary")) { # Create local branch, if necessary, and switch to it ---- if (!gert::git_branch_exists(pr$pr_local_branch, local = TRUE, repo = repo)) { - ui_done(" - Creating and switching to local branch {ui_value(pr$pr_local_branch)}.") - ui_done("Setting {ui_value(pr_remref)} as remote tracking branch.") + ui_bullets(c( + "v" = "Creating and switching to local branch {.val {pr$pr_local_branch}}.", + "v" = "Setting {.val {pr_remref}} as remote tracking branch." + )) gert::git_branch_create(pr$pr_local_branch, ref = pr_remref, repo = repo) config_key <- glue("branch.{pr$pr_local_branch}.created-by") gert::git_config_set(config_key, "usethis::pr_fetch", repo = repo) @@ -346,7 +355,7 @@ pr_fetch <- function(number = NULL, target = c("source", "primary")) { } # Local branch pre-existed; make sure tracking branch is set, switch, & pull - ui_done("Switching to branch {ui_value(pr$pr_local_branch)}.") + ui_bullets(c("v" = "Switching to branch {.val {pr$pr_local_branch}}.")) gert::git_branch_checkout(pr$pr_local_branch, repo = repo) config_url <- glue("branch.{pr$pr_local_branch}.pr-url") gert::git_config_set(config_url, pr$pr_html_url, repo = repo) @@ -354,7 +363,7 @@ pr_fetch <- function(number = NULL, target = c("source", "primary")) { pr_branch_ours_tracking <- git_branch_tracking(pr$pr_local_branch) if (is.na(pr_branch_ours_tracking) || pr_branch_ours_tracking != pr_remref) { - ui_done("Setting {ui_value(pr_remref)} as remote tracking branch.") + ui_bullets(c("v" = "Setting {.val {pr_remref}} as remote tracking branch.")) gert::git_branch_set_upstream(pr_remref, repo = repo) } git_pull(verbose = FALSE) @@ -400,8 +409,9 @@ pr_push <- function() { if (is.null(pr)) { pr_create() } else { - ui_todo(" - View PR at {ui_value(pr$pr_html_url)} or call {ui_code('pr_view()')}.") + ui_bullets(c( + "_" = "View PR at {.url {pr$pr_html_url}} or call {.run usethis::pr_view()}." + )) } invisible() @@ -431,7 +441,7 @@ pr_merge_main <- function() { tr <- target_repo(github_get = TRUE, ask = FALSE) challenge_uncommitted_changes() remref <- glue("{tr$remote}/{tr$default_branch}") - ui_done("Pulling changes from {ui_value(remref)}.") + ui_bullets(c("v" = "Pulling changes from {.val {remref}}.")) git_pull(remref, verbose = FALSE) } @@ -446,13 +456,15 @@ pr_view <- function(number = NULL, target = c("source", "primary")) { if (branch != default_branch) { url <- pr_url(branch = branch, tr = tr) if (is.null(url)) { - ui_info(" - Current branch ({ui_value(branch)}) does not appear to be \\ - connected to a PR.") + ui_bullets(c( + "i" = "Current branch ({.val {branch}}) does not appear to be + connected to a PR." + )) } else { number <- sub("^.+pull/", "", url) - ui_info(" - Current branch ({ui_value(branch)}) is connected to PR #{number}.") + ui_bullets(c( + "i" = "Current branch ({.val {branch}}) is connected to PR #{number}." + )) } } } else { @@ -460,14 +472,14 @@ pr_view <- function(number = NULL, target = c("source", "primary")) { url <- pr$pr_html_url } if (is.null(url)) { - ui_info("No PR specified ... looking up open PRs.") + ui_bullets(c("i" = "No PR specified ... looking up open PRs.")) pr <- choose_pr(tr = tr) if (is.null(pr)) { - ui_oops("No open PRs found for {ui_value(tr$repo_spec)}.") + ui_bullets(c("x" = "No open PRs found for {.val {tr$repo_spec}}.")) return(invisible()) } if (min(lengths(pr)) == 0) { - ui_oops("No PR selected, exiting.") + ui_bullets(c("x" = "No PR selected, exiting.")) return(invisible()) } url <- pr$pr_html_url @@ -481,19 +493,22 @@ pr_pause <- function() { # intentionally naive selection of target repo tr <- target_repo(github_get = FALSE, ask = FALSE) - ui_done("Switching back to the default branch.") + ui_bullets(c("v" = "Switching back to the default branch.")) default_branch <- git_default_branch() if (git_branch() == default_branch) { - ui_info(" - Already on this repo's default branch ({ui_value(default_branch)}), \\ - nothing to do.") + ui_bullets(c( + "!" = "Already on this repo's default branch ({.val {default_branch}}), + nothing to do." + )) return(invisible()) } challenge_uncommitted_changes() # TODO: what happens here if offline? check_branch_pulled(use = "pr_pull()") - ui_done("Switching back to default branch ({ui_value(default_branch)}).") + ui_bullets(c( + "v" = "Switching back to default branch ({.val {default_branch}})." + )) gert::git_branch_checkout(default_branch, repo = git_repo()) pr_pull_source_override(tr = tr, default_branch = default_branch) } @@ -548,7 +563,7 @@ pr_clean <- function(number = NULL, If we delete {ui_value(pr_local_branch)}, any work that exists only \\ on this branch may be hard for you to recover. Proceed anyway?")) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } else { @@ -562,20 +577,24 @@ pr_clean <- function(number = NULL, If we delete {ui_value(pr_local_branch)}, this work may be hard \\ for you to recover. Proceed anyway?")) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } } if (git_branch() != default_branch) { - ui_done("Switching back to default branch ({ui_value(default_branch)}).") + ui_bullets(c( + "v" = "Switching back to default branch ({.val {default_branch}})." + )) gert::git_branch_checkout(default_branch, force = TRUE, repo = repo) pr_pull_source_override(tr = tr, default_branch = default_branch) } if (!is.na(pr_local_branch)) { - ui_done("Deleting local {ui_value(pr_local_branch)} branch.") + ui_bullets(c( + "v" = "Deleting local {.val {pr_local_branch}} branch." + )) gert::git_branch_delete(pr_local_branch, repo = repo) } @@ -596,7 +615,7 @@ pr_clean <- function(number = NULL, branches <- gert::git_branch_list(local = TRUE, repo = repo) branches <- branches[!is.na(branches$upstream), ] if (sum(grepl(glue("^refs/remotes/{pr$pr_remote}"), branches$upstream)) == 0) { - ui_done("Removing remote {ui_value(pr$pr_remote)}") + ui_bullets(c("v" = "Removing remote {.val {pr$pr_remote}}.")) gert::git_remote_remove(remote = pr$pr_remote, repo = repo) } invisible() @@ -615,9 +634,9 @@ pr_pull_source_override <- function(tr = NULL, default_branch = NULL) { current_branch <- git_branch() default_branch <- default_branch %||% git_default_branch() if (current_branch != default_branch) { - ui_stop(" - Internal error: pr_pull_source_override() should only be used when on \\ - default branch") + ui_abort(" + Internal error: {.fun pr_pull_source_override} should only be used when on + default branch.") } # guard against mis-configured forks, that have default branch tracking @@ -625,12 +644,13 @@ pr_pull_source_override <- function(tr = NULL, default_branch = NULL) { # TODO: should I just change the upstream tracking branch, i.e. fix it? remref <- glue("{tr$remote}/{default_branch}") if (is_online(tr$host)) { - ui_done("Pulling changes from {ui_value(remref)}") + ui_bullets(c("v" = "Pulling changes from {.val {remref}}.")) git_pull(remref = remref, verbose = FALSE) } else { - ui_info(" - Can't reach {ui_value(tr$host)}, therefore unable to pull changes from \\ - {ui_value(remref)}") + ui_bullets(c( + "!" = "Can't reach {.val {tr$host}}, therefore unable to pull changes from + {.val {remref}}." + )) } } @@ -639,7 +659,7 @@ pr_create <- function() { tracking_branch <- git_branch_tracking(branch) remote <- remref_remote(tracking_branch) remote_dat <- github_remotes(remote, github_get = FALSE) - ui_todo("Create PR at link given below") + ui_bullets(c("_" = "Create PR at link given below.")) view_url(glue_data(remote_dat, "{host_url}/{repo_spec}/compare/{branch}")) } @@ -672,12 +692,12 @@ pr_find <- function(branch = git_branch(), } if (nrow(pr_dat) > 1) { spec <- sub(":", "/", pr_head) - ui_info("Multiple PRs are associated with {ui_value(spec)}.") + ui_bullets(c("!" = "Multiple PRs are associated with {.val {spec}}.")) pr_dat <- choose_pr(pr_dat = pr_dat) if (min(lengths(pr_dat)) == 0) { - ui_stop(" + ui_abort(" One of these PRs must be specified explicitly or interactively: \\ - {ui_value(paste0('#', pr_dat$pr_number))}") + {.or {paste0('#', pr_dat$pr_number)}}.") } } @@ -762,7 +782,7 @@ pr_list <- function(tr = NULL, if (is.null(out$error)) { prs <- out$result } else { - ui_oops("Unable to retrieve PRs for {ui_value(tr$repo_spec)}.") + ui_bullets(c("x" = "Unable to retrieve PRs for {.value {tr$repo_spec}}.")) prs <- NULL } no_prs <- length(prs) == 0 @@ -929,22 +949,25 @@ pr_branch_delete <- function(pr) { pr_remref <- glue_data(pr, "{pr_remote}/{pr_ref}") if (is.null(pr_ref)) { - ui_info(" - PR {ui_value(pr$pr_string)} originated from branch \\ - {ui_value(pr_remref)}, which no longer exists") + ui_bullets(c( + "i" = "PR {.val {pr$pr_string}} originated from branch {.val {pr_remref}}, + which no longer exists." + )) return(invisible(FALSE)) } if (is.na(pr$pr_merged_at)) { - ui_info(" - PR {ui_value(pr$pr_string)} is unmerged, \\ - we will not delete the remote branch {ui_value(pr_remref)}") + ui_bullets(c( + "i" = "PR {.val {pr$pr_string}} is unmerged, we will not delete the + remote branch {.val {pr_remref}}." + )) return(invisible(FALSE)) } - ui_done(" - PR {ui_value(pr$pr_string)} has been merged, \\ - deleting remote branch {ui_value(pr_remref)}") + ui_bullets(c( + "v" = "PR {.val {pr$pr_string}} has been merged, deleting remote branch + {.val {pr_remref}}." + )) # TODO: tryCatch here? gh( "DELETE /repos/{owner}/{repo}/git/refs/{ref}", diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 3da8bbe09..f37d0a793 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -290,3 +290,14 @@ ui_bullets(c("x" = "Things are very wrong.")) Key points: - Put `ui_bullets(c(` on its own line, then all of the bullet items, followed by `))` on its own line. Sometimes I make an exception for a bullet list with exactly one, short bullet item. - Use hard line breaks inside bullet text to comply with surrounding line length. In subsequent lines, use indentation to get you just past the opening `"`. This extraneous white space is later rationalized by cli, which handles wrapping. + - Surround bullet names like `x` and `i` with quotes, even though you don't have to, because it's required for other names, such as `!` or `_` and it's better to be consistent. + - Here's another style that applies to `ui_abort()`, where there's just one, unnamed bullet, but the call doesn't fit on one line. +```{r} +pr <- list(pr_number = 13) +ui_abort(" + The repo or branch where PR #{pr$pr_number} originates seems to have been + deleted.") +``` + + +TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. From 64679927f39f7e21492382b214e503c63f64bc85 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 12:29:58 -0800 Subject: [PATCH 053/111] proj-desc.R --- R/proj-desc.R | 9 ++++----- tests/testthat/_snaps/proj-desc.md | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/R/proj-desc.R b/R/proj-desc.R index b5097ff89..d2e110a7e 100644 --- a/R/proj-desc.R +++ b/R/proj-desc.R @@ -42,13 +42,12 @@ proj_desc_field_update <- function(key, value, overwrite = TRUE, append = FALSE) } if (!overwrite && length(old > 0) && any(old != "")) { - ui_stop( - "{ui_field(key)} has a different value in DESCRIPTION. \\ - Use {ui_code('overwrite = TRUE')} to overwrite." - ) + ui_abort(" + {.field {key}} has a different value in DESCRIPTION. + Use {.code overwrite = TRUE} to overwrite.") } - ui_done("Adding {ui_value(value)} to {ui_field(key)}") + ui_bullets(c("v" = "Adding {.val {value}} to {.field {key}}.")) if (append) { value <- union(old, value) diff --git a/tests/testthat/_snaps/proj-desc.md b/tests/testthat/_snaps/proj-desc.md index 9c787aa0f..8d75d6819 100644 --- a/tests/testthat/_snaps/proj-desc.md +++ b/tests/testthat/_snaps/proj-desc.md @@ -3,17 +3,17 @@ Code proj_desc_field_update("Config/Needs/foofy", "alfa", append = TRUE) Message - v Adding 'alfa' to Config/Needs/foofy + v Adding "alfa" to 'Config/Needs/foofy'. Code proj_desc_field_update("Config/Needs/foofy", "alfa", append = TRUE) proj_desc_field_update("Config/Needs/foofy", "bravo", append = TRUE) Message - v Adding 'bravo' to Config/Needs/foofy + v Adding "bravo" to 'Config/Needs/foofy'. # proj_desc_field_update() works with multiple values Code proj_desc_field_update("Config/Needs/foofy", c("alfa", "bravo"), append = TRUE) Message - v Adding 'alfa', 'bravo' to Config/Needs/foofy + v Adding "alfa" and "bravo" to 'Config/Needs/foofy'. From cb51d5f377cb136a607c19c3f5aa2fc2cbc8b5f3 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 13:16:49 -0800 Subject: [PATCH 054/111] proj.R --- R/proj.R | 34 +++++++++++++++++++-------------- tests/testthat/_snaps/github.md | 4 ++-- tests/testthat/_snaps/proj.md | 12 ++++++------ 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/R/proj.R b/R/proj.R index 60cd28a2c..73b9f6129 100644 --- a/R/proj.R +++ b/R/proj.R @@ -81,16 +81,17 @@ proj_set <- function(path = ".", force = FALSE) { path <- proj_path_prep(path) if (is.null(path) || force) { proj_string <- if (is.null(path)) "" else path - ui_done("Setting active project to {ui_value(proj_string)}") + ui_bullets(c("v" = "Setting active project to {.val {proj_string}}.")) return(proj_set_(path)) } check_path_is_directory(path) new_project <- proj_find(path) if (is.null(new_project)) { - ui_stop(' - Path {ui_path(path)} does not appear to be inside a project or package. - Read more in the help for {ui_code("proj_get()")}.') + ui_abort(c( + "Path {.path {pth(path)}} does not appear to be inside a project or package.", + "Read more in the help for {.fun usethis::proj_get}." + )) } proj_set(path = new_project, force = TRUE) } @@ -103,7 +104,7 @@ proj_path <- function(..., ext = "") { has_absolute_path <- function(x) any(is_absolute_path(x)) dots <- list(...) if (any(map_lgl(dots, has_absolute_path))) { - ui_stop("Paths must be relative to the active project") + ui_abort("Paths must be relative to the active project, not absolute.") } path_norm(path(proj_get(), ..., ext = ext)) @@ -211,21 +212,22 @@ check_is_package <- function(whos_asking = NULL) { return(invisible()) } - message <- "Project {ui_value(project_name())} is not an R package." + message <- "Project {.val {project_name()}} is not an R package." if (!is.null(whos_asking)) { message <- c( - "{ui_code(whos_asking)} is designed to work with packages.", - message + "i" = "{.code {whos_asking}} is designed to work with packages.", + "x" = message ) } - ui_stop(message) + ui_abort(message) } check_is_project <- function() { if (!possibly_in_proj()) { - ui_stop(' - We do not appear to be inside a valid project or package. - Read more in the help for {ui_code("proj_get()")}.') + ui_abort(c( + "We do not appear to be inside a valid project or package.", + "Read more in the help for {.fun usethis::proj_get}." + )) } } @@ -272,14 +274,18 @@ proj_activate <- function(path) { path <- user_path_prep(path) if (rstudio_available() && rstudioapi::hasFun("openProject")) { - ui_done("Opening {ui_path(path, base = NA)} in new RStudio session") + ui_bullets(c( + "v" = "Opening {.path {pth(path, base = NA)}} in new RStudio session." + )) rstudioapi::openProject(path, newSession = TRUE) invisible(FALSE) } else { proj_set(path) rel_path <- path_rel(proj_get(), path_wd()) if (rel_path != ".") { - ui_done("Changing working directory to {ui_path(path, base = NA)}") + ui_bullets(c( + "v" = "Changing working directory to {.path {pth(path, base = NA)}}" + )) setwd(proj_get()) } invisible(TRUE) diff --git a/tests/testthat/_snaps/github.md b/tests/testthat/_snaps/github.md index 6a1160a13..d1be44e33 100644 --- a/tests/testthat/_snaps/github.md +++ b/tests/testthat/_snaps/github.md @@ -3,6 +3,6 @@ Code use_github_links(overwrite = FALSE) Condition - Error: - ! URL has a different value in DESCRIPTION. Use `overwrite = TRUE` to overwrite. + Error in `proj_desc_field_update()`: + x 'URL' has a different value in DESCRIPTION. Use `overwrite = TRUE` to overwrite. diff --git a/tests/testthat/_snaps/proj.md b/tests/testthat/_snaps/proj.md index 97308bbeb..a54abb4d3 100644 --- a/tests/testthat/_snaps/proj.md +++ b/tests/testthat/_snaps/proj.md @@ -3,22 +3,22 @@ Code proj_path(c("/a", "b", "/c")) Condition - Error: - ! Paths must be relative to the active project + Error in `proj_path()`: + x Paths must be relative to the active project, not absolute. --- Code proj_path("/a", "b", "/c") Condition - Error: - ! Paths must be relative to the active project + Error in `proj_path()`: + x Paths must be relative to the active project, not absolute. --- Code proj_path("/a", c("b", "/c")) Condition - Error: - ! Paths must be relative to the active project + Error in `proj_path()`: + x Paths must be relative to the active project, not absolute. From dd3d14f3c98ada7e7aed7accd8261d44ddd48231 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 13:21:02 -0800 Subject: [PATCH 055/111] rcpp.R --- R/rcpp.R | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/R/rcpp.R b/R/rcpp.R index c82ade1f4..0d4df066a 100644 --- a/R/rcpp.R +++ b/R/rcpp.R @@ -92,11 +92,15 @@ use_makevars <- function(settings = NULL) { if (!file_exists(makevars_path) && !file_exists(makevars_win_path)) { write_utf8(makevars_path, makevars_content) file_copy(makevars_path, makevars_win_path) - ui_done("Created {ui_path(makevars_path)} and {ui_path(makevars_win_path)} \\ - with requested compilation settings.") + ui_bullets(c( + "v" = "Created {.path {pth(makevars_path)}} and + {.path {pth(makevars_win_path)}} with requested compilation settings." + )) } else { - ui_todo("Ensure the following Makevars compilation settings are set for both \\ - {ui_path(makevars_path)} and {ui_path(makevars_win_path)}:") + ui_bullets(c( + "_" = "Ensure the following Makevars compilation settings are set for both + {.path {pth(makevars_path)}} and {.path {pth(makevars_win_path)}}:" + )) ui_code_snippet( makevars_content, language = "" From b0fe62013d25fb962073f685d377d80587f855ea Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 13:22:39 -0800 Subject: [PATCH 056/111] readme.R --- R/readme.R | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/R/readme.R b/R/readme.R index 3563e0a7a..cf1a3651c 100644 --- a/R/readme.R +++ b/R/readme.R @@ -60,8 +60,9 @@ use_readme_rmd <- function(open = rlang::is_interactive()) { } if (is_pkg && !data$on_github) { - ui_todo(" - Update {ui_path('README.Rmd')} to include installation instructions.") + ui_bullets(c( + "_" = "Update {.path {pth('README.Rmd')}} to include installation instructions." + )) } if (uses_git()) { @@ -96,8 +97,9 @@ use_readme_md <- function(open = rlang::is_interactive()) { ) if (is_pkg && !data$on_github) { - ui_todo(" - Update {ui_path('README.md')} to include installation instructions.") + ui_bullets(c( + "_" = "Update {.path {pth('README.md')}} to include installation instructions." + )) } invisible(new) From 8c89ffe36638d5b5e96d279b026a33c830511423 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 13:30:06 -0800 Subject: [PATCH 057/111] release.R --- R/release.R | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/R/release.R b/R/release.R index 4f8c2eac5..500da61ab 100644 --- a/R/release.R +++ b/R/release.R @@ -36,11 +36,12 @@ use_release_issue <- function(version = NULL) { check_is_package("use_release_issue()") tr <- target_repo(github_get = TRUE) if (!isTRUE(tr$can_push)) { - ui_line(" - It is very unusual to open a release issue on a repo you can't push to: - {ui_value(tr$repo_spec)}") + ui_bullets(c( + "!" = "It is very unusual to open a release issue on a repo you can't push + to ({.val {tr$repo_spec}})." + )) if (ui_nope("Do you really want to do this?")) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } @@ -286,7 +287,7 @@ get_release_data <- function(tr = target_repo(github_get = TRUE)) { path_first_existing(proj_path(c("CRAN-SUBMISSION", "CRAN-RELEASE"))) if (is.null(cran_submission)) { - ui_done("Using current HEAD commit for the release") + ui_bullets(c("v" = "Using current HEAD commit for the release.")) challenge_non_default_branch() check_branch_pushed() return(list( @@ -345,8 +346,9 @@ get_release_data <- function(tr = target_repo(github_get = TRUE)) { out$Package <- project_name() out$file <- cran_submission - ui_done(" - {ui_path(out$file)} file found, from a submission on {as.Date(out$Date)}") + ui_bullets(c( + "{.path {pth(out$file)}} file found, from a submission on {as.Date(out$Date)}." + )) out } @@ -362,11 +364,12 @@ check_github_has_SHA <- function(SHA = gert::git_info(repo = git_repo())$commit, return() } if (inherits(SHA_GET$error, "http_error_404")) { - ui_stop(" - Can't find SHA {ui_value(substr(SHA, 1, 7))} in {ui_value(tr$repo_spec)}. - Do you need to push?") + ui_abort(c( + "Can't find SHA {.val {substr(SHA, 1, 7)}} in {.val {tr$repo_spec}}.", + "Do you need to push?" + )) } - ui_stop("Internal error: Unexpected error when checking for SHA on GitHub") + ui_abort("Internal error: Unexpected error when checking for SHA on GitHub.") } get_release_news <- function(SHA = gert::git_info(repo = git_repo())$commit, @@ -390,10 +393,11 @@ get_release_news <- function(SHA = gert::git_info(repo = git_repo())$commit, } if (is.null(news)) { - ui_oops(" - Can't find {ui_path('NEWS.md')} in the released package source. - usethis consults this file for release notes. - Call {ui_code('usethis::use_news_md()')} to set this up for the future.") + ui_bullets(c( + "x" = "Can't find {.path {pth('NEWS.md')}} in the released package source.", + "i" = "{.pkg usethis} consults this file for release notes.", + "i" = "Call {.run usethis::use_news_md()} to set this up for the future." + )) if (on_cran) "-- no release notes --" else "Initial release" } else { news_latest(news) @@ -433,7 +437,7 @@ news_latest <- function(lines) { headings <- which(grepl("^#\\s+", lines)) if (length(headings) == 0) { - ui_stop("No top-level headings found in {ui_value('NEWS.md')}") + ui_abort("No top-level headings found in {.path {pth('NEWS.md')}}.") } else if (length(headings) == 1) { news <- lines[seq2(headings + 1, length(lines))] } else { From 42bfc88006d6e4e20400c4968a1129b65795caa3 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 13:34:49 -0800 Subject: [PATCH 058/111] rename-files.R --- R/rename-files.R | 20 ++++++++++++++------ tests/testthat/_snaps/rename-files.md | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/R/rename-files.R b/R/rename-files.R index e856b6d41..5f71605b8 100644 --- a/R/rename-files.R +++ b/R/rename-files.R @@ -24,7 +24,9 @@ rename_files <- function(old, new) { r_old_path <- proj_path("R", old, ext = "R") r_new_path <- proj_path("R", new, ext = "R") if (file_exists(r_old_path)) { - ui_done("Moving {ui_path(r_old_path)} to {ui_path(r_new_path)}") + ui_bullets(c( + "v" = "Moving {.path {pth(r_old_path)}} to {.path {pth(r_new_path)}}." + )) file_move(r_old_path, r_new_path) } @@ -36,7 +38,9 @@ rename_files <- function(old, new) { src_new <- path(path_dir(src_old), src_new_file) if (length(src_old) > 1) { - ui_done("Moving {ui_path(src_old)} to {ui_path(src_new)}") + ui_bullets(c( + "v" = "Moving {.path {pth(src_old)}} to {.path {pth(src_new)}}." + )) file_move(src_old, src_new) } } @@ -57,7 +61,9 @@ rename_files <- function(old, new) { ) new_test <- rename_test(old_test) if (length(old_test) > 0) { - ui_done("Moving {ui_path(old_test)} to {ui_path(new_test)}") + ui_bullets(c( + "v" = "Moving {.path {pth(old_test)}} to {.path {pth(new_test)}}." + )) file_move(old_test, new_test) } snaps_dir <- proj_path("tests", "testthat", "_snaps") @@ -65,7 +71,9 @@ rename_files <- function(old, new) { old_snaps <- dir_ls(snaps_dir, glob = glue("*/{old}.md")) if (length(old_snaps) > 0) { new_snaps <- rename_test(old_snaps) - ui_done("Moving {ui_path(old_snaps)} to {ui_path(new_snaps)}") + ui_bullets(c( + "v" = "Moving {.path {pth(old_snaps)}} to {.path {pth(new_snaps)}}." + )) file_move(old_snaps, new_snaps) } } @@ -81,7 +89,7 @@ rename_files <- function(old, new) { # Remove old context lines context <- grepl("context\\(.*\\)", lines) if (any(context)) { - ui_done("Removing call to {ui_code('context()')}") + ui_bullets(c("v" = "Removing call to {.fun context}.")) lines <- lines[!context] if (lines[[1]] == "") { lines <- lines[-1] @@ -92,7 +100,7 @@ rename_files <- function(old, new) { new_test <- new_test[new_test != test_path] if (length(old_test) > 0) { - ui_done("Updating paths in {ui_path(test_path)}") + ui_bullets(c("v" = "Updating paths in {.path {pth(test_path)}}.")) for (i in seq_along(old_test)) { lines <- gsub(path_file(old_test[[i]]), path_file(new_test[[i]]), lines, fixed = TRUE) diff --git a/tests/testthat/_snaps/rename-files.md b/tests/testthat/_snaps/rename-files.md index 731216fd6..a110ec3f3 100644 --- a/tests/testthat/_snaps/rename-files.md +++ b/tests/testthat/_snaps/rename-files.md @@ -3,5 +3,5 @@ Code rename_files("foo", "bar") Message - v Moving 'src/foo.c', 'src/foo.h' to 'src/bar.c', 'src/bar.h' + v Moving 'src/foo.c' and 'src/foo.h' to 'src/bar.c' and 'src/bar.h'. From 9f5d79b36d19eb0c1cc50e48712a8039136c5da2 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 13:35:59 -0800 Subject: [PATCH 059/111] revdep.R --- R/revdep.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/revdep.R b/R/revdep.R index 2ed41f49c..4621e2d83 100644 --- a/R/revdep.R +++ b/R/revdep.R @@ -20,6 +20,8 @@ use_revdep <- function() { ) ) - ui_todo("Run checks with {ui_code('revdepcheck::revdep_check(num_workers = 4)')}") + ui_bullets(c( + "_" = "Run checks with {.run revdepcheck::revdep_check(num_workers = 4)}." + )) invisible() } From ae18cedc85eb369d1d63306b952dcb143f1886c1 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:01:15 -0800 Subject: [PATCH 060/111] roxygen.R --- R/block.R | 2 +- R/roxygen.R | 47 +++++++++++++------------ tests/testthat/_snaps/data-table.md | 8 ++--- tests/testthat/_snaps/github-actions.md | 2 +- tests/testthat/_snaps/lifecycle.md | 4 +-- tests/testthat/_snaps/roxygen.md | 16 +++++++++ tests/testthat/_snaps/tibble.md | 2 +- tests/testthat/_snaps/tidyverse.md | 8 ++--- tests/testthat/test-roxygen.R | 4 +-- 9 files changed, 56 insertions(+), 37 deletions(-) create mode 100644 tests/testthat/_snaps/roxygen.md diff --git a/R/block.R b/R/block.R index 5f66476dc..01d768a35 100644 --- a/R/block.R +++ b/R/block.R @@ -23,7 +23,7 @@ block_append <- function(desc, value, path, return(FALSE) } - ui_bullets(c("v" = "Adding {desc} to {.path {pth(path)}}.")) + ui_bullets(c("v" = "Adding {.val {desc}} to {.path {pth(path)}}.")) start <- block_lines[[1]] end <- block_lines[[2]] diff --git a/R/roxygen.R b/R/roxygen.R index e02eb30b2..de1727b12 100644 --- a/R/roxygen.R +++ b/R/roxygen.R @@ -18,7 +18,7 @@ use_roxygen_md <- function(overwrite = FALSE) { proj_desc_field_update("Roxygen", "list(markdown = TRUE)", overwrite = FALSE) proj_desc_field_update("RoxygenNote", roxy_ver, overwrite = FALSE) - ui_todo("Run {ui_code('devtools::document()')}") + ui_bullets(c("_" = "Run {.run devtools::document()}.")) return(invisible()) } @@ -32,22 +32,25 @@ use_roxygen_md <- function(overwrite = FALSE) { proj_desc_field_update("Roxygen", "list(markdown = TRUE)", overwrite = TRUE) check_installed("roxygen2md") - ui_todo(" - Run {ui_code('roxygen2md::roxygen2md()')} to convert existing Rd \\ - comments to markdown") + ui_bullets(c( + "_" = "Run {.run roxygen2md::roxygen2md()} to convert existing Rd + comments to markdown." + )) if (!uses_git()) { - ui_todo(" - Consider using Git for greater visibility into and control over the \\ - conversion process") + ui_bullets(c( + "!" = "Consider using Git for greater visibility into and control over + the conversion process." + )) } - ui_todo("Run {ui_code('devtools::document()')} when you're done") + ui_bullets(c("v" = "Run {.run devtools::document()} when you're done.")) return(invisible()) } - ui_stop(" - {ui_path('DESCRIPTION')} already has a {ui_field('Roxygen')} field - Delete it and try again or call {ui_code('use_roxygen_md(overwrite = TRUE)')}") + ui_abort(c( + "DESCRIPTION already has a {.field Roxygen} field.", + "Delete that field and try again or call {.code use_roxygen_md(overwrite = TRUE)}." + )) invisible() } @@ -77,7 +80,7 @@ uses_roxygen <- function() { roxygen_ns_append <- function(tag) { block_append( - glue("{ui_value(tag)}"), + tag, glue("#' {tag}"), path = proj_path(package_doc_path()), block_start = "## usethis namespace: start", @@ -96,18 +99,20 @@ roxygen_ns_show <- function() { } roxygen_remind <- function() { - ui_todo("Run {ui_code('devtools::document()')} to update {ui_path('NAMESPACE')}") + ui_bullets(c( + "_" = "Run {.run devtools::document()} to update {.path {pth('NAMESPACE')}}." + )) TRUE } roxygen_update_ns <- function(load = is_interactive()) { - ui_done("Writing {ui_path('NAMESPACE')}") + ui_bullets(c("v" = "Writing {.path {pth('NAMESPACE')}}.")) utils::capture.output( suppressMessages(roxygen2::roxygenise(proj_get(), "namespace")) ) if (load) { - ui_done("Loading {project_name()}") + ui_bullets(c("v" = "Loading {.pkg {project_name()}}.")) pkgload::load_all(path = proj_get(), quiet = TRUE) } @@ -123,11 +128,9 @@ check_uses_roxygen <- function(whos_asking) { return(invisible()) } - ui_stop( - " - Project {ui_value(project_name())} does not use roxygen2. - {ui_code(whos_asking)} can not work without it. - You might just need to run {ui_code('devtools::document()')} once, then try again. - " - ) + ui_abort(c( + "Package {.pkg {project_name()}} does not use roxygen2.", + "{.fun {whos_asking}} can not work without it.", + "You might just need to run {.run devtools::document()} once, then try again." + )) } diff --git a/tests/testthat/_snaps/data-table.md b/tests/testthat/_snaps/data-table.md index 517edd4ac..4123fc57c 100644 --- a/tests/testthat/_snaps/data-table.md +++ b/tests/testthat/_snaps/data-table.md @@ -21,10 +21,10 @@ ! data.table should be in 'Imports' or 'Suggests', not 'Depends'! v Removing data.table from 'Depends'. v Adding data.table to 'Imports' field in DESCRIPTION. - v Adding '@importFrom data.table data.table', '@importFrom data.table :=', - '@importFrom data.table .SD', '@importFrom data.table .BY', '@importFrom - data.table .N', '@importFrom data.table .I', '@importFrom data.table .GRP', - '@importFrom data.table .NGRP', '@importFrom data.table .EACHI' to + v Adding "@importFrom data.table data.table", "@importFrom data.table :=", + "@importFrom data.table .SD", "@importFrom data.table .BY", "@importFrom + data.table .N", "@importFrom data.table .I", "@importFrom data.table .GRP", + "@importFrom data.table .NGRP", and "@importFrom data.table .EACHI" to 'R/{TESTPKG}-package.R'. --- diff --git a/tests/testthat/_snaps/github-actions.md b/tests/testthat/_snaps/github-actions.md index 018ecf6bb..7d1aa714d 100644 --- a/tests/testthat/_snaps/github-actions.md +++ b/tests/testthat/_snaps/github-actions.md @@ -11,7 +11,7 @@ v Saving 'r-lib/actions/examples/check-full.yaml@v2' to '.github/workflows/R-CMD-check.yaml' [ ] Learn more at . - v Adding R-CMD-check badge to 'README.md'. + v Adding "R-CMD-check badge" to 'README.md'. # use_github_action() still errors in non-interactive environment diff --git a/tests/testthat/_snaps/lifecycle.md b/tests/testthat/_snaps/lifecycle.md index d93e9c737..773bdd957 100644 --- a/tests/testthat/_snaps/lifecycle.md +++ b/tests/testthat/_snaps/lifecycle.md @@ -5,9 +5,9 @@ Message v Adding lifecycle to 'Imports' field in DESCRIPTION. [ ] Refer to functions with `lifecycle::fun()`. - v Adding '@importFrom lifecycle deprecated' to + v Adding "@importFrom lifecycle deprecated" to 'R/{TESTPKG}-package.R'. - v Writing 'NAMESPACE' + v Writing 'NAMESPACE'. v Creating 'man/figures/' v Copied SVG badges to 'man/figures/'. [ ] Add badges in documentation topics by inserting a line like this: diff --git a/tests/testthat/_snaps/roxygen.md b/tests/testthat/_snaps/roxygen.md new file mode 100644 index 000000000..ec003d7df --- /dev/null +++ b/tests/testthat/_snaps/roxygen.md @@ -0,0 +1,16 @@ +# use_package_doc() compatible with roxygen_ns_append() + + Code + use_package_doc() + Message + v Writing 'R/{TESTPKG}-package.R' + +--- + + Code + roxygen_ns_append("test") + Message + v Adding "test" to 'R/{TESTPKG}-package.R'. + Output + [1] TRUE + diff --git a/tests/testthat/_snaps/tibble.md b/tests/testthat/_snaps/tibble.md index cdfd223c0..bdaa5325d 100644 --- a/tests/testthat/_snaps/tibble.md +++ b/tests/testthat/_snaps/tibble.md @@ -4,7 +4,7 @@ use_tibble() Message v Adding tibble to 'Imports' field in DESCRIPTION. - v Adding '@importFrom tibble tibble' to 'R/{TESTPKG}-package.R'. + v Adding "@importFrom tibble tibble" to 'R/{TESTPKG}-package.R'. * Document a returned tibble like so: #' @return a [tibble][tibble::tibble-package] diff --git a/tests/testthat/_snaps/tidyverse.md b/tests/testthat/_snaps/tidyverse.md index 7a5de4d09..b29c19995 100644 --- a/tests/testthat/_snaps/tidyverse.md +++ b/tests/testthat/_snaps/tidyverse.md @@ -8,10 +8,10 @@ v Adding cli to 'Imports' field in DESCRIPTION. v Adding glue to 'Imports' field in DESCRIPTION. v Adding withr to 'Imports' field in DESCRIPTION. - v Adding '@import rlang' to 'R/{TESTPKG}-package.R'. - v Adding '@importFrom glue glue' to 'R/{TESTPKG}-package.R'. - v Adding '@importFrom lifecycle deprecated' to + v Adding "@import rlang" to 'R/{TESTPKG}-package.R'. + v Adding "@importFrom glue glue" to 'R/{TESTPKG}-package.R'. + v Adding "@importFrom lifecycle deprecated" to 'R/{TESTPKG}-package.R'. - v Writing 'NAMESPACE' + v Writing 'NAMESPACE'. v Writing 'R/import-standalone-purrr.R' diff --git a/tests/testthat/test-roxygen.R b/tests/testthat/test-roxygen.R index 89ffc86ea..b3161bfa9 100644 --- a/tests/testthat/test-roxygen.R +++ b/tests/testthat/test-roxygen.R @@ -2,8 +2,8 @@ test_that("use_package_doc() compatible with roxygen_ns_append()", { create_local_package() withr::local_options(list(usethis.quiet = FALSE, crayon.enabled = FALSE)) - expect_message(use_package_doc()) - expect_message(roxygen_ns_append("test"), "Adding 'test'") + expect_snapshot(use_package_doc(), transform = scrub_testpkg) + expect_snapshot(roxygen_ns_append("test"), transform = scrub_testpkg) expect_silent(roxygen_ns_append("test")) }) From 5075412e315d5e4b1f83fda70fecaf51297128dc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 13:57:14 -0800 Subject: [PATCH 061/111] rprofile.R --- R/rprofile.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/R/rprofile.R b/R/rprofile.R index 4395fa1aa..68f176a5e 100644 --- a/R/rprofile.R +++ b/R/rprofile.R @@ -39,10 +39,10 @@ use_devtools <- function() { use_rprofile_package <- function(package) { check_installed(package) - ui_todo( - "Include this code in {ui_value('.Rprofile')} to make \\ - {ui_field(package)} available in all interactive sessions." - ) + ui_bullets(c( + "_" = "Include this code in {.path .Rprofile} to make {.pkg {package}} + available in all interactive sessions:" + )) ui_code_snippet(" if (interactive()) {{ suppressMessages(require({package})) @@ -53,9 +53,9 @@ use_rprofile_package <- function(package) { #' @rdname rprofile-helper #' @export use_partial_warnings <- function() { - ui_todo( - "Include this code in {ui_path('.Rprofile')} to warn on partial matches." - ) + ui_bullets(c( + "_" = "Include this code in {.path .Rprofile} to warn on partial matches:" + )) ui_code_snippet(" options( warnPartialMatchArgs = TRUE, From 83161d70d9c786e94ab0bb3e78eba4612e1e8ab7 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:59:57 -0800 Subject: [PATCH 062/111] rstudio.R --- R/git.R | 2 +- R/rstudio.R | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/R/git.R b/R/git.R index 040486778..6957e8715 100644 --- a/R/git.R +++ b/R/git.R @@ -23,7 +23,7 @@ use_git <- function(message = "Initial commit") { } if (needs_init) { - restart_rstudio("A restart of RStudio is required to activate the Git pane") + restart_rstudio("A restart of RStudio is required to activate the Git pane.") } invisible(TRUE) diff --git a/R/rstudio.R b/R/rstudio.R index 427ee5055..f3cceda3d 100644 --- a/R/rstudio.R +++ b/R/rstudio.R @@ -167,7 +167,7 @@ restart_rstudio <- function(message = NULL) { } if (!is.null(message)) { - ui_todo(message) + ui_bullets(message) } if (!rstudioapi::hasFun("openProject")) { @@ -225,7 +225,7 @@ use_rstudio_preferences <- function(...) { next } - ui_done("Setting RStudio preference {ui_field(name)} to {ui_value(val)}.") + ui_bullets(c("v" = "Setting RStudio preference {.field {name}} to {.val {val}}.")) json[[name]] <- val } From 4eab6d4b95a70e858a5ac8a08580e43fccebb89d Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:14:38 -0800 Subject: [PATCH 063/111] sitrep.R --- R/sitrep.R | 76 ++++++++++++++++++++++++------------------------------ 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/R/sitrep.R b/R/sitrep.R index 316c2bb8d..15e64309d 100644 --- a/R/sitrep.R +++ b/R/sitrep.R @@ -49,60 +49,52 @@ print.sitrep <- function(x, ...) { x[["active_rstudio_proj"]] != x[["active_usethis_proj"]] if (rstudio_available() && !rstudio_proj_is_active) { - ui_todo( - " - You are working in RStudio, but are not in an RStudio Project. - A Project-based workflow offers many advantages. Read more at: - {ui_field('https://support.rstudio.com/hc/en-us/articles/200526207-Using-Projects')} - {ui_field('https://whattheyforgot.org/project-oriented-workflow.html')} - " - ) + ui_bullets(c( + "i" = "You are working in RStudio, but are not in an RStudio Project.", + "i" = "A Project-based workflow offers many advantages. Read more at:", + " " = "{.url https://support.rstudio.com/hc/en-us/articles/200526207-Using-Projects}", + " " = "{.url https://rstats.wtf/projects}" + )) } if (!usethis_proj_is_active) { - ui_todo( - " - There is currently no active usethis project. - usethis attempts to activate a project upon first need. - Call {ui_code('proj_get()')} to initiate project discovery. - Call {ui_code('proj_set(\"path/to/project\")')} or \\ - {ui_code('proj_activate(\"path/to/project\")')} to provide - an explicit path. - " - ) + ui_bullets(c( + "i" = "There is currently no active {.pkg usethis} project.", + "i" = "{.pkg usethis} attempts to activate a project upon first need.", + "_" = "Call {.run usethis::proj_get()} to initiate project discovery.", + "_" = 'Call {.code proj_set("path/to/project")} or + {.code proj_activate("path/to/project")} to provide an explicit + path.' + )) } if (usethis_proj_is_not_wd) { - ui_todo( - " - Your working directory is not the same as the active usethis project. - Set working directory to the project: {ui_code('setwd(proj_get())')} - Set project to working directory: {ui_code('proj_set(getwd())')} - " - ) + ui_bullets(c( + "i" = "Your working directory is not the same as the active usethis project.", + "_" = "Set working directory to the project: {.run setwd(proj_get())}.", + "_" = "Set project to working directory: {.run usethis::proj_set(getwd())}." + )) } if (rstudio_proj_is_not_wd) { - ui_todo( - " - Your working directory is not the same as the active RStudio Project. - Set working directory to the Project: {ui_code('setwd(rstudioapi::getActiveProject())')} - " - ) + ui_bullets(c( + "i" = "Your working directory is not the same as the active RStudio Project.", + "_" = "Set working directory to the Project: + {.run setwd(rstudioapi::getActiveProject())}." + )) } if (usethis_proj_is_not_rstudio_proj) { - ui_todo( - " - Your active RStudio Project is not the same as the active usethis project. - Set usethis project to RStudio Project: \\ - {ui_code('proj_set(rstudioapi::getActiveProject())')} - Restart RStudio in the usethis project: \\ - {ui_code('rstudioapi::openProject(proj_get())')} - Open the usethis project in a new instance of RStudio: \\ - {ui_code('proj_activate(proj_get())')} - " - ) + ui_bullets(c( + "i" = "Your active RStudio Project is not the same as the active + {.pkg usethis} project.", + "_" = "Set active {.pkg usethis} project to RStudio Project: + {.run usethis::proj_set(rstudioapi::getActiveProject())}.", + "_" = "Restart RStudio in the active {.pkg usethis} project: + {.run rstudioapi::openProject(usethis::proj_get())}.", + "_" = "Open the active {.pkg usethis} project in a new instance of RStudio: + {.run usethis::proj_activate(usethis::proj_get())}." + )) } invisible(x) From 747629c88838ed71bf9cd1eb80727722593771ad Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:15:51 -0800 Subject: [PATCH 064/111] spelling.R --- R/spelling.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/spelling.R b/R/spelling.R index f53ad20f0..b62942c02 100644 --- a/R/spelling.R +++ b/R/spelling.R @@ -24,5 +24,5 @@ use_spell_check <- function(vignettes = TRUE, spelling::spell_check_setup( pkg = proj_get(), vignettes = vignettes, lang = lang, error = error ) - ui_todo("Run {ui_code('devtools::check()')} to trigger spell check") + ui_bullets(c("_" = "Run {.run devtools::check()} to trigger spell check.")) } From 4b40d2708fd84513707e0fc73f74ab505d1e49d9 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:17:25 -0800 Subject: [PATCH 065/111] template.R --- R/template.R | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/R/template.R b/R/template.R index d001b7c17..c8504f93a 100644 --- a/R/template.R +++ b/R/template.R @@ -69,10 +69,9 @@ find_template <- function(template_name, package = "usethis") { error = function(e) "" ) if (identical(path, "")) { - ui_stop( - "Could not find template {ui_value(template_name)} \\ - in package {ui_value(package)}." - ) + ui_abort(" + Could not find template {.val {template_name}} in package {.pkg package} + package.") } path } From 1b5a8d03088e959e022ab781091a7fd3eece9f89 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:21:12 -0800 Subject: [PATCH 066/111] test.R --- R/test.R | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/R/test.R b/R/test.R index f317d8b08..057217b2b 100644 --- a/R/test.R +++ b/R/test.R @@ -21,16 +21,17 @@ use_testthat <- function(edition = NULL, parallel = FALSE) { use_testthat_impl(edition, parallel = parallel) - ui_todo( - "Call {ui_code('use_test()')} to initialize a basic test file and open it \\ - for editing." - ) + ui_bullets(c( + "_" = "Call {.run usethis::use_test()} to initialize a basic test file and + open it for editing." + )) } use_testthat_impl <- function(edition = NULL, parallel = FALSE) { check_installed("testthat") if (utils::packageVersion("testthat") < "2.1.0") { - ui_stop("testthat 2.1.0 or greater needed. Please install before re-trying") + ui_abort(" + {.pkg testthat} 2.1.0 or greater needed. Please install before re-trying") } if (is_package()) { @@ -46,7 +47,7 @@ use_testthat_impl <- function(edition = NULL, parallel = FALSE) { } } else { if (!is.null(edition)) { - ui_stop("Can't declare testthat edition outside of a package") + ui_abort("Can't declare {.pkg testthat} edition outside of a package.") } } @@ -70,11 +71,13 @@ check_edition <- function(edition = NULL) { version } else { if (!is.numeric(edition) || length(edition) != 1) { - ui_stop("`edition` must be a single number") + ui_abort("{.arg edition} must be a single number.") } if (edition > version) { vers <- utils::packageVersion("testthat") - ui_stop("`edition` ({edition}) not available in installed testthat ({vers})") + ui_abort(" + {.var edition} ({edition}) not available in installed verion of + {.pkg testthat} ({vers}).") } as.integer(edition) } From d7c2a896dcbcc1da594983aefaf3d8157f156c58 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:22:08 -0800 Subject: [PATCH 067/111] tibble.R --- R/tibble.R | 2 +- tests/testthat/_snaps/tibble.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/tibble.R b/R/tibble.R index a0f8e4e07..abb00b5fb 100644 --- a/R/tibble.R +++ b/R/tibble.R @@ -33,7 +33,7 @@ use_tibble <- function() { created <- use_import_from("tibble", "tibble") - ui_todo("Document a returned tibble like so:") + ui_bullets(c("_" = "Document a returned tibble like so:")) ui_code_snippet( "#' @return a [tibble][tibble::tibble-package]", language = "", diff --git a/tests/testthat/_snaps/tibble.md b/tests/testthat/_snaps/tibble.md index bdaa5325d..de3efaf45 100644 --- a/tests/testthat/_snaps/tibble.md +++ b/tests/testthat/_snaps/tibble.md @@ -5,6 +5,6 @@ Message v Adding tibble to 'Imports' field in DESCRIPTION. v Adding "@importFrom tibble tibble" to 'R/{TESTPKG}-package.R'. - * Document a returned tibble like so: + [ ] Document a returned tibble like so: #' @return a [tibble][tibble::tibble-package] From a1f1d0e84702f2c91a258e4bcb12dc77286e5bfa Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:29:32 -0800 Subject: [PATCH 068/111] tidyverse.R --- R/tidyverse.R | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/R/tidyverse.R b/R/tidyverse.R index bdf7fc726..4daa46b88 100644 --- a/R/tidyverse.R +++ b/R/tidyverse.R @@ -85,13 +85,15 @@ create_tidy_package <- function(path, copyright_holder = NULL) { use_cran_comments(open = FALSE) - ui_todo("In the new package, remember to do:") - ui_todo("{ui_code('use_git()')}") - ui_todo("{ui_code('use_github()')}") - ui_todo("{ui_code('use_tidy_github()')}") - ui_todo("{ui_code('use_tidy_github_actions()')}") - ui_todo("{ui_code('use_tidy_github_labels()')}") - ui_todo("{ui_code('use_pkgdown_github_pages()')}") + ui_bullets(c( + "i" = "In the new package, remember to do:", + "_" = "{.run usethis::use_git()}", + "_" = "{.run usethis::use_github()}", + "_" = "{.run usethis::use_tidy_github()}", + "_" = "{.run usethis::use_tidy_github_actions()}", + "_" = "{.run usethis::use_tidy_github_labels()}", + "_" = "{.run usethis::use_pkgdown_github_pages()}" + )) proj_activate(path) } @@ -231,8 +233,10 @@ use_tidy_style <- function(strict = TRUE) { strict = strict ) } - ui_line() - ui_done("Styled project according to the tidyverse style guide") + ui_bullets(c( + " " = "", + "v" = "Styled project according to the tidyverse style guide." + )) invisible(styled) } @@ -297,9 +301,10 @@ use_tidy_thanks <- function(repo_spec = NULL, from_timestamp <- as_timestamp(repo_spec, x = from) %||% "2008-01-01" to_timestamp <- as_timestamp(repo_spec, x = to) - ui_done(" - Looking for contributors from {as.Date(from_timestamp)} to \\ - {to_timestamp %||% 'now'}") + ui_bullets(c( + "i" = "Looking for contributors from {as.Date(from_timestamp)} to + {to_timestamp %||% 'now'}." + )) res <- gh::gh( "/repos/{owner}/{repo}/issues", @@ -310,7 +315,7 @@ use_tidy_thanks <- function(repo_spec = NULL, .limit = Inf ) if (length(res) < 1) { - ui_oops("No matching issues/PRs found") + ui_bullets(c("x" = "No matching issues/PRs found.")) return(invisible()) } @@ -324,14 +329,14 @@ use_tidy_thanks <- function(repo_spec = NULL, res <- res[creation_time(res) <= as.POSIXct(to_timestamp)] } if (length(res) == 0) { - ui_line("No matching issues/PRs found.") + ui_bullets(c("x" = "No matching issues/PRs found.")) return(invisible()) } contributors <- sort(unique(map_chr(res, c("user", "login")))) contrib_link <- glue("[@{contributors}](https://github.com/{contributors})") - ui_done("Found {length(contributors)} contributors:") + ui_bullets(c("v" = "Found {length(contributors)} contributors:")) ui_code_snippet( glue_collapse(contrib_link, sep = ", ", last = ", and ") + glue("."), language = "" @@ -350,7 +355,7 @@ as_timestamp <- function(repo_spec, x = NULL) { if (inherits(as_POSIXct, "POSIXct")) { return(x) } - ui_done("Resolving timestamp for ref {ui_value(x)}") + ui_bullets(c("v" = "Resolving timestamp for ref {.val {x}}.")) ref_df(repo_spec, refs = x)$timestamp } @@ -414,7 +419,7 @@ base_and_recommended <- function() { #' @export use_tidy_logo <- function(geometry = "240x278", retina = TRUE) { if (!is_posit_pkg()) { - ui_stop("This function can only be used for Posit packages") + ui_abort("This function only works for Posit packages.") } tf <- withr::local_tempfile(fileext = ".png") From 4bd21a1a78ba7bc26324e376cb1dcc418f66a142 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:31:19 -0800 Subject: [PATCH 069/111] upkeep.R --- R/upkeep.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/upkeep.R b/R/upkeep.R index 51db4fd14..ac55a0d83 100644 --- a/R/upkeep.R +++ b/R/upkeep.R @@ -29,11 +29,12 @@ make_upkeep_issue <- function(year, tidy) { tr <- target_repo(github_get = TRUE) if (!isTRUE(tr$can_push)) { - ui_line(" - It is very unusual to open an upkeep issue on a repo you can't push to: - {ui_value(tr$repo_spec)}") + ui_bullets(c( + "!" = "It is very unusual to open an upkeep issue on a repo you can't push + to ({.val {tr$repo_spec}})." + )) if (ui_nope("Do you really want to do this?")) { - ui_oops("Cancelling.") + ui_bullets(c("x" = "Cancelling.")) return(invisible()) } } From b9311c161cac4017f0bfa5cd3764201713a6dcb0 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:35:31 -0800 Subject: [PATCH 070/111] use_github_file.R --- R/use_github_file.R | 8 +++++--- tests/testthat/_snaps/github-actions.md | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/R/use_github_file.R b/R/use_github_file.R index f5f131ced..afba90e27 100644 --- a/R/use_github_file.R +++ b/R/use_github_file.R @@ -72,7 +72,9 @@ use_github_file <- function(repo_spec, ref_string <- if (is.null(ref)) "" else glue("@{ref}") github_string <- glue("{repo_spec}/{path}{ref_string}") - ui_done("Saving {ui_path(github_string)} to {ui_path(save_as)}") + ui_bullets(c( + "v" = "Saving {.val {github_string}} to {.path {pth(save_as)}}." + )) lines <- read_github_file( repo_spec = repo_spec, @@ -137,13 +139,13 @@ parse_file_url <- function(x) { # TODO: generalize here for GHE hosts that don't include 'github' if (!grepl("github", dat$host)) { - ui_stop("URL doesn't seem to be associated with GitHub.") + ui_abort("URL doesn't seem to be associated with GitHub.") } if (!grepl("^(raw[.])?github", dat$host) || !nzchar(dat$fragment) || (grepl("^github", dat$host) && !grepl("^/blob/", dat$fragment))) { - ui_stop("Can't parse the URL provided via {ui_code('repo_spec')}.") + ui_abort("Can't parse the URL provided via {.arg repo_spec}.") } out$parsed <- TRUE diff --git a/tests/testthat/_snaps/github-actions.md b/tests/testthat/_snaps/github-actions.md index 7d1aa714d..b1c1b40db 100644 --- a/tests/testthat/_snaps/github-actions.md +++ b/tests/testthat/_snaps/github-actions.md @@ -8,7 +8,8 @@ v Adding '^\\.github$' to '.Rbuildignore' v Adding '*.html' to '.github/.gitignore' v Creating '.github/workflows/' - v Saving 'r-lib/actions/examples/check-full.yaml@v2' to '.github/workflows/R-CMD-check.yaml' + v Saving "r-lib/actions/examples/check-full.yaml@v2" to + '.github/workflows/R-CMD-check.yaml'. [ ] Learn more at . v Adding "R-CMD-check badge" to 'README.md'. From a6d8e97dc8092f3110de47fee3038d680185d8c2 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:43:21 -0800 Subject: [PATCH 071/111] use_import_fun.R --- R/use_import_from.R | 13 +++++++------ tests/testthat/_snaps/use_import_from.md | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/R/use_import_from.R b/R/use_import_from.R index d31a702e8..0b41d7f7a 100644 --- a/R/use_import_from.R +++ b/R/use_import_from.R @@ -23,7 +23,7 @@ #' } use_import_from <- function(package, fun, load = is_interactive()) { if (!is_string(package)) { - ui_stop("{ui_code('package')} must be a single string") + ui_abort("{.arg package} must be a single string.") } check_is_package("use_import_from()") check_uses_roxygen("use_import_from()") @@ -49,8 +49,8 @@ check_fun_exists <- function(package, fun) { if (exists(fun, envir = asNamespace(package))) { return() } - name <- paste0(package, "::", fun, "()") - ui_stop("Can't find {ui_code(name)}") + name <- paste0(package, "::", fun) + ui_abort("Can't find {.fun {name}}.") } check_has_package_doc <- function(whos_asking) { @@ -58,6 +58,7 @@ check_has_package_doc <- function(whos_asking) { return(invisible(TRUE)) } + # TODO: update this msg when dealing with ui_yeah() msg <- c( "{ui_code(whos_asking)} requires package-level documentation.", "Would you like to add it now?" @@ -65,9 +66,9 @@ check_has_package_doc <- function(whos_asking) { if (is_interactive() && ui_yeah(msg)) { use_package_doc() } else { - ui_stop(c( - "{ui_code(whos_asking)} requires package docs", - "You can add it by running {ui_code('use_package_doc()')}" + ui_abort(c( + "{.fun {whos_asking}} requires package-level documentation.", + "You can add it by running {.run usethis::use_package_doc()}." )) } diff --git a/tests/testthat/_snaps/use_import_from.md b/tests/testthat/_snaps/use_import_from.md index 669911af6..857fe0ef3 100644 --- a/tests/testthat/_snaps/use_import_from.md +++ b/tests/testthat/_snaps/use_import_from.md @@ -11,18 +11,18 @@ Code use_import_from(1) Condition - Error: - ! `package` must be a single string + Error in `use_import_from()`: + x `package` must be a single string. Code use_import_from(c("tibble", "rlang")) Condition - Error: - ! `package` must be a single string + Error in `use_import_from()`: + x `package` must be a single string. Code use_import_from("tibble", "pool_noodle") Condition Error in `map2()`: i In index: 1. - Caused by error: - ! Can't find `tibble::pool_noodle()` + Caused by error in `.f()`: + x Can't find `tibble::pool_noodle()`. From 8b0ccad8ce245df4b845fc9325e44423a9911388 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sat, 24 Feb 2024 14:58:20 -0800 Subject: [PATCH 072/111] usethis-defunct.R --- R/usethis-defunct.R | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/R/usethis-defunct.R b/R/usethis-defunct.R index 7b1cbf327..6d5877df4 100644 --- a/R/usethis-defunct.R +++ b/R/usethis-defunct.R @@ -19,15 +19,10 @@ pr_pull_upstream <- function() { #' @rdname pr_pull_upstream #' @export pr_sync <- function() { - details <- glue(" - Sync a PR with: - * {ui_code('pr_pull()')} - * {ui_code('pr_merge_main()')} - * {ui_code('pr_push()')}") lifecycle::deprecate_stop( when = "2.0.0", what = "pr_sync()", - details = details + details = "Sync a PR with:`pr_pull(); pr_merge_main(); pr_push();`" ) } @@ -65,9 +60,9 @@ browse_github_pat <- function(...) { #' @export github_token <- function() { details <- glue(" - Call {ui_code('gh::gh_token()')} to retrieve a GitHub personal access token - Call {ui_code('gh_token_help()')} if you need help getting or configuring \\ - your token") + Call `gh::gh_token()` to retrieve a GitHub personal access token. + Call `usethis::gh_token_help()` if you need help getting or configuring \\ + your token.") lifecycle::deprecate_stop( "2.0.0", what = "github_token()", @@ -190,9 +185,9 @@ deprecate_warn_host <- function(whos_asking, details = NULL) { what <- glue("{whos_asking}(host = )") host_explanation <- glue(" - usethis now determines the {ui_code('host')} from the current project's \\ + usethis now determines the `host` from the current project's \\ Git remotes. - The {ui_code('host')} argument is ignored and will eventually be removed.") + The `host` argument is ignored and will eventually be removed.") lifecycle::deprecate_warn( "2.0.0", @@ -209,8 +204,7 @@ deprecate_warn_auth_token <- function(whos_asking, details = NULL) { usethis now delegates token lookup to the gh package, which retrieves \\ credentials based on the targeted host URL. This URL is determined by the current project's Git remotes. - The {ui_code('auth_token')} argument is ignored and will eventually be \\ - removed.") + The `auth_token` argument is ignored and will eventually be removed.") lifecycle::deprecate_warn( "2.0.0", @@ -226,8 +220,7 @@ deprecate_warn_repo_spec <- function(whos_asking, details = NULL) { repo_spec_explanation <- glue(" usethis now consults the current project's Git remotes to determine the \\ target repo. - The {ui_code('repo_spec')} argument is ignored and will eventually be \\ - removed.") + The `repo_spec` argument is ignored and will eventually be removed.") lifecycle::deprecate_warn( "2.0.0", @@ -412,9 +405,8 @@ use_github_action_check_full <- function(save_as = "R-CMD-check.yaml", details <- glue(" It is overkill for the vast majority of R packages. The \"check-full\" workflow is among those configured by \\ - {ui_code('use_tidy_github_actions()')}. - If you really want it, request it by name with \\ - {ui_code('use_github_action()')}.") + `use_tidy_github_actions()`. + If you really want it, request it by name with `use_github_action()`.") lifecycle::deprecate_stop( "2.1.0", "use_github_action_check_full()", From fd254f8919e02392612e8385bcad0c4cbd9068ca Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 08:16:07 -0800 Subject: [PATCH 073/111] Rename to ui_legacy_bullet(); catch up on note-taking --- R/git.R | 2 +- R/ui.R | 10 +++++----- vignettes/articles/ui-cli-conversion.Rmd | 23 ++++++++++++++++------- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/R/git.R b/R/git.R index 6957e8715..adf23b25f 100644 --- a/R/git.R +++ b/R/git.R @@ -424,7 +424,7 @@ git_sitrep <- function(tool = c("git", "github"), cli::cli_text("Project:") } - purrr::walk(format(cfg), ui_bullet) + purrr::walk(format(cfg), ui_legacy_bullet) } invisible() diff --git a/R/ui.R b/R/ui.R index 47807a1bf..124859743 100644 --- a/R/ui.R +++ b/R/ui.R @@ -63,7 +63,7 @@ ui_line <- function(x = character(), .envir = parent.frame()) { ui_todo <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") x <- glue(x, .envir = .envir) - ui_bullet(x, crayon::red(cli::symbol$bullet)) + ui_legacy_bullet(x, crayon::red(cli::symbol$bullet)) } #' @rdname ui @@ -71,7 +71,7 @@ ui_todo <- function(x, .envir = parent.frame()) { ui_done <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") x <- glue(x, .envir = .envir) - ui_bullet(x, crayon::green(cli::symbol$tick)) + ui_legacy_bullet(x, crayon::green(cli::symbol$tick)) } #' @rdname ui @@ -79,7 +79,7 @@ ui_done <- function(x, .envir = parent.frame()) { ui_oops <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") x <- glue(x, .envir = .envir) - ui_bullet(x, crayon::red(cli::symbol$cross)) + ui_legacy_bullet(x, crayon::red(cli::symbol$cross)) } #' @rdname ui @@ -87,7 +87,7 @@ ui_oops <- function(x, .envir = parent.frame()) { ui_info <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") x <- glue(x, .envir = .envir) - ui_bullet(x, crayon::yellow(cli::symbol$info)) + ui_legacy_bullet(x, crayon::yellow(cli::symbol$info)) } #' @param copy If `TRUE`, the session is interactive, and the clipr package @@ -280,7 +280,7 @@ indent <- function(x, first = " ", indent = first) { paste0(first, x) } -ui_bullet <- function(x, bullet = cli::symbol$bullet) { +ui_legacy_bullet <- function(x, bullet = cli::symbol$bullet) { bullet <- paste0(bullet, " ") x <- indent(x, bullet, " ") ui_inform(x) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index f37d0a793..c7b2c5b67 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -19,7 +19,8 @@ I'll start with a review of the current (soon to be legacy) `usethis::ui_*()` fu ## Block styles -The block styles mostly exist to produce bulleted output with a specific symbol, using a specific color, that can be shutdown package-wide via the `usethis.quiet` option. +The block styles mostly exist to produce bulleted output with a specific symbol, using a specific color. +All of this output can be turned off package-wide via the `usethis.quiet` option. ```{r} f <- function() { @@ -55,21 +56,26 @@ A direct translation looks something like this: | `ui_done()` | `v` | perfect match | | `ui_oops()` | `x` | perfect match | | `ui_info()` | `i` | blue (default) -\> yellow | -| `ui_line()` | (unnamed) | might be perfect match? although sometimes ui_line is used just to get a blank line | +| `ui_line()` | (unnamed) | sort of a perfect match? although sometimes ui_line is used just to get a blank line | -I'm experimenting with a few more tweaks related to TODOs: +The overall conversion plan is to switch to the new `ui_bullets()`, which is a wrapper around `cli::cli_bullets()`, that adds a few features: +* Early exit, without emitting messages, if the `usethis.quiet` option is `TRUE`. +* A usethis theme, that changes the color of certain bullet styles and adds a new style for todo's. + +Summary of what I've done for todo's: + +- Introduce a new bullet shortcode for a todo. Proposal: `_` (instead of `*`), which seems to be the best single ascii character that evokes a place to check something off. - Use `cli::symbol$checkbox_off` as the symbol (instead of a generic bullet). I guess it will continue to be red. -- Introduce a new bullet shortcode for a TODO. Proposal: `_` (instead of `*`), which seems to be the best single ascii character that evokes a place to check something off. -That just leaves `code_block()`, which is pretty different. -It exists to put some code on the screen and optionally place it on the clipboard. +In terms of the block styles, that just leaves `ui_code_block()`, which is pretty different. +`ui_code_block()` is used to put some code on the screen and optionally place it on the clipboard. I have created a new function, `ui_code_snippet()` that is built around `cli::code_block()`. Main observations: * `cli::code_block(language = "R")` applies syntax highlighting and hyperlinks (e.g. to function help topics) to R code, which is cool. Therefore the `language` argument is also exposed in `ui_code_snippet()`, defaulting to `"R"`. Use `""` for anything that's not R code: ```{r} -#| eval: false +#| eval: true ui_code_snippet("x <- 1 + 2") ui_code_snippet("#include ", language = "") ``` @@ -109,6 +115,7 @@ is_quiet <- function() { `ui_todo()`, `ui_done()`, `ui_oops()`, `ui_info()` all route through `ui_bullet()`, which does some hygiene related to indentation (using the `indent()` utility function), then calls `ui_inform()`. `ui_line()` and `ui_code()` call `ui_inform()` directly. +(I have renamed `ui_bullet()` to `ui_legacy_bullet()` for auto-completion happiness with the new `ui_bullets()`.) `ui_inform()` is just a wrapper around `rlang::inform()` that is guarded by a call to `is_quiet()` @@ -301,3 +308,5 @@ ui_abort(" TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. + +TODO: come back to this bit in git.R: ` purrr::walk(format(cfg), ui_bullet)` From c1f5d48104634e5bad5b0ceb8644daedc6699f63 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 16:15:51 -0800 Subject: [PATCH 074/111] utils-git.R --- R/utils-git.R | 114 ++++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 54 deletions(-) diff --git a/R/utils-git.R b/R/utils-git.R index adeedcbb0..5ac4c2c7c 100644 --- a/R/utils-git.R +++ b/R/utils-git.R @@ -32,9 +32,9 @@ check_uses_git <- function() { return(invisible()) } - ui_stop(c( + ui_abort(c( "Cannot detect that project is already a Git repository.", - "Do you need to run {ui_code('use_git()')}?" + "Do you need to run {.run usethis::use_git()}?" )) } @@ -111,7 +111,9 @@ ensure_core_excludesFile <- function() { # express path relative to user's home directory, except on Windows path <- path("~", path_rel(path, path_home())) } - ui_done("Configuring {ui_field('core.excludesFile')}: {ui_path(path)}") + ui_bullets(c( + "v" = "Configuring {.field core.excludesFile}: {.path {pth(path)}}" + )) gert::git_config_global_set("core.excludesFile", path) invisible() } @@ -135,9 +137,9 @@ git_ask_commit <- function(message, untracked, push = FALSE, paths = NULL) { # this is defined here to encourage all commits to route through this function git_commit <- function(paths, message) { repo <- git_repo() - ui_done("Adding files") + ui_bullets(c("v" = "Adding files.")) gert::git_add(paths, repo = repo) - ui_done("Making a commit with message {ui_value(message)}") + ui_bullets(c("v" = "Making a commit with message {.val {message}}.")) gert::git_commit(message, repo = repo) } @@ -153,19 +155,11 @@ git_ask_commit <- function(message, untracked, push = FALSE, paths = NULL) { } paths <- sort(paths) - ui_paths <- map_chr(paths, ui_path) - if (n > 10) { - ui_paths <- c(ui_paths[1:10], "...") - } - - if (n == 1) { - file_hint <- "There is 1 uncommitted file:" - } else { - file_hint <- "There are {n} uncommitted files:" - } - ui_line(c( - file_hint, - paste0("* ", ui_paths) + ui_paths <- usethis_map_cli(paths, template = '{.path {pth("<>")}}') + file_hint <- "{cli::qty(n)}There {?is/are} {n} uncommitted file{?s}:" + ui_bullets(c( + "i" = file_hint, + bulletize(ui_paths, n_show = 10) )) # Only push if no remote & a single change @@ -208,7 +202,7 @@ challenge_uncommitted_changes <- function(untracked = FALSE, msg = NULL) { if (ui_yeah("{msg}\nDo you want to proceed anyway?")) { return(invisible()) } else { - ui_stop("Uncommitted changes. Please commit before continuing.") + ui_abort("Uncommitted changes. Please commit before continuing.") } } } @@ -221,10 +215,14 @@ git_conflict_report <- function() { return(invisible()) } - conflicted_paths <- map_chr(conflicted, ui_path) - ui_line(c( - "There are {n} conflicted files:", - paste0("* ", conflicted_paths) + conflicted_paths <- usethis_map_cli( + conflicted, + template = '{.path {pth("<>")}}' + ) + file_hint <- "{cli::qty(n)}There {?is/are} {n} conflicted file{?s}:" + ui_bullets(c( + "i" = file_hint, + bulletize(conflicted_paths, n_show = 10) )) msg <- glue(" @@ -234,13 +232,14 @@ git_conflict_report <- function() { no <- "No, I want to abort this merge." if (ui_yeah(msg, yes = yes, no = no, shuffle = FALSE)) { ui_silence(purrr::walk(conflicted, edit_file)) - ui_stop(" - Please fix each conflict, save, stage, and commit. - To back out of this merge, run {ui_code('gert::git_merge_abort()')} \\ - (in R) or {ui_code('git merge --abort')} (in the shell).") + ui_abort(c( + "Please fix each conflict, save, stage, and commit.", + "To back out of this merge, run {.code gert::git_merge_abort()} + (in R) or {.code git merge --abort} (in the shell)." + )) } else { gert::git_merge_abort(repo = git_repo()) - ui_stop("Abandoning the merge, since it will cause merge conflicts") + ui_abort("Abandoning the merge, since it will cause merge conflicts.") } } @@ -265,12 +264,12 @@ git_pull <- function(remref = NULL, verbose = TRUE) { remref <- remref %||% git_branch_tracking(branch) if (is.na(remref)) { if (verbose) { - ui_done("No remote branch to pull from for {ui_value(branch)}.") + ui_bullets(c("v" = "No remote branch to pull from for {.val {branch}}.")) } return(invisible()) } if (verbose) { - ui_done("Pulling from {ui_value(remref)}.") + ui_bullets(c("v" = "Pulling from {.val {remref}}.")) } gert::git_fetch( remote = remref_remote(remref), @@ -301,10 +300,10 @@ git_branch <- function() { info <- gert::git_info(repo = git_repo()) branch <- info$shorthand if (identical(branch, "HEAD")) { - ui_stop("Detached head; can't continue") + ui_abort("Detached head; can't continue.") } if (is.na(branch)) { - ui_stop("On an unborn branch -- do you need to make an initial commit?") + ui_abort("On an unborn branch -- do you need to make an initial commit?") } branch } @@ -312,7 +311,7 @@ git_branch <- function() { git_branch_tracking <- function(branch = git_branch()) { repo <- git_repo() if (!gert::git_branch_exists(branch, local = TRUE, repo = repo)) { - ui_stop("There is no local branch named {ui_value(branch)}") + ui_abort("There is no local branch named {.val {branch}}.") } gbl <- gert::git_branch_list(local = TRUE, repo = repo) sub("^refs/remotes/", "", gbl$upstream[gbl$name == branch]) @@ -342,7 +341,9 @@ git_can_push <- function(max_local = Inf, branch = git_branch(), remref = NULL) git_push <- function(branch = git_branch(), remref = NULL, verbose = TRUE) { remref <- remref %||% git_branch_tracking(branch) if (verbose) { - ui_done("Pushing local {ui_value(branch)} branch to {ui_value(remref)}.") + ui_bullets(c( + "v" = "Pushing local {.val {branch}} branch to {.val {remref}}." + )) } gert::git_push( @@ -356,10 +357,10 @@ git_push <- function(branch = git_branch(), remref = NULL, verbose = TRUE) { git_push_first <- function(branch = git_branch(), remote = "origin", verbose = TRUE) { if (verbose) { remref <- glue("{remote}/{branch}") - ui_done(" - Pushing {ui_value(branch)} branch to GitHub and setting \\ - {ui_value(remref)} as upstream branch" - ) + ui_bullets(c( + "v" = "Pushing {.val {branch}} branch to GitHub and setting + {.val {remref}} as upstream branch." + )) } gert::git_push( remote = remote, @@ -413,41 +414,46 @@ check_branch_up_to_date <- function(direction = c("pull", "push"), use <- use %||% switch(direction, pull = "git pull", push = "git push") if (is.na(remref)) { - ui_done("Local branch {ui_value(branch)} is not tracking a remote branch.") + ui_bullets(c( + "i" = "Local branch {.val {branch}} is not tracking a remote branch." + )) return(invisible()) } if (direction == "pull") { - ui_done(" - Checking that local branch {ui_value(branch)} has the changes \\ - in {ui_value(remref)}") + ui_bullets(c( + "v" = "Checking that local branch {.val {branch}} has the changes + in {.val {remref}}." + )) } else { - ui_done(" - Checking that remote branch {ui_value(remref)} has the changes \\ - in {ui_value(branch)}") + ui_bullets(c( + "v" = "Checking that remote branch {.val {remref}} has the changes + in {.val {branch}}." + )) } comparison <- git_branch_compare(branch, remref) - # TODO: properly pluralize "commit(s)" when I switch to cli if (direction == "pull") { if (comparison$remote_only == 0) { return(invisible()) } else { - ui_stop(" - Local branch {ui_value(branch)} is behind {ui_value(remref)} by \\ - {comparison$remote_only} commit(s). - Please use {ui_code(use)} to update.") + ui_abort(c( + "Local branch {.val {branch}} is behind {.val {remref}} by + {comparison$remote_only} commit{?s}.", + "Please use {.code {use}} to update." + )) } } else { if (comparison$local_only == 0) { return(invisible()) } else { # TODO: consider offering to push for them? - ui_stop(" - Local branch {ui_value(branch)} is ahead of {ui_value(remref)} by \\ - {comparison$local_only} commit(s). - Please use {ui_code(use)} to update.") + ui_abort(c( + "Local branch {.val {branch}} is ahead of {.val {remref}} by + {comparison$remote_only} commit{?s}.", + "Please use {.code {use}} to update." + )) } } } From 7cb0602794574a495248189c7696342bc66d79d0 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 14:27:51 -0800 Subject: [PATCH 075/111] utils.R --- R/utils.R | 8 +++----- tests/testthat/_snaps/utils.md | 24 ++++++++++++++++++++++++ tests/testthat/test-utils.R | 10 +++++++--- 3 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 tests/testthat/_snaps/utils.md diff --git a/R/utils.R b/R/utils.R index a48c9d563..e4847d34e 100644 --- a/R/utils.R +++ b/R/utils.R @@ -24,13 +24,11 @@ can_overwrite <- function(path) { check_is_named_list <- function(x, nm = deparse(substitute(x))) { if (!is_list(x)) { - bad_class <- paste(class(x), collapse = "/") - ui_stop("{ui_code(nm)} must be a list, not {ui_value(bad_class)}.") + ui_abort("{.code {nm}} must be a list, not {.obj_type_friendly {x}}.") } if (!is_dictionaryish(x)) { - ui_stop( - "Names of {ui_code(nm)} must be non-missing, non-empty, and non-duplicated." - ) + ui_abort( + "Names of {.code {nm}} must be non-missing, non-empty, and non-duplicated.") } x } diff --git a/tests/testthat/_snaps/utils.md b/tests/testthat/_snaps/utils.md new file mode 100644 index 000000000..dd26dff81 --- /dev/null +++ b/tests/testthat/_snaps/utils.md @@ -0,0 +1,24 @@ +# check_is_named_list() works + + Code + user_facing_function(NULL) + Condition + Error in `check_is_named_list()`: + x `somevar` must be a list, not NULL. + +--- + + Code + user_facing_function(c(a = "a", b = "b")) + Condition + Error in `check_is_named_list()`: + x `somevar` must be a list, not a character vector. + +--- + + Code + user_facing_function(list("a", b = 2)) + Condition + Error in `check_is_named_list()`: + x Names of `somevar` must be non-missing, non-empty, and non-duplicated. + diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 508593554..5a9ab7c38 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -2,9 +2,13 @@ test_that("check_is_named_list() works", { l <- list(a = "a", b = 2, c = letters) expect_identical(l, check_is_named_list(l)) - expect_usethis_error(check_is_named_list(NULL), "must be a list") - expect_usethis_error(check_is_named_list(c(a = "a", b = "b")), "must be a list") - expect_usethis_error(check_is_named_list(list("a", b = 2)), "Names of .+ must be") + user_facing_function <- function(somevar) { + check_is_named_list(somevar) + } + + expect_snapshot(error = TRUE, user_facing_function(NULL)) + expect_snapshot(error = TRUE, user_facing_function(c(a = "a", b = "b"))) + expect_snapshot(error = TRUE, user_facing_function(list("a", b = 2))) }) test_that("asciify() substitutes non-ASCII but respects case", { From 5f80d3ad73fe361cc2b7e2bb9b60e464a59b91c9 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 14:30:31 -0800 Subject: [PATCH 076/111] version.R --- R/version.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/version.R b/R/version.R index 07343e882..226e3a98f 100644 --- a/R/version.R +++ b/R/version.R @@ -141,8 +141,9 @@ use_c_version <- function(ver) { } hint <- glue("{project_name()}_version") - ui_done(" - Setting {ui_field(hint)} to {ui_value(ver)} in {ui_path(version_path)}") + ui_bullets(c( + "v" = "Setting {.field {hint}} to {.val {ver}} {.path {pth(version_path)}}." + )) lines <- read_utf8(version_path) From a9ae3637776b2fc595ab6c64dcedfa3787e9c76c Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 14:32:22 -0800 Subject: [PATCH 077/111] vignette.R --- R/vignette.R | 8 ++++---- tests/testthat/_snaps/vignette.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/R/vignette.R b/R/vignette.R index 1e563dcbf..ceab26aa0 100644 --- a/R/vignette.R +++ b/R/vignette.R @@ -87,10 +87,10 @@ use_vignette_template <- function(template, name, title, subdir = NULL) { check_vignette_name <- function(name) { if (!valid_vignette_name(name)) { - ui_stop(c( - "{ui_value(name)} is not a valid filename for a vignette. It must:", - "* Start with a letter.", - "* Contain only letters, numbers, '_', and '-'." + ui_abort(c( + "{.val {name}} is not a valid filename for a vignette. It must:", + "Start with a letter.", + "Contain only letters, numbers, '_', and '-'." )) } } diff --git a/tests/testthat/_snaps/vignette.md b/tests/testthat/_snaps/vignette.md index de81c8c18..e7359ae1b 100644 --- a/tests/testthat/_snaps/vignette.md +++ b/tests/testthat/_snaps/vignette.md @@ -8,8 +8,8 @@ Code use_vignette("bad name") Condition - Error: - ! 'bad name' is not a valid filename for a vignette. It must: - * Start with a letter. - * Contain only letters, numbers, '_', and '-'. + Error in `check_vignette_name()`: + x "bad name" is not a valid filename for a vignette. It must: + i Start with a letter. + i Contain only letters, numbers, '_', and '-'. From 42b7c28a07c32ca59ed16eb1ac8c79c16ce6ba90 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 15:10:15 -0800 Subject: [PATCH 078/111] write.R --- R/write.R | 6 +++--- tests/testthat/_snaps/github-actions.md | 4 ++-- tests/testthat/_snaps/pkgdown.md | 6 +++--- tests/testthat/_snaps/roxygen.md | 2 +- tests/testthat/_snaps/tidyverse.md | 2 +- tests/testthat/_snaps/write.md | 2 +- vignettes/articles/ui-cli-conversion.Rmd | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/R/write.R b/R/write.R index 1a97ad3a9..e506ec563 100644 --- a/R/write.R +++ b/R/write.R @@ -60,7 +60,7 @@ write_union <- function(path, lines, quiet = FALSE) { } if (!quiet) { - ui_done("Adding {ui_value(new)} to {ui_path(path)}") + ui_bullets(c("v" = "Adding {.val {new}} to {.path {pth(path)}}.")) } all <- c(existing_lines, new) @@ -86,12 +86,12 @@ write_over <- function(path, lines, quiet = FALSE, overwrite = FALSE) { if (overwrite || can_overwrite(path)) { if (!quiet) { - ui_done("Writing {ui_path(path)}") + ui_bullets(c("v" = "Writing {.path {pth(path)}}.")) } write_utf8(path, lines) } else { if (!quiet) { - ui_done("Leaving {ui_path(path)} unchanged") + ui_bullets(c("i" = "Leaving {.path {pth(path)}} unchanged.")) } invisible(FALSE) } diff --git a/tests/testthat/_snaps/github-actions.md b/tests/testthat/_snaps/github-actions.md index b1c1b40db..c9c60e91b 100644 --- a/tests/testthat/_snaps/github-actions.md +++ b/tests/testthat/_snaps/github-actions.md @@ -5,8 +5,8 @@ readme = "https://github.com/r-lib/actions/blob/v2/examples/README.md") Message v Creating '.github/' - v Adding '^\\.github$' to '.Rbuildignore' - v Adding '*.html' to '.github/.gitignore' + v Adding "^\\.github$" to '.Rbuildignore'. + v Adding "*.html" to '.github/.gitignore'. v Creating '.github/workflows/' v Saving "r-lib/actions/examples/check-full.yaml@v2" to '.github/workflows/R-CMD-check.yaml'. diff --git a/tests/testthat/_snaps/pkgdown.md b/tests/testthat/_snaps/pkgdown.md index d9e7863bf..75ac9ba7d 100644 --- a/tests/testthat/_snaps/pkgdown.md +++ b/tests/testthat/_snaps/pkgdown.md @@ -3,9 +3,9 @@ Code use_pkgdown() Message - v Adding '^_pkgdown\\.yml$', '^docs$', '^pkgdown$' to '.Rbuildignore' - v Adding 'docs' to '.gitignore' - v Writing '_pkgdown.yml' + v Adding "^_pkgdown\\.yml$", "^docs$", and "^pkgdown$" to '.Rbuildignore'. + v Adding "docs" to '.gitignore'. + v Writing '_pkgdown.yml'. [ ] Edit '_pkgdown.yml'. # pkgdown_url() returns correct data, warns if pedantic diff --git a/tests/testthat/_snaps/roxygen.md b/tests/testthat/_snaps/roxygen.md index ec003d7df..548c6a0fb 100644 --- a/tests/testthat/_snaps/roxygen.md +++ b/tests/testthat/_snaps/roxygen.md @@ -3,7 +3,7 @@ Code use_package_doc() Message - v Writing 'R/{TESTPKG}-package.R' + v Writing 'R/{TESTPKG}-package.R'. --- diff --git a/tests/testthat/_snaps/tidyverse.md b/tests/testthat/_snaps/tidyverse.md index b29c19995..dcb9680d6 100644 --- a/tests/testthat/_snaps/tidyverse.md +++ b/tests/testthat/_snaps/tidyverse.md @@ -13,5 +13,5 @@ v Adding "@importFrom lifecycle deprecated" to 'R/{TESTPKG}-package.R'. v Writing 'NAMESPACE'. - v Writing 'R/import-standalone-purrr.R' + v Writing 'R/import-standalone-purrr.R'. diff --git a/tests/testthat/_snaps/write.md b/tests/testthat/_snaps/write.md index 2b3e8a910..006f0e831 100644 --- a/tests/testthat/_snaps/write.md +++ b/tests/testthat/_snaps/write.md @@ -3,5 +3,5 @@ Code write_union(proj_path("somefile"), letters[4:6]) Message - v Adding 'd', 'e', 'f' to 'somefile' + v Adding "d", "e", and "f" to 'somefile'. diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index c7b2c5b67..20b187229 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -159,7 +159,7 @@ ui_bullets(c( "v" = "Setting {.field LazyData} to {.val true} in {.path {pth('DESCRIPTION')}}." )) ``` -It would be nice to create a custom inline class, e.g. `{.usethis_path {some_path}}`, which I have done in, e.g., googledrive. But it's not easy to this *while also inheriting cli's `file:` hyperlink behaviour*, which is very desirable. So that leads to the somewhat clunk pattern above, but it gives a nice result. +It would be nice to create a custom inline class, e.g. `{.usethis_path {some_path}}`, which I have done in, e.g., googledrive. But it's not easy to do this *while still inheriting cli's `file:` hyperlink behaviour*, which is very desirable. So that leads to the somewhat clunky pattern above, but it gives a nice result. - `ui_code()` gets replaced by various inline styles, depending on what the actual goal is: - `{.arg some_argument}` - `{.cls some_class}` From 8e390e799b0a566c2d14364e61bb63b95f8ee3d6 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Sun, 25 Feb 2024 22:00:23 -0800 Subject: [PATCH 079/111] utils-github.R --- R/utils-github.R | 201 +++++++++++------------ R/utils-ui.R | 4 + tests/testthat/_snaps/utils-github.md | 13 +- tests/testthat/test-utils-github.R | 2 + vignettes/articles/ui-cli-conversion.Rmd | 2 +- 5 files changed, 107 insertions(+), 115 deletions(-) diff --git a/R/utils-github.R b/R/utils-github.R index 7a9e213ea..59a72af7c 100644 --- a/R/utils-github.R +++ b/R/utils-github.R @@ -2,7 +2,7 @@ parse_repo_spec <- function(repo_spec) { repo_split <- strsplit(repo_spec, "/")[[1]] if (length(repo_split) != 2) { - ui_stop("{ui_code('repo_spec')} must be of form {ui_value('owner/repo')}.") + ui_abort("{.arg repo_spec} must be of the form {.val owner/repo}.") } list(owner = repo_split[[1]], repo = repo_split[[2]]) } @@ -77,7 +77,7 @@ parse_repo_url <- function(x) { dat <- parse_github_remotes(x) # TODO: generalize here for GHE hosts that don't include 'github' if (!grepl("github", dat$host)) { - ui_stop("URL doesn't seem to be associated with GitHub: {ui_value(x)}") + ui_abort("URL doesn't seem to be associated with GitHub: {.val {x}}") } list( repo_spec = make_spec(owner = dat$repo_owner, repo = dat$repo_name), @@ -188,12 +188,13 @@ github_remotes <- function(these = c("origin", "upstream"), oops <- which(!grl$github_got) oops_remotes <- grl$remote[oops] oops_hosts <- unique(grl$host[oops]) - ui_stop(" - Unable to get GitHub info for these remotes: {ui_value(oops_remotes)} - Are we offline? Is GitHub down? Has the repo been deleted? - Otherwise, you probably need to configure a personal access token (PAT) \\ - for {ui_value(oops_hosts)} - See {ui_code('?gh_token_help')} for advice") + ui_abort(c( + "Unable to get GitHub info for these remotes: {.val {oops_remotes}}.", + "Are we offline? Is GitHub down? Has the repo been deleted?", + "Otherwise, you probably need to configure a personal access token (PAT) + for {.val {oops_hosts}}.", + "See {.run usethis::gh_token_help()} for advice." + )) } grl$default_branch <- map_chr(repo_info, "default_branch", .default = NA) @@ -327,18 +328,20 @@ github_remote_config <- function(github_get = NA) { if (!single_remote) { if (length(unique(grl$host)) != 1) { - ui_stop(" - Internal error: Multiple GitHub hosts - {ui_value(grl$host)}") + ui_abort(c( + "Internal error: Multiple GitHub hosts.", + "{.val {grl$host}}" + )) } if (length(unique(grl$github_got)) != 1) { - ui_stop(" - Internal error: Got GitHub API info for some remotes, but not all - Do all the remotes still exist? Do you still have access?") + ui_abort(c( + "Internal error: Got GitHub API info for some remotes, but not all.", + "Do all the remotes still exist? Do you still have access?" + )) } if (length(unique(grl$perm_known)) != 1) { - ui_stop(" - Internal error: Know GitHub permissions for some remotes, but not all") + ui_abort(" + Internal error: Know GitHub permissions for some remotes, but not all.") } } cfg$host_url <- unique(grl$host_url) @@ -495,11 +498,12 @@ target_repo <- function(cfg = NULL, } choices <- c( - origin = glue("{cfg$origin$repo_spec} = {ui_value('origin')}"), - upstream = glue("{cfg$upstream$repo_spec} = {ui_value('upstream')}") + origin = ui_pre_glue("<> = {.val origin}"), + upstream = ui_pre_glue("<> = {.val upstream}") ) - title <- glue("Which repo should we target?") - choice <- utils::menu(choices, graphics = FALSE, title = title) + choices_formatted <- map_chr(choices, cli::format_inline) + title <- "Which repo should we target?" + choice <- utils::menu(choices_formatted, graphics = FALSE, title = title) cfg[[names(choices)[choice]]] } @@ -510,12 +514,12 @@ target_repo_spec <- function(role = c("source", "primary"), } # formatting github remote configurations for humans --------------------------- -format_remote <- function(remote) { +pre_format_remote <- function(remote) { effective_spec <- function(remote) { if (remote$is_configured) { - ui_value(remote$repo_spec) + ui_pre_glue("{.val <>}") } else { - ui_unset("not configured") + ui_special("not configured") } } push_clause <- function(remote) { @@ -528,35 +532,37 @@ format_remote <- function(remote) { glue("{remote$name} = {effective_spec(remote)}"), push_clause(remote), if (isTRUE(remote$is_fork)) { - glue(" = fork of {ui_value(remote$parent_repo_spec)}") + ui_pre_glue(" = fork of {.val <>}") } ) glue_collapse(out) } -format_fields <- function(cfg) { +pre_format_fields <- function(cfg) { list( - type = glue("Type = {ui_value(cfg$type)}"), - host_url = glue("Host = {ui_value(cfg$host_url)}"), - pr_ready = glue("Config supports a pull request = {ui_value(cfg$pr_ready)}"), - origin = format_remote(cfg$origin), - upstream = format_remote(cfg$upstream), + type = ui_pre_glue("Type = {.val <>}"), + host_url = ui_pre_glue("Host = {.val <>}"), + # extra brackets here ensure value is formatted as logical (vs string) + pr_ready = ui_pre_glue("Config supports a pull request = {.val {<>}}"), + origin = pre_format_remote(cfg$origin), + upstream = pre_format_remote(cfg$upstream), desc = if (is.na(cfg$desc)) { - glue("Desc = {ui_unset('no description')}") + ui_pre_glue("Desc = {ui_special('no description')}") } else { - glue("Desc = {cfg$desc}") + ui_pre_glue("Desc = <>") } ) } #' @export format.github_remote_config <- function(x, ...) { - glue::as_glue(format_fields(x)) + map_chr(pre_format_fields(x), cli::format_inline) } #' @export print.github_remote_config <- function(x, ...) { - cat(format(x, ...), sep = "\n") + withr::local_options(usethis.quiet = FALSE) + ui_bullets(bulletize(format(x, ...), n_show = 20)) invisible(x) } @@ -565,9 +571,9 @@ print.github_remote_config <- function(x, ...) { github_remote_config_wat <- function(cfg, context = c("menu", "abort")) { context <- match.arg(context) adjective <- switch(context, menu = "Unexpected", abort = "Unsupported") - out <- format_fields(cfg) + out <- pre_format_fields(cfg) out$pr_ready <- NULL - out$type <- glue("{adjective} GitHub remote configuration: {ui_value(cfg$type)}") + out$type <- ui_pre_glue("<> GitHub remote configuration: {.val <>}") out$desc <- if (is.na(cfg$desc)) NULL else cfg$desc out } @@ -584,24 +590,24 @@ ui_github_remote_config_wat <- function(cfg) { } stop_bad_github_remote_config <- function(cfg) { - abort( + ui_abort( message = unname(unlist(github_remote_config_wat(cfg, context = "abort"))), - class = c("usethis_error_bad_github_remote_config", "usethis_error"), + class = "usethis_error_bad_github_remote_config", cfg = cfg ) } stop_maybe_github_remote_config <- function(cfg) { msg <- github_remote_config_wat(cfg) - msg$type <- glue(" - Pull request functions can't work with GitHub remote configuration: \\ - {ui_value(cfg$type)} - The most likely problem is that we aren't discovering your GitHub \\ - personal access token - Call {ui_code('gh_token_help()')} for help") - abort( + msg$type <- ui_pre_glue(" + Pull request functions can't work with GitHub remote configuration: + {.val <>}. + The most likely problem is that we aren't discovering your GitHub + personal access token. + Call {.run usethis::gh_token_help()} for help.") + ui_abort( message = unname(unlist(msg)), - class = c("usethis_error_invalid_pr_config", "usethis_error"), + class = "usethis_error_invalid_pr_config", cfg = cfg ) } @@ -643,8 +649,8 @@ check_for_config <- function(cfg = NULL, check_for_bad_config(cfg, bad_configs = bad_configs) - ui_stop(" - Internal error: Unexpected GitHub remote configuration: {ui_value(cfg$type)}") + ui_abort(" + Internal error: Unexpected GitHub remote configuration: {.val {cfg$type}}.") } check_can_push <- function(tr = target_repo(github_get = TRUE), @@ -652,8 +658,8 @@ check_can_push <- function(tr = target_repo(github_get = TRUE), if (isTRUE(tr$can_push)) { return(invisible()) } - ui_stop(" - You don't seem to have push access for {ui_value(tr$repo_spec)}, which \\ + ui_abort(" + You don't seem to have push access for {.val {tr$repo_spec}}, which is required {objective}.") } @@ -673,17 +679,13 @@ all_configs <- function() { } read_more <- function() { - cli::format_inline( - "Read more about the GitHub remote configurations that usethis supports at: -{.url https://happygitwithr.com/common-remote-setups.html}." - ) + "Read more about the GitHub remote configurations that usethis supports at: + {.url https://happygitwithr.com/common-remote-setups.html}." } read_more_maybe <- function() { - cli::format_inline( - "Read more about what this GitHub remote configurations means at: -{.url https://happygitwithr.com/common-remote-setups.html}." - ) + "Read more about what this GitHub remote configurations means at: + {.url https://happygitwithr.com/common-remote-setups.html}." } cfg_no_github <- function(cfg) { @@ -692,11 +694,9 @@ cfg_no_github <- function(cfg) { list( type = "no_github", pr_ready = FALSE, - desc = glue(" - Neither {ui_value('origin')} nor {ui_value('upstream')} is a GitHub \\ - repo. - - {read_more()}") + desc = ui_pre_glue(" + Neither {.val origin} nor {.val upstream} is a GitHub repo. + <>") ) ) } @@ -707,10 +707,8 @@ cfg_ours <- function(cfg) { list( type = "ours", pr_ready = TRUE, - desc = glue(" - {ui_value('origin')} is both the source and primary repo. - - {read_more()}") + desc = ui_pre_glue(" + {.val origin} is both the source and primary repo! <>") ) ) } @@ -722,13 +720,12 @@ cfg_theirs <- function(cfg) { list( type = "theirs", pr_ready = FALSE, - desc = glue(" - The only configured GitHub remote is {ui_value(configured)}, which + desc = ui_pre_glue(" + The only configured GitHub remote is {.val <>}, which you cannot push to. If your goal is to make a pull request, you must fork-and-clone. - {ui_code('usethis::create_from_github()')} can do this. - - {read_more()}") + {.fun usethis::create_from_github} can do this. + <>") ) ) } @@ -746,14 +743,12 @@ cfg_maybe_ours_or_theirs <- function(cfg) { list( type = "maybe_ours_or_theirs", pr_ready = NA, - desc = glue(" - {ui_value(configured)} is a GitHub repo and {ui_value(not_configured)} \\ + desc = ui_pre_glue(" + {.val <>} is a GitHub repo and {.val <>} is either not configured or is not a GitHub repo. - We may be offline or you may need to configure a GitHub personal access - token. {ui_code('gh_token_help()')} can help with that. - - {read_more_maybe()}") + token. {.run usethis::gh_token_help()} can help with that. + <>") ) ) } @@ -764,11 +759,10 @@ cfg_fork <- function(cfg) { list( type = "fork", pr_ready = TRUE, - desc = glue(" - {ui_value('origin')} is a fork of {ui_value(cfg$upstream$repo_spec)}, \\ - which is configured as the {ui_value('upstream')} remote. - - {read_more()}") + desc = ui_pre_glue(" + {.val origin} is a fork of {.val <>}, + which is configured as the {.val upstream} remote. + <>") ) ) } @@ -779,16 +773,14 @@ cfg_maybe_fork <- function(cfg) { list( type = "maybe_fork", pr_ready = NA, - desc = glue(" - Both {ui_value('origin')} and {ui_value('upstream')} appear to be \\ - GitHub repos. However, we can't confirm their relationship to each \\ - other (e.g., fork and fork parent) or your permissions (e.g. push \\ + desc = ui_pre_glue(" + Both {.val origin} and {.val upstream} appear to be + GitHub repos. However, we can't confirm their relationship to each + other (e.g., fork and fork parent) or your permissions (e.g. push access). - We may be offline or you may need to configure a GitHub personal access - token. {ui_code('gh_token_help()')} can help with that. - - {read_more_maybe()}") + token. {.run usethis::gh_token_help()} can help with that. + <>") ) ) } @@ -799,10 +791,9 @@ cfg_fork_cannot_push_origin <- function(cfg) { list( type = "fork_cannot_push_origin", pr_ready = FALSE, - desc = glue(" - The {ui_value('origin')} remote is a fork, but you can't push to it. - - {read_more()}") + desc = ui_pre_glue(" + The {.val origin} remote is a fork, but you can't push to it. + <>") ) ) } @@ -813,11 +804,10 @@ cfg_fork_upstream_is_not_origin_parent <- function(cfg) { list( type = "fork_upstream_is_not_origin_parent", pr_ready = FALSE, - desc = glue(" - The {ui_value('origin')} GitHub remote is a fork, but its parent is \\ - not configured as the {ui_value('upstream')} remote. - - {read_more()}") + desc = ui_pre_glue(" + The {.val origin} GitHub remote is a fork, but its parent is + not configured as the {.val upstream} remote. + <>") ) ) } @@ -828,12 +818,11 @@ cfg_upstream_but_origin_is_not_fork <- function(cfg) { list( type = "upstream_but_origin_is_not_fork", pr_ready = FALSE, - desc = glue(" - Both {ui_value('origin')} and {ui_value('upstream')} are GitHub \\ - remotes, but {ui_value('origin')} is not a fork and, in particular, \\ - is not a fork of {ui_value('upstream')}. - - {read_more()}") + desc = ui_pre_glue(" + Both {.val origin} and {.val upstream} are GitHub remotes, but + {.val origin} is not a fork and, in particular, is not a fork of + {.val upstream}. + <>") ) ) } diff --git a/R/utils-ui.R b/R/utils-ui.R index 0d274b9f5..bdf5dc0fc 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -138,6 +138,10 @@ usethis_map_cli.character <- function(x, as.character(glue(template, .open = .open, .close = .close)) } +ui_pre_glue <- function(..., .envir = parent.frame()) { + glue(..., .open = "<<", .close = ">>", .envir = .envir) +} + bulletize <- function(x, bullet = "*", n_show = 5, n_fudge = 2) { n <- length(x) n_show_actual <- compute_n_show(n, n_show, n_fudge) diff --git a/tests/testthat/_snaps/utils-github.md b/tests/testthat/_snaps/utils-github.md index 5132700e1..1d15a5662 100644 --- a/tests/testthat/_snaps/utils-github.md +++ b/tests/testthat/_snaps/utils-github.md @@ -15,12 +15,9 @@ stop_bad_github_remote_config(cfg) Condition Error in `stop_bad_github_remote_config()`: - ! Unsupported GitHub remote configuration: 'fork_upstream_is_not_origin_parent' - * Host = 'https://github.com' - * origin = 'jennybc/gh' (can push) = fork of NA - * upstream = 'r-pkgs/gh' (can push) - * The 'origin' GitHub remote is a fork, but its parent is not configured as the 'upstream' remote. - - Read more about the GitHub remote configurations that usethis supports at: - . + x Unsupported GitHub remote configuration: "fork_upstream_is_not_origin_parent" + i Host = "https://github.com" + i origin = "jennybc/gh" (can push) = fork of "r-lib/gh" + i upstream = "r-pkgs/gh" (can push) + i The "origin" GitHub remote is a fork, but its parent is not configured as the "upstream" remote. Read more about the GitHub remote configurations that usethis supports at: . diff --git a/tests/testthat/test-utils-github.R b/tests/testthat/test-utils-github.R index 9a34a1715..459687e68 100644 --- a/tests/testthat/test-utils-github.R +++ b/tests/testthat/test-utils-github.R @@ -174,6 +174,8 @@ test_that("fork_upstream_is_not_origin_parent is detected", { gr$can_push <- TRUE gr$perm_known <- TRUE gr$parent_repo_owner <- c("r-lib", NA) + gr$parent_repo_name <- c("gh", NA) + gr$parent_repo_spec <- c("r-lib/gh", NA) local_mocked_bindings(github_remotes = function(...) gr) cfg <- github_remote_config() expect_equal(cfg$type, "fork_upstream_is_not_origin_parent") diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 20b187229..d56bdc96a 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -307,6 +307,6 @@ ui_abort(" ``` -TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. +TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. Ditto for `target_repo()` in utils-github.R. TODO: come back to this bit in git.R: ` purrr::walk(format(cfg), ui_bullet)` From 043e8cf66be66e669e16bc99a73837ee2dedf650 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 26 Feb 2024 18:19:09 -0800 Subject: [PATCH 080/111] Add snapshot tests for remote GitHub configurations --- R/utils-github.R | 160 ++++++++++++++++++++++++++ tests/testthat/_snaps/utils-github.md | 137 +++++++++++++++++++++- tests/testthat/test-utils-github.R | 42 ++++++- 3 files changed, 335 insertions(+), 4 deletions(-) diff --git a/R/utils-github.R b/R/utils-github.R index 59a72af7c..7f6e6f5d8 100644 --- a/R/utils-github.R +++ b/R/utils-github.R @@ -826,3 +826,163 @@ cfg_upstream_but_origin_is_not_fork <- function(cfg) { ) ) } + +# construct instances of `github_remote_config` for dev/testing purposes-------- +new_no_github <- function() { + cfg <- new_github_remote_config() + cfg_no_github(cfg) +} + +new_ours <- function() { + remotes <- data.frame(name = "origin", url = "https://github.com/OWNER/REPO.git") + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <- grl$perm_known <- TRUE + grl$default_branch <- "DEFAULT_BRANCH" + + grl$is_fork <- FALSE + grl$can_push <- grl$can_admin <- TRUE + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$host_url <- grl$host_url + cfg$origin$is_configured <- TRUE + cfg_ours(cfg) +} + +new_theirs <- function() { + remotes <- data.frame(name = "origin", url = "https://github.com/OWNER/REPO.git") + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <- grl$perm_known <- TRUE + grl$default_branch <- "DEFAULT_BRANCH" + + grl$can_push <- grl$can_admin <- FALSE + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$host_url <- grl$host_url + cfg$origin$is_configured <- TRUE + cfg_theirs(cfg) +} + +new_fork <- function() { + remotes <- data.frame( + name = c("origin", "upstream"), + url = c("https://github.com/CONTRIBUTOR/REPO.git", "https://github.com/OWNER/REPO.git") + ) + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <- grl$perm_known <- TRUE + grl$default_branch <- "DEFAULT_BRANCH" + + grl$is_fork <- c(TRUE, FALSE) + grl$parent_repo_owner <- c("OWNER", NA) + grl$parent_repo_name <- c("REPO", NA) + grl$can_push_to_parent <- c(FALSE, NA) + grl$parent_repo_spec <- make_spec(grl$parent_repo_owner, grl$parent_repo_name) + + grl$can_push <- grl$can_admin <- c(TRUE, FALSE) + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$upstream <- utils::modifyList(cfg$upstream, grl[grl$remote == "upstream",]) + cfg$host_url <- grl$host_url[1] + cfg$origin$is_configured <- cfg$upstream$is_configured <- TRUE + cfg$origin$parent_is_upstream <- TRUE + cfg_fork(cfg) +} + +new_maybe_ours_or_theirs <- function() { + remotes <- data.frame(name = "origin", url = "https://github.com/OWNER/REPO.git") + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <-grl$perm_known <- FALSE + grl$default_branch <- "DEFAULT_BRANCH" + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$host_url <- grl$host_url + cfg$origin$is_configured <- TRUE + cfg_maybe_ours_or_theirs(cfg) +} + +new_maybe_fork <- function() { + remotes <- data.frame( + name = c("origin", "upstream"), + url = c("https://github.com/CONTRIBUTOR/REPO.git", "https://github.com/OWNER/REPO.git") + ) + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <-grl$perm_known <- FALSE + grl$default_branch <- "DEFAULT_BRANCH" + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$upstream <- utils::modifyList(cfg$upstream, grl[grl$remote == "upstream",]) + cfg$host_url <- grl$host_url[1] + cfg$origin$is_configured <- cfg$upstream$is_configured <- TRUE + cfg_maybe_fork(cfg) +} + +new_fork_cannot_push_origin <- function() { + remotes <- data.frame( + name = c("origin", "upstream"), + url = c("https://github.com/CONTRIBUTOR/REPO.git", "https://github.com/OWNER/REPO.git") + ) + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <-grl$perm_known <- TRUE + grl$default_branch <- "DEFAULT_BRANCH" + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$upstream <- utils::modifyList(cfg$upstream, grl[grl$remote == "upstream",]) + cfg$host_url <- grl$host_url[1] + cfg$origin$is_configured <- cfg$upstream$is_configured <- TRUE + + cfg$origin$parent_is_upstream <- FALSE + + cfg_fork_cannot_push_origin(cfg) +} + +new_fork_upstream_is_not_origin_parent<- function() { + remotes <- data.frame( + name = c("origin", "upstream"), + url = c("https://github.com/CONTRIBUTOR/REPO.git", "https://github.com/OLD_OWNER/REPO.git") + ) + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <- grl$perm_known <- TRUE + grl$default_branch <- "DEFAULT_BRANCH" + + grl$is_fork <- c(TRUE, FALSE) + grl$parent_repo_owner <- c("NEW_OWNER", NA) + grl$parent_repo_name <- c("REPO", NA) + grl$can_push_to_parent <- c(FALSE, NA) + grl$parent_repo_spec <- make_spec(grl$parent_repo_owner, grl$parent_repo_name) + grl$can_push <- grl$can_admin <- c(TRUE, FALSE) + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$upstream <- utils::modifyList(cfg$upstream, grl[grl$remote == "upstream",]) + cfg$host_url <- grl$host_url[1] + cfg$origin$is_configured <- cfg$upstream$is_configured <- TRUE + + cfg_fork_upstream_is_not_origin_parent(cfg) +} + +new_upstream_but_origin_is_not_fork <- function() { + remotes <- data.frame( + name = c("origin", "upstream"), + url = c("https://github.com/CONTRIBUTOR/REPO.git", "https://github.com/OWNER/REPO.git") + ) + grl <- github_remotes(github_get = FALSE, x = remotes) + grl$github_got <-grl$perm_known <- TRUE + grl$default_branch <- "DEFAULT_BRANCH" + + grl$is_fork <- FALSE + + cfg <- new_github_remote_config() + cfg$origin <- utils::modifyList(cfg$origin, grl[grl$remote == "origin",]) + cfg$upstream <- utils::modifyList(cfg$upstream, grl[grl$remote == "upstream",]) + cfg$host_url <- grl$host_url[1] + cfg$origin$is_configured <- cfg$upstream$is_configured <- TRUE + + cfg$origin$parent_is_upstream <- FALSE + + cfg_upstream_but_origin_is_not_fork(cfg) +} diff --git a/tests/testthat/_snaps/utils-github.md b/tests/testthat/_snaps/utils-github.md index 1d15a5662..828e84fb9 100644 --- a/tests/testthat/_snaps/utils-github.md +++ b/tests/testthat/_snaps/utils-github.md @@ -9,7 +9,142 @@ [7] "fork_cannot_push_origin" "fork_upstream_is_not_origin_parent" [9] "upstream_but_origin_is_not_fork" -# fork_upstream_is_not_origin_parent is detected +# 'no_github' is reported correctly + + Code + new_no_github() + Message + * Type = "no_github" + * Host = "NA" + * Config supports a pull request = FALSE + * origin = + * upstream = + * Desc = Neither "origin" nor "upstream" is a GitHub repo. Read more about the + GitHub remote configurations that usethis supports at: + . + +# 'ours' is reported correctly + + Code + new_ours() + Message + * Type = "ours" + * Host = "https://github.com" + * Config supports a pull request = TRUE + * origin = "OWNER/REPO" (can push) + * upstream = + * Desc = "origin" is both the source and primary repo! Read more about the + GitHub remote configurations that usethis supports at: + . + +# 'theirs' is reported correctly + + Code + new_theirs() + Message + * Type = "theirs" + * Host = "https://github.com" + * Config supports a pull request = FALSE + * origin = "OWNER/REPO" (can not push) + * upstream = + * Desc = The only configured GitHub remote is "origin", which you cannot push + to. If your goal is to make a pull request, you must fork-and-clone. + `usethis::create_from_github()` can do this. Read more about the GitHub + remote configurations that usethis supports at: + . + +# 'fork' is reported correctly + + Code + new_fork() + Message + * Type = "fork" + * Host = "https://github.com" + * Config supports a pull request = TRUE + * origin = "CONTRIBUTOR/REPO" (can push) = fork of "OWNER/REPO" + * upstream = "OWNER/REPO" (can not push) + * Desc = "origin" is a fork of "OWNER/REPO", which is configured as the + "upstream" remote. Read more about the GitHub remote configurations that + usethis supports at: . + +# 'maybe_ours_or_theirs' is reported correctly + + Code + new_maybe_ours_or_theirs() + Message + * Type = "maybe_ours_or_theirs" + * Host = "https://github.com" + * Config supports a pull request = NA + * origin = "OWNER/REPO" + * upstream = + * Desc = "origin" is a GitHub repo and "upstream" is either not configured or + is not a GitHub repo. We may be offline or you may need to configure a GitHub + personal access token. `usethis::gh_token_help()` can help with that. Read + more about what this GitHub remote configurations means at: + . + +# 'maybe_fork' is reported correctly + + Code + new_maybe_fork() + Message + * Type = "maybe_fork" + * Host = "https://github.com" + * Config supports a pull request = NA + * origin = "CONTRIBUTOR/REPO" + * upstream = "OWNER/REPO" + * Desc = Both "origin" and "upstream" appear to be GitHub repos. However, we + can't confirm their relationship to each other (e.g., fork and fork parent) + or your permissions (e.g. push access). We may be offline or you may need to + configure a GitHub personal access token. `usethis::gh_token_help()` can help + with that. Read more about what this GitHub remote configurations means at: + . + +# 'fork_cannot_push_origin' is reported correctly + + Code + new_fork_cannot_push_origin() + Message + * Type = "fork_cannot_push_origin" + * Host = "https://github.com" + * Config supports a pull request = FALSE + * origin = "CONTRIBUTOR/REPO" + * upstream = "OWNER/REPO" + * Desc = The "origin" remote is a fork, but you can't push to it. Read more + about the GitHub remote configurations that usethis supports at: + . + +# 'fork_upstream_is_not_origin_parent' is reported correctly + + Code + new_fork_upstream_is_not_origin_parent() + Message + * Type = "fork_upstream_is_not_origin_parent" + * Host = "https://github.com" + * Config supports a pull request = FALSE + * origin = "CONTRIBUTOR/REPO" (can push) = fork of "NEW_OWNER/REPO" + * upstream = "OLD_OWNER/REPO" (can not push) + * Desc = The "origin" GitHub remote is a fork, but its parent is not configured + as the "upstream" remote. Read more about the GitHub remote configurations + that usethis supports at: + . + +# 'upstream_but_origin_is_not_fork' is reported correctly + + Code + new_upstream_but_origin_is_not_fork() + Message + * Type = "upstream_but_origin_is_not_fork" + * Host = "https://github.com" + * Config supports a pull request = FALSE + * origin = "CONTRIBUTOR/REPO" + * upstream = "OWNER/REPO" + * Desc = Both "origin" and "upstream" are GitHub remotes, but "origin" is not a + fork and, in particular, is not a fork of "upstream". Read more about the + GitHub remote configurations that usethis supports at: + . + +# 'fork_upstream_is_not_origin_parent' is detected correctly Code stop_bad_github_remote_config(cfg) diff --git a/tests/testthat/test-utils-github.R b/tests/testthat/test-utils-github.R index 459687e68..2000cdf7f 100644 --- a/tests/testthat/test-utils-github.R +++ b/tests/testthat/test-utils-github.R @@ -85,6 +85,7 @@ test_that("parse_repo_url() errors for non-GitHub remote URLs", { }) test_that("github_remote_list() works", { + local_interactive(FALSE) create_local_project() use_git() use_git_remote("origin", "https://github.com/OWNER/REPO.git") @@ -150,14 +151,49 @@ test_that("github_remotes() works", { }) # GitHub remote configuration -------------------------------------------------- -# very sparse, but you have to start somewhere! test_that("we understand the list of all possible configs", { expect_snapshot(all_configs()) }) -test_that("fork_upstream_is_not_origin_parent is detected", { - # We've already encountered this in the wild. Here's how it happens: +test_that("'no_github' is reported correctly", { + expect_snapshot(new_no_github()) +}) + +test_that("'ours' is reported correctly", { + expect_snapshot(new_ours()) +}) + +test_that("'theirs' is reported correctly", { + expect_snapshot(new_theirs()) +}) + +test_that("'fork' is reported correctly", { + expect_snapshot(new_fork()) +}) + +test_that("'maybe_ours_or_theirs' is reported correctly", { + expect_snapshot(new_maybe_ours_or_theirs()) +}) + +test_that("'maybe_fork' is reported correctly", { + expect_snapshot(new_maybe_fork()) +}) + +test_that("'fork_cannot_push_origin' is reported correctly", { + expect_snapshot(new_fork_cannot_push_origin()) +}) + +test_that("'fork_upstream_is_not_origin_parent' is reported correctly", { + expect_snapshot(new_fork_upstream_is_not_origin_parent()) +}) + +test_that("'upstream_but_origin_is_not_fork' is reported correctly", { + expect_snapshot(new_upstream_but_origin_is_not_fork()) +}) + +test_that("'fork_upstream_is_not_origin_parent' is detected correctly", { + # inspired by something that actually happened: # 1. r-pkgs/gh is created # 2. user forks and clones: origin = USER/gh, upstream = r-pkgs/gh # 3. parent repo becomes r-lib/gh, due to transfer or ownership or owner From 667c148ebc136bb208d6b1c8a844df0daa0471ac Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 26 Feb 2024 21:25:20 -0800 Subject: [PATCH 081/111] Finish converting UI around GitHub remote configurations --- R/utils-github.R | 173 ++++++++++++++++---------- R/utils-ui.R | 44 +++++++ tests/testthat/_snaps/utils-github.md | 102 ++++++++++----- tests/testthat/test-utils-github.R | 14 +++ 4 files changed, 234 insertions(+), 99 deletions(-) diff --git a/R/utils-github.R b/R/utils-github.R index 7f6e6f5d8..4630448e5 100644 --- a/R/utils-github.R +++ b/R/utils-github.R @@ -546,23 +546,28 @@ pre_format_fields <- function(cfg) { pr_ready = ui_pre_glue("Config supports a pull request = {.val {<>}}"), origin = pre_format_remote(cfg$origin), upstream = pre_format_remote(cfg$upstream), - desc = if (is.na(cfg$desc)) { - ui_pre_glue("Desc = {ui_special('no description')}") - } else { - ui_pre_glue("Desc = <>") - } + desc = cfg$desc ) } #' @export format.github_remote_config <- function(x, ...) { - map_chr(pre_format_fields(x), cli::format_inline) + x_fmt <- pre_format_fields(x) + x_fmt$desc <- map_chr(x_fmt$desc, cli::format_inline) + x_fmt <- purrr::map_if(x_fmt, function(x) length(x) == 1, cli::format_inline) + out <- unlist(unname(x_fmt)) + + nms <- names2(out) + nms <- ifelse(nzchar(nms), nms, "*") + names(out) <- nms + + out } #' @export print.github_remote_config <- function(x, ...) { withr::local_options(usethis.quiet = FALSE) - ui_bullets(bulletize(format(x, ...), n_show = 20)) + ui_bullets(format(x, ...)) invisible(x) } @@ -571,17 +576,23 @@ print.github_remote_config <- function(x, ...) { github_remote_config_wat <- function(cfg, context = c("menu", "abort")) { context <- match.arg(context) adjective <- switch(context, menu = "Unexpected", abort = "Unsupported") - out <- pre_format_fields(cfg) - out$pr_ready <- NULL - out$type <- ui_pre_glue("<> GitHub remote configuration: {.val <>}") - out$desc <- if (is.na(cfg$desc)) NULL else cfg$desc - out + out <- format(cfg) + + type_idx <- grep("^Type", out) + out[type_idx] <- ui_pre_glue(" + <> GitHub remote configuration: {.val <>}") + names(out)[type_idx] <- "x" + + pr_idx <- grep("pull request", out) + out <- out[-pr_idx] + + unlist(out) } # returns TRUE if user selects "no" --> exit the calling function # return FALSE if user select "yes" --> keep going, they've been warned ui_github_remote_config_wat <- function(cfg) { - ui_nope( + ui_nah( github_remote_config_wat(cfg, context = "menu"), yes = "Yes, I want to proceed. I know what I'm doing.", no = "No, I want to stop and straighten out my GitHub remotes first.", @@ -591,22 +602,26 @@ ui_github_remote_config_wat <- function(cfg) { stop_bad_github_remote_config <- function(cfg) { ui_abort( - message = unname(unlist(github_remote_config_wat(cfg, context = "abort"))), + github_remote_config_wat(cfg, context = "abort"), class = "usethis_error_bad_github_remote_config", cfg = cfg ) } stop_maybe_github_remote_config <- function(cfg) { - msg <- github_remote_config_wat(cfg) - msg$type <- ui_pre_glue(" - Pull request functions can't work with GitHub remote configuration: - {.val <>}. - The most likely problem is that we aren't discovering your GitHub - personal access token. - Call {.run usethis::gh_token_help()} for help.") + msg <- c( + ui_pre_glue(" + Pull request functions can't work with GitHub remote configuration: + {.val <>}."), + "The most likely problem is that we aren't discovering your GitHub personal + access token.", + github_remote_config_wat(cfg) + ) + idx <- grep("Unexpected GitHub remote configuration", msg) + msg <- msg[-idx] + ui_abort( - message = unname(unlist(msg)), + message = unlist(msg), class = "usethis_error_invalid_pr_config", cfg = cfg ) @@ -679,13 +694,17 @@ all_configs <- function() { } read_more <- function() { - "Read more about the GitHub remote configurations that usethis supports at: - {.url https://happygitwithr.com/common-remote-setups.html}." + c( + "i" = "Read more about the GitHub remote configurations that usethis supports at:", + " " = "{.url https://happygitwithr.com/common-remote-setups.html}." + ) } read_more_maybe <- function() { - "Read more about what this GitHub remote configurations means at: - {.url https://happygitwithr.com/common-remote-setups.html}." + c( + "i" = "Read more about what this GitHub remote configuration means at:", + " " = "{.url https://happygitwithr.com/common-remote-setups.html}." + ) } cfg_no_github <- function(cfg) { @@ -694,9 +713,10 @@ cfg_no_github <- function(cfg) { list( type = "no_github", pr_ready = FALSE, - desc = ui_pre_glue(" - Neither {.val origin} nor {.val upstream} is a GitHub repo. - <>") + desc = c( + "!" = "Neither {.val origin} nor {.val upstream} is a GitHub repo.", + read_more() + ) ) ) } @@ -707,8 +727,10 @@ cfg_ours <- function(cfg) { list( type = "ours", pr_ready = TRUE, - desc = ui_pre_glue(" - {.val origin} is both the source and primary repo! <>") + desc = c( + "i" = "{.val origin} is both the source and primary repo.", + read_more() + ) ) ) } @@ -720,12 +742,14 @@ cfg_theirs <- function(cfg) { list( type = "theirs", pr_ready = FALSE, - desc = ui_pre_glue(" - The only configured GitHub remote is {.val <>}, which - you cannot push to. - If your goal is to make a pull request, you must fork-and-clone. - {.fun usethis::create_from_github} can do this. - <>") + desc = c( + "!" = ui_pre_glue(" + The only configured GitHub remote is {.val <>}, + which you cannot push to."), + "i" = "If your goal is to make a pull request, you must fork-and-clone.", + "i" = "{.fun usethis::create_from_github} can do this.", + read_more() + ) ) ) } @@ -743,12 +767,16 @@ cfg_maybe_ours_or_theirs <- function(cfg) { list( type = "maybe_ours_or_theirs", pr_ready = NA, - desc = ui_pre_glue(" - {.val <>} is a GitHub repo and {.val <>} - is either not configured or is not a GitHub repo. - We may be offline or you may need to configure a GitHub personal access - token. {.run usethis::gh_token_help()} can help with that. - <>") + desc = c( + "!" = ui_pre_glue(" + {.val <>} is a GitHub repo and + {.val <>} is either not configured or is not a + GitHub repo."), + "i" = "We may be offline or you may need to configure a GitHub personal + access token.", + "i" = "{.run usethis::gh_token_help()} can help with that.", + read_more_maybe() + ) ) ) } @@ -759,10 +787,12 @@ cfg_fork <- function(cfg) { list( type = "fork", pr_ready = TRUE, - desc = ui_pre_glue(" - {.val origin} is a fork of {.val <>}, - which is configured as the {.val upstream} remote. - <>") + desc = c( + "i" = ui_pre_glue(" + {.val origin} is a fork of {.val <>}, + which is configured as the {.val upstream} remote."), + read_more() + ) ) ) } @@ -773,14 +803,17 @@ cfg_maybe_fork <- function(cfg) { list( type = "maybe_fork", pr_ready = NA, - desc = ui_pre_glue(" - Both {.val origin} and {.val upstream} appear to be - GitHub repos. However, we can't confirm their relationship to each - other (e.g., fork and fork parent) or your permissions (e.g. push - access). - We may be offline or you may need to configure a GitHub personal access - token. {.run usethis::gh_token_help()} can help with that. - <>") + desc = c( + "!" = ui_pre_glue(" + Both {.val origin} and {.val upstream} appear to be GitHub + repos. However, we can't confirm their relationship to each + other (e.g., fork and fork parent) or your permissions (e.g. + push access)."), + "i" = "We may be offline or you may need to configure a GitHub personal + access token.", + "i" = "{.run usethis::gh_token_help()} can help with that.", + read_more_maybe() + ) ) ) } @@ -791,9 +824,11 @@ cfg_fork_cannot_push_origin <- function(cfg) { list( type = "fork_cannot_push_origin", pr_ready = FALSE, - desc = ui_pre_glue(" - The {.val origin} remote is a fork, but you can't push to it. - <>") + desc = c( + "!" = ui_pre_glue(" + The {.val origin} remote is a fork, but you can't push to it."), + read_more() + ) ) ) } @@ -804,10 +839,12 @@ cfg_fork_upstream_is_not_origin_parent <- function(cfg) { list( type = "fork_upstream_is_not_origin_parent", pr_ready = FALSE, - desc = ui_pre_glue(" - The {.val origin} GitHub remote is a fork, but its parent is - not configured as the {.val upstream} remote. - <>") + desc = c( + "!" = ui_pre_glue(" + The {.val origin} GitHub remote is a fork, but its parent is + not configured as the {.val upstream} remote."), + read_more() + ) ) ) } @@ -818,11 +855,13 @@ cfg_upstream_but_origin_is_not_fork <- function(cfg) { list( type = "upstream_but_origin_is_not_fork", pr_ready = FALSE, - desc = ui_pre_glue(" - Both {.val origin} and {.val upstream} are GitHub remotes, but - {.val origin} is not a fork and, in particular, is not a fork of - {.val upstream}. - <>") + desc = c( + "!" = ui_pre_glue(" + Both {.val origin} and {.val upstream} are GitHub remotes, but + {.val origin} is not a fork and, in particular, is not a fork of + {.val upstream}."), + read_more() + ) ) ) } diff --git a/R/utils-ui.R b/R/utils-ui.R index bdf5dc0fc..efecc7ac2 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -184,3 +184,47 @@ ui_special <- function(x = "unset") { I(glue("{cli::col_grey('<[x]>')}", .open = "[", .close = "]")) } +# questions -------------------------------------------------------------------- +ui_yep <- function(x, + yes = c("Yes", "Definitely", "For sure", "Yup", "Yeah", "I agree", "Absolutely"), + no = c("No way", "Not now", "Negative", "No", "Nope", "Absolutely not"), + n_yes = 1, n_no = 2, shuffle = TRUE, + .envir = parent.frame()) { + #x <- glue_collapse(x, "\n") + #x <- glue(x, .envir = .envir) + + if (!is_interactive()) { + ui_stop(c( + "User input required, but session is not interactive.", + "Query: {x}" + )) + } + + n_yes <- min(n_yes, length(yes)) + n_no <- min(n_no, length(no)) + + qs <- c(sample(yes, n_yes), sample(no, n_no)) + + if (shuffle) { + qs <- sample(qs) + } + + # TODO: should this be ui_inform()? + cli::cli_inform(x, .envir = .envir) + out <- utils::menu(qs) + out != 0L && qs[[out]] %in% yes +} + +ui_nah <- function(x, + yes = c("Yes", "Definitely", "For sure", "Yup", "Yeah", "I agree", "Absolutely"), + no = c("No way", "Not now", "Negative", "No", "Nope", "Absolutely not"), + n_yes = 1, n_no = 2, shuffle = TRUE, + .envir = parent.frame()) { + # TODO(jennybc): is this correct in the case of no selection / cancelling? + !ui_yep( + x = x, yes = yes, no = no, + n_yes = n_yes, n_no = n_no, + shuffle = shuffle, + .envir = .envir + ) +} diff --git a/tests/testthat/_snaps/utils-github.md b/tests/testthat/_snaps/utils-github.md index 828e84fb9..d8d1feb03 100644 --- a/tests/testthat/_snaps/utils-github.md +++ b/tests/testthat/_snaps/utils-github.md @@ -19,8 +19,8 @@ * Config supports a pull request = FALSE * origin = * upstream = - * Desc = Neither "origin" nor "upstream" is a GitHub repo. Read more about the - GitHub remote configurations that usethis supports at: + ! Neither "origin" nor "upstream" is a GitHub repo. + i Read more about the GitHub remote configurations that usethis supports at: . # 'ours' is reported correctly @@ -33,8 +33,8 @@ * Config supports a pull request = TRUE * origin = "OWNER/REPO" (can push) * upstream = - * Desc = "origin" is both the source and primary repo! Read more about the - GitHub remote configurations that usethis supports at: + i "origin" is both the source and primary repo. + i Read more about the GitHub remote configurations that usethis supports at: . # 'theirs' is reported correctly @@ -47,10 +47,10 @@ * Config supports a pull request = FALSE * origin = "OWNER/REPO" (can not push) * upstream = - * Desc = The only configured GitHub remote is "origin", which you cannot push - to. If your goal is to make a pull request, you must fork-and-clone. - `usethis::create_from_github()` can do this. Read more about the GitHub - remote configurations that usethis supports at: + ! The only configured GitHub remote is "origin", which you cannot push to. + i If your goal is to make a pull request, you must fork-and-clone. + i `usethis::create_from_github()` can do this. + i Read more about the GitHub remote configurations that usethis supports at: . # 'fork' is reported correctly @@ -63,9 +63,10 @@ * Config supports a pull request = TRUE * origin = "CONTRIBUTOR/REPO" (can push) = fork of "OWNER/REPO" * upstream = "OWNER/REPO" (can not push) - * Desc = "origin" is a fork of "OWNER/REPO", which is configured as the - "upstream" remote. Read more about the GitHub remote configurations that - usethis supports at: . + i "origin" is a fork of "OWNER/REPO", which is configured as the "upstream" + remote. + i Read more about the GitHub remote configurations that usethis supports at: + . # 'maybe_ours_or_theirs' is reported correctly @@ -77,10 +78,12 @@ * Config supports a pull request = NA * origin = "OWNER/REPO" * upstream = - * Desc = "origin" is a GitHub repo and "upstream" is either not configured or - is not a GitHub repo. We may be offline or you may need to configure a GitHub - personal access token. `usethis::gh_token_help()` can help with that. Read - more about what this GitHub remote configurations means at: + ! "origin" is a GitHub repo and "upstream" is either not configured or is not a + GitHub repo. + i We may be offline or you may need to configure a GitHub personal access + token. + i `usethis::gh_token_help()` can help with that. + i Read more about what this GitHub remote configuration means at: . # 'maybe_fork' is reported correctly @@ -93,11 +96,13 @@ * Config supports a pull request = NA * origin = "CONTRIBUTOR/REPO" * upstream = "OWNER/REPO" - * Desc = Both "origin" and "upstream" appear to be GitHub repos. However, we - can't confirm their relationship to each other (e.g., fork and fork parent) - or your permissions (e.g. push access). We may be offline or you may need to - configure a GitHub personal access token. `usethis::gh_token_help()` can help - with that. Read more about what this GitHub remote configurations means at: + ! Both "origin" and "upstream" appear to be GitHub repos. However, we can't + confirm their relationship to each other (e.g., fork and fork parent) or your + permissions (e.g. push access). + i We may be offline or you may need to configure a GitHub personal access + token. + i `usethis::gh_token_help()` can help with that. + i Read more about what this GitHub remote configuration means at: . # 'fork_cannot_push_origin' is reported correctly @@ -110,8 +115,8 @@ * Config supports a pull request = FALSE * origin = "CONTRIBUTOR/REPO" * upstream = "OWNER/REPO" - * Desc = The "origin" remote is a fork, but you can't push to it. Read more - about the GitHub remote configurations that usethis supports at: + ! The "origin" remote is a fork, but you can't push to it. + i Read more about the GitHub remote configurations that usethis supports at: . # 'fork_upstream_is_not_origin_parent' is reported correctly @@ -124,9 +129,9 @@ * Config supports a pull request = FALSE * origin = "CONTRIBUTOR/REPO" (can push) = fork of "NEW_OWNER/REPO" * upstream = "OLD_OWNER/REPO" (can not push) - * Desc = The "origin" GitHub remote is a fork, but its parent is not configured - as the "upstream" remote. Read more about the GitHub remote configurations - that usethis supports at: + ! The "origin" GitHub remote is a fork, but its parent is not configured as the + "upstream" remote. + i Read more about the GitHub remote configurations that usethis supports at: . # 'upstream_but_origin_is_not_fork' is reported correctly @@ -139,9 +144,9 @@ * Config supports a pull request = FALSE * origin = "CONTRIBUTOR/REPO" * upstream = "OWNER/REPO" - * Desc = Both "origin" and "upstream" are GitHub remotes, but "origin" is not a - fork and, in particular, is not a fork of "upstream". Read more about the - GitHub remote configurations that usethis supports at: + ! Both "origin" and "upstream" are GitHub remotes, but "origin" is not a fork + and, in particular, is not a fork of "upstream". + i Read more about the GitHub remote configurations that usethis supports at: . # 'fork_upstream_is_not_origin_parent' is detected correctly @@ -151,8 +156,41 @@ Condition Error in `stop_bad_github_remote_config()`: x Unsupported GitHub remote configuration: "fork_upstream_is_not_origin_parent" - i Host = "https://github.com" - i origin = "jennybc/gh" (can push) = fork of "r-lib/gh" - i upstream = "r-pkgs/gh" (can push) - i The "origin" GitHub remote is a fork, but its parent is not configured as the "upstream" remote. Read more about the GitHub remote configurations that usethis supports at: . + * Host = "https://github.com" + * origin = "jennybc/gh" (can push) = fork of "r-lib/gh" + * upstream = "r-pkgs/gh" (can push) + ! The "origin" GitHub remote is a fork, but its parent is not configured as the "upstream" remote. + i Read more about the GitHub remote configurations that usethis supports at: + . + +# bad github config error + + Code + stop_bad_github_remote_config(new_fork_upstream_is_not_origin_parent()) + Condition + Error in `stop_bad_github_remote_config()`: + x Unsupported GitHub remote configuration: "fork_upstream_is_not_origin_parent" + * Host = "https://github.com" + * origin = "CONTRIBUTOR/REPO" (can push) = fork of "NEW_OWNER/REPO" + * upstream = "OLD_OWNER/REPO" (can not push) + ! The "origin" GitHub remote is a fork, but its parent is not configured as the "upstream" remote. + i Read more about the GitHub remote configurations that usethis supports at: + . + +# maybe bad github config error + + Code + stop_maybe_github_remote_config(new_maybe_fork()) + Condition + Error in `stop_maybe_github_remote_config()`: + x Pull request functions can't work with GitHub remote configuration: "maybe_fork". + i The most likely problem is that we aren't discovering your GitHub personal access token. + * Host = "https://github.com" + * origin = "CONTRIBUTOR/REPO" + * upstream = "OWNER/REPO" + ! Both "origin" and "upstream" appear to be GitHub repos. However, we can't confirm their relationship to each other (e.g., fork and fork parent) or your permissions (e.g. push access). + i We may be offline or you may need to configure a GitHub personal access token. + i `usethis::gh_token_help()` can help with that. + i Read more about what this GitHub remote configuration means at: + . diff --git a/tests/testthat/test-utils-github.R b/tests/testthat/test-utils-github.R index 2000cdf7f..5875e40a8 100644 --- a/tests/testthat/test-utils-github.R +++ b/tests/testthat/test-utils-github.R @@ -217,3 +217,17 @@ test_that("'fork_upstream_is_not_origin_parent' is detected correctly", { expect_equal(cfg$type, "fork_upstream_is_not_origin_parent") expect_snapshot(error = TRUE, stop_bad_github_remote_config(cfg)) }) + +test_that("bad github config error", { + expect_snapshot( + error = TRUE, + stop_bad_github_remote_config(new_fork_upstream_is_not_origin_parent()) + ) +}) + +test_that("maybe bad github config error", { + expect_snapshot( + error = TRUE, + stop_maybe_github_remote_config(new_maybe_fork()) + ) +}) From f7f5ee0e411e8ffe704e889d9af8e2afa20bc5f8 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 26 Feb 2024 21:25:28 -0800 Subject: [PATCH 082/111] Refactor flaky test --- tests/testthat/test-upkeep.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/testthat/test-upkeep.R b/tests/testthat/test-upkeep.R index 876add3a8..f0da07051 100644 --- a/tests/testthat/test-upkeep.R +++ b/tests/testthat/test-upkeep.R @@ -49,12 +49,12 @@ test_that("upkeep bullets don't change accidentally",{ }) test_that("get extra upkeep bullets works", { - env <- env(upkeep_bullets = function() c("extra", "upkeep bullets")) + e <- new.env(parent = empty_env()) + expect_equal(upkeep_extra_bullets(e), "") + + e$upkeep_bullets <- function() c("extra", "upkeep bullets") expect_equal( - upkeep_extra_bullets(env), + upkeep_extra_bullets(e), c("* [ ] extra", "* [ ] upkeep bullets", "") ) - - env <- NULL - expect_equal(upkeep_extra_bullets(env), "") }) From 25dcfdc1851a2418df69ccd2ded0aaa299a6daed Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 26 Feb 2024 21:41:14 -0800 Subject: [PATCH 083/111] Better organization --- R/utils-ui.R | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/R/utils-ui.R b/R/utils-ui.R index efecc7ac2..c07a3880c 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -1,3 +1,4 @@ +# usethis theme ---------------------------------------------------------------- usethis_theme <- function() { list( # add a "todo" bullet, which is intended to be seen as an unchecked checkbox @@ -35,6 +36,7 @@ quote_if_no_color <- function(x, quote = "'") { } } +# bullets, helpers, and friends ------------------------------------------------ ui_bullets <- function(text, .envir = parent.frame()) { if (is_quiet()) { return(invisible()) @@ -43,23 +45,6 @@ ui_bullets <- function(text, .envir = parent.frame()) { cli::cli_bullets(text, .envir = .envir) } -ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { - cli::cli_div(theme = usethis_theme()) - - nms <- names2(message) - default_nms <- rep_along(message, "i") - default_nms[1] <- "x" - nms <- ifelse(nzchar(nms), nms, default_nms) - names(message) <- nms - - cli::cli_abort( - message, - class = c(class, "usethis_error"), - .envir = .envir, - ... - ) -} - ui_path_impl <- function(x, base = NULL) { is_directory <- is_dir(x) | grepl("/$", x) if (is.null(base)) { @@ -123,7 +108,7 @@ usethis_map_cli <- function(x, ...) UseMethod("usethis_map_cli") usethis_map_cli.default <- function(x, ...) { ui_abort(c( "x" = "Don't know how to {.fun usethis_map_cli} an object of class - {.obj_type_friendlys {x}}." + {.obj_type_friendly {x}}." )) } @@ -184,6 +169,24 @@ ui_special <- function(x = "unset") { I(glue("{cli::col_grey('<[x]>')}", .open = "[", .close = "]")) } +# errors ----------------------------------------------------------------------- +ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { + cli::cli_div(theme = usethis_theme()) + + nms <- names2(message) + default_nms <- rep_along(message, "i") + default_nms[1] <- "x" + nms <- ifelse(nzchar(nms), nms, default_nms) + names(message) <- nms + + cli::cli_abort( + message, + class = c(class, "usethis_error"), + .envir = .envir, + ... + ) +} + # questions -------------------------------------------------------------------- ui_yep <- function(x, yes = c("Yes", "Definitely", "For sure", "Yup", "Yeah", "I agree", "Absolutely"), From f5b380dc9382e21e2591eb8e41b9017c4cf74a2a Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 27 Feb 2024 10:11:00 -0800 Subject: [PATCH 084/111] Catch up on note-taking --- vignettes/articles/ui-cli-conversion.Rmd | 145 +++++++++++++++++------ 1 file changed, 108 insertions(+), 37 deletions(-) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index d56bdc96a..fe68e1cba 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -13,10 +13,6 @@ knitr::opts_chunk$set( library(usethis) ``` -# Review of the *status quo* - -I'll start with a review of the current (soon to be legacy) `usethis::ui_*()` functions. - ## Block styles The block styles mostly exist to produce bulleted output with a specific symbol, using a specific color. @@ -58,7 +54,7 @@ A direct translation looks something like this: | `ui_info()` | `i` | blue (default) -\> yellow | | `ui_line()` | (unnamed) | sort of a perfect match? although sometimes ui_line is used just to get a blank line | -The overall conversion plan is to switch to the new `ui_bullets()`, which is a wrapper around `cli::cli_bullets()`, that adds a few features: +The overall conversion plan is to switch to a new function, `ui_bullets()`, which is a wrapper around `cli::cli_bullets()`, that adds a few features: * Early exit, without emitting messages, if the `usethis.quiet` option is `TRUE`. * A usethis theme, that changes the color of certain bullet styles and adds a new style for todo's. @@ -113,9 +109,9 @@ is_quiet <- function() { } ``` -`ui_todo()`, `ui_done()`, `ui_oops()`, `ui_info()` all route through `ui_bullet()`, which does some hygiene related to indentation (using the `indent()` utility function), then calls `ui_inform()`. -`ui_line()` and `ui_code()` call `ui_inform()` directly. -(I have renamed `ui_bullet()` to `ui_legacy_bullet()` for auto-completion happiness with the new `ui_bullets()`.) +`ui_bullet()` is an intermediate helper used by `ui_todo()`, `ui_done()`, `ui_oops()` and `ui_info()`. +It does some hygiene related to indentation (using the `indent()` utility function), then calls `ui_inform()`. +`ui_line()` and `ui_code()` both call `ui_inform()` directly. `ui_inform()` is just a wrapper around `rlang::inform()` that is guarded by a call to `is_quiet()` @@ -130,7 +126,10 @@ ui_inform <- function(...) { ``` Other than `is_quiet()`, which will continue to play the same role, I anticipate that we no longer need these utilities (`indent()`, `ui_bullet()`, `ui_inform()`). -Update: `indent()` turns out to still be useful in `ui_code_snippet()`. +Updates from the future: + +* `indent()` turns out to still be useful in `ui_code_snippet()`. +* `ui_bullet()` has been renamed to `ui_legacy_bullet()` for auto-completion happiness with the new `ui_bullets()`. Let's cover `ui_silence()` while we're here, which *is* exported. It's just a `withr::with_*()` function for executing `code` with `usethis.quiet = TRUE`. @@ -144,11 +143,50 @@ ui_silence <- function(code) { ## Inline styles +### Legacy functions + +usethis has its own inline styles (mostly) for use inside functions like `ui_todo()` + +* `ui_field()` +* `ui_value()` +* `ui_path()` +* `ui_code()` +* `ui_unset()` + +```{r} +new_val <- "oxnard" +x <- glue("{ui_field('name')} set to {ui_value(new_val)}") +dput(x) +ui_done(x) +``` + +The inline styles enact some combination of: + +* Color, e.g. `crayon::green(x)` +* Collapsing a collection of things to one thing, e.g. `c("a", "b", "c")` to "a, b, c" +* Quoting, e.g. `encodeString(x, quote = "'")` + +`ui_path()` is special because it potentially modifies the input before styling it. +`up_path()` first makes the path relative to a specific base (by default, the active project root) and, if the path is a directory, it also ensures there is a trailing `/`. + +`ui_unset()` is a special purpose helper used when we need to report that something is unknown, not configured, nonexistent, etc. + +```{r} +x <- glue("Your super secret password is {ui_unset()}.") +dput(x) +ui_info(x) +``` + +### cli replacements + +In general, we can move towards cli's native inline-markup: -- `ui_field()` becomes `{.field blah}`. TO THINK ABOUT: I'm not seeing single quotes in the 'plain' snapshots, but I think we will want quoting if there's no color. -- `ui_value()` becomes `{.val value}` -- `ui_path()` is connected to `{.path path/to/thing}`, but `ui_path()` also has some legitimately useful logic, such as making a path relative to project root or ensuring a directory has a trailing `/`. Therefore, this has been abstracted into `ui_path_impl()`, which is aliased to `pth()` for compactness. Here's a typical conversion: +Here's the general conversion plan: + +- `ui_field()` becomes `{.field blah}`. In `usethis_theme()`, I tweak the `.field` style to apply single quotes if color is not available, which is what `ui_field()` has always done. +- `ui_value()` becomes `{.val value}`. +- `ui_path()` is connected to `{.path path/to/thing}`, but, as explained above, `ui_path()` also does more. Therefore, I abstracted the "path math" into an internal helper, `ui_path_impl()`, which is aliased to `pth()` for compactness. Here's a typical conversion: ```{r} #| eval: false # using legacy functions @@ -159,24 +197,19 @@ ui_bullets(c( "v" = "Setting {.field LazyData} to {.val true} in {.path {pth('DESCRIPTION')}}." )) ``` -It would be nice to create a custom inline class, e.g. `{.usethis_path {some_path}}`, which I have done in, e.g., googledrive. But it's not easy to do this *while still inheriting cli's `file:` hyperlink behaviour*, which is very desirable. So that leads to the somewhat clunky pattern above, but it gives a nice result. -- `ui_code()` gets replaced by various inline styles, depending on what the actual goal is: +It would be nice to create a custom inline class, e.g. `{.ui_path {some_path}}`, which I have done in, e.g., googledrive. +But it's not easy to do this *while still inheriting cli's `file:` hyperlink behaviour*, which is very desirable. +So that leads to the somewhat clunky pattern above, but it gives a nice result. +- `ui_code()` gets replaced by various inline styles, depending on what the actual goal is, such as: + - `{.code some_text}` - `{.arg some_argument}` - `{.cls some_class}` - - `{.code some_text}` - - `{.envvar SOME_ENV_VAR}` - `{.fun devtools::build_readme}` - `{.help some_function}` - - `{.run some_code}` + - `{.run usethis::usethis_function()}` - `{.topic some_topic}` - - `{.var variable_name}` - -Inline styles that don't get quoted in the absence of color and that I'm worried about: - -* ~`.field`~ I've fixed this in the theme -* `.pkg` - -(I'll talk about `ui_unset()` below.) + - `{.var some_variable}` +- `ui_unset()` is replaced by `ui_special()`, which you'll see more of below. ## Conditions @@ -211,10 +244,9 @@ ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { } ``` -At this point, the `usethis_theme()` doesn't do anything that affects the styling of an error. -So the main point of `ui_abort()` is to use to `cli_abort()` (and to continue applying the `"usethis_error"` class). +The main point of `ui_abort()` is to use to `cli_abort()` (and to continue applying the `"usethis_error"` class). -I also use `ui_abort()` to apply different default bullet styling. +I also use `ui_abort()` to apply different default bullet naming/styling. Starting with `"x"` and then defaulting to `"i"` seems to fit best with usethis's existing errors. ```{r} @@ -229,7 +261,7 @@ ui_abort(c( Any bullets that are explicitly given are honored. ```{r} -ui_abort(c("v" = "Weird thing to do in an error, but whatever.")) +ui_abort(c("v" = "It's weird to give a green check in an error, but whatever.")) ui_abort(c( "!" = "Things are not ideal.", ">" = "Look at me!" @@ -249,19 +281,59 @@ ui_warn <- function(x, .envir = parent.frame()) { } ``` -It has very little usage and, instead of converting it, I'm going to eliminate its use altogether in favor of `ui_bullets(c("!" = "Thing I'm warning about.")) -`. +It has very little usage and, instead of converting it, I've eliminated its use altogether in favor of `ui_bullets(c("!" = "Thing I'm warning about."))`. -Now that I'm looking at a lot of the new errors with `ui_abort()` I realize that usethis also needs to be passing the `call` argument along. -I'm not going to tackle this in the main conversion. +Sidebar: Now that I'm looking at a lot of the new errors with `ui_abort()` I realize that usethis also needs to be passing the `call` argument along. +I'm going to leave that for a future, separate effort. ## Sitrep and format helpers This is a small clump of functions that support sitrep-type output. - `hd_line()` *unexported and, apparently, unused! now removed* -- `kv_line()` *unexported* -- `ui_unset()` *exported* +- `kv_line()` *unexported, so has new cli implementation* +- `ui_unset()` *exported and succeeded by `ui_special()`* + +`kv_line()` stands for "key-value line". +Here's what it used to be: + +```{r} +kv_line_legacy <- function(key, value, .envir = parent.frame()) { + value <- if (is.null(value)) ui_unset() else ui_value(value) + key <- glue(key, .envir = .envir) + ui_inform(glue("{cli::symbol$bullet} {key}: {value}")) +} + +url <- "https://github.com/r-lib/usethis.git" +remote <- "origin" +kv_line_legacy("URL for the {ui_value(remote)} remote", url) + +host <- "github.com" +kv_line_legacy("Personal access token for {ui_value(host)}", NULL) +``` + +Key features: + +* Interpolates data and applies inline style to the result. + Works differently for `key` and `value`. +* Has special handling when `value` is `NULL`. +* Applies `"*"` bullet name/style to over all result. + +I won't show the updated source for `kv_line()` but here is some usage to show what it's capable of: + +```{r} +noun <- "thingy" +value <- "VALUE" +kv_line("Key associated with {.val {noun}}", value) + +kv_line("URL for the {.val {remote}} remote", I("{.url {url}})")) + +kv_line("Personal access token for {ui_value(host)}", NULL) + +kv_line("Personal access token for {ui_value(host)}", ui_special("discovered")) +``` + +`ui_unset()` is replaced by `ui_special()`. ## Questions @@ -280,7 +352,7 @@ if (ui_nope(" If I create new versions of `ui_yeah()` and `ui_nope()`, then I could still switch over to cli's inline styling. Otherwise, I won't be able to remove all internal usage of legacy `ui_*()` functions. -## Miscellany +## Miscellaneous notes * I've been adding a period to the end of messages, as a general rule. * In terms of whitespace and indentation, I've settled on some conventions: @@ -306,7 +378,6 @@ ui_abort(" deleted.") ``` - TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. Ditto for `target_repo()` in utils-github.R. TODO: come back to this bit in git.R: ` purrr::walk(format(cfg), ui_bullet)` From de7e066a98f51d1ce1d72a26c0ece2b5332bda02 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Tue, 27 Feb 2024 15:46:09 -0800 Subject: [PATCH 085/111] Work on making the article actually render the way I want --- vignettes/articles/ui-cli-conversion.Rmd | 244 ++++++++++++++--------- 1 file changed, 154 insertions(+), 90 deletions(-) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index fe68e1cba..83a3740c4 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -5,20 +5,54 @@ title: "Converting usethis's UI to use cli" ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>" + comment = "", + out.width = "100%", + #asciicast_knitr_output = "html", + asciicast_theme = "solarized-light", # tango, solarized-dark, solarized-light + asciicast_cols = 72 +) +asciicast::init_knitr_engine( + #echo = TRUE, + #echo_input = FALSE, + startup = quote({ + library(usethis) + library(glue) + options(cli.num_colors = 256) + ui_code_snippet <- usethis:::ui_code_snippet + usethis_theme <- usethis:::usethis_theme + ui_inform <- usethis:::ui_inform + kv_line <- usethis:::kv_line + ui_special <- usethis:::ui_special + ui_abort <- usethis:::ui_abort + ui_bullets <- usethis:::ui_bullets + set.seed(1) }) ) ``` ```{r setup} library(usethis) +library(glue) +``` + +*In a hidden chunk here, I'm "exporting" some unexported internal helpers, so that I can use them and talk about them. For similar reasons, I attach glue above, so that certain glue functions work here, without explicitly namespacing them.* + +```{r} +#| include: false +ui_code_snippet <- usethis:::ui_code_snippet +usethis_theme <- usethis:::usethis_theme +ui_inform <- usethis:::ui_inform +kv_line <- usethis:::kv_line +ui_special <- usethis:::ui_special +ui_abort <- usethis:::ui_abort +ui_bullets <- usethis:::ui_bullets ``` ## Block styles -The block styles mostly exist to produce bulleted output with a specific symbol, using a specific color. -All of this output can be turned off package-wide via the `usethis.quiet` option. +The block styles exist to produce bulleted output with a specific symbol, using a specific color. -```{r} +```{asciicast} +#| collapse: false f <- function() { ui_todo("ui_todo(): red bullet") ui_done("ui_done(): green check") @@ -29,9 +63,22 @@ f <- function() { f() ``` +Another important feature is that all of this output can be turned off package-wide via the `usethis.quiet` option. + +```{asciicast} +withr::with_options( + list(usethis.quiet = TRUE), + ui_info("You won't see this message.") +) +withr::with_options( + list(usethis.quiet = FALSE), # this is the default + ui_info("But you will see this one.") +) +``` + These styles are very close to what can be done with `cli::cli_bullets()` and the way it responds to the names of its input `text`. -```{r} +```{asciicast} cli::cli_bullets(c( "noindent", " " = "indent", @@ -44,21 +91,33 @@ cli::cli_bullets(c( )) ``` -A direct translation looks something like this: +A direct translation would look something like this: -| `ui_*()` | `cli_bullets()` shortcode | tweaks needed | -|-------------|---------------------------|-------------------------------| -| `ui_todo()` | `*` | blue (default) -\> red | -| `ui_done()` | `v` | perfect match | -| `ui_oops()` | `x` | perfect match | -| `ui_info()` | `i` | blue (default) -\> yellow | -| `ui_line()` | (unnamed) | sort of a perfect match? although sometimes ui_line is used just to get a blank line | +| Legacy `ui_*()` | `cli_bullets()` shortcode | tweaks needed | +|-----------------|---------------------------|-------------------------------| +| `ui_todo()` | `*` | blue (default) -\> red | +| `ui_done()` | `v` | perfect match | +| `ui_oops()` | `x` | perfect match | +| `ui_info()` | `i` | blue (default) -\> yellow | +| `ui_line()` | (unnamed) | sort of a perfect match? although sometimes `ui_line()` is used just to get a blank line | The overall conversion plan is to switch to a new function, `ui_bullets()`, which is a wrapper around `cli::cli_bullets()`, that adds a few features: * Early exit, without emitting messages, if the `usethis.quiet` option is `TRUE`. * A usethis theme, that changes the color of certain bullet styles and adds a new style for todo's. +```{asciicast} +ui_bullets(c( + "v" = "A great success!", + "_" = "Something you need to do.", + "x" = "Bad news.", + "i" = "The more you know.", + " " = "I'm just here for the indentation.", + "No indentation at all. Not used much in usethis." +)) +``` + + Summary of what I've done for todo's: - Introduce a new bullet shortcode for a todo. Proposal: `_` (instead of `*`), which seems to be the best single ascii character that evokes a place to check something off. @@ -70,27 +129,26 @@ I have created a new function, `ui_code_snippet()` that is built around `cli::co Main observations: * `cli::code_block(language = "R")` applies syntax highlighting and hyperlinks (e.g. to function help topics) to R code, which is cool. Therefore the `language` argument is also exposed in `ui_code_snippet()`, defaulting to `"R"`. Use `""` for anything that's not R code: -```{r} -#| eval: true -ui_code_snippet("x <- 1 + 2") -ui_code_snippet("#include ", language = "") -``` -* `ui_code_snippet()` takes a scalar glue-type template string or a vector of lines. -```{r} -ui_code_snippet(" - options( - warnPartialMatchArgs = TRUE, - warnPartialMatchDollar = TRUE, - warnPartialMatchAttr = TRUE - )") -# produces same result as -ui_code_snippet(c( - "options(", - " warnPartialMatchArgs = TRUE,", - " warnPartialMatchDollar = TRUE,", - " warnPartialMatchAttr = TRUE", - ")")) -``` + ```{asciicast} + ui_code_snippet("x <- 1 + 2") + ui_code_snippet("#include ", language = "") + ``` +* `ui_code_snippet()` takes a scalar glue-type template string or a vector of lines. Note that the two calls below produce the same output. + ```{asciicast} + ui_code_snippet(" + options( + warnPartialMatchArgs = TRUE, + warnPartialMatchDollar = TRUE, + warnPartialMatchAttr = TRUE + )") + # produces same result as + ui_code_snippet(c( + "options(", + " warnPartialMatchArgs = TRUE,", + " warnPartialMatchDollar = TRUE,", + " warnPartialMatchAttr = TRUE", + ")")) + ``` * `ui_code_snippet()` does glue interpolation, by default, before calling `cli::cli_code()`, which means you have to think about your use of `{` and `}`. If you want literal `{` or `}`: - Use `interpolate = FALSE`, if you don't need interpolation. - Do the usual glue thing and double them, i.e. `{{` or `}}`. @@ -145,7 +203,7 @@ ui_silence <- function(code) { ### Legacy functions -usethis has its own inline styles (mostly) for use inside functions like `ui_todo()` +usethis has its own inline styles (mostly) for use inside functions like `ui_todo()`: * `ui_field()` * `ui_value()` @@ -153,7 +211,8 @@ usethis has its own inline styles (mostly) for use inside functions like `ui_tod * `ui_code()` * `ui_unset()` -```{r} +```{asciicast} +# why is this block truncated from the top in the rendered document? new_val <- "oxnard" x <- glue("{ui_field('name')} set to {ui_value(new_val)}") dput(x) @@ -167,11 +226,12 @@ The inline styles enact some combination of: * Quoting, e.g. `encodeString(x, quote = "'")` `ui_path()` is special because it potentially modifies the input before styling it. -`up_path()` first makes the path relative to a specific base (by default, the active project root) and, if the path is a directory, it also ensures there is a trailing `/`. +`ui_path()` first makes the path relative to a specific base (by default, the active project root) and, if the path is a directory, it also ensures there is a trailing `/`. `ui_unset()` is a special purpose helper used when we need to report that something is unknown, not configured, nonexistent, etc. -```{r} +```{asciicast} +# why is this block truncated from the top in the rendered document? x <- glue("Your super secret password is {ui_unset()}.") dput(x) ui_info(x) @@ -187,19 +247,19 @@ Here's the general conversion plan: - `ui_field()` becomes `{.field blah}`. In `usethis_theme()`, I tweak the `.field` style to apply single quotes if color is not available, which is what `ui_field()` has always done. - `ui_value()` becomes `{.val value}`. - `ui_path()` is connected to `{.path path/to/thing}`, but, as explained above, `ui_path()` also does more. Therefore, I abstracted the "path math" into an internal helper, `ui_path_impl()`, which is aliased to `pth()` for compactness. Here's a typical conversion: -```{r} -#| eval: false -# using legacy functions -ui_done("Setting {ui_field('LazyData')} to \\ - {ui_value('true')} in {ui_path('DESCRIPTION')}") -# using new cli-based ui -ui_bullets(c( - "v" = "Setting {.field LazyData} to {.val true} in {.path {pth('DESCRIPTION')}}." -)) -``` -It would be nice to create a custom inline class, e.g. `{.ui_path {some_path}}`, which I have done in, e.g., googledrive. -But it's not easy to do this *while still inheriting cli's `file:` hyperlink behaviour*, which is very desirable. -So that leads to the somewhat clunky pattern above, but it gives a nice result. + ```{r} + #| eval: false + # using legacy functions + ui_done("Setting {ui_field('LazyData')} to \\ + {ui_value('true')} in {ui_path('DESCRIPTION')}") + # using new cli-based ui + ui_bullets(c( + "v" = "Setting {.field LazyData} to {.val true} in {.path {pth('DESCRIPTION')}}." + )) + ``` + It would be nice to create a custom inline class, e.g. `{.ui_path {some_path}}`, which I have done in, e.g., googledrive. + But it's not easy to do this *while still inheriting cli's `file:` hyperlink behaviour*, which is very desirable. + So that leads to the somewhat clunky, verbose pattern above, but it gives a nice result. - `ui_code()` gets replaced by various inline styles, depending on what the actual goal is, such as: - `{.code some_text}` - `{.arg some_argument}` @@ -209,13 +269,14 @@ So that leads to the somewhat clunky pattern above, but it gives a nice result. - `{.run usethis::usethis_function()}` - `{.topic some_topic}` - `{.var some_variable}` -- `ui_unset()` is replaced by `ui_special()`, which you'll see more of below. +- `ui_unset()` is replaced by `ui_special()`, which you'll see more of below. Currently the intended grey color doesn't show up when I render this document using solarized-dark and so far I can't get to the bottom of that :( Why isn't it the same grey as "[Copied to clipboard]" in `ui_code_snippet()`, which does work? ## Conditions I'm moving from `ui_stop()`: ```{r} +#| eval: false ui_stop <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") x <- glue(x, .envir = .envir) @@ -232,9 +293,10 @@ ui_stop <- function(x, .envir = parent.frame()) { to `ui_abort()`: ```{r} +#| eval: false ui_abort <- function(message, ..., class = NULL, .envir = parent.frame()) { cli::cli_div(theme = usethis_theme()) - # bullet naming stuff, see below + # bullet naming gymnastics, see below cli::cli_abort( message, class = c(class, "usethis_error"), @@ -249,7 +311,9 @@ The main point of `ui_abort()` is to use to `cli_abort()` (and to continue apply I also use `ui_abort()` to apply different default bullet naming/styling. Starting with `"x"` and then defaulting to `"i"` seems to fit best with usethis's existing errors. -```{r} +```{asciicast} +#| error: true +# why is this block truncated from the top in the rendered document? block_start = "# <<<" block_end = "# >>>" ui_abort(c( @@ -260,7 +324,8 @@ ui_abort(c( Any bullets that are explicitly given are honored. -```{r} +```{asciicast} +#| error: true ui_abort(c("v" = "It's weird to give a green check in an error, but whatever.")) ui_abort(c( "!" = "Things are not ideal.", @@ -270,19 +335,13 @@ ui_abort(c( `rlang::abort()` and `cli::cli_abort()` start with `"!"` by default, then use `"*"` and `" "`, respectively. -The legacy functions include `ui_warn()`: - -```{r} -ui_warn <- function(x, .envir = parent.frame()) { - x <- glue_collapse(x, "\n") - x <- glue(x, .envir = .envir) +The legacy functions also include `ui_warn()`. +It has very little usage and, instead of converting it, I've eliminated its use altogether in favor of a `"!"` bullet: - warning(x, call. = FALSE, immediate. = TRUE) -} +```{asciicast} +ui_bullets(c("!" = "The guy she told you not to worry about.")) ``` -It has very little usage and, instead of converting it, I've eliminated its use altogether in favor of `ui_bullets(c("!" = "Thing I'm warning about."))`. - Sidebar: Now that I'm looking at a lot of the new errors with `ui_abort()` I realize that usethis also needs to be passing the `call` argument along. I'm going to leave that for a future, separate effort. @@ -297,7 +356,7 @@ This is a small clump of functions that support sitrep-type output. `kv_line()` stands for "key-value line". Here's what it used to be: -```{r} +```{asciicast} kv_line_legacy <- function(key, value, .envir = parent.frame()) { value <- if (is.null(value)) ui_unset() else ui_value(value) key <- glue(key, .envir = .envir) @@ -315,25 +374,28 @@ kv_line_legacy("Personal access token for {ui_value(host)}", NULL) Key features: * Interpolates data and applies inline style to the result. - Works differently for `key` and `value`. + Works differently for `key` and `value`, because you're much more likely to use interpolation and styling in `key` than `value`. * Has special handling when `value` is `NULL`. * Applies `"*"` bullet name/style to over all result. I won't show the updated source for `kv_line()` but here is some usage to show what it's capable of: -```{r} +```{asciicast} +# why is this block truncated from the top in the rendered document? noun <- "thingy" value <- "VALUE" kv_line("Key associated with {.val {noun}}", value) -kv_line("URL for the {.val {remote}} remote", I("{.url {url}})")) +kv_line("URL for the {.val {remote}} remote", I("{.url {url}}")) -kv_line("Personal access token for {ui_value(host)}", NULL) +kv_line("Personal access token for {.val {host}}", NULL) -kv_line("Personal access token for {ui_value(host)}", ui_special("discovered")) +kv_line("Personal access token for {.val {host}}", ui_special("discovered")) + +x <- kv_line("Personal access token for {.val {host}}", ui_special("discovered")) ``` -`ui_unset()` is replaced by `ui_special()`. +`ui_special()` is the successor to `ui_unset()`. ## Questions @@ -355,28 +417,30 @@ If I create new versions of `ui_yeah()` and `ui_nope()`, then I could still swit ## Miscellaneous notes * I've been adding a period to the end of messages, as a general rule. -* In terms of whitespace and indentation, I've settled on some conventions: -```{r} -#| eval: false -ui_bullets(c( - "i" = "Downloading into {.path {pth(destdir)}}.", - "_" = "Prefer a different location? Cancel, try again, and specify - {.arg destdir}." -)) -... -ui_bullets(c("x" = "Things are very wrong.")) -``` +* In terms of whitespace and indentation, I've settled on some conventions. + The overall goal is to get the right user-facing output (obviously), while making it as easy as possible to _predict_ what that's going to look like when you're writing the code. + ```{r} + #| eval: false + ui_bullets(c( + "i" = "Downloading into {.path {pth(destdir)}}.", + "_" = "Prefer a different location? Cancel, try again, and specify + {.arg destdir}." + )) + ... + ui_bullets(c("x" = "Things are very wrong.")) + ``` Key points: - Put `ui_bullets(c(` on its own line, then all of the bullet items, followed by `))` on its own line. Sometimes I make an exception for a bullet list with exactly one, short bullet item. - Use hard line breaks inside bullet text to comply with surrounding line length. In subsequent lines, use indentation to get you just past the opening `"`. This extraneous white space is later rationalized by cli, which handles wrapping. - Surround bullet names like `x` and `i` with quotes, even though you don't have to, because it's required for other names, such as `!` or `_` and it's better to be consistent. - - Here's another style that applies to `ui_abort()`, where there's just one, unnamed bullet, but the call doesn't fit on one line. -```{r} -pr <- list(pr_number = 13) -ui_abort(" - The repo or branch where PR #{pr$pr_number} originates seems to have been - deleted.") -``` + - Here's another style I like that applies to `ui_abort()`, where there's just one, unnamed bullet, but the call doesn't fit on one line. + ```{r} + #| eval: false + pr <- list(pr_number = 13) + ui_abort(" + The repo or branch where PR #{pr$pr_number} originates seems to have been + deleted.") + ``` TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. Ditto for `target_repo()` in utils-github.R. From 86b3278d433457c06676f373fcd069bb317e16cf Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 28 Feb 2024 11:41:35 -0800 Subject: [PATCH 086/111] Mark ui_*() functions as superseded --- R/{ui.R => ui-legacy.R} | 70 +++++++++++++++------------ R/utils-ui.R | 20 ++++++++ _pkgdown.yml | 3 +- man/{ui.Rd => ui-legacy-functions.Rd} | 42 ++++++++-------- man/ui-questions.Rd | 10 ++-- man/ui_silence.Rd | 25 ++++++++++ 6 files changed, 108 insertions(+), 62 deletions(-) rename R/{ui.R => ui-legacy.R} (82%) rename man/{ui.Rd => ui-legacy-functions.Rd} (68%) create mode 100644 man/ui_silence.Rd diff --git a/R/ui.R b/R/ui-legacy.R similarity index 82% rename from R/ui.R rename to R/ui-legacy.R index 124859743..17bab16a5 100644 --- a/R/ui.R +++ b/R/ui-legacy.R @@ -1,9 +1,22 @@ -#' User interface +#' Legacy functions related to user interface #' #' @description -#' These functions are used to construct the user interface of usethis. Use -#' them in your own package so that your `use_` functions work the same way -#' as usethis. +#' `r lifecycle::badge("superseded")` +#' + +#' These functions are now superseded. External users of the `usethis::ui_*()` +#' functions are encouraged to use the [cli package](https://cli.r-lib.org/) +#' instead. The cli package did not have the required functionality when the +#' `usethis::ui_*()` functions were created, but it has had that for a while +#' now and it's the superior option. There is even a cli vignette about how to +#' make this transition: `vignette("usethis-ui", package = "cli")`. +#' +#' usethis itself now uses cli internally for its UI, but these new functions +#' are not exported and presumably never will be. There is a developer-focused +#' article on the process of transitioning usethis's own UI to use cli (LINK +#' TO COME). + +#' @details #' #' The `ui_` functions can be broken down into four main categories: #' @@ -17,9 +30,8 @@ #' The question functions [ui_yeah()] and [ui_nope()] have their own [help #' page][ui-questions]. #' -#' @section Silencing output: #' All UI output (apart from `ui_yeah()`/`ui_nope()` prompts) can be silenced -#' by setting `options(usethis.quiet = TRUE)`. Use `ui_silence()` to silence +#' by setting `options(usethis.quiet = TRUE)`. Use [ui_silence()] to silence #' selected actions. #' #' @param x A character vector. @@ -34,8 +46,7 @@ #' @return The block styles, conditions, and questions are called for their #' side-effect. The inline styles return a string. #' @keywords internal -#' @family user interface functions -#' @name ui +#' @name ui-legacy-functions #' @examples #' new_val <- "oxnard" #' ui_done("{ui_field('name')} set to {ui_value(new_val)}") @@ -50,7 +61,7 @@ NULL # Block styles ------------------------------------------------------------ -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_line <- function(x = character(), .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -58,7 +69,7 @@ ui_line <- function(x = character(), .envir = parent.frame()) { ui_inform(x) } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_todo <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -66,7 +77,7 @@ ui_todo <- function(x, .envir = parent.frame()) { ui_legacy_bullet(x, crayon::red(cli::symbol$bullet)) } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_done <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -74,7 +85,7 @@ ui_done <- function(x, .envir = parent.frame()) { ui_legacy_bullet(x, crayon::green(cli::symbol$tick)) } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_oops <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -82,7 +93,7 @@ ui_oops <- function(x, .envir = parent.frame()) { ui_legacy_bullet(x, crayon::red(cli::symbol$cross)) } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_info <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -92,7 +103,7 @@ ui_info <- function(x, .envir = parent.frame()) { #' @param copy If `TRUE`, the session is interactive, and the clipr package #' is installed, will copy the code block to the clipboard. -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_code_block <- function(x, copy = rlang::is_interactive(), @@ -113,7 +124,7 @@ ui_code_block <- function(x, # Conditions -------------------------------------------------------------- -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_stop <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -127,7 +138,7 @@ ui_stop <- function(x, .envir = parent.frame()) { stop(cnd) } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_warn <- function(x, .envir = parent.frame()) { x <- glue_collapse(x, "\n") @@ -137,24 +148,16 @@ ui_warn <- function(x, .envir = parent.frame()) { } -# Silence ----------------------------------------------------------------- - -#' @rdname ui -#' @param code Code to execute with usual UI output silenced. -#' @export -ui_silence <- function(code) { - withr::with_options(list(usethis.quiet = TRUE), code) -} # Questions --------------------------------------------------------------- #' User interface - Questions #' #' These functions are used to interact with the user by posing a simple yes or -#' no question. For details on the other `ui_*()` functions, see the [ui] help -#' page. +#' no question. For details on the other `ui_*()` functions, see the +#' [ui-legacy-functions] help page. #' -#' @inheritParams ui +#' @inheritParams ui-legacy-functions #' @param yes A character vector of "yes" strings, which are randomly sampled to #' populate the menu. #' @param no A character vector of "no" strings, which are randomly sampled to @@ -206,6 +209,9 @@ ui_yeah <- function(x, } # TODO: should this be ui_inform()? + # later observation: probably not? you would not want these prompts to be + # suppressed when `usethis.quiet = TRUE`, i.e. if the menu() appears, then + # the introduction should also always appear rlang::inform(x) out <- utils::menu(qs) out != 0L && qs[[out]] %in% yes @@ -229,7 +235,7 @@ ui_nope <- function(x, # Inline styles ----------------------------------------------------------- -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_field <- function(x) { x <- crayon::green(x) @@ -237,7 +243,7 @@ ui_field <- function(x) { x } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_value <- function(x) { if (is.character(x)) { @@ -248,14 +254,14 @@ ui_value <- function(x) { x } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export #' @param base If specified, paths will be displayed relative to this path. ui_path <- function(x, base = NULL) { ui_value(ui_path_impl(x, base = base)) } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_code <- function(x) { x <- encodeString(x, quote = "`") @@ -264,7 +270,7 @@ ui_code <- function(x) { x } -#' @rdname ui +#' @rdname ui-legacy-functions #' @export ui_unset <- function(x = "unset") { check_string(x) diff --git a/R/utils-ui.R b/R/utils-ui.R index c07a3880c..1672abef8 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -36,6 +36,26 @@ quote_if_no_color <- function(x, quote = "'") { } } +# silence ----------------------------------------------------------------- +#' Suppress usethis's messaging +#' +#' Execute a bit of code without usethis's normal messaging. +#' +#' @param code Code to execute with usual UI output silenced. +#' +#' @returns Whatever `code` returns. +#' @export +#' @examples +#' # compare the messaging you see from this: +#' browse_github("usethis") +#' # vs. this: +#' ui_silence( +#' browse_github("usethis") +#' ) +ui_silence <- function(code) { + withr::with_options(list(usethis.quiet = TRUE), code) +} + # bullets, helpers, and friends ------------------------------------------------ ui_bullets <- function(text, .envir = parent.frame()) { if (is_quiet()) { diff --git a/_pkgdown.yml b/_pkgdown.yml index 9b984ec66..b04f4fe49 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -103,10 +103,11 @@ reference: - matches("tidy") - title: Configuration desc: > - Configure the behaviour of R or RStudio, globally as a user or + Configure the behaviour of R or RStudio or usethis, globally as a user or for a specific project contents: - usethis_options + - ui_silence - use_blank_slate - use_devtools - use_usethis diff --git a/man/ui.Rd b/man/ui-legacy-functions.Rd similarity index 68% rename from man/ui.Rd rename to man/ui-legacy-functions.Rd index 2bbe5b32d..1a1976122 100644 --- a/man/ui.Rd +++ b/man/ui-legacy-functions.Rd @@ -1,7 +1,7 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ui.R -\name{ui} -\alias{ui} +% Please edit documentation in R/ui-legacy.R +\name{ui-legacy-functions} +\alias{ui-legacy-functions} \alias{ui_line} \alias{ui_todo} \alias{ui_done} @@ -10,13 +10,12 @@ \alias{ui_code_block} \alias{ui_stop} \alias{ui_warn} -\alias{ui_silence} \alias{ui_field} \alias{ui_value} \alias{ui_path} \alias{ui_code} \alias{ui_unset} -\title{User interface} +\title{Legacy functions related to user interface} \usage{ ui_line(x = character(), .envir = parent.frame()) @@ -34,8 +33,6 @@ ui_stop(x, .envir = parent.frame()) ui_warn(x, .envir = parent.frame()) -ui_silence(code) - ui_field(x) ui_value(x) @@ -60,8 +57,6 @@ environment. For expert use only.} \item{copy}{If \code{TRUE}, the session is interactive, and the clipr package is installed, will copy the code block to the clipboard.} -\item{code}{Code to execute with usual UI output silenced.} - \item{base}{If specified, paths will be displayed relative to this path.} } \value{ @@ -69,10 +64,21 @@ The block styles, conditions, and questions are called for their side-effect. The inline styles return a string. } \description{ -These functions are used to construct the user interface of usethis. Use -them in your own package so that your \code{use_} functions work the same way -as usethis. - +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} + +These functions are now superseded. External users of the \verb{usethis::ui_*()} +functions are encouraged to use the \href{https://cli.r-lib.org/}{cli package} +instead. The cli package did not have the required functionality when the +\verb{usethis::ui_*()} functions were created, but it has had that for a while +now and it's the superior option. There is even a cli vignette about how to +make this transition: \code{vignette("usethis-ui", package = "cli")}. + +usethis itself now uses cli internally for its UI, but these new functions +are not exported and presumably never will be. There is a developer-focused +article on the process of transitioning usethis's own UI to use cli (LINK +TO COME). +} +\details{ The \code{ui_} functions can be broken down into four main categories: \itemize{ \item block styles: \code{ui_line()}, \code{ui_done()}, \code{ui_todo()}, \code{ui_oops()}, @@ -84,14 +90,11 @@ The \code{ui_} functions can be broken down into four main categories: } The question functions \code{\link[=ui_yeah]{ui_yeah()}} and \code{\link[=ui_nope]{ui_nope()}} have their own \link[=ui-questions]{help page}. -} -\section{Silencing output}{ All UI output (apart from \code{ui_yeah()}/\code{ui_nope()} prompts) can be silenced -by setting \code{options(usethis.quiet = TRUE)}. Use \code{ui_silence()} to silence +by setting \code{options(usethis.quiet = TRUE)}. Use \code{\link[=ui_silence]{ui_silence()}} to silence selected actions. } - \examples{ new_val <- "oxnard" ui_done("{ui_field('name')} set to {ui_value(new_val)}") @@ -103,9 +106,4 @@ ui_code_block(c( "Line 3" )) } -\seealso{ -Other user interface functions: -\code{\link{ui-questions}} -} -\concept{user interface functions} \keyword{internal} diff --git a/man/ui-questions.Rd b/man/ui-questions.Rd index db32d71a0..50ea582fb 100644 --- a/man/ui-questions.Rd +++ b/man/ui-questions.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/ui.R +% Please edit documentation in R/ui-legacy.R \name{ui-questions} \alias{ui-questions} \alias{ui_yeah} @@ -58,8 +58,8 @@ of \code{ui_yeah()}. } \description{ These functions are used to interact with the user by posing a simple yes or -no question. For details on the other \verb{ui_*()} functions, see the \link{ui} help -page. +no question. For details on the other \verb{ui_*()} functions, see the +\link{ui-legacy-functions} help page. } \examples{ \dontrun{ @@ -68,9 +68,5 @@ ui_nope("Have you tried turning it off and on again?", n_yes = 1, n_no = 1) ui_yeah("Are you sure its plugged in?", yes = "Yes", no = "No", shuffle = FALSE) } } -\seealso{ -Other user interface functions: -\code{\link{ui}} -} \concept{user interface functions} \keyword{internal} diff --git a/man/ui_silence.Rd b/man/ui_silence.Rd new file mode 100644 index 000000000..42da92845 --- /dev/null +++ b/man/ui_silence.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils-ui.R +\name{ui_silence} +\alias{ui_silence} +\title{Suppress usethis's messaging} +\usage{ +ui_silence(code) +} +\arguments{ +\item{code}{Code to execute with usual UI output silenced.} +} +\value{ +Whatever \code{code} returns. +} +\description{ +Execute a bit of code without usethis's normal messaging. +} +\examples{ +# compare the messaging you see from this: +browse_github("usethis") +# vs. this: +ui_silence( + browse_github("usethis") +) +} From 63339761a3502d180407c96430619f90ff90f0c6 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 28 Feb 2024 17:54:10 -0800 Subject: [PATCH 087/111] Mark ui_yeah() and ui_no() as superseded --- R/ui-legacy.R | 13 ++++++++----- R/utils-ui.R | 3 ++- man/ui-questions.Rd | 10 ++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/R/ui-legacy.R b/R/ui-legacy.R index 17bab16a5..f031a3c57 100644 --- a/R/ui-legacy.R +++ b/R/ui-legacy.R @@ -150,12 +150,16 @@ ui_warn <- function(x, .envir = parent.frame()) { # Questions --------------------------------------------------------------- - #' User interface - Questions #' -#' These functions are used to interact with the user by posing a simple yes or -#' no question. For details on the other `ui_*()` functions, see the -#' [ui-legacy-functions] help page. +#' @description +#' `r lifecycle::badge("superseded")` +#' + +#' `ui_yeah()` and `ui_nope()` are technically superseded, but, unlike the rest +#' of the legacy [`ui_*()`][ui-legacy-functions] functions, there's not yet a +#' drop-in replacement available in the [cli package](https://cli.r-lib.org/). +#' `ui_yeah()` and `ui_nope()` are no longer used internally in usethis. #' #' @inheritParams ui-legacy-functions #' @param yes A character vector of "yes" strings, which are randomly sampled to @@ -173,7 +177,6 @@ ui_warn <- function(x, .envir = parent.frame()) { #' of `ui_yeah()`. #' @name ui-questions #' @keywords internal -#' @family user interface functions #' @examples #' \dontrun{ #' ui_yeah("Do you like R?") diff --git a/R/utils-ui.R b/R/utils-ui.R index 1672abef8..a27254520 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -11,11 +11,12 @@ usethis_theme <- function() { "text-exdent" = 2, before = function(x) paste0(cli::col_yellow(cli::symbol$info), " ") ), - # we have enough color going on already, let's have black bullets + # we have enough color going on already, don't add color to `*` bullets ".bullets .bullet-*" = list( "text-exdent" = 2, before = function(x) paste0(cli::symbol$bullet, " ") ), + # apply quotes to `.field` if we can't style it with color span.field = list(transform = single_quote_if_no_color) ) } diff --git a/man/ui-questions.Rd b/man/ui-questions.Rd index 50ea582fb..5f8982796 100644 --- a/man/ui-questions.Rd +++ b/man/ui-questions.Rd @@ -57,9 +57,12 @@ refuses to make a selection (cancels). \code{ui_nope()} is the logical opposite of \code{ui_yeah()}. } \description{ -These functions are used to interact with the user by posing a simple yes or -no question. For details on the other \verb{ui_*()} functions, see the -\link{ui-legacy-functions} help page. +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#superseded}{\figure{lifecycle-superseded.svg}{options: alt='[Superseded]'}}}{\strong{[Superseded]}} + +\code{ui_yeah()} and \code{ui_nope()} are technically superseded, but, unlike the rest +of the legacy \code{\link[=ui-legacy-functions]{ui_*()}} functions, there's not yet a +drop-in replacement available in the \href{https://cli.r-lib.org/}{cli package}. +\code{ui_yeah()} and \code{ui_nope()} are no longer used internally in usethis. } \examples{ \dontrun{ @@ -68,5 +71,4 @@ ui_nope("Have you tried turning it off and on again?", n_yes = 1, n_no = 1) ui_yeah("Are you sure its plugged in?", yes = "Yes", no = "No", shuffle = FALSE) } } -\concept{user interface functions} \keyword{internal} From e0df61c2771b7210d05b380a4db86f5d03b1a42d Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Wed, 28 Feb 2024 17:54:41 -0800 Subject: [PATCH 088/111] Work on kv_line() --- R/git.R | 5 ++++- R/utils-ui.R | 17 +++++++++++------ tests/testthat/_snaps/utils-ui.md | 16 ++++++++++++---- tests/testthat/test-utils-ui.R | 5 +++-- vignettes/articles/ui-cli-conversion.Rmd | 14 ++++++-------- 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/R/git.R b/R/git.R index adf23b25f..c32576520 100644 --- a/R/git.R +++ b/R/git.R @@ -341,7 +341,10 @@ git_sitrep <- function(tool = c("git", "github"), if ("git" %in% tool && "user" %in% scope) { cli::cli_h3("Git global (user)") git_user_sitrep("user") - kv_line("Global (user-level) gitignore file", git_ignore_path("user")) + kv_line( + "Global (user-level) gitignore file", + I("{.path {git_ignore_path('user')}}") + ) vaccinated <- git_vaccinated() kv_line("Vaccinated", vaccinated) if (!vaccinated) { diff --git a/R/utils-ui.R b/R/utils-ui.R index a27254520..68bbc6599 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -173,20 +173,25 @@ compute_n_show <- function(n, n_show_nominal = 5, n_fudge = 2) { } kv_line <- function(key, value, .envir = parent.frame()) { - key <- cli::format_inline(key, .envir = .envir) + cli::cli_div(theme = usethis_theme()) - value <- value %||% ui_special() + key_fmt <- cli::format_inline(key, .envir = .envir) + # this must happen first, before `value` has been forced + value_fmt <- cli::format_inline("{.val {value}}") + # but we might actually want something other than value_fmt + if (is.null(value)) { + value <- ui_special() + } if (inherits(value, "AsIs")) { - value <- cli::format_inline(value, .envir = .envir) - } else { - value <- cli::format_inline("{.val {value}}") + value_fmt <- cli::format_inline(value, .envir = .envir) } - ui_bullets(c("*" = "{key}: {value}")) + ui_bullets(c("*" = "{key_fmt}: {value_fmt}")) } ui_special <- function(x = "unset") { + force(x) I(glue("{cli::col_grey('<[x]>')}", .open = "[", .close = "]")) } diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index 5dd999bab..6c3e4a7a8 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -380,16 +380,16 @@ # kv_line() can interpolate and style inline in key [plain] Code - kv_line("Personal access token for {.val {value}}", "some_secret") + kv_line("Let's reveal {.field {field}}", "whatever") Message - * Personal access token for "SOME_HOST": "some_secret" + * Let's reveal 'SOME_FIELD': "whatever" # kv_line() can interpolate and style inline in key [fancy] Code - kv_line("Personal access token for {.val {value}}", "some_secret") + kv_line("Let's reveal {.field {field}}", "whatever") Message - • Personal access token for "SOME_HOST": "some_secret" + • Let's reveal SOME_FIELD: "whatever" # kv_line() can treat value in different ways [plain] @@ -417,6 +417,10 @@ kv_line("Key", I("something {.emph {adjective}}")) Message * Key: something great + Code + kv_line("Interesting file", I("{.path {git_ignore_path('user')}}")) + Message + * Interesting file: '~/.gitignore_global' # kv_line() can treat value in different ways [fancy] @@ -444,4 +448,8 @@ kv_line("Key", I("something {.emph {adjective}}")) Message • Key: something great + Code + kv_line("Interesting file", I("{.path {git_ignore_path('user')}}")) + Message + • Interesting file: ~/.gitignore_global diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index eef5429b2..78bfe0a42 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -198,9 +198,9 @@ cli::test_that_cli("kv_line() looks as expected in basic use", { cli::test_that_cli("kv_line() can interpolate and style inline in key", { withr::local_options(list(usethis.quiet = FALSE)) - value <- "SOME_HOST" + field <- "SOME_FIELD" expect_snapshot( - kv_line("Personal access token for {.val {value}}", "some_secret") + kv_line("Let's reveal {.field {field}}", "whatever") ) }, configs = c("plain", "fancy")) @@ -225,5 +225,6 @@ cli::test_that_cli("kv_line() can treat value in different ways", { # I() indicates value has markup kv_line("Key", I("something {.emph important}")) kv_line("Key", I("something {.emph {adjective}}")) + kv_line("Interesting file", I("{.path {git_ignore_path('user')}}")) }) }, configs = c("plain", "fancy")) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 83a3740c4..559caee51 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -384,24 +384,24 @@ I won't show the updated source for `kv_line()` but here is some usage to show w # why is this block truncated from the top in the rendered document? noun <- "thingy" value <- "VALUE" -kv_line("Key associated with {.val {noun}}", value) +kv_line("Let's reveal {.field {noun}}", "whatever") kv_line("URL for the {.val {remote}} remote", I("{.url {url}}")) kv_line("Personal access token for {.val {host}}", NULL) kv_line("Personal access token for {.val {host}}", ui_special("discovered")) - -x <- kv_line("Personal access token for {.val {host}}", ui_special("discovered")) ``` `ui_special()` is the successor to `ui_unset()`. ## Questions -There's currently no cli alternative to `ui_yeah()` and `ui_nope()`, so I won't make changes here. +There's currently no drop-in substitute for `ui_yeah()` and `ui_nope()` in cli. +Related issues: , . +Therefore, in the meantime, `ui_yeah()` and `ui_nope()` are not-quite-superseded for external users. -TO RECONSIDER: If I keep using `ui_yeah()` and `ui_nope()`, that means I retain usage of legacy inline styling. For example: +However, internally, I've switched to the unexported functions `ui_yep()` and `ui_nah()` that are lightly modified versions of `ui_yeah()` and `ui_nope()` that use cli for styling. ```{r} #| eval: false @@ -412,8 +412,6 @@ if (ui_nope(" } ``` -If I create new versions of `ui_yeah()` and `ui_nope()`, then I could still switch over to cli's inline styling. Otherwise, I won't be able to remove all internal usage of legacy `ui_*()` functions. - ## Miscellaneous notes * I've been adding a period to the end of messages, as a general rule. @@ -444,4 +442,4 @@ If I create new versions of `ui_yeah()` and `ui_nope()`, then I could still swit TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. Ditto for `target_repo()` in utils-github.R. -TODO: come back to this bit in git.R: ` purrr::walk(format(cfg), ui_bullet)` +TODO: come back to this bit in git.R: `purrr::walk(format(cfg), ui_legacy_bullet)` From 397e0ac6ffb5838aeb481565e31e603531138ef2 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 07:42:54 -0800 Subject: [PATCH 089/111] Use ui_yep() and ui_nah() everywhere --- R/author.R | 4 ++-- R/course.R | 2 +- R/git.R | 4 ++-- R/github-actions.R | 4 +--- R/package.R | 9 +++++---- R/pkgdown.R | 10 +++++----- R/use_import_from.R | 7 +++---- R/utils-git.R | 24 ++++++++++++------------ R/utils.R | 2 +- tests/testthat/helper-mocks.R | 4 ++-- tests/testthat/test-package.R | 4 ++-- 11 files changed, 36 insertions(+), 38 deletions(-) diff --git a/R/author.R b/R/author.R index 9fb567e61..7e396e4ac 100644 --- a/R/author.R +++ b/R/author.R @@ -95,7 +95,7 @@ challenge_legacy_author_fields <- function(d = proj_desc()) { "_" = "Convert to {.field Authors@R} with {.fun desc::desc_coerce_authors_at_r}, then delete the legacy fields." )) - if (ui_yeah("Do you want to cancel this operation and sort that out first?")) { + if (ui_yep("Do you want to cancel this operation and sort that out first?")) { ui_abort("Cancelling.") } invisible() @@ -135,7 +135,7 @@ challenge_default_author <- function(d = proj_desc()) { "i" = "{.field Authors@R} appears to include a placeholder author:", " " = "{format(default_author, style = 'text')}" )) - if(is_interactive() && ui_yeah("Would you like to remove it?")) { + if(is_interactive() && ui_yep("Would you like to remove it?")) { # TODO: Do I want to suppress this output? # Authors removed: First Last, NULL NULL. do.call(d$del_author, unclass(default_author)[[1]]) diff --git a/R/course.R b/R/course.R index 371fadcf1..07d7e589d 100644 --- a/R/course.R +++ b/R/course.R @@ -377,7 +377,7 @@ tidy_unzip <- function(zipfile, cleanup = FALSE) { if (isNA(cleanup)) { cleanup <- is_interactive() && - ui_yeah("Shall we delete the ZIP file ({ui_path(zipfile, base_path)})?") + ui_yep("Shall we delete the ZIP file ({.path {pth(zipfile, base_path)}})?") } if (isTRUE(cleanup)) { diff --git a/R/git.R b/R/git.R index c32576520..70e44f8c3 100644 --- a/R/git.R +++ b/R/git.R @@ -299,8 +299,8 @@ git_clean <- function() { bulletize(usethis_map_cli(ui_paths, template = "{.file <>}")) )) - if (ui_yeah(" - Do you want to remove {if (n == 1) 'it' else 'them'}?", + if (ui_yep( + "{cli::qty(n)}Do you want to remove {?it/them}?", yes = "yes", no = "no", shuffle = FALSE)) { file_delete(paths) ui_bullets(c("v" = "{n} file{?s} deleted.")) diff --git a/R/github-actions.R b/R/github-actions.R index e401ffd38..0c3768288 100644 --- a/R/github-actions.R +++ b/R/github-actions.R @@ -227,9 +227,7 @@ use_tidy_github_actions <- function(ref = NULL) { has_appveyor_travis <- file_exists(old_configs) if (any(has_appveyor_travis)) { - if (ui_yeah( - "Remove existing {ui_path('.travis.yml')} and {ui_path('appveyor.yml')}?" - )) { + if (ui_yep("Remove existing {.path .travis.yml} and {.path appveyor.yml}?")) { file_delete(old_configs[has_appveyor_travis]) ui_bullets(c("_" = "Remove old badges from README.")) } diff --git a/R/package.R b/R/package.R index b621b97e9..3c8ac4289 100644 --- a/R/package.R +++ b/R/package.R @@ -118,10 +118,11 @@ package_remote <- function(desc) { } parsed <- parse_github_remotes(urls$url[[1]]) remote <- paste0(parsed$repo_owner, "/", parsed$repo_name) - if (ui_yeah(" - {ui_value(package)} was either installed from CRAN or local source. - Based on DESCRIPTION, we propose the remote: {ui_value(remote)} - Is this OK?")) { + if (ui_yep(c( + "!" = "{.pkg {package}} was either installed from CRAN or local source.", + "i" = "Based on DESCRIPTION, we propose the remote: {.val {remote}}.", + " " = "Is this OK?" + ))) { remote } else { ui_abort("Cannot determine remote for {.pkg {package}}.") diff --git a/R/pkgdown.R b/R/pkgdown.R index b2b7f1151..78b3457d0 100644 --- a/R/pkgdown.R +++ b/R/pkgdown.R @@ -128,11 +128,11 @@ tidyverse_url <- function(url, tr = NULL) { if (grepl(glue("{custom_url}/?"), url)) { return(url) } - if (ui_yeah(" - {ui_value(tr$repo_name)} is owned by the {ui_value(tr$repo_owner)} GitHub \\ - organization. - Shall we configure {ui_value(custom_url)} as the (eventual) \\ - pkgdown URL?")) { + if (ui_yep(c( + "i" = "{.val {tr$repo_name}} is owned by the {.val {tr$repo_owner}} GitHub + organization.", + " " = "Shall we configure {.val {custom_url}} as the (eventual) pkgdown URL?" + ))) { custom_url } else { url diff --git a/R/use_import_from.R b/R/use_import_from.R index 0b41d7f7a..b1e8dce8f 100644 --- a/R/use_import_from.R +++ b/R/use_import_from.R @@ -58,12 +58,11 @@ check_has_package_doc <- function(whos_asking) { return(invisible(TRUE)) } - # TODO: update this msg when dealing with ui_yeah() msg <- c( - "{ui_code(whos_asking)} requires package-level documentation.", - "Would you like to add it now?" + "!" = "{.fun {whos_asking}} requires package-level documentation.", + " " = "Would you like to add it now?" ) - if (is_interactive() && ui_yeah(msg)) { + if (is_interactive() && ui_yep(msg)) { use_package_doc() } else { ui_abort(c( diff --git a/R/utils-git.R b/R/utils-git.R index 5ac4c2c7c..c1716ffdc 100644 --- a/R/utils-git.R +++ b/R/utils-git.R @@ -165,13 +165,9 @@ git_ask_commit <- function(message, untracked, push = FALSE, paths = NULL) { # Only push if no remote & a single change push <- push && git_can_push(max_local = 1) - msg <- paste0( - "Is it ok to commit ", - if (push) "and push ", - if (n == 1) 'it' else 'them', - "?" - ) - if (ui_yeah(msg)) { + if (ui_yep(c( + "!" = "Is it ok to commit {if (push) 'and push '} {cli::qty(n)} {?it/them}?" + ))) { git_commit(paths, message) if (push) { git_push() @@ -199,7 +195,10 @@ challenge_uncommitted_changes <- function(untracked = FALSE, msg = NULL) { we push, pull, switch, or compare branches" msg <- glue(msg %||% default_msg) if (git_uncommitted(untracked = untracked)) { - if (ui_yeah("{msg}\nDo you want to proceed anyway?")) { + if (ui_yep(c( + "!" = msg, + " " = "Do you want to proceed anyway?" + ))) { return(invisible()) } else { ui_abort("Uncommitted changes. Please commit before continuing.") @@ -225,12 +224,13 @@ git_conflict_report <- function() { bulletize(conflicted_paths, n_show = 10) )) - msg <- glue(" - Are you ready to sort this out? - If so, we will open the conflicted files for you to edit.") + msg <- c( + "!" = "Are you ready to sort this out?", + " " = "If so, we will open the conflicted files for you to edit." + ) yes <- "Yes, I'm ready to resolve the merge conflicts." no <- "No, I want to abort this merge." - if (ui_yeah(msg, yes = yes, no = no, shuffle = FALSE)) { + if (ui_yep(msg, yes = yes, no = no, shuffle = FALSE)) { ui_silence(purrr::walk(conflicted, edit_file)) ui_abort(c( "Please fix each conflict, save, stage, and commit.", diff --git a/R/utils.R b/R/utils.R index e4847d34e..50c9846a2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -16,7 +16,7 @@ can_overwrite <- function(path) { } if (is_interactive()) { - ui_yeah("Overwrite pre-existing file {ui_path(path)}?") + ui_yep(c("!" = "Overwrite pre-existing file {.path {pth(path)}}?")) } else { FALSE } diff --git a/tests/testthat/helper-mocks.R b/tests/testthat/helper-mocks.R index 845216a05..d2dc19ef4 100644 --- a/tests/testthat/helper-mocks.R +++ b/tests/testthat/helper-mocks.R @@ -22,8 +22,8 @@ local_check_fun_exists <- function(.env = caller_env()) { local_mocked_bindings(check_fun_exists = function(...) NULL, .env = .env) } -local_ui_yeah <- function(.env = caller_env()) { - local_mocked_bindings(ui_yeah = function(...) TRUE, .env = .env) +local_ui_yep <- function(.env = caller_env()) { + local_mocked_bindings(ui_yep = function(...) TRUE, .env = .env) } local_git_default_branch_remote <- function(.env = caller_env()) { diff --git a/tests/testthat/test-package.R b/tests/testthat/test-package.R index a58b05fd3..db4b44e52 100644 --- a/tests/testthat/test-package.R +++ b/tests/testthat/test-package.R @@ -40,7 +40,7 @@ test_that("use_package(type = 'Suggests') guidance w/o and w/ rlang", { test_that("use_dev_package() writes a remote", { create_local_package() - local_ui_yeah() + local_ui_yep() use_dev_package("usethis") expect_equal(proj_desc()$get_remotes(), "r-lib/usethis") @@ -59,7 +59,7 @@ test_that("package_remote() works for an installed package with github URL", { "Package: test", "URL: https://github.com/OWNER/test" )) - local_ui_yeah() + local_ui_yep() expect_equal(package_remote(d), "OWNER/test") }) From c71ed6bb3efa60f4ba8365135cfb155f0b6222a1 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 07:46:48 -0800 Subject: [PATCH 090/111] Re-align filenames re: legacy ui --- tests/testthat/_snaps/{ui.md => ui-legacy.md} | 0 tests/testthat/{test-ui.R => test-ui-legacy.R} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/_snaps/{ui.md => ui-legacy.md} (100%) rename tests/testthat/{test-ui.R => test-ui-legacy.R} (100%) diff --git a/tests/testthat/_snaps/ui.md b/tests/testthat/_snaps/ui-legacy.md similarity index 100% rename from tests/testthat/_snaps/ui.md rename to tests/testthat/_snaps/ui-legacy.md diff --git a/tests/testthat/test-ui.R b/tests/testthat/test-ui-legacy.R similarity index 100% rename from tests/testthat/test-ui.R rename to tests/testthat/test-ui-legacy.R From 4653e814c737f8fd864a7bd6a9789d02b7ea785e Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 07:48:47 -0800 Subject: [PATCH 091/111] OMG I don't want people to depend on usethis for ui functions --- R/use_import_from.R | 2 +- man/use_import_from.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/use_import_from.R b/R/use_import_from.R index b1e8dce8f..c27607294 100644 --- a/R/use_import_from.R +++ b/R/use_import_from.R @@ -19,7 +19,7 @@ #' @export #' @examples #' \dontrun{ -#' use_import_from("usethis", "ui_todo") +#' use_import_from("glue", "glue") #' } use_import_from <- function(package, fun, load = is_interactive()) { if (!is_string(package)) { diff --git a/man/use_import_from.Rd b/man/use_import_from.Rd index 1c29d105c..63d1e78cc 100644 --- a/man/use_import_from.Rd +++ b/man/use_import_from.Rd @@ -29,6 +29,6 @@ session. } \examples{ \dontrun{ -use_import_from("usethis", "ui_todo") +use_import_from("glue", "glue") } } From 7f99c385e9945f2e9e06cf3b8000af8054797688 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 08:25:04 -0800 Subject: [PATCH 092/111] Switch to ui_nah() , for real --- R/course.R | 12 ++++++------ R/create.R | 4 ++-- R/git-default-branch.R | 13 ++++++------- R/issue.R | 4 ++-- R/pr.R | 26 ++++++++++++++------------ R/release.R | 2 +- R/rstudio.R | 2 +- R/upkeep.R | 2 +- tests/testthat/_snaps/author.md | 6 +++--- 9 files changed, 36 insertions(+), 35 deletions(-) diff --git a/R/course.R b/R/course.R index 07d7e589d..772743717 100644 --- a/R/course.R +++ b/R/course.R @@ -68,7 +68,7 @@ use_course <- function(url, destdir = getOption("usethis.destdir")) { "_" = "Prefer a different location? Cancel, try again, and specify {.arg destdir}." )) - if (ui_nope("OK to proceed?")) { + if (ui_nah("OK to proceed?")) { ui_bullets(c(x = "Cancelling download.")) return(invisible()) } @@ -306,11 +306,11 @@ download_url <- function(url, status <- try_download(url, destfile, handle = handle) if (inherits(status, "error") && is_interactive()) { ui_bullets(c("x" = status$message)) - if (ui_nope(" - Download failed :( - See above for everything we know about why it failed. - Shall we try a couple more times, with a longer timeout? - ")) { + if (ui_nah(c( + "!" = "Download failed :(", + "i" = "See above for everything we know about why it failed.", + " " = "Shall we try a couple more times, with a longer timeout?" + ))) { n_tries <- 1 } } diff --git a/R/create.R b/R/create.R index 3cf6a93b4..5b264ad0c 100644 --- a/R/create.R +++ b/R/create.R @@ -358,7 +358,7 @@ challenge_nested_project <- function(path, name) { {.fun here::dr_here} that reveals why {.path {pth(path)}} is regarded as a project." )) - if (ui_nope("Do you want to create anyway?")) { + if (ui_nah("Do you want to create anyway?")) { ui_abort("Cancelling project creation.") } invisible() @@ -380,7 +380,7 @@ challenge_home_directory <- function(path) { "i" = "It is generally a bad idea to create a new project here.", "i" = "You should probably create your new project in a subdirectory." )) - if (ui_nope("Do you want to create anyway?")) { + if (ui_nah("Do you want to create anyway?")) { ui_abort("Good move! Cancelling project creation.") } invisible() diff --git a/R/git-default-branch.R b/R/git-default-branch.R index d06ea4ae3..d977ee73e 100644 --- a/R/git-default-branch.R +++ b/R/git-default-branch.R @@ -369,7 +369,7 @@ git_default_branch_rename <- function(from = NULL, to = "main") { the source repo are different:", " " = "{.val {old_local_db}} (local) != {.val {old_source_db}} (source)" )) - if (ui_nope( + if (ui_nah( "Are you sure you want to proceed?", yes = "yes", no = "no", shuffle = FALSE)) { ui_bullets(c("x" = "Cancelling.")) @@ -531,13 +531,12 @@ challenge_non_default_branch <- function(details = "Are you sure you want to pro default_branch = NULL) { actual <- git_branch() default_branch <- default_branch %||% git_default_branch() - if (nzchar(details)) { - details <- paste0("\n", details) - } if (actual != default_branch) { - if (ui_nope(" - Current branch ({ui_value(actual)}) is not repo's default \\ - branch ({ui_value(default_branch)}).{details}")) { + if (ui_nah(c( + "!" = "Current branch ({.val {actual}}) is not repo's default branch + ({.val {default_branch}}).", + " " = details + ))) { ui_abort("Cancelling. Not on desired branch.") } } diff --git a/R/issue.R b/R/issue.R index 4e6dc907b..a4b0e6085 100644 --- a/R/issue.R +++ b/R/issue.R @@ -46,7 +46,7 @@ issue_close_community <- function(number, reprex = FALSE) { "i" = "Unless you have triage permissions, you won't be allowed to close an issue." )) - if (ui_nope("Do you want to try anyway?")) { + if (ui_nah("Do you want to try anyway?")) { ui_bullets(c("x" = "Cancelling.")) return(invisible()) } @@ -95,7 +95,7 @@ issue_reprex_needed <- function(number) { "i" = "Unless you have triage permissions, you won't be allowed to label an issue." )) - if (ui_nope("Do you want to try anyway?")) { + if (ui_nah("Do you want to try anyway?")) { ui_bullets(c("x" = "Cancelling.")) return(invisible()) } diff --git a/R/pr.R b/R/pr.R index ed8348133..55b8ec43a 100644 --- a/R/pr.R +++ b/R/pr.R @@ -557,12 +557,13 @@ pr_clean <- function(number = NULL, } tracking_branch <- git_branch_tracking(pr_local_branch) if (is.na(tracking_branch)) { - if (ui_nope(" - Local branch {ui_value(pr_local_branch)} has no associated remote \\ - branch. - If we delete {ui_value(pr_local_branch)}, any work that exists only \\ - on this branch may be hard for you to recover. - Proceed anyway?")) { + if (ui_nah(c( + "!" = "Local branch {.val {pr_local_branch}} has no associated remote + branch.", + "i" = "If we delete {.val {pr_local_branch}}, any work that exists only + on this branch may be hard for you to recover.", + " " = "Proceed anyway?" + ))) { ui_bullets(c("x" = "Cancelling.")) return(invisible()) } @@ -571,12 +572,13 @@ pr_clean <- function(number = NULL, branch = pr_local_branch, remref = tracking_branch ) - if (cmp$local_only > 0 && ui_nope(" - Local branch {ui_value(pr_local_branch)} has 1 or more commits \\ - that have not been pushed to {ui_value(tracking_branch)}. - If we delete {ui_value(pr_local_branch)}, this work may be hard \\ - for you to recover. - Proceed anyway?")) { + if (cmp$local_only > 0 && ui_nah(c( + "!" = "Local branch {.val {pr_local_branch}} has 1 or more commits that + have not been pushed to {.val {tracking_branch}}.", + "i" = "If we delete {.val {pr_local_branch}}, this work may be hard for + you to recover.", + " " = "Proceed anyway?" + ))) { ui_bullets(c("x" = "Cancelling.")) return(invisible()) } diff --git a/R/release.R b/R/release.R index 500da61ab..02a122594 100644 --- a/R/release.R +++ b/R/release.R @@ -40,7 +40,7 @@ use_release_issue <- function(version = NULL) { "!" = "It is very unusual to open a release issue on a repo you can't push to ({.val {tr$repo_spec}})." )) - if (ui_nope("Do you really want to do this?")) { + if (ui_nah("Do you really want to do this?")) { ui_bullets(c("x" = "Cancelling.")) return(invisible()) } diff --git a/R/rstudio.R b/R/rstudio.R index f3cceda3d..10f43c910 100644 --- a/R/rstudio.R +++ b/R/rstudio.R @@ -174,7 +174,7 @@ restart_rstudio <- function(message = NULL) { return(FALSE) } - if (ui_nope("Restart now?")) { + if (ui_nah("Restart now?")) { return(FALSE) } diff --git a/R/upkeep.R b/R/upkeep.R index ac55a0d83..a24ba0993 100644 --- a/R/upkeep.R +++ b/R/upkeep.R @@ -33,7 +33,7 @@ make_upkeep_issue <- function(year, tidy) { "!" = "It is very unusual to open an upkeep issue on a repo you can't push to ({.val {tr$repo_spec}})." )) - if (ui_nope("Do you really want to do this?")) { + if (ui_nah("Do you really want to do this?")) { ui_bullets(c("x" = "Cancelling.")) return(invisible()) } diff --git a/tests/testthat/_snaps/author.md b/tests/testthat/_snaps/author.md index c226a8d95..48bbed63c 100644 --- a/tests/testthat/_snaps/author.md +++ b/tests/testthat/_snaps/author.md @@ -10,9 +10,9 @@ [ ] Convert to 'Authors@R' with `desc::desc_coerce_authors_at_r()`, then delete the legacy fields. Condition - Error: - ! User input required, but session is not interactive. - Query: Do you want to cancel this operation and sort that out first? + Error in `ui_yep()`: + x User input required, but session is not interactive. + i Query: "Do you want to cancel this operation and sort that out first?" # Decline to tweak an existing author From cb91031e6eea31e11ab89e6de7bef3aa67d860fc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 08:26:15 -0800 Subject: [PATCH 093/111] Make sure I don't call into the legacy file --- R/ui-legacy.R | 4 ---- R/utils-ui.R | 10 +++++++++- vignettes/articles/ui-cli-conversion.Rmd | 7 ++++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/R/ui-legacy.R b/R/ui-legacy.R index f031a3c57..92473119e 100644 --- a/R/ui-legacy.R +++ b/R/ui-legacy.R @@ -303,7 +303,3 @@ ui_inform <- function(...) { } invisible() } - -is_quiet <- function() { - isTRUE(getOption("usethis.quiet", default = FALSE)) -} diff --git a/R/utils-ui.R b/R/utils-ui.R index 68bbc6599..fbd35331b 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -57,6 +57,10 @@ ui_silence <- function(code) { withr::with_options(list(usethis.quiet = TRUE), code) } +is_quiet <- function() { + isTRUE(getOption("usethis.quiet", default = FALSE)) +} + # bullets, helpers, and friends ------------------------------------------------ ui_bullets <- function(text, .envir = parent.frame()) { if (is_quiet()) { @@ -92,6 +96,11 @@ ui_code_snippet <- function(x, .envir = parent.frame()) { language <- arg_match(language) + indent <- function(x, first = " ", indent = first) { + x <- gsub("\n", paste0("\n", indent), x) + paste0(first, x) + } + x <- glue_collapse(x, "\n") if (interpolate) { x <- glue(x, .envir = .envir) @@ -238,7 +247,6 @@ ui_yep <- function(x, qs <- sample(qs) } - # TODO: should this be ui_inform()? cli::cli_inform(x, .envir = .envir) out <- utils::menu(qs) out != 0L && qs[[out]] %in% yes diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 559caee51..fd1f8c905 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -186,7 +186,8 @@ ui_inform <- function(...) { Other than `is_quiet()`, which will continue to play the same role, I anticipate that we no longer need these utilities (`indent()`, `ui_bullet()`, `ui_inform()`). Updates from the future: -* `indent()` turns out to still be useful in `ui_code_snippet()`. +* `indent()` turns out to still be useful in `ui_code_snippet()`, so I've inlined + it there, to avoid any reliance on definitions in ui-legacy.R. * `ui_bullet()` has been renamed to `ui_legacy_bullet()` for auto-completion happiness with the new `ui_bullets()`. Let's cover `ui_silence()` while we're here, which *is* exported. @@ -443,3 +444,7 @@ if (ui_nope(" TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. Ditto for `target_repo()` in utils-github.R. TODO: come back to this bit in git.R: `purrr::walk(format(cfg), ui_legacy_bullet)` + +TODO: NEWS bullet + +TODO: Update principles.md From 2aaa255dbeeb879d7cfbcb9fe35be00929ebe1f8 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 08:26:47 -0800 Subject: [PATCH 094/111] Deal with stragglers that are not TODOs --- R/data.R | 14 ++++++-------- R/directory.R | 2 +- R/git-default-branch.R | 7 +++---- R/utils-ui.R | 7 ++----- tests/testthat/_snaps/github-actions.md | 4 ++-- tests/testthat/_snaps/lifecycle.md | 2 +- tests/testthat/helper.R | 6 +++--- 7 files changed, 18 insertions(+), 24 deletions(-) diff --git a/R/data.R b/R/data.R index c2dfc2fd2..75069e708 100644 --- a/R/data.R +++ b/R/data.R @@ -88,12 +88,12 @@ use_data <- function(..., get_objs_from_dots <- function(.dots) { if (length(.dots) == 0L) { - ui_stop("Nothing to save.") + ui_abort("Nothing to save.") } is_name <- vapply(.dots, is.symbol, logical(1)) if (any(!is_name)) { - ui_stop("Can only save existing named objects.") + ui_abort("Can only save existing named objects.") } objs <- vapply(.dots, as.character, character(1)) @@ -118,12 +118,10 @@ check_files_absent <- function(paths, overwrite) { return() } - ui_stop( - " - {ui_path(paths[!ok])} already exist., - Use {ui_code('overwrite = TRUE')} to overwrite. - " - ) + ui_abort(c( + "{.path {pth(paths[!ok])}} already exist.", + "Use {.code overwrite = TRUE} to overwrite." + )) } #' @param name Name of the dataset to be prepared for inclusion in the package. diff --git a/R/directory.R b/R/directory.R index 3e465a43d..3ec9d5046 100644 --- a/R/directory.R +++ b/R/directory.R @@ -30,7 +30,7 @@ create_directory <- function(path) { } dir_create(path, recurse = TRUE) - ui_done("Creating {ui_path(path)}") + ui_bullets(c("v" = "Creating {.path {pth(path)}}.")) invisible(TRUE) } diff --git a/R/git-default-branch.R b/R/git-default-branch.R index d977ee73e..db8e2a988 100644 --- a/R/git-default-branch.R +++ b/R/git-default-branch.R @@ -616,11 +616,10 @@ fishy_badges <- function(old_name = "master") { return(invisible(character())) } - ui_path <- ui_path_impl(proj_rel_path(path)) ui_bullets(c( "x" = "Some badges appear to refer to the old default branch {.val {old_name}}.", - "_" = "Check and correct, if needed, in this file: {.file {ui_path}}" + "_" = "Check and correct, if needed, in this file: {.path {pth(path)}}" )) invisible(path) @@ -650,11 +649,11 @@ fishy_bookdown_config <- function(old_name = "master") { return(invisible(character())) } - ui_path <- ui_path_impl(proj_rel_path(bookdown_config)) ui_bullets(c( "x" = "The bookdown configuration file may refer to the old default branch {.val {old_name}}.", - "_" = "Check and correct, if needed, in this file: {.file {ui_path}}" + "_" = "Check and correct, if needed, in this file: + {.path {pth(bookdown_config)}}" )) invisible(path) diff --git a/R/utils-ui.R b/R/utils-ui.R index fbd35331b..fa0a9b187 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -228,13 +228,10 @@ ui_yep <- function(x, no = c("No way", "Not now", "Negative", "No", "Nope", "Absolutely not"), n_yes = 1, n_no = 2, shuffle = TRUE, .envir = parent.frame()) { - #x <- glue_collapse(x, "\n") - #x <- glue(x, .envir = .envir) - if (!is_interactive()) { - ui_stop(c( + ui_abort(c( "User input required, but session is not interactive.", - "Query: {x}" + "Query: {.val {x}}" )) } diff --git a/tests/testthat/_snaps/github-actions.md b/tests/testthat/_snaps/github-actions.md index c9c60e91b..be83f2d64 100644 --- a/tests/testthat/_snaps/github-actions.md +++ b/tests/testthat/_snaps/github-actions.md @@ -4,10 +4,10 @@ use_github_action(url = "https://raw.githubusercontent.com/r-lib/actions/v2/examples/check-full.yaml", readme = "https://github.com/r-lib/actions/blob/v2/examples/README.md") Message - v Creating '.github/' + v Creating '.github/'. v Adding "^\\.github$" to '.Rbuildignore'. v Adding "*.html" to '.github/.gitignore'. - v Creating '.github/workflows/' + v Creating '.github/workflows/'. v Saving "r-lib/actions/examples/check-full.yaml@v2" to '.github/workflows/R-CMD-check.yaml'. [ ] Learn more at diff --git a/tests/testthat/_snaps/lifecycle.md b/tests/testthat/_snaps/lifecycle.md index 773bdd957..e4bb79e48 100644 --- a/tests/testthat/_snaps/lifecycle.md +++ b/tests/testthat/_snaps/lifecycle.md @@ -8,7 +8,7 @@ v Adding "@importFrom lifecycle deprecated" to 'R/{TESTPKG}-package.R'. v Writing 'NAMESPACE'. - v Creating 'man/figures/' + v Creating 'man/figures/'. v Copied SVG badges to 'man/figures/'. [ ] Add badges in documentation topics by inserting a line like this: #' `r lifecycle::badge('experimental')` diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 0a21f8555..b6a448b02 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -31,7 +31,7 @@ create_local_thing <- function(dir = file_temp(pattern = pattern), thing = c("package", "project")) { thing <- match.arg(thing) if (fs::dir_exists(dir)) { - ui_stop("Target {ui_code('dir')} {ui_path(dir)} already exists.") + ui_abort("Target {.arg dir} {.path {pth(dir)}} already exists.") } old_project <- proj_get_() # this could be `NULL`, i.e. no active project @@ -39,7 +39,7 @@ create_local_thing <- function(dir = file_temp(pattern = pattern), withr::defer( { - ui_done("Deleting temporary project: {ui_path(dir)}") + ui_bullets(c("Deleting temporary project: {.path {dir}}")) fs::dir_delete(dir) }, envir = env @@ -68,7 +68,7 @@ create_local_thing <- function(dir = file_temp(pattern = pattern), withr::defer( { - ui_done("Restoring original working directory: {ui_path(old_wd)}") + ui_bullets(c("Restoring original working directory: {.path {old_wd}}")) setwd(old_wd) }, envir = env From b25065c161cd834cbf54ec0c2bf157199eb2b744 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 08:37:04 -0800 Subject: [PATCH 095/111] Update principles.md --- principles.md | 12 ++++++++---- vignettes/articles/ui-cli-conversion.Rmd | 2 -- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/principles.md b/principles.md index af74dc73d..6a76fe5d9 100644 --- a/principles.md +++ b/principles.md @@ -81,14 +81,18 @@ In order to be consistent everywhere, all paths supplied by the user should be p ## Communicating with the user -User-facing messages are emitted via helpers in `ui.R` and *everything* is eventually routed through `rlang::inform()` via `ui_inform()`. +User-facing messages are ALWAYS emitted via helpers in `utils-ui.R`. This is all intentional and should be preserved. This is so we can control verbosity package-wide with the `usethis.quiet` option, which defaults to `FALSE`. -- Exploited in usethis tests: option is set and unset in `setup.R` and `teardown.R`. Eliminates the need for ubiquitous `capture_output()` calls. -- Other packages can muffle a usethis call via, e.g., `withr::local_options(list(usethis.quiet = TRUE))`. -- Use `ui_silence()` for executing small bits of code silently. +- Exploited in usethis tests: option is set and unset in `setup.R` and `teardown.R`, to suppress output by default. +- Individual tests that need output, e.g. for snapshot testing, should include `withr::local_options(usethis.quiet = FALSE)`. +- Other packages can muffle a usethis call through similar means. +- `ui_silence()` is a convenience helper for executing small bits of code silently. + +Note that usethis exports many `ui_*()` functions for historical reasons, all defined in `ui-legacy.R`, and all marked as superseded now. +See the developer-facing article "Converting usethis's UI to use cli" for more. ## Git/GitHub diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index fd1f8c905..cd729a86b 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -446,5 +446,3 @@ TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_ TODO: come back to this bit in git.R: `purrr::walk(format(cfg), ui_legacy_bullet)` TODO: NEWS bullet - -TODO: Update principles.md From d0cc81e5c00b1b30de1a8966dfcf45b1f2630789 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 12:18:57 -0800 Subject: [PATCH 096/111] NEWS bullet --- NEWS.md | 13 +++++++++++++ vignettes/articles/ui-cli-conversion.Rmd | 2 -- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 35cba70f6..d7f3a70a0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,18 @@ # usethis (development version) +* The `ui_*()` functions have been marked as + [superseded](https://lifecycle.r-lib.org/articles/stages.html#superseded). + External users of these functions are encouraged to use the + [cli package](https://cli.r-lib.org/) instead. + The cli package did not have the required functionality when the + `usethis::ui_*()` functions were first created, but it does now and it's the + superior option. + There is a cli vignette about how to make this transition: + `vignette("usethis-ui", package = "cli")`. + + usethis no longer uses the `ui_*()` functions internally, in favor of new + cli-based helpers that are not exported. + # usethis 2.2.3 * Patch release with changes to `.Rd` files requested by CRAN. diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index cd729a86b..3731f0f96 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -444,5 +444,3 @@ if (ui_nope(" TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. Ditto for `target_repo()` in utils-github.R. TODO: come back to this bit in git.R: `purrr::walk(format(cfg), ui_legacy_bullet)` - -TODO: NEWS bullet From a3ab22bdd0b2ea23ed392350ed4b9ffbf3a50095 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 15:44:38 -0800 Subject: [PATCH 097/111] Yet another straggler --- tests/testthat/helper.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index b6a448b02..ff3f76c6b 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -6,10 +6,10 @@ session_temp_proj <- proj_find(path_temp()) if (!is.null(session_temp_proj)) { Rproj_files <- fs::dir_ls(session_temp_proj, glob = "*.Rproj") - ui_line(c( - "Rproj file(s) found at or above session temp dir:", - paste0("* ", Rproj_files), - "Expect this to cause spurious test failures." + ui_bullets(c( + "x" = "Rproj {cli::qty(length(Rproj_files))} file{?s} found at or above session temp dir:", + bulletize(usethis_map_cli(Rproj_files)), + "!" = "Expect this to cause spurious test failures." )) } From b2805d2ce6a79de585453eac218e1e6a2cbdca5b Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 15:45:14 -0800 Subject: [PATCH 098/111] Deal with utils::menu() prep and other unusual stuff --- R/git.R | 2 +- R/pr.R | 68 ++++++++++++++---------- vignettes/articles/ui-cli-conversion.Rmd | 4 -- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/R/git.R b/R/git.R index 70e44f8c3..0ac6f5ab3 100644 --- a/R/git.R +++ b/R/git.R @@ -427,7 +427,7 @@ git_sitrep <- function(tool = c("git", "github"), cli::cli_text("Project:") } - purrr::walk(format(cfg), ui_legacy_bullet) + ui_bullets(format(cfg)) } invisible() diff --git a/R/pr.R b/R/pr.R index 55b8ec43a..abb496502 100644 --- a/R/pr.R +++ b/R/pr.R @@ -385,13 +385,14 @@ pr_push <- function() { # this is the first push if (cfg$type == "fork" && cfg$upstream$can_push && is_interactive()) { choices <- c( - origin = glue( - "{cfg$origin$repo_spec} = {ui_value('origin')} (external PR)"), - upstream = glue( - "{cfg$upstream$repo_spec} = {ui_value('upstream')} (internal PR)") + origin = ui_pre_glue(" + <> = {.val origin} (external PR)"), + upstream = ui_pre_glue(" + <> = {.val upstream} (internal PR)") ) + choices_formatted <- map_chr(choices, cli::format_inline) title <- glue("Which repo do you want to push to?") - choice <- utils::menu(choices, graphics = FALSE, title = title) + choice <- utils::menu(choices_formatted, graphics = FALSE, title = title) remote <- names(choices)[[choice]] } else { remote <- "origin" @@ -854,27 +855,30 @@ choose_branch <- function(exclude = character()) { return() } prompt <- "Which branch do you want to checkout? (0 to exit)" - if (nrow(dat) > 9) { - branches_not_shown <- utils::tail(dat$name, -9) - n <- length(branches_not_shown) - dat <- dat[1:9, ] - pre <- glue("{n} branch{if (n > 1) 'es' else ''} not listed: ") - listing <- glue_collapse( - branches_not_shown, sep = ", ", width = getOption("width") - nchar(pre) + n_show_max <- 9 + n <- nrow(dat) + n_shown <- compute_n_show(n, n_show_nominal = n_show_max) + n_not_shown <- n - n_shown + if (n_not_shown > 0) { + branches_not_shown <- utils::tail(dat$name, -n_shown) + dat <- dat[seq_len(n_shown), ] + fine_print <- cli::format_inline( + "{n_not_shown} branch{?/es} not listed: {.val {branches_not_shown}}" ) - prompt <- glue(" - {prompt} - {pre}{listing}") + prompt <- glue("{prompt}\n{fine_print}") } - dat$pretty_user <- map(dat$pr_user, ~ glue("@{.x}")) + dat$pretty_user <- glue_data(dat, "@{pr_user}") dat$pretty_name <- format(dat$name, justify = "right") dat_pretty <- purrr::pmap_chr( dat[c("pretty_name", "pr_number", "pretty_user", "pr_title")], function(pretty_name, pr_number, pretty_user, pr_title) { if (is.na(pr_number)) { - glue("{pretty_name}") + pretty_name } else { - glue("{pretty_name} --> #{pr_number} ({ui_value(pretty_user)}): {pr_title}") + template <- ui_pre_glue( + "{pretty_name} {cli::symbol$arrow_right} #{pr_number} ({.field <>}): {pr_title}" + ) + cli::format_inline(template) } } ) @@ -897,13 +901,17 @@ choose_pr <- function(tr = NULL, pr_dat = NULL) { # wording needs to make sense for several PR-choosing tasks, e.g. fetch, view, # finish, forget prompt <- "Which PR are you interested in? (0 to exit)" - if (nrow(pr_dat) > 9) { - n <- nrow(pr_dat) - 9 - pr_dat <- pr_dat[1:9, ] - prompt <- glue(" - {prompt} - Not shown: {n} more {if (n > 1) 'PRs' else 'PR'}; \\ - call {ui_code('browse_github_pulls()')} to browse all PRs.") + n_show_max <- 9 + n <- nrow(pr_dat) + n_shown <- compute_n_show(n, n_show_nominal = n_show_max) + n_not_shown <- n - n_shown + if (n_not_shown > 0) { + pr_dat <- pr_dat[seq_len(n_shown), ] + info1 <- cli::format_inline("Not shown: {n_not_shown} more PR{?s}.") + info2 <- cli::format_inline( + "Call {.run usethis::browse_github_pulls()} to browse all PRs." + ) + prompt <- glue("{prompt}\n{info1}\n{info2}") } some_closed <- any(pr_dat$pr_state == "closed") @@ -913,9 +921,15 @@ choose_pr <- function(tr = NULL, pr_dat = NULL) { hash_number <- glue("#{pr_number}") at_user <- glue("@{pr_user}") if (some_closed) { - glue("{hash_number} ({ui_field(at_user)}, {pr_state}): {ui_value(pr_title)}") + template <- ui_pre_glue( + "{hash_number} ({.field <>}, {pr_state}): {.val <>}" + ) + cli::format_inline(template) } else { - glue("{hash_number} ({ui_field(at_user)}): {ui_value(pr_title)}") + template <- ui_pre_glue( + "{hash_number} ({.field <>}): {.val <>}" + ) + cli::format_inline(template) } } ) diff --git a/vignettes/articles/ui-cli-conversion.Rmd b/vignettes/articles/ui-cli-conversion.Rmd index 3731f0f96..b403a411e 100644 --- a/vignettes/articles/ui-cli-conversion.Rmd +++ b/vignettes/articles/ui-cli-conversion.Rmd @@ -440,7 +440,3 @@ if (ui_nope(" The repo or branch where PR #{pr$pr_number} originates seems to have been deleted.") ``` - -TODO: come back to `pr_push()` and deal with that menu stuff. Ditto for `choose_branch()` and `choose_pr()`. Ditto for `target_repo()` in utils-github.R. - -TODO: come back to this bit in git.R: `purrr::walk(format(cfg), ui_legacy_bullet)` From c462abf278caf0ac402404b08f851cb4fb1690cc Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 16:51:13 -0800 Subject: [PATCH 099/111] Make test less sensitive to local conditions --- tests/testthat/_snaps/utils-ui.md | 8 ++++---- tests/testthat/test-utils-ui.R | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/testthat/_snaps/utils-ui.md b/tests/testthat/_snaps/utils-ui.md index 6c3e4a7a8..b738a1af3 100644 --- a/tests/testthat/_snaps/utils-ui.md +++ b/tests/testthat/_snaps/utils-ui.md @@ -418,9 +418,9 @@ Message * Key: something great Code - kv_line("Interesting file", I("{.path {git_ignore_path('user')}}")) + kv_line("Interesting file", I("{.url {url}}")) Message - * Interesting file: '~/.gitignore_global' + * Interesting file: # kv_line() can treat value in different ways [fancy] @@ -449,7 +449,7 @@ Message • Key: something great Code - kv_line("Interesting file", I("{.path {git_ignore_path('user')}}")) + kv_line("Interesting file", I("{.url {url}}")) Message - • Interesting file: ~/.gitignore_global + • Interesting file:  diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 78bfe0a42..c25a4917a 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -209,6 +209,7 @@ cli::test_that_cli("kv_line() can treat value in different ways", { value <- "some value" adjective <- "great" + url <- "https://usethis.r-lib.org/" expect_snapshot({ # evaluation in .envir @@ -225,6 +226,6 @@ cli::test_that_cli("kv_line() can treat value in different ways", { # I() indicates value has markup kv_line("Key", I("something {.emph important}")) kv_line("Key", I("something {.emph {adjective}}")) - kv_line("Interesting file", I("{.path {git_ignore_path('user')}}")) + kv_line("Interesting file", I("{.url {url}}")) }) }, configs = c("plain", "fancy")) From 12ee388a110a0e53b6543163222ff3043fd40278 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 17:08:56 -0800 Subject: [PATCH 100/111] Article needs asciicast --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index f64862fd1..5c101feb3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -53,7 +53,7 @@ Suggests: spelling (>= 1.2), styler (>= 1.2.0), testthat (>= 3.1.8) -Config/Needs/website: tidyverse/tidytemplate, xml2 +Config/Needs/website: r-lib/asciicast, tidyverse/tidytemplate, xml2 Config/testthat/edition: 3 Config/testthat/parallel: TRUE Config/testthat/start-first: github-actions, release From c1c92970e488922b53f6a2e124abb28b92d1a2da Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Thu, 29 Feb 2024 18:09:24 -0800 Subject: [PATCH 101/111] Increase width to forcibly prevent snapshot diff due to linebreaks I guess temp paths vary in length, even across separate runs on the same platform? --- tests/testthat/test-lifecycle.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-lifecycle.R b/tests/testthat/test-lifecycle.R index ae7a6ec5d..603894da6 100644 --- a/tests/testthat/test-lifecycle.R +++ b/tests/testthat/test-lifecycle.R @@ -1,7 +1,7 @@ test_that("use_lifecycle() imports badges", { create_local_package() use_package_doc() - withr::local_options(usethis.quiet = FALSE) + withr::local_options(usethis.quiet = FALSE, width = 150) expect_snapshot( use_lifecycle(), From db3c536ad9b885d30145f598583616c441f08db6 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 08:27:03 -0800 Subject: [PATCH 102/111] Organize the articles --- _pkgdown.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index b04f4fe49..11664e0a4 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -170,3 +170,21 @@ reference: - use_github_actions - use_tidy_labels - use_travis + +articles: +- title: Basics + navbar: ~ + contents: + - articles/usethis-setup + +- title: Git and GitHub + navbar: Git and GitHub + contents: + - articles/git-credentials + - articles/pr-functions + +- title: Developer + desc: Articles about package internals and development experiments + contents: + - articles/ui-cli-conversion + - articles/badge-accessibility From 914371716fcbec22efad39e003217a671f9cdf3d Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 08:27:41 -0800 Subject: [PATCH 103/111] Update README --- README.Rmd | 2 +- README.md | 66 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/README.Rmd b/README.Rmd index 6f8d69db7..0fbb3d006 100644 --- a/README.Rmd +++ b/README.Rmd @@ -46,7 +46,7 @@ Most `use_*()` functions operate on the *active project*: literally, a directory A few usethis functions have no strong connections to projects and will expect you to provide a path. -usethis is quite chatty, explaining what it's doing and assigning you tasks. `✔` indicates something usethis has done for you. `●` indicates that you'll need to do some work yourself. +usethis is quite chatty, explaining what it's doing and assigning you tasks. `✔` indicates something usethis has done for you. `☐` indicates that you'll need to do some work yourself. Below is a quick look at how usethis can help to set up a package. But remember, many usethis functions are also applicable to analytical projects that are not packages. diff --git a/README.md b/README.md index b8c3baf3f..de29e6036 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ A few usethis functions have no strong connections to projects and will expect you to provide a path. usethis is quite chatty, explaining what it’s doing and assigning you -tasks. `✔` indicates something usethis has done for you. `●` indicates +tasks. `✔` indicates something usethis has done for you. `☐` indicates that you’ll need to do some work yourself. Below is a quick look at how usethis can help to set up a package. But @@ -61,10 +61,10 @@ library(usethis) # Create a new package ------------------------------------------------- path <- file.path(tempdir(), "mypkg") create_package(path) -#> ✔ Creating '/tmp/RtmpPqIkgo/mypkg/' -#> ✔ Setting active project to '/private/tmp/RtmpPqIkgo/mypkg' -#> ✔ Creating 'R/' -#> ✔ Writing 'DESCRIPTION' +#> ✔ Creating '/tmp/Rtmp7yJh5e/mypkg/'. +#> ✔ Setting active project to "/private/tmp/Rtmp7yJh5e/mypkg". +#> ✔ Creating 'R/'. +#> ✔ Writing 'DESCRIPTION'. #> Package: mypkg #> Title: What the Package Does (One Line, Title Case) #> Version: 0.0.0.9000 @@ -76,54 +76,56 @@ create_package(path) #> Encoding: UTF-8 #> Roxygen: list(markdown = TRUE) #> RoxygenNote: 7.3.1 -#> ✔ Writing 'NAMESPACE' -#> ✔ Setting active project to '' +#> ✔ Writing 'NAMESPACE'. +#> ✔ Setting active project to "". # only needed since this session isn't interactive proj_activate(path) -#> ✔ Setting active project to '/private/tmp/RtmpPqIkgo/mypkg' -#> ✔ Changing working directory to '/tmp/RtmpPqIkgo/mypkg/' +#> ✔ Setting active project to "/private/tmp/Rtmp7yJh5e/mypkg". +#> ✔ Changing working directory to '/tmp/Rtmp7yJh5e/mypkg/' # Modify the description ---------------------------------------------- use_mit_license("My Name") -#> ✔ Adding 'MIT + file LICENSE' to License -#> ✔ Writing 'LICENSE' -#> ✔ Writing 'LICENSE.md' -#> ✔ Adding '^LICENSE\\.md$' to '.Rbuildignore' +#> ✔ Adding "MIT + file LICENSE" to 'License'. +#> ✔ Writing 'LICENSE'. +#> ✔ Writing 'LICENSE.md'. +#> ✔ Adding "^LICENSE\\.md$" to '.Rbuildignore'. use_package("ggplot2", "Suggests") -#> ✔ Adding 'ggplot2' to Suggests field in DESCRIPTION -#> • Use `requireNamespace("ggplot2", quietly = TRUE)` to test if package is installed -#> • Then directly refer to functions with `ggplot2::fun()` +#> ✔ Adding ggplot2 to 'Suggests' field in DESCRIPTION. +#> ☐ Use `requireNamespace("ggplot2", quietly = TRUE)` to test if package is +#> installed. +#> ☐ Then directly refer to functions with `ggplot2::fun()`. # Set up other files ------------------------------------------------- use_readme_md() -#> ✔ Writing 'README.md' -#> • Update 'README.md' to include installation instructions. +#> ✔ Writing 'README.md'. +#> ☐ Update 'README.md' to include installation instructions. use_news_md() -#> ✔ Writing 'NEWS.md' +#> ✔ Writing 'NEWS.md'. use_test("my-test") -#> ✔ Adding 'testthat' to Suggests field in DESCRIPTION -#> ✔ Adding '3' to Config/testthat/edition -#> ✔ Creating 'tests/testthat/' -#> ✔ Writing 'tests/testthat.R' -#> ✔ Writing 'tests/testthat/test-my-test.R' -#> • Edit 'tests/testthat/test-my-test.R' +#> ✔ Adding testthat to 'Suggests' field in DESCRIPTION. +#> ✔ Adding "3" to 'Config/testthat/edition'. +#> ✔ Creating 'tests/testthat/'. +#> ✔ Writing 'tests/testthat.R'. +#> ✔ Writing 'tests/testthat/test-my-test.R'. +#> ☐ Edit 'tests/testthat/test-my-test.R'. x <- 1 y <- 2 use_data(x, y) -#> ✔ Adding 'R' to Depends field in DESCRIPTION -#> ✔ Creating 'data/' -#> ✔ Setting LazyData to 'true' in 'DESCRIPTION' -#> ✔ Saving 'x', 'y' to 'data/x.rda', 'data/y.rda' -#> • Document your data (see 'https://r-pkgs.org/data.html') +#> ✔ Adding R to 'Depends' field in DESCRIPTION. +#> ✔ Creating 'data/'. +#> ✔ Setting 'LazyData' to "true" in 'DESCRIPTION'. +#> ✔ Saving "x" and "y" to "data/x.rda" and "data/y.rda". +#> ☐ Document your data (see ). # Use git ------------------------------------------------------------ use_git() -#> ✔ Initialising Git repo -#> ✔ Adding '.Rproj.user', '.Rhistory', '.Rdata', '.httr-oauth', '.DS_Store', '.quarto' to '.gitignore' +#> ✔ Initialising Git repo. +#> ✔ Adding ".Rproj.user", ".Rhistory", ".Rdata", ".httr-oauth", ".DS_Store", and +#> ".quarto" to '.gitignore'. ``` ## Code of Conduct From d7b30f757f6201e5894e953ee58766613efa006f Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 09:18:54 -0800 Subject: [PATCH 104/111] Make the width even larger??? --- tests/testthat/test-lifecycle.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-lifecycle.R b/tests/testthat/test-lifecycle.R index 603894da6..a37a066db 100644 --- a/tests/testthat/test-lifecycle.R +++ b/tests/testthat/test-lifecycle.R @@ -1,7 +1,7 @@ test_that("use_lifecycle() imports badges", { create_local_package() use_package_doc() - withr::local_options(usethis.quiet = FALSE, width = 150) + withr::local_options(usethis.quiet = FALSE, width = 200) expect_snapshot( use_lifecycle(), From 226c4a00081f3cb993f2118894b8d10077ef3255 Mon Sep 17 00:00:00 2001 From: "Jennifer (Jenny) Bryan" Date: Fri, 1 Mar 2024 09:33:11 -0800 Subject: [PATCH 105/111] Apply suggestions from code review Co-authored-by: olivroy <52606734+olivroy@users.noreply.github.com> --- R/git-default-branch.R | 4 ++-- R/github-actions.R | 2 +- R/proj.R | 4 ++-- R/sitrep.R | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/R/git-default-branch.R b/R/git-default-branch.R index db8e2a988..fdcaac79d 100644 --- a/R/git-default-branch.R +++ b/R/git-default-branch.R @@ -149,7 +149,7 @@ git_default_branch <- function() { {.val {db_source$default_branch}}.", " " = "But the local repo has no branch named {.val {db_source$default_branch}}.", - "_" = "Call {.fun git_default_branch_rediscover} to resolve this." + "_" = "Call {.run [git_default_branch_rediscover()](usethis::git_default_branch_rediscover())} to resolve this." ), class = "error_default_branch", db_source = db_source @@ -177,7 +177,7 @@ git_default_branch <- function() { {.val {db_source$default_branch}}.", " " = "But the default branch of the local repo appears to be {.val {db_local_with_source}}.", - "_" = "Call {.fun git_default_branch_rediscover} to resolve this." + "_" = "Call {.run [git_default_branch_rediscover()](usethis::git_default_branch_rediscover())} to resolve this." ), class = "error_default_branch", db_source = db_source, db_local = db_local_with_source diff --git a/R/github-actions.R b/R/github-actions.R index 0c3768288..c60a78ad0 100644 --- a/R/github-actions.R +++ b/R/github-actions.R @@ -250,7 +250,7 @@ check_uses_github_actions <- function() { ui_abort(c( "Cannot detect that package {.pkg {project_name()}} already uses GitHub Actions.", - "Do you need to run {.fun use_github_actions}?" + "Do you need to run {.fun use_github_action}?" )) } diff --git a/R/proj.R b/R/proj.R index 73b9f6129..261373efe 100644 --- a/R/proj.R +++ b/R/proj.R @@ -90,7 +90,7 @@ proj_set <- function(path = ".", force = FALSE) { if (is.null(new_project)) { ui_abort(c( "Path {.path {pth(path)}} does not appear to be inside a project or package.", - "Read more in the help for {.fun usethis::proj_get}." + "Read more in the help for {.help usethis::proj_get}." )) } proj_set(path = new_project, force = TRUE) @@ -226,7 +226,7 @@ check_is_project <- function() { if (!possibly_in_proj()) { ui_abort(c( "We do not appear to be inside a valid project or package.", - "Read more in the help for {.fun usethis::proj_get}." + "Read more in the help for {.help usethis::proj_get}." )) } } diff --git a/R/sitrep.R b/R/sitrep.R index 15e64309d..6d43e400e 100644 --- a/R/sitrep.R +++ b/R/sitrep.R @@ -52,7 +52,7 @@ print.sitrep <- function(x, ...) { ui_bullets(c( "i" = "You are working in RStudio, but are not in an RStudio Project.", "i" = "A Project-based workflow offers many advantages. Read more at:", - " " = "{.url https://support.rstudio.com/hc/en-us/articles/200526207-Using-Projects}", + " " = "{.url https://docs.posit.co/ide/user/ide/guide/code/projects.html}", " " = "{.url https://rstats.wtf/projects}" )) } @@ -71,7 +71,7 @@ print.sitrep <- function(x, ...) { if (usethis_proj_is_not_wd) { ui_bullets(c( "i" = "Your working directory is not the same as the active usethis project.", - "_" = "Set working directory to the project: {.run setwd(proj_get())}.", + "_" = "Set working directory to the project: {.code setwd(proj_get())}.", "_" = "Set project to working directory: {.run usethis::proj_set(getwd())}." )) } @@ -80,7 +80,7 @@ print.sitrep <- function(x, ...) { ui_bullets(c( "i" = "Your working directory is not the same as the active RStudio Project.", "_" = "Set working directory to the Project: - {.run setwd(rstudioapi::getActiveProject())}." + {.code setwd(rstudioapi::getActiveProject())}." )) } From 4cea0779618590c5b7cb32538025a490b20a6bed Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 09:37:26 -0800 Subject: [PATCH 106/111] Update snapshot --- tests/testthat/_snaps/github-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/_snaps/github-actions.md b/tests/testthat/_snaps/github-actions.md index be83f2d64..e11c0056e 100644 --- a/tests/testthat/_snaps/github-actions.md +++ b/tests/testthat/_snaps/github-actions.md @@ -36,5 +36,5 @@ Condition Error in `check_uses_github_actions()`: x Cannot detect that package {TESTPKG} already uses GitHub Actions. - i Do you need to run `use_github_actions()`? + i Do you need to run `use_github_action()`? From 0baf2b53c91535689f1d3b74ca052107f05b2d1f Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 11:38:28 -0800 Subject: [PATCH 107/111] How about cli.width????? --- tests/testthat/_snaps/lifecycle.md | 3 +-- tests/testthat/test-lifecycle.R | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/testthat/_snaps/lifecycle.md b/tests/testthat/_snaps/lifecycle.md index e4bb79e48..c284edbc9 100644 --- a/tests/testthat/_snaps/lifecycle.md +++ b/tests/testthat/_snaps/lifecycle.md @@ -5,8 +5,7 @@ Message v Adding lifecycle to 'Imports' field in DESCRIPTION. [ ] Refer to functions with `lifecycle::fun()`. - v Adding "@importFrom lifecycle deprecated" to - 'R/{TESTPKG}-package.R'. + v Adding "@importFrom lifecycle deprecated" to 'R/{TESTPKG}-package.R'. v Writing 'NAMESPACE'. v Creating 'man/figures/'. v Copied SVG badges to 'man/figures/'. diff --git a/tests/testthat/test-lifecycle.R b/tests/testthat/test-lifecycle.R index a37a066db..c6792299b 100644 --- a/tests/testthat/test-lifecycle.R +++ b/tests/testthat/test-lifecycle.R @@ -1,7 +1,7 @@ test_that("use_lifecycle() imports badges", { create_local_package() use_package_doc() - withr::local_options(usethis.quiet = FALSE, width = 200) + withr::local_options(usethis.quiet = FALSE, cli.width = 200) expect_snapshot( use_lifecycle(), From 3e343a36015c6c8f0ebd04b60885f0dd5297aeb5 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 12:38:52 -0800 Subject: [PATCH 108/111] YOLO --- tests/testthat/test-lifecycle.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-lifecycle.R b/tests/testthat/test-lifecycle.R index c6792299b..4d71695ba 100644 --- a/tests/testthat/test-lifecycle.R +++ b/tests/testthat/test-lifecycle.R @@ -1,7 +1,7 @@ test_that("use_lifecycle() imports badges", { create_local_package() use_package_doc() - withr::local_options(usethis.quiet = FALSE, cli.width = 200) + withr::local_options(usethis.quiet = FALSE, cli.width = 1000) expect_snapshot( use_lifecycle(), From 210905f039197cbd2e2cba92c749c94b2b1e9c8f Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 12:58:33 -0800 Subject: [PATCH 109/111] `.run` fixups --- R/git.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/git.R b/R/git.R index 0ac6f5ab3..6596a6449 100644 --- a/R/git.R +++ b/R/git.R @@ -493,7 +493,7 @@ default_branch_sitrep <- function() { {.val {e$db_source$default_branch}}.", "!" = "The local repo has no branch named {.val {e$db_source$default_branch}}.", - "_" = "Call {.fun git_default_branch_rediscover} to resolve this." + "_" = "Call {.run [git_default_branch_rediscover()](usethis::git_default_branch_rediscover())} to resolve this." )) } else if (has_name(e, "db_source")) { ui_bullets(c( @@ -502,7 +502,7 @@ default_branch_sitrep <- function() { {.val {e$db_source$default_branch}}.", "!" = "The local repo has no branch by that name, nor any other obvious candidates.", - "_" = "Call {.fun git_default_branch_rediscover} to resolve this." + "_" = "Call {.run [git_default_branch_rediscover()](usethis::git_default_branch_rediscover())} to resolve this." )) } else { ui_bullets(c("Default branch cannot be determined.")) From 56c130c58d39095a021943991e1937830efeb0b1 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 13:00:35 -0800 Subject: [PATCH 110/111] These run afoul of the `.run` rules, so why bother --- R/sitrep.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/sitrep.R b/R/sitrep.R index 6d43e400e..6637fddc7 100644 --- a/R/sitrep.R +++ b/R/sitrep.R @@ -72,7 +72,7 @@ print.sitrep <- function(x, ...) { ui_bullets(c( "i" = "Your working directory is not the same as the active usethis project.", "_" = "Set working directory to the project: {.code setwd(proj_get())}.", - "_" = "Set project to working directory: {.run usethis::proj_set(getwd())}." + "_" = "Set project to working directory: {.code usethis::proj_set(getwd())}." )) } @@ -89,11 +89,11 @@ print.sitrep <- function(x, ...) { "i" = "Your active RStudio Project is not the same as the active {.pkg usethis} project.", "_" = "Set active {.pkg usethis} project to RStudio Project: - {.run usethis::proj_set(rstudioapi::getActiveProject())}.", + {.code usethis::proj_set(rstudioapi::getActiveProject())}.", "_" = "Restart RStudio in the active {.pkg usethis} project: - {.run rstudioapi::openProject(usethis::proj_get())}.", + {.code rstudioapi::openProject(usethis::proj_get())}.", "_" = "Open the active {.pkg usethis} project in a new instance of RStudio: - {.run usethis::proj_activate(usethis::proj_get())}." + {.code usethis::proj_activate(usethis::proj_get())}." )) } From d6a35525cc6dc8d88b38f7af80266fde766d2c37 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Fri, 1 Mar 2024 13:57:50 -0800 Subject: [PATCH 111/111] Better captures the intent --- tests/testthat/test-lifecycle.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-lifecycle.R b/tests/testthat/test-lifecycle.R index 4d71695ba..214bd0a1b 100644 --- a/tests/testthat/test-lifecycle.R +++ b/tests/testthat/test-lifecycle.R @@ -1,7 +1,7 @@ test_that("use_lifecycle() imports badges", { create_local_package() use_package_doc() - withr::local_options(usethis.quiet = FALSE, cli.width = 1000) + withr::local_options(usethis.quiet = FALSE, cli.width = Inf) expect_snapshot( use_lifecycle(),