From 51f689b069289d01b7318bc30939637d6701abd9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Mar 2026 10:39:26 +0000 Subject: [PATCH] Pre-evaluate inline expressions, add format_inline_(), fix print.ab - All bare {variable}/{expression} in message_()/warning_()/stop_() calls are now pre-evaluated via paste0(), so users without cli/glue never see raw template syntax (mo_source.R, first_isolate.R, join_microorganisms.R, antibiogram.R, atc_online.R) - Add format_inline_() helper: formats a cli-markup string and returns it (not emits it), using cli::format_inline() when available and cli_to_plain() otherwise - Rewrite .onAttach to use format_inline_() for all packageStartupMessage calls; also adds {.topic} link and {.code} markup for option names - print.ab: pre-evaluate function_name via paste0 (no .envir needed), apply highlight_code() to each example bullet for R syntax highlighting - join_microorganisms: pre-evaluate {type} and {nrow(...)} expressions https://claude.ai/code/session_01XHWLohiSTdZvCutwD7ag2b --- DESCRIPTION | 2 +- NEWS.md | 4 +++- R/aa_helper_functions.R | 13 +++++++++++++ R/ab.R | 19 ++++++++----------- R/antibiogram.R | 4 ++-- R/atc_online.R | 2 +- R/first_isolate.R | 7 ++++--- R/join_microorganisms.R | 4 ++-- R/mo_source.R | 2 +- R/zzz.R | 20 +++++++++----------- 10 files changed, 44 insertions(+), 33 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index cf7388753..a087266e4 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: AMR -Version: 3.0.1.9037 +Version: 3.0.1.9038 Date: 2026-03-19 Title: Antimicrobial Resistance Data Analysis Description: Functions to simplify and standardise antimicrobial resistance (AMR) diff --git a/NEWS.md b/NEWS.md index b937b423b..cc6d16ecc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# AMR 3.0.1.9037 +# AMR 3.0.1.9038 ### New * Integration with the **tidymodels** framework to allow seamless use of SIR, MIC and disk data in modelling pipelines via `recipes` @@ -31,6 +31,8 @@ ### Updates * Replaced all bare backtick-quoted text in `message_()`, `warning_()`, and `stop_()` calls with proper cli inline markup (`{.arg}`, `{.cls}`, `{.fun}`, `{.pkg}`, `{.code}`); rewrote `print.ab` to use a cli named-vector with `*` bullets and code highlighting when cli is available +* Added `format_inline_()` helper that formats a cli-markup string and returns it (rather than emitting it), using `cli::format_inline()` when available and `cli_to_plain()` otherwise; used this in `.onAttach` to replace the duplicated cli/non-cli startup message pattern +* All inline `{variable}` / `{expression}` in messaging calls are now pre-evaluated via `paste0()`, so users without cli or glue never see raw template syntax * `mdro()` now infers resistance for a _missing_ base drug column from an _available_ corresponding drug+inhibitor combination showing resistance (e.g., piperacillin is absent but required, while piperacillin/tazobactam available and resistant). Can be set with the new argument `infer_from_combinations`, which defaults to `TRUE` (#209). Note that this can yield a higher MDRO detection (which is a good thing as it has become more reliable). * `susceptibility()` and `resistance()` gained the argument `guideline`, which defaults to EUCAST, for interpreting the 'I' category correctly. * Added to the `antimicrobials` data set: cefepime/taniborbactam (`FTA`), ceftibuten/avibactam (`CTA`), clorobiocin (`CLB`), kasugamycin (`KAS`), ostreogrycin (`OST`), taniborbactam (`TAN`), thiostrepton (`THS`), xeruborbactam (`XER`), and zorbamycin (`ZOR`) diff --git a/R/aa_helper_functions.R b/R/aa_helper_functions.R index f2e9c8ec4..f0b84430b 100644 --- a/R/aa_helper_functions.R +++ b/R/aa_helper_functions.R @@ -390,6 +390,19 @@ highlight_code <- function(code) { } } +# Format a cli-markup string for output, with a plain-text fallback when cli is +# unavailable. Unlike message_() / warning_() / stop_(), this function returns +# the formatted string rather than emitting it, so it can be passed to any +# output function (e.g. packageStartupMessage()). +format_inline_ <- function(...) { + msg <- paste0(c(...), collapse = "") + if (pkg_is_available("cli", min_version = "3.0.0")) { + cli::format_inline(msg) + } else { + cli_to_plain(msg, envir = parent.frame()) + } +} + import_fn <- function(name, pkg, error_on_fail = TRUE) { if (isTRUE(error_on_fail)) { stop_ifnot_installed(pkg) diff --git a/R/ab.R b/R/ab.R index 9380fdc51..9fd084d91 100755 --- a/R/ab.R +++ b/R/ab.R @@ -552,17 +552,14 @@ print.ab <- function(x, ...) { if (!is.null(attributes(x)$amr_selector)) { function_name <- attributes(x)$amr_selector if (pkg_is_available("cli", min_version = "3.0.0")) { - cli::cli_inform( - c( - "i" = "This {.cls ab} vector was retrieved using {.fun {function_name}}, which should normally be used inside a {.pkg dplyr} verb or {.code data.frame} call, e.g.:", - "*" = "{.code your_data %>% select({function_name}())}", - "*" = "{.code your_data %>% select(column_a, column_b, {function_name}())}", - "*" = "{.code your_data %>% filter(any({function_name}() == \"R\"))}", - "*" = "{.code your_data[, {function_name}()]}", - "*" = "{.code your_data[, c(\"column_a\", \"column_b\", {function_name}())]}" - ), - .envir = environment() - ) + cli::cli_inform(c( + "i" = paste0("This {.cls ab} vector was retrieved using {.fun ", function_name, "}, which should normally be used inside a {.pkg dplyr} verb or {.code data.frame} call, e.g.:"), + "*" = highlight_code(paste0("your_data %>% select(", function_name, "()")), + "*" = highlight_code(paste0("your_data %>% select(column_a, column_b, ", function_name, "()")), + "*" = highlight_code(paste0("your_data %>% filter(any(", function_name, "() == \"R\"))")), + "*" = highlight_code(paste0("your_data[, ", function_name, "()]")), + "*" = highlight_code(paste0("your_data[, c(\"column_a\", \"column_b\", ", function_name, "())]")) + )) } else { message(word_wrap(paste0( "This 'ab' vector was retrieved using `", function_name, "()`, which should normally be used inside a dplyr verb or data.frame call, e.g.:\n", diff --git a/R/antibiogram.R b/R/antibiogram.R index 1b304879e..fb23aa038 100755 --- a/R/antibiogram.R +++ b/R/antibiogram.R @@ -619,7 +619,7 @@ antibiogram.default <- function(x, out$n_susceptible <- out$n_susceptible + out$I + out$SDD } if (all(out$n_tested < minimum, na.rm = TRUE) && wisca == FALSE) { - warning_("All combinations had less than {.arg minimum} = {minimum} results, returning an empty antibiogram") + warning_("All combinations had less than {.arg minimum} = ", minimum, " results, returning an empty antibiogram") return(as_original_data_class(data.frame(), class(x), extra_class = "antibiogram")) } else if (any(out$n_tested < minimum, na.rm = TRUE)) { mins <- sum(out$n_tested < minimum, na.rm = TRUE) @@ -627,7 +627,7 @@ antibiogram.default <- function(x, out <- out %pm>% subset(n_tested >= minimum) if (isTRUE(info) && mins > 0) { - message_("NOTE: {mins} combinations had less than {.arg minimum} = {minimum} results and were ignored") + message_("NOTE: ", mins, " combinations had less than {.arg minimum} = ", minimum, " results and were ignored") } } } diff --git a/R/atc_online.R b/R/atc_online.R index 89bfc0f93..833a72144 100755 --- a/R/atc_online.R +++ b/R/atc_online.R @@ -180,7 +180,7 @@ atc_online_property <- function(atc_code, colnames(out) <- gsub("^atc.*", "atc", tolower(colnames(out))) if (length(out) == 0) { - message_("in {.help [{.fun atc_online_property}](AMR::atc_online_property)}: no properties found for ATC ", atc_code[i], ". Please check {.href {atc_url} this WHOCC webpage}.") + message_("in {.help [{.fun atc_online_property}](AMR::atc_online_property)}: no properties found for ATC ", atc_code[i], ". Please check {.href ", atc_url, " this WHOCC webpage}.") returnvalue[i] <- NA next } diff --git a/R/first_isolate.R b/R/first_isolate.R index 675ad2d63..e82134e8b 100644 --- a/R/first_isolate.R +++ b/R/first_isolate.R @@ -373,7 +373,7 @@ first_isolate <- function(x = NULL, if (!is.null(specimen_group)) { check_columns_existance(col_specimen, x) if (isTRUE(info) && message_not_thrown_before("first_isolate", "excludingspecimen")) { - message_("Excluding other than specimen group '{specimen_group}'") + message_("Excluding other than specimen group '", specimen_group, "'") } } if (!is.null(col_keyantimicrobials)) { @@ -430,7 +430,8 @@ first_isolate <- function(x = NULL, } if (length(c(row.start:row.end)) == pm_n_distinct(x[c(row.start:row.end), col_mo, drop = TRUE])) { if (isTRUE(info)) { - message_("=> Found {.strong {length(c(row.start:row.end))} first isolates}, as all isolates were different microbial species", + n_rows <- length(c(row.start:row.end)) + message_("=> Found {.strong ", n_rows, " first isolates}, as all isolates were different microbial species", as_note = FALSE ) } @@ -537,7 +538,7 @@ first_isolate <- function(x = NULL, paste0('"', x, '"') } }) - message_("\nGroup: {toString(paste0(names(group), ' = ', group))}\n", + message_("\nGroup: ", toString(paste0(names(group), " = ", group)), "\n", as_note = FALSE ) } diff --git a/R/join_microorganisms.R b/R/join_microorganisms.R index d97a9e1ae..e282b9933 100755 --- a/R/join_microorganisms.R +++ b/R/join_microorganisms.R @@ -145,7 +145,7 @@ join_microorganisms <- function(type, x, by, suffix, ...) { } else { stop_if(is.null(by), "no column with microorganism names or codes found, set this column with {.arg by}", call = -2) } - message_('Joining, by = "{by}"', as_note = FALSE) # message same as dplyr::join functions + message_("Joining, by = \"", by, "\"", as_note = FALSE) # message same as dplyr::join functions } if (!all(x[, by, drop = TRUE] %in% AMR_env$MO_lookup$mo, na.rm = TRUE)) { x$join.mo <- as.mo(x[, by, drop = TRUE]) @@ -185,7 +185,7 @@ join_microorganisms <- function(type, x, by, suffix, ...) { } if (type %like% "full|left|right|inner" && NROW(joined) > NROW(x)) { - warning_("in {.fun {type}_microorganisms}: the newly joined data set contains {nrow(joined) - nrow(x)} rows more than the number of rows of {.arg x}.") + warning_("in {.fun ", type, "_microorganisms}: the newly joined data set contains ", nrow(joined) - nrow(x), " rows more than the number of rows of {.arg x}.") } as_original_data_class(joined, class(x.bak)) # will remove tibble groups diff --git a/R/mo_source.R b/R/mo_source.R index e68bb3f09..89bc7a238 100755 --- a/R/mo_source.R +++ b/R/mo_source.R @@ -249,7 +249,7 @@ get_mo_source <- function(destination = getOption("AMR_mo_source", "~/mo_source. current_ext <- regexpr("\\.([[:alnum:]]+)$", destination) current_ext <- ifelse(current_ext > -1L, substring(destination, current_ext + 1L), "") vowel <- ifelse(current_ext %like% "^[AEFHILMNORSX]", "n", "") - stop_("The AMR mo source must be an RDS file, not a{vowel} {toupper(current_ext)} file. If \"{basename(destination)}\" was meant as your input file, use {.help [{.fun set_mo_source}](AMR::set_mo_source)} on this file. In any case, the option {.code AMR_mo_source} must be set to another path.") + stop_("The AMR mo source must be an RDS file, not a", vowel, " ", toupper(current_ext), " file. If \"", basename(destination), "\" was meant as your input file, use {.help [{.fun set_mo_source}](AMR::set_mo_source)} on this file. In any case, the option {.code AMR_mo_source} must be set to another path.") } if (is.null(AMR_env$mo_source)) { AMR_env$mo_source <- readRDS_AMR(path.expand(destination)) diff --git a/R/zzz.R b/R/zzz.R index bb579052f..39eca912f 100755 --- a/R/zzz.R +++ b/R/zzz.R @@ -116,42 +116,40 @@ AMR_env$cross_icon <- if (isTRUE(base::l10n_info()$`UTF-8`)) "\u00d7" else "x" .onAttach <- function(libname, pkgname) { if (interactive() && is.null(getOption("AMR_guideline"))) { - packageStartupMessage( - word_wrap( - "Assuming ", AMR::clinical_breakpoints$guideline[1], " as the default AMR guideline, see `?AMR-options` to change this." - ) - ) + packageStartupMessage(format_inline_( + "Assuming ", AMR::clinical_breakpoints$guideline[1], " as the default AMR guideline, see {.topic [AMR-options](AMR::AMR-options)} to change this." + )) } # if custom ab option is available, load it if (!is.null(getOption("AMR_custom_ab")) && file.exists(getOption("AMR_custom_ab", default = ""))) { if (getOption("AMR_custom_ab") %unlike% "[.]rds$") { - packageStartupMessage("The file with custom antimicrobials must be an RDS file. Set the option `AMR_custom_ab` to another path.") + packageStartupMessage(format_inline_("The file with custom antimicrobials must be an RDS file. Set the option {.code AMR_custom_ab} to another path.")) } else { - packageStartupMessage("Adding custom antimicrobials from '", getOption("AMR_custom_ab"), "'...", appendLF = FALSE) + packageStartupMessage(format_inline_("Adding custom antimicrobials from '", getOption("AMR_custom_ab"), "'..."), appendLF = FALSE) x <- readRDS_AMR(getOption("AMR_custom_ab")) tryCatch( { suppressWarnings(suppressMessages(add_custom_antimicrobials(x))) packageStartupMessage("OK.") }, - error = function(e) packageStartupMessage("Failed: ", conditionMessage(e)) + error = function(e) packageStartupMessage(format_inline_("Failed: ", conditionMessage(e))) ) } } # if custom mo option is available, load it if (!is.null(getOption("AMR_custom_mo")) && file.exists(getOption("AMR_custom_mo", default = ""))) { if (getOption("AMR_custom_mo") %unlike% "[.]rds$") { - packageStartupMessage("The file with custom microorganisms must be an RDS file. Set the option `AMR_custom_mo` to another path.") + packageStartupMessage(format_inline_("The file with custom microorganisms must be an RDS file. Set the option {.code AMR_custom_mo} to another path.")) } else { - packageStartupMessage("Adding custom microorganisms from '", getOption("AMR_custom_mo"), "'...", appendLF = FALSE) + packageStartupMessage(format_inline_("Adding custom microorganisms from '", getOption("AMR_custom_mo"), "'..."), appendLF = FALSE) x <- readRDS_AMR(getOption("AMR_custom_mo")) tryCatch( { suppressWarnings(suppressMessages(add_custom_microorganisms(x))) packageStartupMessage("OK.") }, - error = function(e) packageStartupMessage("Failed: ", conditionMessage(e)) + error = function(e) packageStartupMessage(format_inline_("Failed: ", conditionMessage(e))) ) } }