Skip to content

Expose or repackage useful formatters (e.g. pluralization) #152

Description

@jonocarroll

I didn't see this in an existing issue, but apologies if it's been discussed already.

cli::cli_text("time: {x} year{?s}")

is an extremely useful piece of code but it's limited to console output (unless I'm mistaken). It would be more widely beneficial to either export a function which produces the text as a string before going to cat(), PR such a helper to {glue}, or create a helper package which {cli} could import.

It seems odd if one needs to use capture.output() on this to make use of it outside of a console.

Activity

  1. gaborcsardi commented on May 20, 2020

    @gaborcsardi
    Member

    Yeah, I agree it would be useful. How about a pluralize() function?

    (Note to self: cli cannot use this function internally, because of the two-phase substitution, etc. but pluralize() could just call the relevant parts of cli w/o theming and emitting anything.)

    Also, don't use capture.output for this, because it'll not work in non-interactive processes:

    ❯ R -q -e 'capture.output(cli::cli_text("time: {100} year{?s}"))'
    > capture.output(cli::cli_text("time: {100} year{?s}"))
    time: 100 years
    character(0)
    >

    The message goes to stderr, and nothing is captured on stdout. This is a bug: #153.

  2. jonocarroll commented on May 21, 2020

    @jonocarroll
    ContributorAuthor

    I was thinking more generally of exposing the (very cool) machinery you've built. For example, instead of triggering a message on the CLI the result could be inlined to a string, taking advantage of all the substitutions you support

    library(cli)
    
    cli_string <- function (..., .envir = parent.frame()) {
      string_inline(list(text = cli:::glue_cmd(..., .envir = .envir)))
    }
    
    string_inline <- function(texts) {
      out <- lapply(texts, function(t) {
        glue::glue(t$str, .envir = t$values, 
                   .open = paste0("{", t$values$marker), 
                   .close = paste0(t$values$marker, "}"))
      })
      paste(out, collapse = "")
    }
    
    cli_string("time: {1} year{?s}")
    #> [1] "time: 1 year"
    cli_string("time: {2} year{?s}")
    #> [1] "time: 2 years"
    cli_string("time: {3} year{?s}")
    #> [1] "time: 3 years"
    
    ndir <- 1; cli_string("Found {ndir} director{?y/ies}.")
    #> [1] "Found 1 directory."
    ndir <- 5; cli_string("Found {ndir} director{?y/ies}.")
    #> [1] "Found 5 directories."
    
    nfile <- 0; cli_string("Found {no(nfile)} file{?s}.")
    #> [1] "Found no files."
    nfile <- 2; cli_string("Found {no(nfile)} file{?s}.")
    #> [1] "Found 2 files."
    
    ## since I've removed the inline_transformer, this fails
    pkgs <- c("pkg1", "pkg2", "pkg3")
    cli_string("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.")
    #> Error in parse(text = text, keep.source = FALSE): <text>:1:6: unexpected '{'
    #> 1: .pkg {
    #>          ^
    
    nfiles <- 3; ndirs <- 1
    cli_string("Found {nfiles} file{?s} and {ndirs} director{?y/ies}")
    #> [1] "Found 3 files and 1 directory"

    Created on 2020-05-21 by the reprex package (v0.3.0)

    I appreciate (now that I've dug through enough of the source) that you're doing a lot to the text if it's headed for the terminal so this can't just be processed as a string then sent, but maybe the substitution mechanism can still be extracted to be used by both. It would be strange to have {cli} as a dependency for another package to build on top of this very general machinery (as above), but that's another possibility.

    @jimhester is this (substitution markers) something that could find a home in {glue}?

  3. gaborcsardi commented on May 21, 2020

    @gaborcsardi
    Member

    If you want a way to get a fully formatted string, you can do this:

    pkgs <- c("pkg1", "pkg2", "pkg3")
    cli_format_method(cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}."))
    #> [1] "Will remove the \033[34m\033[34mpkg1\033[34m\033[39m, \033[34m\033[34mpkg2\033[34m\033[39m, and \033[34m\033[34mpkg3\033[34m\033[39m packages."

    This does not use the current theme, for good or bad.

    I don't know how you could move the "substitution markers" to another package, if their visual appearance is computed in cli. What would {.pkg } do in another package?

  4. jonocarroll commented on May 21, 2020

    @jonocarroll
    ContributorAuthor

    Ah, that's exactly what I wanted, cheers! That's essentially a sink() around the cli output, right? My version above is purely external to the cliapp() so all of that code could be used externally with just a few extracted functions, starting with cli:::glue_cmd, but I don't think any of them touch cliapp().

    For the 'substitution markers' I only imagined the {?s} and {?none/one/several} possibilities outside of cli - the cli-specific styling has no meaning for strings so I don't expect e.g. {.pkg } to be useful outside of cli. The n-based replacements are a unique and valuable mechanism, and I'd love to see those more generally available.

  5. gaborcsardi commented on May 21, 2020

    @gaborcsardi
    Member

    That's essentially a sink() around the cli output, right?

    Well, not really. cli has a client-server model, where the client is throwing conditions and the server can catch these. If there is no server configured, then yes, cli will print the output on stdout (in interactive sessions) or stderr (in non-interactive ones). But as soon as there is a user or IDE defined server, a simple sink() will potentially not get any of the output. E.g. in a callr subprocess callr defines a server, catches these conditions and copies them over to the main process, so nothing is printed in the subprocess.

    For the 'substitution markers' I only imagined the {?s} and {?none/one/several} possibilities outside of cli - the cli-specific styling has no meaning for strings so I don't expect e.g. {.pkg } to be useful outside of cli. The n-based replacements are a unique and valuable mechanism, and I'd love to see those more generally available.

    Yes, this is what I suggested to have a pluralize() function for. Since the pluralization is my brain child, I wouldn't really want to move it to another package. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featurea feature request or enhancement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions