cli icon indicating copy to clipboard operation
cli copied to clipboard

Metaprogramming: extracting formatted objects

Open MichaelChirico opened this issue 9 months ago • 7 comments

To support interpreting {cli} calls in lintr::object_usage_linter() & thereby prevent false positives related to "unused variables", we'd like to examine relevant calls & extracting out the used variables.

https://github.com/r-lib/lintr/issues/2252

We do something similar for {glue} already, but the language for {cli} is similar-but-much-richer, e.g. {glue} does not support {.arg x}-style markup.

For {glue}, we use the .transformer argument to pull out all the names in {} execution regions inside glue() calls:

https://github.com/r-lib/lintr/blob/07c55846c1189740b7ac7a83b4b53203e23940ca/R/shared_constants.R#L264-L272

Is there any equivalent for {cli}? I saw #152 but I don't think cli_format_method() is quite suitable.

To be a bit clearer, here's some sample input/output:

cli_abort("{.arg x} has {length(x)} elements, but it should be {.emph {length(y)}}")
# --> c("x", "y")

We might be able to do something where we regex our way through and delete the {.markup ...} wrappers, but I think that's less than ideal.

MichaelChirico avatar Feb 20 '25 01:02 MichaelChirico

I think the best would be to add an option or function to cli to do this.

gaborcsardi avatar Feb 20 '25 06:02 gaborcsardi

OK, glad to know I'm not missing something.

Any initial thoughts on an API? something similar to the {glue} approach? I'm not all that familiar with {cli} internals.

cc @olivroy who has helped out with some related issues here.

MichaelChirico avatar Feb 20 '25 07:02 MichaelChirico

I would add a cli_extract_vars() (internal?) function.

gaborcsardi avatar Feb 20 '25 07:02 gaborcsardi

Looked at the implementation a little bit. AIUI, we'd want to use all.vars() (or all.names()) on expr here:

https://github.com/r-lib/cli/blob/b388132b4936cd1fb7536183c50b10f187dccd96/R/inline.R#L303

I'm still not sure the right way to surface that.

I also see that all of the functions like cli_abort() that support inline formatting are doing so through a call to cli::format_inline().

WDYT about a new argument like .record_names=FALSE to that function which would then alter how glue() is invoked?

MichaelChirico avatar Feb 20 '25 21:02 MichaelChirico

I also see that all of the functions like cli_abort() that support inline formatting are doing so through a call to cli::format_inline().

That would be very surprising to me. I would think almost none of them call format_inline().

gaborcsardi avatar Feb 20 '25 21:02 gaborcsardi

Oh, my mistake :)

I only checked one

https://github.com/r-lib/cli/blob/b388132b4936cd1fb7536183c50b10f187dccd96/R/rlang.R#L39-L44

MichaelChirico avatar Feb 20 '25 21:02 MichaelChirico

In that case the workhorse is actually glue_cmd(), which is currently private.

Maybe that could be exported with the suggested .record_names option instead?

Another idea is an option to strip {cli}-specific markup instead, returning a string that could be passed directly to {glue} with the {cli} logic removed, I think that means returning code in the above branch.

"{.arg x} has {length(x)} elements, but it should be {.emph {length(y)}} in {.pkg {.emph {pkg}}}"
# --> "x has {length(x)} elements, but it should be {length(y)} in {pkg}"

MichaelChirico avatar Feb 20 '25 21:02 MichaelChirico