From 0bbd779b6f5b037588deb4192770f950059e0c59 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 19 Sep 2025 09:52:03 -0400 Subject: [PATCH 1/5] Escape curly braces in pr_title Added a simple helper to escape curly braces for glue (`ui_escape_glue()` in `utils-ui.R`), then applied it in the relevant `pr_*()` functions. I used Positron Assistant and Google Gemini as a test on this, then revised their fixes significantly. Do you want to add a test for this in `manual-pr-functions.R`? Or I could automate this stuff with mocked github API calls if you want to go that far. Fixes #2107 --- R/pr.R | 8 ++++---- R/utils-ui.R | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/R/pr.R b/R/pr.R index 6b1c20dad..c73ab72dd 100644 --- a/R/pr.R +++ b/R/pr.R @@ -322,7 +322,7 @@ pr_fetch <- function(number = NULL, target = c("source", "primary")) { pr_user <- glue("@{pr$pr_user}") ui_bullets(c( "v" = "Checking out PR {.href [{pr$pr_string}]({pr$pr_html_url})} ({.field {pr_user}}): - {.val {pr$pr_title}}." + {.val {ui_escape_glue(pr$pr_title)}}." )) if (pr$pr_from_fork && isFALSE(pr$maintainer_can_modify)) { @@ -944,7 +944,7 @@ choose_branch <- function(exclude = character()) { ) at_user <- glue("@{pr_user}") template <- ui_pre_glue( - "{pretty_name} {cli::symbol$arrow_right} <> ({.field <>}): {.val <>}" + "{pretty_name} {cli::symbol$arrow_right} <> ({.field <>}): {.val <>}" ) cli::format_inline(template) } @@ -990,12 +990,12 @@ choose_pr <- function(tr = NULL, pr_dat = NULL) { at_user <- glue("@{pr_user}") if (some_closed) { template <- ui_pre_glue( - "<> ({.field <>}, {pr_state}): {.val <>}" + "<> ({.field <>}, {pr_state}): {.val <>}" ) cli::format_inline(template) } else { template <- ui_pre_glue( - "<> ({.field <>}): {.val <>}" + "<> ({.field <>}): {.val <>}" ) cli::format_inline(template) } diff --git a/R/utils-ui.R b/R/utils-ui.R index 98e3c86e3..125e064cc 100644 --- a/R/utils-ui.R +++ b/R/utils-ui.R @@ -162,6 +162,11 @@ ui_pre_glue <- function(..., .envir = parent.frame()) { glue(..., .open = "<<", .close = ">>", .envir = .envir) } +ui_escape_glue <- function(x) { + gsub("([{}])", "\\1\\1", x) +} + + bulletize <- function(x, bullet = "*", n_show = 5, n_fudge = 2) { n <- length(x) n_show_actual <- compute_n_show(n, n_show, n_fudge) From 8ce1ebe7b8ae8710ec8e5d50653021e2d969d888 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 19 Sep 2025 09:57:10 -0400 Subject: [PATCH 2/5] Do the escape once in the sub-function toward the end. --- R/pr.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/pr.R b/R/pr.R index c73ab72dd..e1c2ca493 100644 --- a/R/pr.R +++ b/R/pr.R @@ -988,14 +988,15 @@ choose_pr <- function(tr = NULL, pr_dat = NULL) { function(pr_number, pr_html_url, pr_user, pr_state, pr_title) { href_number <- ui_pre_glue("{.href [PR #<>](<>)}") at_user <- glue("@{pr_user}") + pr_title_escaped <- ui_escape_glue(pr_title) if (some_closed) { template <- ui_pre_glue( - "<> ({.field <>}, {pr_state}): {.val <>}" + "<> ({.field <>}, {pr_state}): {.val <>}" ) cli::format_inline(template) } else { template <- ui_pre_glue( - "<> ({.field <>}): {.val <>}" + "<> ({.field <>}): {.val <>}" ) cli::format_inline(template) } From 3c949046dd75481ad48a5a660d53c4feb9a19713 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 19 Sep 2025 11:48:52 -0400 Subject: [PATCH 3/5] Add a {test} for `ui_escape_glue()` helper And implicitly test the curly brace thing. --- tests/testthat/test-utils-ui.R | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/testthat/test-utils-ui.R b/tests/testthat/test-utils-ui.R index 6d1381e6a..26774ace2 100644 --- a/tests/testthat/test-utils-ui.R +++ b/tests/testthat/test-utils-ui.R @@ -267,3 +267,17 @@ cli::test_that_cli( }, configs = c("plain", "fancy") ) + +test_that("ui_escape_glue() doubles curly braces", { + expect_equal(ui_escape_glue("no braces"), "no braces") + expect_equal(ui_escape_glue("one { brace"), "one {{ brace") + expect_equal(ui_escape_glue("one } brace"), "one }} brace") + expect_equal( + ui_escape_glue("A {brace_set} in text"), + "A {{brace_set}} in text" + ) + expect_equal( + ui_escape_glue("{multiple} {brace} {sets}"), + "{{multiple}} {{brace}} {{sets}}" + ) +}) From 04495d2b8a33535489bfb673c3a0422a6dd75d0d Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 19 Sep 2025 11:57:42 -0400 Subject: [PATCH 4/5] Don't escape in un-glued bullets. --- R/pr.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/pr.R b/R/pr.R index e1c2ca493..650dbe2b5 100644 --- a/R/pr.R +++ b/R/pr.R @@ -322,7 +322,7 @@ pr_fetch <- function(number = NULL, target = c("source", "primary")) { pr_user <- glue("@{pr$pr_user}") ui_bullets(c( "v" = "Checking out PR {.href [{pr$pr_string}]({pr$pr_html_url})} ({.field {pr_user}}): - {.val {ui_escape_glue(pr$pr_title)}}." + {.val {pr$pr_title}}." )) if (pr$pr_from_fork && isFALSE(pr$maintainer_can_modify)) { From 7079ea1cb37d4deb8044d403203a43ad3c478d23 Mon Sep 17 00:00:00 2001 From: Jon Harmon Date: Fri, 19 Sep 2025 12:07:31 -0400 Subject: [PATCH 5/5] Add NEWS.md bullet. @jennybc I added the `Fixes #2107` to the description, but it didn't retroactively link to the issue, so make sure that's showing in Development to auto-close. --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 215d21e0f..37d7ede9f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # usethis (development version) +* `pr_resume()` (without a specific `branch`) and `pr_fetch()` (without a specific `number`) no longer error when a branch name contains curly braces (#2107, @jonthegeek). + # usethis 3.2.1 * `create_quarto_project()` exits early if the Quarto CLI does not appear to be