cli/0000755000176200001440000000000013605457531011032 5ustar liggesuserscli/NAMESPACE0000644000176200001440000000542613574451013012252 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(as.character,cli_no) S3method(as.character,cli_noprint) S3method(as.character,cli_sitrep) S3method(cli_format,character) S3method(cli_format,default) S3method(cli_format,numeric) S3method(format,cli_sitrep) S3method(print,ansi_string) S3method(print,ansi_style) S3method(print,boxx) S3method(print,cli_sitrep) S3method(print,cli_spinner) S3method(print,rule) S3method(print,tree) export(ansi_hide_cursor) export(ansi_show_cursor) export(ansi_with_hidden_cursor) export(bg_black) export(bg_blue) export(bg_cyan) export(bg_green) export(bg_magenta) export(bg_red) export(bg_white) export(bg_yellow) export(boxx) export(builtin_theme) export(cat_boxx) export(cat_bullet) export(cat_line) export(cat_print) export(cat_rule) export(cli_alert) export(cli_alert_danger) export(cli_alert_info) export(cli_alert_success) export(cli_alert_warning) export(cli_blockquote) export(cli_code) export(cli_div) export(cli_dl) export(cli_end) export(cli_format) export(cli_format_method) export(cli_h1) export(cli_h2) export(cli_h3) export(cli_li) export(cli_list_themes) export(cli_ol) export(cli_output_connection) export(cli_par) export(cli_process_done) export(cli_process_failed) export(cli_process_start) export(cli_rule) export(cli_sitrep) export(cli_status) export(cli_status_clear) export(cli_status_update) export(cli_text) export(cli_ul) export(cli_verbatim) export(col_black) export(col_blue) export(col_cyan) export(col_green) export(col_grey) export(col_magenta) export(col_red) export(col_silver) export(col_white) export(col_yellow) export(combine_ansi_styles) export(console_width) export(default_app) export(demo_spinners) export(get_spinner) export(is_ansi_tty) export(is_dynamic_tty) export(is_utf8_output) export(list_border_styles) export(list_spinners) export(list_symbols) export(make_ansi_style) export(make_spinner) export(no) export(qty) export(rule) export(simple_theme) export(start_app) export(stop_app) export(style_blurred) export(style_bold) export(style_dim) export(style_hidden) export(style_inverse) export(style_italic) export(style_reset) export(style_strikethrough) export(style_underline) export(symbol) export(tree) importFrom(assertthat,"on_failure<-") importFrom(assertthat,assert_that) importFrom(crayon,bold) importFrom(crayon,col_align) importFrom(crayon,col_nchar) importFrom(crayon,col_substr) importFrom(crayon,col_substring) importFrom(crayon,combine_styles) importFrom(crayon,italic) importFrom(crayon,make_style) importFrom(crayon,underline) importFrom(fansi,nchar_ctl) importFrom(fansi,strwrap_ctl) importFrom(fansi,substr_ctl) importFrom(glue,glue) importFrom(glue,glue_collapse) importFrom(methods,setOldClass) importFrom(stats,na.omit) importFrom(utils,globalVariables) importFrom(utils,head) importFrom(utils,modifyList) importFrom(utils,tail) cli/LICENSE0000644000176200001440000000005413565765747012056 0ustar liggesusersYEAR: 2017 COPYRIGHT HOLDER: Gábor Csárdi cli/README.md0000644000176200001440000001047113573667464012330 0ustar liggesusers cli === > Helpers for Developing Command Line Interfaces [![Linux Build Status](https://api.travis-ci.org/r-lib/cli.svg?branch=master)](https://travis-ci.org/r-lib/cli) [![Windows Build status](https://ci.appveyor.com/api/projects/status/github/r-lib/cli?svg=true)](https://ci.appveyor.com/project/gaborcsardi/cli) [![](http://www.r-pkg.org/badges/version/cli)](http://www.r-pkg.org/pkg/cli) [![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/cli)](http://www.r-pkg.org/pkg/cli) [![Coverage Status](https://img.shields.io/codecov/c/github/r-lib/cli/master.svg)](https://codecov.io/github/r-lib/cli?branch=master) A suite of tools to build attractive command line interfaces (CLIs), from semantic elements: headers, lists, alerts, paragraphs, etc. Supports theming via a CSS-like language. It also contains a number of lower level CLI elements: rules, boxes, trees, and Unicode symbols with ASCII alternatives. It integrates with the crayon package to support ANSI terminal colors. ------------------------------------------------------------------------ Features ======== - Build a CLI using semantic elements: headings, lists, alerts, paragraphs. - Theming via a CSS-like language. - Terminal colors via the [crayon](https://github.com/r-lib/crayon) package. - Create cli elements in subprocesses, using the [callr](https://github.com/r-lib/callr) package. - All cli text can contain interpreted string literals, via the [glue](https://github.com/tidyverse/glue) package. - Support for pluralized messages. Installation ============ Install the stable version from CRAN: ``` r install.packages("cli") ``` Short tour ---------- Some of the more commonly used cli elements, and features. ### Short alert messages One liner messages to inform or warn. ``` asciicast pkgs <- c("foo", "bar", "foobar") cli_alert_success("Downloaded {length(pkgs)} packages.") ``` ``` asciicast db_url <- "example.com:port" cli_alert_info("Reopened database {.url {db_url}}.") ``` ``` asciicast cli_alert_warning("Cannot reach GitHub, using local database cache.") ``` ``` asciicast cli_alert_danger("Failed to connect to database.") ``` ``` asciicast cli_alert("A generic alert") ``` ### Headings Three levels of headings. ``` asciicast cli_h1("Heading 1") ``` ``` asciicast cli_h2("Heading 2") ``` ``` asciicast cli_h3("Heading 3") ``` ### Lists Ordered, unordered and description lists, that can be nested. ``` asciicast fun <- function() { cli_ol() cli_li("Item 1") ulid <- cli_ul() cli_li("Subitem 1") cli_li("Subitem 2") cli_end(ulid) cli_li("Item 2") cli_end() } fun() ``` ### Themes Theming via a CSS-like language. ``` asciicast fun <- function() { cli_div(theme = list(span.emph = list(color = "orange"))) cli_text("This is very {.emph important}") cli_end() cli_text("Back to the {.emph previous theme}") } fun() ``` ### Command substitution Automatic command substitution via the [glue](https://github.com/tidyverse/glue) package. ``` asciicast size <- 123143123 dt <- 1.3454 cli_alert_info(c( "Downloaded {prettyunits::pretty_bytes(size)} in ", "{prettyunits::pretty_sec(dt)}")) ``` ### Pluralization Pluralization support. ``` asciicast nfiles <- 3 ndirs <- 1 cli_alert_info("Found {nfiles} file{?s} and {ndirs} director{?y/ies}.") ``` Documentation ------------- See at and also in the installed package: `?"inline-markup"`, `?containers`, `?themes`, `?pluralization`. License ======= MIT © RStudio cli/man/0000755000176200001440000000000013573667373011620 5ustar liggesuserscli/man/is_utf8_output.Rd0000644000176200001440000000102713565765747015115 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utf8.R \name{is_utf8_output} \alias{is_utf8_output} \title{Whether cli is emitting UTF-8 characters} \usage{ is_utf8_output() } \value{ Flag, whether cli uses UTF-8 characters. } \description{ UTF-8 cli characters can be turned on by setting the \code{cli.unicode} option to \code{TRUE}. They can be turned off by setting if to \code{FALSE}. If this option is not set, then \code{\link[base:l10n_info]{base::l10n_info()}} is used to detect UTF-8 support. } cli/man/cli_output_connection.Rd0000644000176200001440000000106013572271360016474 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tty.R \name{cli_output_connection} \alias{cli_output_connection} \title{The connection option that cli would use} \usage{ cli_output_connection() } \value{ Connection object. } \description{ Note that this only refers to the current R process. If the output is produced in another process, then it is not relevant. } \details{ In interactive sessions the standard output is chosen, othrwise the standard error is used. This is to avoid painting output messages red in the R GUIs. } cli/man/pluralization.Rd0000644000176200001440000001673613571261512015000 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/pluralize.R \name{pluralization} \alias{pluralization} \title{CLI pluralization} \description{ CLI pluralization } \seealso{ Other pluralization: \code{\link{no}()} } \concept{pluralization} \section{Introduction}{ cli has tools to create messages that are printed correctly in singular and plural forms. This usually requires minimal extra work, and increases the quality of the messages greatly. In this document we first show some pluralization examples that you can use as guidelines. Hopefully these are intuitive enough, so that they can be used without knowing the exact cli pluralization rules. } \section{Examples}{ \subsection{Pluralization markup}{ In the simplest case the message contains a single \code{{}} glue substitution, which specifies the quantity that is used to select between the singular and plural forms. Pluralization uses markup that is similar to glue, but uses the \verb{\{?} and \verb{\}} delimiters:\if{html}{\out{
}}\preformatted{library(cli) nfile <- 0; cli_text("Found \{nfile\} file\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Found 0 files. }\if{html}{\out{
}}\preformatted{nfile <- 1; cli_text("Found \{nfile\} file\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Found 1 file. }\if{html}{\out{
}}\preformatted{nfile <- 2; cli_text("Found \{nfile\} file\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Found 2 files. } Here the value of \code{nfile} is used to decide whether the singular or plural form of \code{file} is used. This is the most common case for English messages. } \subsection{Irregular plurals}{ If the plural form is more difficult than a simple \code{s} suffix, then the singular and plural forms can be given, separated with a forward slash:\if{html}{\out{
}}\preformatted{ndir <- 1; cli_text("Found \{ndir\} director\{?y/ies\}.") }\if{html}{\out{
}}\preformatted{#> Found 1 directory. }\if{html}{\out{
}}\preformatted{ndir <- 5; cli_text("Found \{ndir\} director\{?y/ies\}.") }\if{html}{\out{
}}\preformatted{#> Found 5 directories. } } \subsection{Use “no” instead of zero}{ For readability, it is better to use the \code{no()} helper function to include a count in a message. \code{no()} prints the word “no” if the count is zero, and prints the numeric count otherwise:\if{html}{\out{
}}\preformatted{nfile <- 0; cli_text("Found \{no(nfile)\} file\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Found no files. }\if{html}{\out{
}}\preformatted{nfile <- 1; cli_text("Found \{no(nfile)\} file\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Found 1 file. }\if{html}{\out{
}}\preformatted{nfile <- 2; cli_text("Found \{no(nfile)\} file\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Found 2 files. } } \subsection{Use the length of character vectors}{ With the auto-collapsing feature of cli it is easy to include a list of objects in a message. When cli interprets a character vector as a pluralization quantity, it takes the length of the vector:\if{html}{\out{
}}\preformatted{pkgs <- "pkg1" cli_text("Will remove the \{.pkg \{pkgs\}\} package\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Will remove the pkg1 package. }\if{html}{\out{
}}\preformatted{pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove the \{.pkg \{pkgs\}\} package\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Will remove the pkg1, pkg2 and pkg3 packages. } Note that the length is only used for non-numeric vectors (when \code{is.numeric(x)} return \code{FALSE}). If you want to use the length of a numeric vector, convert it to character via \code{as.character()}. You can combine collapsed vectors with “no”, like this:\if{html}{\out{
}}\preformatted{pkgs <- character() cli_text("Will remove \{?no/the/the\} \{.pkg \{pkgs\}\} package\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Will remove no packages. }\if{html}{\out{
}}\preformatted{pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove \{?no/the/the\} \{.pkg \{pkgs\}\} package\{?s\}.") }\if{html}{\out{
}}\preformatted{#> Will remove the pkg1, pkg2 and pkg3 packages. } When the pluralization markup contains three alternatives, like above, the first one is used for zero, the second for one, and the third one for larger quantities. } \subsection{Choosing the right quantity}{ When the text contains multiple glue \code{{}} substitutions, the one right before the pluralization markup is used. For example:\if{html}{\out{
}}\preformatted{nfiles <- 3; ndirs <- 1 cli_text("Found \{nfiles\} file\{?s\} and \{ndirs\} director\{?y/ies\}") }\if{html}{\out{
}}\preformatted{#> Found 3 files and 1 directory } This is sometimes not the the correct one. You can explicitly specify the correct quantity using the \code{qty()} function. This sets that quantity without printing anything:\if{html}{\out{
}}\preformatted{nupd <- 3; ntotal <- 10 cli_text("\{nupd\}/\{ntotal\} \{qty(nupd)\} file\{?s\} \{?needs/need\} updates") }\if{html}{\out{
}}\preformatted{#> 3/10 files need updates } Note that if the message only contains a single \code{{}} substitution, then this may appear before or after the pluralization markup. If the message contains multiple \code{{}} substitutions \emph{after} pluralization markup, an error is thrown. Similarly, if the message contains no \code{{}} substituions at all, but has pluralization markup, and error is thrown. } } \section{Rules}{ The exact rules of cli’s pluralization. There are two sets of rules. The first set specifies how a quantity is associated with a \verb{\{?\}} pluralization markup. The second set describes how the \verb{\{?\}} is parsed and interpreted. \subsection{Quantities}{ \enumerate{ \item \code{{}} substitutions define quantities. If the value of a \code{{}} substitution is numeric (i.e. \code{is.numeric(x)} holds), then it has to have length one to define a quantity. This is only enforced if the \code{{}} substitution is used for pluralization. The quantity is defined as the value of \code{{}} then, rounded with \code{as.integer()}. If the value of \code{{}} is not numeric, then its quantity is defined as its length. \item If a message has \verb{\{?\}} markup but no \code{{}} substitution, an error is thrown. \item If a message has exactly one \code{{}} substitution, its value is used as the pluralization quantity for all \verb{\{?\}} markup in the message. \item If a message has multiple \code{{}} substitutions, then for each \verb{\{?\}} markup cli uses the quantity of the \code{{}} substitution that precedes it. \item If a message has multiple \code{{}} substitutions and has pluralization markup with a preceding \code{{}} substitution, and error is thrown. } } \subsection{Pluralization markup}{ \enumerate{ \item Pluralization markup start with \verb{\{?} and ends with \verb{\}}. It may not contain \verb{\{} and \verb{\}} characters, so it may not contains \code{{}} substitutions either. \item Alternative words or suffixes are separated by \code{/}. \item If there is a single alternative, then \emph{nothing} is used if \code{quantity == 1} and this single alternative is used if \code{quantity != 1}. \item If there are two alternatives, the first one is used for \code{quantity == 1}, the second one for \code{quantity != 1} (include 0). \item If there are three alternatives, the first one is used for \code{quantity == 0}, the second for \code{quantity == 1}, and the third otherwise. } } } cli/man/make_spinner.Rd0000644000176200001440000000470613565765747014576 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/spinner.R \name{make_spinner} \alias{make_spinner} \title{Create a spinner} \usage{ make_spinner( which = NULL, stream = stderr(), template = "{spin}", static = c("dots", "print", "print_line", "silent") ) } \arguments{ \item{which}{The name of the chosen spinner. The default depends on whether the platform supports Unicode.} \item{stream}{The stream to use for the spinner. Typically this is standard error, or maybe the standard output stream.} \item{template}{A template string, that will contain the spinner. The spinner itself will be substituted for \code{{spin}}. See example below.} \item{static}{What to do if the terminal does not support dynamic displays: \itemize{ \item \code{"dots"}: show a dot for each \verb{$spin()} call. \item \code{"print"}: just print the frames of the spinner, one after another. \item \code{"print_line"}: print the frames of the spinner, each on its own line. \item \code{"silent"} do not print anything, just the \code{template}. }} } \value{ A \code{cli_spinner} object, which is a list of functions. See its methods below. \code{cli_spinner} methods: \itemize{ \item \verb{$spin()}: output the next frame of the spinner. \item \verb{$finish()}: terminate the spinner. Depending on terminal capabilities this removes the spinner from the screen. Spinners can be reused, you can start calling the \verb{$spin()} method again. } All methods return the spinner object itself, invisibly. The spinner is automatically throttled to its ideal update frequency. } \description{ Create a spinner } \section{Examples}{ \preformatted{## Default spinner sp1 <- make_spinner() fun_with_spinner <- function() \{ lapply(1:100, function(x) \{ sp1$spin(); Sys.sleep(0.05) \}) sp1$finish() \} ansi_with_hidden_cursor(fun_with_spinner()) ## Spinner with a template sp2 <- make_spinner(template = "Computing \{spin\}") fun_with_spinner2 <- function() \{ lapply(1:100, function(x) \{ sp2$spin(); Sys.sleep(0.05) \}) sp2$finish() \} ansi_with_hidden_cursor(fun_with_spinner2()) ## Custom spinner sp3 <- make_spinner("simpleDotsScrolling", template = "Downloading \{spin\}") fun_with_spinner3 <- function() \{ lapply(1:100, function(x) \{ sp3$spin(); Sys.sleep(0.05) \}) sp2$finish() \} ansi_with_hidden_cursor(fun_with_spinner3()) } } \seealso{ Other spinners: \code{\link{demo_spinners}()}, \code{\link{get_spinner}()}, \code{\link{list_spinners}()} } \concept{spinners} cli/man/console_width.Rd0000644000176200001440000000067113565765747014761 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/width.R \name{console_width} \alias{console_width} \title{Determine the width of the console} \usage{ console_width() } \value{ Integer scalar, the console with, in number of characters. } \description{ It uses the \code{RSTUDIO_CONSOLE_WIDTH} environment variable, if set. Otherwise it uses the \code{width} option. If this is not set either, then 80 is used. } cli/man/cli_text.Rd0000644000176200001440000000173313565765747013733 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_text} \alias{cli_text} \title{CLI text} \usage{ cli_text(..., .envir = parent.frame()) } \arguments{ \item{...}{The text to show, in character vectors. They will be concatenated into a single string. Newlines are \emph{not} preserved.} \item{.envir}{Environment to evaluate the glue expressions in.} } \description{ It is wrapped to the screen width automatically. It may contain inline markup. (See \link{inline-markup}.) } \examples{ cli_text("Hello world!") cli_text(packageDescription("cli")$Description) ## Arguments are concatenated cli_text("this", "that") ## Command substitution greeting <- "Hello" subject <- "world" cli_text("{greeting} {subject}!") ## Inline theming cli_text("The {.fn cli_text} function in the {.pkg cli} package") ## Use within container elements ul <- cli_ul() cli_li() cli_text("{.emph First} item") cli_li() cli_text("{.emph Second} item") cli_end(ul) } cli/man/cli_dl.Rd0000644000176200001440000000260313565765747013343 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_dl} \alias{cli_dl} \title{Definition list} \usage{ cli_dl( items = NULL, id = NULL, class = NULL, .close = TRUE, .auto_close = TRUE, .envir = parent.frame() ) } \arguments{ \item{items}{Named character vector, or \code{NULL}. If not \code{NULL}, they are used as list items.} \item{id}{Id of the list container. Can be used for closing it with \code{\link[=cli_end]{cli_end()}} or in themes. If \code{NULL}, then an id is generated and retuned invisibly.} \item{class}{Class of the list container. Can be used in themes.} \item{.close}{Whether to close the list container if the \code{items} were specified. If \code{FALSE} then new items can be added to the list.} \item{.auto_close}{Whether to close the container, when the calling function finishes (or \code{.envir} is removed, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-close the container if \code{.auto_close} is \code{TRUE}.} } \value{ The id of the new container element, invisibly. } \description{ A definition list is a container, see \link{containers}. } \examples{ ## Specifying the items at the beginning cli_dl(c(foo = "one", bar = "two", baz = "three")) ## Adding items one by one cli_dl() cli_li(c(foo = "one")) cli_li(c(bar = "two")) cli_li(c(baz = "three")) cli_end() } cli/man/cli_li.Rd0000644000176200001440000000235113565765747013350 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_li} \alias{cli_li} \title{CLI list item(s)} \usage{ cli_li( items = NULL, id = NULL, class = NULL, .auto_close = TRUE, .envir = parent.frame() ) } \arguments{ \item{items}{Character vector of items, or \code{NULL}.} \item{id}{Id of the new container. Can be used for closing it with \code{\link[=cli_end]{cli_end()}} or in themes. If \code{NULL}, then an id is generated and retuned invisibly.} \item{class}{Class of the item container. Can be used in themes.} \item{.auto_close}{Whether to close the container, when the calling function finishes (or \code{.envir} is removed, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-close the container if \code{.auto_close} is \code{TRUE}.} } \value{ The id of the new container element, invisibly. } \description{ A list item is a container, see \link{containers}. } \examples{ ## Adding items one by one cli_ul() cli_li("one") cli_li("two") cli_li("three") cli_end() ## Complex item, added gradually. cli_ul() cli_li() cli_verbatim("Beginning of the {.emph first} item") cli_text("Still the first item") cli_end() cli_li("Second item") cli_end() } cli/man/cli_format_method.Rd0000644000176200001440000000403013571261512015542 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/print.R \name{cli_format_method} \alias{cli_format_method} \title{Create a format method for an object using cli tools} \usage{ cli_format_method(expr, theme = getOption("cli.theme")) } \arguments{ \item{expr}{Expression that calls \verb{cli_*} methods, \code{\link[base:cat]{base::cat()}} or \code{\link[base:print]{base::print()}} to format an object's printout.} \item{theme}{Theme to use for the formatting.} } \value{ Character vector, one element for each line of the printout. } \description{ This method can be typically used in \code{format()} S3 methods. Then the \code{print()} method of the class can be easily defined in terms of such a \code{format()} method. See examples below. } \examples{ # Let's create format and print methods for a new S3 class that # represents the an installed R package: `r_package` # An `r_package` will contain the DESCRIPTION metadata of the package # and also its installation path. new_r_package <- function(pkg) { tryCatch( desc <- packageDescription(pkg), warning = function(e) stop("Cannot find R package `", pkg, "`") ) file <- dirname(attr(desc, "file")) if (basename(file) != pkg) file <- dirname(file) structure( list(desc = unclass(desc), lib = dirname(file)), class = "r_package" ) } format.r_package <- function(x, ...) { cli_format_method({ cli_h1("{.pkg {x$desc$Package}} {cli::symbol$line} {x$desc$Title}") cli_text("{x$desc$Description}") cli_ul(c( "Version: {x$desc$Version}", if (!is.null(x$desc$Maintainer)) "Maintainer: {x$desc$Maintainer}", "License: {x$desc$License}" )) if (!is.na(x$desc$URL)) cli_text("See more at {.url {x$desc$URL}}") }) } # Now the print method is easy: print.r_package <- function(x, ...) { cat(format(x, ...), sep = "\n") } # Try it out new_r_package("cli") # The formatting of the output depends on the current theme: opt <- options(cli.theme = simple_theme()) print(new_r_package("cli")) options(opt) # <- restore theme } cli/man/get_spinner.Rd0000644000176200001440000000144113565765747014431 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/spinner.R \name{get_spinner} \alias{get_spinner} \title{Character vector to put a spinner on the screen} \usage{ get_spinner(which = NULL) } \arguments{ \item{which}{The name of the chosen spinner. The default depends on whether the platform supports Unicode.} } \value{ A list with entries: \code{name}, \code{interval}: the suggested update interval in milliseconds and \code{frames}: the character vector of the spinner's frames. } \description{ \code{cli} contains many different spinners, you choose one according to your taste. } \examples{ get_spinner() get_spinner("shark") } \seealso{ Other spinners: \code{\link{demo_spinners}()}, \code{\link{list_spinners}()}, \code{\link{make_spinner}()} } \concept{spinners} cli/man/simple_theme.Rd0000644000176200001440000000364213573304532014550 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/simple-theme.R \name{simple_theme} \alias{simple_theme} \title{A simple CLI theme} \usage{ simple_theme(dark = getOption("cli_theme_dark", "auto")) } \arguments{ \item{dark}{Whether the theme should be optiomized for a dark background. If \code{"auto"}, then cli will try to detect this. Detection usually works in recent RStudio versions, and in iTerm on macOS, but not on other platforms.} } \description{ Note that this is in addition to the builtin theme. To use this theme, you can set it as the \code{cli.theme} option: } \details{ \preformatted{options(cli.theme = cli::simple_theme()) } and then CLI apps started after this will use it as the default theme. You can also use it temporarily, in a div element:\preformatted{cli_div(theme = cli::simple_theme()) } } \examples{ cli_div(theme = cli::simple_theme()) cli_h1("Heading 1") cli_h2("Heading 2") cli_h3("Heading 3") cli_alert_danger("Danger alert") cli_alert_warning("Warning alert") cli_alert_info("Info alert") cli_alert_success("Success alert") cli_alert("Alert for starting a process or computation", class = "alert-start") cli_text("Packages and versions: {.pkg cli} {.version 1.0.0}.") cli_text("Time intervals: {.timestamp 3.4s}") cli_text("{.emph Emphasis} and {.strong strong emphasis}") cli_text("This is a piece of code: {.code sum(x) / length(x)}") cli_text("Function names: {.fn cli::simple_theme}") cli_text("Files: {.file /usr/bin/env}") cli_text("URLs: {.url https://r-project.org}") cli_h2("Longer code chunk") cli_par(class = "code R") cli_verbatim( '# window functions are useful for grouped mutates', 'mtcars \%>\%', ' group_by(cyl) \%>\%', ' mutate(rank = min_rank(desc(mpg)))') cli_end() cli_h2("Even longer code chunk") cli_par(class = "code R") cli_verbatim(format(ls)) cli_end() cli_end() } \seealso{ \link{themes}, \code{\link[=builtin_theme]{builtin_theme()}}. } cli/man/inline-markup.Rd0000644000176200001440000001052113571734652014652 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cliapp-docs.R \name{inline-markup} \alias{inline-markup} \title{CLI inline markup} \description{ CLI inline markup } \section{Command substitution}{ All text emitted by cli supports glue interpolation. Expressions enclosed by braces will be evaluated as R code. See \code{\link[glue:glue]{glue::glue()}} for details. In addition to regular glue interpolation, cli can also add classes to parts of the text, and these classes can be used in themes. For example\preformatted{cli_text("This is \{.emph important\}.") } adds a class to the "important" word, class "emph". Note that in this case the string within the braces is usually not a valid R expression. If you want to mix classes with interpolation, add another pair of braces:\preformatted{adjective <- "great" cli_text("This is \{.emph \{adjective\}\}.") } An inline class will always create a \code{span} element internally. So in themes, you can use the \code{span.emph} CSS selector to change how inline text is emphasized:\preformatted{cli_div(theme = list(span.emph = list(color = "red"))) adjective <- "nice and red" cli_text("This is \{.emph \{adjective\}\}.") } } \section{Classes}{ The default theme defines the following inline classes: \itemize{ \item \code{emph} for emphasized text. \item \code{strong} for strong importance. \item \code{code} for a piece of code. \item \code{pkg} for a package name. \item \code{fun} for a function name. \item \code{arg} for a function argument. \item \code{key} for a keyboard key. \item \code{file} for a file name. \item \code{path} for a path (essentially the same as \code{file}). \item \code{email} for an email address. \item \code{url} for a URL. \item \code{var} for a variable name. \item \code{envvar} for the name of an environment variable. \item \code{val} for a "value". } See examples below. You can simply add new classes by defining them in the theme, and then using them, see the example below. } \section{Collapsing inline vectors}{ When cli performs inline text formatting, it automatically collapses glue substitutions, after formatting. This is handy to create lists of files, packages, etc. See examples below. } \section{Formatting values}{ The \code{val} inline class formats values. By default (c.f. the builtin theme), it calls the \code{\link[=cli_format]{cli_format()}} generic function, with the current style as the argument. See \code{\link[=cli_format]{cli_format()}} for examples. } \section{Escaping \verb{\{} and \verb{\}}}{ It might happen that you want to pass a string to \verb{cli_*} functions, and you do not_ want command substitution in that string, because it might contain \verb{\}} and \verb{\{} characters. The simplest solution for this is referring to the string from a template:\preformatted{msg <- "Error in if (ncol(dat$y)) \{: argument is of length zero" cli_alert_warning("\{msg\}") } If you want to explicitly escape \verb{\{} and \verb{\}} characters, just double them:\preformatted{cli_alert_warning("A warning with \{\{ braces \}\}") } See also examples below. } \section{Pluralization}{ All cli commands that emit text support pluralization. Some examples:\preformatted{cli_alert_info("Found \{ndirs\} diretor\{?y/ies\} and \{nfiles\} file\{?s\}.") cli_text("Will install \{length(pkgs)\} package\{?s\}: \{.pkg \{pkgs\}\}") } See \link{pluralization} for details. } \examples{ ## Some inline markup examples cli_ul() cli_li("{.emph Emphasized} text") cli_li("{.strong Strong} importance") cli_li("A piece of code: {.code sum(a) / length(a)}") cli_li("A package name: {.pkg cli}") cli_li("A function name: {.fn cli_text}") cli_li("A keyboard key: press {.kbd ENTER}") cli_li("A file name: {.file /usr/bin/env}") cli_li("An email address: {.email bugs.bunny@acme.com}") cli_li("A URL: {.url https://acme.com}") cli_li("An environment variable: {.envvar R_LIBS}") cli_end() ## Adding a new class cli_div(theme = list( span.myclass = list(color = "lightgrey"), "span.myclass" = list(before = "["), "span.myclass" = list(after = "]"))) cli_text("This is {.myclass in brackets}.") cli_end() ## Collapsing pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Packages: {pkgs}.") cli_text("Packages: {.pkg {pkgs}}") ## Escaping msg <- "Error in if (ncol(dat$y)) {: argument is of length zero" cli_alert_warning("{msg}") cli_alert_warning("A warning with {{ braces }}") } cli/man/match_selector_node.Rd0000644000176200001440000000170713565765747016122 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/themes.R \name{match_selector_node} \alias{match_selector_node} \title{Match a selector node to a container} \usage{ match_selector_node(node, cnt) } \arguments{ \item{node}{Selector node, as parsed by \code{parse_selector_node()}.} \item{cnt}{Container node, has elements \code{tag}, \code{id}, \code{class}. The selector node matches the container, if all these hold: \itemize{ \item The id of the selector is missing or unique. \item The tag of the selector is missing or unique. \item The id of the container is missing or unique. \item The tag of the container is unique. \item If the selector specifies an id, it matches the id of the container. \item If the selector specifies a tag, it matxhes the tag of the container. \item If the selector specifies class names, the container has all these classes. }} } \description{ Match a selector node to a container } \keyword{internal} cli/man/cli_sitrep.Rd0000644000176200001440000000173113565765747014253 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/sitrep.R \name{cli_sitrep} \alias{cli_sitrep} \title{cli situation report} \usage{ cli_sitrep() } \value{ Named list with entries listed above. It has a \code{cli_sitrep} class, with a \code{print()} and \code{format()} method. } \description{ Contains currenty: \itemize{ \item \code{cli_unicode_option}: whether the \code{cli.unicode} option is set and its value. See \code{\link[=is_utf8_output]{is_utf8_output()}}. \item \code{symbol_charset}: the selected character set for \link{symbol}, UTF-8, Windows, or ASCII. \item \code{console_utf8}: whether the console supports UTF-8. See \code{\link[base:l10n_info]{base::l10n_info()}}. \item \code{latex_active}: whether we are inside knitr, creating a LaTeX document. \item \code{num_colors}: number of ANSI colors. See \code{\link[crayon:num_colors]{crayon::num_colors()}}. \item \code{console_with}: detected console width. } } \examples{ cli_sitrep() } cli/man/parse_selector.Rd0000644000176200001440000000153713565765747015134 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/themes.R \name{parse_selector} \alias{parse_selector} \title{Parse a CSS3-like selector} \usage{ parse_selector(x) } \arguments{ \item{x}{CSS3-like selector string.} } \description{ This is the rather small subset of CSS3 that is supported: } \details{ Selectors: \itemize{ \item Type selectors, e.g. \code{input} selects all \verb{} elements. \item Class selectors, e.g. \code{.index} selects any element that has a class of "index". \item ID selector. \verb{#toc} will match the element that has the ID "toc". } Combinators: \itemize{ \item Descendant combinator, i.e. the space, that combinator selects nodes that are descendants of the first element. E.g. \verb{div span} will match all \verb{} elements that are inside a \verb{
} element. } } \keyword{internal} cli/man/cli_process_start.Rd0000644000176200001440000000667413571723077015636 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/status-bar.R \name{cli_process_start} \alias{cli_process_start} \alias{cli_process_done} \alias{cli_process_failed} \title{Indicate the start and termination of some computation in the status bar} \usage{ cli_process_start( msg, msg_done = paste(msg, "... done"), msg_failed = paste(msg, "... failed"), on_exit = c("failed", "done"), msg_class = "alert-info", done_class = "alert-success", failed_class = "alert-danger", .auto_close = TRUE, .envir = parent.frame() ) cli_process_done( id = NULL, msg_done = NULL, .envir = parent.frame(), done_class = "alert-success" ) cli_process_failed( id = NULL, msg = NULL, msg_failed = NULL, .envir = parent.frame(), failed_class = "alert-danger" ) } \arguments{ \item{msg}{The message to show to indicate the start of the process or compuration. It will be collapsed into a single string, and the first line is kept and cut to \code{\link[=console_width]{console_width()}}.} \item{msg_done}{The message to use for successful termination.} \item{msg_failed}{The message to use for unsuccessful termination.} \item{on_exit}{Whether this process should fail or terminate successfully when the calling function (or the environment in \code{.envir}) exits.x} \item{msg_class}{The style class to add to the message. Use an empty string to suppress styling.} \item{done_class}{The style class to add to the successful termination message. Use an empty string to suppress styling.a} \item{failed_class}{The style class to add to the unsuccessful termination message. Use an empty string to suppress styling.a} \item{.auto_close}{Whether to clear the status bar when the calling function finishes (or ‘.envir’ is removed from the stack, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-clear the status bar if \code{.auto_close} is `TRUE.} \item{id}{Id of the status bar container to clear. If \code{id} is not the id of the current status bar (because it was overwritten by another status bar container), then the status bar is not cleared. If \code{NULL} (the default) then the status bar is always cleared.} } \value{ Id of the status bar container. } \description{ Typically you call \code{cli_process_start()} to start the process, and then \code{cli_process_done()} when it is done. If an error happens before \code{cli_process_fone()} is called, then cli automatically shows the message for unsuccessful termination. } \details{ If you handle the errors of the process or computation, then you can do the opposite: call \code{cli_process_start()} with \code{on_exit = "done"}, and in the error handler call \code{cli_process_failed()}. cli will automatically call \code{cli_process_done()} on successful termination, when the calling function finishes. See examples below. } \examples{ ## Failure by default fun <- function() { cli_process_start("Calculating") if (interactive()) Sys.sleep(1) if (runif(1) < 0.5) stop("Failed") cli_process_done() } tryCatch(fun(), error = function(err) err) ## Success by default fun2 <- function() { cli_process_start("Calculating", on_exit = "done") tryCatch({ if (interactive()) Sys.sleep(1) if (runif(1) < 0.5) stop("Failed") }, error = function(err) cli_process_failed()) } fun2() } \seealso{ Other status bar: \code{\link{cli_status_clear}()}, \code{\link{cli_status_update}()}, \code{\link{cli_status}()} } \concept{status bar} cli/man/match_selector.Rd0000644000176200001440000000107613571261512015066 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/themes.R \name{match_selector} \alias{match_selector} \title{Match a selector to a container stack} \usage{ match_selector(sels, cnts) } \arguments{ \item{sels}{A list of selector nodes.} \item{cnts}{A list of container nodes. The last selector in the list must match the last container, so we do the matching from the back. This is because we use this function to calculate the style of newly encountered containers.} } \description{ Match a selector to a container stack } \keyword{internal} cli/man/builtin_theme.Rd0000644000176200001440000000133013573307244014720 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/themes.R \name{builtin_theme} \alias{builtin_theme} \title{The built-in CLI theme} \usage{ builtin_theme(dark = getOption("cli_theme_dark", "auto")) } \arguments{ \item{dark}{Whether to use a dark theme. The \code{cli_theme_dark} option can be used to request a dark theme explicitly. If this is not set, or set to \code{"auto"}, then cli tries to detect a dark theme, this works in recent RStudio versions and in iTerm on macOS.} } \value{ A named list, a CLI theme. } \description{ This theme is always active, and it is at the bottom of the theme stack. See \link{themes}. } \seealso{ \link{themes}, \code{\link[=simple_theme]{simple_theme()}}. } cli/man/list_spinners.Rd0000644000176200001440000000075613565765747015020 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/spinner.R \name{list_spinners} \alias{list_spinners} \title{List all available spinners} \usage{ list_spinners() } \value{ Character vector of all available spinner names. } \description{ List all available spinners } \examples{ list_spinners() get_spinner(list_spinners()[1]) } \seealso{ Other spinners: \code{\link{demo_spinners}()}, \code{\link{get_spinner}()}, \code{\link{make_spinner}()} } \concept{spinners} cli/man/pluralization-helpers.Rd0000644000176200001440000000111113571261512016415 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/pluralize.R \name{no} \alias{no} \alias{qty} \title{Pluralization helper functions} \usage{ no(expr) qty(expr) } \arguments{ \item{expr}{For \code{no()} it is an expression that is printed as "no" in cli expressions, it is interpreted as a zero quantity. For \code{qty()} an expression that sets the pluralization quantity without printing anything. See examples below.} } \description{ Pluralization helper functions } \seealso{ Other pluralization: \code{\link{pluralization}} } \concept{pluralization} cli/man/rule.Rd0000644000176200001440000000525313571261512013042 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/rules.R \name{rule} \alias{rule} \title{Make a rule with one or two text labels} \usage{ rule( left = "", center = "", right = "", line = 1, col = NULL, line_col = col, background_col = NULL, width = console_width() ) } \arguments{ \item{left}{Label to show on the left. It interferes with the \code{center} label, only at most one of them can be present.} \item{center}{Label to show at the center. It interferes with the \code{left} and \code{right} labels.} \item{right}{Label to show on the right. It interferes with the \code{center} label, only at most one of them can be present.} \item{line}{The character or string that is used to draw the line. It can also \code{1} or \code{2}, to request a single line (Unicode, if available), or a double line. Some strings are interpreted specially, see \emph{Line styles} below.} \item{col}{Color of text, and default line color. Either an ANSI style function (see \link{ansi-styles}), or a color name that is passed to \code{\link[=make_ansi_style]{make_ansi_style()}}.} \item{line_col, background_col}{Either a color name (used in \code{\link[=make_ansi_style]{make_ansi_style()}}), or a style function (see \link{ansi-styles}), to color the line and background.} \item{width}{Width of the rule. Defaults to the \code{width} option, see \code{\link[base:options]{base::options()}}.} } \value{ Character scalar, the rule. } \description{ The rule can include either a centered text label, or labels on the left and right side. } \details{ To color the labels, use the functions \verb{col_*}, \verb{bg_*} and \verb{style_*} functions, see \link{ansi-styles}, and the examples below. To color the line, either these functions directly, or the \code{line_col} option. } \section{Line styles}{ Some strings for the \code{line} argument are interpreted specially: \itemize{ \item \code{"single"}: (same as \code{1}), a single line, \item \code{"double"}: (same as \code{2}), a double line, \item \code{"bar1"}, \code{"bar2"}, \code{"bar3"}, etc., \code{"bar8"} uses varying height bars. } } \examples{ ## Simple rule rule() ## Double rule rule(line = 2) ## Bars rule(line = "bar2") rule(line = "bar5") ## Left label rule(left = "Results") ## Centered label rule(center = " * RESULTS * ") ## Colored labels rule(center = col_red(" * RESULTS * ")) ## Colored line rule(center = col_red(" * RESULTS * "), line_col = "red") ## Custom line rule(center = "TITLE", line = "~") ## More custom line rule(center = "TITLE", line = col_blue("~-")) ## Even more custom line rule(center = bg_red(" ", symbol$star, "TITLE", symbol$star, " "), line = "\u2582", line_col = "orange") } cli/man/cli_ol.Rd0000644000176200001440000000311613565765747013356 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_ol} \alias{cli_ol} \title{Ordered CLI list} \usage{ cli_ol( items = NULL, id = NULL, class = NULL, .close = TRUE, .auto_close = TRUE, .envir = parent.frame() ) } \arguments{ \item{items}{If not \code{NULL}, then a character vector. Each element of the vector will be one list item, and the list container will be closed by default (see the \code{.close} argument).} \item{id}{Id of the list container. Can be used for closing it with \code{\link[=cli_end]{cli_end()}} or in themes. If \code{NULL}, then an id is generated and retuned invisibly.} \item{class}{Class of the list container. Can be used in themes.} \item{.close}{Whether to close the list container if the \code{items} were specified. If \code{FALSE} then new items can be added to the list.} \item{.auto_close}{Whether to close the container, when the calling function finishes (or \code{.envir} is removed, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-close the container if \code{.auto_close} is \code{TRUE}.} } \value{ The id of the new container element, invisibly. } \description{ An ordered list is a container, see \link{containers}. } \examples{ ## Specifying the items at the beginning cli_ol(c("one", "two", "three")) ## Adding items one by one cli_ol() cli_li("one") cli_li("two") cli_li("three") cli_end() ## Nested lists cli_div(theme = list(ol = list("margin-left" = 2))) cli_ul() cli_li("one") cli_ol(c("foo", "bar", "foobar")) cli_li("two") cli_end() cli_end() } cli/man/tree.Rd0000644000176200001440000000770313565765747013062 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tree.R \name{tree} \alias{tree} \title{Draw a tree} \usage{ tree( data, root = data[[1]][[1]], style = NULL, width = console_width(), trim = FALSE ) } \arguments{ \item{data}{Data frame that contains the tree structure. The first column is an id, and the second column is a list column, that contains the ids of the child nodes. The optional third column may contain the text to print to annotate the node.} \item{root}{The name of the root node.} \item{style}{Optional box style list.} \item{width}{Maximum width of the output. Defaults to the \code{width} option, see \code{\link[base:options]{base::options()}}.} \item{trim}{Whether to avoid traversing the same nodes multiple times. If \code{TRUE} and \code{data} has a \code{trimmed} column, then that is used for printing repeated noded.} } \value{ Character vector, the lines of the tree drawing. } \description{ Draw a tree using box drawing characters. Unicode characters are used if available. (Set the \code{cli.unicode} option if auto-detection fails.) } \details{ A node might appear multiple times in the tree, or might not appear at all. } \examples{ data <- data.frame( stringsAsFactors = FALSE, package = c("processx", "backports", "assertthat", "Matrix", "magrittr", "rprojroot", "clisymbols", "prettyunits", "withr", "desc", "igraph", "R6", "crayon", "debugme", "digest", "irlba", "rcmdcheck", "callr", "pkgconfig", "lattice"), dependencies = I(list( c("assertthat", "crayon", "debugme", "R6"), character(0), character(0), "lattice", character(0), "backports", character(0), c("magrittr", "assertthat"), character(0), c("assertthat", "R6", "crayon", "rprojroot"), c("irlba", "magrittr", "Matrix", "pkgconfig"), character(0), character(0), "crayon", character(0), "Matrix", c("callr", "clisymbols", "crayon", "desc", "digest", "prettyunits", "R6", "rprojroot", "withr"), c("processx", "R6"), character(0), character(0) )) ) tree(data) tree(data, root = "rcmdcheck") # Colored nodes data$label <- paste(data$package, style_dim(paste0("(", c("2.0.0.1", "1.1.1", "0.2.0", "1.2-11", "1.5", "1.2", "1.2.0", "1.0.2", "2.0.0", "1.1.1.9000", "1.1.2", "2.2.2", "1.3.4", "1.0.2", "0.6.12", "2.2.1", "1.2.1.9002", "1.0.0.9000", "2.0.1", "0.20-35"), ")")) ) roots <- ! data$package \%in\% unlist(data$dependencies) data$label[roots] <- col_cyan(style_italic(data$label[roots])) tree(data) tree(data, root = "rcmdcheck") # Trimming pkgdeps <- list( "dplyr@0.8.3" = c("assertthat@0.2.1", "glue@1.3.1", "magrittr@1.5", "R6@2.4.0", "Rcpp@1.0.2", "rlang@0.4.0", "tibble@2.1.3", "tidyselect@0.2.5"), "assertthat@0.2.1" = character(), "glue@1.3.1" = character(), "magrittr@1.5" = character(), "pkgconfig@2.0.3" = character(), "R6@2.4.0" = character(), "Rcpp@1.0.2" = character(), "rlang@0.4.0" = character(), "tibble@2.1.3" = c("cli@1.1.0", "crayon@1.3.4", "fansi@0.4.0", "pillar@1.4.2", "pkgconfig@2.0.3", "rlang@0.4.0"), "cli@1.1.0" = c("assertthat@0.2.1", "crayon@1.3.4"), "crayon@1.3.4" = character(), "fansi@0.4.0" = character(), "pillar@1.4.2" = c("cli@1.1.0", "crayon@1.3.4", "fansi@0.4.0", "rlang@0.4.0", "utf8@1.1.4", "vctrs@0.2.0"), "utf8@1.1.4" = character(), "vctrs@0.2.0" = c("backports@1.1.5", "ellipsis@0.3.0", "digest@0.6.21", "glue@1.3.1", "rlang@0.4.0", "zeallot@0.1.0"), "backports@1.1.5" = character(), "ellipsis@0.3.0" = c("rlang@0.4.0"), "digest@0.6.21" = character(), "glue@1.3.1" = character(), "zeallot@0.1.0" = character(), "tidyselect@0.2.5" = c("glue@1.3.1", "purrr@1.3.1", "rlang@0.4.0", "Rcpp@1.0.2"), "purrr@0.3.3" = c("magrittr@1.5", "rlang@0.4.0") ) pkgs <- data.frame( stringsAsFactors = FALSE, name = names(pkgdeps), deps = I(unname(pkgdeps)) ) tree(pkgs) tree(pkgs, trim = TRUE) # Mark the trimmed nodes pkgs$label <- pkgs$name pkgs$trimmed <- paste(pkgs$name, " (trimmed)") tree(pkgs, trim = TRUE) } cli/man/cli_ul.Rd0000644000176200001440000000314613565765747013367 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_ul} \alias{cli_ul} \title{Unordered CLI list} \usage{ cli_ul( items = NULL, id = NULL, class = NULL, .close = TRUE, .auto_close = TRUE, .envir = parent.frame() ) } \arguments{ \item{items}{If not \code{NULL}, then a character vector. Each element of the vector will be one list item, and the list container will be closed by default (see the \code{.close} argument).} \item{id}{Id of the list container. Can be used for closing it with \code{\link[=cli_end]{cli_end()}} or in themes. If \code{NULL}, then an id is generated and retuned invisibly.} \item{class}{Class of the list container. Can be used in themes.} \item{.close}{Whether to close the list container if the \code{items} were specified. If \code{FALSE} then new items can be added to the list.} \item{.auto_close}{Whether to close the container, when the calling function finishes (or \code{.envir} is removed, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-close the container if \code{.auto_close} is \code{TRUE}.} } \value{ The id of the new container element, invisibly. } \description{ An unordered list is a container, see \link{containers}. } \examples{ ## Specifying the items at the beginning cli_ul(c("one", "two", "three")) ## Adding items one by one cli_ul() cli_li("one") cli_li("two") cli_li("three") cli_end() ## Complex item, added gradually. cli_ul() cli_li() cli_verbatim("Beginning of the {.emph first} item") cli_text("Still the first item") cli_end() cli_li("Second item") cli_end() } cli/man/ansi_hide_cursor.Rd0000644000176200001440000000145313565765747015437 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tty.R \name{ansi_hide_cursor} \alias{ansi_hide_cursor} \alias{ansi_show_cursor} \alias{ansi_with_hidden_cursor} \title{Hide/show cursor in a terminal} \usage{ ansi_hide_cursor(stream = stderr()) ansi_show_cursor(stream = stderr()) ansi_with_hidden_cursor(expr, stream = stderr()) } \arguments{ \item{stream}{The stream of the terminal to output the ANSI sequence to.} \item{expr}{R expression to evaluate.} } \description{ This only works in terminal emulators. In other environments, it does nothing. } \details{ \code{ansi_hide_cursor()} hides the cursor. \code{ansi_show_cursor()} shows the cursor. \code{ansi_with_hidden_cursor()} temporarily hides the cursor for evaluating an expression. } \concept{terminal capabiltiies} cli/man/cli_list_themes.Rd0000644000176200001440000000144413565765747015266 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/themes.R \name{cli_list_themes} \alias{cli_list_themes} \title{List the currently active themes} \usage{ cli_list_themes() } \value{ A list of data frames with the active themes. Each data frame row is a style that applies to selected CLI tree nodes. Each data frame has columns: \itemize{ \item \code{selector}: The original CSS-like selector string. See \link{themes}. \item \code{parsed}: The parsed selector, as used by cli for matching to nodes. \item \code{style}: The original style. \item \code{cnt}: The id of the container the style is currently applied to, or \code{NA} if the style is not used. } } \description{ If there is no active app, then it calls \code{\link[=start_app]{start_app()}}. } \seealso{ themes } cli/man/cli_blockquote.Rd0000644000176200001440000000153313573304254015072 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_blockquote} \alias{cli_blockquote} \title{CLI block quote} \usage{ cli_blockquote( quote, citation = NULL, id = NULL, class = NULL, .envir = parent.frame() ) } \arguments{ \item{quote}{Text of the quotation.} \item{citation}{Source of the quotation, typically a link or the name of a person.} \item{id}{Element id, a string. If \code{NULL}, then a new id is generated and returned.} \item{class}{Class name, sting. Can be used in themes.} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-close the container if \code{.auto_close} is \code{TRUE}.} } \description{ A section that is quoted from another source. It is typically indented. } \examples{ cli_blockquote(cli:::lorem_ipsum(), citation = "Nobody, ever") } cli/man/containers.Rd0000644000176200001440000000226313565765747014264 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cliapp-docs.R \name{containers} \alias{containers} \title{CLI containers} \description{ Container elements may contain other elements. Currently the following commands create container elements: \code{\link[=cli_div]{cli_div()}}, \code{\link[=cli_par]{cli_par()}}, the list elements: \code{\link[=cli_ul]{cli_ul()}}, \code{\link[=cli_ol]{cli_ol()}}, \code{\link[=cli_dl]{cli_dl()}}, and list items are containers as well: \code{\link[=cli_li]{cli_li()}}. } \details{ Container elements need to be closed with \code{\link[=cli_end]{cli_end()}}. For convenience, they are have an \code{.auto_close} argument, which allows automatically closing a container element, when the function that created it terminates (either regularly, or with an error). } \examples{ ## div with custom theme d <- cli_div(theme = list(h1 = list(color = "blue", "font-weight" = "bold"))) cli_h1("Custom title") cli_end(d) ## Close automatically div <- function() { cli_div(class = "tmp", theme = list(.tmp = list(color = "yellow"))) cli_text("This is yellow") } div() cli_text("This is not yellow any more") } cli/man/cli_rule.Rd0000644000176200001440000000313213565765747013711 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_rule} \alias{cli_rule} \title{CLI horizontal rule} \usage{ cli_rule( left = "", center = "", right = "", id = NULL, .envir = parent.frame() ) } \arguments{ \item{left}{Label to show on the left. It interferes with the \code{center} label, only at most one of them can be present.} \item{center}{Label to show at the center. It interferes with the \code{left} and \code{right} labels.} \item{right}{Label to show on the right. It interferes with the \code{center} label, only at most one of them can be present.} \item{id}{Element id, a string. If \code{NULL}, then a new id is generated and returned.} \item{.envir}{Environment to evaluate the glue expressions in.} } \description{ It can be used to separate parts of the output. The line style of the rule can be changed via the the \code{line-type} property. Possible values are: } \details{ \itemize{ \item \code{"single"}: (same as \code{1}), a single line, \item \code{"double"}: (same as \code{2}), a double line, \item \code{"bar1"}, \code{"bar2"}, \code{"bar3"}, etc., \code{"bar8"} uses varying height bars. } Colors and background colors can similarly changed via a theme, see examples below. } \examples{ cli_rule() cli_text(packageDescription("cli")$Description) cli_rule() # Theming d <- cli_div(theme = list(rule = list( color = "blue", "background-color" = "darkgrey", "line-type" = "double"))) cli_rule("Left", right = "Right") cli_end(d) # Interpolation cli_rule(left = "One plus one is {1+1}") cli_rule(left = "Package {.pkg mypackage}") } cli/man/ansi-styles.Rd0000644000176200001440000000677013571261512014353 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/crayon.R \name{ansi-styles} \alias{ansi-styles} \alias{bg_black} \alias{bg_blue} \alias{bg_cyan} \alias{bg_green} \alias{bg_magenta} \alias{bg_red} \alias{bg_white} \alias{bg_yellow} \alias{col_black} \alias{col_blue} \alias{col_cyan} \alias{col_green} \alias{col_magenta} \alias{col_red} \alias{col_white} \alias{col_yellow} \alias{col_grey} \alias{col_silver} \alias{style_dim} \alias{style_blurred} \alias{style_bold} \alias{style_hidden} \alias{style_inverse} \alias{style_italic} \alias{style_reset} \alias{style_strikethrough} \alias{style_underline} \title{ANSI colored text} \usage{ bg_black(...) bg_blue(...) bg_cyan(...) bg_green(...) bg_magenta(...) bg_red(...) bg_white(...) bg_yellow(...) col_black(...) col_blue(...) col_cyan(...) col_green(...) col_magenta(...) col_red(...) col_white(...) col_yellow(...) col_grey(...) col_silver(...) style_dim(...) style_blurred(...) style_bold(...) style_hidden(...) style_inverse(...) style_italic(...) style_reset(...) style_strikethrough(...) style_underline(...) } \arguments{ \item{...}{Character strings, they will be pasted together with \code{paste0()}, before applying the style function.} } \value{ An ANSI string (class \code{ansi_string}), that contains ANSI sequences, if the current platform supports them. You can simply use \code{cat()} to print them to the terminal. } \description{ cli has a number of functions to color and style text at the command line. These all use the crayon package under the hood, but provide a slightly simpler interface. } \details{ The \verb{col_*} functions change the (foreground) color to the text. These are the eight original ANSI colors. Note that in some terminals, they might actually look differently, as terminals have their own settings for how to show them. The \verb{bg_*} functions change the background color of the text. These are the eight original ANSI background colors. These, too, can vary in appearence, depending on terminal settings. The \verb{style_*} functions apply other styling to the text. The currently supported styling funtions are: \itemize{ \item \code{style_reset()} to remove any style, including color, \item \code{style_bold()} for boldface / strong text, although some terminals show a bright, high intensity text instead, \item \code{style_dim()} (or \code{style_blurred()} reduced intensity text. \item \code{style_italic()} (not widely supported). \item \code{style_underline()}, \item \code{style_inverse()}, \item \code{style_hidden()}, \item `style_strikethrough() (not widely supported). } The style functions take any number of character vectors as arguments, and they concatenate them using \code{paste0()} before adding the style. Styles can also be nested, and then inner style takes precedence, see examples below. } \examples{ col_blue("Hello ", "world!") cat(col_blue("Hello ", "world!")) cat("... to highlight the", col_red("search term"), "in a block of text\n") ## Style stack properly cat(col_green( "I am a green line ", col_blue(style_underline(style_bold("with a blue substring"))), " that becomes green again!" )) error <- combine_ansi_styles("red", "bold") warn <- combine_ansi_styles("magenta", "underline") note <- col_cyan cat(error("Error: subscript out of bounds!\n")) cat(warn("Warning: shorter argument was recycled.\n")) cat(note("Note: no such directory.\n")) } \seealso{ Other ANSI styling: \code{\link{combine_ansi_styles}()}, \code{\link{make_ansi_style}()} } \concept{ANSI styling} cli/man/is_ansi_tty.Rd0000644000176200001440000000145713565765747014450 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tty.R \name{is_ansi_tty} \alias{is_ansi_tty} \title{Detect if a stream support ANSI escape characters} \usage{ is_ansi_tty(stream = stderr()) } \arguments{ \item{stream}{The stream to check.} } \value{ \code{TRUE} or \code{FALSE}. } \description{ We check that all of the following hold: \itemize{ \item The stream is a terminal. \item The platform is Unix. \item R is not running inside R.app (the macOS GUI). \item R is not running inside RStudio. \item R is not running inside Emacs. \item The terminal is not "dumb". \item \code{stream} is either the standard output or the standard error stream. } } \examples{ is_ansi_tty() } \seealso{ Other terminal capabilities: \code{\link{is_dynamic_tty}()} } \concept{terminal capabilities} cli/man/figures/0000755000176200001440000000000013572314354013247 5ustar liggesuserscli/man/figures/README/0000755000176200001440000000000013573667570014220 5ustar liggesuserscli/man/figures/README/unnamed-chunk-12.svg0000644000176200001440000000243013573667464017717 0ustar liggesusersDownloaded123.14MBin1.3scli/man/figures/README/unnamed-chunk-2.svg0000644000176200001440000000227713573667464017647 0ustar liggesusersDownloaded3packages.cli/man/figures/README/unnamed-chunk-11.svg0000644000176200001440000000305513573667464017722 0ustar liggesusersThisisveryimportantBacktothepreviousthemecli/man/figures/README/unnamed-chunk-13.svg0000644000176200001440000000250213573667464017720 0ustar liggesusersFound3filesand1directory.cli/man/figures/README/unnamed-chunk-5.svg0000644000176200001440000000244513573667464017647 0ustar liggesusersFailedtoconnecttodatabase.cli/man/figures/README/unnamed-chunk-9.svg0000644000176200001440000000214213573667570017643 0ustar liggesusers──Heading3cli/man/figures/README/unnamed-chunk-10.svg0000644000176200001440000000302213573667464017713 0ustar liggesusers1.Item1Subitem1Subitem22.Item2cli/man/figures/README/unnamed-chunk-4.svg0000644000176200001440000000265513573667464017651 0ustar liggesusers!CannotreachGitHub,usinglocaldatabasecache.cli/man/figures/README/unnamed-chunk-6.svg0000644000176200001440000000220213573667464017637 0ustar liggesusersAgenericalertcli/man/figures/README/unnamed-chunk-7.svg0000644000176200001440000000270613573667570017647 0ustar liggesusers──Heading1───────────────────────────────────────────────────────────────────cli/man/figures/README/unnamed-chunk-3.svg0000644000176200001440000000250413573667570017637 0ustar liggesusersReopeneddatabase<example.com:port>.cli/man/figures/README/unnamed-chunk-8.svg0000644000176200001440000000234013573667570017642 0ustar liggesusers──Heading2──cli/man/cli_code.Rd0000644000176200001440000000274013571755424013644 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_code} \alias{cli_code} \title{A block of code} \usage{ cli_code( lines = NULL, ..., language = "R", .auto_close = TRUE, .envir = environment() ) } \arguments{ \item{lines}{Chracter vector, each line will be a line of code, and newline charactes also create new lines. Note that \emph{no} glue substitution is performed on the code.} \item{...}{More character vectors, they are appended to \code{lines}.} \item{language}{Programming language. This is also added as a class, in addition to \code{code}.} \item{.auto_close}{Passed to \code{cli_div()} when creating the container of the code. By default the code container is closed after emitting \code{lines} and \code{...} via \code{cli_verbatim()}. You can keep that container open with \code{.auto_close} and/or \code{.envir}, and then calling \code{cli_verbatim()} to add (more) code. Note that the code will be formatted and syntax highlighted separately for each \code{cli_verbatim()} call.} \item{.envir}{Passed to \code{cli_div()} when creating the container of the code.} } \value{ The id of the container that contains the code. } \description{ A helper function that creates a \code{div} with class \code{code} and then calls \code{cli_verbatim()} to output code lines. The builtin theme formats these containers specially. In particular, it adds syntax highlighting to valid R code. } \examples{ cli_code(format(cli::cli_blockquote)) } cli/man/cli_format.Rd0000644000176200001440000000243413571734652014222 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/format.R \name{cli_format} \alias{cli_format} \alias{cli_format.default} \alias{cli_format.character} \alias{cli_format.numeric} \title{Format a value for printing} \usage{ cli_format(x, style = list(), ...) \method{cli_format}{default}(x, style = list(), ...) \method{cli_format}{character}(x, style = list(), ...) \method{cli_format}{numeric}(x, style = list(), ...) } \arguments{ \item{x}{The object to format.} \item{style}{List of formatting options, see the individual methods for the style options they support.} \item{...}{Additional arguments for methods.} } \description{ This function can be used directly, or via the \verb{\{.val ...\}} inline style. \verb{\{.val \{expr\}\}} calls \code{cli_format()} automatically on the value of \code{expr}, before styling and collapsing it. } \details{ It is possible to define new S3 methods for \code{cli_format} and then these will be used automatically for \verb{\{.cal ...\}} expressions. } \examples{ things <- c(rep("this", 3), "that") cli_format(things) cli_text("{.val {things}}") nums <- 1:5 / 7 cli_format(nums, style = list(digits = 2)) cli_text("{.val {nums}}") divid <- cli_div(theme = list(.val = list(digits = 3))) cli_text("{.val {nums}}") cli_end(divid) } cli/man/cli_status.Rd0000644000176200001440000000530113571552620014242 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/status-bar.R \name{cli_status} \alias{cli_status} \title{Update the status bar} \usage{ cli_status( msg, msg_done = paste(msg, "... done"), msg_failed = paste(msg, "... failed"), .keep = FALSE, .auto_close = TRUE, .envir = parent.frame(), .auto_result = c("clear", "done", "failed") ) } \arguments{ \item{msg}{The text to show, a character vector. It will be collapsed into a single string, and the first line is kept and cut to \code{\link[=console_width]{console_width()}}. The message is often associated with the start of a calculation.} \item{msg_done}{The message to use when the message is cleared, when the calculation finishes successfully. If \code{.auto_close} is \code{TRUE} and \code{.auto_result} is \code{"done"}, then this is printed automatically then the calling function (or \code{.envir}) finishes.} \item{msg_failed}{The message to use when the message is cleared, when the calculation finishes unsuccessfully. If \code{.auto_close} is \code{TRUE} and \code{.auto_result} is \code{"failed"}, then this is printed automatically then the calling function (or \code{.envir}) finishes.} \item{.keep}{What to do when this status bar is cleared. If \code{TRUE} then the content of this status bar is kept, as regular cli output (the screen is scrolled up if needed). If \code{FALSE}, then this status bar is deleted.} \item{.auto_close}{Whether to clear the status bar when the calling function finishes (or ‘.envir’ is removed from the stack, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-clear the status bar if \code{.auto_close} is `TRUE.} \item{.auto_result}{What to do when auto-closing the status bar.} } \value{ The id of the new status bar container element, invisibly. } \description{ The status bar is the last line of the terminal. cli apps can use this to show status information, progress bars, etc. The status bar is kept intact by all semantic cli output. } \details{ Use \code{\link[=cli_status_clear]{cli_status_clear()}} to clear the status bar. Often status messages are associated with processes. E.g. the app starts downloading a large file, so it sets the status bar accordingly. Once the download is done (or failed), the app typically updates the status bar again. cli automates much of this, via the \code{msg_done}, \code{msg_failed}, and \code{.auto_result} arguments. See examples below. } \seealso{ \link{cli_process_start} for a higher level interface to the startus bar, that adds automatic styling. Other status bar: \code{\link{cli_process_start}()}, \code{\link{cli_status_clear}()}, \code{\link{cli_status_update}()} } \concept{status bar} cli/man/cli_status_clear.Rd0000644000176200001440000000251013571552620015407 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/status-bar.R \name{cli_status_clear} \alias{cli_status_clear} \title{Clear the status bar} \usage{ cli_status_clear( id = NULL, result = c("clear", "done", "failed"), msg_done = NULL, msg_failed = NULL, .envir = parent.frame() ) } \arguments{ \item{id}{Id of the status bar container to clear. If \code{id} is not the id of the current status bar (because it was overwritten by another status bar container), then the status bar is not cleared. If \code{NULL} (the default) then the status bar is always cleared.} \item{result}{Whether to show a message for success or failure or just clear the status bar.} \item{msg_done}{If not \code{NULL}, then the message to use for successful process termination. This overrides the message given when the status bar was created.} \item{msg_failed}{If not \code{NULL}, then the message to use for failed process termination. This overrides the message give when the status bar was created.} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-clear the status bar if \code{.auto_close} is `TRUE.} } \description{ Clear the status bar } \seealso{ Other status bar: \code{\link{cli_process_start}()}, \code{\link{cli_status_update}()}, \code{\link{cli_status}()} } \concept{status bar} cli/man/boxx.Rd0000644000176200001440000000751013565765747013077 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/box-styles.R, R/boxes.R \name{list_border_styles} \alias{list_border_styles} \alias{boxx} \title{Draw a banner-like box in the console} \usage{ list_border_styles() boxx( label, border_style = "single", padding = 1, margin = 0, float = c("left", "center", "right"), col = NULL, background_col = NULL, border_col = col, align = c("left", "center", "right"), width = console_width() ) } \arguments{ \item{label}{Label to show, a character vector. Each element will be in a new line. You can color it using the \verb{col_*}, \verb{bg_*} and \verb{style_*} functions, see \link{ansi-styles} and the examples below.} \item{border_style}{String that specifies the border style. \code{list_border_styles} lists all current styles.} \item{padding}{Padding within the box. Either an integer vector of four numbers (bottom, left, top, right), or a single number \code{x}, which is interpreted as \code{c(x, 3*x, x, 3*x)}.} \item{margin}{Margin around the box. Either an integer vector of four numbers (bottom, left, top, right), or a single number \code{x}, which is interpreted as \code{c(x, 3*x, x, 3*x)}.} \item{float}{Whether to display the box on the \code{"left"}, \code{"center"}, or the \code{"right"} of the screen.} \item{col}{Color of text, and default border color. Either a style function (see \link{ansi-styles}) or a color name that is passed to \code{\link[=make_ansi_style]{make_ansi_style()}}.} \item{background_col}{Background color of the inside of the box. Either a style function (see \link{ansi-styles}), or a color name which will be used in \code{\link[=make_ansi_style]{make_ansi_style()}} to create a \emph{background} style (i.e. \code{bg = TRUE} is used).} \item{border_col}{Color of the border. Either a style function (see \link{ansi-styles}) or a color name that is passed to \code{\link[=make_ansi_style]{make_ansi_style()}}.} \item{align}{Alignment of the label within the box: \code{"left"}, \code{"center"}, or \code{"right"}.} \item{width}{Width of the screen, defaults to \code{getOption("width")}.} } \description{ Draw a banner-like box in the console } \section{About fonts and terminal settings}{ The boxes might or might not look great in your terminal, depending on the box style you use and the font the terminal uses. We found that the Menlo font looks nice in most terminals an also in Emacs. RStudio currently has a line height greater than one for console output, which makes the boxes ugly. } \examples{ ## Simple box boxx("Hello there!") ## All border styles list_border_styles() ## Change border style boxx("Hello there!", border_style = "double") ## Multiple lines boxx(c("Hello", "there!"), padding = 1) ## Padding boxx("Hello there!", padding = 1) boxx("Hello there!", padding = c(1, 5, 1, 5)) ## Margin boxx("Hello there!", margin = 1) boxx("Hello there!", margin = c(1, 5, 1, 5)) boxx("Hello there!", padding = 1, margin = c(1, 5, 1, 5)) ## Floating boxx("Hello there!", padding = 1, float = "center") boxx("Hello there!", padding = 1, float = "right") ## Text color boxx(col_cyan("Hello there!"), padding = 1, float = "center") ## Backgorund color boxx("Hello there!", padding = 1, background_col = "brown") boxx("Hello there!", padding = 1, background_col = bg_red) ## Border color boxx("Hello there!", padding = 1, border_col = "green") boxx("Hello there!", padding = 1, border_col = col_red) ## Label alignment boxx(c("Hi", "there", "you!"), padding = 1, align = "left") boxx(c("Hi", "there", "you!"), padding = 1, align = "center") boxx(c("Hi", "there", "you!"), padding = 1, align = "right") ## A very customized box star <- symbol$star label <- c(paste(star, "Hello", star), " there!") boxx( col_white(label), border_style="round", padding = 1, float = "center", border_col = "tomato3", background_col="darkolivegreen" ) } cli/man/themes.Rd0000644000176200001440000000703013565765747013401 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cliapp-docs.R \name{themes} \alias{themes} \title{CLI themes} \description{ CLI elements can be styled via a CSS-like language of selectors and properties. Only a small subset of CSS3 is supported, and a lot visual properties cannot be implemented on a terminal, so these will be ignored as well. } \section{Adding themes}{ The style of an element is calculated from themes from four sources. These form a stack, and the themes on the top of the stack take precedence, over themes in the bottom. \enumerate{ \item The cli package has a builtin theme. This is always active. See \code{\link[=builtin_theme]{builtin_theme()}}. \item When an app object is created via \code{\link[=start_app]{start_app()}}, the caller can specify a theme, that is added to theme stack. If no theme is specified for \code{\link[=start_app]{start_app()}}, the content of the \code{cli.theme} option is used. Removed when the corresponding app stops. \item The user may speficy a theme in the \code{cli.user_theme} option. This is added to the stack \emph{after} the app's theme (step 2.), so it can override its settings. Removed when the app that added it stops. \item Themes specified explicitly in \code{\link[=cli_div]{cli_div()}} elements. These are removed from the theme stack, when the corresponding \code{\link[=cli_div]{cli_div()}} elements are closed. } } \section{Writing themes}{ A theme is a named list of lists. The name of each entry is a CSS selector. Only a subset of CSS is supported: \itemize{ \item Type selectors, e.g. \code{input} selects all \verb{} elements. \item Class selectors, e.g. \code{.index} selects any element that has a class of "index". \item ID selector. \verb{#toc} will match the element that has the ID "toc". \item The descendant combinator, i.e. the space, that selects nodes that are descendants of the first element. E.g. \verb{div span} will match all \verb{} elements that are inside a \verb{
} element. } The content of a theme list entry is another named list, where the names are CSS properties, e.g. \code{color}, or \code{font-weight} or \code{margin-left}, and the list entries themselves define the values of the properties. See \code{\link[=builtin_theme]{builtin_theme()}} and \code{\link[=simple_theme]{simple_theme()}} for examples. } \section{Formatter callbacks}{ For flexibility, themes may also define formatter functions, with property name \code{fmt}. These will be called once the other styles are applied to an element. They are only called on elements that produce output, i.e. \emph{not} on container elements. } \section{Supported properties}{ Right now only a limited set of properties are supported. These include left, right, top and bottom margins, background and foreground colors, bold and italic fonts, underlined text. The \code{before} and \code{after} properties are supported to insert text before and after the content of the element. More properties might be adder later. Please see the example themes and the source code for now for the details. } \section{Examples}{ Color of headings, that are only active in paragraphs with an 'output' class:\preformatted{list( "par.output h1" = list("background-color" = "red", color = "#e0e0e0"), "par.output h2" = list("background-color" = "orange", color = "#e0e0e0"), "par.output h3" = list("background-color" = "blue", color = "#e0e0e0") ) } Create a custom alert type:\preformatted{list( ".alert-start" = list(before = symbol$play), ".alert-stop" = list(before = symbol$stop) ) } } cli/man/combine_ansi_styles.Rd0000644000176200001440000000246213571261512016123 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/crayon.R \name{combine_ansi_styles} \alias{combine_ansi_styles} \title{Combine two or more ANSI styles} \usage{ combine_ansi_styles(...) } \arguments{ \item{...}{The styles to combine. For character strings, the \code{\link[=make_ansi_style]{make_ansi_style()}} function is used to create a style first. They will be applied from right to left.} } \value{ The combined style function. } \description{ Combine two or more styles or style functions into a new style function that can be called on strings to style them. } \details{ It does not usually make sense to combine two foreground colors (or two background colors), because only the first one applied will be used. It does make sense to combine different kind of styles, e.g. background color, foreground color, bold font. } \examples{ ## Use style names alert <- combine_ansi_styles("bold", "red4") cat(alert("Warning!"), "\n") ## Or style functions alert <- combine_ansi_styles(style_bold, col_red, bg_cyan) cat(alert("Warning!"), "\n") ## Combine a composite style alert <- combine_ansi_styles( "bold", combine_ansi_styles("red", bg_cyan)) cat(alert("Warning!"), "\n") } \seealso{ Other ANSI styling: \code{\link{ansi-styles}}, \code{\link{make_ansi_style}()} } \concept{ANSI styling} cli/man/cli_verbatim.Rd0000644000176200001440000000074213571261512014531 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_verbatim} \alias{cli_verbatim} \title{CLI verbatim text} \usage{ cli_verbatim(..., .envir = parent.frame()) } \arguments{ \item{...}{The text to show, in character vectors. Each element is printed on a new line.} \item{.envir}{Environment to evaluate the glue expressions in.} } \description{ It is not wrapped, but printed as is. } \examples{ cli_verbatim("This has\nthree", "lines") } cli/man/cli_end.Rd0000644000176200001440000000070213565765747013510 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_end} \alias{cli_end} \title{Close a CLI container} \usage{ cli_end(id = NULL) } \arguments{ \item{id}{Id of the container to close. If missing, the current container is closed, if any.} } \description{ Close a CLI container } \examples{ ## If id is omitted cli_par() cli_text("First paragraph") cli_end() cli_par() cli_text("Second paragraph") cli_end() } cli/man/cli_par.Rd0000644000176200001440000000157113565765747013531 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_par} \alias{cli_par} \title{CLI paragraph} \usage{ cli_par(id = NULL, class = NULL, .auto_close = TRUE, .envir = parent.frame()) } \arguments{ \item{id}{Element id, a string. If \code{NULL}, then a new id is generated and returned.} \item{class}{Class name, sting. Can be used in themes.} \item{.auto_close}{Whether to close the container, when the calling function finishes (or \code{.envir} is removed, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-close the container if \code{.auto_close} is \code{TRUE}.} } \value{ The id of the new container element, invisibly. } \description{ See \link{containers}. } \examples{ id <- cli_par() cli_text("First paragraph") cli_end(id) id <- cli_par() cli_text("Second paragraph") cli_end(id) } cli/man/cli_status_update.Rd0000644000176200001440000000170113571552620015604 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/status-bar.R \name{cli_status_update} \alias{cli_status_update} \title{Update the status bar} \usage{ cli_status_update( id = NULL, msg = NULL, msg_done = NULL, msg_failed = NULL, .envir = parent.frame() ) } \arguments{ \item{id}{Id of the status bar to update. Defaults to the current status bar container.} \item{msg}{Text to update the status bar with. \code{NULL} if you don't want to change it.} \item{msg_done}{Updated "done" message. \code{NULL} if you don't want to change it.} \item{msg_failed}{Updated "failed" message. \code{NULL} if you don't want to change it.} \item{.envir}{Environment to evaluate the glue expressions in.} } \value{ Id of the status bar container. } \description{ Update the status bar } \seealso{ Other status bar: \code{\link{cli_process_start}()}, \code{\link{cli_status_clear}()}, \code{\link{cli_status}()} } \concept{status bar} cli/man/cli_h1.Rd0000644000176200001440000000141413565765747013253 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_h1} \alias{cli_h1} \alias{cli_h2} \alias{cli_h3} \title{CLI headings} \usage{ cli_h1(text, id = NULL, class = NULL, .envir = parent.frame()) cli_h2(text, id = NULL, class = NULL, .envir = parent.frame()) cli_h3(text, id = NULL, class = NULL, .envir = parent.frame()) } \arguments{ \item{text}{Text of the heading. It can contain inline markup.} \item{id}{Id of the heading element, string. It can be used in themes.} \item{class}{Class of the heading element, string. It can be used in themes.} \item{.envir}{Environment to evaluate the glue expressions in.} } \description{ CLI headings } \examples{ cli_h1("Main title") cli_h2("Subtitle") cli_text("And some regular text....") } cli/man/start_app.Rd0000644000176200001440000000233713565765747014116 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/app.R \name{start_app} \alias{start_app} \alias{stop_app} \alias{default_app} \title{Start, stop, query the default cli application} \usage{ start_app( theme = getOption("cli.theme"), output = c("auto", "message", "stdout", "stderr"), .auto_close = TRUE, .envir = parent.frame() ) stop_app(app = NULL) default_app() } \arguments{ \item{theme}{Theme to use.} \item{output}{How to print the output.} \item{.auto_close}{Whether to stop the app, when the calling frame is destroyed.} \item{.envir}{The environment to use, instead of the calling frame, to trigger the stop of the app.} \item{app}{App to stop. If \code{NULL}, the current default app is stopped. Otherwise we find the supplied app in the app stack, and remote it, together with all the apps above it.} } \value{ \code{start_app} returns the new app, \code{default_app} returns the default app. \code{stop_app} does not return anything. } \description{ \code{start_app} creates an app, and places it on the top of the app stack. } \details{ \code{stop_app} removes the top app, or multiple apps from the app stack. \code{default_app} returns the default app, the one on the top of the stack. } cli/man/demo_spinners.Rd0000644000176200001440000000106113565765747014757 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/spinner.R \name{demo_spinners} \alias{demo_spinners} \title{Show a demo of some (by default all) spinners} \usage{ demo_spinners(which = NULL) } \arguments{ \item{which}{Character vector, which spinners to demo.} } \description{ Each spinner is shown for about 2-3 seconds. } \examples{ \dontrun{ demo_spinners(sample(list_spinners(), 10)) } } \seealso{ Other spinners: \code{\link{get_spinner}()}, \code{\link{list_spinners}()}, \code{\link{make_spinner}()} } \concept{spinners} cli/man/make_ansi_style.Rd0000644000176200001440000000405213565765747015264 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/crayon.R \name{make_ansi_style} \alias{make_ansi_style} \title{Create a new ANSI style} \usage{ make_ansi_style(..., bg = FALSE, grey = FALSE, colors = crayon::num_colors()) } \arguments{ \item{...}{The style to create. See details and examples below.} \item{bg}{Whether the color applies to the background.} \item{grey}{Whether to specifically create a grey color. This flag is included, because ANSI 256 has a finer color scale for greys, then the usual 0:5 scale for red, green and blue components. It is only used for RGB color specifications (either numerically or via a hexa string), and it is ignored on eigth color ANSI terminals.} \item{colors}{Number of colors, detected automatically by default.} } \value{ A function that can be used to color (style) strings. } \description{ Create a function that can be used to add ANSI styles to text. All arguments are passed to \code{\link[crayon:make_style]{crayon::make_style()}}, but see the Details below. } \details{ The styles (elements of \code{...}) can be any of the following: \itemize{ \item An R color name, see \code{\link[grDevices:colors]{grDevices::colors()}}. \item A 6- or 8-digit hexa color string, e.g. \verb{#ff0000} means red. Transparency (alpha channel) values are ignored. \item A one-column matrix with three rows for the red, green and blue channels, as returned by \code{\link[grDevices:col2rgb]{grDevices::col2rgb()}}. } \code{make_ansistyle()} detects the number of colors to use automatically (this can be overridden using the \code{colors} argument). If the number of colors is less than 256 (detected or given), then it falls back to the color in the ANSI eight color mode that is closest to the specified (RGB or R) color. } \examples{ make_ansi_style("orange") make_ansi_style("#123456") make_ansi_style("orange", bg = TRUE) orange <- make_ansi_style("orange") orange("foobar") cat(orange("foobar")) } \seealso{ Other ANSI styling: \code{\link{ansi-styles}}, \code{\link{combine_ansi_styles}()} } \concept{ANSI styling} cli/man/cat_line.Rd0000644000176200001440000000253313565765747013675 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cat.R \name{cat_line} \alias{cat_line} \alias{cat_bullet} \alias{cat_boxx} \alias{cat_rule} \alias{cat_print} \title{\code{cat()} helpers} \usage{ cat_line(..., col = NULL, background_col = NULL, file = stdout()) cat_bullet( ..., col = NULL, background_col = NULL, bullet = "bullet", bullet_col = NULL, file = stdout() ) cat_boxx(..., file = stdout()) cat_rule(..., file = stdout()) cat_print(x, file = "") } \arguments{ \item{...}{For \code{cat_line()} and \code{cat_bullet()}, paste'd together with \code{collapse = "\\n"}. For \code{cat_rule()} and \code{cat_boxx()} passed on to \code{\link[=rule]{rule()}} and \code{\link[=boxx]{boxx()}} respectively.} \item{col, background_col, bullet_col}{Colours for text, background, and bullets respectively.} \item{file}{Output destination. Defaults to standard output.} \item{bullet}{Name of bullet character. Indexes into \link{symbol}} \item{x}{An object to print.} } \description{ These helpers provide useful wrappers around \code{\link[=cat]{cat()}}: most importantly they all set \code{sep = ""}, and \code{cat_line()} automatically adds a newline. } \examples{ cat_line("This is ", "a ", "line of text.", col = "red") cat_bullet(letters[1:5]) cat_bullet(letters[1:5], bullet = "tick", bullet_col = "green") cat_rule() } cli/man/chunks/0000755000176200001440000000000013605174462013077 5ustar liggesuserscli/man/chunks/pluralization.Rmd0000644000176200001440000001270413572272460016444 0ustar liggesusers ```{r, include = FALSE} knitr::opts_chunk$set( R.options = list( crayon.enabled = FALSE, cli.unicode = FALSE ), results = "hold", comment = "#>", cache = TRUE ) ``` # Introduction cli has tools to create messages that are printed correctly in singular and plural forms. This usually requires minimal extra work, and increases the quality of the messages greatly. In this document we first show some pluralization examples that you can use as guidelines. Hopefully these are intuitive enough, so that they can be used without knowing the exact cli pluralization rules. # Examples ## Pluralization markup In the simplest case the message contains a single `{}` glue substitution, which specifies the quantity that is used to select between the singular and plural forms. Pluralization uses markup that is similar to glue, but uses the `{?` and `}` delimiters: ```{r} library(cli) nfile <- 0; cli_text("Found {nfile} file{?s}.") nfile <- 1; cli_text("Found {nfile} file{?s}.") nfile <- 2; cli_text("Found {nfile} file{?s}.") ``` Here the value of `nfile` is used to decide whether the singular or plural form of `file` is used. This is the most common case for English messages. ## Irregular plurals If the plural form is more difficult than a simple `s` suffix, then the singular and plural forms can be given, separated with a forward slash: ```{r} ndir <- 1; cli_text("Found {ndir} director{?y/ies}.") ndir <- 5; cli_text("Found {ndir} director{?y/ies}.") ``` ## Use "no" instead of zero For readability, it is better to use the `no()` helper function to include a count in a message. `no()` prints the word "no" if the count is zero, and prints the numeric count otherwise: ```{r} nfile <- 0; cli_text("Found {no(nfile)} file{?s}.") nfile <- 1; cli_text("Found {no(nfile)} file{?s}.") nfile <- 2; cli_text("Found {no(nfile)} file{?s}.") ``` ## Use the length of character vectors With the auto-collapsing feature of cli it is easy to include a list of objects in a message. When cli interprets a character vector as a pluralization quantity, it takes the length of the vector: ```{r} pkgs <- "pkg1" cli_text("Will remove the {.pkg {pkgs}} package{?s}.") pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove the {.pkg {pkgs}} package{?s}.") ``` Note that the length is only used for non-numeric vectors (when `is.numeric(x)` return `FALSE`). If you want to use the length of a numeric vector, convert it to character via `as.character()`. You can combine collapsed vectors with "no", like this: ```{r} pkgs <- character() cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.") pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.") ``` When the pluralization markup contains three alternatives, like above, the first one is used for zero, the second for one, and the third one for larger quantities. ## Choosing the right quantity When the text contains multiple glue `{}` substitutions, the one right before the pluralization markup is used. For example: ```{r} nfiles <- 3; ndirs <- 1 cli_text("Found {nfiles} file{?s} and {ndirs} director{?y/ies}") ``` This is sometimes not the the correct one. You can explicitly specify the correct quantity using the `qty()` function. This sets that quantity without printing anything: ```{r} nupd <- 3; ntotal <- 10 cli_text("{nupd}/{ntotal} {qty(nupd)} file{?s} {?needs/need} updates") ``` Note that if the message only contains a single `{}` substitution, then this may appear before or after the pluralization markup. If the message contains multiple `{}` substitutions _after_ pluralization markup, an error is thrown. Similarly, if the message contains no `{}` substituions at all, but has pluralization markup, and error is thrown. # Rules The exact rules of cli's pluralization. There are two sets of rules. The first set specifies how a quantity is associated with a `{?}` pluralization markup. The second set describes how the `{?}` is parsed and interpreted. ## Quantities 1. `{}` substitutions define quantities. If the value of a `{}` substitution is numeric (i.e. `is.numeric(x)` holds), then it has to have length one to define a quantity. This is only enforced if the `{}` substitution is used for pluralization. The quantity is defined as the value of `{}` then, rounded with `as.integer()`. If the value of `{}` is not numeric, then its quantity is defined as its length. 1. If a message has `{?}` markup but no `{}` substitution, an error is thrown. 1. If a message has exactly one `{}` substitution, its value is used as the pluralization quantity for all `{?}` markup in the message. 1. If a message has multiple `{}` substitutions, then for each `{?}` markup cli uses the quantity of the `{}` substitution that precedes it. 1. If a message has multiple `{}` substitutions and has pluralization markup with a preceding `{}` substitution, and error is thrown. ## Pluralization markup 1. Pluralization markup start with `{?` and ends with `}`. It may not contain `{` and `}` characters, so it may not contains `{}` substitutions either. 1. Alternative words or suffixes are separated by `/`. 1. If there is a single alternative, then _nothing_ is used if `quantity == 1` and this single alternative is used if `quantity != 1`. 1. If there are two alternatives, the first one is used for `quantity == 1`, the second one for `quantity != 1` (include 0). 1. If there are three alternatives, the first one is used for `quantity == 0`, the second for `quantity == 1`, and the third otherwise. cli/man/symbol.Rd0000644000176200001440000000124113571261512013371 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/symbol.R \name{symbol} \alias{symbol} \alias{list_symbols} \title{Various handy symbols to use in a command line UI} \format{A named list, see \code{names(symbol)} for all sign names.} \usage{ symbol list_symbols() } \description{ Various handy symbols to use in a command line UI } \details{ On Windows they have a fallback to less fancy symbols. \code{list_symbols()} prints a table with all symbols to the screen. } \examples{ cat(symbol$tick, " SUCCESS\n", symbol$cross, " FAILURE\n", sep = "") ## All symbols cat(paste(format(names(symbol), width = 20), unlist(symbol)), sep = "\n") } cli/man/cli_div.Rd0000644000176200001440000000250513573667373013522 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_div} \alias{cli_div} \title{Generic CLI container} \usage{ cli_div( id = NULL, class = NULL, theme = NULL, .auto_close = TRUE, .envir = parent.frame() ) } \arguments{ \item{id}{Element id, a string. If \code{NULL}, then a new id is generated and returned.} \item{class}{Class name, sting. Can be used in themes.} \item{theme}{A custom theme for the container. See \link{themes}.} \item{.auto_close}{Whether to close the container, when the calling function finishes (or \code{.envir} is removed, if specified).} \item{.envir}{Environment to evaluate the glue expressions in. It is also used to auto-close the container if \code{.auto_close} is \code{TRUE}.} } \value{ The id of the new container element, invisibly. } \description{ See \link{containers}. A \code{cli_div} container is special, because it may add new themes, that are valid within the container. } \examples{ ## div with custom theme d <- cli_div(theme = list(h1 = list(color = "blue", "font-weight" = "bold"))) cli_h1("Custom title") cli_end(d) ## Close automatically div <- function() { cli_div(class = "tmp", theme = list(.tmp = list(color = "yellow"))) cli_text("This is yellow") } div() cli_text("This is not yellow any more") } cli/man/cli_alert.Rd0000644000176200001440000000251313565765747014053 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cli.R \name{cli_alert} \alias{cli_alert} \alias{cli_alert_success} \alias{cli_alert_danger} \alias{cli_alert_warning} \alias{cli_alert_info} \title{CLI alerts} \usage{ cli_alert(text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame()) cli_alert_success( text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame() ) cli_alert_danger( text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame() ) cli_alert_warning( text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame() ) cli_alert_info( text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame() ) } \arguments{ \item{text}{Text of the alert.} \item{id}{Id of the alert element. Can be used in themes.} \item{class}{Class of the alert element. Can be used in themes.} \item{wrap}{Whether to auto-wrap the text of the alert.} \item{.envir}{Environment to evaluate the glue expressions in.} } \description{ Alerts are typically short status messages. } \examples{ cli_alert("Cannot lock package library.") cli_alert_success("Package {.pkg cli} installed successfully.") cli_alert_danger("Could not download {.pkg cli}.") cli_alert_warning("Internet seems to be unreacheable.") cli_alert_info("Downloaded 1.45MiB of data") } cli/man/is_dynamic_tty.Rd0000644000176200001440000000334513572271360015115 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/tty.R \name{is_dynamic_tty} \alias{is_dynamic_tty} \title{Detect whether a stream supports \verb{\\\\r} (Carriage return)} \usage{ is_dynamic_tty(stream = cli_output_connection()) } \arguments{ \item{stream}{The stream to inspect, an R connection object. Note that it defaults to the standard \emph{error} stream, since informative messages are typically printed there.} } \description{ In a terminal, \verb{\\\\r} moves the cursor to the first position of the same line. It is also supported by most R IDEs. \verb{\\\\r} is typically used to achive a more dynamic, less cluttered user interface, e.g. to create progress bars. } \details{ If the output is directed to a file, then \verb{\\\\r} characters are typically unwanted. This function detects if \verb{\\\\r} can be used for the given stream or not. The detection mechanism is as follows: \enumerate{ \item If the \code{cli.dynamic} option is set to \code{TRUE}, \code{TRUE} is returned. \item If the \code{cli.dynamic} option is set to anything else, \code{FALSE} is returned. \item If the \code{R_CLI_DYNAMIC} environment variable is not empty and set to the string \code{"true"}, \code{"TRUE"} or \code{"True"}, \code{TRUE} is returned. \item If \code{R_CLI_DYNAMIC} is not empty and set to anything else, \code{FALSE} is returned. \item If the stream is a terminal, then \code{TRUE} is returned. \item If the stream is the standard output or error within RStudio, the macOS R app, or RKWard IDE, \code{TRUE} is returned. \item Otherwise \code{FALSE} is returned. } } \examples{ is_dynamic_tty() is_dynamic_tty(stdout()) } \seealso{ Other terminal capabilities: \code{\link{is_ansi_tty}()} } \concept{terminal capabilities} cli/DESCRIPTION0000644000176200001440000000252113605457531012540 0ustar liggesusersPackage: cli Title: Helpers for Developing Command Line Interfaces Version: 2.0.1 Authors@R: c( person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", c("aut", "cre")), person("Hadley", "Wickham", role = c("ctb")), person("Kirill", "Müller", role = c("ctb")) ) Description: A suite of tools to build attractive command line interfaces ('CLIs'), from semantic elements: headings, lists, alerts, paragraphs, etc. Supports custom themes via a 'CSS'-like language. It also contains a number of lower level 'CLI' elements: rules, boxes, trees, and 'Unicode' symbols with 'ASCII' alternatives. It integrates with the 'crayon' package to support 'ANSI' terminal colors. License: MIT + file LICENSE LazyData: true URL: https://github.com/r-lib/cli#readme BugReports: https://github.com/r-lib/cli/issues RoxygenNote: 7.0.2 Depends: R (>= 2.10) Imports: assertthat, crayon (>= 1.3.4), glue, methods, utils, fansi Suggests: callr, covr, htmlwidgets, knitr, mockery, rmarkdown, rstudioapi, prettycode (>= 1.1.0), testthat, withr Encoding: UTF-8 VignetteBuilder: cli NeedsCompilation: no Packaged: 2020-01-07 21:33:38 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Hadley Wickham [ctb], Kirill Müller [ctb] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2020-01-08 23:01:45 UTC cli/build/0000755000176200001440000000000013605174461012127 5ustar liggesuserscli/build/vignette.rds0000644000176200001440000000031613605174461014466 0ustar liggesusersb```b`fcd`b2 1# ',)-JɬJ, MAW㩀M %9h*J, Q,LHYsS`wI-HK î?"5lP5,n90{C2K7(1 棸(\^P4@btr$$ ncli/tests/0000755000176200001440000000000013565765747012214 5ustar liggesuserscli/tests/testthat/0000755000176200001440000000000013605457531014034 5ustar liggesuserscli/tests/testthat/helper-app.R0000644000176200001440000000155313573303543016215 0ustar liggesusers test_style <- function() { list( ".testcli h1" = list( "font-weight" = "bold", "font-style" = "italic", "margin-top" = 1, "margin-bottom" = 1), ".testcli h2" = list( "font-weight" = "bold", "margin-top" = 1, "margin-bottom" = 1), ".testcli h3" = list( "text-decoration" = "underline", "margin-top" = 1) ) } capture_messages <- function(expr) { msgs <- character() i <- 0 suppressMessages(withCallingHandlers( expr, message = function(e) msgs[[i <<- i + 1]] <<- conditionMessage(e))) paste0(msgs, collapse = "") } capt <- function(expr, print_it = TRUE) { pr <- if (print_it) print else identity paste(capture.output(pr(expr)), collapse = "\n") } capt0 <- function(expr, strip_style = FALSE) { out <- capture_messages(expr) if (strip_style) crayon::strip_style(out) else out } cli/tests/testthat/test-containers.R0000644000176200001440000000540513565765747017325 0ustar liggesusers context("cli containers") setup(start_app()) teardown(stop_app()) test_that("auto closing", { cli_div(theme = list(".xx .emph" = list(before = "itsu:"))) id <- "" out <- "" f <- function() { capt0(id <<- cli_par(class = "xx")) out <<- capt0(cli_text("foo {.emph blah} bar")) } capt0(f()) expect_match(out, "itsu:", fixed = TRUE) out <- capt0(cli_text("foo {.emph blah} bar")) expect_false(grepl("itsu:", out, fixed = TRUE)) }) test_that("opt out of auto closing", { cli_div(theme = list(".xx .emph" = list(before = "itsu:"))) id <- NULL f <- function() { capt0(id <<- cli_par(class = "xx", .auto_close = FALSE)) out <- capt0(cli_text("foo {.emph blah} bar")) expect_match(out, "itsu:", fixed = TRUE) } capt0(f()) ## Still active out <- capt0(cli_text("foo {.emph blah} bar")) expect_match(out, "itsu:", fixed = TRUE) ## close explicitly expect_false(is.null(id)) capt0(cli_end(id)) out <- capt0(cli_text("foo {.emph blah} bar")) expect_false(grepl("itsu:", out, fixed = TRUE)) }) test_that("auto closing with special env", { cli_div(theme = list(".xx .emph" = list(before = "itsu:"))) id <- NULL f <- function() { g() ## Still active out <- capt0(cli_text("foo {.emph blah} bar")) expect_match(out, "itsu:", fixed = TRUE) } g <- function() { capt0(id <<- cli_par(class = "xx", .auto_close = TRUE, .envir = parent.frame())) out <- capt0(cli_text("foo {.emph blah} bar")) expect_match(out, "itsu:", fixed = TRUE) } capt0(f()) ## Not active any more out <- capt0(cli_text("foo {.emph blah} bar")) expect_false(grepl("itsu:", out, fixed = TRUE)) }) test_that("div with special style", { f <- function() { cli_div(theme = list(".xx .emph" = list(before = "itsu:"))) capt0(cli_par(class = "xx")) out <- capt0(cli_text("foo {.emph blah} bar")) expect_match(out, "itsu:", fixed = TRUE) } capt0(f()) ## Not active any more out <- capt0(cli_text("foo {.emph blah} bar")) expect_false(grepl("itsu:", out, fixed = TRUE)) }) test_that("margin is squashed", { cli_div(theme = list(par = list("margin-top" = 3, "margin-bottom" = 3))) out <- capt0({ cli_par(); cli_par(); cli_par() }, strip_style = TRUE) expect_equal(out, "\n\n\n") out <- capt0({ cli_end(); cli_end(); cli_end() }) expect_equal(out, "") out <- capt0({ cli_par(); cli_par(); cli_par() }) expect_equal(out, "") capt0(cli_text(lorem_ipsum())) out <- capt0({ cli_end(); cli_end(); cli_end() }, strip_style = TRUE) expect_equal(out, "\n\n\n") }) test_that("before and after work properly", { cli_div(theme = list( "div.alert-success" = list(before ="!!!") )) out <- capt0(cli_alert_success("{.pkg foobar} is good")) expect_match(out, "!!!", fixed = TRUE) }) cli/tests/testthat/test-assertions.R0000644000176200001440000000526113565765747017352 0ustar liggesusers context("assertions") test_that("is_string", { strings <- list("foo", "", "111", "1", "-", "NA") not_strings <- list(1, character(), NA_character_, NA, c("foo", NA), c("1", "2"), NULL) for (p in strings) { expect_true(is_string(p)) expect_silent(assert_that(is_string(p))) } for (n in not_strings) { expect_false(is_string(n)) expect_error(assert_that(is_string(n)), "is not a string") } }) test_that("is_border_style", { expect_true(is_border_style(rownames(box_styles())[1])) expect_false(is_border_style("blahblahxxx")) expect_silent(assert_that(is_border_style(rownames(box_styles())[1]))) expect_error(assert_that(is_border_style("blahblahxxx")), "not a border style") }) test_that("is_padding_or_margin", { good <- list(1, 0, 0L, 1L, 237, c(1,2,3,4), c(0,0,0,0), rep(1L, 4)) bad <- list(numeric(), integer(), c(1,2), c(1L, 2L, 3L), 1:5, "1", c("1", "2", "3", "1"), NA, NA_real_, NA_integer_, c(1,2,NA,1), c(1L,NA,3L)) for (g in good) { expect_true(is_padding_or_margin(g)) expect_silent(assert_that(is_padding_or_margin(g))) } for (b in bad) { expect_false(is_padding_or_margin(b)) expect_error(assert_that(is_padding_or_margin(b)), "must be an integer of length one or four") } }) test_that("is_col", { good <- list("red", "orange", NULL, crayon::red, col_red) bad <- list(c("red", "orange"), character(), NA_character_) for (g in good) { expect_true(is_col(g)) expect_silent(assert_that(is_col(g))) } for (b in bad) { expect_false(is_col(b)) expect_error(assert_that(is_col(b)), "must be a color name, or a crayon style") } }) test_that("is_count", { counts <- list(1, 1L, 0, 0L, 42, 42L) not_counts <- list(c(1, 2), numeric(), NA_integer_, NA_real_, NA, 1.1, NULL, "1") for (c in counts) { expect_true(is_count(c)) expect_silent(assert_that(is_count(c))) } for (n in not_counts) { expect_false(is_count(n)) expect_error(assert_that(is_count(n)), "must be a count") } }) test_that("is_tree_style", { good <- list( list(h = "1", v = "2", l = "3", j = "4"), list(j = "4", v = "2", h = "1", l = "3") ) bad <- list( NULL, 1:4, c(h = "1", v = "2", l = "3", j = "4"), list(h = "1", v = "2", l = "3", j = "4", x = "10"), list(h = "1", v = c("2", "3"), l = "3", j = "4"), list(h = "1", v = "2", l = character(), j = "4"), list(h = "1", v = "2", l = 3, j = "4"), list("1", v = "2", l = "3", j = "4"), list("1", "2", "3", "4") ) for (x in good) expect_true (is_tree_style(x)) for (x in bad ) expect_false(is_tree_style(x)) }) cli/tests/testthat/test-subprocess.R0000644000176200001440000000756413573440323017333 0ustar liggesusers context("subprocess") test_that("events are properly generated", { ## This needs callr >= 3.0.0.90001, which is not yet on CRAN if (packageVersion("callr") < "3.0.0.9001") skip("Need newer callr") do <- function() { cli::cli_div() cli::cli_h1("title") cli::cli_text("text") } rs <- callr::r_session$new() on.exit(rs$kill(), add = TRUE) msgs <- list() handler <- function(msg) { msgs <<- c(msgs, list(msg)) if (!is.null(findRestart("cli_message_handled"))) { invokeRestart("cli_message_handled") } } withCallingHandlers( rs$run(do), cli_message = handler) expect_equal(length(msgs), 4) lapply(msgs, expect_s3_class, "cli_message") expect_equal(msgs[[1]]$type, "div") expect_equal(msgs[[2]]$type, "h1") expect_equal(msgs[[3]]$type, "text") expect_equal(msgs[[4]]$type, "end") rs$close() }) test_that("subprocess with default handler", { ## This needs callr >= 3.0.0.90001, which is not yet on CRAN if (packageVersion("callr") < "3.0.0.9001") skip("Need newer callr") do <- function() { cli::cli_div() cli::cli_h1("title") cli::cli_text("text") } rs <- callr::r_session$new() on.exit(rs$kill(), add = TRUE) msgs <- list() withr::with_options(list( cli.default_handler = function(msg) { msgs <<- c(msgs, list(msg)) if (!is.null(findRestart("cli_message_handled"))) { invokeRestart("cli_message_handled") } }), rs$run(do) ) expect_equal(length(msgs), 4) lapply(msgs, expect_s3_class, "cli_message") expect_equal(msgs[[1]]$type, "div") expect_equal(msgs[[2]]$type, "h1") expect_equal(msgs[[3]]$type, "text") expect_equal(msgs[[4]]$type, "end") rs$close() }) test_that("output in child process", { ## This needs callr >= 3.0.0.90001, which is not yet on CRAN if (packageVersion("callr") < "3.0.0.9001") skip("Need newer callr") # We need to do our own condition handling, otherwise callr will # handle `cli_message` and copy it to the main process. # So on `cli_message` we just call the default handler, which will # call `message()`, and on `message` we'll copy the formatted message # to the main process. do <- function() { options(crayon.enabled = TRUE) options(crayon.colors = 256) crayon::num_colors(forget = TRUE) withCallingHandlers({ cli::start_app(theme = cli::simple_theme()) cli::cli_h1("Title") cli::cli_text("This is generated in the {.emph subprocess}.") "foobar" }, cli_message = function(msg) { withCallingHandlers({ cli:::cli_server_default(msg) invokeRestart("cli_message_handled") }, message = function(mmsg) { class(mmsg) <- c("callr_message", "message", "condition") signalCondition(mmsg) }) } ) } rs <- callr::r_session$new() on.exit(rs$kill(), add = TRUE) # Store the formatted messages from callr # We also need to muffle the default handler here msgs <- list() result <- withCallingHandlers( rs$run_with_output(do), callr_message = function(msg) { msgs <<- c(msgs, list(msg)) if (!is.null(msg$muffle) && !is.null(findRestart(msg$muffle))) { invokeRestart(msg$muffle) } } ) expect_equal(result$stdout, "") expect_equal(result$stderr, "") expect_identical(result$result, "foobar") expect_null(result$error) lapply(msgs, expect_s3_class, "callr_message") str <- paste(vcapply(msgs, "[[", "message"), collapse = "") expect_true(crayon::has_style(str)) expect_match(str, "Title") expect_match(str, "This is generated") rs$close() }) test_that("substitution in child process", { do <- function() { cli::cli_text("This is process {Sys.getpid()}.") } rs <- callr::r_session$new() on.exit(rs$kill(), add = TRUE) out <- capt0(rs$run(do)) expect_match(out, glue::glue("This is process {rs$get_pid()}")) rs$close() }) cli/tests/testthat/test-cat.R0000644000176200001440000000027213565765747015724 0ustar liggesuserscontext("test-cat.R") test_that("cat_line appends to file", { tmp <- tempfile() cat_line("a", file = tmp) cat_line("b", file = tmp) expect_equal(readLines(tmp), c("a", "b")) }) cli/tests/testthat/test-sitrep.R0000644000176200001440000000101613572445737016450 0ustar liggesusers context("sitrep") test_that("sitrep runs", { expect_true(is.list(cli_sitrep())) expect_true(is.character(format(cli_sitrep()))) out <- capture_output(print(cli_sitrep())) expect_true(all(grepl("^- ", out))) }) test_that("get_active_symbol_set", { withr::with_options(list(cli.unicode = TRUE), { expect_true(get_active_symbol_set() %in% c("UTF-8", "RStudio (UTF-8)")) }) withr::with_options(list(cli.unicode = FALSE), { set <- get_active_symbol_set() expect_equal(set, "ASCII (non UTF-8)") }) }) cli/tests/testthat/test-cliapp-output.R0000644000176200001440000000447713565765747017776 0ustar liggesusers setup(start_app()) teardown(stop_app()) test_that("cliapp output auto", { skip_on_cran() txt <- "Stay calm. This is a test." script <- tempfile(fileext = ".R") on.exit(unlink(script, recursive = TRUE), add = TRUE) # stderr if not interactive ---------------- code <- substitute(env = list(txt = txt), { options(rlib_interactive = FALSE, crayon.enabled = FALSE) cli::cli_text(txt) }) cat(deparse(code), file = script, sep = "\n") out <- callr::rscript(script, show = FALSE, fail_on_status = FALSE) expect_true(out$stderr %in% paste0(txt, c("\n", "\r\n"))) expect_equal(out$stdout, "") # stdout if interactive -------------------- code <- substitute(env = list(txt = txt), { options(rlib_interactive = TRUE, crayon.enabled = FALSE) cli::cli_text(txt) }) cat(deparse(code), file = script, sep = "\n") out <- callr::rscript(script, show = FALSE, fail_on_status = FALSE) expect_true(out$stdout %in% paste0(txt, c("\n", "\r\n"))) expect_equal(out$stderr, "") # choose explicitly ----------------------- txt2 <- "Don't move" code <- substitute(env = list(txt = txt, txt2 = txt2), { options(rlib_interactive = FALSE, crayon.enabled = FALSE) cli::start_app(output = "stderr") cli::cli_text(txt) cli::stop_app() options(rlib_interactive = TRUE) cli::start_app(output = "stdout") cli::cli_text(txt2) cli::stop_app() }) cat(deparse(code), file = script, sep = "\n") out <- callr::rscript(script, show = FALSE, fail_on_status = FALSE) expect_true(out$stderr %in% paste0(txt, c("\n", "\r\n"))) expect_true(out$stdout %in% paste0(txt2, c("\n", "\r\n"))) }) test_that("can also use a connection", { skip_on_cran() txt <- "Stay calm. This is a test." script <- tempfile(fileext = ".R") on.exit(unlink(script, recursive = TRUE), add = TRUE) code <- substitute(env = list(txt = txt), { options(crayon.enabled = FALSE) con <- textConnection(NULL, open = "w", local = TRUE) cli::start_app(output = con) cli::cli_text(txt) cli::stop_app() flush(con) cat("output:", textConnectionValue(con), "\n", sep = "") }) cat(deparse(code), file = script, sep = "\n") out <- callr::rscript(script, show = FALSE, fail_on_status = FALSE) expect_true(out$stdout %in% paste0("output:", txt, c("\n", "\r\n"))) expect_equal(out$stderr, "") }) cli/tests/testthat/test-spinners.R0000644000176200001440000000051413565765747017015 0ustar liggesusers context("spinners") test_that("get_spinner", { if (is_utf8_output()) { expect_equal(get_spinner()$name, "dots") } else { expect_equal(get_spinner()$name, "line") } }) test_that("list_spinners", { ls <- list_spinners() expect_true(is.character(ls)) expect_true("dots" %in% ls) expect_true("line" %in% ls) }) cli/tests/testthat/test-crayon.R0000644000176200001440000000314513565765747016452 0ustar liggesusers context("crayon colors") op <- options() on.exit(options(op)) options(crayon.enabled = TRUE) test_that("Classes", { expect_equal(class(style_underline("foo")), "ansi_string") }) test_that("Coloring and highlighting works", { expect_equal(c(style_underline("foo")), "\u001b[4mfoo\u001b[24m") expect_equal(c(col_red("foo")), "\u001b[31mfoo\u001b[39m") expect_equal(c(bg_red("foo")), "\u001b[41mfoo\u001b[49m") }) test_that("Applying multiple styles at once works", { st <- combine_ansi_styles(col_red, bg_green, "underline") expect_equal( c(st("foo")), "\u001b[31m\u001b[42m\u001b[4mfoo\u001b[24m\u001b[49m\u001b[39m") st <- combine_ansi_styles(style_underline, "red", bg_green) expect_equal( c(st("foo")), "\u001b[4m\u001b[31m\u001b[42mfoo\u001b[49m\u001b[39m\u001b[24m") }) test_that("Nested styles are supported", { st <- combine_ansi_styles(style_underline, bg_blue) expect_equal( c(col_red("foo", st("bar"), "!")), "\u001b[31mfoo\u001b[4m\u001b[44mbar\u001b[49m\u001b[24m!\u001b[39m") }) test_that("Nested styles of the same type are supported", { expect_equal( c(col_red("a", col_blue("b", col_green("c"), "b"), "c")), "\u001b[31ma\u001b[34mb\u001b[32mc\u001b[34mb\u001b[31mc\u001b[39m") }) test_that("Reset all styles", { st <- combine_ansi_styles("red", bg_green, "underline") expect_equal( c(style_reset(st("foo"), "foo")), paste0("\u001b[0m\u001b[31m\u001b[42m\u001b[4mfoo\u001b[24m\u001b[49m", "\u001b[39mfoo\u001b[0m")) }) test_that("Variable number of arguments", { expect_equal(c(col_red("foo", "bar")), "\u001b[31mfoobar\u001b[39m") }) cli/tests/testthat/test-non-breaking-space.R0000644000176200001440000000055313565765747020622 0ustar liggesusers context("non breaking space") test_that("does not break", { op <- options(cli.width = 40) on.exit(options(op)) str30 <- "123456789 123456789 1234567890" out <- capt0(cli_text(c(str30, "this\u00a0is\u00a0not\u00a0breaking"))) expect_equal( crayon::strip_style(out), "123456789 123456789\n1234567890this\u00a0is\u00a0not\u00a0breaking\n" ) }) cli/tests/testthat/test-lists.R0000644000176200001440000001253013565765747016313 0ustar liggesusers context("cli lists") setup(start_app()) teardown(stop_app()) test_that("ul", { cli_div(theme = list(ul = list("list-style-type" = "*"))) lid <- cli_ul() out <- capt0({ cli_li("foo") cli_li(c("bar", "foobar")) }, strip_style = TRUE) expect_equal(out, "* foo\n* bar\n* foobar\n") cli_end(lid) }) test_that("ol", { cli_div(theme = list(ol = list())) lid <- cli_ol() out <- capt0({ cli_li("foo") cli_li(c("bar", "foobar")) }, strip_style = TRUE) expect_equal(out, "1. foo\n2. bar\n3. foobar\n") cli_end(lid) }) test_that("ul ul", { cli_div(theme = list( ul = list("list-style-type" = "*"), "ul ul" = list("list-style-type" = "-", "margin-left" = 2) )) lid <- cli_ul() out <- capt0({ cli_li("1") lid2 <- cli_ul() cli_li("1 1") cli_li(c("1 2", "1 3")) cli_end(lid2) cli_li("2") }, strip_style = TRUE) expect_equal(out, "* 1\n - 1 1\n - 1 2\n - 1 3\n* 2\n") cli_end(lid) }) test_that("ul ol", { cli_div(theme = list( ul = list("list-style-type" = "*"), li = list("margin-left" = 2) )) lid <- cli_ul() out <- capt0({ cli_li("1") lid2 <- cli_ol() cli_li("1 1") cli_li(c("1 2", "1 3")) cli_end(lid2) cli_li("2") }, strip_style = TRUE) expect_equal(out, " * 1\n 1. 1 1\n 2. 1 2\n 3. 1 3\n * 2\n") cli_end(lid) }) test_that("ol ol", { cli_div(theme = list( "li" = list("margin-left" = 2), "li li" = list("margin-left" = 2) )) lid <- cli_ol() out <- capt0({ cli_li("1") lid2 <- cli_ol() cli_li("1 1") cli_li(c("1 2", "1 3")) cli_end(lid2) cli_li("2") }, strip_style = TRUE) expect_equal(out, " 1. 1\n 1. 1 1\n 2. 1 2\n 3. 1 3\n 2. 2\n") cli_end(lid) }) test_that("ol ul", { cli_div(theme = list( ul = list("list-style-type" = "*", "margin-left" = 2) )) lid <- cli_ol() out <- capt0({ cli_li("1") lid2 <- cli_ul() cli_li("1 1") cli_li(c("1 2", "1 3")) cli_end(lid2) cli_li("2") }, strip_style = TRUE) expect_equal(out, "1. 1\n * 1 1\n * 1 2\n * 1 3\n2. 2\n") cli_end(lid) }) test_that("starting with an item", { cli_div(theme = list(ul = list("list-style-type" = "*"))) out <- capt0({ cli_li("foo") cli_li(c("bar", "foobar")) }, strip_style = TRUE) expect_equal(out, "* foo\n* bar\n* foobar\n") }) test_that("ol, with first item", { cli_div(theme = list(ol = list())) out <- capt0({ lid <- cli_ol("foo", .close = FALSE) cli_li(c("bar", "foobar")) }, strip_style = TRUE) expect_equal(out, "1. foo\n2. bar\n3. foobar\n") cli_end(lid) }) test_that("ul, with first item", { cli_div(theme = list(ul = list("list-style-type" = "*"))) out <- capt0({ lid <- cli_ul("foo", .close = FALSE) cli_li(c("bar", "foobar")) }, strip_style = TRUE) expect_equal(out, "* foo\n* bar\n* foobar\n") cli_end(lid) }) test_that("dl", { cli_div(theme = list(ul = list())) lid <- cli_dl() out <- capt0({ cli_li(c(this = "foo")) cli_li(c(that = "bar", other = "foobar")) }, strip_style = TRUE) expect_equal(out, "this: foo\nthat: bar\nother: foobar\n") cli_end(lid) }) test_that("dl dl", { cli_div(theme = list( li = list("margin-left" = 2) )) lid <- cli_dl() out <- capt0({ cli_li(c(a = "1")) lid2 <- cli_dl() cli_li(c("a-a" = "1 1")) cli_li(c("a-b" = "1 2", "a-c" = "1 3")) cli_end(lid2) cli_li(c(b = "2")) }, strip_style = TRUE) expect_equal(out, " a: 1\n a-a: 1 1\n a-b: 1 2\n a-c: 1 3\n b: 2\n") cli_end(lid) }) test_that("dl ol", { cli_div(theme = list( li = list("margin-left" = 2) )) lid <- cli_dl() out <- capt0({ cli_li(c(a = "1")) lid2 <- cli_ol() cli_li(c("1 1")) cli_li(c("1 2", "1 3")) cli_end(lid2) cli_li(c(b = "2")) }, strip_style = TRUE) expect_equal(out, " a: 1\n 1. 1 1\n 2. 1 2\n 3. 1 3\n b: 2\n") cli_end(lid) }) test_that("dl ul", { cli_div(theme = list( ul = list("list-style-type" = "*"), li = list("margin-left" = 2) )) lid <- cli_dl() out <- capt0({ cli_li(c(a = "1")) lid2 <- cli_ul() cli_li(c("1 1")) cli_li(c("1 2", "1 3")) cli_end(lid2) cli_li(c(b = "2")) }, strip_style = TRUE) expect_equal(out, " a: 1\n * 1 1\n * 1 2\n * 1 3\n b: 2\n") cli_end(lid) }) test_that("ol dl", { cli_div(theme = list( li = list("margin-left" = 2) )) lid <- cli_ol() out <- capt0({ cli_li("1") lid2 <- cli_dl() cli_li(c("a-a" = "1 1")) cli_li(c("a-b" = "1 2", "a-c" = "1 3")) cli_end(lid2) cli_li("2") }, strip_style = TRUE) expect_equal(out, " 1. 1\n a-a: 1 1\n a-b: 1 2\n a-c: 1 3\n 2. 2\n") cli_end(lid) }) test_that("ul dl", { cli_div(theme = list( ul = list("list-style-type" = "*"), li = list("margin-left" = 2) )) lid <- cli_ul() out <- capt0({ cli_li("1") lid2 <- cli_dl() cli_li(c("a-a" = "1 1")) cli_li(c("a-b" = "1 2", "a-c" = "1 3")) cli_end(lid2) cli_li("2") }, strip_style = TRUE) expect_equal(out, " * 1\n a-a: 1 1\n a-b: 1 2\n a-c: 1 3\n * 2\n") cli_end(lid) }) test_that("dl, with first item", { cli_div(theme = list(ul = list())) out <- capt0({ lid <- cli_dl(c(this = "foo"), .close = FALSE) cli_li(c(that = "bar", other = "foobar")) }, strip_style = TRUE) expect_equal(out, "this: foo\nthat: bar\nother: foobar\n") cli_end(lid) }) cli/tests/testthat/test-alerts.R0000644000176200001440000000155613565765747016455 0ustar liggesusers context("cli alerts") setup(start_app()) teardown(stop_app()) test_that("generic", { cli_div(theme = list(".alert" = list(before = "GENERIC! "))) out <- capt0(cli_alert("wow")) expect_match(out, "GENERIC") }) test_that("success", { cli_div(theme = list(".alert-success" = list(before = "SUCCESS! "))) out <- capt0(cli_alert_success("wow")) expect_match(out, "SUCCESS") }) test_that("danger", { cli_div(theme = list(".alert-danger" = list(before = "DANGER! "))) out <- capt0(cli_alert_danger("wow")) expect_match(out, "DANGER") }) test_that("warning", { cli_div(theme = list(".alert-warning" = list(before = "WARNING! "))) out <- capt0(cli_alert_warning("wow")) expect_match(out, "WARNING") }) test_that("info", { cli_div(theme = list(".alert-info" = list(before = "INFO! "))) out <- capt0(cli_alert_info("wow")) expect_match(out, "INFO") }) cli/tests/testthat/test-box-styles.R0000644000176200001440000000030613565765747017264 0ustar liggesusers context("box styles") test_that("list_border_styles", { expect_silent(bs <- list_border_styles()) expect_true(is.character(bs)) expect_true(!any(is.na(bs))) expect_true(length(bs) > 0) }) cli/tests/testthat/test-crayon-combine.R0000644000176200001440000000204513565765747020062 0ustar liggesusers context("crayon combine_ansi_styles") test_that("one style", { expect_equal(combine_ansi_styles(col_red), col_red) expect_equal(combine_ansi_styles(style_bold), style_bold) }) test_that("style objects", { withr::with_options( list(crayon.enabled = TRUE, crayon.colors = 256), { expect_equal( combine_ansi_styles(col_red, style_bold)("blah"), col_red(style_bold("blah")) ) expect_equal( combine_ansi_styles(col_red, style_bold, style_underline)("foo"), col_red(style_bold(style_underline("foo"))) ) } ) }) test_that("create styles on the fly", { withr::with_options( list(crayon.enabled = TRUE, crayon.colors = 256), { expect_equal( combine_ansi_styles("darkolivegreen", style_bold)("blah"), make_ansi_style("darkolivegreen")((style_bold("blah"))) ) expect_equal( combine_ansi_styles(style_bold, "darkolivegreen", style_underline)("foo"), style_bold(make_ansi_style("darkolivegreen")(style_underline("foo"))) ) } ) }) cli/tests/testthat/test-substitution.R0000644000176200001440000000030513565765747017726 0ustar liggesusers context("substitution") setup(start_app()) teardown(stop_app()) test_that("glue errors", { expect_error(cli_h1("foo { asdfasdfasdf } bar")) expect_error(cli_text("foo {cmd {dsfsdf()}}")) }) cli/tests/testthat/test-tree.R0000644000176200001440000001066213565765747016120 0ustar liggesusers context("tree") test_that("tree", { data <- data.frame( stringsAsFactors = FALSE, package = c("processx", "backports", "assertthat", "Matrix", "magrittr", "rprojroot", "clisymbols", "prettyunits", "withr", "desc", "igraph", "R6", "crayon", "debugme", "digest", "irlba", "rcmdcheck", "callr", "pkgconfig", "lattice"), dependencies = I(list( c("assertthat", "crayon", "debugme", "R6"), character(0), character(0), "lattice", character(0), "backports", character(0), c("magrittr", "assertthat"), character(0), c("assertthat", "R6", "crayon", "rprojroot"), c("irlba", "magrittr", "Matrix", "pkgconfig"), character(0), character(0), "crayon", character(0), "Matrix", c("callr", "clisymbols", "crayon", "desc", "digest", "prettyunits", "R6", "rprojroot", "withr"), c("processx", "R6"), character(0), character(0) )) ) out <- capt(tree(data)) exp <- rebox(mode = "tree", "processx", "├─assertthat", "├─crayon", "├─debugme", "│ └─crayon", "└─R6" ) expect_equal(out, exp) out <- capt(tree(data, root = "desc")) exp <- rebox(mode = "tree", "desc", "├─assertthat", "├─R6", "├─crayon", "└─rprojroot", " └─backports") expect_equal(out, exp) # Check that trees with apparent circularity error nicely data <- data.frame( stringsAsFactors = FALSE, X = c("a", "b", "c","d", "e", "f", "g", "h", "j"), Y = I(list( c("b", "e", "f"), c("d", "g"), character(0), c("a", "h"), character(0), character(0), character(0), c("j"), character(0) )) ) expect_warning(tree(data), "Endless loop found in tree: a -> b -> d -> a") }) test_that("trimming", { pkgdeps <- list( "dplyr@0.8.3" = c("assertthat@0.2.1", "glue@1.3.1", "magrittr@1.5", "R6@2.4.0", "Rcpp@1.0.2", "rlang@0.4.0", "tibble@2.1.3", "tidyselect@0.2.5"), "assertthat@0.2.1" = character(), "glue@1.3.1" = character(), "magrittr@1.5" = character(), "pkgconfig@2.0.3" = character(), "R6@2.4.0" = character(), "Rcpp@1.0.2" = character(), "rlang@0.4.0" = character(), "tibble@2.1.3" = c("cli@1.1.0", "crayon@1.3.4", "fansi@0.4.0", "pillar@1.4.2", "pkgconfig@2.0.3", "rlang@0.4.0"), "cli@1.1.0" = c("assertthat@0.2.1", "crayon@1.3.4"), "crayon@1.3.4" = character(), "fansi@0.4.0" = character(), "pillar@1.4.2" = c("cli@1.1.0", "crayon@1.3.4", "fansi@0.4.0", "rlang@0.4.0", "utf8@1.1.4", "vctrs@0.2.0"), "utf8@1.1.4" = character(), "vctrs@0.2.0" = c("backports@1.1.5", "ellipsis@0.3.0", "digest@0.6.21", "glue@1.3.1", "rlang@0.4.0", "zeallot@0.1.0"), "backports@1.1.5" = character(), "ellipsis@0.3.0" = c("rlang@0.4.0"), "digest@0.6.21" = character(), "glue@1.3.1" = character(), "zeallot@0.1.0" = character(), "tidyselect@0.2.5" = c("glue@1.3.1", "purrr@1.3.1", "rlang@0.4.0", "Rcpp@1.0.2"), "purrr@0.3.3" = c("magrittr@1.5", "rlang@0.4.0") ) pkgs <- data.frame( stringsAsFactors = FALSE, name = names(pkgdeps), deps = I(unname(pkgdeps)) ) pkgs$label <- pkgs$name pkgs$trimmed <- paste(pkgs$name, " (trimmed)") out <- capt(tree(pkgs, trim = TRUE)) exp <- rebox(mode = "tree", "dplyr@0.8.3", "├─assertthat@0.2.1", "├─glue@1.3.1", "├─magrittr@1.5", "├─R6@2.4.0", "├─Rcpp@1.0.2", "├─rlang@0.4.0", "├─tibble@2.1.3", "│ ├─cli@1.1.0", "│ │ ├─assertthat@0.2.1 (trimmed)", "│ │ └─crayon@1.3.4", "│ ├─crayon@1.3.4 (trimmed)", "│ ├─fansi@0.4.0", "│ ├─pillar@1.4.2", "│ │ ├─cli@1.1.0 (trimmed)", "│ │ ├─crayon@1.3.4 (trimmed)", "│ │ ├─fansi@0.4.0 (trimmed)", "│ │ ├─rlang@0.4.0 (trimmed)", "│ │ ├─utf8@1.1.4", "│ │ └─vctrs@0.2.0", "│ │ ├─backports@1.1.5", "│ │ ├─ellipsis@0.3.0", "│ │ │ └─rlang@0.4.0 (trimmed)", "│ │ ├─digest@0.6.21", "│ │ ├─glue@1.3.1 (trimmed)", "│ │ ├─rlang@0.4.0 (trimmed)", "│ │ └─zeallot@0.1.0", "│ ├─pkgconfig@2.0.3", "│ └─rlang@0.4.0 (trimmed)", "└─tidyselect@0.2.5", " ├─glue@1.3.1 (trimmed)", " ├─rlang@0.4.0 (trimmed)", " └─Rcpp@1.0.2 (trimmed)" ) expect_equal(out, exp) }) cli/tests/testthat/test-rules.R0000644000176200001440000001024013565765747016303 0ustar liggesusers context("rules") test_that("make_line", { expect_equal(make_line(1, "-"), "-") expect_equal(make_line(0, "-"), "") expect_equal(make_line(2, "-"), "--") expect_equal(make_line(10, "-"), "----------") expect_equal(make_line(2, "12"), "12") expect_equal(make_line(0, "12"), "") expect_equal(make_line(1, "12"), "1") expect_equal(make_line(9, "12"), "121212121") expect_equal(make_line(10, "12"), "1212121212") }) test_that("width option", { withr::with_envvar( list(RSTUDIO_CONSOLE_WIDTH = 10), expect_equal( rule(line = "-"), rule_class("----------") ) ) expect_equal( rule(width = 11, line = "-"), rule_class("-----------") ) }) test_that("left label", { expect_equal( rule("label", width = 12, line = "-"), rule_class("-- label ---") ) expect_equal( rule("l", width = 12, line = "-"), rule_class("-- l -------") ) expect_equal( rule("label", width = 9, line = "-"), rule_class("-- label ") ) expect_equal( rule("label", width = 8, line = "-"), rule_class("-- label") ) expect_equal( rule("label", width = 6, line = "-"), rule_class("-- lab") ) }) test_that("centered label", { expect_error( rule(left = "label", center = "label"), "cannot be specified" ) expect_error( rule(center = "label", right = "label"), "cannot be specified" ) expect_equal( rule(center = "label", width = 13, line = "-"), rule_class("--- label ---") ) expect_equal( rule(center = "label", width = 14, line = "-"), rule_class("---- label ---") ) expect_equal( rule(center = "label", width = 9, line = "-"), rule_class("- label -") ) expect_equal( rule(center = "label", width = 8, line = "-"), rule_class("- labe -") ) expect_equal( rule(center = "label", width = 7, line = "-"), rule_class("- lab -") ) }) test_that("right label", { expect_equal( rule(right = "label", width = 12, line = "-"), rule_class("--- label --") ) expect_equal( rule(right = "l", width = 12, line = "-"), rule_class("------- l --") ) expect_equal( rule(right = "label", width = 9, line = "-"), rule_class(" label --") ) expect_equal( rule(right = "label", width = 8, line = "-"), rule_class(" label -") ) expect_equal( rule(right = "label", width = 6, line = "-"), rule_class(" label") ) expect_equal( rule(right = "label", width = 5, line = "-"), rule_class(" labe") ) expect_equal( rule(right = "label", width = 4, line = "-"), rule_class(" lab") ) }) test_that("line_col", { withr::with_options( list(crayon.enabled = TRUE, crayon.colors = 256), { expect_true(crayon::has_style( rule(line_col = "red") )) expect_true(crayon::has_style( rule(left = "left", line_col = "red") )) expect_true(crayon::has_style( rule(left = "left", right = "right", line_col = "red") )) expect_true(crayon::has_style( rule(center = "center", line_col = "red") )) expect_true(crayon::has_style( rule(right = "right", line_col = "red") )) expect_true(crayon::has_style( rule(line_col = col_red) )) } ) }) test_that("get_line_char", { expect_equal(get_line_char(1), cli::symbol$line) expect_equal(get_line_char(2), cli::symbol$double_line) expect_equal(get_line_char("bar1"), cli::symbol$lower_block_1) expect_equal(get_line_char("bar2"), cli::symbol$lower_block_2) expect_equal(get_line_char("bar3"), cli::symbol$lower_block_3) expect_equal(get_line_char("bar4"), cli::symbol$lower_block_4) expect_equal(get_line_char("bar5"), cli::symbol$lower_block_5) expect_equal(get_line_char("bar6"), cli::symbol$lower_block_6) expect_equal(get_line_char("bar7"), cli::symbol$lower_block_7) expect_equal(get_line_char("bar8"), cli::symbol$lower_block_8) expect_equal(get_line_char("xxx"), "xxx") expect_equal(get_line_char(c("x", "y", "z")), "xyz") }) test_that("print.rule", { withr::with_options( list(cli.width = 20), { out <- capt(rule("foo")) exp <- rebox("── foo ─────────────") expect_equal(out, exp) } ) }) cli/tests/testthat/test-collapsing.R0000644000176200001440000000110113565765747017300 0ustar liggesusers setup(start_app()) teardown(stop_app()) test_that("collapsing without formatting", { pkgs <- paste0("pkg", 1:5) out <- capt0(cli_text("Packages: {pkgs}."), strip_style = TRUE) expect_equal(out, "Packages: pkg1, pkg2, pkg3, pkg4 and pkg5.\n") }) test_that("collapsing with formatting", { cli_div(theme = list(".pkg" = list(fmt = function(x) paste0(x, " (P)")))) pkgs <- paste0("pkg", 1:5) out <- capt0(cli_text("Packages: {.pkg {pkgs}}."), strip_style = TRUE) expect_equal( out, "Packages: pkg1 (P), pkg2 (P), pkg3 (P), pkg4 (P) and pkg5 (P).\n" ) }) cli/tests/testthat/test-pluralization.R0000644000176200001440000001041513571261512020024 0ustar liggesusers context("pluralization") setup(start_app()) teardown(stop_app()) test_that("simplest", { cases <- list( list("{0} package{?s}", "0 packages"), list("{1} package{?s}", "1 package"), list("{2} package{?s}", "2 packages") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("irregular", { cases <- list( list("{0} dictionar{?y/ies}", "0 dictionaries"), list("{1} dictionar{?y/ies}", "1 dictionary"), list("{2} dictionar{?y/ies}", "2 dictionaries") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("multiple substitutions", { cases <- list( list("{0} package{?s} {?is/are} ...", "0 packages are ..."), list("{1} package{?s} {?is/are} ...", "1 package is ..."), list("{2} package{?s} {?is/are} ...", "2 packages are ...") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("multiple quantities", { cases <- list( list("{0} package{?s} and {0} folder{?s}", "0 packages and 0 folders"), list("{1} package{?s} and {0} folder{?s}", "1 package and 0 folders"), list("{2} package{?s} and {0} folder{?s}", "2 packages and 0 folders"), list("{0} package{?s} and {1} folder{?s}", "0 packages and 1 folder"), list("{1} package{?s} and {1} folder{?s}", "1 package and 1 folder"), list("{2} package{?s} and {1} folder{?s}", "2 packages and 1 folder"), list("{0} package{?s} and {2} folder{?s}", "0 packages and 2 folders"), list("{1} package{?s} and {2} folder{?s}", "1 package and 2 folders"), list("{2} package{?s} and {2} folder{?s}", "2 packages and 2 folders") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("no()", { cases <- list( list("{no(0)} package{?s}", "no packages"), list("{no(1)} package{?s}", "1 package"), list("{no(2)} package{?s}", "2 packages") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("set qty() explicitly", { cases <- list( list("{qty(0)}There {?is/are} {0} package{?s}", "There are 0 packages"), list("{qty(1)}There {?is/are} {1} package{?s}", "There is 1 package"), list("{qty(2)}There {?is/are} {2} package{?s}", "There are 2 packages") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("collapsing vectors", { pkgs <- function(n) glue("pkg{seq_len(n)}") cases <- list( list("The {pkgs(1)} package{?s}", "The pkg1 package"), list("The {pkgs(2)} package{?s}", "The pkg1 and pkg2 packages"), list("The {pkgs(3)} package{?s}", "The pkg1, pkg2 and pkg3 packages") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("pluralization and style", { special_style <- list(span.foo = list(before = "<", after = ">")) cli_div(theme = special_style) cases <- list( list("{0} {.foo package{?s}}", "0 "), list("{1} {.foo package{?s}}", "1 "), list("{2} {.foo package{?s}}", "2 ") ) for (c in cases) { expect_equal(str_trim(capt0(cli_text(c[[1]]), strip_style = TRUE)), c[[2]]) } pkgs <- function(n) glue("pkg{seq_len(n)}") cases <- list( list("The {.foo {pkgs(1)}} package{?s}", "The package"), list("The {.foo {pkgs(2)}} package{?s}", "The and packages"), list("The {.foo {pkgs(3)}} package{?s}", "The , and packages") ) for (c in cases) { expect_equal(str_trim(capt0(cli_text(c[[1]]), strip_style = TRUE)), c[[2]]) } }) test_that("post-processing", { cases <- list( list("Package{?s}: {0}", "Packages: 0"), list("Package{?s}: {1}", "Package: 1"), list("Package{?s}: {2}", "Packages: 2") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) pkgs <- function(n) glue("pkg{seq_len(n)}") cases <- list( list("Package{?s}: {pkgs(1)}", "Package: pkg1"), list("Package{?s}: {pkgs(2)}", "Packages: pkg1 and pkg2") ) for (c in cases) expect_equal(str_trim(capt0(cli_text(c[[1]]))), c[[2]]) }) test_that("post-processing errors", { expect_error( cli_text("package{?s}"), "Cannot pluralize without a quantity" ) expect_error( cli_text("package{?s} {5} {10}"), "Multiple quantities for pluralization" ) }) cli/tests/testthat/test-headers.R0000644000176200001440000000140413573303607016544 0ustar liggesusers context("cli headers") setup(start_app()) teardown(stop_app()) test_that("headers", { cli_div(class = "testcli", theme = test_style()) withr::with_options(list(crayon.enabled = TRUE, crayon.colors = 256), { out <- capt0(cli_h1("HEADER")) expect_true(crayon::has_style(out)) expect_equal(crayon::strip_style(out), "\nHEADER\n\n") out <- capt0(cli_h2("Header")) expect_true(crayon::has_style(out)) expect_equal(crayon::strip_style(out), "Header\n\n") out <- capt0(cli_h3("Header")) expect_true(crayon::has_style(out)) expect_equal(crayon::strip_style(out), "Header\n") x <- "foobar" xx <- 100 out <- capt0(cli_h2("{xx}. header: {x}")) expect_equal(crayon::strip_style(out), "\n100. header: foobar\n\n") }) }) cli/tests/testthat/test-custom-handler.R0000644000176200001440000000070013565765747020076 0ustar liggesusers context("custom-handler") test_that("custom handler works", { conds <- list() withr::with_options( list(cli.default_handler = function(msg) conds <<- c(conds, list(msg))), { cli_h1("title"); cli_h2("subtitle"); cli_text("text") } ) expect_equal(length(conds), 3) lapply(conds, expect_s3_class, "cli_message") expect_equal(conds[[1]]$type, "h1") expect_equal(conds[[2]]$type, "h2") expect_equal(conds[[3]]$type, "text") }) cli/tests/testthat/helper.R0000644000176200001440000000575613565765747015473 0ustar liggesusers rule_class <- function(x) { structure(x, class = c("rule", "character")) } capture_messages <- function(expr) { msgs <- character() i <- 0 suppressMessages(withCallingHandlers( expr, message = function(e) msgs[[i <<- i + 1]] <<- conditionMessage(e))) paste0(msgs, collapse = "") } capt <- function(expr, print_it = TRUE) { pr <- if (print_it) print else identity paste(capture.output(pr(expr)), collapse = "\n") } capt00 <- function(expr) { capt(expr, print_it = FALSE) } capt0 <- function(expr, strip_style = FALSE) { out <- capture_messages(expr) if (strip_style) crayon::strip_style(out) else out } capt_cat <- function(expr) { paste(capture.output(cat(expr)), collapse = "\n") } ## This function always needs to return the same as the actual correct output ## on the current platform, with the current settings. ## There are four cases: ## 1. Platform is UTF-8 and cli.unicode = TRUE ## There is nothing we need to do ## 2. Platform is UTF-8 and cli.unicode = FALSE ## Need to convert to non-unicode alternative characters ## 3. Platform is not UTF-8 and cli.unicode = TRUE ## Need to use enc2native to convert to platform replacement characters ## 4. Platform is not UTF-8 and cli.unicode = FALSE ## Need to convert to non-unicode alternative characters rebox <- function(..., mode = c("box", "tree")) { mode <- match.arg(mode) bx <- as.character(c(...)) ## Older versions of testthat do not set the encoding on the ## parsed files, so we set it manually here Encoding(bx) <- "UTF-8" bx <- paste(bx, collapse = "\n") utf8 <- l10n_info()$`UTF-8` on <- is_utf8_output() if (utf8 && on) { bx } else if (utf8 && !on) { fallback(bx, mode) } else if (!utf8 && on) { enc2native(bx) } else { fallback(bx, mode) } } fallback <- function(bx, mode) { if (mode == "box") { ## single bx <- chartr( c("\u250c", "\u2510", "\u2518", "\u2514", "\u2502", "\u2500"), c("+", "+", "+", "+", "|", "-"), bx) ## double bx <- chartr( c("\u2554", "\u2557", "\u255d", "\u255a", "\u2551", "\u2550"), c("+", "+", "+", "+", "|", "-"), bx) ## round bx <- chartr( c("\u256d", "\u256e", "\u256f", "\u2570", "\u2502", "\u2500"), c("+", "+", "+", "+", "|", "-"), bx) ## single-double bx <- chartr( c("\u2553", "\u2556", "\u255c", "\u2559", "\u2551", "\u2500"), c("+", "+", "+", "+", "|", "-"), bx) ## double-single bx <- chartr( c("\u2552", "\u2555", "\u255b", "\u2558", "\u2502", "\u2550"), c("+", "+", "+", "+", "|", "-"), bx) ## Bullets bx <- chartr("\u25CF", "*", bx) } else if (mode == "tree") { bx <- chartr( c("\u2500", "\u2502", "\u2514", "\u251c"), c("-", "|", "\\", "+"), bx) } bx } chartr <- function(old, new, x) { assertthat::assert_that( is.character(old), is.character(new), is.character(x), length(old) == length(new) ) for (i in seq_along(old)) { x <- gsub(old[i], new[i], x, fixed = TRUE) } x } cli/tests/testthat/test-text.R0000644000176200001440000000134613565765747016144 0ustar liggesusers context("cli text") setup(start_app()) teardown(stop_app()) test_that("text is wrapped", { cli_div(class = "testcli", theme = test_style()) withr::with_options(c(cli.width = 60), { capt0(cli_h1("Header"), strip_style = TRUE) out <- capt0(cli_text(lorem_ipsum()), strip_style = TRUE) out <- strsplit(out, "\n")[[1]] len <- nchar(strsplit(out, "\n", fixed = TRUE)[[1]]) expect_true(all(len <= 60)) }) }) test_that("verbatim text is not wrapped", { cli_div(class = "testcli", theme = test_style()) withr::with_options(c(cli.width = 60), { capt0(cli_h1("Header")) txt <- strrep("1234567890 ", 20) out <- capt0(cli_verbatim(txt), strip_style = TRUE) expect_equal(out, paste0(txt, "\n")) }) }) cli/tests/testthat/test-status-bar.R0000644000176200001440000001443613571552620017226 0ustar liggesusers context("cli status bar") setup(start_app()) teardown(stop_app()) test_that("create and clear", { f <- function() { cli_status("* This is the current status") cli_status_clear() } out <- crayon::strip_style(capt0(f())) expect_match(out, "* This is the current status", fixed = TRUE) out <- crayon::strip_style(capt0(f())) expect_match(out, "* This is the current status", fixed = TRUE) }) test_that("output while status bar is active", { f <- function() { cli_text("out1") sb <- cli_status("status1") cli_text("out2") cli_status_update("status2", id = sb) } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "out1\n", "\r\rstatus1", "\r \rout2\nstatus1", "\r \rstatus2", "\r \r")) }) test_that("interpolation", { f <- function() { cli_div(theme = list("span.pkg" = list("before" = "{", after = "}"))) cli_status("You see 1+1={1+1}, this is {.pkg cli}") cli_status_clear() } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "\r\rYou see 1+1=2, this is {cli}", "\r \r")) }) test_that("update", { f <- function() { cli_text("out1") sb <- cli_status("status1") cli_status_update("status2", id = sb) } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "out1\n", "\r\rstatus1", "\r \rstatus2", "\r \r")) }) test_that("keep", { f <- function() { cli_status("* This is the current status", .keep = TRUE) cli_status_clear() } out <- crayon::strip_style(capt0(f())) expect_equal(out, "\r\r* This is the current status\n") }) test_that("multiple status bars", { f <- function() { sb1 <- cli_status("status1") cli_text("text1") sb2 <- cli_status("status2") cli_text("text2") cli_status_clear(sb2) cli_text("text3") } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "\r\rstatus1", "\r \rtext1\nstatus1", # emit text1, restore status1 "\r \rstatus2", # show status2 "\r \rtext2\nstatus2", # emit text2, restore status2 "\r \rstatus1", # clear status2, restore status1 "\r \rtext3\nstatus1", # emit text3, restore status1 "\r \r")) # (auto)clear status1 }) test_that("truncating", { f <- function() { withr::local_options(list(cli.width = 40)) txt <- "Eiusmod enim mollit aute aliquip Lorem sunt cupidatat." cli_status(c(txt, txt)) } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "\r\rEiusmod enim mollit aute aliquip Lorem", "\r \r")) }) test_that("ansi colors and clearing", { f <- function() { withr::local_options(list(crayon.enabled = TRUE, crayon.colors = 256)) crayon::num_colors(forget = TRUE) cli_status(col_red("This is red")) cli_status_clear() } out <- capt0(f()) expect_match(out, "\033[31m", fixed = TRUE) expect_match(out, "\r \r", fixed = TRUE) }) test_that("theming status bar", { f <- function() { cli_text("out1") sb <- cli_status("{.alert-info status1}") cli_text("out2") cli_status_update("status2", id = sb) } out <- crayon::strip_style(capt0(f())) out2 <- crayon::strip_style(capt0(cli_alert_info("status1"))) expect_match(out, str_trim(out2), fixed = TRUE) }) test_that("successful termination", { f <- function() { cli_text("out1") sb <- cli_status("status1") cli_text("out2") cli_status_clear(result = "done") } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "out1\n", "\r\rstatus1", "\r \rout2\nstatus1", "\r \rstatus1 ... done\n" )) }) test_that("terminate with failed", { f <- function() { cli_text("out1") sb <- cli_status("status1") cli_text("out2") cli_status_clear(result = "failed") } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "out1\n", "\r\rstatus1", "\r \rout2\nstatus1", "\r \rstatus1 ... failed\n" )) }) test_that("auto close with success", { f <- function() { cli_text("out1") sb <- cli_status("status1", .auto_result = "done") cli_text("out2") } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "out1\n", "\r\rstatus1", "\r \rout2\nstatus1", "\r \rstatus1 ... done\n" )) }) test_that("auto close wtih failure", { f <- function() { cli_text("out1") sb <- cli_status("status1", .auto_result = "failed") if (is_interactive()) Sys.sleep(2) cli_text("out2") if (is_interactive()) Sys.sleep(2) } out <- crayon::strip_style(capt0(f())) expect_equal(out, paste0( "out1\n", "\r\rstatus1", "\r \rout2\nstatus1", "\r \rstatus1 ... failed\n" )) }) test_that("auto close with styling", { f <- function() { cli_text("out1") sb <- cli_status( msg = "{.alert-info status1}", msg_done = "{.alert-success status1 ... done}", msg_failed = "{.alert-danger status1 ... failed}", .auto_result = "failed" ) if (is_interactive()) Sys.sleep(1) cli_text("out2") if (is_interactive()) Sys.sleep(1) } out <- crayon::strip_style(capt0(f())) expect_match(out, "status1 ... failed\n") f2 <- function() { cli_text("out1") sb <- cli_status( msg = "{.alert-info status1}", msg_done = "{.alert-success status1 ... done}", msg_failed = "{.alert-danger status1 ... failed}", .auto_result = "done" ) if (is_interactive()) Sys.sleep(1) cli_text("out2") if (is_interactive()) Sys.sleep(1) } out2 <- crayon::strip_style(capt0(f2())) expect_match(out2, "status1 ... done\n") }) test_that("process auto close with success", { f <- function() { cli_text("out1") sb <- cli_process_start("status1", on_exit = "done") cli_text("out2") } out <- crayon::strip_style(capt0(f())) expect_match(out, "status1 ... done") }) test_that("process auto close with failure", { f <- function() { cli_text("out1") sb <- cli_process_start("status1", on_exit = "failed") if (is_interactive()) Sys.sleep(2) cli_text("out2") if (is_interactive()) Sys.sleep(2) } out <- crayon::strip_style(capt0(f())) expect_match(out, "status1 ... failed") }) cli/tests/testthat/test-cat-helpers.R0000644000176200001440000000365713565765747017376 0ustar liggesusers context("cat helpers") test_that("cat_line", { out <- capt00(cat_line("This is ", "a ", "line of text.")) exp <- rebox("This is a line of text.") expect_equal(out, exp) withr::with_options( list(crayon.enabled = TRUE, crayon.colors = 256), { out <- capt00(cat_line("This is ", "a ", "line of text.", col = "red")) expect_true(crayon::has_style(out)) exp <- rebox("This is a line of text.") expect_equal(crayon::strip_style(out), exp) out <- capt00(cat_line("This is ", "a ", "line of text.", background_col = "green")) expect_true(crayon::has_style(out)) exp <- rebox("This is a line of text.") expect_equal(crayon::strip_style(out), exp) } ) tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) cat_line("This is ", "a ", "line of text.", file = tmp) exp <- rebox("This is a line of text.") expect_equal(readLines(tmp, warn = FALSE), exp) }) test_that("cat_bullet", { out <- capt00(cat_bullet(letters[1:5])) exp <- rebox("● a\n● b\n● c\n● d\n● e") expect_equal(out, exp) }) test_that("cat_boxx", { out <- capt00(cat_boxx("foo")) exp <- rebox( "┌─────────┐", "│ │", "│ foo │", "│ │", "└─────────┘") expect_equal(out, exp) }) test_that("cat_rule", { out <- withr::with_options( c(cli.width = 20), capt00(cat_rule("title")) ) exp <- rebox("── title ───────────") expect_equal(out, exp) }) test_that("cat_print", { out <- capt00(cat_print(boxx(""))) exp <- rebox( "┌──────┐", "│ │", "│ │", "│ │", "└──────┘") expect_equal(out, exp) tmp <- tempfile() on.exit(unlink(tmp), add = TRUE) expect_silent(cat_print(boxx(""), file = tmp)) expect_equal(paste(readLines(tmp, warn = FALSE), collapse = "\n"), exp) }) cli/tests/testthat/test-css.R0000644000176200001440000000701213565765747015744 0ustar liggesusers context("css parsing") test_that("parse_selector_node", { empty <- list(tag = character(), class = character(), id = character()) cases <- list( list("", empty), list("tag", list(tag = "tag")), list(".class", list(class = "class")), list("#id", list(id = "id")), list("tag.class", list(tag = "tag", class = "class")), list("tag.c1.c2.c3", list(tag = "tag", class = c("c1", "c2", "c3"))), list("tag#id", list(tag = "tag", id = "id")), list("tag#id.class", list(tag = "tag", class = "class", id = "id")), list("tag.class#id", list(tag = "tag", class = "class", id = "id")), list("#id.class", list(class = "class", id = "id")), list("#id.c1.c2", list(class = c("c1", "c2"), id = "id")), list("#id1#id2", list(id = c("id1", "id2"))) ) for (c in cases) { exp <- modifyList(empty, c[[2]]) expect_identical(parse_selector_node(c[[1]]), exp, info = c[[1]]) } }) test_that("parse_selector", { empty <- list(tag = character(), class = character(), id = character()) cases <- list( list("", list()), list("foo", list(list(tag = "foo"))), list("foo bar", list(list(tag = "foo"), list(tag = "bar"))), list("foo.c1 bar.c2", list(list(tag = "foo", class = "c1"), list(tag = "bar", class = "c2"))), list("#i1 tag #i2 .cl", list(list(id = "i1"), list(tag = "tag"), list(id = "i2"), list(class = "cl"))) ) for (c in cases) { exp <- lapply(c[[2]], function(x) modifyList(empty, x)) expect_identical(parse_selector(c[[1]]), exp, info = c[[1]]) } }) test_that("match_selector_node", { default <- list(tag = "mytag", class = character(), id = "myid") pos <- list( list("foo", list(tag = "foo", class = "class"), id = "id"), list(".class", list(tag = "foo", class = "class"), id = "id"), list("foo.class", list(tag = "foo", class = "class", id = "id")), list("#id", list(tag = "foo", class = "class", id = "id")), list(".c", list(class = c("c", "d", "e"))) ) for (c in pos) { sel <- parse_selector_node(c[[1]]) cnt <- modifyList(default, c[[2]]) expect_true(match_selector_node(sel, cnt), info = c[[1]]) } neg <- list( list("foo", list()), list(".class", list()), list("#id", list()), list("foo.class", list()), list("foo.c1.c2", list(tag = "foo", class = c("c1", "c3"))) ) for (c in neg) { sel <- parse_selector_node(c[[1]]) cnt <- modifyList(default, c[[2]]) expect_false(match_selector_node(sel, cnt), info = c[[1]]) } }) test_that("match_selector", { default <- list(tag = "mytag", class = character(), id = "myid") pos <- list( list("foo bar", list(list(tag = "foo"), list(tag = "bar"))), list("bar", list(list(tag = "foo"), list(tag = "bar"))), list(".class", list(list(tag = "x"), list(class = "class"))), list(".c1", list(list(tag = "x"), list(class = "c"), list(class = "c1"))) ) for (c in pos) { sels <- parse_selector(c[[1]]) cnts <- lapply(c[[2]], function(x) modifyList(default, x)) expect_true(match_selector(sels, cnts), info = c[[1]]) } neg <- list( list("foo bar", list(list(tag = "foo"), list(tag = "ba"))), list("foo bar", list(list(tag = "foo"), list(class = "bar"))), list(".class", list(list(tag = "x"), list(class = "class1"))), list(".c1", list(list(tag = "x"), list(class = "c"))) ) for (c in neg) { sels <- parse_selector(c[[1]]) cnts <- lapply(c[[2]], function(x) modifyList(default, x)) expect_false(match_selector(sels, cnts), info = c[[1]]) } }) cli/tests/testthat/test-utils.R0000644000176200001440000000641613565765747016323 0ustar liggesusers context("utils") test_that("make_space", { expect_equal(make_space(0), "") expect_equal(make_space(1), " ") expect_equal(make_space(5), " ") }) test_that("apply_style", { expect_error( apply_style("text", raw(0)), "Not a colour name or ANSI style" ) }) test_that("viapply", { expect_equal( viapply(c("foo", "foobar"), length), vapply(c("foo", "foobar"), length, integer(1)) ) expect_equal( viapply(character(), length), vapply(character(), length, integer(1)) ) }) test_that("ruler", { out <- capt00(ruler(20)) exp <- rebox( "----+----1----+----2", "12345678901234567890" ) expect_equal(crayon::strip_style(out), exp) }) test_that("rpad", { expect_equal(rpad(character()), character()) expect_equal(rpad("foo"), "foo") expect_equal(rpad(c("foo", "foobar")), c("foo ", "foobar")) }) test_that("lpad", { expect_equal(lpad(character()), character()) expect_equal(lpad("foo"), "foo") expect_equal(lpad(c("foo", "foobar")), c(" foo", "foobar")) }) test_that("is_utf8_output", { mockery::stub( is_utf8_output, "l10n_info", list(MBCS = TRUE, `UTF-8` = TRUE, `Latin-1` = FALSE) ) withr::with_options( list(cli.unicode = NULL), expect_true(is_utf8_output()) ) mockery::stub( is_utf8_output, "l10n_info", list(MBCS = FALSE, `UTF-8` = FALSE, `Latin-1` = TRUE) ) withr::with_options( list(cli.unicode = NULL), expect_false(is_utf8_output()) ) }) test_that("is_latex_output", { mockery::stub(is_latex_output, "loadedNamespaces", "foobar") expect_false(is_latex_output()) mockery::stub(is_latex_output, "loadedNamespaces", "knitr") mockery::stub( is_latex_output, "get", function(x, ...) { if (x == "is_latex_output") { function() TRUE } else { base::get(x, ...) } } ) expect_true(is_latex_output()) }) test_that("dedent", { cases <- list( list("", 0, ""), list("", 1, ""), list("", 2, ""), list("x", 0, "x"), list("x", 1, "x"), list("x", 2, "x"), list("xx", 0, "xx"), list("xx", 1, "xx"), list("xx", 2, "xx"), list("foobar", 0, "foobar"), list("foobar", 1, "foobar"), list("foobar", 2, "foobar"), list(" ", 0, " "), list(" ", 1, ""), list(" ", 2, ""), list(" ", 0, " "), list(" ", 1, " "), list(" ", 2, ""), list(" x", 0, " x"), list(" x", 1, "x"), list(" x", 2, "x"), list(" x", 0, " x"), list(" x", 1, " x"), list(" x", 2, "x"), list(" x y", 3, "x y"), list(" x y", 4, "x y"), list(" x y", 5, "x y"), list(" x ", 3, "x "), list(" x ", 4, "x "), list(" x ", 5, "x ") ) for (c in cases) expect_identical(dedent(c[[1]], c[[2]]), c[[3]]) }) test_that("tail_na", { cases <- list( list(1:4, 4L), list(1, 1), list(double(), NA_real_), list(character(), NA_character_) ) for (i in seq_along(cases)) { c <- cases[[i]] expect_identical(tail_na(c[[1]]), c[[2]], info = i) } cases2 <- list( list(1:4, 2, 3:4), list(1, 2, c(NA_real_, 1)), list(double(), 2, c(NA_real_, NA_real_)), list(character(), 2, c(NA_character_, NA_character_)) ) for (i in seq_along(cases2)) { c <- cases2[[i]] expect_identical(tail_na(c[[1]], c[[2]]), c[[3]], info = i) } }) cli/tests/testthat/test-boxes.R0000644000176200001440000001404313565765747016276 0ustar liggesusers context("boxes") test_that("empty label", { out <- capt(boxx("")) exp <- rebox( "┌──────┐", "│ │", "│ │", "│ │", "└──────┘") expect_equal(out, exp) }) test_that("empty label 2", { out <- capt(boxx(character())) exp <- rebox( "┌──────┐", "│ │", "│ │", "└──────┘") expect_equal(out, exp) }) test_that("label", { out <- capt(boxx("label")) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ │", "└───────────┘") expect_equal(out, exp) }) test_that("label vector", { out <- capt(boxx(c("label", "l2"))) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ l2 │", "│ │", "└───────────┘") expect_equal(out, exp) }) test_that("border style", { out <- capt(boxx("label", border_style = "classic")) exp <- rebox( "+-----------+", "| |", "| label |", "| |", "+-----------+") expect_equal(out, exp) }) test_that("padding", { out <- capt(boxx("label", padding = 2)) exp <- rebox( "┌─────────────────┐", "│ │", "│ │", "│ label │", "│ │", "│ │", "└─────────────────┘") expect_equal(out, exp) out <- capt(boxx("label", padding = c(1,2,1,2))) exp <- rebox( "┌─────────┐", "│ │", "│ label │", "│ │", "└─────────┘") expect_equal(out, exp) out <- capt(boxx("label", padding = c(1,2,0,2))) exp <- rebox( "┌─────────┐", "│ label │", "│ │", "└─────────┘") expect_equal(out, exp) out <- capt(boxx("label", padding = c(1,2,0,0))) exp <- rebox( "┌───────┐", "│ label│", "│ │", "└───────┘") expect_equal(out, exp) }) test_that("margin", { out <- capt(boxx("label", margin = 1)) exp <- rebox("", " ┌───────────┐", " │ │", " │ label │", " │ │", " └───────────┘", "") expect_equal(out, exp) out <- capt(boxx("label", margin = c(1,2,3,4))) exp <- rebox("", "", "", " ┌───────────┐", " │ │", " │ label │", " │ │", " └───────────┘", "") expect_equal(out, exp) out <- capt(boxx("label", margin = c(0,1,2,0))) exp <- rebox("", "", " ┌───────────┐", " │ │", " │ label │", " │ │", " └───────────┘") expect_equal(out, exp) }) test_that("float", { out <- capt(boxx("label", float = "center", width = 20)) exp <- rebox( " ┌───────────┐", " │ │", " │ label │", " │ │", " └───────────┘") expect_equal(out, exp) out <- capt(boxx("label", float = "right", width = 20)) exp <- rebox( " ┌───────────┐", " │ │", " │ label │", " │ │", " └───────────┘") expect_equal(out, exp) }) test_that("background_col", { withr::with_options( list(crayon.enabled = TRUE, crayon.colors = 256), { bx <- boxx("label", background_col = "red") expect_true(crayon::has_style(bx)) out <- capt_cat(crayon::strip_style(unclass(bx))) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ │", "└───────────┘") expect_equal(out, exp) bx <- boxx("label", background_col = col_red) expect_true(crayon::has_style(bx)) out <- capt_cat(crayon::strip_style(unclass(bx))) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ │", "└───────────┘") expect_equal(out, exp) } ) }) test_that("border_col", { withr::with_options( list(crayon.enabled = TRUE, crayon.colors = 256), { bx <- boxx("label", border_col = "red") expect_true(crayon::has_style(bx)) out <- capt_cat(crayon::strip_style(unclass(bx))) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ │", "└───────────┘") expect_equal(out, exp) bx <- boxx("label", border_col = col_red) expect_true(crayon::has_style(bx)) out <- capt_cat(crayon::strip_style(unclass(bx))) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ │", "└───────────┘") expect_equal(out, exp) } ) }) test_that("align", { out <- capt(boxx(c("label", "l2"), align = "center")) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ l2 │", "│ │", "└───────────┘") expect_equal(out, exp) out <- capt(boxx(c("label", "l2"), align = "right")) exp <- rebox( "┌───────────┐", "│ │", "│ label │", "│ l2 │", "│ │", "└───────────┘") expect_equal(out, exp) }) cli/tests/testthat/test-themes.R0000644000176200001440000000351013565765747016440 0ustar liggesusers context("cli themes") setup(start_app()) teardown(stop_app()) test_that("add/remove/list themes", { id <- default_app()$add_theme(list(".green" = list(color = "green"))) on.exit(default_app()$remove_theme(id), add = TRUE) expect_true(id %in% names(default_app()$list_themes())) withr::with_options(list(crayon.enabled = TRUE, crayon.colors = 256), { capt0(cli_par(class = "green")) out <- capt0(cli_text(lorem_ipsum())) capt0(cli_end()) expect_true(grepl(start(crayon::make_style("green")), out, fixed = TRUE)) }) default_app()$remove_theme(id) expect_false(id %in% names(default_app()$list_themes())) }) test_that("default theme is valid", { expect_error({ id <- default_app()$add_theme(builtin_theme()) default_app()$remove_theme(id) }, NA) }) test_that("explicit formatter is used, and combined", { id <- default_app()$add_theme(list( "span.emph" = list( fmt = function(x) paste0("(((", x, ")))"), before = "<<", after = ">>") )) on.exit(default_app()$remove_theme(id), add = TRUE) out <- capt0(cli_text("this is {.emph it}, really")) expect_match(crayon::strip_style(out), "(((<>)))", fixed = TRUE) }) test_that("simple theme", { def <- simple_theme() expect_true(is.list(def)) expect_false(is.null(names(def))) expect_true(all(names(def) != "")) expect_true(all(vlapply(def, is.list))) }) test_that("user's override", { custom <- list(".alert" = list(before = "custom:")) override <- list(".alert" = list(after = "override:")) start_app(theme = custom, .auto_close = FALSE) out <- capt0(cli_alert("Alert!")) expect_match(out, "custom:") stop_app() withr::with_options(list(cli.user_theme = override), { start_app(theme = custom, .auto_close = FALSE) out <- capt0(cli_alert("Alert!")) expect_match(out, "override:") stop_app() }) }) cli/tests/testthat/test-crayon-make.R0000644000176200001440000000223213565765747017361 0ustar liggesusers context("crayon make_ansi_style") test_that("make_style without name", { pink <- make_ansi_style("pink") expect_true(inherits(pink, "ansi_style")) }) test_that("hexa color regex works", { positive <- c("#000000", "#ffffff", "#0f0f0f", "#f0f0f0", "#00000000", "#ffffffff", "#0f0f0f00", "#f0f0f055") negative <- c("", "#12345", "123456", "1234567", "12345678", "#1234567", "#1234ffg", "#gggggx", "foo#123456", "foo#123456bar") hash_color_regex <- "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$" for (color in positive) { expect_true(grepl(hash_color_regex, color)) expect_true(grepl(hash_color_regex, toupper(color))) } for (color in negative) { expect_false(grepl(hash_color_regex, color)) expect_false(grepl(hash_color_regex, toupper(color))) } }) test_that("we fall back for ANSI 8 if needed", { yellow3 <- make_ansi_style("yellow3", colors = 8) expect_equal(yellow3("foobar"), col_yellow("foobar")) }) test_that("we can create a style from an R color", { red4 <- make_ansi_style("red4") red_text <- red4("text") expect_true(!crayon::has_color() || crayon::has_style(red_text)) }) cli/tests/testthat/test-inline.R0000644000176200001440000000457013573265276016427 0ustar liggesusers context("cli inline") setup(start_app()) teardown(stop_app()) test_that("inline classes", { classes <- c( "emph", "strong", "code", "pkg", "fun", "arg", "key", "file", "path", "email", "url", "var", "envvar") do <- function(class) { special_style <- structure( list( list(color = "cyan"), list(before = "<<<"), list(after =">>>")), names = c( paste0("span.", class), paste0("span.", class), paste0("span.", class) ) ) cli_div(theme = special_style) withr::with_options(list(crayon.enabled = TRUE, crayon.colors = 256), { txt <- glue::glue("This is {. it} really", .open = "<", .close = ">") out <- capt0(cli_text(txt)) expect_true(crayon::has_style(out)) expect_match(crayon::strip_style(out), "<<>>", info = class) expect_match(out, start(crayon::make_style("cyan")), fixed = TRUE, info = class) }) } lapply(classes, do) }) test_that("{{ and }} can be used for comments", { out <- capt0(cli_text("Escaping {{ works")) expect_equal(out, "Escaping { works\n") out <- capt0(cli_text("Escaping }} works")) expect_equal(out, "Escaping } works\n") out <- capt0(cli_text("Escaping {{ and }} works")) expect_equal(out, "Escaping { and } works\n") out <- capt0(cli_text("Escaping {{{{ works")) expect_equal(out, "Escaping {{ works\n") out <- capt0(cli_text("Escaping }}}} works")) expect_equal(out, "Escaping }} works\n") out <- capt0(cli_text("Escaping {{{{ and }} works")) expect_equal(out, "Escaping {{ and } works\n") out <- capt0(cli_text("Escaping {{{{ and }}}} works")) expect_equal(out, "Escaping {{ and }} works\n") out <- capt0(cli_text("Escaping {{ and }}}} works")) expect_equal(out, "Escaping { and }} works\n") }) test_that("no glue substitution in expressions that evaluate to a string", { msg <- "Message with special characters like } { }} {{" out <- capt0(cli_text("{msg}")) expect_equal(out, paste0(msg, "\n")) out <- capt0(cli_text("{.emph {msg}}"), strip_style = TRUE) expect_equal(out, paste0(msg, "\n")) }) test_that("S3 class is used for styling", { cli_div(theme = list( body = list("class-map" = list("foo" = "bar")), ".bar" = list(before = "::")) ) obj <- structure("yep", class = "foo") out <- capt0(cli_text("This is {obj}.")) expect_match(out, "::yep") }) cli/tests/testthat/test.R0000644000176200001440000000010713565765747015154 0ustar liggesusers context("boxes") test_that("boxes works", { expect_true(TRUE) }) cli/tests/testthat.R0000644000176200001440000000126513565765747014203 0ustar liggesuserslibrary(testthat) library(cli) ## Run the tests in fancy mode and non-fancy mode as well ## Also, run them in latin1 encoding as well, this is for Unix, ## because Windows encoding names are different. withr::with_options( list(cli.unicode = FALSE), test_check("cli") ) withr::with_options( list(cli.unicode = TRUE), test_check("cli") ) has_locale <- function(l) { has <- TRUE tryCatch( withr::with_locale(c(LC_CTYPE = l), "foobar"), warning = function(w) has <<- FALSE, error = function(e) has <<- FALSE ) has } if (l10n_info()$`UTF-8` && has_locale("en_US.ISO8859-1")) { withr::with_locale( c(LC_CTYPE = "en_US.ISO8859-1"), test_check("cli") ) } cli/vignettes/0000755000176200001440000000000013605174462013041 5ustar liggesuserscli/vignettes/pluralization.Rmd0000644000176200001440000000046213572435701016403 0ustar liggesusers--- title: "CLI pluralization" author: "Gábor Csárdi" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: keep_md: true vignette: > %\VignetteIndexEntry{CLI pluralization} %\VignetteEngine{cli::lazyrmd} %\VignetteEncoding{UTF-8} --- ```{r child = "../man/chunks/pluralization.Rmd"} ``` cli/vignettes/pluralization.md0000644000176200001440000001407113604652603016260 0ustar liggesusers--- title: "CLI pluralization" author: "Gábor Csárdi" date: "2019-12-06" output: rmarkdown::html_vignette: keep_md: true vignette: > %\VignetteIndexEntry{CLI pluralization} %\VignetteEngine{cli::lazyrmd} %\VignetteEncoding{UTF-8} --- # Introduction cli has tools to create messages that are printed correctly in singular and plural forms. This usually requires minimal extra work, and increases the quality of the messages greatly. In this document we first show some pluralization examples that you can use as guidelines. Hopefully these are intuitive enough, so that they can be used without knowing the exact cli pluralization rules. # Examples ## Pluralization markup In the simplest case the message contains a single `{}` glue substitution, which specifies the quantity that is used to select between the singular and plural forms. Pluralization uses markup that is similar to glue, but uses the `{?` and `}` delimiters: ```r library(cli) nfile <- 0; cli_text("Found {nfile} file{?s}.") ``` ``` #> Found 0 files. ``` ```r nfile <- 1; cli_text("Found {nfile} file{?s}.") ``` ``` #> Found 1 file. ``` ```r nfile <- 2; cli_text("Found {nfile} file{?s}.") ``` ``` #> Found 2 files. ``` Here the value of `nfile` is used to decide whether the singular or plural form of `file` is used. This is the most common case for English messages. ## Irregular plurals If the plural form is more difficult than a simple `s` suffix, then the singular and plural forms can be given, separated with a forward slash: ```r ndir <- 1; cli_text("Found {ndir} director{?y/ies}.") ``` ``` #> Found 1 directory. ``` ```r ndir <- 5; cli_text("Found {ndir} director{?y/ies}.") ``` ``` #> Found 5 directories. ``` ## Use "no" instead of zero For readability, it is better to use the `no()` helper function to include a count in a message. `no()` prints the word "no" if the count is zero, and prints the numeric count otherwise: ```r nfile <- 0; cli_text("Found {no(nfile)} file{?s}.") ``` ``` #> Found no files. ``` ```r nfile <- 1; cli_text("Found {no(nfile)} file{?s}.") ``` ``` #> Found 1 file. ``` ```r nfile <- 2; cli_text("Found {no(nfile)} file{?s}.") ``` ``` #> Found 2 files. ``` ## Use the length of character vectors With the auto-collapsing feature of cli it is easy to include a list of objects in a message. When cli interprets a character vector as a pluralization quantity, it takes the length of the vector: ```r pkgs <- "pkg1" cli_text("Will remove the {.pkg {pkgs}} package{?s}.") ``` ``` #> Will remove the pkg1 package. ``` ```r pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove the {.pkg {pkgs}} package{?s}.") ``` ``` #> Will remove the pkg1, pkg2 and pkg3 packages. ``` Note that the length is only used for non-numeric vectors (when `is.numeric(x)` return `FALSE`). If you want to use the length of a numeric vector, convert it to character via `as.character()`. You can combine collapsed vectors with "no", like this: ```r pkgs <- character() cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.") ``` ``` #> Will remove no packages. ``` ```r pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.") ``` ``` #> Will remove the pkg1, pkg2 and pkg3 packages. ``` When the pluralization markup contains three alternatives, like above, the first one is used for zero, the second for one, and the third one for larger quantities. ## Choosing the right quantity When the text contains multiple glue `{}` substitutions, the one right before the pluralization markup is used. For example: ```r nfiles <- 3; ndirs <- 1 cli_text("Found {nfiles} file{?s} and {ndirs} director{?y/ies}") ``` ``` #> Found 3 files and 1 directory ``` This is sometimes not the the correct one. You can explicitly specify the correct quantity using the `qty()` function. This sets that quantity without printing anything: ```r nupd <- 3; ntotal <- 10 cli_text("{nupd}/{ntotal} {qty(nupd)} file{?s} {?needs/need} updates") ``` ``` #> 3/10 files need updates ``` Note that if the message only contains a single `{}` substitution, then this may appear before or after the pluralization markup. If the message contains multiple `{}` substitutions _after_ pluralization markup, an error is thrown. Similarly, if the message contains no `{}` substituions at all, but has pluralization markup, and error is thrown. # Rules The exact rules of cli's pluralization. There are two sets of rules. The first set specifies how a quantity is associated with a `{?}` pluralization markup. The second set describes how the `{?}` is parsed and interpreted. ## Quantities 1. `{}` substitutions define quantities. If the value of a `{}` substitution is numeric (i.e. `is.numeric(x)` holds), then it has to have length one to define a quantity. This is only enforced if the `{}` substitution is used for pluralization. The quantity is defined as the value of `{}` then, rounded with `as.integer()`. If the value of `{}` is not numeric, then its quantity is defined as its length. 1. If a message has `{?}` markup but no `{}` substitution, an error is thrown. 1. If a message has exactly one `{}` substitution, its value is used as the pluralization quantity for all `{?}` markup in the message. 1. If a message has multiple `{}` substitutions, then for each `{?}` markup cli uses the quantity of the `{}` substitution that precedes it. 1. If a message has multiple `{}` substitutions and has pluralization markup with a preceding `{}` substitution, and error is thrown. ## Pluralization markup 1. Pluralization markup start with `{?` and ends with `}`. It may not contain `{` and `}` characters, so it may not contains `{}` substitutions either. 1. Alternative words or suffixes are separated by `/`. 1. If there is a single alternative, then _nothing_ is used if `quantity == 1` and this single alternative is used if `quantity != 1`. 1. If there are two alternatives, the first one is used for `quantity == 1`, the second one for `quantity != 1` (include 0). 1. If there are three alternatives, the first one is used for `quantity == 0`, the second for `quantity == 1`, and the third otherwise. cli/R/0000755000176200001440000000000013605174414011227 5ustar liggesuserscli/R/cli.R0000644000176200001440000003741013573667362012143 0ustar liggesusers #' CLI text #' #' It is wrapped to the screen width automatically. It may contain inline #' markup. (See [inline-markup].) #' #' @param ... The text to show, in character vectors. They will be #' concatenated into a single string. Newlines are _not_ preserved. #' @param .envir Environment to evaluate the glue expressions in. #' #' @export #' @examples #' cli_text("Hello world!") #' cli_text(packageDescription("cli")$Description) #' #' ## Arguments are concatenated #' cli_text("this", "that") #' #' ## Command substitution #' greeting <- "Hello" #' subject <- "world" #' cli_text("{greeting} {subject}!") #' #' ## Inline theming #' cli_text("The {.fn cli_text} function in the {.pkg cli} package") #' #' ## Use within container elements #' ul <- cli_ul() #' cli_li() #' cli_text("{.emph First} item") #' cli_li() #' cli_text("{.emph Second} item") #' cli_end(ul) cli_text <- function(..., .envir = parent.frame()) { cli__message("text", list(text = glue_cmd(..., .envir = .envir))) } #' CLI verbatim text #' #' It is not wrapped, but printed as is. #' #' @param ... The text to show, in character vectors. Each element is #' printed on a new line. #' @param .envir Environment to evaluate the glue expressions in. #' #' @export #' @examples #' cli_verbatim("This has\nthree", "lines") cli_verbatim <- function(..., .envir = parent.frame()) { cli__message("verbatim", c(list(...), list(.envir = .envir))) } #' CLI headings #' #' @param text Text of the heading. It can contain inline markup. #' @param id Id of the heading element, string. It can be used in themes. #' @param class Class of the heading element, string. It can be used in #' themes. #' @param .envir Environment to evaluate the glue expressions in. #' #' @export #' @examples #' cli_h1("Main title") #' cli_h2("Subtitle") #' cli_text("And some regular text....") cli_h1 <- function(text, id = NULL, class = NULL, .envir = parent.frame()) { cli__message("h1", list(text = glue_cmd(text, .envir = .envir), id = id, class = class)) } #' @rdname cli_h1 #' @export cli_h2 <- function(text, id = NULL, class = NULL, .envir = parent.frame()) { cli__message("h2", list(text = glue_cmd(text, .envir = .envir), id = id, class = class)) } #' @rdname cli_h1 #' @export cli_h3 <- function(text, id = NULL, class = NULL, .envir = parent.frame()) { cli__message("h3", list(text = glue_cmd(text, .envir = .envir), id = id, class = class)) } #' Generic CLI container #' #' See [containers]. A `cli_div` container is special, because it may #' add new themes, that are valid within the container. #' #' @param id Element id, a string. If `NULL`, then a new id is generated #' and returned. #' @param class Class name, sting. Can be used in themes. #' @param theme A custom theme for the container. See [themes]. #' @param .auto_close Whether to close the container, when the calling #' function finishes (or `.envir` is removed, if specified). #' @param .envir Environment to evaluate the glue expressions in. It is #' also used to auto-close the container if `.auto_close` is `TRUE`. #' @return The id of the new container element, invisibly. #' #' @export #' @examples #' ## div with custom theme #' d <- cli_div(theme = list(h1 = list(color = "blue", #' "font-weight" = "bold"))) #' cli_h1("Custom title") #' cli_end(d) #' #' ## Close automatically #' div <- function() { #' cli_div(class = "tmp", theme = list(.tmp = list(color = "yellow"))) #' cli_text("This is yellow") #' } #' div() #' cli_text("This is not yellow any more") cli_div <- function(id = NULL, class = NULL, theme = NULL, .auto_close = TRUE, .envir = parent.frame()) { cli__message("div", list(id = id, class = class, theme = theme), .auto_close = .auto_close, .envir = .envir) } #' CLI paragraph #' #' See [containers]. #' #' @param id Element id, a string. If `NULL`, then a new id is generated #' and returned. #' @param class Class name, sting. Can be used in themes. #' @inheritParams cli_div #' @return The id of the new container element, invisibly. #' #' @export #' @examples #' id <- cli_par() #' cli_text("First paragraph") #' cli_end(id) #' id <- cli_par() #' cli_text("Second paragraph") #' cli_end(id) cli_par <- function(id = NULL, class = NULL, .auto_close = TRUE, .envir = parent.frame()) { cli__message("par", list(id = id, class = class), .auto_close = .auto_close, .envir = .envir) } #' Close a CLI container #' #' @param id Id of the container to close. If missing, the current #' container is closed, if any. #' #' @export #' @examples #' ## If id is omitted #' cli_par() #' cli_text("First paragraph") #' cli_end() #' cli_par() #' cli_text("Second paragraph") #' cli_end() cli_end <- function(id = NULL) { cli__message("end", list(id = id %||% NA_character_)) } #' Unordered CLI list #' #' An unordered list is a container, see [containers]. #' #' @param items If not `NULL`, then a character vector. Each element of #' the vector will be one list item, and the list container will be #' closed by default (see the `.close` argument). #' @param id Id of the list container. Can be used for closing it with #' [cli_end()] or in themes. If `NULL`, then an id is generated and #' retuned invisibly. #' @param class Class of the list container. Can be used in themes. #' @param .close Whether to close the list container if the `items` were #' specified. If `FALSE` then new items can be added to the list. #' @inheritParams cli_div #' @return The id of the new container element, invisibly. #' #' @export #' @examples #' ## Specifying the items at the beginning #' cli_ul(c("one", "two", "three")) #' #' ## Adding items one by one #' cli_ul() #' cli_li("one") #' cli_li("two") #' cli_li("three") #' cli_end() #' #' ## Complex item, added gradually. #' cli_ul() #' cli_li() #' cli_verbatim("Beginning of the {.emph first} item") #' cli_text("Still the first item") #' cli_end() #' cli_li("Second item") #' cli_end() cli_ul <- function(items = NULL, id = NULL, class = NULL, .close = TRUE, .auto_close = TRUE, .envir = parent.frame()) { cli__message( "ul", list( items = lapply(items, glue_cmd, .envir = .envir), id = id, class = class, .close = .close), .auto_close = .auto_close, .envir = .envir) } #' Ordered CLI list #' #' An ordered list is a container, see [containers]. #' #' @inheritParams cli_ul #' @return The id of the new container element, invisibly. #' #' @export #' @examples #' ## Specifying the items at the beginning #' cli_ol(c("one", "two", "three")) #' #' ## Adding items one by one #' cli_ol() #' cli_li("one") #' cli_li("two") #' cli_li("three") #' cli_end() #' #' ## Nested lists #' cli_div(theme = list(ol = list("margin-left" = 2))) #' cli_ul() #' cli_li("one") #' cli_ol(c("foo", "bar", "foobar")) #' cli_li("two") #' cli_end() #' cli_end() cli_ol <- function(items = NULL, id = NULL, class = NULL, .close = TRUE, .auto_close = TRUE, .envir = parent.frame()) { cli__message( "ol", list( items = lapply(items, glue_cmd, .envir = .envir), id = id, class = class, .close = .close), .auto_close = .auto_close, .envir = .envir) } #' Definition list #' #' A definition list is a container, see [containers]. #' #' @param items Named character vector, or `NULL`. If not `NULL`, they #' are used as list items. #' @inheritParams cli_ul #' @return The id of the new container element, invisibly. #' #' @export #' @examples #' ## Specifying the items at the beginning #' cli_dl(c(foo = "one", bar = "two", baz = "three")) #' #' ## Adding items one by one #' cli_dl() #' cli_li(c(foo = "one")) #' cli_li(c(bar = "two")) #' cli_li(c(baz = "three")) #' cli_end() cli_dl <- function(items = NULL, id = NULL, class = NULL, .close = TRUE, .auto_close = TRUE, .envir = parent.frame()) { cli__message( "dl", list( items = lapply(items, glue_cmd, .envir = .envir), id = id, class = class, .close = .close), .auto_close = .auto_close, .envir = .envir) } #' CLI list item(s) #' #' A list item is a container, see [containers]. #' #' @param items Character vector of items, or `NULL`. #' @param id Id of the new container. Can be used for closing it with #' [cli_end()] or in themes. If `NULL`, then an id is generated and #' retuned invisibly. #' @param class Class of the item container. Can be used in themes. #' @inheritParams cli_div #' @return The id of the new container element, invisibly. #' #' @export #' @examples #' ## Adding items one by one #' cli_ul() #' cli_li("one") #' cli_li("two") #' cli_li("three") #' cli_end() #' #' ## Complex item, added gradually. #' cli_ul() #' cli_li() #' cli_verbatim("Beginning of the {.emph first} item") #' cli_text("Still the first item") #' cli_end() #' cli_li("Second item") #' cli_end() cli_li <- function(items = NULL, id = NULL, class = NULL, .auto_close = TRUE, .envir = parent.frame()) { cli__message( "li", list( items = lapply(items, glue_cmd, .envir = .envir), id = id, class = class), .auto_close = .auto_close, .envir = .envir) } #' CLI alerts #' #' Alerts are typically short status messages. #' #' @param text Text of the alert. #' @param id Id of the alert element. Can be used in themes. #' @param class Class of the alert element. Can be used in themes. #' @param wrap Whether to auto-wrap the text of the alert. #' @param .envir Environment to evaluate the glue expressions in. #' #' @export #' @examples #' #' cli_alert("Cannot lock package library.") #' cli_alert_success("Package {.pkg cli} installed successfully.") #' cli_alert_danger("Could not download {.pkg cli}.") #' cli_alert_warning("Internet seems to be unreacheable.") #' cli_alert_info("Downloaded 1.45MiB of data") cli_alert <- function(text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame()) { cli__message("alert", list(text = glue_cmd(text, .envir = .envir), id = id, class = class, wrap = wrap)) } #' @rdname cli_alert #' @export cli_alert_success <- function(text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame()) { cli__message("alert_success", list(text = glue_cmd(text, .envir = .envir), id = id, class = class, wrap = wrap)) } #' @rdname cli_alert #' @export cli_alert_danger <- function(text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame()) { cli__message("alert_danger", list(text = glue_cmd(text, .envir = .envir), id = id, class = class, wrap = wrap)) } #' @rdname cli_alert #' @export cli_alert_warning <- function(text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame()) { cli__message("alert_warning", list(text = glue_cmd(text, .envir = .envir), id = id, class = class, wrap = wrap)) } #' @rdname cli_alert #' @export cli_alert_info <- function(text, id = NULL, class = NULL, wrap = FALSE, .envir = parent.frame()) { cli__message("alert_info", list(text = glue_cmd(text, .envir = .envir), id = id, class = class, wrap = wrap)) } #' CLI horizontal rule #' #' It can be used to separate parts of the output. The line style of the #' rule can be changed via the the `line-type` property. Possible values #' are: #' #' * `"single"`: (same as `1`), a single line, #' * `"double"`: (same as `2`), a double line, #' * `"bar1"`, `"bar2"`, `"bar3"`, etc., `"bar8"` uses varying height bars. #' #' Colors and background colors can similarly changed via a theme, see #' examples below. #' #' @param .envir Environment to evaluate the glue expressions in. #' @inheritParams rule #' @inheritParams cli_div #' #' @export #' @examples #' cli_rule() #' cli_text(packageDescription("cli")$Description) #' cli_rule() #' #' # Theming #' d <- cli_div(theme = list(rule = list( #' color = "blue", #' "background-color" = "darkgrey", #' "line-type" = "double"))) #' cli_rule("Left", right = "Right") #' cli_end(d) #' #' # Interpolation #' cli_rule(left = "One plus one is {1+1}") #' cli_rule(left = "Package {.pkg mypackage}") cli_rule <- function(left = "", center = "", right = "", id = NULL, .envir = parent.frame()) { cli__message("rule", list(left = glue_cmd(left, .envir = .envir), center = glue_cmd(center, .envir = .envir), right = glue_cmd(right, .envir = .envir), id = id)) } #' CLI block quote #' #' A section that is quoted from another source. It is typically indented. #' #' @export #' @param quote Text of the quotation. #' @param citation Source of the quotation, typically a link or the name #' of a person. #' @inheritParams cli_div #' @examples #' cli_blockquote(cli:::lorem_ipsum(), citation = "Nobody, ever") cli_blockquote <- function(quote, citation = NULL, id = NULL, class = NULL, .envir = parent.frame()) { cli__message( "blockquote", list( quote = glue_cmd(quote, .envir = .envir), citation = glue_cmd(citation, .envir = .envir), id = id, class = class ) ) } #' A block of code #' #' A helper function that creates a `div` with class `code` and then calls #' `cli_verbatim()` to output code lines. The builtin theme formats these #' containers specially. In particular, it adds syntax highlighting to #' valid R code. #' #' @param lines Chracter vector, each line will be a line of code, and #' newline charactes also create new lines. Note that _no_ glue #' substitution is performed on the code. #' @param ... More character vectors, they are appended to `lines`. #' @param language Programming language. This is also added as a class, #' in addition to `code`. #' @param .auto_close Passed to `cli_div()` when creating the container of #' the code. By default the code container is closed after emitting #' `lines` and `...` via `cli_verbatim()`. You can keep that container #' open with `.auto_close` and/or `.envir`, and then calling #' `cli_verbatim()` to add (more) code. Note that the code will be #' formatted and syntax highlighted separately for each `cli_verbatim()` #' call. #' @param .envir Passed to `cli_div()` when creating the container of the #' code. #' @return The id of the container that contains the code. #' #' @export #' @examples #' cli_code(format(cli::cli_blockquote)) cli_code <- function(lines = NULL, ..., language = "R", .auto_close = TRUE, .envir = environment()) { lines <- c(lines, unlist(list(...))) id <- cli_div( class = paste("code", language), .auto_close = .auto_close, .envir = .envir ) cli_verbatim(lines) invisible(id) } cli__message <- function(type, args, .auto_close = TRUE, .envir = NULL) { if ("id" %in% names(args) && is.null(args$id)) args$id <- new_uuid() if (.auto_close && !is.null(.envir) && !identical(.envir, .GlobalEnv)) { if (type == "status") { defer(cli_status_clear(id = args$id, result = args$auto_result), envir = .envir, priority = "first") } else { defer(cli_end(id = args$id), envir = .envir, priority = "first") } } cond <- list(message = paste("cli message", type), type = type, args = args, pid = clienv$pid) class(cond) <- c("cli_message", "callr_message", "condition") withRestarts( { signalCondition(cond) cli__default_handler(cond) }, callr_message_handled = function() NULL, cli_message_handled = function() NULL) invisible(args$id) } cli__default_handler <- function(msg) { custom_handler <- getOption("cli.default_handler") if (is.function(custom_handler)) { custom_handler(msg) } else { cli_server_default(msg) } } cli/R/print.R0000644000176200001440000000516013565765747012534 0ustar liggesusers #' Create a format method for an object using cli tools #' #' This method can be typically used in `format()` S3 methods. Then the #' `print()` method of the class can be easily defined in terms of such a #' `format()` method. See examples below. #' #' @param expr Expression that calls `cli_*` methods, [base::cat()] or #' [base::print()] to format an object's printout. #' @param theme Theme to use for the formatting. #' @return Character vector, one element for each line of the printout. #' #' @export #' @examples #' #' # Let's create format and print methods for a new S3 class that #' # represents the an installed R package: `r_package` #' #' # An `r_package` will contain the DESCRIPTION metadata of the package #' # and also its installation path. #' new_r_package <- function(pkg) { #' tryCatch( #' desc <- packageDescription(pkg), #' warning = function(e) stop("Cannot find R package `", pkg, "`") #' ) #' file <- dirname(attr(desc, "file")) #' if (basename(file) != pkg) file <- dirname(file) #' structure( #' list(desc = unclass(desc), lib = dirname(file)), #' class = "r_package" #' ) #' } #' #' format.r_package <- function(x, ...) { #' cli_format_method({ #' cli_h1("{.pkg {x$desc$Package}} {cli::symbol$line} {x$desc$Title}") #' cli_text("{x$desc$Description}") #' cli_ul(c( #' "Version: {x$desc$Version}", #' if (!is.null(x$desc$Maintainer)) "Maintainer: {x$desc$Maintainer}", #' "License: {x$desc$License}" #' )) #' if (!is.na(x$desc$URL)) cli_text("See more at {.url {x$desc$URL}}") #' }) #' } #' #' # Now the print method is easy: #' print.r_package <- function(x, ...) { #' cat(format(x, ...), sep = "\n") #' } #' #' # Try it out #' new_r_package("cli") #' #' # The formatting of the output depends on the current theme: #' opt <- options(cli.theme = simple_theme()) #' print(new_r_package("cli")) #' options(opt) # <- restore theme cli_format_method <- function(expr, theme = getOption("cli.theme")) { # This is not needed for cli, but needed for sink() and crayon opts <- options( crayon.enabled = crayon::has_color(), crayon.colors = crayon::num_colors() ) on.exit(options(opts), add = TRUE) # Redirect everything to the connection con <- textConnection(NULL, open = "w", local = TRUE, encoding = "bytes") sink(con) on.exit(sink(NULL), add = TRUE) on.exit(close(con), add = TRUE) start_app(theme = theme, output = con) # Run the code withCallingHandlers( expr, cli_message = function(msg) { cli_server_default(msg) invokeRestart("cli_message_handled") } ) # Collect the output textConnectionValue(con) } cli/R/lazyrmd.R0000644000176200001440000001635013571552620013042 0ustar liggesusers # # Standalone file for controlling when and where to build vignettes ---- # # The canonical location of this file is in the asciicast package: # https://github.com/r-lib/asciicast/master/R/lazyrmd.R # # This standalone file provides a vignette builder that gives you more # control about when, where and how the vignettes of an R package will be # built. Possible use cases: # * do not rebuild vignettes on CRAN # * do not rebuild vignettes on a CI if the build time dependencies are # not available. # * avoid rebuilding long running vignettes. # * fully static vignettes that never rebuild. # * etc. # # ## API # # ``` # onload_hook(local = "if-newer", ci = TRUE, cran = "no-code") # build(mode = c("force", "if-newer")) # ``` # # ## Usage # # To use this standalone in your package, do the following: # 1. Copy this file into your package, without changes. # 2. Call `lazyrmd$onload_hook()` from the `.onLoad()` function of your # package. For example: # ``` # .onLoad <- function(libname, pkgname) { # lazyrmd$onload_hook( # local = FALSE, # ci = function() is_recording_supported(), # cran = "no-code" # ) # } # ``` # 3. Add the package itself as a vignette builder, in `DESCRIPTION`. # 4. Also in `DESCRIPTION`, add knitr and rmarkdown to `Suggests`, if you # want to install them automatically on the CI, and/or you want to # build vignettes in CRAN. # 5. Change the builder of the vignettes that you want to build lazily, # in the YAML header. E.g.: # ``` # vignette: > # %\VignetteIndexEntry{asciicast example vignette} # %\VignetteEngine{asciicast::lazyrmd} # %\VignetteEncoding{UTF-8} # ``` # # ## NEWS: # # ### 1.0.0 -- 2019-12-01 # # * First release. lazyrmd <- local({ # config --------------------------------------------------------------- # Need to do this before we mess up the environment package_name <- utils::packageName() lazy_env <- environment() parent.env(lazy_env) <- baseenv() re_lazy_rmd <- "[.]Rmd$" config = list() # API ------------------------------------------------------------------ #' Configure the lazy vignette builder #' #' @noRd #' @param local Whether/when to build vignettes for local builds. #' @param ci Whether/when to build vignettes on the CI(s). #' @param cran Whether/when to build vignettes on CRAN. #' #' Possible values for `local`, `ci` and `cran` are: #' * `TRUE`: always (re)build the vignettes. #' * `FALSE`: never (re)build the vignettes. #' * `"if-newer"`: Only re(build) the vignettes if the Rmd file is newer. #' * `"no-code"`: never rebuild the vignettes, and make sure that the #' `.R` file only contains dummy code. (This is to make #' sure that the `.R` file runs on CRAN.) #' * a function object. This is called without arguments and if it #' returns an `isTRUE()` value, then the vignette is built. onload_hook <- function(local = "if-newer", ci = TRUE, cran = "no-code") { config <<- list(local = local, ci = ci, cran = cran, forced = NULL) tools::vignetteEngine( "lazyrmd", weave = lazy_weave, tangle = lazy_tangle, pattern = re_lazy_rmd, package = package_name ) } #' Build vignettes #' #' This is a utility function, and you would probably not use it in the #' package code itself. It can force the re-building of all vignettes. #' This function ignores the vignette configuration that was created with #' `onload_hook()`. #' #' @param mode `"force"` forces vignette re-building, even if the `.Rmd` #' file is older than the output file(s). `"if-newer"` will only #' re-build if the `.Rmd` file is newer. #' @return The return value of [tools::buildVignettes()]. build <- function(mode = c("force", "if-newer")) { mode <- match.arg(mode) config$forced <<- if (mode == "force") TRUE else mode env_save <- Sys.getenv("LAZY_RMD_FORCED", NA_character_) if (is.na(env_save)) { on.exit(Sys.unsetenv("LAZY_RMD_FORCED"), add = TRUE) } else { on.exit(Sys.setenv(LAZY_RMD_FORCED = env_save), add = TRUE) } Sys.setenv(LAZY_RMD_FORCED = "true") tools::buildVignettes(dir = ".", tangle = TRUE) } # internals ------------------------------------------------------------ #' This is called by [tools::buildVignettes], as the weave function of #' the lazyrmd vignette engine lazy_weave <- function(file, ...) { output <- sub(re_lazy_rmd, ".html", file) env <- detect_env() conf <- config[[env]] if (! should_build(file, output, conf)) { create_if_needed(output, conf) return(output) } weave(file, ...) } weave <- function(file, driver, syntax, encoding = "UTF-8", quiet = TRUE, ...) { opts <- options(markdown.HTML.header = NULL) on.exit({ knitr::opts_chunk$restore() knitr::knit_hooks$restore() options(opts) }, add = TRUE) knitr::opts_chunk$set(error = FALSE) # This will create the `.R` file as well. Then it might get overwritten # by a tangle step, if any. Typically it won't because it will be # newer than the `.Rmd` file. knitr::knit_hooks$set(purl = knitr::hook_purl) rmarkdown::render( file, encoding = encoding, quiet = quiet, envir = globalenv(), ... ) } #' This is called by [tools::buildVignettes], as the tangle function of #' the lazyrmd vignette engine lazy_tangle <- function(file, ...) { output <- sub(re_lazy_rmd, ".R", file) env <- detect_env() conf <- config[[env]] if (! should_build(file, output, conf)) { create_if_needed(output, conf) return(output) } tangle(file, ...) } tangle <- function(file, ..., encoding = "UTF-8", quiet = FALSE) { output <- sub(re_lazy_rmd, ".R", file) knitr::purl(file, encoding = encoding, quiet = quiet, ...) } is_ci <- function() { isTRUE(as.logical(Sys.getenv("CI"))) } #' If NOT_CRAN is set to a true value, then we are local #' Otherwise we are local if not running inside `R CMD check`. is_local <- function() { if (isTRUE(as.logical(Sys.getenv("NOT_CRAN")))) return(TRUE) Sys.getenv("_R_CHECK_PACKAGE_NAME_", "") == "" } detect_env <- function() { # Order matters here, is_local() == TRUE on the CI as well, usually if (!is.na(Sys.getenv("LAZY_RMD_FORCED", NA_character_))) return("forced") if (is_ci()) return("ci") if (is_local()) return("local") "cran" } should_build <- function(input, output, conf) { if (isTRUE(conf)) return(TRUE) if (identical(conf, FALSE)) return(FALSE) if (is.function(conf)) return(isTRUE(conf())) if (identical(conf, "if-newer")) { return(!file.exists(output) || file_mtime(input) > file_mtime(output)) } FALSE } file_mtime <- function(...) { file.info(..., extra_cols = FALSE)$mtime } create_if_needed <- function(path, conf) { if (!file.exists(path)) file.create(path) if (identical(conf, "no-code")) { code_file <- sub("\\.html$", ".R", path) cat("# Dummy file for static vignette\n", file = code_file) } Sys.setFileTime(path, Sys.time()) } structure( list( .internal = lazy_env, onload_hook = onload_hook, build = build ), class = c("standalone_lazyrmd", "standalone") ) }) cli/R/sysdata.rda0000644000176200001440000000377413565765747013426 0ustar liggesusersBZh91AY&SY׫S@}`N@8_So6g 30FXF%V**\؜خnX@VAV5Z"fA+!Fh!4軖N,][̀oOQE #Eh0dYeQEjeQH!tF6 t)ئibh4';goѻn"R9†t"4vR n!B!B$m䇱x vcmxiw׻GQ ^+6h5 l:)„Xcli/R/inline.R0000644000176200001440000001126213573257522012640 0ustar liggesusers #' @importFrom utils globalVariables if (getRversion() >= "2.15.1") globalVariables("app") inline_generic <- function(app, class, x, style) { xx <- paste0(style$before, x, style$after) if (!is.null(style$fmt)) xx <- vcapply(xx, style$fmt) xx } inline_collapse <- function(x) { glue_collapse(x, sep = ", ", last = " and ") } #' @importFrom glue glue glue_collapse inline_transformer <- local({ inline_styling <- FALSE transform_hook <- function(x, ...) x style <- list() function(code, envir) { failed <- FALSE res <- suppressWarnings(tryCatch({ expr <- parse(text = code, keep.source = FALSE) transform_hook(eval(expr, envir = envir), style = style) }, error = function(e) failed <<- TRUE )) if (!failed) { rcls <- class(res) stls <- app$get_current_style()$`class-map` cls <- na.omit(match(rcls, names(stls)))[1] if (!is.na(cls)) { id <- clii__container_start(app, "span", class = stls[[cls]]) on.exit(clii__container_end(app, id), add = TRUE) style_save <- style on.exit(style <<- style_save, add = TRUE) style <<- app$get_current_style() res <- inline_generic(app, stls[[cls]], res, style) } if (inline_styling) return(res) else return(inline_collapse(res)) } code <- glue_collapse(code, "\n") m <- regexpr(inline_regex(), code, perl = TRUE) has_match <- m != -1 if (!has_match) return(paste0("{", code, "}")) starts <- attr(m, "capture.start") ends <- starts + attr(m, "capture.length") - 1L captures <- substring(code, starts, ends) funname <- captures[[1]] text <- captures[[2]] inline_styling <<- TRUE on.exit(inline_styling <<- FALSE, add = TRUE) id <- clii__container_start(app, "span", class = funname) on.exit(clii__container_end(app, id), add = TRUE) style_save <- style on.exit(style <<- style_save, add = TRUE) style <<- app$get_current_style() transform_hook_save <- transform_hook on.exit(transform_hook <<- transform_hook_save, add = TRUE) if (is.function(style$transform)) { transform_hook <<- style$transform } out <- glue( text, .envir = envir, .transformer = inline_transformer, .open = paste0("{", envir$marker), .close = paste0(envir$marker, "}") ) inline_collapse(inline_generic(app, funname, out, style = style)) } }) clii__inline <- function(app, text, .list) { ## Inject that app, so we can style assign("app", app, envir = environment(inline_transformer)) on.exit(rm(list = "app", envir = environment(inline_transformer)), add = TRUE) texts <- c(if (!is.null(text)) list(text), .list) out <- lapply(texts, function(t) { glue( t$str, .envir = t$values, .transformer = inline_transformer, .open = paste0("{", t$values$marker), .close = paste0(t$values$marker, "}") ) }) paste(out, collapse = "") } inline_regex <- function() "(?s)^[.]([-[:alnum:]_]+)[[:space:]]+(.+)" make_cmd_transformer <- function(values) { values$marker <- random_id() values$qty <- NA_integer_ values$num_subst <- 0L values$postprocess <- FALSE values$pmarkers <- list() function(code, envir) { res <- tryCatch({ expr <- parse(text = code, keep.source = FALSE) eval(expr, envir = list("?" = function(...) stop()), enclos = envir) }, error = function(e) e) if (!inherits(res, "error")) { id <- paste0("v", length(values)) if (length(res) == 0) res <- qty(0) values[[id]] <- res values$qty <- res values$num_subst <- values$num_subst + 1L return(paste0("{", values$marker, id, values$marker, "}")) } # plurals if (substr(code, 1, 1) == "?") { return(parse_plural(code, values)) } else { # inline styles m <- regexpr(inline_regex(), code, perl = TRUE) has_match <- m != -1 if (!has_match) stop(res) starts <- attr(m, "capture.start") ends <- starts + attr(m, "capture.length") - 1L captures <- substring(code, starts, ends) funname <- captures[[1]] text <- captures[[2]] out <- glue(text, .envir = envir, .transformer = sys.function()) paste0("{", values$marker, ".", funname, " ", out, values$marker, "}") } } } glue_cmd <- function(..., .envir) { str <- paste0(unlist(list(...), use.names = FALSE), collapse = "") values <- new.env(parent = emptyenv()) transformer <- make_cmd_transformer(values) pstr <- glue(str, .envir = .envir, .transformer = transformer) glue_delay( str = post_process_plurals(pstr, values), values = values ) } glue_delay <- function(str, values = NULL) { structure( list(str = str, values = values), class = "cli_glue_delay" ) } cli/R/cliapp-docs.R0000644000176200001440000002072613571734652013567 0ustar liggesusers #' CLI inline markup #' #' @section Command substitution: #' #' All text emitted by cli supports glue interpolation. Expressions #' enclosed by braces will be evaluated as R code. See [glue::glue()] for #' details. #' #' In addition to regular glue interpolation, cli can also add classes #' to parts of the text, and these classes can be used in themes. For #' example #' #' ``` #' cli_text("This is {.emph important}.") #' ``` #' #' adds a class to the "important" word, class "emph". Note that in this #' case the string within the braces is usually not a valid R expression. #' If you want to mix classes with interpolation, add another pair of #' braces: #' #' ``` #' adjective <- "great" #' cli_text("This is {.emph {adjective}}.") #' ``` #' #' An inline class will always create a `span` element internally. So in #' themes, you can use the `span.emph` CSS selector to change how inline #' text is emphasized: #' #' ``` #' cli_div(theme = list(span.emph = list(color = "red"))) #' adjective <- "nice and red" #' cli_text("This is {.emph {adjective}}.") #' ``` #' #' @section Classes: #' #' The default theme defines the following inline classes: #' * `emph` for emphasized text. #' * `strong` for strong importance. #' * `code` for a piece of code. #' * `pkg` for a package name. #' * `fun` for a function name. #' * `arg` for a function argument. #' * `key` for a keyboard key. #' * `file` for a file name. #' * `path` for a path (essentially the same as `file`). #' * `email` for an email address. #' * `url` for a URL. #' * `var` for a variable name. #' * `envvar` for the name of an environment variable. #' * `val` for a "value". #' #' See examples below. #' #' You can simply add new classes by defining them in the theme, and then #' using them, see the example below. #' #' @section Collapsing inline vectors: #' #' When cli performs inline text formatting, it automatically collapses #' glue substitutions, after formatting. This is handy to create lists of #' files, packages, etc. See examples below. #' #' @section Formatting values: #' #' The `val` inline class formats values. By default (c.f. the builtin #' theme), it calls the [cli_format()] generic function, with the current #' style as the argument. See [cli_format()] for examples. #' #' @section Escaping `{` and `}`: #' #' It might happen that you want to pass a string to `cli_*` functions, #' and you do not_ want command substitution in that string, because it #' might contain `}` and `{` characters. The simplest solution for this is #' referring to the string from a template: #' #' ``` #' msg <- "Error in if (ncol(dat$y)) {: argument is of length zero" #' cli_alert_warning("{msg}") #' ``` #' #' If you want to explicitly escape `{` and `}` characters, just double #' them: #' #' ``` #' cli_alert_warning("A warning with {{ braces }}") #' ``` #' #' See also examples below. #' #' @section Pluralization: #' #' All cli commands that emit text support pluralization. Some examples: #' #' ``` #' cli_alert_info("Found {ndirs} diretor{?y/ies} and {nfiles} file{?s}.") #' cli_text("Will install {length(pkgs)} package{?s}: {.pkg {pkgs}}") #' ``` #' #' See [pluralization] for details. #' #' @name inline-markup #' @examples #' ## Some inline markup examples #' cli_ul() #' cli_li("{.emph Emphasized} text") #' cli_li("{.strong Strong} importance") #' cli_li("A piece of code: {.code sum(a) / length(a)}") #' cli_li("A package name: {.pkg cli}") #' cli_li("A function name: {.fn cli_text}") #' cli_li("A keyboard key: press {.kbd ENTER}") #' cli_li("A file name: {.file /usr/bin/env}") #' cli_li("An email address: {.email bugs.bunny@acme.com}") #' cli_li("A URL: {.url https://acme.com}") #' cli_li("An environment variable: {.envvar R_LIBS}") #' cli_end() #' #' ## Adding a new class #' cli_div(theme = list( #' span.myclass = list(color = "lightgrey"), #' "span.myclass" = list(before = "["), #' "span.myclass" = list(after = "]"))) #' cli_text("This is {.myclass in brackets}.") #' cli_end() #' #' ## Collapsing #' pkgs <- c("pkg1", "pkg2", "pkg3") #' cli_text("Packages: {pkgs}.") #' cli_text("Packages: {.pkg {pkgs}}") #' #' ## Escaping #' msg <- "Error in if (ncol(dat$y)) {: argument is of length zero" #' cli_alert_warning("{msg}") #' #' cli_alert_warning("A warning with {{ braces }}") NULL #' CLI containers #' #' Container elements may contain other elements. Currently the following #' commands create container elements: [cli_div()], [cli_par()], the list #' elements: [cli_ul()], [cli_ol()], [cli_dl()], and list items are #' containers as well: [cli_li()]. #' #' Container elements need to be closed with [cli_end()]. For convenience, #' they are have an `.auto_close` argument, which allows automatically #' closing a container element, when the function that created it #' terminates (either regularly, or with an error). #' #' @name containers #' @examples #' ## div with custom theme #' d <- cli_div(theme = list(h1 = list(color = "blue", #' "font-weight" = "bold"))) #' cli_h1("Custom title") #' cli_end(d) #' #' ## Close automatically #' div <- function() { #' cli_div(class = "tmp", theme = list(.tmp = list(color = "yellow"))) #' cli_text("This is yellow") #' } #' div() #' cli_text("This is not yellow any more") NULL #' CLI themes #' #' CLI elements can be styled via a CSS-like language of selectors and #' properties. Only a small subset of CSS3 is supported, and #' a lot visual properties cannot be implemented on a terminal, so these #' will be ignored as well. #' #' @section Adding themes: #' The style of an element is calculated from themes from four sources. #' These form a stack, and the themes on the top of the stack take #' precedence, over themes in the bottom. #' #' 1. The cli package has a builtin theme. This is always active. #' See [builtin_theme()]. #' 2. When an app object is created via [start_app()], the caller can #' specify a theme, that is added to theme stack. If no theme is #' specified for [start_app()], the content of the `cli.theme` option #' is used. Removed when the corresponding app stops. #' 3. The user may speficy a theme in the `cli.user_theme` option. This #' is added to the stack _after_ the app's theme (step 2.), so it can #' override its settings. Removed when the app that added it stops. #' 4. Themes specified explicitly in [cli_div()] elements. These are #' removed from the theme stack, when the corresponding [cli_div()] #' elements are closed. #' #' @section Writing themes: #' A theme is a named list of lists. The name of each entry is a CSS #' selector. Only a subset of CSS is supported: #' * Type selectors, e.g. `input` selects all `` elements. #' * Class selectors, e.g. `.index` selects any element that has a class #' of "index". #' * ID selector. `#toc` will match the element that has the ID "toc". #' * The descendant combinator, i.e. the space, that selects nodes #' that are descendants of the first element. E.g. `div span` will match #' all `` elements that are inside a `
` element. #' #' The content of a theme list entry is another named list, where the #' names are CSS properties, e.g. `color`, or `font-weight` or #' `margin-left`, and the list entries themselves define the values of #' the properties. See [builtin_theme()] and [simple_theme()] for examples. #' #' @section Formatter callbacks: #' For flexibility, themes may also define formatter functions, with #' property name `fmt`. These will be called once the other styles are #' applied to an element. They are only called on elements that produce #' output, i.e. _not_ on container elements. #' #' @section Supported properties: #' Right now only a limited set of properties are supported. These include #' left, right, top and bottom margins, background and foreground colors, #' bold and italic fonts, underlined text. The `before` and `after` #' properties are supported to insert text before and after the #' content of the element. #' #' More properties might be adder later. #' #' Please see the example themes and the source code for now for the #' details. #' #' @section Examples: #' Color of headings, that are only active in paragraphs with an #' 'output' class: #' ``` #' list( #' "par.output h1" = list("background-color" = "red", color = "#e0e0e0"), #' "par.output h2" = list("background-color" = "orange", color = "#e0e0e0"), #' "par.output h3" = list("background-color" = "blue", color = "#e0e0e0") #' ) #' ``` #' #' Create a custom alert type: #' ``` #' list( #' ".alert-start" = list(before = symbol$play), #' ".alert-stop" = list(before = symbol$stop) #' ) #' ``` #' @name themes NULL cli/R/utils.R0000644000176200001440000000653013571552620012517 0ustar liggesusers `%||%` <- function(l, r) if (is.null(l)) r else l new_class <- function(class_name, ...) { structure(as.environment(list(...)), class = class_name) } make_space <- function(len) { strrep(" ", len) } strrep <- function(x, times) { x <- as.character(x) if (length(x) == 0L) return(x) r <- .mapply( function(x, times) { if (is.na(x) || is.na(times)) return(NA_character_) if (times <= 0L) return("") paste0(replicate(times, x), collapse = "") }, list(x = x, times = times), MoreArgs = list() ) res <- unlist(r, use.names = FALSE) Encoding(res) <- Encoding(x) res } is_latex_output <- function() { if (!("knitr" %in% loadedNamespaces())) return(FALSE) get("is_latex_output", asNamespace("knitr"))() } is_windows <- function() { .Platform$OS.type == "windows" } apply_style <- function(text, style, bg = FALSE) { if (identical(text, "")) return(text) if (is.function(style)) { style(text) } else if (is.character(style)) { make_ansi_style(style, bg = bg)(text) } else if (is.null(style)) { text } else { stop("Not a colour name or ANSI style function", call. = FALSE) } } vcapply <- function(X, FUN, ..., USE.NAMES = TRUE) { vapply(X, FUN, FUN.VALUE = character(1), ..., USE.NAMES = USE.NAMES) } viapply <- function(X, FUN, ..., USE.NAMES = TRUE) { vapply(X, FUN, FUN.VALUE = integer(1), ..., USE.NAMES = USE.NAMES) } vlapply <- function(X, FUN, ..., USE.NAMES = TRUE) { vapply(X, FUN, FUN.VALUE = logical(1), ..., USE.NAMES = USE.NAMES) } ruler <- function(width = console_width()) { x <- seq_len(width) y <- rep("-", length(x)) y[x %% 5 == 0] <- "+" y[x %% 10 == 0] <- style_bold(as.character((x[x %% 10 == 0] %/% 10) %% 10)) cat(y, "\n", sep = "") cat(x %% 10, "\n", sep = "") } rpad <- function(x, width = NULL) { if (!length(x)) return(x) w <- nchar(x, type = "width") if (is.null(width)) width <- max(w) paste0(x, strrep(" ", pmax(width - w, 0))) } lpad <- function(x, width = NULL) { if (!length(x)) return(x) w <- nchar(x, type = "width") if (is.null(width)) width <- max(w) paste0(strrep(" ", pmax(width - w, 0)), x) } #' @importFrom utils tail tail_na <- function(x, n = 1) { tail(c(rep(NA, n), x), n) } #' @importFrom crayon col_substr #' @importFrom utils head dedent <- function(x, n = 2) { first_n_char <- strsplit(col_substr(x, 1, n), "")[[1]] n_space <- cumsum(first_n_char == " ") d_n_space <- diff(c(0, n_space)) first_not_space <- head(c(which(d_n_space == 0), n + 1), 1) col_substr(x, first_not_space, nchar(x)) } new_uuid <- (function() { cnt <- 0 function() { cnt <<- cnt + 1 paste0("cli-", clienv$pid, "-", cnt) } })() na.omit <- function(x) { if (is.atomic(x)) x[!is.na(x)] else x } last <- function(x) { tail(x, 1)[[1]] } str_tail <- function(x) { substr(x, 2, nchar(x)) } push <- function(l, el, name = NULL) { c(l, structure(list(el), names = name)) } try_silently <- function(expr) { suppressWarnings(tryCatch(expr, error = function(x) x)) } random_id <- function() { paste(sample(c(letters, LETTERS, 0:9), 7, replace = TRUE), collapse = "") } str_trim <- function(x) { sub("^\\s+", "", sub("\\s+$", "", x)) } has_asciicast_support <- function() { tryCatch({ asNamespace("asciicast")$is_recording_supported() && asNamespace("asciicast")$is_svg_supported() }, error = function(e) FALSE) } cli/R/box-styles.R0000644000176200001440000000336213565765747013513 0ustar liggesusers box_styles <- function() { styles <- list( single = list( top_left = "\u250c", top_right = "\u2510", bottom_right = "\u2518", bottom_left = "\u2514", vertical = "\u2502", horizontal = "\u2500" ), double = list( top_left = "\u2554", top_right = "\u2557", bottom_right = "\u255d", bottom_left = "\u255a", vertical = "\u2551", horizontal = "\u2550" ), round= list( top_left = "\u256d", top_right = "\u256e", bottom_right = "\u256f", bottom_left = "\u2570", vertical = "\u2502", horizontal = "\u2500" ), "single-double" = list( top_left = "\u2553", top_right = "\u2556", bottom_right = "\u255c", bottom_left = "\u2559", vertical = "\u2551", horizontal = "\u2500" ), "double-single" = list( top_left = "\u2552", top_right = "\u2555", bottom_right = "\u255b", bottom_left = "\u2558", vertical = "\u2502", horizontal = "\u2550" ), classic = list( top_left = "+", top_right = "+", bottom_right = "+", bottom_left = "+", vertical = "|", horizontal = "-" ), none = list( top_left = " ", top_right = " ", bottom_right = " ", bottom_left = " ", vertical = " ", horizontal = " " ) ) ## If the platform is not UTF-8, then we replace the styles that have ## Unicode characters, with the classic style. if (!is_utf8_output()) { for (n in setdiff(names(styles), c("classic", "none"))) { styles[[n]] <- styles[["classic"]] } } do.call(rbind, styles) } #' @export #' @rdname boxx list_border_styles <- function() { rownames(box_styles()) } cli/R/onload.R0000644000176200001440000000231213574535455012637 0ustar liggesusers ## nocov start dummy <- function() { } cli <- NULL clienv <- new.env() clienv$pid <- Sys.getpid() .onLoad <- function(libname, pkgname) { lazyrmd$onload_hook( local = "if-newer", ci = function() has_asciicast_support() && getRversion() >= "3.3", cran = FALSE ) pkgenv <- environment(dummy) makeActiveBinding( "symbol", function() { ## If `cli.unicode` is set we use that opt <- getOption("cli.unicode", NULL) if (!is.null(opt)) { if (isTRUE(opt)) { if (rstudio$is_rstudio()) { return(symbol_rstudio) } else { return(symbol_utf8) } } else { return(symbol_ascii) } } ## Otherwise we try to auto-detect if (is_utf8_output() && rstudio$is_rstudio()) { symbol_rstudio } else if (is_utf8_output()) { symbol_utf8 } else if (is_latex_output()) { symbol_ascii } else if (is_windows()) { symbol_win } else { symbol_ascii } }, pkgenv ) if (is.null(getOption("callr.condition_handler_cli_message"))) { options(callr.condition_handler_cli_message = cli__default_handler) } } ## nocov end cli/R/boxes.R0000644000176200001440000001312313565765747012516 0ustar liggesusers #' Draw a banner-like box in the console #' #' @param label Label to show, a character vector. Each element will be #' in a new line. You can color it using the `col_*`, `bg_*` and #' `style_*` functions, see [ansi-styles] and the examples below. #' @param border_style String that specifies the border style. #' `list_border_styles` lists all current styles. #' @param padding Padding within the box. Either an integer vector of #' four numbers (bottom, left, top, right), or a single number `x`, which #' is interpreted as `c(x, 3*x, x, 3*x)`. #' @param margin Margin around the box. Either an integer vector of four #' numbers (bottom, left, top, right), or a single number `x`, which is #' interpreted as `c(x, 3*x, x, 3*x)`. #' @param float Whether to display the box on the `"left"`, `"center"`, or #' the `"right"` of the screen. #' @param background_col Background color of the inside of the box. #' Either a style function (see [ansi-styles]), or a color name which #' will be used in [make_ansi_style()] to create a *background* style #' (i.e. `bg = TRUE` is used). #' @param col Color of text, and default border color. Either a style #' function (see [ansi-styles]) or a color name that is passed to #' [make_ansi_style()]. #' @param border_col Color of the border. Either a style function #' (see [ansi-styles]) or a color name that is passed to #' [make_ansi_style()]. #' @param align Alignment of the label within the box: `"left"`, #' `"center"`, or `"right"`. #' @param width Width of the screen, defaults to `getOption("width")`. #' #' @section About fonts and terminal settings: #' The boxes might or might not look great in your terminal, depending #' on the box style you use and the font the terminal uses. We found that #' the Menlo font looks nice in most terminals an also in Emacs. #' #' RStudio currently has a line height greater than one for console output, #' which makes the boxes ugly. #' #' @export #' @importFrom crayon col_align #' @examples #' ## Simple box #' boxx("Hello there!") #' #' ## All border styles #' list_border_styles() #' #' ## Change border style #' boxx("Hello there!", border_style = "double") #' #' ## Multiple lines #' boxx(c("Hello", "there!"), padding = 1) #' #' ## Padding #' boxx("Hello there!", padding = 1) #' boxx("Hello there!", padding = c(1, 5, 1, 5)) #' #' ## Margin #' boxx("Hello there!", margin = 1) #' boxx("Hello there!", margin = c(1, 5, 1, 5)) #' boxx("Hello there!", padding = 1, margin = c(1, 5, 1, 5)) #' #' ## Floating #' boxx("Hello there!", padding = 1, float = "center") #' boxx("Hello there!", padding = 1, float = "right") #' #' ## Text color #' boxx(col_cyan("Hello there!"), padding = 1, float = "center") #' #' ## Backgorund color #' boxx("Hello there!", padding = 1, background_col = "brown") #' boxx("Hello there!", padding = 1, background_col = bg_red) #' #' ## Border color #' boxx("Hello there!", padding = 1, border_col = "green") #' boxx("Hello there!", padding = 1, border_col = col_red) #' #' ## Label alignment #' boxx(c("Hi", "there", "you!"), padding = 1, align = "left") #' boxx(c("Hi", "there", "you!"), padding = 1, align = "center") #' boxx(c("Hi", "there", "you!"), padding = 1, align = "right") #' #' ## A very customized box #' star <- symbol$star #' label <- c(paste(star, "Hello", star), " there!") #' boxx( #' col_white(label), #' border_style="round", #' padding = 1, #' float = "center", #' border_col = "tomato3", #' background_col="darkolivegreen" #' ) boxx <- function(label, border_style = "single", padding = 1, margin = 0, float = c("left", "center", "right"), col = NULL, background_col = NULL, border_col = col, align = c("left", "center", "right"), width = console_width()) { label <- apply_style(as.character(label), col) widest <- max(col_nchar(label), 0) assert_that(is_border_style(border_style)) assert_that(is_padding_or_margin(padding)) assert_that(is_padding_or_margin(margin)) float <- match.arg(float) align <- match.arg(align) if (length(padding) == 1) { padding <- c(padding, padding * 3, padding, padding * 3) } if (length(margin) == 1) { margin <- c(margin, margin * 3, margin, margin * 3) } label <- col_align(label, align = align, width = widest) content_width <- widest + padding[2] + padding[4] mar_left <- if (float == "center") { make_space((width - content_width) / 2) } else if (float == "right") { make_space(max(width - content_width - 2, 0)) } else { make_space(margin[2]) } color_border <- function(x) apply_style(x, border_col) color_content <- function(x) apply_style(x, background_col, bg = TRUE) label <- c(rep("", padding[3]), label, rep("", padding[1])) chars <- box_styles()[border_style, ] horizontal <- strrep(chars$horizontal, content_width) top <- color_border(paste0( strrep("\n", margin[3]), mar_left, chars$top_left, horizontal, chars$top_right )) bottom <- color_border(paste0( mar_left, chars$bottom_left, horizontal, chars$bottom_right, strrep("\n", margin[1]) )) side <- color_border(chars$vertical) pad_left <- make_space(padding[2]) pad_right <- make_space(content_width - col_nchar(label) - padding[2]) middle <- paste0(mar_left, side, color_content(paste0(pad_left, label, pad_right)), side) box <- paste0(top, "\n", paste0(middle, collapse = "\n"), "\n", bottom) class(box) <- unique(c("boxx", class(box), "character")) box } #' @importFrom methods setOldClass setOldClass(c("boxx", "character")) #' @export print.boxx <- function(x, ..., sep = "\n") { cat(x, ..., sep = sep) invisible(x) } cli/R/cat.R0000644000176200001440000000344613565765747012154 0ustar liggesusers#' `cat()` helpers #' #' These helpers provide useful wrappers around [cat()]: most importantly #' they all set `sep = ""`, and `cat_line()` automatically adds a newline. #' #' @export #' @param ... For `cat_line()` and `cat_bullet()`, paste'd together with #' `collapse = "\n"`. For `cat_rule()` and `cat_boxx()` passed on to #' [rule()] and [boxx()] respectively. #' @param bullet Name of bullet character. Indexes into [symbol] #' @param col,background_col,bullet_col Colours for text, background, and #' bullets respectively. #' @param x An object to print. #' @param file Output destination. Defaults to standard output. #' @examples #' cat_line("This is ", "a ", "line of text.", col = "red") #' cat_bullet(letters[1:5]) #' cat_bullet(letters[1:5], bullet = "tick", bullet_col = "green") #' cat_rule() cat_line <- function(..., col = NULL, background_col = NULL, file = stdout()) { out <- paste0(..., collapse = "\n") out <- apply_style(out, col) out <- apply_style(out, background_col, bg = TRUE) cat(out, "\n", sep = "", file = file, append = TRUE) } #' @export #' @rdname cat_line cat_bullet <- function(..., col = NULL, background_col = NULL, bullet = "bullet", bullet_col = NULL, file = stdout()) { out <- apply_style(paste0(...), col) bullet <- apply_style(symbol[[bullet]], bullet_col) cat_line(paste(bullet, out), background_col = background_col, file = file) } #' @export #' @rdname cat_line cat_boxx <- function(..., file = stdout()) { cat_line(boxx(...), file = file) } #' @export #' @rdname cat_line cat_rule <- function(..., file = stdout()) { cat_line(rule(...), file = file) } #' @export #' @rdname cat_line cat_print <- function(x, file = "") { if (!identical(file, "")) { sink(file) on.exit(sink(NULL)) } print(x) } cli/R/simple-theme.R0000644000176200001440000001207213605174414013745 0ustar liggesusers #' A simple CLI theme #' #' Note that this is in addition to the builtin theme. To use this theme, #' you can set it as the `cli.theme` option: #' #' ``` #' options(cli.theme = cli::simple_theme()) #' ``` #' #' and then CLI apps started after this will use it as the default theme. #' You can also use it temporarily, in a div element: #' #' ``` #' cli_div(theme = cli::simple_theme()) #' ``` #' #' @param dark Whether the theme should be optiomized for a dark #' background. If `"auto"`, then cli will try to detect this. #' Detection usually works in recent RStudio versions, and in iTerm #' on macOS, but not on other platforms. #' #' @seealso [themes], [builtin_theme()]. #' @export #' @examples #' cli_div(theme = cli::simple_theme()) #' #' cli_h1("Heading 1") #' cli_h2("Heading 2") #' cli_h3("Heading 3") #' #' cli_alert_danger("Danger alert") #' cli_alert_warning("Warning alert") #' cli_alert_info("Info alert") #' cli_alert_success("Success alert") #' cli_alert("Alert for starting a process or computation", #' class = "alert-start") #' #' cli_text("Packages and versions: {.pkg cli} {.version 1.0.0}.") #' cli_text("Time intervals: {.timestamp 3.4s}") #' #' cli_text("{.emph Emphasis} and {.strong strong emphasis}") #' #' cli_text("This is a piece of code: {.code sum(x) / length(x)}") #' cli_text("Function names: {.fn cli::simple_theme}") #' #' cli_text("Files: {.file /usr/bin/env}") #' cli_text("URLs: {.url https://r-project.org}") #' #' cli_h2("Longer code chunk") #' cli_par(class = "code R") #' cli_verbatim( #' '# window functions are useful for grouped mutates', #' 'mtcars %>%', #' ' group_by(cyl) %>%', #' ' mutate(rank = min_rank(desc(mpg)))') #' cli_end() #' #' cli_h2("Even longer code chunk") #' cli_par(class = "code R") #' cli_verbatim(format(ls)) #' cli_end() #' #' cli_end() simple_theme <- function(dark = getOption("cli_theme_dark", "auto")) { dark <- detect_dark_theme(dark) list( h1 = list( "margin-top" = 1, "margin-bottom" = 0, color = "cyan", fmt = function(x) cli::rule(x, line_col = "cyan")), h2 = list( "margin-top" = 1, "margin-bottom" = 0, color = "cyan", fmt = function(x) paste0(symbol$line, " ", x, " ", symbol$line, symbol$line)), h3 = list( "margin-top" = 1, "margin-bottom" = 0, color = "cyan"), par = list("margin-top" = 0, "margin-bottom" = 1), ".alert-danger" = list( "background-color" = "red", color = "white", before = paste0(symbol$cross, " ") ), ".alert-warning" = list( color = "orange", "font-weight" = "bold", before = paste0("!", " ") ), ".alert-success" = list( before = paste0(crayon::green(symbol$tick), " ") ), ".alert-info" = list( before = paste0(crayon::cyan(symbol$info), " ") ), ".alert-start" = list( before = paste0(symbol$arrow_right, " ")), span.pkg = list( color = "blue", "font-weight" = "bold"), span.version = list(color = "blue"), span.emph = simple_theme_emph(), span.strong = list("font-weight" = "bold", "font-style" = "italic"), span.fun = modifyList(simple_theme_code(dark), list(after = "()")), span.fn = modifyList(simple_theme_code(dark), list(after = "()")), span.arg = simple_theme_code(dark), span.kbd = modifyList(simple_theme_code(dark), list(before = "<", after = ">")), span.key = modifyList(simple_theme_code(dark), list(before = "<", after = ">")), span.file = simple_theme_file(), span.path = simple_theme_file(), span.email = simple_theme_url(), span.url = modifyList(simple_theme_url(), list(before = "<", after = ">")), span.var = simple_theme_code(dark), span.envvar = simple_theme_code(dark), span.timestamp = list(before = "[", after = "]", color = "grey") ) } simple_theme_emph <- function() { list("font-style" = "italic") } simple_theme_url <- function() { list(color = "blue") } simple_theme_code <- function(dark) { if (dark) { list("background-color" = "#232323", color = "#f0f0f0") } else{ list("background-color" = "#f8f8f8", color = "#202020") } } simple_theme_file <- function() { list(color = "blue") } simple_theme_r_code <- function(dark) { dark <- dark style <- if (dark) { crayon::make_style("#f0f0f0") } else { crayon::make_style("#202020") } function(x) { x <- crayon::strip_style(x) lines <- strsplit(x, "\n", fixed = TRUE)[[1]] fmd <- tryCatch(prettycode::highlight(lines), error = function(x) lines) style(fmd) } } is_iterm <- function() { isatty(stdout()) && Sys.getenv("TERM_PROGRAM", "") == "iTerm.app" } is_iterm_dark <- function() { tryCatch( error = function(x) FALSE, { osa <- ' tell application "iTerm2" tell current session of current window get background color end tell end tell ' out <- system2("osascript", c("-e", shQuote(osa)), stdout = TRUE) nums <- scan(text = gsub(",", "", out), quiet = TRUE) mean(nums) < 20000 }) } cli/R/spinner.R0000644000176200001440000002072713565765747013064 0ustar liggesusers ## This is how the RDS file is created: ' json <- "https://raw.githubusercontent.com/sindresorhus/cli-spinners/dac4fc6571059bb9e9bc204711e9dfe8f72e5c6f/spinners.json" parsed <- jsonlite::fromJSON(json, simplifyVector = TRUE) pasis <- lapply(parsed, function(x) { x$frames <- I(x$frames); x }) pdt <- as.data.frame(do.call(rbind, pasis)) pdt$name <- rownames(pdt) rownames(pdt) <- NULL spinners <- pdt[, c("name", "interval", "frames")] usethis::use_data(spinners, internal = TRUE) ' #' Character vector to put a spinner on the screen #' #' `cli` contains many different spinners, you choose one according to your #' taste. #' #' @param which The name of the chosen spinner. The default depends on #' whether the platform supports Unicode. #' @return A list with entries: `name`, `interval`: the suggested update #' interval in milliseconds and `frames`: the character vector of the #' spinner's frames. #' #' @family spinners #' @export #' @examples #' get_spinner() #' get_spinner("shark") get_spinner <- function(which = NULL) { assert_that(is.null(which) || is_string(which)) if (is.null(which)) { which <- if (is_utf8_output()) "dots" else "line" } row <- match(which, spinners$name) list( name = which, interval = spinners$interval[[row]], frames = spinners$frames[[row]]) } #' List all available spinners #' #' @return Character vector of all available spinner names. #' #' @family spinners #' @export #' @examples #' list_spinners() #' get_spinner(list_spinners()[1]) list_spinners <- function() { spinners$name } #' Create a spinner #' #' @param template A template string, that will contain the spinner. The #' spinner itself will be substituted for `{spin}`. See example below. #' @param stream The stream to use for the spinner. Typically this is #' standard error, or maybe the standard output stream. #' @param static What to do if the terminal does not support dynamic #' displays: #' * `"dots"`: show a dot for each `$spin()` call. #' * `"print"`: just print the frames of the spinner, one after another. #' * `"print_line"`: print the frames of the spinner, each on its own line. #' * `"silent"` do not print anything, just the `template`. #' @inheritParams get_spinner #' @return A `cli_spinner` object, which is a list of functions. See #' its methods below. #' #' `cli_spinner` methods: #' * `$spin()`: output the next frame of the spinner. #' * `$finish()`: terminate the spinner. Depending on terminal capabilities #' this removes the spinner from the screen. Spinners can be reused, #' you can start calling the `$spin()` method again. #' #' All methods return the spinner object itself, invisibly. #' #' The spinner is automatically throttled to its ideal update frequency. #' #' @section Examples: #' ``` #' ## Default spinner #' sp1 <- make_spinner() #' fun_with_spinner <- function() { #' lapply(1:100, function(x) { sp1$spin(); Sys.sleep(0.05) }) #' sp1$finish() #' } #' ansi_with_hidden_cursor(fun_with_spinner()) #' #' ## Spinner with a template #' sp2 <- make_spinner(template = "Computing {spin}") #' fun_with_spinner2 <- function() { #' lapply(1:100, function(x) { sp2$spin(); Sys.sleep(0.05) }) #' sp2$finish() #' } #' ansi_with_hidden_cursor(fun_with_spinner2()) #' #' ## Custom spinner #' sp3 <- make_spinner("simpleDotsScrolling", template = "Downloading {spin}") #' fun_with_spinner3 <- function() { #' lapply(1:100, function(x) { sp3$spin(); Sys.sleep(0.05) }) #' sp2$finish() #' } #' ansi_with_hidden_cursor(fun_with_spinner3()) #' ``` #' #' @family spinners #' @export make_spinner <- function(which = NULL, stream = stderr(), template = "{spin}", static = c("dots", "print", "print_line", "silent")) { assert_that( inherits(stream, "connection"), is_string(template)) c_stream <- stream c_spinner <- get_spinner(which) c_template <- template c_static <- match.arg(static) c_state <- 1L c_first <- TRUE c_col <- 1L c_width <- console_width() c_last <- Sys.time() - as.difftime(1, units = "secs") c_int <- as.difftime(c_spinner$interval / 1000, units = "secs") c_res <- list() throttle <- function() Sys.time() - c_last < c_int clear_line <- function() { str <- paste0(c("\r", rep(" ", c_width), "\r"), collapse = "") cat(str, file = c_stream) } inc <- function() { c_state <<- c_state + 1L c_first <<- FALSE if (c_state > length(c_spinner$frames)) c_state <<- 1L c_last <<- Sys.time() invisible(c_res) } c_res$finish <- function() { c_state <<- 1L c_first <<- TRUE c_col <<- 1L c_last <<- Sys.time() if (is_dynamic_tty()) clear_line() else cat("\n", file = c_stream) invisible(c_res) } if (is_dynamic_tty()) { c_res$spin <- function(template = NULL) { if (!is.null(template)) c_template <<- template if (throttle()) return() line <- sub("{spin}", c_spinner$frames[[c_state]], c_template, fixed = TRUE) cat("\r", line, sep = "", file = stream) inc() } } else { if (c_static == "dots") { c_res$spin <- function(template = NULL) { if (!is.null(template)) c_template <<- template if (c_first) cat(template, "\n", sep = "", file = c_stream) if (throttle()) return() cat(".", file = c_stream) c_col <<- c_col + 1L if (c_col == c_width) { cat("\n", file = c_stream) c_col <<- 1L } inc() } } else if (c_static == "print") { c_res$spin <- function(template = NULL) { if (!is.null(template)) c_template <<- template if (throttle()) return() line <- sub("{spin}", c_spinner$frames[[c_state]], c_template, fixed = TRUE) cat(line, file = stream) inc() } } else if (c_static == "print_line") { c_res$spin <- function(template = NULL) { if (!is.null(template)) c_template <<- template if (throttle()) return() line <- sub("{spin}", c_spinner$frames[[c_state]], c_template, fixed = TRUE) cat(line, "\n", sep = "", file = stream) inc() } } else if (c_static == "silent") { c_res$spin <- function(template = NULL) { if (!is.null(template)) c_template <<- template if (throttle()) return() inc() } } } class(c_res) <- "cli_spinner" c_res } #' @export print.cli_spinner <- function(x, ...) { cat("\n") invisible(x) } ## nocov start #' Show a demo of some (by default all) spinners #' #' Each spinner is shown for about 2-3 seconds. #' #' @param which Character vector, which spinners to demo. #' #' @family spinners #' @export #' @examples #' \dontrun{ #' demo_spinners(sample(list_spinners(), 10)) #' } demo_spinners <- function(which = NULL) { assert_that(is.null(which) || is.character(which)) all <- list_spinners() which <- which %||% all if (length(bad <- setdiff(which, all))) { stop("Unknown spinners: ", paste(bad, collapse = ", ")) } for (w in which) { sp <- get_spinner(w) interval <- sp$interval / 1000 frames <- sp$frames cycles <- max(round(2.5 / ((length(frames) - 1) * interval)), 1) for (i in 1:(length(frames) * cycles) - 1) { fr <- unclass(frames[i %% length(frames) + 1]) cat("\r", rpad(fr, width = 10), w, sep = "") Sys.sleep(interval) } cat("\n") } } demo_spinners_terminal <- function() { up <- function(n) cat(paste0("\u001B[", n, "A")) show <- function() cat("\u001b[?25h") hide <- function() cat("\u001b[?25l") on.exit(show(), add = TRUE) names <- unlist(spinners$name) frames <- spinners$frames intervals <- unlist(spinners$interval) num_frames <- viapply(frames, length) spin_width <- viapply(frames, function(x) max(nchar(x, type = "width"))) name_width <- nchar(names, type = "width") col_width <- spin_width + max(name_width) + 3 col1_width <- max(col_width[1:(length(col_width)/2)]) frames <- mapply( frames, names, FUN = function(f, n) { rpad(paste(lpad(n, max(name_width) + 2), f), col1_width) } ) hide() for (tick in 0:1000000) { tic <- Sys.time() wframe <- trunc(tick / intervals) %% num_frames + 1 sp <- mapply(frames, wframe, FUN = "[") sp2 <- paste( sep = " ", sp[1:(length(sp) / 2)], sp[(length(sp) / 2 + 1):length(sp)] ) cat(sp2, sep = "\n") up(length(sp2)) took <- Sys.time() - tic togo <- as.difftime(1/1000, units = "secs") - took if (togo > 0) Sys.sleep(togo) } } ## nocov end cli/R/lorem.R0000644000176200001440000000310613565765747012514 0ustar liggesusers lorem_words <- c( "ad", "adipisicing", "aliqua", "aliquip", "amet", "anim", "aute", "cillum", "commodo", "consectetur", "consequat", "culpa", "cupidatat", "deserunt", "do", "dolor", "dolore", "duis", "ea", "eiusmod", "elit", "enim", "esse", "est", "et", "eu", "ex", "excepteur", "exercitation", "fugiat", "id", "in", "incididunt", "ipsum", "irure", "labore", "laboris", "laborum", "Lorem", "magna", "minim", "mollit", "nisi", "non", "nostrud", "nulla", "occaecat", "officia", "pariatur", "proident", "qui", "quis", "reprehenderit", "sint", "sit", "sunt", "tempor", "ullamco", "ut", "velit", "veniam", "voluptate" ) lorem_ipsum <- function(paragraphs = 1, par_sentence_range = 5:10, sentence_word_range = 5:15) { vcapply( 1:paragraphs, function(x, ...) lorem_paragraph(...), par_sentence_range = par_sentence_range, sentence_word_range = sentence_word_range ) } lorem_paragraph <- function(par_sentence_range, sentence_word_range) { num <- sample(par_sentence_range, 1) paste( collapse = " ", vcapply( 1:num, function(x, ...) lorem_sentence(...), sentence_word_range = sentence_word_range ) ) } lorem_sentence <- function(sentence_word_range) { num <- sample(sentence_word_range, 1) words <- sample(lorem_words, num, replace = TRUE) words[1] <- capitalize(words[1]) paste0(paste(words, collapse = " "), ".") } capitalize <- function(x) { substr(x, 1, 1) <- toupper(substr(x, 1, 1)) x } cli/R/assertions.R0000644000176200001440000000253613565765747013576 0ustar liggesusers #' @importFrom assertthat assert_that on_failure<- is_string <- function(x) { is.character(x) && length(x) == 1 && !is.na(x) } on_failure(is_string) <- function(call, env) { paste0(deparse(call$x), " is not a string (length 1 character)") } is_border_style <- function(x) { is_string(x) && x %in% rownames(box_styles()) } on_failure(is_border_style) <- function(call, env) { paste0(deparse(call$x), " is not a border style (see ", sQuote("border_styles"), ")") } is_padding_or_margin <- function(x) { is.numeric(x) && length(x) %in% c(1, 4) && all(!is.na(x)) && all(as.integer(x) == x) } on_failure(is_padding_or_margin) <- function(call, env) { paste0(deparse(call$x), " must be an integer of length one or four") } is_col <- function(x) { is.null(x) || is_string(x) || is.function(x) } on_failure(is_col) <- function(call, env) { paste0(deparse(call$x), " must be a color name, or a crayon style") } is_count <- function(x) { is.numeric(x) && length(x) == 1 && !is.na(x) && as.integer(x) == x && x >= 0 } on_failure(is_count) <- function(call, env) { paste0(deparse(call$x), " must be a count (length 1 non-negative integer)") } is_tree_style <- function(x) { is.list(x) && length(x) == 4 && !is.null(names(x)) && all(sort(names(x)) == sort(c("h", "v", "l", "j"))) && all(sapply(x, is_string)) } cli/R/containers.R0000644000176200001440000001177513573171026013532 0ustar liggesusers add_child <- function(x, tag, ...) { push(x, list(tag = tag, ...)) } #' @importFrom glue glue clii__container_start <- function(app, tag, class = NULL, id = NULL, theme = NULL) { id <- id %||% new_uuid() if (!length(class)) class <- "" class <- setdiff(unique(strsplit(class, " ", fixed = TRUE)[[1]]), "") app$doc <- add_child(app$doc, tag, id = id, class = class, theme = theme) ## Go over all themes, and collect the selectors that match the ## current element new_sels <- list() for (t in seq_along(app$themes)) { theme <- app$themes[[t]] for (i in seq_len(nrow(theme))) { if (is.na(theme$cnt[i]) && match_selector(theme$parsed[[i]], app$doc)) { app$themes[[t]]$cnt[i] <- id new_sels <- modifyList(new_sels, theme$style[[i]]) } } } new_style <- merge_embedded_styles(last(app$styles) %||% list(), new_sels) app$styles <- push(app$styles, new_style, name = id) ## Top margin, if any app$vspace(new_style$`margin-top` %||% 0) invisible(id) } #' @importFrom utils head #' @importFrom stats na.omit clii__container_end <- function(app, id) { ## Defaults to last container if (is.null(id) || is.na(id)) id <- last(app$doc)$id ## Do not remove the if (id == "body") return(invisible(app)) ## Do we have 'id' at all? wh <- which(vlapply(app$doc, function(x) identical(x$id, id)))[1] if (is.na(wh)) return(invisible(app)) ## ids to remove del_ids <- unlist(lapply(tail(app$doc, - (wh - 1L)), "[[", "id")) ## themes to remove del_thm <- unlist(lapply(tail(app$doc, - (wh - 1L)), "[[", "theme")) ## Remove the whole subtree of 'cnt' app$doc <- head(app$doc, wh - 1L) ## Bottom margin del_from <- match(id, names(app$styles)) bottom <- max(viapply( app$styles[del_from:length(app$styles)], function(x) as.integer(x$`margin-bottom` %||% 0L) )) app$vspace(bottom) ## Remove styles app$styles <- head(app$styles, del_from - 1L) ## Remove claimed styles that are not used any more for (t in seq_along(app$themes)) { m <- app$themes[[t]]$cnt %in% del_ids app$themes[[t]]$cnt[m] <- NA_character_ } ## Remove themes app$themes <- app$themes[setdiff(names(app$themes), del_thm)] invisible(app) } ## div -------------------------------------------------------------- clii_div <- function(app, id, class, theme) { theme_id <- app$add_theme(theme) clii__container_start(app, "div", class, id, theme = theme_id) id } ## Paragraph -------------------------------------------------------- clii_par <- function(app, id, class) { clii__container_start(app, "par", class, id) } ## Lists ------------------------------------------------------------ clii_ul <- function(app, items, id, class, .close) { id <- clii__container_start(app, "ul", id = id, class = class) if (length(items)) { app$li(items); if (.close) app$end(id) } invisible(id) } clii_ol <- function(app, items, id, class, .close) { id <- clii__container_start(app, "ol", id = id, class = class) if (length(items)) { app$li(items); if (.close) app$end(id) } invisible(id) } clii_dl <- function(app, items, id, class, .close) { id <- clii__container_start(app, "dl", id = id, class = class) if (length(items)) { app$li(items); if (.close) app$end(id) } invisible(id) } clii_li <- function(app, items, id, class) { id <- id %||% new_uuid() ## check the last active list container last <- length(app$doc) while (! app$doc[[last]]$tag %in% c("ul", "ol", "dl", "body")) { last <- last - 1L } ## if not the last container, close the ones below it if (app$doc[[last]]$tag != "body" && last != length(app$doc)) { app$end(app$doc[[last + 1L]]$id) } ## if none, then create an ul container if (app$doc[[last]]$tag == "body") { cnt_id <- app$ul() type <- "ul" } else { cnt_id <- app$doc[[last]]$id type <- app$doc[[last]]$tag } if (length(items) > 0) { for (i in seq_along(items)) { id <- clii__container_start(app, "li", id = id, class = class) app$item_text(type, names(items)[i], cnt_id, items[[i]]) if (i < length(items)) app$end(id) } } else { app$delayed_item <- list(type = type, cnt_id = cnt_id) id <- clii__container_start(app, "li", id = id, class = class) } invisible(id) } clii__item_text <- function(app, type, name, cnt_id, text, .list) { style <- app$get_current_style() cnt_style <- app$styles[[cnt_id]] head <- if (type == "ul") { paste0(style$`list-style-type` %||% "*", " ") } else if (type == "ol") { res <- paste0(cnt_style$start %||% 1L, ". ") app$styles[[cnt_id]]$start <- (cnt_style$start %||% 1L) + 1L res } else if (type == "dl") { paste0(name, ": ") } app$xtext( .list = c(list(glue_delay(head)), list(text), .list), indent = - style$`padding-left` %||% 0, padding = cnt_style$`padding-left` %||% 0 ) } ## Close container(s) ----------------------------------------------- clii_end <- function(app, id) { clii__container_end(app, id) } cli/R/defer.R0000644000176200001440000000274713565765747012475 0ustar liggesusers defer <- function(expr, envir = parent.frame(), priority = c("first", "last")) { if (identical(envir, .GlobalEnv)) { stop("attempt to defer event on global environment") } priority <- match.arg(priority) front <- priority == "first" invisible(add_handler( envir, list(expr = substitute(expr), envir = parent.frame()), front)) } # Handlers used for 'defer' calls. Attached as a list of expressions for the # 'handlers' attribute on the environment, with 'on.exit' called to ensure # those handlers get executed on exit. get_handlers <- function(envir) { as.list(attr(envir, "handlers")) } set_handlers <- function(envir, handlers) { has_handlers <- "handlers" %in% names(attributes(envir)) attr(envir, "handlers") <- handlers if (!has_handlers) { call <- as.call(list(execute_handlers, envir)) # We have to use do.call here instead of eval because of the way on.exit # determines its evaluation context # (https://stat.ethz.ch/pipermail/r-devel/2013-November/067867.html) do.call(base::on.exit, list(substitute(call), TRUE), envir = envir) } } execute_handlers <- function(envir) { handlers <- get_handlers(envir) for (handler in handlers) { tryCatch(eval(handler$expr, handler$envir), error = identity) } } add_handler <- function(envir, handler, front) { handlers <- if (front) { c(list(handler), get_handlers(envir)) } else { c(get_handlers(envir), list(handler)) } set_handlers(envir, handlers) handler } cli/R/utf8.R0000644000176200001440000000101313565765747012257 0ustar liggesusers #' Whether cli is emitting UTF-8 characters #' #' UTF-8 cli characters can be turned on by setting the `cli.unicode` #' option to `TRUE`. They can be turned off by setting if to `FALSE`. #' If this option is not set, then [base::l10n_info()] is used to detect #' UTF-8 support. #' #' @return Flag, whether cli uses UTF-8 characters. #' #' @export is_utf8_output <- function() { opt <- getOption("cli.unicode", NULL) if (! is.null(opt)) { isTRUE(opt) } else { l10n_info()$`UTF-8` && !is_latex_output() } } cli/R/crayon.R0000644000176200001440000002036313565765747012675 0ustar liggesusers cray_wrapper_fun <- function(style) { style fun <- function(...) { txt <- paste0(...) structure(style(txt), class = "ansi_string") } class(fun) <- "ansi_style" attr(fun, "crayon_style") <- style fun } cray_wrapper <- function(name) { cray_wrapper_fun(asNamespace("crayon")[[name]]) } #' @export print.ansi_string <- function(x, ...) { cat("\n") if (length(x)) { cat(format(paste0("[", seq_along(x), "] ", format(x))), sep = "\n") } invisible(x) } #' Create a new ANSI style #' #' Create a function that can be used to add ANSI styles to text. #' All arguments are passed to [crayon::make_style()], but see the #' Details below. #' #' @param ... The style to create. See details and examples below. #' @param bg Whether the color applies to the background. #' @param grey Whether to specifically create a grey color. #' This flag is included, because ANSI 256 has a finer color scale #' for greys, then the usual 0:5 scale for red, green and blue components. #' It is only used for RGB color specifications (either numerically #' or via a hexa string), and it is ignored on eigth color ANSI #' terminals. #' @param colors Number of colors, detected automatically #' by default. #' @return A function that can be used to color (style) strings. #' #' @details #' The styles (elements of `...`) can be any of the #' following: #' * An R color name, see [grDevices::colors()]. #' * A 6- or 8-digit hexa color string, e.g. `#ff0000` means #' red. Transparency (alpha channel) values are ignored. #' * A one-column matrix with three rows for the red, green #' and blue channels, as returned by [grDevices::col2rgb()]. #' #' `make_ansistyle()` detects the number of colors to use #' automatically (this can be overridden using the `colors` #' argument). If the number of colors is less than 256 (detected or given), #' then it falls back to the color in the ANSI eight color mode that #' is closest to the specified (RGB or R) color. #' #' #' @family ANSI styling #' @export #' @examples #' make_ansi_style("orange") #' make_ansi_style("#123456") #' make_ansi_style("orange", bg = TRUE) #' #' orange <- make_ansi_style("orange") #' orange("foobar") #' cat(orange("foobar")) make_ansi_style <- function(..., bg = FALSE, grey = FALSE, colors = crayon::num_colors()) { dots <- lapply(list(...), function(x) { if (identical(x, "dim")) return("blurred") else x }) args <- c(dots, list(bg = bg , grey = grey, colors = colors)) style <- do.call(crayon::make_style, args) cray_wrapper_fun(style) } #' @export print.ansi_style <- function(x, ...) { cat("\n") cat(x("Example output")) cat("\n") invisible(x) } #' Combine two or more ANSI styles #' #' Combine two or more styles or style functions into a new style function #' that can be called on strings to style them. #' #' It does not usually make sense to combine two foreground #' colors (or two background colors), because only the first one #' applied will be used. #' #' It does make sense to combine different kind of styles, #' e.g. background color, foreground color, bold font. #' #' @param ... The styles to combine. For character strings, the #' [make_ansi_style()] function is used to create a style first. #' They will be applied from right to left. #' @return The combined style function. #' #' @family ANSI styling #' @export #' @examples #' ## Use style names #' alert <- combine_ansi_styles("bold", "red4") #' cat(alert("Warning!"), "\n") #' #' ## Or style functions #' alert <- combine_ansi_styles(style_bold, col_red, bg_cyan) #' cat(alert("Warning!"), "\n") #' #' ## Combine a composite style #' alert <- combine_ansi_styles( #' "bold", #' combine_ansi_styles("red", bg_cyan)) #' cat(alert("Warning!"), "\n") combine_ansi_styles <- function(...) { args <- list(...) args <- lapply(args, function(x) { if (inherits(x, "ansi_style")) attr(x, "crayon_style") else x }) style <- do.call(crayon::combine_styles, args) cray_wrapper_fun(style) } #' ANSI colored text #' #' cli has a number of functions to color and style text at the command #' line. These all use the crayon package under the hood, but provide a #' slightly simpler interface. #' #' The `col_*` functions change the (foreground) color to the text. #' These are the eight original ANSI colors. Note that in some terminals, #' they might actually look differently, as terminals have their own #' settings for how to show them. #' #' The `bg_*` functions change the background color of the text. #' These are the eight original ANSI background colors. These, too, can #' vary in appearence, depending on terminal settings. #' #' The `style_*` functions apply other styling to the text. The currently #' supported styling funtions are: #' * `style_reset()` to remove any style, including color, #' * `style_bold()` for boldface / strong text, although some terminals #' show a bright, high intensity text instead, #' * `style_dim()` (or `style_blurred()` reduced intensity text. #' * `style_italic()` (not widely supported). #' * `style_underline()`, #' * `style_inverse()`, #' * `style_hidden()`, #' * `style_strikethrough() (not widely supported). #' #' The style functions take any number of character vectors as arguments, #' and they concatenate them using `paste0()` before adding the style. #' #' Styles can also be nested, and then inner style takes precedence, see #' examples below. #' #' @param ... Character strings, they will be pasted together with #' `paste0()`, before applying the style function. #' @return An ANSI string (class `ansi_string`), that contains ANSI #' sequences, if the current platform supports them. You can simply #' use `cat()` to print them to the terminal. #' #' @family ANSI styling #' @name ansi-styles #' @examples #' col_blue("Hello ", "world!") #' cat(col_blue("Hello ", "world!")) #' #' cat("... to highlight the", col_red("search term"), #' "in a block of text\n") #' #' ## Style stack properly #' cat(col_green( #' "I am a green line ", #' col_blue(style_underline(style_bold("with a blue substring"))), #' " that becomes green again!" #' )) #' #' error <- combine_ansi_styles("red", "bold") #' warn <- combine_ansi_styles("magenta", "underline") #' note <- col_cyan #' cat(error("Error: subscript out of bounds!\n")) #' cat(warn("Warning: shorter argument was recycled.\n")) #' cat(note("Note: no such directory.\n")) #' NULL #' @export #' @name ansi-styles bg_black <- cray_wrapper("bgBlack") #' @export #' @name ansi-styles bg_blue <- cray_wrapper("bgBlue") #' @export #' @name ansi-styles bg_cyan <- cray_wrapper("bgCyan") #' @export #' @name ansi-styles bg_green <- cray_wrapper("bgGreen") #' @export #' @name ansi-styles bg_magenta <- cray_wrapper("bgMagenta") #' @export #' @name ansi-styles bg_red <- cray_wrapper("bgRed") #' @export #' @name ansi-styles bg_white <- cray_wrapper("bgWhite") #' @export #' @name ansi-styles bg_yellow <- cray_wrapper("bgYellow") #' @export #' @name ansi-styles col_black <- cray_wrapper("black") #' @export #' @name ansi-styles col_blue <- cray_wrapper("blue") #' @export #' @name ansi-styles col_cyan <- cray_wrapper("cyan") #' @export #' @name ansi-styles col_green <- cray_wrapper("green") #' @export #' @name ansi-styles col_magenta <- cray_wrapper("magenta") #' @export #' @name ansi-styles col_red <- cray_wrapper("red") #' @export #' @name ansi-styles col_white <- cray_wrapper("white") #' @export #' @name ansi-styles col_yellow <- cray_wrapper("yellow") #' @export #' @name ansi-styles col_grey <- cray_wrapper("silver") #' @export #' @name ansi-styles col_silver <- cray_wrapper("silver") #' @export #' @name ansi-styles style_dim <- cray_wrapper("blurred") #' @export #' @name ansi-styles style_blurred <- cray_wrapper("blurred") #' @export #' @name ansi-styles style_bold <- cray_wrapper("bold") #' @export #' @name ansi-styles style_hidden <- cray_wrapper("hidden") #' @export #' @name ansi-styles style_inverse <- cray_wrapper("inverse") #' @export #' @name ansi-styles style_italic <- cray_wrapper("italic") #' @export #' @name ansi-styles style_reset <- cray_wrapper("reset") #' @export #' @name ansi-styles style_strikethrough <- cray_wrapper("strikethrough") #' @export #' @name ansi-styles style_underline <- cray_wrapper("underline") cli/R/server.R0000644000176200001440000000047713565765747012714 0ustar liggesusers cli_server_default <- function(msg) { cli_server_default_safe(msg) } cli_server_default_safe <- function(msg) { type <- as.character(msg$type)[1] app <- default_app() %||% start_app(.auto_close = FALSE) do.call(app[[type]], msg$args) } cli_server_callr_handler <- function(msg) { cli_server_default(msg) } cli/R/rstudio-detect.R0000644000176200001440000001065113572444237014322 0ustar liggesusers rstudio <- local({ standalone_env <- environment() parent.env(standalone_env) <- baseenv() # -- Collect data ------------------------------------------------------ data <- NULL get_data <- function() { envs <- c( "R_BROWSER", "R_PDFVIEWER", "RSTUDIO", "RSTUDIO_TERM", "RSTUDIO_CONSOLE_COLOR", "ASCIICAST") d <- list( pid = Sys.getpid(), envs = Sys.getenv(envs), api = tryCatch( asNamespace("rstudioapi")$isAvailable(), error = function(err) FALSE ), tty = isatty(stdin()), gui = .Platform$GUI, args = commandArgs(), search = search() ) d$ver <- if (d$api) asNamespace("rstudioapi")$getVersion() d$desktop <- if (d$api) asNamespace("rstudioapi")$versionInfo()$mode d } # -- Auto-detect environment ------------------------------------------- is_rstudio <- function() { Sys.getenv("RSTUDIO") == "1" } detect <- function(clear_cache = FALSE) { # Cached? if (clear_cache) data <<- list() if (!is.null(data)) return(get_caps(data)) # Otherwise get data new <- get_data() # Cache unless told otherwise cache <- TRUE new$type <- if (new$envs[["RSTUDIO"]] != "1") { # 1. Not RStudio at all "not_rstudio" } else if (new$gui == "RStudio" && new$api) { # 2. RStudio console, properly initialized "rstudio_console" } else if (new$gui == "RStudio" && ! new$api) { # 3. RStudio console, initilizing cache <- FALSE "rstudio_console_starting" } else if (new$tty && new$envs[["ASCIICAST"]] != "true") { # 4. R in the RStudio terminal # This could also be a subprocess of the console or build pane # with a pseudo-terminal. There isn't really a way to rule that # out, without inspecting some process data with ps::ps_*(). # At least we rule out asciicast "rstudio_terminal" } else if (! new$tty && new$envs[["RSTUDIO_TERM"]] == "" && new$envs[["R_BROWSER"]] == "false" && new$envs[["R_PDFVIEWER"]] == "false" && is_build_pane_command(new$args)) { # 5. R in the RStudio build pane # https://github.com/rstudio/rstudio/blob/master/src/cpp/session/ # modules/build/SessionBuild.cpp#L231-L240 "rstudio_build_pane" } else { # Otherwise it is a subprocess of the console, terminal or # build pane, and it is hard to say which, so we do not try. "rstudio_subprocess" } if (cache) data <<- new get_caps(new) } is_build_pane_command <- function(args) { cmd <- gsub("[\"']", "", args[[length(args)]]) rcmd <- sub("[(].*$", "", cmd) rcmd %in% c("devtools::build", "devtools::test", "devtools::check") } # -- Capabilities ------------------------------------------------------ caps <- list() caps$not_rstudio <- function(data) { list( type = "not_rstudio", dynamic_tty = FALSE, ansi_tty = FALSE, ansi_color = FALSE, num_colors = 1L ) } caps$rstudio_console <- function(data) { list( type = "rstudio_console", dynamic_tty = TRUE, ansi_tty = FALSE, ansi_color = data$envs[["RSTUDIO_CONSOLE_COLOR"]] != "", num_colors = as.integer(data$envs[["RSTUDIO_CONSOLE_COLOR"]]) ) } caps$rstudio_console_starting <- function(data) { res <- caps$rstudio_console(data) res$type <- "rstudio_console_starting" res } caps$rstudio_terminal <- function(data) { list( type = "rstudio_terminal", dynamic_tty = TRUE, ansi_tty = TRUE, ansi_color = data$envs[["RSTUDIO_CONSOLE_COLOR"]] != "", num_colors = as.integer(data$envs[["RSTUDIO_CONSOLE_COLOR"]]) ) } caps$rstudio_build_pane <- function(data) { list( type = "rstudio_build_pane", dynamic_tty = TRUE, ansi_tty = FALSE, ansi_color = data$envs[["RSTUDIO_CONSOLE_COLOR"]] != "", num_colors = as.integer(data$envs[["RSTUDIO_CONSOLE_COLOR"]]) ) } caps$rstudio_subprocess <- function(data) { list( type = "rstudio_subprocess", dynamic_tty = FALSE, ansi_tty = FALSE, ansi_color = FALSE, num_colors = 1L ) } get_caps <- function(data, type = data$type) caps[[type]](data) structure( list( .internal = standalone_env, is_rstudio = is_rstudio, detect = detect ), class = c("standalone_rstudio_detect", "standalone") ) }) cli/R/width.R0000644000176200001440000000072113565765747012515 0ustar liggesusers #' Determine the width of the console #' #' It uses the `RSTUDIO_CONSOLE_WIDTH` environment variable, if set. #' Otherwise it uses the `width` option. If this is not set either, #' then 80 is used. #' #' @return Integer scalar, the console with, in number of characters. #' #' @export console_width <- function() { width <- getOption( "cli.width", Sys.getenv( "RSTUDIO_CONSOLE_WIDTH", getOption("width", 80) ) ) as.integer(width) } cli/R/internals.R0000644000176200001440000000452713603643514013361 0ustar liggesusers #' @importFrom fansi strwrap_ctl clii__xtext <- function(app, text, .list, indent, padding) { style <- app$get_current_style() text <- app$inline(text, .list = .list) text <- strwrap_ctl(text, width = app$get_width(extra = padding)) text[1] <- paste0(style$before, text[1]) text[length(text)] <- paste0(text[length(text)], style$after) if (!is.null(style$fmt)) text <- style$fmt(text) app$cat_ln(text, indent = indent, padding) invisible(app) } clii__get_width <- function(app, extra) { style <- app$get_current_style() left <- style$`margin-left` %||% 0 + style$`padding-left` %||% 0 right <- style$`margin-right` %||% 0 + style$`padding-right` %||% 0 console_width() - left - right - extra } clii__cat <- function(app, lines) { clii__message(lines, appendLF = FALSE, output = app$output) } clii__cat_ln <- function(app, lines, indent, padding) { if (!is.null(item <- app$state$delayed_item)) { app$state$delayed_item <- NULL return(app$item_text(item$type, NULL, item$cnt_id, .list = lines)) } style <- app$get_current_style() ## left margin left <- padding + style$`margin-left` %||% 0 + style$`padding-left` %||% 0 if (length(lines) && left) lines <- paste0(strrep(" ", left), lines) ## indent or negative indent if (length(lines)) { if (indent < 0) { lines[1] <- dedent(lines[1], - indent) } else if (indent > 0) { lines[1] <- paste0(strrep(" ", indent), lines[1]) } } ## zero out margin app$margin <- 0 if (length(app$status_bar)) clii__clear_status_bar(app) app$cat(paste0(paste0(lines, "\n"), collapse = "")) if (length(app$status_bar)) app$cat(paste0(app$status_bar[[1]]$content)) } clii__vspace <- function(app, n) { if (app$margin < n) { sp <- strrep("\n", n - app$margin) clii__message(sp, appendLF = FALSE, output = app$output) app$margin <- n } } clii__message <- function(..., domain = NULL, appendLF = TRUE, output = stderr()) { msg <- .makeMessage(..., domain = domain, appendLF = appendLF) if (! inherits(output, "connection")) { output <- switch( output, "auto" = cli_output_connection(), "message" = , "stderr" = stderr(), "stdout" = stdout() ) } withRestarts(muffleMessage = function() NULL, { signalCondition(simpleMessage(msg)) cat(msg, file = output, sep = "") }) } cli/R/tree.R0000644000176200001440000001440213565765747012336 0ustar liggesusers #' Draw a tree #' #' Draw a tree using box drawing characters. Unicode characters are #' used if available. (Set the `cli.unicode` option if auto-detection #' fails.) #' #' A node might appear multiple times in the tree, or might not appear #' at all. #' #' @param data Data frame that contains the tree structure. #' The first column is an id, and the second column is a list column, #' that contains the ids of the child nodes. The optional third column #' may contain the text to print to annotate the node. #' @param root The name of the root node. #' @param style Optional box style list. #' @param width Maximum width of the output. Defaults to the `width` #' option, see [base::options()]. #' @param trim Whether to avoid traversing the same nodes multiple times. #' If `TRUE` and `data` has a `trimmed` column, then that is used for #' printing repeated noded. #' @return Character vector, the lines of the tree drawing. #' #' @export #' @examples #' data <- data.frame( #' stringsAsFactors = FALSE, #' package = c("processx", "backports", "assertthat", "Matrix", #' "magrittr", "rprojroot", "clisymbols", "prettyunits", "withr", #' "desc", "igraph", "R6", "crayon", "debugme", "digest", "irlba", #' "rcmdcheck", "callr", "pkgconfig", "lattice"), #' dependencies = I(list( #' c("assertthat", "crayon", "debugme", "R6"), character(0), #' character(0), "lattice", character(0), "backports", character(0), #' c("magrittr", "assertthat"), character(0), #' c("assertthat", "R6", "crayon", "rprojroot"), #' c("irlba", "magrittr", "Matrix", "pkgconfig"), character(0), #' character(0), "crayon", character(0), "Matrix", #' c("callr", "clisymbols", "crayon", "desc", "digest", "prettyunits", #' "R6", "rprojroot", "withr"), #' c("processx", "R6"), character(0), character(0) #' )) #' ) #' tree(data) #' tree(data, root = "rcmdcheck") #' #' # Colored nodes #' data$label <- paste(data$package, #' style_dim(paste0("(", c("2.0.0.1", "1.1.1", "0.2.0", "1.2-11", #' "1.5", "1.2", "1.2.0", "1.0.2", "2.0.0", "1.1.1.9000", "1.1.2", #' "2.2.2", "1.3.4", "1.0.2", "0.6.12", "2.2.1", "1.2.1.9002", #' "1.0.0.9000", "2.0.1", "0.20-35"), ")")) #' ) #' roots <- ! data$package %in% unlist(data$dependencies) #' data$label[roots] <- col_cyan(style_italic(data$label[roots])) #' tree(data) #' tree(data, root = "rcmdcheck") #' #' # Trimming #' pkgdeps <- list( #' "dplyr@0.8.3" = c("assertthat@0.2.1", "glue@1.3.1", "magrittr@1.5", #' "R6@2.4.0", "Rcpp@1.0.2", "rlang@0.4.0", "tibble@2.1.3", #' "tidyselect@0.2.5"), #' "assertthat@0.2.1" = character(), #' "glue@1.3.1" = character(), #' "magrittr@1.5" = character(), #' "pkgconfig@2.0.3" = character(), #' "R6@2.4.0" = character(), #' "Rcpp@1.0.2" = character(), #' "rlang@0.4.0" = character(), #' "tibble@2.1.3" = c("cli@1.1.0", "crayon@1.3.4", "fansi@0.4.0", #' "pillar@1.4.2", "pkgconfig@2.0.3", "rlang@0.4.0"), #' "cli@1.1.0" = c("assertthat@0.2.1", "crayon@1.3.4"), #' "crayon@1.3.4" = character(), #' "fansi@0.4.0" = character(), #' "pillar@1.4.2" = c("cli@1.1.0", "crayon@1.3.4", "fansi@0.4.0", #' "rlang@0.4.0", "utf8@1.1.4", "vctrs@0.2.0"), #' "utf8@1.1.4" = character(), #' "vctrs@0.2.0" = c("backports@1.1.5", "ellipsis@0.3.0", #' "digest@0.6.21", "glue@1.3.1", "rlang@0.4.0", "zeallot@0.1.0"), #' "backports@1.1.5" = character(), #' "ellipsis@0.3.0" = c("rlang@0.4.0"), #' "digest@0.6.21" = character(), #' "glue@1.3.1" = character(), #' "zeallot@0.1.0" = character(), #' "tidyselect@0.2.5" = c("glue@1.3.1", "purrr@1.3.1", "rlang@0.4.0", #' "Rcpp@1.0.2"), #' "purrr@0.3.3" = c("magrittr@1.5", "rlang@0.4.0") #' ) #' #' pkgs <- data.frame( #' stringsAsFactors = FALSE, #' name = names(pkgdeps), #' deps = I(unname(pkgdeps)) #' ) #' #' tree(pkgs) #' tree(pkgs, trim = TRUE) #' #' # Mark the trimmed nodes #' pkgs$label <- pkgs$name #' pkgs$trimmed <- paste(pkgs$name, " (trimmed)") #' tree(pkgs, trim = TRUE) tree <- function(data, root = data[[1]][[1]], style = NULL, width = console_width(), trim = FALSE) { assert_that( is.data.frame(data), ncol(data) >= 2, is_string(root), is.null(style) || (is_tree_style(style)), is_count(width) ) style <- style %||% box_chars() labels <- if (ncol(data) >= 3) data[[3]] else data[[1]] trimlabs <- data$trimmed %||% labels seen <- character() res <- character() pt <- function(root, n = integer(), mx = integer(), used = character()) { num_root <- match(root, data[[1]]) if (is.na(num_root)) return() level <- length(n) - 1 prefix <- vcapply(seq_along(n), function(i) { if (n[i] < mx[i]) { if (i == length(n)) { paste0(style$j, style$h) } else { paste0(style$v, " ") } } else if (n[i] == mx[i] && i == length(n)) { paste0(style$l, style$h) } else { " " } }) root_seen <- root %in% seen root_lab <- if (trim && root_seen) trimlabs[[num_root]] else labels[[num_root]] res <<- c(res, paste0(paste(prefix, collapse = ""), root_lab)) # Detect infinite loops if (!trim && root %in% used) { warning(call. = FALSE, "Endless loop found in tree: ", paste0(c(used, root), collapse = " -> ")) } else if (! trim || ! root_seen) { seen <<- c(seen, root) children <- data[[2]][[num_root]] for (d in seq_along(children)) { pt(children[[d]], c(n, d), c(mx, length(children)), c(used, root)) } } } if (nrow(data)) pt(root) res <- col_substr(res, 1, width) class(res) <- unique(c("tree", class(res), "character")) res } box_chars <- function() { if (is_utf8_output()) { list( "h" = "\u2500", # horizontal "v" = "\u2502", # vertical "l" = "\u2514", # leaf "j" = "\u251C" # junction ) } else { list( "h" = "-", # horizontal "v" = "|", # vertical "l" = "\\", # leaf "j" = "+" # junction ) } } #' @importFrom methods setOldClass setOldClass(c("tree", "character")) #' @export print.tree <- function(x, ..., sep = "\n") { cat(x, ..., sep = sep) invisible(x) } cli/R/pluralize.R0000644000176200001440000000452313571261512013363 0ustar liggesusers #' CLI pluralization #' #' @name pluralization #' @family pluralization #' @includeRmd man/chunks/pluralization.Rmd NULL make_quantity <- function(object) { val <- if (is.numeric(object)) { stopifnot(length(object) == 1) as.integer(object) } else { length(object) } } #' Pluralization helper functions #' #' @rdname pluralization-helpers #' @param expr For `no()` it is an expression that is printed as "no" in #' cli expressions, it is interpreted as a zero quantity. For `qty()` #' an expression that sets the pluralization quantity without printing #' anything. See examples below. #' #' @export #' @family pluralization no <- function(expr) { stopifnot(is.numeric(expr), length(expr) == 1, !is.na(expr)) structure( expr, class = "cli_no" ) } #' @export as.character.cli_no <- function(x, ...) { if (make_quantity(x) == 0) "no" else as.character(unclass(x)) } #' @rdname pluralization-helpers #' @export qty <- function(expr) { structure( make_quantity(expr), class = "cli_noprint" ) } #' @export as.character.cli_noprint <- function(x, ...) { "" } parse_plural <- function(code, values) { # If we have the quantity already, then process it now. # Otherwise we put in a marker for it, and request post-processing. qty <- make_quantity(values$qty) if (!is.na(qty)) { process_plural(qty, code) } else { values$postprocess <- TRUE id <- random_id() values$pmarkers[[id]] <- code id } } process_plural <- function(qty, code) { parts <- strsplit(str_tail(code), "/", fixed = TRUE)[[1]] if (length(parts) == 1) { if (qty != 1) parts[1] else "" } else if (length(parts) == 2) { if (qty == 1) parts[1] else parts[2] } else if (length(parts) == 3) { if (qty == 0) { parts[1] } else if (qty == 1) { parts[2] } else { parts[3] } } else { stop("Invalid pluralization directive: `", code, "`") } } post_process_plurals <- function(str, values) { if (!values$postprocess) return(str) if (values$num_subst == 0) { stop("Cannot pluralize without a quantity") } if (values$num_subst != 1) { stop("Multiple quantities for pluralization") } qty <- make_quantity(values$qty) for (i in seq_along(values$pmarkers)) { mark <- values$pmarkers[i] str <- sub(names(mark), process_plural(qty, mark[[1]]), str) } str } cli/R/rules.R0000644000176200001440000001365013565765747012535 0ustar liggesusers #' @importFrom crayon col_substring make_line <- function(x, char = symbol$line, col = NULL) { ## Easiest to handle this specially if (x <= 0) return("") cw <- col_nchar(char, "width") ## We handle the simple case differently, to make it faster if (cw == 1) { line <- paste(rep(char, x), collapse = "") } else { line <- substr(paste(rep(char, ceiling(x / cw)), collapse = ""), 1, x) } apply_style(line, col) } #' Make a rule with one or two text labels #' #' The rule can include either a centered text label, or labels on the #' left and right side. #' #' To color the labels, use the functions `col_*`, `bg_*` and `style_*` #' functions, see [ansi-styles], and the examples below. #' To color the line, either these functions directly, or the `line_col` #' option. #' #' @param left Label to show on the left. It interferes with the `center` #' label, only at most one of them can be present. #' @param center Label to show at the center. It interferes with the #' `left` and `right` labels. #' @param right Label to show on the right. It interferes with the `center` #' label, only at most one of them can be present. #' @param line The character or string that is used to draw the line. #' It can also `1` or `2`, to request a single line (Unicode, if #' available), or a double line. Some strings are interpreted specially, #' see *Line styles* below. #' @param col Color of text, and default line color. Either an ANSI style #' function (see [ansi-styles]), or a color name that is passed #' to [make_ansi_style()]. #' @param line_col,background_col Either a color name (used in #' [make_ansi_style()]), or a style function (see [ansi-styles]), to #' color the line and background. #' @param width Width of the rule. Defaults to the `width` option, see #' [base::options()]. #' @return Character scalar, the rule. #' #' @section Line styles: #' Some strings for the `line` argument are interpreted specially: #' #' * `"single"`: (same as `1`), a single line, #' * `"double"`: (same as `2`), a double line, #' * `"bar1"`, `"bar2"`, `"bar3"`, etc., `"bar8"` uses varying height bars. #' #' @export #' @importFrom crayon col_substr #' @examples #' #' ## Simple rule #' rule() #' #' ## Double rule #' rule(line = 2) #' #' ## Bars #' rule(line = "bar2") #' rule(line = "bar5") #' #' ## Left label #' rule(left = "Results") #' #' ## Centered label #' rule(center = " * RESULTS * ") #' #' ## Colored labels #' rule(center = col_red(" * RESULTS * ")) #' #' ## Colored line #' rule(center = col_red(" * RESULTS * "), line_col = "red") #' #' ## Custom line #' rule(center = "TITLE", line = "~") #' #' ## More custom line #' rule(center = "TITLE", line = col_blue("~-")) #' #' ## Even more custom line #' rule(center = bg_red(" ", symbol$star, "TITLE", #' symbol$star, " "), #' line = "\u2582", #' line_col = "orange") rule <- function(left = "", center = "", right = "", line = 1, col = NULL, line_col = col, background_col = NULL, width = console_width()) { try_silently(left <- as.character(left)) try_silently(center <- as.character(center)) try_silently(right <- as.character(right)) assert_that( is_string(left), is_string(center), is_string(right), is_string(line) || line == 1 || line == 2, is_col(col), is_col(line_col), is_count(width) ) left <- apply_style(left, col) center <- apply_style(center, col) right <- apply_style(right, col) options <- as.list(environment()) options$line <- get_line_char(options$line) res <- if (nchar(center)) { if (nchar(left) || nchar(right)) { stop(sQuote("center"), " cannot be specified with ", sQuote("left"), " or ", sQuote("right")) } rule_center(options) } else if (nchar(left) && nchar(right)) { rule_left_right(options) } else if (nchar(left)) { rule_left(options) } else if (nchar(right)) { rule_right(options) } else { rule_line(options) } res <- col_substr(res, 1, width) res <- apply_style(res, background_col, bg = TRUE) class(res) <- unique(c("rule", class(res), "character")) res } get_line_char <- function(line) { if (identical(line, 1) || identical(line, 1L) || identical(line, "single")) { symbol$line } else if (identical(line, 2) || identical(line, 2L) || identical(line, "double")) { symbol$double_line } else if (length(line) == 1 && line %in% paste0("bar", 1:8)) { bars <- structure( paste0("lower_block_", 1:8), names = paste0("bar", 1:8) ) symbol[[ bars[[line]] ]] } else { paste(as.character(line), collapse = "") } } rule_line <- function(o) { make_line(o$width, o$line, o$line_col) } #' @importFrom crayon col_nchar rule_center <- function(o) { o$center <- col_substring(o$center, 1, o$width - 4) o$center <- paste0(" ", o$center, " ") ncc <- col_nchar(o$center, "width") ndashes <- o$width - ncc paste0( make_line(ceiling(ndashes / 2), o$line, o$line_col), o$center, make_line(floor(ndashes / 2), o$line, o$line_col) ) } rule_left <- function(o) { ncl <- col_nchar(o$left, "width") paste0( make_line(2, get_line_char(o$line), o$line_col), " ", o$left, " ", make_line(o$width - ncl - 4, o$line, o$line_col) ) } rule_right <- function(o) { ncr <- col_nchar(o$right, "width") paste0( make_line(o$width - ncr - 4, o$line, o$line_col), " ", o$right, " ", make_line(2, o$line, o$line_col) ) } rule_left_right <- function(o) { ncl <- col_nchar(o$left, "width") ncr <- col_nchar(o$right, "width") ## -- (ncl) -- (ncr) -- if (ncl + ncr + 10 > o$width) return(rule_left(o)) paste0( make_line(2, o$line, o$line_col), " ", o$left, " ", make_line(o$width - ncl - ncr - 8, o$line, o$line_col), " ", o$right, " ", make_line(2, o$line, o$line_col) ) } #' @importFrom methods setOldClass setOldClass(c("rule", "character")) #' @export print.rule <- function(x, ..., sep = "\n") { cat(x, ..., sep = sep) invisible(x) } cli/R/cliapp.R0000644000176200001440000001700713573313437012633 0ustar liggesusers cliapp <- function(theme = getOption("cli.theme"), user_theme = getOption("cli.user_theme"), output = c("auto", "message", "stdout", "stderr")) { app <- new_class( "cliapp", new = function(theme, user_theme, output) clii_init(app, theme, user_theme, output), ## Themes list_themes = function() clii_list_themes(app), add_theme = function(theme) clii_add_theme(app, theme), remove_theme = function(id) clii_remove_theme(app, id), ## Close container(s) end = function(id = NULL) clii_end(app, id), ## Generic container div = function(id = NULL, class = NULL, theme = NULL) clii_div(app, id, class, theme), ## Paragraphs par = function(id = NULL, class = NULL) clii_par(app, id, class), ## Text, wrapped text = function(text) clii_text(app, text), ## Text, not wrapped verbatim = function(...) clii_verbatim(app, ...), ## Markdow(ish) text, wrapped: emphasis, strong emphasis, links, code md_text = function(...) clii_md_text(app, ...), ## Headings h1 = function(text, id = NULL, class = NULL) clii_h1(app, text, id, class), h2 = function(text, id = NULL, class = NULL) clii_h2(app, text, id, class), h3 = function(text, id = NULL, class = NULL) clii_h3(app, text, id, class), ## Block quote blockquote = function(quote, citation = NULL, id, class = NULL) clii_blockquote(app, quote, citation, id, class), ## Lists ul = function(items = NULL, id = NULL, class = NULL, .close = TRUE) clii_ul(app, items, id, class, .close), ol = function(items = NULL, id = NULL, class = NULL, .close = TRUE) clii_ol(app, items, id, class, .close), dl = function(items = NULL, id = NULL, class = NULL, .close = TRUE) clii_dl(app, items, id, class, .close), li = function(items = NULL, id = NULL, class = NULL) clii_li(app, items, id, class), ## Tables table = function(cells, id = NULL, class = NULL) clii_table(app, cells, class), ## Alerts alert = function(text, id = NULL, class = NULL, wrap = FALSE) clii_alert(app, "alert", text, id, class, wrap), alert_success = function(text, id = NULL, class = NULL, wrap = FALSE) clii_alert(app, "alert-success", text, id, class, wrap), alert_danger = function(text, id = NULL, class = NULL, wrap = FALSE) clii_alert(app, "alert-danger", text, id, class, wrap), alert_warning = function(text, id = NULL, class = NULL, wrap = FALSE) clii_alert(app, "alert-warning", text, id, class, wrap), alert_info = function(text, id = NULL, class = NULL, wrap = FALSE) clii_alert(app, "alert-info", text, id, class, wrap), ## Horizontal rule rule = function(left, center, right, id = NULL) clii_rule(app, left, center, right, id), ## Status bar status = function(id = NULL, msg, msg_done = NULL, msg_failed = NULL, keep, auto_result) clii_status(app, id, msg, msg_done, msg_failed, keep, auto_result), status_clear = function(id = NULL, result, msg_done = NULL, msg_failed = NULL) clii_status_clear(app, id, result, msg_done, msg_failed), status_update = function(id = NULL, msg, msg_done = NULL, msg_failed = NULL) clii_status_update(app, id, msg, msg_done, msg_failed), doc = NULL, themes = NULL, styles = NULL, delayed_item = NULL, status_bar = list(), margin = 0, output = NULL, get_current_style = function() tail(app$styles, 1)[[1]], xtext = function(text = NULL, .list = NULL, indent = 0, padding = 0) clii__xtext(app, text, .list = .list, indent = indent, padding = padding), vspace = function(n = 1) clii__vspace(app, n), inline = function(text = NULL, .list = NULL) clii__inline(app, text, .list = .list), item_text = function(type, name, cnt_id, items = list(), .list = NULL) clii__item_text(app, type, name, cnt_id, items, .list = .list), get_width = function(extra = 0) clii__get_width(app, extra), cat = function(lines) clii__cat(app, lines), cat_ln = function(lines, indent = 0, padding = 0) clii__cat_ln(app, lines, indent, padding) ) if (! inherits(output, "connection")) output <- match.arg(output) app$new(theme, user_theme, output) app } clii_init <- function(app, theme, user_theme, output) { app$doc <- list() app$output <- output app$styles <- NULL app$add_theme(builtin_theme()) app$add_theme(theme) app$add_theme(user_theme) clii__container_start(app, "body", id = "body") invisible(app) } ## Text ------------------------------------------------------------- #' @importFrom fansi strwrap_ctl clii_text <- function(app, text) { app$xtext(text) } clii_verbatim <- function(app, ..., .envir) { style <- app$get_current_style() text <- paste(unlist(list(...), use.names = FALSE), collapse = "\n") if (!is.null(style$fmt)) text <- style$fmt(text) app$cat_ln(text) invisible(app) } clii_md_text <- function(app, ...) { stop("Markdown text is not implemented yet") } ## Headings ---------------------------------------------------------- clii_h1 <- function(app, text, id, class) { clii__heading(app, "h1", text, id, class) } clii_h2 <- function(app, text, id, class) { clii__heading(app, "h2", text, id, class) } clii_h3 <- function(app, text, id, class) { clii__heading(app, "h3", text, id, class) } clii__heading <- function(app, type, text, id, class) { id <- new_uuid() clii__container_start(app, type, id = id, class = class) on.exit(clii__container_end(app, id), add = TRUE) text <- app$inline(text) style <- app$get_current_style() if (is.function(style$fmt)) text <- style$fmt(text) app$cat_ln(text) invisible(app) } ## Block quote ------------------------------------------------------ clii_blockquote <- function(app, quote, citation, id, class) { c1 <- clii__container_start(app, "blockquote", id = id, class = class) on.exit(clii__container_end(app, id), add = TRUE) app$xtext(quote) c2 <- clii__container_start(app, "cite", id = new_uuid()) app$xtext(citation) } ## Table ------------------------------------------------------------ clii_table <- function(app, cells, id, class) { stop("Tables are not implemented yet") } ## Rule ------------------------------------------------------------- clii_rule <- function(app, left, center, right, id) { left <- app$inline(left) center <- app$inline(center) right <- app$inline(right) clii__container_start(app, "rule", id = id) on.exit(clii__container_end(app, id), add = TRUE) style <- app$get_current_style() width <- console_width() - nchar_ctl(style$before %||% "") - nchar_ctl(style$after %||% "") text <- rule(left, center, right, line = style$`line-type` %||% 1) text[1] <- paste0(style$before, text[1]) text[length(text)] <- paste0(text[length(text)], style$after) if (is.function(style$fmt)) text <- style$fmt(text) app$cat_ln(text) } ## Alerts ----------------------------------------------------------- clii_alert <- function(app, type, text, id, class, wrap) { clii__container_start(app, "div", id = id, class = paste(class, "alert", type)) on.exit(clii__container_end(app, id), add = TRUE) text <- app$inline(text) style <- app$get_current_style() text[1] <- paste0(style$before, text[1]) text[length(text)] <- paste0(text[length(text)], style$after) if (is.function(style$fmt)) text <- style$fmt(text) if (wrap) text <- strwrap_ctl(text, exdent = 2) app$cat_ln(text) } cli/R/symbol.R0000644000176200001440000001540013572445366012671 0ustar liggesusers#' Various handy symbols to use in a command line UI #' #' @usage #' symbol #' #' @format A named list, see \code{names(symbol)} for all sign names. #' #' @details #' #' On Windows they have a fallback to less fancy symbols. #' #' `list_symbols()` prints a table with all symbols to the screen. #' #' @name symbol #' @aliases symbol #' @export symbol #' #' @examples #' cat(symbol$tick, " SUCCESS\n", symbol$cross, " FAILURE\n", sep = "") #' #' ## All symbols #' cat(paste(format(names(symbol), width = 20), #' unlist(symbol)), sep = "\n") NULL symbol_utf8 <- list( "tick" = '\u2714', "cross" = '\u2716', "star" = '\u2605', "square" = '\u2587', "square_small" = '\u25FB', "square_small_filled" = '\u25FC', "circle" = '\u25EF', "circle_filled" = '\u25C9', "circle_dotted" = '\u25CC', "circle_double" = '\u25CE', "circle_circle" = '\u24DE', "circle_cross" = '\u24E7', "circle_pipe" = '\u24be', "circle_question_mark" = '?\u20DD', "bullet" = '\u25CF', "dot" = '\u2024', "line" = '\u2500', "double_line" = "\u2550", "ellipsis" = '\u2026', "continue" = '\u2026', "pointer" = '\u276F', "info" = '\u2139', "warning" = '\u26A0', "menu" = '\u2630', "smiley" = '\u263A', "mustache" = '\u0DF4', "heart" = '\u2665', "arrow_up" = '\u2191', "arrow_down" = '\u2193', "arrow_left" = '\u2190', "arrow_right" = '\u2192', "radio_on" = '\u25C9', "radio_off" = '\u25EF', "checkbox_on" = '\u2612', "checkbox_off" = '\u2610', "checkbox_circle_on" = '\u24E7', "checkbox_circle_off" = '\u24BE', "fancy_question_mark" = '\u2753', "neq" = "\u2260", "geq" = "\u2265", "leq" = "\u2264", "times" = "\u00d7", "upper_block_1" = "\u2594", "upper_block_4" = "\u2580", "lower_block_1" = "\u2581", "lower_block_2" = "\u2582", "lower_block_3" = "\u2583", "lower_block_4" = "\u2584", "lower_block_5" = "\u2585", "lower_block_6" = "\u2586", "lower_block_7" = "\u2587", "lower_block_8" = "\u2588", "full_block" = "\u2588", "sup_0" = "\u2070", "sup_1" = "\u00b9", "sup_2" = "\u00b2", "sup_3" = "\u00b3", "sup_4" = "\u2074", "sup_5" = "\u2075", "sup_6" = "\u2076", "sup_7" = "\u2077", "sup_8" = "\u2078", "sup_9" = "\u2079", "sup_minus" = "\u207b", "sup_plus" = "\u207a", "play" = "\u25b6", "stop" = "\u25a0", "record" = "\u25cf", "figure_dash" = "\u2012", "en_dash" = "\u2013", "em_dash" = "\u2014", "dquote_left" = "\u201c", "dquote_right" = "\u201d", "squote_left" = "\u2018", "squote_right" = "\u2019" ) symbol_rstudio <- symbol_utf8 symbol_rstudio$tick <- "\u2713" symbol_rstudio$cross <- "x" symbol_rstudio$star <- "\u066d" symbol_rstudio$square # ??? symbol_rstudio$square_small # ??? symbol_rstudio$suare_small_filled # ??? symbol_rstudio$circle_circle # ??? symbol_rstudio$circle_cross # ??? symbol_rstudio$circle_pipe # ??? symbol_rstudio$circle_question_mark # ??? symbol_rstudio$pointer <- ">" symbol_rstudio$warning <- "!" symbol_rstudio$menu # ??? symbol_rstudio$mustache # ??? symbol_rstudio$checkbox_circle_on # ??? symbol_rstudio$checkbox_circle_off # ??? symbol_rstudio$fancy_question_mark # ??? symbol_win <- list( "tick" = '\u221A', "cross" = 'x', "star" = '*', "square" = '\u2588', "square_small" = '[ ]', "square_small_filled" = '[\u2588]', "circle" = '( )', "circle_filled" = '(*)', "circle_dotted" = '( )', "circle_double" = '(o)', "circle_circle" = '(o)', "circle_cross" = '(x)', "circle_pipe" = '(|)', "circle_question_mark" = '(?)', "bullet" = '*', "dot" = '.', "line" = '-', "double_line" = "=", "ellipsis" = '...', "continue" = '~', "pointer" = '>', "info" = 'i', "warning" = '\u203C', "menu" = '\u2261', "smiley" = '\u263A', "mustache" = '\u250C\u2500\u2510', "heart" = '\u2665', "arrow_up" = '^', "arrow_down" = 'v', "arrow_left" = '<', "arrow_right" = '>', "radio_on" = '(*)', "radio_off" = '( )', "checkbox_on" = '[x]', "checkbox_off" = '[ ]', "checkbox_circle_on" = '(x)', "checkbox_circle_off" = '( )', "fancy_question_mark" = "(?)", "neq" = "!=", "geq" = ">=", "leq" = "<=", "times" = "x", "upper_block_1" = "^", "upper_block_4" = "^", "lower_block_1" = ".", "lower_block_2" = "_", "lower_block_3" = "_", "lower_block_4" = "=", "lower_block_5" = "=", "lower_block_6" = "*", "lower_block_7" = "\u2588", "lower_block_8" = "\u2588", "full_block" = "\u2588", "sup_0" = "0", "sup_1" = "1", "sup_2" = "2", "sup_3" = "3", "sup_4" = "4", "sup_5" = "5", "sup_6" = "6", "sup_7" = "7", "sup_8" = "8", "sup_9" = "9", "sup_minus" = "-", "sup_plus" = "+", "play" = ">", "stop" = "#", "record" = "o", "figure_dash" = "-", "en_dash" = "--", "em_dash" = "---", "dquote_left" = "\"", "dquote_right" = "\"", "squote_left" = "'", "squote_right" = "'" ) symbol_ascii <- list( "tick" = 'v', "cross" = 'x', "star" = '*', "square" = '[ ]', "square_small" = '[ ]', "square_small_filled" = '[x]', "circle" = '( )', "circle_filled" = '(*)', "circle_dotted" = '( )', "circle_double" = '(o)', "circle_circle" = '(o)', "circle_cross" = '(x)', "circle_pipe" = '(|)', "circle_question_mark" = '(?)', "bullet" = '*', "dot" = '.', "line" = '-', "double_line" = "=", "ellipsis" = '...', "continue" = '~', "pointer" = '>', "info" = 'i', "warning" = '!', "menu" = '=', "smiley" = ':)', "mustache" = '/\\/', "heart" = '<3', "arrow_up" = '^', "arrow_down" = 'v', "arrow_left" = '<', "arrow_right" = '>', "radio_on" = '(*)', "radio_off" = '( )', "checkbox_on" = '[x]', "checkbox_off" = '[ ]', "checkbox_circle_on" = '(x)', "checkbox_circle_off" = '( )', "fancy_question_mark" = "(?)", "neq" = "!=", "geq" = ">=", "leq" = "<=", "times" = "x", "upper_block_1" = "^", "upper_block_4" = "^", "lower_block_1" = ".", "lower_block_2" = "_", "lower_block_3" = "_", "lower_block_4" = "=", "lower_block_5" = "=", "lower_block_6" = "*", "lower_block_7" = "#", "lower_block_8" = "#", "full_block" = "#", "sup_0" = "0", "sup_1" = "1", "sup_2" = "2", "sup_3" = "3", "sup_4" = "4", "sup_5" = "5", "sup_6" = "6", "sup_7" = "7", "sup_8" = "8", "sup_9" = "9", "sup_minus" = "-", "sup_plus" = "+", "play" = ">", "stop" = "#", "record" = "o", "figure_dash" = "-", "en_dash" = "--", "em_dash" = "---", "dquote_left" = "\"", "dquote_right" = "\"", "squote_left" = "'", "squote_right" = "'" ) #' @export #' @rdname symbol list_symbols <- function() { rpad <- function(x, width) { w <- nchar(x, type = "width") paste0(x, strrep(" ", width - w)) } chars <- rpad(paste0(symbol, "\t", names(symbol)), 25) if (length(chars) %% 2) chars <- c(chars, "") chars <- paste( sep = " ", chars[1:(length(chars)/2)], chars[(length(chars)/2 + 1):length(chars)]) cat(chars, sep = "\n") } cli/R/status-bar.R0000644000176200001440000003076413603644243013451 0ustar liggesusers #' Update the status bar #' #' The status bar is the last line of the terminal. cli apps can use this #' to show status information, progress bars, etc. The status bar is kept #' intact by all semantic cli output. #' #' Use [cli_status_clear()] to clear the status bar. #' #' Often status messages are associated with processes. E.g. the app starts #' downloading a large file, so it sets the status bar accordingly. Once the #' download is done (or failed), the app typically updates the status bar #' again. cli automates much of this, via the `msg_done`, `msg_failed`, and #' `.auto_result` arguments. See examples below. #' #' @param msg The text to show, a character vector. It will be #' collapsed into a single string, and the first line is kept and cut to #' [console_width()]. The message is often associated with the start of #' a calculation. #' @param msg_done The message to use when the message is cleared, when #' the calculation finishes successfully. If `.auto_close` is `TRUE` #' and `.auto_result` is `"done"`, then this is printed automatically #' then the calling function (or `.envir`) finishes. #' @param msg_failed The message to use when the message is cleared, when #' the calculation finishes unsuccessfully. If `.auto_close` is `TRUE` #' and `.auto_result` is `"failed"`, then this is printed automatically #' then the calling function (or `.envir`) finishes. #' @param .keep What to do when this status bar is cleared. If `TRUE` then #' the content of this status bar is kept, as regular cli output (the #' screen is scrolled up if needed). If `FALSE`, then this status bar #' is deleted. #' @param .auto_close Whether to clear the status bar when the calling #' function finishes (or ‘.envir’ is removed from the stack, if #' specified). #' @param .envir Environment to evaluate the glue expressions in. It is #' also used to auto-clear the status bar if `.auto_close` is `TRUE. #' @param .auto_result What to do when auto-closing the status bar. #' @return The id of the new status bar container element, invisibly. #' #' @seealso [cli_process_start] for a higher level interface to the #' startus bar, that adds automatic styling. #' @family status bar #' @export cli_status <- function(msg, msg_done = paste(msg, "... done"), msg_failed = paste(msg, "... failed"), .keep = FALSE, .auto_close = TRUE, .envir = parent.frame(), .auto_result = c("clear", "done", "failed")) { cli__message( "status", list( id = NULL, msg = glue_cmd(msg, .envir = .envir), msg_done = glue_cmd(msg_done, .envir = .envir), msg_failed = glue_cmd(msg_failed, .envir = .envir), keep = .keep, auto_result = match.arg(.auto_result) ), .auto_close = .auto_close, .envir = .envir ) } #' Clear the status bar #' #' @param id Id of the status bar container to clear. If `id` is not the id #' of the current status bar (because it was overwritten by another #' status bar container), then the status bar is not cleared. If `NULL` #' (the default) then the status bar is always cleared. #' @param result Whether to show a message for success or failure or just #' clear the status bar. #' @param msg_done If not `NULL`, then the message to use for successful #' process termination. This overrides the message given when the status #' bar was created. #' @param msg_failed If not `NULL`, then the message to use for failed #' process termination. This overrides the message give when the status #' bar was created. #' @inheritParams cli_status #' #' @family status bar #' @export cli_status_clear <- function(id = NULL, result = c("clear", "done", "failed"), msg_done = NULL, msg_failed = NULL, .envir = parent.frame()) { cli__message( "status_clear", list( id = id %||% NA_character_, result = match.arg(result), msg_done = if (!is.null(msg_done)) glue_cmd(msg_done, .envir = .envir), msg_failed = if (!is.null(msg_failed)) glue_cmd(msg_failed, .envir = .envir) ) ) } #' Update the status bar #' #' @param msg Text to update the status bar with. `NULL` if you don't want #' to change it. #' @param msg_done Updated "done" message. `NULL` if you don't want to #' change it. #' @param msg_failed Updated "failed" message. `NULL` if you don't want to #' change it. #' @param id Id of the status bar to update. Defaults to the current #' status bar container. #' @param .envir Environment to evaluate the glue expressions in. #' @return Id of the status bar container. #' #' @family status bar #' @export cli_status_update <- function(id = NULL, msg = NULL, msg_done = NULL, msg_failed = NULL, .envir = parent.frame()) { cli__message( "status_update", list( msg = if (!is.null(msg)) glue_cmd(msg, .envir = .envir), msg_done = if (!is.null(msg_done)) glue_cmd(msg_done, .envir = .envir), msg_failed = if (!is.null(msg_failed)) glue_cmd(msg_failed, .envir = .envir), id = id %||% NA_character_ ) ) } #' Indicate the start and termination of some computation in the status bar #' #' Typically you call `cli_process_start()` to start the process, and then #' `cli_process_done()` when it is done. If an error happens before #' `cli_process_fone()` is called, then cli automatically shows the message #' for unsuccessful termination. #' #' If you handle the errors of the process or computation, then you can do #' the opposite: call `cli_process_start()` with `on_exit = "done"`, and #' in the error handler call `cli_process_failed()`. cli will automatically #' call `cli_process_done()` on successful termination, when the calling #' function finishes. #' #' See examples below. #' #' @param msg The message to show to indicate the start of the process or #' compuration. It will be collapsed into a single string, and the first #' line is kept and cut to [console_width()]. #' @param msg_done The message to use for successful termination. #' @param msg_failed The message to use for unsuccessful termination. #' @param on_exit Whether this process should fail or terminate #' successfully when the calling function (or the environment in `.envir`) #' exits.x #' @param msg_class The style class to add to the message. Use an empty #' string to suppress styling. #' @param done_class The style class to add to the successful termination #' message. Use an empty string to suppress styling.a #' @param failed_class The style class to add to the unsuccessful #' termination message. Use an empty string to suppress styling.a #' @inheritParams cli_status #' @return Id of the status bar container. #' #' @family status bar #' @export #' @examples #' #' ## Failure by default #' fun <- function() { #' cli_process_start("Calculating") #' if (interactive()) Sys.sleep(1) #' if (runif(1) < 0.5) stop("Failed") #' cli_process_done() #' } #' tryCatch(fun(), error = function(err) err) #' #' ## Success by default #' fun2 <- function() { #' cli_process_start("Calculating", on_exit = "done") #' tryCatch({ #' if (interactive()) Sys.sleep(1) #' if (runif(1) < 0.5) stop("Failed") #' }, error = function(err) cli_process_failed()) #' } #' fun2() cli_process_start <- function(msg, msg_done = paste(msg, "... done"), msg_failed = paste(msg, "... failed"), on_exit = c("failed", "done"), msg_class = "alert-info", done_class = "alert-success", failed_class = "alert-danger", .auto_close = TRUE, .envir = parent.frame()) { # Force the defaults, because we might modify msg msg_done msg_failed if (length(msg_class) > 0 && msg_class != "") { msg <- paste0("{.", msg_class, " ", msg, "}") } if (length(done_class) > 0 && done_class != "") { msg_done <- paste0("{.", done_class, " ", msg_done, "}") } if (length(failed_class) > 0 && failed_class != "") { msg_failed <- paste0("{.", failed_class, " ", msg_failed, "}") } cli_status(msg, msg_done, msg_failed, .auto_close = .auto_close, .envir = .envir, .auto_result = match.arg(on_exit)) } #' @param id Id of the status bar container to clear. If `id` is not the id #' of the current status bar (because it was overwritten by another #' status bar container), then the status bar is not cleared. If `NULL` #' (the default) then the status bar is always cleared. #' #' @rdname cli_process_start #' @export cli_process_done <- function(id = NULL, msg_done = NULL, .envir = parent.frame(), done_class = "alert-success") { if (!is.null(msg_done) && length(done_class) > 0 && done_class != "") { msg_done <- paste0("{.", done_class, " ", msg_done, "}") } cli_status_clear(id, result = "done", msg_done = msg_done, .envir = .envir) } #' @rdname cli_process_start #' @export cli_process_failed <- function(id = NULL, msg = NULL, msg_failed = NULL, .envir = parent.frame(), failed_class = "alert-danger") { if (!is.null(msg_failed) && length(failed_class) > 0 && failed_class != "") { msg_failed <- paste0("{.", failed_class, " ", msg_failed, "}") } cli_status_clear( id, result = "failed", msg_failed = msg_failed, .envir = .envir ) } # ----------------------------------------------------------------------- clii_status <- function(app, id, msg, msg_done, msg_failed, keep, auto_result) { bar_app <- cliapp( theme = NULL, user_theme = NULL, output = app$output ) bar_app$themes <- app$themes clii__container_start(bar_app, "div", class = "statusbar", id = id) app$status_bar[[id]] <- list( app = bar_app, content = "", msg_done = msg_done, msg_failed = msg_failed, keep = keep, auto_result = auto_result ) clii_status_update(app, id, msg, msg_done = NULL, msg_failed = NULL) } clii_status_clear <- function(app, id, result, msg_done, msg_failed) { ## If NA then the most recent one if (is.na(id)) id <- names(app$status_bar)[1] ## If no active status bar, then ignore if (is.na(id)) return(invisible()) if (! id %in% names(app$status_bar)) return(invisible()) if (result == "done") { msg <- msg_done %||% app$status_bar[[id]]$msg_done clii_status_update(app, id, msg, NULL, NULL) app$status_bar[[id]]$keep <- TRUE } else if (result == "failed") { msg <- msg_failed %||% app$status_bar[[id]]$msg_failed clii_status_update(app, id, msg, NULL, NULL) app$status_bar[[id]]$keep <- TRUE } if (names(app$status_bar)[1] == id) { ## This is the active one if (app$status_bar[[id]]$keep) { ## Keep? Just emit it app$cat("\n") } else { ## Not keep? Remove it clii__clear_status_bar(app) } } else { if (app$status_bar[[id]]$keep) { ## Keep? clii__clear_status_bar(app) app$cat(paste0(app$status_bar[[id]]$content, "\n")) app$cat(paste0(app$status_bar[[1]]$content)) } else { ## Not keep? Nothing to output } } ## Remove app$status_bar[[id]] <- NULL ## Switch to the previous one if (length(app$status_bar)) app$cat(paste0(app$status_bar[[1]]$content)) } #' @importFrom fansi substr_ctl clii_status_update <- function(app, id, msg, msg_done, msg_failed) { ## If NA then the most recent one if (is.na(id)) id <- names(app$status_bar)[1] ## If no active status bar, then ignore if (is.na(id)) return(invisible()) ## Update messages if (!is.null(msg_done)) app$status_bar[[id]]$msg_done <- msg_done if (!is.null(msg_failed)) app$status_bar[[id]]$msg_failed <- msg_failed ## Do we have a new message? if (is.null(msg)) return(invisible()) ## Otherwise clear line if (length(app$status_bar)) clii__clear_status_bar(app) ## Format the line content <- "" withCallingHandlers( app$status_bar[[id]]$app$xtext(msg), message = function(msg) { content <<- paste0(content, msg$message) invokeRestart("muffleMessage") } ) content <- strsplit(content, "\r?\n")[[1]][1] ## Update status bar, put it in front app$status_bar[[id]]$content <- content app$status_bar <- c( app$status_bar[id], app$status_bar[setdiff(names(app$status_bar), id)]) ## New content app$cat(content) } #' @importFrom fansi nchar_ctl clii__clear_status_bar <- function(app) { text <- app$status_bar[[1]]$content len <- nchar_ctl(text) app$cat(paste0("\r", strrep(" ", len), "\r")) } cli/R/app.R0000644000176200001440000000424613572171767012153 0ustar liggesusers cliappenv <- new.env() cliappenv$stack <- list() cliappenv$pid <- Sys.getpid() #' Start, stop, query the default cli application #' #' `start_app` creates an app, and places it on the top of the app stack. #' #' `stop_app` removes the top app, or multiple apps from the app stack. #' #' `default_app` returns the default app, the one on the top of the stack. #' #' @param theme Theme to use. #' @param output How to print the output. #' @param .auto_close Whether to stop the app, when the calling frame #' is destroyed. #' @param .envir The environment to use, instead of the calling frame, #' to trigger the stop of the app. #' @param app App to stop. If `NULL`, the current default app is stopped. #' Otherwise we find the supplied app in the app stack, and remote it, #' together with all the apps above it. #' @return #' `start_app` returns the new app, `default_app` returns the default app. #' `stop_app` does not return anything. #' #' @export start_app <- function(theme = getOption("cli.theme"), output = c("auto", "message", "stdout", "stderr"), .auto_close = TRUE, .envir = parent.frame()) { if (! inherits(output, "connection")) output <- match.arg(output) app <- cliapp( theme = theme, user_theme = getOption("cli.user_theme"), output = output ) cliappenv$stack[[length(cliappenv$stack) + 1]] <- app if (.auto_close && !identical(.envir, globalenv())) { defer(stop_app(app = app), envir = .envir, priority = "first") } invisible(app) } #' @export #' @importFrom utils head #' @name start_app stop_app <- function(app = NULL) { if (is.null(app)) { cliappenv$stack <- head(cliappenv$stack, -1) } else { if (!inherits(app, "cliapp")) stop("Not a CLI app") ndl <- format.default(app) nms <- vapply(cliappenv$stack, format.default, character(1)) if (! ndl %in% nms) { warning("No app to end") return() } wh <- which(nms == ndl)[1] cliappenv$stack <- head(cliappenv$stack, wh - 1) } invisible() } #' @export #' @importFrom utils tail #' @name start_app default_app <- function() { top <- tail(cliappenv$stack, 1) if (length(top)) top[[1]] else NULL } cli/R/tty.R0000644000176200001440000001266513572271360012205 0ustar liggesusers is_interactive <- function() { opt <- getOption("rlib_interactive") if (isTRUE(opt)) { TRUE } else if (identical(opt, FALSE)) { FALSE } else if (tolower(getOption("knitr.in.progress", "false")) == "true") { FALSE } else if (tolower(getOption("rstudio.notebook.executing", "false")) == "true") { FALSE } else if (identical(Sys.getenv("TESTTHAT"), "true")) { FALSE } else { interactive() } } #' The connection option that cli would use #' #' Note that this only refers to the current R process. If the output #' is produced in another process, then it is not relevant. #' #' In interactive sessions the standard output is chosen, othrwise the #' standard error is used. This is to avoid painting output messages red #' in the R GUIs. #' #' @return Connection object. #' #' @export cli_output_connection <- function() { if (is_interactive()) stdout() else stderr() } is_stdout <- function(stream) { identical(stream, stdout()) && sink.number() == 0 } is_stderr <- function(stream) { identical(stream, stderr()) && sink.number("message") == 2 } is_stdx <- function(stream){ is_stdout(stream) || is_stderr(stream) } is_rstudio_dynamic_tty <- function(stream) { rstudio$detect()[["dynamic_tty"]] && (is_stdout(stream) || is_stderr(stream)) } is_rapp <- function() { Sys.getenv("R_GUI_APP_VERSION") != "" } is_rapp_stdx <- function(stream) { interactive() && is_rapp() && (is_stdout(stream) || is_stderr(stream)) } is_emacs <- function() { Sys.getenv("EMACS") != "" || Sys.getenv("INSIDE_EMACS") != "" } is_rkward <- function() { "rkward" %in% (.packages()) } is_rkward_stdx <- function(stream) { interactive() && is_rkward() && (is_stdout(stream) || is_stderr(stream)) } #' Detect whether a stream supports `\\r` (Carriage return) #' #' In a terminal, `\\r` moves the cursor to the first position of the #' same line. It is also supported by most R IDEs. `\\r` is typically #' used to achive a more dynamic, less cluttered user interface, e.g. #' to create progress bars. #' #' If the output is directed to a file, then `\\r` characters are typically #' unwanted. This function detects if `\\r` can be used for the given #' stream or not. #' #' The detection mechanism is as follows: #' 1. If the `cli.dynamic` option is set to `TRUE`, `TRUE` is returned. #' 2. If the `cli.dynamic` option is set to anything else, `FALSE` is #' returned. #' 3. If the `R_CLI_DYNAMIC` environment variable is not empty and set to #' the string `"true"`, `"TRUE"` or `"True"`, `TRUE` is returned. #' 4. If `R_CLI_DYNAMIC` is not empty and set to anything else, `FALSE` is #' returned. #' 5. If the stream is a terminal, then `TRUE` is returned. #' 6. If the stream is the standard output or error within RStudio, #' the macOS R app, or RKWard IDE, `TRUE` is returned. #' 7. Otherwise `FALSE` is returned. #' #' @param stream The stream to inspect, an R connection object. #' Note that it defaults to the standard _error_ stream, since #' informative messages are typically printed there. #' #' @family terminal capabilities #' @export #' @examples #' is_dynamic_tty() #' is_dynamic_tty(stdout()) is_dynamic_tty <- function(stream = cli_output_connection()) { ## Option? if (!is.null(x <- getOption("cli.dynamic"))) { return(isTRUE(x)) } ## Env var? if ((x <- Sys.getenv("R_CLI_DYNAMIC", "")) != "") { return(isTRUE(as.logical(x))) } ## Autodetect... ## RGui has isatty(stdout()) and isatty(stderr()), so we don't need ## to check that explicitly isatty(stream) || is_rstudio_dynamic_tty(stream) || is_rapp_stdx(stream) || is_rkward_stdx(stream) } ANSI_ESC <- "\u001B[" ANSI_HIDE_CURSOR <- paste0(ANSI_ESC, "?25l") ANSI_SHOW_CURSOR <- paste0(ANSI_ESC, "?25h") #' Detect if a stream support ANSI escape characters #' #' We check that all of the following hold: #' * The stream is a terminal. #' * The platform is Unix. #' * R is not running inside R.app (the macOS GUI). #' * R is not running inside RStudio. #' * R is not running inside Emacs. #' * The terminal is not "dumb". #' * `stream` is either the standard output or the standard error stream. #' #' @param stream The stream to check. #' @return `TRUE` or `FALSE`. #' #' @family terminal capabilities #' @export #' @examples #' is_ansi_tty() is_ansi_tty <- function(stream = stderr()) { # RStudio is handled separately if (rstudio$detect()[["ansi_tty"]] && is_stdx(stream)) return(TRUE) isatty(stream) && .Platform$OS.type == "unix" && !is_rapp() && !is_emacs() && Sys.getenv("TERM", "") != "dumb" && is_stdx(stream) } #' Hide/show cursor in a terminal #' #' This only works in terminal emulators. In other environments, it #' does nothing. #' #' `ansi_hide_cursor()` hides the cursor. #' #' `ansi_show_cursor()` shows the cursor. #' #' `ansi_with_hidden_cursor()` temporarily hides the cursor for #' evaluating an expression. #' #' @param stream The stream of the terminal to output the ANSI sequence to. #' @param expr R expression to evaluate. #' #' @family terminal capabiltiies #' @export ansi_hide_cursor <- function(stream = stderr()) { if (is_ansi_tty(stream)) cat(ANSI_HIDE_CURSOR, file = stream) } #' @export #' @name ansi_hide_cursor ansi_show_cursor <- function(stream = stderr()) { if (is_ansi_tty(stream)) cat(ANSI_SHOW_CURSOR, file = stream) } #' @export #' @name ansi_hide_cursor ansi_with_hidden_cursor <- function(expr, stream = stderr()) { ansi_hide_cursor(stream) on.exit(ansi_show_cursor(), add = TRUE) expr } cli/R/themes.R0000644000176200001440000002465113604653535012654 0ustar liggesusers #' List the currently active themes #' #' If there is no active app, then it calls [start_app()]. #' #' @return A list of data frames with the active themes. #' Each data frame row is a style that applies to selected CLI tree nodes. #' Each data frame has columns: #' * `selector`: The original CSS-like selector string. See [themes]. #' * `parsed`: The parsed selector, as used by cli for matching to nodes. #' * `style`: The original style. #' * `cnt`: The id of the container the style is currently applied to, or #' `NA` if the style is not used. #' #' @export #' @seealso themes cli_list_themes <- function() { app <- default_app() %||% start_app() app$list_themes() } clii_list_themes <- function(app) { app$themes } clii_add_theme <- function(app, theme) { id <- new_uuid() app$themes <- c(app$themes, structure(list(theme_create(theme)), names = id)) id } clii_remove_theme <- function(app, id) { if (! id %in% names(app$themes)) return(invisible(FALSE)) app$themes[[id]] <- NULL invisible(TRUE) } #' The built-in CLI theme #' #' This theme is always active, and it is at the bottom of the theme #' stack. See [themes]. #' #' @seealso [themes], [simple_theme()]. #' @return A named list, a CLI theme. #' #' @param dark Whether to use a dark theme. The `cli_theme_dark` option #' can be used to request a dark theme explicitly. If this is not set, #' or set to `"auto"`, then cli tries to detect a dark theme, this #' works in recent RStudio versions and in iTerm on macOS. #' @export builtin_theme <- function(dark = getOption("cli_theme_dark", "auto")) { dark <- detect_dark_theme(dark) list( body = list( "class-map" = list( fs_path = "file" ) ), h1 = list( "font-weight" = "bold", "margin-top" = 1, "margin-bottom" = 0, fmt = function(x) cli::rule(x, line_col = "cyan")), h2 = list( "font-weight" = "bold", "margin-top" = 1, "margin-bottom" = 1, fmt = function(x) paste0(symbol$line, symbol$line, " ", x, " ", symbol$line, symbol$line)), h3 = list( "margin-top" = 1, fmt = function(x) paste0(symbol$line, symbol$line, " ", x, " ")), ".alert" = list( before = paste0(symbol$arrow_right, " ") ), ".alert-success" = list( before = paste0(crayon::green(symbol$tick), " ") ), ".alert-danger" = list( before = paste0(crayon::red(symbol$cross), " ") ), ".alert-warning" = list( before = paste0(crayon::yellow("!"), " ") ), ".alert-info" = list( before = paste0(crayon::cyan(symbol$info), " ") ), par = list("margin-top" = 0, "margin-bottom" = 1), li = list("padding-left" = 2), ul = list("list-style-type" = symbol$bullet, "padding-left" = 0), "ul ul" = list("list-style-type" = symbol$circle, "padding-left" = 2), "ul ul ul" = list("list-style-type" = symbol$line), "ul ul" = list("padding-left" = 2), "ul dl" = list("padding-left" = 2), "ol ol" = list("padding-left" = 2), "ol ul" = list("padding-left" = 2), "ol dl" = list("padding-left" = 2), "dl ol" = list("padding-left" = 2), "dl ul" = list("padding-left" = 2), "dl dl" = list("padding-left" = 2), blockquote = list("padding-left" = 4L, "padding-right" = 10L, "font-style" = "italic", "margin-top" = 1L, "margin-bottom" = 1L, before = symbol$dquote_left, after = symbol$dquote_right), "blockquote cite" = list(before = paste0(symbol$em_dash, " "), "font-style" = "italic", "font-weight" = "bold"), .code = list(fmt = format_code(dark)), .code.R = list(fmt = format_r_code(dark)), span.emph = list("font-style" = "italic"), span.strong = list("font-weight" = "bold"), span.code = theme_code_tick(dark), span.pkg = list(color = "blue"), span.fn = theme_function(dark), span.fun = theme_function(dark), span.arg = theme_code_tick(dark), span.kbd = list(before = "[", after = "]", color = "blue"), span.key = list(before = "[", after = "]", color = "blue"), span.file = list(color = "blue"), span.path = list(color = "blue"), span.email = list(color = "blue"), span.url = list(before = "<", after = ">", color = "blue", "font-style" = "italic"), span.var = theme_code_tick(dark), span.envvar = theme_code_tick(dark), span.val = list( transform = function(x, ...) cli_format(x, ...), color = "blue" ), span.field = list(color = "green") ) } detect_dark_theme <- function(dark) { if (dark == "auto") { dark <- if (Sys.getenv("RSTUDIO", "0") == "1") { tryCatch( rstudioapi::getThemeInfo()$dark, error = function(x) FALSE) } else if (is_iterm()) { is_iterm_dark() } else { FALSE } } dark } theme_code <- function(dark) { if (dark) { list("background-color" = "#232323", color = "#d0d0d0") } else{ list("background-color" = "#e8e8e8", color = "#202020") } } theme_code_tick <- function(dark) { modifyList(theme_code(dark), list(before = "`", after = "`")) } theme_function <- function(dark) { modifyList(theme_code(dark), list(before = "`", after = "()`")) } format_r_code <- function(dark) { function(x) { x <- crayon::strip_style(x) lines <- strsplit(x, "\n", fixed = TRUE)[[1]] tryCatch(prettycode::highlight(lines), error = function(x) lines) } } format_code <- function(dark) { function(x) { unlist(strsplit(x, "\n", fixed = TRUE)) } } theme_create <- function(theme) { mtheme <- theme mtheme[] <- lapply(mtheme, create_formatter) selectors <- names(theme) res <- data.frame( stringsAsFactors = FALSE, selector = as.character(selectors), parsed = I(lapply(selectors, parse_selector) %||% list()), style = I(mtheme %||% list()), cnt = rep(NA_character_, length(selectors)) ) rownames(res) <- NULL res } #' @importFrom crayon bold italic underline make_style combine_styles create_formatter <- function(x) { is_bold <- identical(x[["font-weight"]], "bold") is_italic <- identical(x[["font-style"]], "italic") is_underline <- identical(x[["text-decoration"]], "underline") is_color <- "color" %in% names(x) is_bg_color <- "background-color" %in% names(x) if (!is_bold && !is_italic && !is_underline && !is_color && !is_bg_color) return(x) fmt <- c( if (is_bold) list(bold), if (is_italic) list(italic), if (is_underline) list(underline), if (is_color) make_style(x[["color"]]), if (is_bg_color) make_style(x[["background-color"]], bg = TRUE) ) new_fmt <- do.call(combine_styles, fmt) if (is.null(x[["fmt"]])) { x[["fmt"]] <- new_fmt } else { orig_fmt <- x[["fmt"]] x[["fmt"]] <- function(x) orig_fmt(new_fmt(x)) } x } #' @importFrom utils modifyList merge_embedded_styles <- function(old, new) { # before and after is not inherited, # side margins are additive, class mappings are merged # rest is updated, counter is reset old$before <- old$after <- NULL top <- new$`margin-top` %||% 0L bottom <- new$`margin-bottom` %||% 0L left <- (old$`margin-left` %||% 0L) + (new$`margin-left` %||% 0L) right <- (old$`margin-right` %||% 0L) + (new$`margin-right` %||% 0L) map <- modifyList(old$`class-map` %||% list(), new$`class-map` %||% list()) start <- new$start %||% 1L mrg <- modifyList(old, new) mrg[c("margin-top", "margin-bottom", "margin-left", "margin-right", "start", "class-map")] <- list(top, bottom, left, right, start, map) ## Formatter needs to be re-generated create_formatter(mrg) } #' Parse a CSS3-like selector #' #' This is the rather small subset of CSS3 that is supported: #' #' Selectors: #' #' * Type selectors, e.g. `input` selects all `` elements. #' * Class selectors, e.g. `.index` selects any element that has a class #' of "index". #' * ID selector. `#toc` will match the element that has the ID "toc". #' #' Combinators: #' #' * Descendant combinator, i.e. the space, that combinator selects nodes #' that are descendants of the first element. E.g. `div span` will match #' all `` elements that are inside a `
` element. #' #' @param x CSS3-like selector string. #' #' @keywords internal parse_selector <- function(x) { lapply(strsplit(x, " ", fixed = TRUE)[[1]], parse_selector_node) } parse_selector_node <- function(x) { parse_ids <- function(y) { r <- strsplit(y, "#", fixed = TRUE)[[1]] if (length(r) > 1) r[-1] <- paste0("#", r[-1]) r } parts <- strsplit(x, ".", fixed = TRUE)[[1]] if (length(parts) > 1) parts[-1] <- paste0(".", parts[-1]) parts <- unlist(lapply(parts, parse_ids)) parts <- parts[parts != ""] m_cls <- grepl("^\\.", parts) m_ids <- grepl("^#", parts) list(tag = as.character(unique(parts[!m_cls & !m_ids])), class = str_tail(unique(parts[m_cls])), id = str_tail(unique(parts[m_ids]))) } #' Match a selector node to a container #' #' @param node Selector node, as parsed by `parse_selector_node()`. #' @param cnt Container node, has elements `tag`, `id`, `class`. #' #' The selector node matches the container, if all these hold: #' #' * The id of the selector is missing or unique. #' * The tag of the selector is missing or unique. #' * The id of the container is missing or unique. #' * The tag of the container is unique. #' * If the selector specifies an id, it matches the id of the container. #' * If the selector specifies a tag, it matxhes the tag of the container. #' * If the selector specifies class names, the container has all these #' classes. #' #' @keywords internal match_selector_node <- function(node, cnt) { if (length(node$id) > 1 || length(cnt$id) > 1) return(FALSE) if (length(node$tag) > 1 || length(cnt$tag) > 1) return(FALSE) all(node$id %in% cnt$id) && all(node$tag %in% cnt$tag) && all(node$class %in% cnt$class) } #' Match a selector to a container stack #' #' @param sels A list of selector nodes. #' @param cnts A list of container nodes. #' #' The last selector in the list must match the last container, so we #' do the matching from the back. This is because we use this function #' to calculate the style of newly encountered containers. #' #' @keywords internal match_selector <- function(sels, cnts) { sptr <- length(sels) cptr <- length(cnts) while (sptr != 0L && sptr <= cptr) { if (match_selector_node(sels[[sptr]], cnts[[cptr]])) { sptr <- sptr - 1L cptr <- cptr - 1L } else { cptr <- cptr - 1L } } sptr == 0 } cli/R/sitrep.R0000644000176200001440000000323213574535455012673 0ustar liggesusers #' cli situation report #' #' Contains currenty: #' * `cli_unicode_option`: whether the `cli.unicode` option is set and its #' value. See [is_utf8_output()]. #' * `symbol_charset`: the selected character set for [symbol], UTF-8, #' Windows, or ASCII. #' * `console_utf8`: whether the console supports UTF-8. See #' [base::l10n_info()]. #' * `latex_active`: whether we are inside knitr, creating a LaTeX #' document. #' * `num_colors`: number of ANSI colors. See [crayon::num_colors()]. #' * `console_with`: detected console width. #' #' @return Named list with entries listed above. It has a `cli_sitrep` #' class, with a `print()` and `format()` method. #' #' @export #' @examples #' cli_sitrep() cli_sitrep <- function() { structure( list( cli_unicode_option = getOption("cli.unicode", NULL), symbol_charset = get_active_symbol_set(), console_utf8 = l10n_info()$`UTF-8`, latex_active = is_latex_output(), num_colors = crayon::num_colors(), console_width = console_width()), class = "cli_sitrep") } #' @export print.cli_sitrep <- function(x, ...) { cat(format(x, ...), sep = "\n") invisible(x) } get_active_symbol_set <- function() { if (identical(symbol, symbol_utf8)) { "UTF-8" } else if (identical(symbol, symbol_rstudio)) { "RStudio (UTF-8)" } else if (identical(symbol, symbol_win)) { "Windows (non UTF-8)" } else { "ASCII (non UTF-8)" } } #' @export format.cli_sitrep <- function(x, ...) { fmt_names <- format(names(x)) fmt_vals <- vapply(x, format, character(1)) paste0("- ", fmt_names, " : ", fmt_vals) } #' @export as.character.cli_sitrep <- function(x, ...) { "" } cli/R/format.R0000644000176200001440000000311213571734652012647 0ustar liggesusers #' Format a value for printing #' #' This function can be used directly, or via the `{.val ...}` inline #' style. `{.val {expr}}` calls `cli_format()` automatically on the value #' of `expr`, before styling and collapsing it. #' #' It is possible to define new S3 methods for `cli_format` and then #' these will be used automatically for `{.cal ...}` expressions. #' #' @param x The object to format. #' @param style List of formatting options, see the individual methods #' for the style options they support. #' @param ... Additional arguments for methods. #' #' @export #' @examples #' things <- c(rep("this", 3), "that") #' cli_format(things) #' cli_text("{.val {things}}") #' #' nums <- 1:5 / 7 #' cli_format(nums, style = list(digits = 2)) #' cli_text("{.val {nums}}") #' divid <- cli_div(theme = list(.val = list(digits = 3))) #' cli_text("{.val {nums}}") #' cli_end(divid) cli_format <- function(x, style = list(), ...) UseMethod("cli_format") #' @rdname cli_format #' @export cli_format.default <- function(x, style = list(), ...) { x } #' * Styles for character vectors: #' - `string_quote` is the quoting character for [encodeString()]. #' #' @rdname cli_format #' @export cli_format.character <- function(x, style = list(), ...) { quote <- style$string_quote %||% "'" encodeString(x, quote = quote) } #' * Styles for numeric vectors: #' - `digits` is the number of digits to print after the decimal point. #' #' @rdname cli_format #' @export cli_format.numeric <- function(x, style = list(), ...) { digits <- style$digits if (!is.null(digits)) x <- round(x, digits) x } cli/NEWS.md0000644000176200001440000000343313605174414012127 0ustar liggesusers # cli 2.0.1 * Symbols (`symbol$*`) are now correctly printed in RStudio on Windows (#124). * The default theme for `cli_code()` output looks better now, especially in RStudio (#123). * Remove spurious newline after a `cli_process_start()` was cleared manually, and also at the end of the function. # cli 2.0.0 ## Semantic command line interface tools cli 2.0.0 has a new set of functions that help creating a CLI using a set of higher level elements: headings, paragraphs, lists, alerts, code blocks, etc. The formatting of all elements can be customized via themes. See the "Building a semantic CLI" article on the package web site: https://cli.r-lib.org ## Bug fixes: * Fix a bug in `is_dynamic_tty()`, setting `R_CLI_DYNAMIC="FALSE"` now properly turns dynamic tty off (#70). # cli 1.1.0 * cli has now functions to add ANSI styles to text. These use the crayon package internally, and provide a simpler interface. See the `col_*`, `bg_*`, `style_*` and also the `make_ansi_style()` and `combine_ansi_styles()` functions (#51). * New `is_dynamic_tty()` function detects if `\r` should be used for a stream (#62). * New `is_ansi_tty()` function detects if ANSI control sequences can be used for a stream. * New `ansi_hide_cursor()`, `ansi_show_cursor()` and `ansi_with_hidden_cursor()` functions to hide and show the cursor in terminals. * New `make_spinner()` function helps integrating spinners into your functions. * Now `symbol` always uses ASCII symbols when the `cli.unicode` option is set to `FALSE`. # 1.0.1 * New `cli_sitrep()` function, situation report about UTF-8 and ANSI color support (#53). * Fall back to ASCII only characters on non-Windows platforms without UTF-8 support, and also in LaTeX when running knitr (#34). # cli 1.0.0 First public release. cli/MD50000644000176200001440000002035213605457531011344 0ustar liggesusers78e28f9ca0356384e76944908bf0e076 *DESCRIPTION 4f631b60276f1da4fb82bf51eb99ea9d *LICENSE ad5352399cebaefceac7d5ac6c2c7f4d *NAMESPACE 5c9f2e506e842006a2cf78054b39d9a3 *NEWS.md dc1f4c50de7ec5bae8cd577360bc98af *R/app.R d2f5eabaed39d2b012c2490d5310eecd *R/assertions.R f70df326a4e14bbdc143d1c984663303 *R/box-styles.R 5a2ec68e5d39ab6ff2adadecdbecb28b *R/boxes.R 9fd28cd2d5b5ff2d368b556683850b62 *R/cat.R 8c964a700d14a898e0173e3ed7515be8 *R/cli.R 48d36f26b7eaff4ad5d88e340420781c *R/cliapp-docs.R 507b1202b0428606a33fbcb8b8470d9b *R/cliapp.R 290966de2bf96add3124262e084c50ee *R/containers.R 1f342ff7bca16715e7f5ba86c005dd35 *R/crayon.R 3180b2d8841fd2361e3ba364018d7e0e *R/defer.R 5ae1b030f59f80e3a62d34185158d1ca *R/format.R d7cd6c8fbf3a36d352974dd6c93f4e0d *R/inline.R a7fb4eabb7d3432a2fc450ab81bcfff5 *R/internals.R f92f30b1b2157057f5973f8b94b208e0 *R/lazyrmd.R ff681a26e738a802bd685bd2961a5039 *R/lorem.R b3202fa47bb1c1b7e31d09a2d12a7cec *R/onload.R 63c1297bd840dfca787e116aade2db05 *R/pluralize.R 718124ac84f5968e723a62a5bf33fe72 *R/print.R e77f5d61f6af7d021459b20afd3e761e *R/rstudio-detect.R 9ba20c09bcb1195a7d2f9a94d345d235 *R/rules.R 6314e0be26278524847b03e0eaa82536 *R/server.R ec2a88a37f1c73f3272b173932417963 *R/simple-theme.R fa76bc22febbf6cce703df4e78c1e305 *R/sitrep.R 71f05740ac78e6146934545bd7716bac *R/spinner.R 4ac158aa3e7f29f1f0acd159873410b3 *R/status-bar.R b3cf4be18d5d00e40d3b1a11f2795f91 *R/symbol.R f99b159e5a37f7f11f1768ec08f3f1ec *R/sysdata.rda 4227b0d3051c59bda1a6e3a2dc5de5d5 *R/themes.R 2657c09ba035e1a696343d4698a620ac *R/tree.R ab0d279cc3fcc654b0ca47a0c7588cee *R/tty.R fde9a9c7e0a13b06f558e8935d60021d *R/utf8.R 5c34eb1c7a9bac7be766733c8be8b02c *R/utils.R 67e81a2b922925593eec470d2853e605 *R/width.R 9ac6b6c5d3c84e9bd1a047ce5ba31aac *README.md 5fadfacdf8127205ecc36960eef2a4fc *build/vignette.rds cc62ba7c3570178d335d862ed6a99096 *inst/doc/pluralization.R 92cde6752fcbc1640d34f105c9561922 *inst/doc/pluralization.Rmd 87cb0d0450e30ae4662160b232367158 *inst/doc/pluralization.html 31e4fcd9b4c135bb253e9314857084b5 *inst/examples/apps/news.R d71030204ac721b2b6fb9dd1f013066b *inst/examples/apps/outdated.R e3540d59292af42b3b1570c25cb55260 *inst/examples/apps/search.R 234ede852bac1dc638fc0d774a764ee8 *inst/examples/apps/up.R aaffd10fd0246bb9d2333d0d2c920093 *inst/logo.txt 31e4fcd9b4c135bb253e9314857084b5 *inst/scripts/news.R b6a34628690fc7587a54c6f271956f3c *inst/scripts/outdated.R 7b3b382306a00ed2ed4751dc20684556 *inst/scripts/search.R 004ffa924d426a2165686502c0aab396 *inst/scripts/up.R a1c31830c6e464bafd5cfb56855d0a17 *man/ansi-styles.Rd a5a302c7ec2146d1a163a9d32c1e4768 *man/ansi_hide_cursor.Rd 9ec6cf85c5f1973e8ed3c335e81c5d75 *man/boxx.Rd 6e45f3c7c37baecd569518cf9aa984f2 *man/builtin_theme.Rd 0ef346e3cb39482c9e9eb8fbc3408f8c *man/cat_line.Rd 6fd010030e1430b56e266d19f51d60fb *man/chunks/pluralization.Rmd ce243cbe29bf54da8462c9532987ec02 *man/cli_alert.Rd 7566c81c5cc6eab0ac23179a392f98d2 *man/cli_blockquote.Rd 2f4820a6c2600bc7a33dde661e60e58e *man/cli_code.Rd 13d93fc0b1ffd78a0f6ecff315003efc *man/cli_div.Rd 23bd64058a50fdaaa0661e97598de962 *man/cli_dl.Rd 0462d2c6ea3c69b3e9fd214471a9eb9b *man/cli_end.Rd c2c6456fa70471dcc2846aa087dd0e76 *man/cli_format.Rd 304d09c6e0020b2247bfa9fe7ac558b8 *man/cli_format_method.Rd cba53132f5336526d7d4095c4701113e *man/cli_h1.Rd 9b183bd061a28fe229fca565bb74ebce *man/cli_li.Rd 2fcdf20e54e5bb95c045df71bb8fe113 *man/cli_list_themes.Rd 8dac4dcc4cc28497d34da4f1efb5417a *man/cli_ol.Rd 46c17c8ba0b0bf2d6945fb9a5761bd4e *man/cli_output_connection.Rd c8b322d8d9667c815c3f9f629b4d3324 *man/cli_par.Rd 1c1e70f84f29dc9e4288233f5b832547 *man/cli_process_start.Rd 41b813ca6b3eba0c50e79e97eb6b8426 *man/cli_rule.Rd 1a06535e6b792c08146792be9f6159a7 *man/cli_sitrep.Rd cb232dd171505a72f44d3a1d20b6ca14 *man/cli_status.Rd 2ef330cb252e868958334ada431c98ee *man/cli_status_clear.Rd 677acb4c0e03741da245867fbdf43da5 *man/cli_status_update.Rd dd6c62e3bba43be12259f123c2bb0161 *man/cli_text.Rd acb5c77f2f97b6068378d3c07553ec58 *man/cli_ul.Rd 38caf9cc05ccc86b25349ec5a779fcc5 *man/cli_verbatim.Rd 231333e05eee973a6c897fdb3b075f56 *man/combine_ansi_styles.Rd af2b8af7935f0dacce4f41f646885a85 *man/console_width.Rd 2b6d1c4c234a395829f12fad90c59d9e *man/containers.Rd e67071d0e1f3ba9cbd332a122e0643fd *man/demo_spinners.Rd bceaacfc6c79757d0c69d89b9c81425c *man/figures/README/unnamed-chunk-10.svg 207ccb03f7c7b31eea7349423490c974 *man/figures/README/unnamed-chunk-11.svg 864bfbc7604b6b2d396cb145b71a6af2 *man/figures/README/unnamed-chunk-12.svg 8525851a5e88f0d2819fb55b7f8af733 *man/figures/README/unnamed-chunk-13.svg 6cfb9d42fc2ebb92937da1aa5bf99c95 *man/figures/README/unnamed-chunk-2.svg 682f9d1a7c8cd48e10b1d238e439b694 *man/figures/README/unnamed-chunk-3.svg c27ccd26dedd1fbe7205508fabcd9678 *man/figures/README/unnamed-chunk-4.svg b2a1fe480374ffb57759688b8db402e4 *man/figures/README/unnamed-chunk-5.svg 0d6017c8849788b39079e42ab860df79 *man/figures/README/unnamed-chunk-6.svg 65667c91bf957204f06ce0149670b418 *man/figures/README/unnamed-chunk-7.svg 462d80ccdc44979b121913ba4c4b53bf *man/figures/README/unnamed-chunk-8.svg 08c35184df22da6c5cc4c66dc72c2e71 *man/figures/README/unnamed-chunk-9.svg ecefec1cb1ec6bd10a041e06998fe2f1 *man/get_spinner.Rd 316dfa560fa5b87f3bed7718114b69dd *man/inline-markup.Rd 6ee7c61e14da3d6c822ecf3fdc20d5fb *man/is_ansi_tty.Rd 3989054fb787f48cc5e6768e900d2dbf *man/is_dynamic_tty.Rd db6609161227f964e931096d054ad27d *man/is_utf8_output.Rd 5873e187476cb94ea1f54d2899ee892e *man/list_spinners.Rd f35f08ca8f235f68b20bfbb4a9ed3ed9 *man/make_ansi_style.Rd 22f2202d28d148ae597158136a35b2d8 *man/make_spinner.Rd af2aa306413ad76d65eeb0a5e8d0f705 *man/match_selector.Rd 7d892cb3919a619a975b6369a502b14b *man/match_selector_node.Rd 1fb6031e46a41eafde3ed8d98734556b *man/parse_selector.Rd 77cce5a73cf5e0aa56a64cbe14510384 *man/pluralization-helpers.Rd 8fb4fe46b9317dd5827b93034c1f7d0b *man/pluralization.Rd b7af58b0af93365af67e316c45bb867d *man/rule.Rd 34b16b9a529f495b7b82968065f076d7 *man/simple_theme.Rd a1885f5e91610f989cab217160a42ef0 *man/start_app.Rd f51238f61cf6e7c53db865806c37ff10 *man/symbol.Rd 186ace2afe1da08c7de0afd11ab901be *man/themes.Rd 7fd2adc8387eeda24d9bbf855f10133a *man/tree.Rd cd3595b6eed4540bf5745437d188f777 *tests/testthat.R a5b0779559df15325a58482cdc5141a5 *tests/testthat/helper-app.R 7249897062d1f978e821759b454e4b7c *tests/testthat/helper.R 9351c7f12bae79f7907b949ae59e29d8 *tests/testthat/test-alerts.R 4cb4e739daa8e2f8803f0cf300587b83 *tests/testthat/test-assertions.R a3910cd9ff071fd9c8ad55a6b5146d63 *tests/testthat/test-box-styles.R bfbec3b2ec07bf440b7bcbf922ec5546 *tests/testthat/test-boxes.R c68a8766a933a5c0ce4dbd5f974e29a3 *tests/testthat/test-cat-helpers.R 9e7727f662b507c4b83d0066e723fcfa *tests/testthat/test-cat.R dac6dc4bea1d297b1e8f092f2162513a *tests/testthat/test-cliapp-output.R 9405980c1b21b954b336c4f81bd2840d *tests/testthat/test-collapsing.R f43c4e548d2c757fdb456b139a7c02e6 *tests/testthat/test-containers.R 2a15a60ace8c57b8cf5b8f0afbb6dc6c *tests/testthat/test-crayon-combine.R 64b232b542026baad747b4bc8ee11f37 *tests/testthat/test-crayon-make.R 3670c3acd66a8eff8a159a42e96adf17 *tests/testthat/test-crayon.R e65d6a46664d7bab031e5f125759e94d *tests/testthat/test-css.R db07346ce05fda09f8a91533020c07a9 *tests/testthat/test-custom-handler.R fc56969e627760ac89ea7ba6bc79893b *tests/testthat/test-headers.R ca574a6f96f406522f3e846542b10157 *tests/testthat/test-inline.R 61d5064c55f69aaec8126dd32b547974 *tests/testthat/test-lists.R 6f39881d81e307e337cdb67134a01c47 *tests/testthat/test-non-breaking-space.R d89ae96d0c114072a75643c41d62efd5 *tests/testthat/test-pluralization.R 2777f4a1a1a6f2365932f290a6d4ac30 *tests/testthat/test-rules.R 994526ceeb8ae027897c71244a5aa330 *tests/testthat/test-sitrep.R e8a83592b5f644f78d81efbdbd26c509 *tests/testthat/test-spinners.R b05cea841acfafafc04455076e70b198 *tests/testthat/test-status-bar.R 3c570b8b8ad6eebe6f9c0f053b5c03a7 *tests/testthat/test-subprocess.R c971bd6c1ec5e354df75164b4586249d *tests/testthat/test-substitution.R bbb435c5bd6e5e70276ad3f4c48e7905 *tests/testthat/test-text.R 4c7e402a4d37932e43d5fbf13441a076 *tests/testthat/test-themes.R 05d5aa6e5c51ee15c615d662490bc4b6 *tests/testthat/test-tree.R 2b07221a85572af0df0c4350745f90f6 *tests/testthat/test-utils.R 5e468429ba43cf3e9174ec7b67c23c8d *tests/testthat/test.R 92cde6752fcbc1640d34f105c9561922 *vignettes/pluralization.Rmd aa62a67ca15a271c06141787f5c42d0c *vignettes/pluralization.md cli/inst/0000755000176200001440000000000013605174461012005 5ustar liggesuserscli/inst/logo.txt0000644000176200001440000000526613572423330013512 0ustar liggesusers                     oooo    o8o Yb                   `888    `"'  `Yb       .ooooo.    888   oooo    `Yb    d88' `"Y8   888   `888    .dP    888         888    888  .dP      888   .o8   888    888 dP        `Y8bod8P'  o888o  o888o cli/inst/examples/0000755000176200001440000000000013535515324013622 5ustar liggesuserscli/inst/examples/apps/0000755000176200001440000000000013565765747014610 5ustar liggesuserscli/inst/examples/apps/search.R0000755000176200001440000000403213565765747016202 0ustar liggesusers#! /usr/bin/env Rscript setup_app <- function() { theme <- list( "url" = list(color = "blue"), ".pkg" = list(color = "orange")) start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch({ library(cliapp) library(pkgsearch) library(docopt) library(prettyunits) error = function(e) { cli_alert_danger( "The {.pkg pkgsearch}, {.pkg prettyunits} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) } }) } search <- function(terms, from = 1, size = 5) { load_packages() setup_app() term <- paste(encodeString(quote = '"', terms), collapse = " ") result <- do_query(term, from = from, size = size) format_result(result, from = from, size = size) invisible() } `%||%` <- function(l, r) if (is.null(l)) r else l do_query <- function(query, from, size) { cli_alert_info("Searching...") pkg_search(query, from = from, size = size) } format_result <- function(obj, from, size) { meta <- attr(obj, "metadata") if (!meta$total) { cli_alert_danger("No results :(") return() } cli_alert_success("Found {meta$total} packages in {pretty_ms(meta$took)}") cli_text() cli_div(theme = list(ul = list("list-style-type" = ""))) cli_ol() lapply(seq_len(nrow(obj)), function(i) format_hit(obj[i,])) } format_hit <- function(hit) { ago <- vague_dt(Sys.time() - hit$date) cli_li() cli_text("{.pkg {hit$package}} {hit$version} -- {.emph {hit$title}}") cli_par() cli_text(hit$description) cli_text("{.emph {ago} by {hit$maintainer_name}}") } parse_arguments <- function() { "Usage: cransearch.R [-h | --help] [ -f from ] [ -n size ] ... Options: -h --help Print this help message -f first First hit to include -n size Number of hits to include Seach for CRAN packages on r-pkg.org " -> doc docopt(doc) } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() search(opts$term, from = as.numeric(opts$f %||% 1), size = as.numeric(opts$n %||% 5)) } cli/inst/examples/apps/news.R0000755000176200001440000000565313565765747015723 0ustar liggesusers#! /usr/bin/env Rscript setup_app <- function() { theme <- list( "url" = list(color = "blue"), ".pkg" = list(color = "orange"), "it" = list("margin-bottom" = 1)) start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch({ library(cli) library(httr) library(jsonlite) library(prettyunits) library(glue) library(parsedate) library(docopt) }, error = function(e) { cli_alert_danger( "The {.pkg glue}, {.pkg httr}, {.pkg jsonlite}, {.pkg prettyunits},", " {.pkg parsedate} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) }) } news <- function(n = 10, day = FALSE, week = FALSE, since = NULL, reverse = FALSE) { load_packages() setup_app() result <- if (day) news_day() else if (week) news_week() else if (!is.null(since)) news_since(since) else news_n(as.numeric(n)) if (reverse) result <- rev(result) format_results(result) invisible() } news_day <- function() { date <- format_iso_8601(Sys.time() - as.difftime(1, units="days")) ep <- glue("/-/pkgreleases?descending=true&endkey=%22{date}%22") do_query(ep) } news_week <- function() { date <- format_iso_8601(Sys.time() - as.difftime(7, units="days")) ep <- glue("/-/pkgreleases?descending=true&endkey=%22{date}%22") do_query(ep) } news_since <- function(since) { date <- format_iso_8601(parse_date(since)) ep <- glue("/-/pkgreleases?descending=true&endkey=%22{date}%22") do_query(ep) } news_n <- function(n) { ep <- glue("/-/pkgreleases?limit={n}&descending=true") do_query(ep) } do_query <- function(ep) { base <- "https://crandb.r-pkg.org" url <- glue("{base}{ep}") response <- GET(url) stop_for_status(response) fromJSON(content(response, as = "text"), simplifyVector = FALSE) } format_results <- function(results) { cli_div(theme = list(ul = list("list-style-type" = ""))) cli_ol() lapply(results, format_result) } parse_arguments <- function() { "Usage: news.R [-r | --reverse] [-n num ] news.R [-r | --reverse] --day | --week | --since date news.R [-h | --help] Options: -n num Show the last 'n' releases [default: 10]. --day Show releases in the last 24 hours --week Show relaases in the last 7 * 24 hours --since date Show releases since 'date' -r --reverse Reverse the order, show older on top -h --help Print this help message New package releases on CRAN " -> doc docopt(doc) } format_result <- function(result) { pkg <- result$package ago <- vague_dt(Sys.time() - parse_iso_8601(result$date)) cli_li() cli_text("{.pkg {pkg$Package}} {pkg$Version} -- {ago} by {.emph {pkg$Maintainer}}") cli_text("{pkg$Title}") cli_text("{.url https://r-pkg.org/pkg/{pkg$Package}}") } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() news(opts$n, opts$day, opts$week, opts$since, opts$reverse) } cli/inst/examples/apps/outdated.R0000755000176200001440000000403013565765747016544 0ustar liggesusers#! /usr/bin/env Rscript ## To get the pkgcache package: ## source("https://install-github.me/r-lib/pkgcache") setup_app <- function() { theme <- list( "url" = list(color = "blue"), ".pkg" = list(color = "orange")) start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch(suppressPackageStartupMessages({ library(cli) library(pkgcache) library(docopt) }), error = function(e) { cli_alert_danger("The {.pkg pkgcache} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) }) } outdated <- function(lib = NULL, notcran = FALSE) { load_packages() setup_app() if (is.null(lib)) lib <- .libPaths()[1] inst <- utils::installed.packages(lib = lib) cli_alert_info("Getting repository metadata") repo <- meta_cache_list(rownames(inst)) if (!notcran) inst <- inst[inst[, "Package"] %in% repo$package, ] for (i in seq_len(nrow(inst))) { pkg <- inst[i, "Package"] iver <- inst[i, "Version"] if (! pkg %in% repo$package) { cli_alert_info("{.pkg {pkg}}: \tnot a CRAN/BioC package") next } rpkg <- repo[repo$package == pkg, ] newer <- rpkg[package_version(rpkg$version) > iver, ] if (!nrow(newer)) next nver <- package_version(newer$version) mnver <- max(nver) newest <- newer[mnver == nver, ] bin <- if (any(newest$platform != "source")) "bin" else "" src <- if (any(newest$platform == "source")) "src" else "" cli_alert_danger("{.emph {pkg}}") cli_alert_danger( "{.pkg {pkg}} \t{iver} {symbol$arrow_right} {mnver} {.emph ({bin} {src})}") } } parse_arguments <- function() { "Usage: outdated.R [-l lib] [-x] outdated.R -h | --help Options: -x Print not CRAN/BioC packages as well -l lib Library directory, default is first directory in the lib path -h --help Print this help message Check for outdated packages in a package library. " -> doc docopt(doc) } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() outdated(opts$l, opts$x) } cli/inst/examples/apps/up.R0000755000176200001440000000277613565765747015376 0ustar liggesusers#! /usr/bin/env Rscript ## To get the async package: ## source("https://install-github.me/r-lib/async") setup_app <- function() { theme <- list("url" = list(color = "blue")) app <- cliapp::start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch({ library(cliapp) library(async) library(docopt) }, error = function(e) { cli_alert_danger("The {.pkg async} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) }) } up <- function(urls, timeout = 5) { load_packages() setup_app() chk_url <- async(function(url, ...) { http_head(url, ...)$ then(function(res) { if (res$status_code < 300) { cli_alert_success("{.url {url}} ({res$times[['total']]}s)") } else { cli_alert_danger("{.url {url}} (HTTP {res$status_code})") } })$ catch(error = function(err) { e <- if (grepl("timed out", err$message)) "timed out" else "error" cli_alert_danger("{.url {url}} ({e})") }) }) invisible(synchronise( async_map(urls, chk_url, options = list(timeout = timeout)) )) } parse_arguments <- function() { "Usage: up.R [-t timeout] [URLS ...] up.R -h | --help Options: -t timeout Timeout for giving up on a site, in seconds [default: 5]. -h --help Print this help message Check if web sites are up. " -> doc docopt(doc) } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() up(opts$URLS, timeout = as.numeric(opts$t)) } cli/inst/doc/0000755000176200001440000000000013605174461012552 5ustar liggesuserscli/inst/doc/pluralization.html0000644000176200001440000004650713605174461016351 0ustar liggesusers CLI pluralization

CLI pluralization

Gábor Csárdi

2020-01-06

Introduction

cli has tools to create messages that are printed correctly in singular and plural forms. This usually requires minimal extra work, and increases the quality of the messages greatly. In this document we first show some pluralization examples that you can use as guidelines. Hopefully these are intuitive enough, so that they can be used without knowing the exact cli pluralization rules.

Examples

Pluralization markup

In the simplest case the message contains a single {} glue substitution, which specifies the quantity that is used to select between the singular and plural forms. Pluralization uses markup that is similar to glue, but uses the {? and } delimiters:

library(cli)
nfile <- 0; cli_text("Found {nfile} file{?s}.")
#> Found 0 files.
nfile <- 1; cli_text("Found {nfile} file{?s}.")
#> Found 1 file.
nfile <- 2; cli_text("Found {nfile} file{?s}.")
#> Found 2 files.

Here the value of nfile is used to decide whether the singular or plural form of file is used. This is the most common case for English messages.

Irregular plurals

If the plural form is more difficult than a simple s suffix, then the singular and plural forms can be given, separated with a forward slash:

ndir <- 1; cli_text("Found {ndir} director{?y/ies}.")
#> Found 1 directory.
ndir <- 5; cli_text("Found {ndir} director{?y/ies}.")
#> Found 5 directories.

Use “no” instead of zero

For readability, it is better to use the no() helper function to include a count in a message. no() prints the word “no” if the count is zero, and prints the numeric count otherwise:

nfile <- 0; cli_text("Found {no(nfile)} file{?s}.")
#> Found no files.
nfile <- 1; cli_text("Found {no(nfile)} file{?s}.")
#> Found 1 file.
nfile <- 2; cli_text("Found {no(nfile)} file{?s}.")
#> Found 2 files.

Use the length of character vectors

With the auto-collapsing feature of cli it is easy to include a list of objects in a message. When cli interprets a character vector as a pluralization quantity, it takes the length of the vector:

pkgs <- "pkg1"
cli_text("Will remove the {.pkg {pkgs}} package{?s}.")
#> Will remove the pkg1 package.
pkgs <- c("pkg1", "pkg2", "pkg3")
cli_text("Will remove the {.pkg {pkgs}} package{?s}.")
#> Will remove the pkg1, pkg2 and pkg3 packages.

Note that the length is only used for non-numeric vectors (when is.numeric(x) return FALSE). If you want to use the length of a numeric vector, convert it to character via as.character().

You can combine collapsed vectors with “no”, like this:

pkgs <- character()
cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.")
#> Will remove no packages.
pkgs <- c("pkg1", "pkg2", "pkg3")
cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.")
#> Will remove the pkg1, pkg2 and pkg3 packages.

When the pluralization markup contains three alternatives, like above, the first one is used for zero, the second for one, and the third one for larger quantities.

Choosing the right quantity

When the text contains multiple glue {} substitutions, the one right before the pluralization markup is used. For example:

nfiles <- 3; ndirs <- 1
cli_text("Found {nfiles} file{?s} and {ndirs} director{?y/ies}")
#> Found 3 files and 1 directory

This is sometimes not the the correct one. You can explicitly specify the correct quantity using the qty() function. This sets that quantity without printing anything:

nupd <- 3; ntotal <- 10
cli_text("{nupd}/{ntotal} {qty(nupd)} file{?s} {?needs/need} updates")
#> 3/10 files need updates

Note that if the message only contains a single {} substitution, then this may appear before or after the pluralization markup. If the message contains multiple {} substitutions after pluralization markup, an error is thrown.

Similarly, if the message contains no {} substituions at all, but has pluralization markup, and error is thrown.

Rules

The exact rules of cli’s pluralization. There are two sets of rules. The first set specifies how a quantity is associated with a {?} pluralization markup. The second set describes how the {?} is parsed and interpreted.

Quantities

  1. {} substitutions define quantities. If the value of a {} substitution is numeric (i.e. is.numeric(x) holds), then it has to have length one to define a quantity. This is only enforced if the {} substitution is used for pluralization. The quantity is defined as the value of {} then, rounded with as.integer(). If the value of {} is not numeric, then its quantity is defined as its length.

  2. If a message has {?} markup but no {} substitution, an error is thrown.

  3. If a message has exactly one {} substitution, its value is used as the pluralization quantity for all {?} markup in the message.

  4. If a message has multiple {} substitutions, then for each {?} markup cli uses the quantity of the {} substitution that precedes it.

  5. If a message has multiple {} substitutions and has pluralization markup with a preceding {} substitution, and error is thrown.

Pluralization markup

  1. Pluralization markup start with {? and ends with }. It may not contain { and } characters, so it may not contains {} substitutions either.

  2. Alternative words or suffixes are separated by /.

  3. If there is a single alternative, then nothing is used if quantity == 1 and this single alternative is used if quantity != 1.

  4. If there are two alternatives, the first one is used for quantity == 1, the second one for quantity != 1 (include 0).

  5. If there are three alternatives, the first one is used for quantity == 0, the second for quantity == 1, and the third otherwise.

cli/inst/doc/pluralization.R0000644000176200001440000000357713605174461015606 0ustar liggesusers## ---- include = FALSE----------------------------------------------------------------------------- knitr::opts_chunk$set( R.options = list( crayon.enabled = FALSE, cli.unicode = FALSE ), results = "hold", comment = "#>", cache = TRUE ) ## ------------------------------------------------------------------------------------------------- library(cli) nfile <- 0; cli_text("Found {nfile} file{?s}.") nfile <- 1; cli_text("Found {nfile} file{?s}.") nfile <- 2; cli_text("Found {nfile} file{?s}.") ## ------------------------------------------------------------------------------------------------- ndir <- 1; cli_text("Found {ndir} director{?y/ies}.") ndir <- 5; cli_text("Found {ndir} director{?y/ies}.") ## ------------------------------------------------------------------------------------------------- nfile <- 0; cli_text("Found {no(nfile)} file{?s}.") nfile <- 1; cli_text("Found {no(nfile)} file{?s}.") nfile <- 2; cli_text("Found {no(nfile)} file{?s}.") ## ------------------------------------------------------------------------------------------------- pkgs <- "pkg1" cli_text("Will remove the {.pkg {pkgs}} package{?s}.") pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove the {.pkg {pkgs}} package{?s}.") ## ------------------------------------------------------------------------------------------------- pkgs <- character() cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.") pkgs <- c("pkg1", "pkg2", "pkg3") cli_text("Will remove {?no/the/the} {.pkg {pkgs}} package{?s}.") ## ------------------------------------------------------------------------------------------------- nfiles <- 3; ndirs <- 1 cli_text("Found {nfiles} file{?s} and {ndirs} director{?y/ies}") ## ------------------------------------------------------------------------------------------------- nupd <- 3; ntotal <- 10 cli_text("{nupd}/{ntotal} {qty(nupd)} file{?s} {?needs/need} updates") cli/inst/doc/pluralization.Rmd0000644000176200001440000000046213572435701016115 0ustar liggesusers--- title: "CLI pluralization" author: "Gábor Csárdi" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: keep_md: true vignette: > %\VignetteIndexEntry{CLI pluralization} %\VignetteEngine{cli::lazyrmd} %\VignetteEncoding{UTF-8} --- ```{r child = "../man/chunks/pluralization.Rmd"} ``` cli/inst/scripts/0000755000176200001440000000000013565765747013516 5ustar liggesuserscli/inst/scripts/search.R0000755000176200001440000000402713565765747015114 0ustar liggesusers#! /usr/bin/env Rscript setup_app <- function() { theme <- list( "url" = list(color = "blue"), ".pkg" = list(color = "orange")) start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch({ library(cli) library(pkgsearch) library(docopt) library(prettyunits) error = function(e) { cli_alert_danger( "The {.pkg pkgsearch}, {.pkg prettyunits} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) } }) } search <- function(terms, from = 1, size = 5) { load_packages() setup_app() term <- paste(encodeString(quote = '"', terms), collapse = " ") result <- do_query(term, from = from, size = size) format_result(result, from = from, size = size) invisible() } `%||%` <- function(l, r) if (is.null(l)) r else l do_query <- function(query, from, size) { cli_alert_info("Searching...") pkg_search(query, from = from, size = size) } format_result <- function(obj, from, size) { meta <- attr(obj, "metadata") if (!meta$total) { cli_alert_danger("No results :(") return() } cli_alert_success("Found {meta$total} packages in {pretty_ms(meta$took)}") cli_text() cli_div(theme = list(ul = list("list-style-type" = ""))) cli_ol() lapply(seq_len(nrow(obj)), function(i) format_hit(obj[i,])) } format_hit <- function(hit) { ago <- vague_dt(Sys.time() - hit$date) cli_li() cli_text("{.pkg {hit$package}} {hit$version} -- {.emph {hit$title}}") cli_par() cli_text(hit$description) cli_text("{.emph {ago} by {hit$maintainer_name}}") } parse_arguments <- function() { "Usage: cransearch.R [-h | --help] [ -f from ] [ -n size ] ... Options: -h --help Print this help message -f first First hit to include -n size Number of hits to include Seach for CRAN packages on r-pkg.org " -> doc docopt(doc) } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() search(opts$term, from = as.numeric(opts$f %||% 1), size = as.numeric(opts$n %||% 5)) } cli/inst/scripts/news.R0000755000176200001440000000565313565765747014631 0ustar liggesusers#! /usr/bin/env Rscript setup_app <- function() { theme <- list( "url" = list(color = "blue"), ".pkg" = list(color = "orange"), "it" = list("margin-bottom" = 1)) start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch({ library(cli) library(httr) library(jsonlite) library(prettyunits) library(glue) library(parsedate) library(docopt) }, error = function(e) { cli_alert_danger( "The {.pkg glue}, {.pkg httr}, {.pkg jsonlite}, {.pkg prettyunits},", " {.pkg parsedate} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) }) } news <- function(n = 10, day = FALSE, week = FALSE, since = NULL, reverse = FALSE) { load_packages() setup_app() result <- if (day) news_day() else if (week) news_week() else if (!is.null(since)) news_since(since) else news_n(as.numeric(n)) if (reverse) result <- rev(result) format_results(result) invisible() } news_day <- function() { date <- format_iso_8601(Sys.time() - as.difftime(1, units="days")) ep <- glue("/-/pkgreleases?descending=true&endkey=%22{date}%22") do_query(ep) } news_week <- function() { date <- format_iso_8601(Sys.time() - as.difftime(7, units="days")) ep <- glue("/-/pkgreleases?descending=true&endkey=%22{date}%22") do_query(ep) } news_since <- function(since) { date <- format_iso_8601(parse_date(since)) ep <- glue("/-/pkgreleases?descending=true&endkey=%22{date}%22") do_query(ep) } news_n <- function(n) { ep <- glue("/-/pkgreleases?limit={n}&descending=true") do_query(ep) } do_query <- function(ep) { base <- "https://crandb.r-pkg.org" url <- glue("{base}{ep}") response <- GET(url) stop_for_status(response) fromJSON(content(response, as = "text"), simplifyVector = FALSE) } format_results <- function(results) { cli_div(theme = list(ul = list("list-style-type" = ""))) cli_ol() lapply(results, format_result) } parse_arguments <- function() { "Usage: news.R [-r | --reverse] [-n num ] news.R [-r | --reverse] --day | --week | --since date news.R [-h | --help] Options: -n num Show the last 'n' releases [default: 10]. --day Show releases in the last 24 hours --week Show relaases in the last 7 * 24 hours --since date Show releases since 'date' -r --reverse Reverse the order, show older on top -h --help Print this help message New package releases on CRAN " -> doc docopt(doc) } format_result <- function(result) { pkg <- result$package ago <- vague_dt(Sys.time() - parse_iso_8601(result$date)) cli_li() cli_text("{.pkg {pkg$Package}} {pkg$Version} -- {ago} by {.emph {pkg$Maintainer}}") cli_text("{pkg$Title}") cli_text("{.url https://r-pkg.org/pkg/{pkg$Package}}") } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() news(opts$n, opts$day, opts$week, opts$since, opts$reverse) } cli/inst/scripts/outdated.R0000755000176200001440000000376113565765747015464 0ustar liggesusers#! /usr/bin/env Rscript ## To get the pkgcache package: ## source("https://install-github.me/r-lib/pkgcache") setup_app <- function() { theme <- list( "url" = list(color = "blue"), ".pkg" = list(color = "orange")) start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch(suppressPackageStartupMessages({ library(cli) library(pkgcache) library(docopt) }), error = function(e) { cli_alert_danger("The {.pkg pkgcache} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) }) } outdated <- function(lib = NULL, notcran = FALSE) { load_packages() setup_app() if (is.null(lib)) lib <- .libPaths()[1] inst <- utils::installed.packages(lib = lib) cli_alert_info("Getting repository metadata") repo <- meta_cache_list(rownames(inst)) if (!notcran) inst <- inst[inst[, "Package"] %in% repo$package, ] for (i in seq_len(nrow(inst))) { pkg <- inst[i, "Package"] iver <- inst[i, "Version"] if (! pkg %in% repo$package) { cli_alert_info("{.pkg {pkg}}: \tnot a CRAN/BioC package") next } rpkg <- repo[repo$package == pkg, ] newer <- rpkg[package_version(rpkg$version) > iver, ] if (!nrow(newer)) next nver <- package_version(newer$version) mnver <- max(nver) newest <- newer[mnver == nver, ] bin <- if (any(newest$platform != "source")) "bin" else "" src <- if (any(newest$platform == "source")) "src" else "" cli_alert_danger( "{.pkg {pkg}} \t{iver} {symbol$arrow_right} {mnver} {emph ({bin} {src})}") } } parse_arguments <- function() { "Usage: outdated.R [-l lib] [-x] outdated.R -h | --help Options: -x Print not CRAN/BioC packages as well -l lib Library directory, default is first directory in the lib path -h --help Print this help message Check for outdated packages in a package library. " -> doc docopt(doc) } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() outdated(opts$l, opts$x) } cli/inst/scripts/up.R0000755000176200001440000000277013565765747014276 0ustar liggesusers#! /usr/bin/env Rscript ## To get the async package: ## source("https://install-github.me/r-lib/async") setup_app <- function() { theme <- list("url" = list(color = "blue")) app <- cli::start_app(theme = theme, output = "stdout") } load_packages <- function() { tryCatch({ library(cli) library(async) library(docopt) }, error = function(e) { cli_alert_danger("The {.pkg async} and {.pkg docopt} packages are needed!") q(save = "no", status = 1) }) } up <- function(urls, timeout = 5) { load_packages() setup_app() chk_url <- async(function(url, ...) { http_head(url, ...)$ then(function(res) { if (res$status_code < 300) { cli_alert_success("{.url {url}} ({res$times[['total']]}s)") } else { cli_alert_danger("{.url {url}} (HTTP {res$status_code})") } })$ catch(error = function(err) { e <- if (grepl("timed out", err$message)) "timed out" else "error" cli_alert_danger("{.url {url}} ({e})") }) }) invisible(synchronise( async_map(urls, chk_url, options = list(timeout = timeout)) )) } parse_arguments <- function() { "Usage: up.R [-t timeout] [URLS ...] up.R -h | --help Options: -t timeout Timeout for giving up on a site, in seconds [default: 5]. -h --help Print this help message Check if web sites are up. " -> doc docopt(doc) } if (is.null(sys.calls())) { load_packages() opts <- parse_arguments() up(opts$URLS, timeout = as.numeric(opts$t)) }