cliapp/0000755000176200001440000000000014612270172011524 5ustar liggesuserscliapp/NAMESPACE0000644000176200001440000000240214521233614012740 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(builtin_theme) export(cli_alert) export(cli_alert_danger) export(cli_alert_info) export(cli_alert_success) export(cli_alert_warning) export(cli_div) export(cli_dl) export(cli_end) export(cli_h1) export(cli_h2) export(cli_h3) export(cli_it) export(cli_ol) export(cli_par) export(cli_progress_bar) export(cli_text) export(cli_ul) export(cli_verbatim) export(console_width) export(default_app) export(simple_theme) export(start_app) export(stop_app) importFrom(R6,R6Class) importFrom(cli,symbol) importFrom(crayon,bold) importFrom(crayon,col_substr) importFrom(crayon,combine_styles) importFrom(crayon,italic) importFrom(crayon,make_style) importFrom(crayon,underline) importFrom(fansi,strwrap_ctl) importFrom(glue,glue) importFrom(glue,glue_collapse) importFrom(progress,progress_bar) importFrom(selectr,css_to_xpath) importFrom(stats,na.omit) importFrom(utils,globalVariables) importFrom(utils,head) importFrom(utils,modifyList) importFrom(utils,tail) importFrom(withr,defer) importFrom(xml2,read_html) importFrom(xml2,xml_add_child) importFrom(xml2,xml_attr) importFrom(xml2,xml_find_all) importFrom(xml2,xml_find_first) importFrom(xml2,xml_name) importFrom(xml2,xml_parent) importFrom(xml2,xml_path) importFrom(xml2,xml_remove) cliapp/LICENSE0000644000176200001440000000005414521236664012537 0ustar liggesusersYEAR: 2023 COPYRIGHT HOLDER: cliapp authors cliapp/README.md0000644000176200001440000000620014612255547013012 0ustar liggesuserscliapp ================ > Create Rich Command Line Applications [![lifecycle](https://img.shields.io/badge/lifecycle-superseded-blue.svg)](https://lifecycle.r-lib.org/articles/stages.html) [![R-CMD-check](https://github.com/r-lib/cliapp/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/cliapp/actions/workflows/R-CMD-check.yaml) [![](https://www.r-pkg.org/badges/version/cliapp)](https://www.r-pkg.org/pkg/cliapp) [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/cliapp)](https://www.r-pkg.org/pkg/cliapp) [![Codecov test coverage](https://codecov.io/gh/r-lib/cliapp/branch/main/graph/badge.svg)](https://app.codecov.io/gh/r-lib/cliapp?branch=main) Create rich command line applications, with colors, headings, lists, alerts, progress bars, etc. It uses CSS for theming. --- - [Superseded](#superseded) - [Installation](#installation) - [Usage](#usage) - [Headings](#headings) - [Text and inline markup](#text-and-inline-markup) - [Alerts](#alerts) - [Lists](#lists) - [Progress bars](#progress-bars) - [License](#license) ## Superseded This package is superseded, and we focus on the cli package now: ## Installation Stable version: ``` r install.packages("cliapp") ``` Development version: ``` r pak::pak("r-lib/cliapp") ``` ## Usage This README uses the simple theme, included in the package, see `?simple_theme()`. ``` r library(cliapp) start_app(theme = simple_theme()) ``` ### Headings `cli_h1()`, `cli_h2()` and `cli_h3()` create three levels of headings: ``` r cli_h1("Title") cli_h2("Subtitle") cli_h3("Subsubtitle") ``` ![](man/figures/headings-1.png)![](man/figures/headings-2.png)![](man/figures/headings-3.png) ### Text and inline markup All (non-verbatim) outputted text runs through `glue::glue()`. In addition to glue interpolation, cliapp also supports inline markup via the `{markup text}` form. The builtin theme defines inline markup classes, see `?inline-markup`. ``` r cli_text("{emph Emphasized text}, {strong Strong} importance. A piece of code: {code sum(a) / length(a)}. Package names: {pkg cliapp}, file names: {path /usr/bin/env}, etc.") ``` ![](man/figures/inline-markup-1.png) ### Alerts ``` r cli_alert("Generic alert") cli_alert_danger("Something went horribly wrong") cli_alert_warning("Better watch out!") cli_alert_info("About to download 1.4GiB of data.") cli_alert_success("All downloads finished successfully") ``` ![](man/figures/alerts-1.png)![](man/figures/alerts-2.png)![](man/figures/alerts-3.png)![](man/figures/alerts-4.png)![](man/figures/alerts-5.png) ### Lists Ordered, unordered and definition lists, they can be nested. See `?cli_ol()`, `?cli_ul()`, `?cli_dl()` and `?cli_it()`. ``` r cli_div(theme = list(ol = list("margin-left" = 2))) cli_ul("one", .close = FALSE) cli_ol(c("foo", "bar", "foobar")) cli_it("two") cli_end() cli_end() ``` ![](man/figures/lists-1.png) ### Progress bars Progress bars are supported via the [progress package](https://github.com/r-lib/progress). ## License MIT © RStudio cliapp/man/0000755000176200001440000000000014521235155012300 5ustar liggesuserscliapp/man/cliapp-package.Rd0000644000176200001440000000262414521236436015437 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cliapp-package.R \docType{package} \name{cliapp-package} \alias{cliapp-package} \title{Create Rich Command Line Applications} \description{ Create rich command line applications, with colors, headings, lists, alerts, progress bars, etc. It uses CSS for theming. } \details{ See \link{themes} for theming, \link{containers} for container elements, \link{inline-markup} for more about command substitution and inline markup. See also the various CLI elements: \itemize{ \item Text elements: \code{\link[=cli_text]{cli_text()}}, \code{\link[=cli_verbatim]{cli_verbatim()}}, \code{\link[=cli_h1]{cli_h1()}}. \item Containers: \code{\link[=cli_div]{cli_div()}}, \code{\link[=cli_par]{cli_par()}}, \code{\link[=cli_end]{cli_end()}}. \item Lists: \code{\link[=cli_ul]{cli_ul()}}, \code{\link[=cli_ol]{cli_ol()}}, \code{\link[=cli_dl]{cli_dl()}}, \code{\link[=cli_it]{cli_it()}}. \item Alerts: \code{\link[=cli_alert]{cli_alert()}}. \item Progress bars: \code{\link[=cli_progress_bar]{cli_progress_bar()}}. } } \seealso{ Useful links: \itemize{ \item \url{https://github.com/r-lib/cliapp#readme} \item Report bugs at \url{https://github.com/r-lib/cliapp/issues} } } \author{ \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} Other contributors: \itemize{ \item Posit Software, PBC [copyright holder, funder] } } \keyword{internal} cliapp/man/console_width.Rd0000644000176200001440000000067114521233614015432 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. } cliapp/man/cli_text.Rd0000644000176200001440000000174114521233614014403 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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("cliapp")$Description) ## Arguments are concatenated cli_text("this", "that") ## Command substitution greeting <- "Hello" subject <- "world" cli_text("{greeting} {subject}!") ## Inline theming cli_text("The {fun cli_text} function in the {pkg cliapp} package") ## Use within container elements ul <- cli_ul() cli_it() cli_text("{emph First} item") cli_it() cli_text("{emph Second} item") cli_end(ul) } cliapp/man/cli_dl.Rd0000644000176200001440000000260614521233614014017 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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_it(c(foo = "one")) cli_it(c(bar = "two")) cli_it(c(baz = "three")) cli_end() } cliapp/man/cli_progress_bar.Rd0000644000176200001440000000151314521233614016104 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.R \name{cli_progress_bar} \alias{cli_progress_bar} \title{CLI progress bar} \usage{ cli_progress_bar(...) } \arguments{ \item{...}{All arguments are passed to the constuctor of the \link[progress:progress_bar]{progress::progress_bar} class.} } \value{ A remote progress bar object that can be used the same way as \link[progress:progress_bar]{progress::progress_bar}, see examples below. } \description{ A progress bar using the progress package } \examples{ \dontshow{if (!cliapp:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} { p <- cli_progress_bar(total = 10) cli_alert_info("Starting computation") for (i in 1:10) { p$tick(); Sys.sleep(0.2) } cli_alert_success("Done") } \dontshow{\}) # examplesIf} } cliapp/man/cli_it.Rd0000644000176200001440000000235314521233614014033 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.R \name{cli_it} \alias{cli_it} \title{CLI list item(s)} \usage{ cli_it( 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_it("one") cli_it("two") cli_it("three") cli_end() ## Complex item, added gradually. cli_ul() cli_it() cli_verbatim("Beginning of the {emph first} item") cli_text("Still the first item") cli_end() cli_it("Second item") cli_end() } cliapp/man/simple_theme.Rd0000644000176200001440000000426014521234223015237 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 = "auto") } \arguments{ \item{dark}{Whether the theme should be optiomized for a dark background. If \code{"auto"}, then cliapp 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{ \if{html}{\out{
}}\preformatted{options(cli.theme = cliapp::simple_theme()) }\if{html}{\out{
}} and then CLI apps started after this will use it as the default theme. You can also use it temporarily, in a div element: \if{html}{\out{
}}\preformatted{cli_div(theme = cliapp::simple_theme()) }\if{html}{\out{
}} } \examples{ \dontshow{if (!cliapp:::is_cran_check()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} cli_div(theme = cliapp::simple_theme()) cli_h1("Header 1") cli_h2("Header 2") cli_h3("Header 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 cliapp} {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: {fun cliapp::simple_theme} and {arg arguments}.") cli_text("Files: {file /usr/bin/env}") cli_text("URLs: {url https://r-project.org}") cli_h2("Longer code chunk") cli_par(class = "r-code") 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 = "r-code") cli_verbatim(format(ls)) cli_end() cli_end() \dontshow{\}) # examplesIf} } \seealso{ \link{themes}, \code{\link[=builtin_theme]{builtin_theme()}}. } cliapp/man/inline-markup.Rd0000644000176200001440000000565214521234223015345 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/docs.R \name{inline-markup} \alias{inline-markup} \title{CLI inline markup} \description{ CLI inline markup } \section{Command substitution}{ All text emitted by cliapp 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, cliapp can also add classes to parts of the text, and these classes can be used in themes. For example \if{html}{\out{
}}\preformatted{cli_text("This is \{emph important\}.") }\if{html}{\out{
}} adds a class to the "important" word, class "emph". Note that in this cases the string within the braces is not a valid R expression. If you want to mix classes with interpolation, add another pair of braces: \if{html}{\out{
}}\preformatted{adjective <- "great" cli_text("This is \{emph \{adjective\}\}.") }\if{html}{\out{
}} 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: \if{html}{\out{
}}\preformatted{cli_div(theme = list(span.emph = list(color = "red"))) adjective <- "nice and red" cli_text("This is \{emph \{adjective\}\}.") }\if{html}{\out{
}} } \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. } See examples below. You can simply add new classes by defining them in the theme, and then using them, see the example below. } \examples{ ## Some inline markup examples cli_ul() cli_it("{emph Emphasized} text") cli_it("{strong Strong} importance") cli_it("A piece of code: {code sum(a) / length(a)}") cli_it("A package name: {pkg cliapp}") cli_it("A function name: {fun cli_text}") cli_it("A function argument: {arg text}") cli_it("A keyboard key: press {key ENTER}") cli_it("A file name: {file /usr/bin/env}") cli_it("An email address: {email bugs.bunny@acme.com}") cli_it("A URL: {url https://acme.com}") cli_it("A variable name: {var mtcars}") cli_it("An environment variable: {envvar R_LIBS}") cli_end() ## Adding a new class cli_div(theme = list( span.myclass = list(color = "lightgrey"), "span.myclass::before" = list(content = "["), "span.myclass::after" = list(content = "]"))) cli_text("This is {myclass in brackets}.") cli_end() } cliapp/man/builtin_theme.Rd0000644000176200001440000000062414521233614015417 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() } \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()}}. } cliapp/man/cli_ol.Rd0000644000176200001440000000312114521233614014023 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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_it("one") cli_it("two") cli_it("three") cli_end() ## Nested lists cli_div(theme = list(ol = list("margin-left" = 2))) cli_ul() cli_it("one") cli_ol(c("foo", "bar", "foobar")) cli_it("two") cli_end() cli_end() } cliapp/man/cli_ul.Rd0000644000176200001440000000315014521233614014033 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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_it("one") cli_it("two") cli_it("three") cli_end() ## Complex item, added gradually. cli_ul() cli_it() cli_verbatim("Beginning of the {emph first} item") cli_text("Still the first item") cli_end() cli_it("Second item") cli_end() } cliapp/man/containers.Rd0000644000176200001440000000225414521233614014735 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/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_it]{cli_it()}}. } \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") } cliapp/man/figures/0000755000176200001440000000000014612252113013736 5ustar liggesuserscliapp/man/figures/headings-2.png0000644000176200001440000001121214612252113016362 0ustar liggesusersPNG  IHDRoBJ pHYs  l*uG'fvJsssfٲWxsil\gQt1ӧ}?Ÿq3nL[[[[syi _egw9~?w>U:=z|rcݕ[n,Vg]RW7$I֚ں0r K1C0lnLJGr[N8s /u|N]v+?-ӧ`v[}Wg޼b+'?y=^wKc1b~Rl'=ᥗfy7;oqI c|NC5+z 88modPU5`ظVWW+**R(zXLEņ/.TW89sffΜruOgx/x?$_|&ײ`#wG|3ӳ{/aDcc$_媫kd'{G+lPޗg ^j?o3}:[̝{Eν"&}%$[[[zmṶ^鉛X,>XO@߆ds[[kMέsg;"zl ڼ&YGv㭷N]XM̀ C$Iee{|ssOd{*lNҷ2;8*Iֶ&ÇLw]ݰ9zs;s!̐!;>PIZ_ l8pH鹲2[r~8{}Hd{rYt +^$ߧvx-z˝g=wNvCƿsgd+:~ރ'?yӱY9sչG^t:ڒiMCÂ.rw>IN OȘ1sC^^Bf`^n"iOCөMSӚ$ɰa#3hаbmf}KϿ'--Min^d}ѣ?XjUcxlUuum,y)M;M<˗ڵr/RɧWnrۙoI'HeӴÆWϓOޛ> :6rU3mAYx}?873f8'~4s;/sS޽gqe~ԧ$il\~#I0z;2s훹=]6grM 0`o07|Y{16?/5לY7yi9+ }Nc>wCSo:ޏ|sΙnx)\|On%g}l)^{fB^~yagfܸvWUՀkR_?ٛO+AK0pS(O9s.|NsR{9/I $w>񺺡9O欳7/at44,ȢEv2Ғo*GhJemBdĈ]sK_|&f[o+.'LVCzm)\1crС;梋uםn>1y;?y]jjj$ RXv'scb[z*I[/=7x;#wdذxseϮ]]'<Ľ]H:ZZ[]S;%W^:Yc>wi$]5$5u7 _&Ia卹K.26S ϭ~lNP˻Z3]/Y8VgآuuCӻ*Torֶ7lz)+rUR: ~K}46.8s۝w^sȀ} ̚5sRn$텽&y;I${;Cw: IڒddEņS߭-B*d,xQv;wllh$ Nu)SdT֗_{%BUu->R'[hcm}$_{Ovkk|^bYg{"MMkJ@mQ{n`;7ԪH}}yY䖖 S Jt{;I-_xnސ͓[yX}7s淗?6|l~g &eǖS4l4hXƎݿ>C옣Xnj?84ijڒ۽^~a;vgo(zvo%Kj;+qsXe^˃-SNo>w"@ c}s媫QGBN:[9>ԽIZ^x U9e᥾w%W^u]N+BU.e͜9T;I^z,Zh{ʢq=֬YYnmX~{Y̗tQ,ےԪK/;F9sf2̘qS֗U/7dȈL2#?\qC;-DŶ`&N<13f)+۳xqCVx+BUs\׬Y/|aϬXvYN<ݩɸqGT-b1vxqgfܼ&cN;T66cy;C/w V^TV2`\sk:]vCYb=C V|;k֬Le冓Ћ38.ݍ$_E9s;|+PJPK_ɗ$^?˜w_rL򭲾<92je>-WWWw諨tͺuO4C9Y_A&O>W(Te[[SUUBZ{TCٳ/d9IV\Y7&7>a5jLٽӝ2dx )ߣ.y/ĉ'dQꫬ,%Gvhٲr3/Kof@Yf)'|AP矬M%q_O…۹--MY9`]λӱb=wŴu|ڵ+}euoSӚ\r/3lۮOSOݗU;--'[[Sĺuzme  ijZ7GGԩ3;:v9O?@.,\Xb6.W}98F-KNcn“y_w>wgĈ]3i)ijZB2KG3+W.ۢ(s\Əd֭[#FWw4]Lp|F^C^?,ZDzۡXnOzIENDB`cliapp/man/figures/headings-1.png0000644000176200001440000000625714612252113016376 0ustar liggesusersPNG  IHDRoBJ pHYs   aIDATxol]}8MBdckIl$P[XUؖS -kRDWRڨ`]Tmt 3v&Nk߻xc|^yMs"jTj De+_Q,[vmRسnJ+;=mM45|p:8t) de)2'm{1,Nz\.=Sg:,YsGD+&}dQWW?t:MM-SXp Y\M_p|\,'ƍߌի޷ۖGĉ cFEDP46FCCspPЅB!LUϣ7=l(H]2--s*::~? Br}+~33 jᆏǗ<ֿŮؼ;v9 |[>vg?{:qbjftpIhjj=|J&=6عP,\tJf O@WCWm Jc'<_/F8R1"RqMdg^^*_G84b\.GVueغuo45*Jx=q}U֔mnn- ^0#؀^XnY-xcɒb#6nf^}wDDuŝw^ӵ?2bpMm_}&-6""[M|{[^`ZxTlsVWć>tswoyǏwG)^}WD䧪T`zـPμJz:D])r3ȥR1~qxb׮Gʿf^˗T ^$v*O}tәs b;jīKZ6~4EqbKauqѲlٵ<'WǾ}T 5k>WJRRW j%ZYg5,9%W+:If,9%W+:If,ZɬVYM2Kڙ}ۛj3}-S+h|ssk*97b6s_jթS'b5144Cͦ" /j ~zoj%ZYg5,9%W+:If,9%W+:If,ZɬVYM2Ky?8ULG[_\|C;}}* ǧNZm63 qm@wwotw{矝.:Yd̒je$d{#9%'3HNf瘹߀uuu㡡yzN^&3n qcckY R pѳ}Re{odzf͍+~͙L6nٹ|o[ko_K&W(p1}aT*M 7,g2ٸ|Uq{^ֿ◿IwKؾ?ʕ7Ɩ-OoĦMOTT/pQIMw3¦M7gް;'>qMR/E:-e2ַ:~2'xhj5cQ(t: 裛?~tk5ne燆 qXK˜ؾ3ZZF>ztv>--m7嗯\._>o߿xӄk.jԠ|]KJcs-j1G>_F 鉼l_چ?hZS笿h_M<F{r]\CWpG6rTw&(#ts}Q_*bx~U|4У9zHH<ߘ.^dcժ[⪫>t:sd &QNe- IENDB`cliapp/man/figures/alerts-4.png0000644000176200001440000002046714612252113016110 0ustar liggesusersPNG  IHDRJ+JJ pHYs   IDATx{$uyoTA+Q h9&FИGh4D=GGMQU@EEAeݙ:vuO_gg˯k~USopio\U/XMkVm{λ8O H 6qa_L=x>~s$I$I$IQ9S6VVl#޽Ǟ{~{n;bry>Jdl2uw6O]3EI$I$I$I⪬{.Z̝vsؗvZCC[ PTbvdrbΜP$I$I$I$Il,_۬7 =KGNox[-I$I$I$I9WZ]Z|B~.eȲ H!+Q*C>w_\b V\͆ XlQ[yPn\-݀k:|?< K\'RZbj<֜nNq;pqcS$I$IҒQT*{+cLM$Sd?gیÚemdAg)WTwgE_҇\ XYi(m΢ZLv~Fo|8N_N_t;(ކt-#>|` p~*#_[:|?l"&'Aew&(cS$I$IDD%2X3 $l~{M۹͌Kz#K!ɠT\i%q~s5.1F/^sFB XD[jU`5X[g!SGlG?>C'r>EYkS7{n o[_{}xpmUˉk$I$IP%+g$Iʖ*P+=3k8GQ`gMrcc%(@iJ\x9:FpaS,8J',rU+:-|uڲTx\yF)bm$׭v2m/\ ^C'|?leG >9ZzK6-pFը^%I$I$m*YT]MlX%eu9դJ>B I,Xb kVi{2A~06JPOgD&hJAy |-فۀϡm3j | \TibȈSAmQq`4 /k9;tM$I$IdiF&f9*_p;-\7@J9Yf rsq=o p41^:YtPYWyFa3vs(wAZ0p9fKKk[>Q%`-Ác{muDYig$I$IҔ4Wm 2,k~v;3 4c\"M *IPÃr$t!2j%če N:AdqؑFZ;&m17A{*u| hM1׿ ֻٖ2aE)x"#u_eo]Eq<~"3zO!GcG)i>sh'eWtXॣKtD9p1uBgxjF}ר.]y1i{+{}M$I$IVR%RzFKd%,/QQMRDM33IF v%dقTR>F4g'7 (m ٙs8(]_^ |x;.8F\8e[Lj`!Kd.w˲vάZ6vX |(y.kna8zCskPj/}%plOyuNdotcMilb\GۊE ߼Q*?I\o#_Gs&7iz^ j%,pq<:P"ͽ#AxW^T$I$I$-% Y>aBFz^B"qulNK"݂TEۜw"+L"81~r]J<Q^ݕ=oS]Nu \?iרnׁ"ulO10EM$I$IPI*I$YiXel"؃f5PVտ!V)YW;I~z/"V=Ɓ:&" yϷ#P C}C~ Qyl3N&D[6 O̥}im߲ =EdCtߴ,OuXW|1p@i}&"~_0әp.x S&2sGݺh;-ߧՃ qK|Då<6S. O>Q}ƀO7Y_}c9( ^NBE?\{=\\P 4s+a}!n#0y<:P"ֱ~Ki~`^ܲ0EM$I$IPVFrY jÚs6iF$YJ%w ա}2ߔFzyopޜ>([D\kh "Xeɫlc~B,;YLȤۉ8׳ |duB8CLݱ6(lgP˘]J_MFdn>~zz_xUZ)눀L^}2>FI"`}lmz3"x>6'R J{Ɖ!ꙩ1ϊKYa;PJtU#q}b|u>=E)OiTQ86:>쇫 _ѧM\lXI$I$IZʐ i+eZ3YRdIB 5]&2Ƚ^,"?`v[G y@榗YV i2~ez1=\ȿ|:/0 얛A ,~C.]{8tc}$ٞ^Is\}lm~ J3D|P677(}\% \=bef[Q5!뉠yn֍5c?}xAi?vaM$I$IVe)Ye^d YvE u$iM4g|-~`u7 3nMDsb>ΙA\V1}oX^/Oe@1N~bv&R$;'ާtc~H#_ =1NtV[>` #7dow v"6u3!"Rs:fQDgۼ& װ.@" S׺ŸF}lgi>7?eZ m$I$I*$ U{iɳ$!M~҄$j o| q~$h/B^R^MA}mmܼ qS`vDcTLRg5t#a||Xl{(}g}WSh#G(Q^6i{ Ww.σ9FNe[ "RIpͥ]?#_;oDF4q;x q J4J#/k0a<$2-Qn$I$IPI$e$+K!YU>*C)0~gۈqi%o ,|#vtO76cyjn\va;Ix}٫Ӻ|_T?"AƸ3D8A'1n'sƱ9|woRtX*N~[Oh^׋"%4xxkzY툱5R |1qj EI$I$I!4Ȳ_Y Y$}#%#^J"[.#2v$%;7*}ZgfےM(qi]\xcayJsq;bNzҊg݀m`Sk;[f:fLQP )1 4?$QtX u?}u[7gq}X%:" :}˽e)\ql B#}Fm$I$IFL%K3$%/)$Y1$2ܽBz\IB m c1kX{S}[nfk*,N&Pkӷѭ]I#y8ȖƉ\~.0uE8Kˀ?}9o<J<(Q?[qN%8xp837_zR{MdۉL2:[õn~P>bL i<0},s&I$I$iU2ҜMJȤNҌtxq'{Czs@X=TfvP1qrZ{=ufmiot/g~["[#퉇:R,M oSKU&fĿmO=߱ezXcY^I "f"hb|]vFO30v9Xt>OvV}bw -iN&e=8CCc4?\pRbx%[}EEYyfm!,O$I$IFI4K,%MߛAnFnLOW$0zt8c@?L'u D\ q;ٖ]4g=ܒ)~ S9:Y4guɿ ,A:"UOEoi3o uU5ە{GN{Xx*DzSn[{{u!MY>k]e;3ȾpQ&杉߹e ^8ĹgE>?KkwE4_%ƇsШ*̏'=M +j6 zMf}n-H$I$Iqdc 謯pѕ/|Ϲu\u$Y,Mz7q`/9w|0tϰ+gv.iC3pOn?-JcOl3+:|I~`((uZ73|/7= ü0XOFܖaS}z~vpH]k~طci6˜C~},S>k=.1b\^"Bi !N^O9MD~x Gcs8s?ia(4ulv˦~&Ϡ6ǵ$I$IBe4Y4߯_ί~s7m}-&I!$,'goX67=,nN>NidV\ކ |xN'ڴ.1;8Dz@`>ZhMG*}.QXev 4x1rݑr=l*xu rۻ=]oqimKi[xKegS4O_EFUfU3aӀ31sMv$I$I$meSIdf4ֲUJWʌWʔ^.ͪTl|߳7n^K3Cslx~s"p}!zоL?XؽxnYDV?/%T,xMۇXqg}^W&C[jZGcv^G;'45w5EN#v]BogМ=Qn~1^Od_-q"Xں@lD}h{UB`⸺W/h+D{|~+> ڼ^N8F<3b\뉿ŇZ xssШ*VAv? 2V|n$I$IL%+=ls*V Lujj󕛦ן_KXcAםFfbjs='Th;A}9^uڿP/Z>oZ\q <*Snzg:gbx`g}CXȧ@sU{uw"hxLuw-[n>'ӁÈ vgF+/%;'E}d Pu/VDZ3Y{Ej@)qհǰ.Zb͛g>[e2~ |n$I$ILy]lzƍW%Շ i:C:3y Lo/=ާ@n*иy\\")[Dp.;8[M_n$Zvh";n:>^[R"۩]hX%\LEdmj34\S8g=+ "p9^u1aWm)?.˽Os4C?}XOF]}n(]>|<|bFnru).us.{ @ =滱6+h_b>I?3BJd<Pt>sPssQԱy&J67q]o|>aS^s$I$I!gꨉUO8TۡTfJWVʲl:Ku5ӛ6||"ֽƀe,SD"#lPڕ(g{\&'2!=K6"7uzov^Fd@F8(-%E<ql^;v747<8gL|)Q^[͖9cs 8g}ں㡯Qn$I$IQ: g2͞x֬rL<~h<]4ze Ҵe^u]$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$IIDAT$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I&oj&IENDB`cliapp/man/figures/alerts-1.png0000644000176200001440000000721314612252113016077 0ustar liggesusersPNG  IHDRJ+JJ pHYs  =IDATximgY=e"E) T+P*A DLj"*($c8D?qƩ A#bT&"c)նi{o~x>3߶_rZ]z} y:X..WǫVZ~paݥP]U۪/ܷS[ƴ^+n#1FRꏫwpg6ZU4G߯޺bL_}MuIUoΩ>cc'N=^CKG#8mW>zJy-HwLe75mﭼccM^Y}cTzl/LO~\u} V?r=緿iv]yOj{%኱=~1*Rzh%_ѩ?N8#ҷK=Q}l^1n$۫pFn6Ϯ.kzr'SCߺAkL|]fQ)uVc~]}kN{ނ+ZMzT}=n =)ky5/k{Z=T[oVai縤zjw{ycE.mvLHįv^ ͎tS?jvВɥG擧%69&o$9z;uI-sM ~AK.[yQ-Nvϟ ?\}6c܊V{/?ګ1=Fz'6;h>n&e: =<}݂6k,.o6a}T_Ǫor6^7\,I\ӮlfƳonC}N}|?_ Wklo@WU8R0|{'Џu>1wuҹ=f'*7֊^_V}jWFխ+-6}]uPlկ(ڿmg4k$_}n5f+@?mAc՟7*Vov^Kmu7^;ufT |Om\l\~OveSp/s.U>^'#mF{Yv^WۤkWod ͝Z4겹c74Mc+ڽ{+jlijp'sFꅓ^wQe_[=kף彫/i4g-97͍WM[\y7`,ӞTׯA/p>46;h[ͮ|mFn;ZQZi-O@/OѨ=okL}ƲyL\+ `6o2FyL>{AS ku6unPl;jWUGZ>w=秓_46l?qPp0z~$Ykj>˖mɹ˚MBȤ\0SV?ukky* ۪OX{3j:~'VfkQ=skHcƩ{pT6)VkwSmvju6矱ɽ֝٨}ӱQuIkInpߪϮo-iU_ݷgSOzO[;u|Ϳoh+Fv۫]ẽ/v^Y=?ΏU? _߉??(wn{UonԄL@W삸V[ /z7V>2}y.'M'JozY:wM{;$-՟VxV;#->blvFyᵍqSu. vZ~՗޷žiA? 荪MlTn-ƵJt2}; ;5!v}W.w'm7.gl#]mUeInn߻n vkBo lٵg_Il_PynXGx7;nrUW^Ш:_VF=V't_xӵ'Z\l_oi+W?ˏ8:vqE}WG[gc5WHWO}_uƳzRF2ƻMb91m[9<`?~}Ύ>(IENDB`cliapp/man/figures/alerts-2.png0000644000176200001440000001665214612252113016107 0ustar liggesusersPNG  IHDRJ+JJ pHYs  \IDATxl7L0 !i &)uېr06xK[Pn^셆D&R6 beMNQ)FSQYThiݰM !&Sènj3x~x/|gyOH$iByI$I$IlOv$I$I$I$I %I$I$I$IeaZ$I$I$IT& 7χu0<{e8 =ݸ8cm:Kbú]8Tj]68RoWBqؽff = kwL 5?oǐ$I$I$}jj[W]_ y*|Y4=qaf=ԍ/qf7d3kf#ZDlb0/Aq?Pd}!I$I$I @7774¬!{K5WClkҚWj1UJd7kp='u$I$I$Ic45k|C]2"#6/]m?e6EJwA3IWFvI$I$I4aS -E}`?BG`BXz39tv6GRe1w^nmI$I$I%N??3_UcyӇa?MʷmÔʧFJ<$I$I$I@'50XtziU~q:k=?@a1G9}l5\iK=E6lxCe2 K8|x =z9E۶ X:-m|1 ;mh@䕭o 1̴y礳%_4 ~$l)v"u @Oo¢t >^ C9_*mW <$I$I$i:)|>'6fUK#Sv+~hiX  .Ib |t%On#0/ӮĢ[+#1۞m+7o?p? rZHcp7$,lGȶ>xp96// K_ ܷ} B]߅yr?k0zvXcqޣa5vGGt&66UAh C#ljG(B7\?·]IXVhŏwW ÖҾ#|>?w?4;J<vÂL^>:Z @){z@[oAx,]$I$IΧyI3@ Uqh|O^(ASpIvf+Ì=5MGT|hVonX^ (P7 ?X9;c2)/ >@]#|g?/,1]IՅC渕>UW}߆gFl(=T,(;g+|hCJ Bܶ ?z̾n}={7ϐ,D\ZyH$I$Iv>,m#*:7:'v;&M ‰$?y r|d$xݍy?ˆ!y<ݶ+aUd^~KEȮ̥!@MvVc~C4009~S@C 8ѓ^4oaúI< G1rJim??@(7*gm@AA3Z#pwNu`0p7N=pͅMca"(<5y˟Kkt@a&$J8 HNi8#7_u[dps <>vT`4ce%V,וg6Y{P})aAAaѢJkD]xb;g`wxٗ#r;p4\^pKz+`nïf9| ~}rl1Y-7i9:Él7sQz1 u d]X~V}&>+uuu.`e_ghQwy: \.̀%` orzwВ9^}a^ h&1"REFUbhx1Ol/4@d|"f->$I$I$i NKE>_xJٵ va'`0ct_ñ( )xG]wDZC(5HuehCSːfTcۦE,فOS,}O SQ.Dv MJӹ_umvݼ˳S}Tg7i"ZTo mʹxձg/fW',&C@zv;܎uCbә9zT6##myJM3E_P$;kV$I$I45eSpW/~^P,^_|NJIv-^pT;WG2,Z#5't%՗w 8E?BCˬ놑r+\$%^?'6 dp?پQfzU  M־ i9LX⫟ MAGGw@Vp]S~*V?qu`0<3ƨr}#.v+,1~H$I$Iu$\R|*KaGGBGtB['ڜ6c9p/d}||2 +ޤsFU8U = il7- #^U}l(jރ9ȼ`?píB  al0@^7gt+6.Xk}Zf5$I$I4Mu5_ݫne ovDK[<ϯl~6j4~Q0<:sE`_d`dOȱ[}(tP. T8\I>/(\.6g3@$I$Ih{퉃жv}y=}tr-,I`n\`TL0S_ZWKn x'\~ J}?Cf}Km@F2f}/o+uOV~VN>[)#T9-@0uU#gz|,7Uet<hK=3SptˮYCH3W]=//O6uM`ƆZ~H$I$Is>9'mAjWlaaύVx}p?-ʿ_.w(~Jnu-.o%c"v#o*}D/s۴mJSPߐlz8iko&ŒYvW2Ji,{Y0+p@%g g$I$I$M1{[2AhyĎ6؝ zUkhhl][aEY?K /~pkKKwX#;|\I=Vش*lH#s,.UkK1Čp7gy#(~ˣ78]QO7HP7kn/{$I$I4ńsTw *آ`i,7 K^lyF l`Nsx5.)s|:aVv5¡/@MlF LE99e+}pRJeuֿÝya'gF hX DNpߨam bLL)ze88Wü`x:yh?+Trk^M}'3z{a)3x,\ayvA)S.ߟA|qC:Aν/fFg~VBC4$"ENGFWBFfG8g3GRiwj I$I$ISūL XYԔ'[W_ǦYCK 84;htzhnѹ'Ex۾A}=C] .5ne\6g_Ǡ!R̮\:6ΣŲ9]pz07O_ OjϖS-߃ʩif] R{Aec;FCSEσ`ڛEҜONz#ˆ9~ UiG(d DhW5 $I$I[#<$ Yxێ6xp 篓[r"x;0dHhZpԳ-T-r㼘/GqgQg)s-p;_l^2c>a6$I$I4a&Rhea28 OAW %07Pq~^I=6p\YXa I$I$I`Z c0w^oqH޿uH4^}T3@$I$I&+^nشҭ4{Lv+4>$I$I$i @Ktzn> u1e"o OCgr[*z/T*]>vi|H$I$IR9w4ٍ$IyI$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I,C GIENDB`cliapp/man/figures/inline-markup-1.png0000644000176200001440000006401514612252113017363 0ustar liggesusersPNG  IHDRoBJ pHYs   IDATxkxՀ_b"ш"jEVS/~ UbUV[V*j(Eb1@$$|?Ξ̜ۜK`ϓ'ggϞ}Z(((l޼yWˠ(={vHwEQE;JwEQEQEQEQEQEQEQ=.((((n_6rЫkZˣ(㶢((U@+(((^S??Q>峞H(ۊ(G8 |;Oi-Q hvgi-Q^"mI`\ˣ򁓁&+c/mM_ _1g01%ReoBǠ= "kxNP툽]M jn3E ։L!y)/Zg-@-C/wI}!6,dM8fF>KwQ~Ty&(f S^Rh˸kй3_}5_>UAqz ZQEQtaƀ|4bkҰ,@D`_zzuOoqRBP<}Vx ,,3U`-08$MeTOp8GQkf7i-Q1h!xc9@ϴNPD%7<4=Rr&]Nmm 9SԹZjkqlf9ŧرQ|,O63)e؋i@MC44|Ǝ$7uVEQ%53TXh\ƃ3x&8_ҿ+*=INЮb\q1šخ= =o긭((J;#qϋ3%1tJrO: IfOK).[\F'ގ)jO3ڟ/ck|>?@ql?b Uꋦ){JvWUj]c2Ბ,%ZGL8ʼnfq=͚{llܜxMEQEiO)EXR*AP\>TX]{H,n:zݍV'N3bvcgB@8Q禥DEǠ=NmF"xU(5r'w;i-Q{ _̨+Ћ a f$FRߦqn/7e5{Gq[QEQj*2>2MI-{=>=kI.6 '>=x:P?)M1s6pgjt jt7 ʞϐ 8~%[Ow2/20-;޽(dU\ͣ V2lHKΆRiL, mQ'E}SmEQEQ~ xWYw?`?D9#? >0\ 3Sټ&Wd֙y>(FƥC i3O"\WD E׈{~Ro 5n'|2P2PK~ɜ7md#@|xZg+p?d3 Tfa(s  GH] GCގ<ޏ\9u+BG m82)&isˁDmf!cЌD6"cl7dzӺc?m4 "dǡWHXLl3'A :#}zLIo3V&ͅ.)ݛWAj@45]kt@.}>E$ݦj^~m[hIc2 J%a+ֱfmd˖49d􍧾Q,_T O, 6 K(3MXj _6@nnMQ?l@DEhc}r5~UKaۡI'bHG5_6Kntq-FTv%ƻ۷4!rsEwq7MkYn=7na{Stߟ‘E}cWj'o&>n+($gB%>V@ +,&<^Dk8q.x8߶!Qxչu@?ۮE"u}D`f8oπ{WA$>3&AH{dיYOo"Ǹ%e糀"[ƠqcX s`G1(g+A6)YfV s|njNE> M چI"m6/Ts~5&b`"pG9"" ?;u+1:7(Ȇ/^D6ڍd޾'\]&Ƞ9?Tqinkf*$T֎As4ds}'2Bj@f!s">i3ngyFwQ7oNW:Ϝ=3\wmfZ̫NgFr}<6ik>fr^ܷ1[YW1ݮh R[Sk>no"7U\]+ MG܏?Dl."p80q#"hH}ffYwᯘ#u֊X\s]7d4(r#ޱBKx':˧\"|x4Fv}vE,d95]=o6&?@tx6n\gsro^g%d4.{oBq S+Aѷ/祰<\dnZ"qHt 2 mf!cД!mK ,RM{+A¿L3άyމ_h'11A P[-bSsϷ![Nʝg7(iD ڸ?6ѧ˾M,ʹgs~R0t8C-y/Osf5GqYAP5fLQ>V1emj6-sO,@匛7{%W1C Ph|!vUn$HoBݪLI P˂rőn'>8$tv6=o6܈&?9OKuȜ68;Y\yWZ 3CyzYwFގ nH;vbZg&b>ȣb%(1DD+ oh87h6v"cdb*@ .A0c6j1hB6HZFQuH6ǥ |&xǴ'~Bۍ-Ȝ'} wdTI kOmlQ4b ÇA6}wWyG Lb~E5~ݧ?eO3SX`knfԌ lF :xz0=y*ڼ Dm(׏E6})9n(NBښE?D Q:UugnewԑؽI܅XzYZ%L6t9=yD@وlKQߊ{!?B,:"mr9̶d}U(F3^@ز= y^#'CΉ``@9o'278cg#}6܃MW>A*INODdj*=qn!2^"!,t>5HP|q @fg#cך61(T r4FCuLDg-=2:`2e(9ްɏ<n|u?ojjCOˇS{Ӗ2q,ʆ8jDUWò5u /R 5{k‡8$FQSrpҐd'Nw]Ô2d9Oa%Ѫi kFp"xn ךU,BpW>6':Vqj%"t۔p+`$)x*.<.ۉ(M!i+"Z_t57AՙE%pqܓp<ح?@ԋD3SywHD:(b!u-EO6t Fk ؅o6jDQ*Hxڌ+s Ȣ 8ˑx&2o&m@mI '|qj3 Y5pgֈ6-M|&}?۩:;،X];AnFsW6> ͜%6,%sCS`tB렠K6dSSyIբČD&~b moG m}u@ ͆W(Øx,,v G }a|3 5ijs2 }ÎJTU_tQ>[iJnƙUwM2ְp>X }mQ٥ -uFdC]1c&?aW>0N>Q;|[7#aЋ~{'7nٳ,09 GM6VVùwVy챥y{9}:JoBAUTVͧtd[峕fH ,7zgיl"vDhmqcL%q7ϣQYg=g>y4"~Ī.y8nDMuf Š,ecؕJ0:Zgs!ċnoH$YuQ<e/[4Vh>MC݈!!líx+D*NĚU,vȼaku#ԊF2'n #_lZ®yQv&e(,NXZoOg jb$fMݮ7"1f-r݈u mf +ޟ½ L2e]G\[q): ] onwԮ[a1v3_1G_HnRh݄S =w{EU֊._16IBsmÍ_puhdi/64{R_̹Ɛ_<|bY}ۦ%KVy(*=}qjlp[Y39#yEc4W~DF}f3VEq(S((JSp~C@]_a3"Av;cYl{|֊׋]DF,Gc5bnm !o.20ݳ.s8x 7-LZ~bƃ.u q/HR݋~㧈4"v+w+f7}3VmkdDq!bI/|GAlAA`*[{q㿈ո*P 9 >;V}$ G8̔E+m7\;L]TCUn0ݹpy{`-/[Ç-D QOuRV PLnMkȜ)qa*߿7vc" UO2m&E.6W ÃΦmDز4E^jVfx9I>ܧ@&of|f%q$EQEQŎ#R/bބ̽0w(#[کw9oe]fx3p?bq'."~(O'9E^a$6'c.7BG_#1I纍D 4Ju-" irD$("M 1ؕb;qM&ޱ6qiш8uވ|ȼakM"'hM#N(MI6PDd+LRںGxNX@rۊFx&&dNFBrA oqeh"o7': ] lWpec=95r67UΆ 6Dѡ9aj/VޱǢųi vT$F䅎y(qzs$gJ"SGC7m\I0q#:sQ@2a KjDz(5tDb&CAlH됱5Zycm+:D˞J"fۆEm6Ku}3b}I]EL<[(s_#k~͆ hA<ؗ7q$Jm#6uP"81ƽVwHޕ2 d.,;zWe\GWְr\SHwSwb? v}_]=ۉޥCSڗT3cAX?j3H>2 u>9ѿeهꁷ'uȚ&U l͵E6d} +FPm6DqcYo/.7cwLyYgA dr;k6;ٴ5kV0n|ڟ:o.:/*RtQ0PcٟQSs(y 1 QX\N}= ^{cs`~ꕁA"ه]ijʑl꛰YKe?#u)((J Ȋ$x[T [o(aGR}6Wɓ>^c}#]Wo챪A֙Y-?#,Og@Rӂ}h/o["/t'lQbxK޽DLmA3'CHo !V7.^PA! RwZjkޤ2lBWTdx,$w(_39;hAYngYp`7 | -yP%Lw2}5r]Æ֬`'u%Cj\I]ղ8sO=ϰoo_f?|![W $Voz`͵[V;-d54xU_+YXg[~ϙ7ɬ(({(Q/&]`E /Q>›x8.#\O;m*]@;-cqEdG,,h̏!D-o^@ 88Su۰[$v# `y>.ao&SXL"f& ''S4ׄbX J ?u@.d!SWoHd Y aZN8ٌ dƦmb|N&kflR%pp\yYgA $A< =oL 72^øNs#'ט*> C(qܫ]/M\dqk,[Ⱥu rpt\+4C\Tƹ2' [o1) mlAʛI2iW"f& 'A'?ӱo2Ic.!a88 h H88i{Ӳ/U E88n!XǠFPm6렠x9xo^q,kd8Lÿn]YgA [W+4Guu6>hn2}wܴyxhvc0ӱY5Dr(>e1[Ϝ?MK`J :萨&dv#T%_f6^\\^ba wȆ Wa%^NUϙNe2؞wӦxƕVo̜i kwEQEQ`SQ-ND?{CWf ''SxcHtmfg.i;"wk{:](#^O#]JvT언JǏ"sso}LxƠFPm]G:"yN,`&z ?Ja8inpA6=F۸AY2:h]u]f׭䮟O1~އ4jl._ɔqW(JG د/r]nͤr],\먵 ̾"*o^"C?6AމZf7 K(g:۶`Sʙ$zxe6Ep}~UgR>v\0$\)\lP.m /wWνq1Bz7];*LB=Z-w"sP5'Ŀ9$s((JVJER#"L&Ab9]Z.{'{D*,>3tU5"֓#J*D;2sjo쯮حY:"BzKHK9mG=Wt*{#B[s" RWH;H0g:GˈxG26 N球m]2Yg.^ ڑ#GƇC7Q chEܗ[Lm#Iurt];qy"츦ERW#}|\.VIJZbsOvI|Զ$'ՆNE"dd5z-V};_8Q_;z ߇H]/ HT&R2^CJGn7^AY2|hX/n)*>AL|[̭td'a77,KU-z֯U45}?svq>UĦˇEе1ܲq5/>9#J9/V1o"j|h/6~Rzl,7-~=4-b"oeETeM3Ö¦C52"l]9*ggTɷI=MUoRUmaI~iɣ<}djS5sܹW Ҿjh<Ӭ{n` ̙\H €2fU=޾ +)pMɜh%%#̪hy-$0eJTEQEI%Nt.C}.@*w?4-6G2]]P"H׊n.?.@tm/R7b}0G| rTNtD3zK-,#.B粱 Qg`owG!BM>-VvT&؎QT9G1V$a+ngi#^B ڕK4sFy,GYUYgH{ơl"oz=vW址鲐U<[}k|79qVG^^Opo^nM%U}3&ݢ x16!mqwd}1/~MAAA,v 4kq(v 3pfvEg,wdL{a]5P]*TrDs\=TZװhNqܰ[gfm͵ΩwU1,T͑ER}EC4 KcKyE*cQUx)]yܰQP˛3p?Fgx%XRlt}CJ+{NDjX4%ECW|t}G3}&YZ`Ęb-IZTv-%WaZjj̥li#I=q!XC5s۸oh++{mɔنtEQE1Nw}`:2=8cm݊g^JgwnV+Ci/!b|>Bv?o_ WmE,~߫;n'㝈K$+&2i:ߛ@b&E/gV7MoHκ`D\)H@-V$qt؄(RͣH*"ǽL31ЈXzN<}c-H}=:J/˩xq"kgα |f?G~̛A ڬD2x3-V7u,}Pe6t-ؿx|)ҟ$rIt^ȯHu`ODyl!y̋nC,MfoG6TL#1$T֎A3CI#\dzȺ;tWu.v^Q\?bDe!]~?SGy3|:pg)cL'8(.{c 51<;Al.~Q9Q5s@yzq :B7y`HmT>QAiq_H2:}!wڞݍ>~؂锍h YOq9mH؋TL_R_4oC*xkBƔOeֽ9{gMvWEQeC 9O%dz>Ȣ)f+,TЍ K>zkD dF J뇸 |&X' _ ikĪtLe*bD0 e1BG!ud@,D{[#lB#L' `no:dP*cg ɞ73mA6ROÀ Ȇe&(,8'>}ˑGcQax]|jd>hR$6.3:?ADC96jaZ4DXdvq2VvBkH8h ΂~Gfټy@c=>a/ز=.۩1" 0@Qů.tiNNa$971tZYWi-+VbKnw7ma{B<5ldcRrY>INgncbWV0$]2i[wְtWHӖt^HFO7/X=G.4ѕrvy귰y3Uz$7Vj–& Cٟ7{d(쁴E ]Q=lDxyqِQZJ'7#QySQ2 D"[Z*$D{#+)1aoq,*Jm'Άݻ2K+P< ԯѓʦ2kⰔCF\5 xa Oj hmQ((bE(JmcU>휁XzaYYS(($!\Y>baUpFM% [Ex.O^ KU.6\Ɇz=΃F3Ϡ}SQEQ%P(Ɖ`H5U{AN~i8$]/$>3c>~2*(({EWSRhU+SsRq3Kz[vTUU*8]c (kT'M,s_=͈ܟ UѾ((tLwEQ|qI: d Ȧ??^EQEQ% eңcx8r3^pY%hhjlM[\3FQ7EQEQ2(ʞπE@+ЌTh$=d&9=7((푮vƮM! !r(l}LB((Ji `p,b(Q%ɴJw!8 CoAۀOPLCME\*]H_1KQEQ)YjK-klK# /FѨQ((,:((($͛7JwEQгgO*((((((((((((((((((((((((((((((((i74W8$~/ƐFxWxJlB = ?|gOfZ<2/EQt ^G;||"suю((((Jh|tZ} X(yEK=XEQƾw+H7 {CsN=<4q0܀b92 >e^DEAr;Cn'xQuYKD,ؿ/t"=ORAPkvMEQEQEQEٻ0Y@w7G+B3 vEQֿ۷Bs=7Zv:[_IYDbyq\Xl tnymLQ›~+K{!:ەv1ֶfl/OFw+((($XEa\Q%CxnM6|W"''CȎBV,Fj`<٘{{ yJ}  a(LE' aeҡeHffHY9}nE5`mioN&p90d8M_CVY K҅E\-CsOo>x|BB QӠS>tk3X_AXcȜ2w>GFrK:#、@9{wcnDq pM&`9jI&)^= M/ 9p0pu&>]կӳ`x!GCV] &Zg= pPufxXR `u]ð:1ȅDk,~GHyr;OqXŸ}eGeL%q; ;y|ב"pxVEQEQEQ@8yO`(v)Ghy,v+<[7[<~7qI}(hcd̊PE&smBQ}:\7!/`*"KD \1$,vXoQ 5pnUq?»<7A!~1]N(ߍB7~}}<5ye4xHy&bWjӌ(XcaOz,W " \ 3!K9gA+ O VӖH[j G\nl aIIBg.8g`ً}%(ȹ 5Rqڷϸ%zkv<͟wG+~9~ڲ*/>S~;`՛ +#/wZ>qlPӵf'`=s'DŽn:h;} : js#!ٟ*jul?F@@SF?Ģ<$5ka//G?.^\nԼ M_7a34qN߯|$| ֯p]QϟqԙއσG c{/k>ڏ94FuȂkwJ}Xmk ,}꿀qg1ȋ8luŮVx5~zO~gN>&!\o g7.=gA;]#FijiwaEQEQEQE X\p_Wv;q f&tpmƻ ~./{c? ZGz*H[k^)GikYirI;ٖ74y݌BU6x|6,eIGA6 Ʊqscu$&V]B.l3=yٝ2~F.]8~L VC\*nHwOsvOoXny9h> z)[\~;ݝ&3v4u]=LE"m-^{`y3*݆(-1=ƥl?ƾsל3DM%d؜ưQ1pA s9z 7;* pދZDyl+ÿ'փDo/nj@vks]T^+ b3`Sk9qptAmO۫gl{]g A&cg|A +(((JJsqbĊd`k"Tsp%ߌ>;a !Q-V$]>u9]#&刅Z$ ,6#V_ՎtwsWYIrĢ?C]YXu܂(g,WB:lY]b[y| ZOIA8-;gg|pqb,<`+O4o=*|dg3< 6m:|C"ǫ2K[btN{b ;џx0[9Itco"睡_!ԝ;]iNq% .ypg<>%e0-\oz߂oCE szvG?[[`ui+K t'QTgK_n{ +c4KCX&xck\f{ac!dtҊXyEFϨkn=GAz#}g Dqy b"Pn9ߗ;r.\ 8v]s^8FI` F":kmWٕ$&DݣԺAO15y!:+1zW+ 1n<0n'ro kk{]ڦX1n$'Š(((^]KH}6b:1_챗- -7#BVD!X$r=O-P"DP37^̀a@" 7|37AH8-Qf] BoXjB:챵S` ywr?^C'Z[Sg{hXq%K'éKI1{r#e'q}!j׹gYш{Or2hNq_H<\+T<X%:5Qg۷BMǗ_Wᔐ}C`͊3>cbOu4Ypmp%|Lv6Kٝݪލ+x3A +Ɂ2dAwaEQEQEQEI X\l[F"zЉiYXe~qtw sZ?˰EoΣ; YIXB !( &aS86GQe`e`TVYa k NY$twzU{Z^UW:9uNWnw޽}w'JZ)6S-h^Zt-7"uIx^ <{-_MjդA&kiFM@|do)pի)ԣNR= K$ITBpwwdwfހσ 'R]{s%T:I8G aU.ZG%gIG Qo !sM?˖z}d'V 0y ?[!#`+-ե0T =rux9YA|³E 0꫈uwn_nQ8{ pB;{s(CFDK$bP5s A+l5 v>ȫ?{0\s:< a."˩UZ;h<Մ]O78`#BVS`r= E]/,P’$I$Eܽ5xyl[eTR:0D+FB1i}l !T>Ugu {Y_-$7I⡲7Aq 08;ubN6=pH >[psbzjnᄩpHNOHyuumO:_Ԕg]aAC`hCBgGY2f>hT$ o@^x$I$IGts,nMSVwhysne%y;xT7z8ptWE%/`_VOs <i<3;%yGK]睰DgŞGUv{3;{ʌJ2- !gd+ʼO+ddP@;:)&S$g \Y&N:0o}Rh=dE#Mޣڽ+l pɎFy5tM8ȋkV;?\+YWdaf,HԽ`s6^~4v;ՆўyVq{&+I$IRգ3&"ۥs7VҘ~;=!Kَ6vIZ l7VtN!k+gl*,)*x? 5~D;-o"=`2`LyOq0\8*U6mv7\LDsX ڸxgaV&5ڹQ{V: _{6nWXY^mŴ] unRY'n4B?n)[|>HG̐0q6|VA>hMS YTK+¯~#TZo,ɳ/s)0r{hYAGfpk u 8}WQ}f>Gx7bQYM$Ih GX`mF:ywFERiMӁǀ/W1iP'>΋\X\dtn,ʬۧ6W~3,)I;!kB% w \8j93a0z\nKK4ɴV;iS-#^[u;- ǯg޿\C;0 ߡ0WS|;5^ttF:wn7uӹ68x zY0Q5B:}X8\={[W)<7N){){pˌ*Mo,IH=lv^&uvsn·4~gA.\x XkLwiܾ$.r;XY8Y/,I$IR"*[2̈́ e2; #t*qNqətG%YJ,&^Ⱥ #>?9anraк Ǿqȍz[cr$aD$ +}xWzΒtwKK @GIMb~u|\gk vt2\ cIT7+tJG;ߚX7] {ɻmK[a0y- :_> s;ht7Q= o@MIah"_/6Fw~EȺ^@qhru[ [ [ Yلz)ٿAVPxY/gK9mVqiǰ[p)xQ*8?ruڅ=>&9J9c'I3`d땣O,~lD`2C5CEuN 9 8x'sO֍W 6KNJ0~zlܵ5G`hp2 Rnw7NìJ-|kOU0|ǒ.ӀS |.{o`haI$Ij߁Ԉ7|Ch䞔]x2,0LU}d::!*?ts1746_?#vEw.&Ep{3w?Yd8r  aZ/:}OIo>UqtG׷&ٶ0Wx 56|`rd]0!rXv.rW1GppP #BB`"wJ  #’a^?95yOK+ٴgҚ ! txjv)~Iu͠D\R4|')>ªyu^~JhORx$[=7't?k#tS軄Gv)]u/*F:eσSt/ d=IDATNʭK{Ƿ[ mBٽ #YKk[NNM2ϊ=.Yo W6#˔-ޠDY[jJ{'gñ[ØH],ϲTPnzQ8oM.,ЁS%Jpb_$I$?!Cא"BC%yИ~{d݇]`ۄ?tFj| >«TމQYwg%}݄ug>F)p&adm%=rC;M|$D:zG% ]|I*#(KIPye|7w_|;$*& ]Urpܟ R,0ZdEGC{.D>߅>>7bt7K8fj3} ^y?m'1DσjjJ;):#*beiK %"zGW!%>:N^a}ArHYv8,s9\ڒs ֓[/3+==]Ze4't! ]-Pn'zy-9>\ ]W+i냤|FOpM_G9%I$I5i)IͶ >7oaDV1Ms27r_qnLh\F !%)*?+nMPyV=v^H$Iԏ%5糒6#˝u#IHVzBx|IJZ >ӷ̳g @$I~w@1ӀoWB=GSq7G3+I}3i핤:X{j1Ϫg%I$I1;Kf'e,^=씷|%/vD$5 $I$In.-n"70"鵷瞷@*iŜ 7~˹ hyy]׳-9-T$I$I:C: |xO}W+>v /OZ:V;&wXƈkg ^ \N|]}﹭[=画|ƈ! p՟5|k1ArZ꿩$I$I$tЧ toDz^`V`:iNvַAjs_nr׍n&KD/֕};._oSL^/DCYlIF-J$I$I1|p[;G IzSsmIz?wQκLq8Ysk2;G}8R?|Ucbԃ׳ ~SI$I$Ibi~d}7A o"~="1Y<ZX&;:LuI" L#Yq")̗[o*I$I$i ZWٺ8o~Sa[G33b I#9C&9J$I$IZbb1~4n 7fq*zD`''>IS6<41wVu^ܻUN'w 78n.ÔY݀J<|5jDnniz88-D{.uўSnZ$cz.{QOv{}*mL&i 17Đw &<VFՕC"wi1*~=D~ >@r֍?^7$_O-m]V{X1$$I$I& }\q99qW\'PH7<ȋms͓]^AspgMZyϩ/Û<]/黅\ܐ x\{ !rj4wW@JSަa=]&U&(ιZ]58)gZ9FYkOފ \Cf=dFMaPs- D!3kIh+_[Ki |<[Cڟ }fNC׶Ҟ[:ǟ&oLgtX֕Dt&͙N+0iP͐0iYN_O} 8E^~"8u3DZcZ37C$pq+q 䔳=b*7M|vw6+u#,W{"dg?M<Sq>UC{/9߅;keo%FZ$I$I4Rs^ײRwgbn[ߛ?MsrHyQXwW!{]Pf(!> !Tm.ɾ+.=lQf]ݞwSJyvgy^"p6MDCZ u V?ׇ)G8bT׍: kd-U9Yt>ϿX~z7vޟ 䔫= 5n ő,WQ5oc9Y7r^r*O?w[71c*S?(+u=%I$I$e*ϗ { H_xc8]SoXVu7Sו:gR:)vY)?N>+qa3M}ZEϢX?屫ݦn*57ܿV|} mo*\(ׇiAG>o>VmZ7i3fn/ prg=5=R~>AQrQ/X_qGVκyg9S(u~)5۝O>-I$I$[ɜGf?$r6PׯK^OSč2y=ivCc79Mˀl{>!R_p;/J>[MoiWdS)hn T\{)ga"W68Fӈ`|םT?0 |8a֗Q׃6PSbH蹘004 r^{J )/1w(@N=^Oggri0ٞ۳]ǀ߮f1w#G/I$I$ik>79s߬eGexUVfS_G1hp>A+Ӟsw˥bbq/`놆*F .DOO{b$:u5oud۩Nm|Yfw2X|==k[ {A}IL w\v{w;T޾ ;trs}`$?}m ڞ=|on@U{R{K3׫[[}$I$IԱ#؋س; $<ݼ7Gx{giK5Lbm쾎^/ w zELS}Qi*3 sP=_e=6xBb=k{&1z.[!x 8wUPO4BW|x 1C;mLǞ/=圶`TS 9TI=A-CcQ5I$I$iڑyId>\N194o'"p=ƒ׏<we7˛cQ#=V7?@ u7kE憞y$gJA&Q^]~o׎̞z͝ex) ~D;]ּo=7÷4` ba{& 䔣=}CЛSwY7Rg=A޺\H<0NԻ3ߒl--I$I$)1~k1ÇyW(C=Ch,-h\塨wvn>ݛށ)l|Dj:w4CvBL[ r>Wk8ַ)>@mҜ&=F ?J i03D}^%@V0*S]gWi4Eks!aFI9iur^=b}bWS?Ň$I$ISw*di43viLN{ATӜmd6%^Q y$ǁ3=thi/$y)pUg}^>ymF2"]\Ui5hXZ AAKޛW3&Ӏ[BiOUԟ[gPbaL+gh#l!urkZS>= !I$I$)7e[ԭz{6S p$n# 7|)T>i@z-A?,W 5f Q+}7ߦ0LYfoT8p(KDl8!zힼމ?X-&h6.h|z&YRh9lO+׫b pZMR HLPu#N+g݀׳؞ oݨwP>I}N=? ;I$I$I&8q- ˗s:`[\b7b}Vs%w 3D~>X jO9)n3EuO+JеcKDv{ܝxeMZ9sc UN$wn9lO甶bpv_6Ml 1OnmTo; !q L+gY9S)oxo ݨwPҺ2~$I$Iko站$\\}Mw7nxchұ|- 6(YX_CMn0Ay/_~R5'L}b0]LU59ˬ`ހH'q&KTYN \I롚s+`'vPN5i\ЫrO{eznt >"߉!yK5M|6oH)m9c7]Gu`-y&ʶ|{: p=:n6STh~8ie/xV+xm+_GVκs*s @n9Fyi;(WZ'T췉j$I$I*0^ќ)| B7ӛ6V=nnN3uG0?9j>O)}^wSуnزn*5TXnߜ?L2z0Ks6tV%}fض~j|moف٦e3DGqU2-\ցk9nOH#}p-]J+WY9S5i4n#Kծ%I$I$-"/`$I$I$Bg@b7&D0lbۇLh"h @d:Ǽ.W`s@O_7d^D/;`0s9$ݏEK.1D@nQ&_"B#>!8wrٞB#9Wub(\u#g/4u{@YI~2$I$I$I$IZs|_ّ$I$I$I$-E+pMSH$I$Is*K$IKˈ!| অȔ$I$I$I$ii9].\$I$I$I$IKpS /I$I$iܒ$IZNygI_?[|I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$)xIENDB`cliapp/man/figures/lists-1.png0000644000176200001440000002036514612252113015746 0ustar liggesusersPNG  IHDR pHYs   IDATx}ew]wnUQy"@H$D0MhH@qDl[vӆ02ӽƱgMtCzFqu۽mOȌ6NKie!hH+ϘUJU}Ծ9nZk:>U}&g̹vn=7VTWwUVΫn~zp{?[gWTVP`oU}ww~sv'VqV]ޞ]ٜ9iWƿzN\=9~hv7;@vTCpC5UwyUWuXyiXr|U?YoRg_iqwo)׮ۿ2 ՛{}`6>1;:1@uĘppLVԪasq?>Q)zT;XSZL0&zf@:j7e̿s&ƚ vɇǪTnCL{S;5*{mQW/h~ɛf& ohſ:cWOc[8+ 3gZ\]co-a6VЗ>8g@Ì鵜1/_z:j:zp&g59x^|iFZ6o|B}[7UaywY AZ`ֆarpwafUݦgv>|Fu{Glp~6=`WXlj޽En}SМϪ0:[WnӳNyK[֟WۢZcW]^v]}A n{n~zBuaE `afn9g;~oYl;`i ONzE5:.]Qվ7VhX҅u=: O:Z}~[by Zݲ>WzVuz,=F̩TLѳz:zZ)_kg5]Ұgՙ d?nNܦZ@sGOU:xfuٌKգ#MT qtX&j*W8:_zgU6:?PݹMNϮ/V/-S}k-շ7OKF4̌^W6@k'CpƢﮞT}a)EXn4T=zZuvuVC_, ^VB;8 -UWU8n^O־ꎎˍmo. .7+L~ptUOxT?\]ҰYwUwO v;vlΘCXomXXݶsկ|{QUTOάWo~o5έzNjOP;7Pkh3\@uSuj2.iDw,пS:Y+kN>ax4@Bv:|bِz='ͨ :ҰZگ[%wƳ&x!]l۫gV/}5ջ?2qꫣ?T=ໆe:ag쩞0;vs/zFU hcf̀~ĸULfFw b_RZoXϕЇG<\gaWQVi@sk&ƽoƸs{Qkx A9iu `uB;ڸHcϬ>={O Z]8>V$ l=w4c?Q}blg\{zǗ>zju{I 3Wutoyz޸α@oWhu`ΖZ[yCg4̊y==W9"g{K?--wkC;G''3k >j,_=I@o [G߽Ŵ-XZd|Ӱ ƿwpg^wV5[ţn^v͕չ͞}8>jt~ą5,TSҢ8MV콭ՑMԻˆYMSy'Q=jG>mX{E^_4;|_={3 !>y:,ַ_4K =q\.c͸vVF_>1:?J809Ϫ.LbOR>U=KT߯wT^%ojﰯ.QgOu]k' B}r~zH_*cvկT~ut~:XwU3mշ7]ձM_nXb{Kwm} YO{M)c?R\CwػicN);\}OLY.}a30|T}2 }roѦm 3WVٳ?VW_q){<s}oE7!>z{ujw>V]6n_uG{q4?v̅t^uq@ rMӃׯV̸tTvT?U=ꢅu9h`V蛪WUϩ?zO臫v-wNَ[Ζ@ ƮV?MZ=mgK$]{s[Ǹk6U߼mm/}eW<~bL Q˫+_0-s+k8a>\ xf:>\a ;Y{ 3o@m`!(܍k'{9ui_gsu&g@N95o.S뫯ie_lM{?_7 YnCΆjs3j஧ơ[Uk2^I3k}d]2?Uu~:I{NE4I>԰sSFcT=qY 4]=wWhX6_Q 6ך4%₩a gv2ڇ'?2;^qEU߿.iXv{C !WC TeWT߉QhQ nǷzIWl+[\ի&N%/[5j4@ Z2˧Z@Zj{7wnƸ[ۉ{.oei;gԻfbYj}+ ᆀ{܇B̚v:qf8P=kamK\ct߃ Hrc٭Ro@^4֙էGcΝyn-E7p9аgOU_];TWZq^þ+>2W:4Z>8ڃ'zyZhl6, ,۰3~R՝;)jKkˉ󳷩4+˦rhU\Wx ju2z{V}OKnbqoC୬5ͭ ,8{X\laq8%w?n?bA&{= uպn/.[ovC6yl KpKh_Ñ5ƟC:ٳ'[״s3grЗ4Y|px-4,qkp|?V֚Qs?zD/}b̩sEf2p@}'RGOUr!X_̆媧YnڦZY]0޿Xgr2>='i_7%V`Cp٪4}'7Pc;]e_:m~x֞ꃣYȜnmʌ_X1-b??vn4qv'45ϷVj9ѹ 9]=|uCRՋbr˪x{u㋫W#%}ձ㕋mE7 KE5q1:Xmcwεs奄zwu[.Z|uaot1+6]uV)o/立Von3v'V]i]V{YkqϾY+ܸ]3pѵ77nF[b[]v7v󴽑˥m?:ˉn`XzGVt.,?%7putt>^{{kG[G߸ߚ2w휎Ǫmq|aP~kH7MϽ!_wONCTvrU;궉g~o)ΜRʎ+k'щ3Ui =ՇF_7PY^}'߯.X:gLnǪ˦\WݾvszRCCòi [_U}l9eUIDAT6yɊIENDB`cliapp/man/figures/alerts-3.png0000644000176200001440000001553114612252113016103 0ustar liggesusersPNG  IHDRJ+JJ pHYs   IDATxyxUչoIB$SdV@Y+J՟SՊtmk[Vmkũ:Vjq" Ȭa)fsrIyg^g@$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I MgSw'6$I$I$IHz_0^=H îcev-h7>L'3VJ~(xk&oeI{鐕I@I&a7p2$JIf:OS=e =/I$I$I tC.SI$+19Y+%4M2hIVU˕'rWͳ,QwJ'PZ:O%4Rڿ{9v$cj.%QJ۱цު.g^ gIVj.ytI\%I$I$I[Mh*f7D*:29u5diI/!'+% 5J#OI$I$I՘Y\ћyL޺h<5^0 hDDıJc6<>OI$I$I.:=25Qie%q+LV-.ʨ^U\B$3YynWwo,( YIvpQ*қ_b2>mVGA]u&ɡY8L㊟= Cqƈ׾VdWUwUW87fw ҖM2˲ 7Q89oVvOPn.QmN˴mNS%  nW-g bd<EVB4I_,]#*+힗EYC9<LO%'||mD_FܟI8;_2(ǩ$I$I$IuR%Ԋ@Ҟ.΅u9L>T~`N{F^j.9MY8Vvw/qg]UZgz_6nDcgoWonytO˓yTeti2-#pz\R2MKg/oes͟1M3hZ<_w n nx~f5=^xj`VgV<}`"O)@cWKzc=^~T|퐎 XR?pq\hu<={cFr@ʇ_SZIrk,=vzevyJ$I$I$K9sgQE,s,*(8`ivjP)u5Vd*RbZY90rr2aVo֠=[Aډ.Y 1/8>/G>Ӫ >C/?g甽(5;3>~m@JINY`*ןusm뜜LʄRjW|HFrSY >\8 ˴ʂρ0iw]+3-wbByJ$I$I$k \z:7JpHfZΜKQ;9%d.Z79VYP FpQXjGG)iԁ.=+5GD&bRp8V~X93 Y.%eXoގܵsNmwf=ؾm,1~ fQged K֯g{Us4N<}g&]ܶ9 ʠN mV6VX8D8XO4Fn# "NI;w"fU5d8?S"M̄Y{R?NJGb+w\;[C:{$I$I$I.=/l;eP+RJsxcL ./d9v egy_ghKf/۠̅s&= ƍ>)8x م`n2J:w,>~K"}^U{~έ霝MMX+7ٛaͬ]}mk,)(T8PNz&^ fOImr|'Ӻ)¢ ;W6"ss}͔٘xϥ}Ia<^(PX(ТQױ hɝL:;CY%GsE͜k7sgb|um N\}sߘ _d)I$I$Iw(%ާJv:&+P|JO% 5^FUK+?ŠFƕ#W>kx˿ >I$I$IVS:2}3ͬ]:(tBbgrM+%vC\ZMlџ'`hCہS9%36c:#Q"P R)O zDϲL],Z+K'P}菎G[jߟdc2J0["K{NiA#%2/{k5 f *fKWcfA L܋Ox{^h|8rn%yd)۔$I$I$I+XqwnC-.6iBmE@KLJu+ DR]{ vpcpobVq(-gs۹8? فA;%f%dιK[WfAWcE U8 ̦%!e{lO 8#|:=;2,e-%$`i]֚vmrhg-fVϾ䳪%\\k>/=DcD1;%9+ֱ}.ѭ%4Mv}}RwX{QK$I$I$jtU{֗kX:%i&gO\ Agp/qLʴx_oK C{;NS7mo;ݴ}L weeËFP%&mh/{pd0wE|7}Acz1?^鵫]=ɓmKHbpr2%k ;g?W&=n_>iKิҵ;u{Řƍh!yKI$I$I=](ۃM[ٸ~} 3^vf:s^ MޝK}sf6f^pܶ9y-[vt/wEnp {`AŲWO5j|]~_E2ƍDȏoH%Cldudob/!#I$I$I}՘<Al_ҢiHO_ࢠQyppK[N n/8~RR<&yu.י"%9O 9-LZ0Z)_ZS{@G(_t2ށuY<4bZG}MHI 5|Y#-LzC!e+Sy<1ϥF4 {^y,]|p ׬a Y]=KVd NJlm^=/g4B:Vl`Uٞ;36c/? w4'9yA,῵=k\y@;9mh>OI$I$I}Y=r?yO=yO==Ӌ5DUc>s0/8YםεfZ9sPSyK㟕En.鞺׹xGcDcriJ[3x _y-%ږ%;~;SU4:kC8]y.(`wl' y1hI7qT_B?s5KU, M3h:Lkל/?wmMJ[$>Co<t67ɘj^jn?ϊFq~<'&wGʂsenQdB2;*>ppfjELZG뒜|`"A.P #{r)8C9!1@NfuYCbfy}Y?&y&تƻE~5-ng -JͧP3)I$I$I/13>aLJ!Vq)P4=W=ϚكU^ɕYNs2&îgyo' X^g(X$ܖgoCy>DZRbN.p~cϐ 0Zź1#ïbu|_.*`QeQnyl&UoQ<Θ٘x~6R(U!aKdyy /2> &eTuc}[Ȋcd K*Ces⿡Z3)3/^[$I$I$)Rz{rʠ ۰0KꋥL =$OYg2i;:ʦe}S^uCCrb9hj6N#c:V{>.\sަ߁??'7JѺ-kF) § ڷ9t@,_˲F4p>~%K$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I7DȘIENDB`cliapp/man/figures/headings-3.png0000644000176200001440000001332714612252113016374 0ustar liggesusersPNG  IHDRoBJ pHYs  IDATx{e/pDA%4ЖyZ;򀹻ry`WVz;ڪfW.,smeUN]j05 )s/;Ȁ?>wfy'x*wt&f2mmk|k嗟A%IE՟eMYYY}';;Ɓ{ ѣ)ӝe^KYYYvid++*ۼЮUUUeeeoPY9hihHk떯ΫsWoS[o}:>asɆ:s]_m]d_{˳~>;)ee![E_<7TUUd͚Ymں;]d3^:-7ߝdQ9+˖3 qVc~Y+I:;;$;vx ZWyy =T\lɬYn84rjF7դ#;3n{sAeԨ1io_,]Z.yNҹCOϺb~]L9.C MuuM^~yaOryvy=]]8"ˮV4g__df2RbNy-+ՍHMM_,C cՃظS$Iyywǖ-?稣N͘1#5YlIzYx**jRY@9l؈m:uGrU?In7>wh.|̊|{$s>.:mmkr)ӟJO8sY̜y\/_o@.45sNOOw|\zDz==5jlfμ3Gko 7RN=zŽ/_N|}ȌS<zGs?s!?K%ny}XU[rq*9zWdΜs%'wwn55[=u괜uֵ?%g?]wݣɹ->'IYYy&O>&&Pr|{geeu,>'IEEef̘sι \XYYYJ^l!C;vv;wJ&Oo;̺---ik[uEҽ: r㍟lKG~c'C/5Ozbz=8SQO{̙s^x ޔ=/{ޔW }o=:Cw_ΑG#F>/hEG} mio_dݑMM{f\ٚ^k.zNUUMZZLtkֶ:ɺC (׮~ߥ47?>2f47?Yu~cwnʷD;tg쳿.ԩkuGOR;f=3q?#<-?-y{3͛ur)w}}nB{„7~U(O8y2 s8U.̴if͚?oʕ1,Y8Ir-3seGRs~ w.SW72?As ZC;ʿ [MWcE=\}}['3myI֖sfs?󙛊ϯJf<&/lo?2$1btObkvx[o8E9ԋs h˭g>ԴώNO$ &_E}nÆ /|-\҅y9= ;JggG.By\}y2i!2=hɱA}NCà~fo?G`bŲsΤ^˗ҥM;7%oC Ћ/̙f%gg'gdgs%wd+w]z08f$׮ jn (1ґNUQQ7VGGGo%㽽x==ݹ|>fͽn8ƽq׌Ҋ O=`>9o?X}81cr?j_>Ff޼s䑧&Ijkf*X/r/su7t+3?sl_}m`wGN첲~G2vvvd=޽̚u^f:/ƽ7x775jL.\t1;&$===moo+|.++Keeȏ=,]joJn'~>',[$Og'3oރyKl>֬i/jWW9V}Te}.7=ϛS^^ѣܹߊ~GwΫsEgckEs;vLȢEO%Y]]+OJ[^kw.͵"g?=G+wuufxK˒\_EE6:@߬'go|4u*r!2yS &gєf=c۩伦o=sϝ_}㪪 f|Kǧ=έ,Keeu+u_7l]z|{䪫~E{zzSڞ{'I] ҭ׿~f38:?5'?9vVU Yg]/}5YQQ˼yQr|Ԩ],tumۭY"O=di}y9%^oѢ'޾6ɺwK Z==Y<[<Џr~!E44 n{gMM}?cw>/_tԑgmUS3$uu|o+@݇dР>WjOOOw]WXr^c.5jzv]N0aj3qZWHsmU;y =im.~>B{ݽ}JYنuOOV=UUm(wvuKut-|?k@rw|+eƌY9ԋ jqwӂ)Ǖt_跐]JYYyN>cG}Rw),xW˗2Uӟ^Yg/YIMM];=K)/ɥ~߽*G45+qAaT*ZvXFSTt-W񶦦6 Zbvgo,ߴ %at+5\xw}_bi>{bS_߳ oqn6ۑ/?>ws-[U/ _ ⊏9_9LJK>_g>353jՆc~GQ29g Y|Y?aN̹?klz7t?ᆿUW!z5ko4ޖ/`,uɘmmqUOo2֜սftvD[YvUɘoUVlMww[wߞ??Z2^{{[u$>/ ]ҥ{oʵ~rr#Fqd]ǥzP::3lȴ4穧+,,v {)Y \z8>[*r'f]LWWwjk#fѢ9[i@veLVKWVnO }sI9cv ޔ^z6]HkRyNp$Y5y?~_N?IENDB`cliapp/man/themes.Rd0000644000176200001440000000660614521234223014057 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/docs.R \name{themes} \alias{themes} \title{CLI themes} \description{ CLI elements can be styled via a CSS-like language of selectors and properties. Note that while most of the CSS3 language is supported, a lot visual properties cannot be implemented on a terminal, so these will be ignored. } \section{Adding themes}{ The style of an element is calculated from themes from four sources. These form a stack, and the styles on the top of the stack take precedence, over styles in the bottom. \enumerate{ \item The cliapp 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. Most features of CSS selectors are supported here:, for a complete reference, see the selectr package. 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{CSS pseudo elements}{ Currently only the \verb{::before} and \verb{::after} pseudo elements are supported. } \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{content} property is supported to insert text via \verb{::before} and \verb{::after} selectors. More properties might be adder later. Please see the example themes and the source code for now for the details. } \section{Examples}{ Color of headers, that are only active in paragraphs with an 'output' class: \if{html}{\out{
}}\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") ) }\if{html}{\out{
}} Create a custom alert type: \if{html}{\out{
}}\preformatted{list( ".alert-start::before" = list(content = symbol$play), ".alert-stop::before" = list(content = symbol$stop) ) }\if{html}{\out{
}} } cliapp/man/cli_verbatim.Rd0000644000176200001440000000074514521233614015233 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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") } cliapp/man/cli_end.Rd0000644000176200001440000000070514521233614014164 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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() } cliapp/man/cli_par.Rd0000644000176200001440000000157414521233614014205 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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) } cliapp/man/cli_h1.Rd0000644000176200001440000000141314521233614013723 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.R \name{cli_h1} \alias{cli_h1} \alias{cli_h2} \alias{cli_h3} \title{CLI headers} \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 header. It can contain inline markup.} \item{id}{Id of the header element, string. It can be used in themes.} \item{class}{Class of the header element, string. It can be used in themes.} \item{.envir}{Environment to evaluate the glue expressions in.} } \description{ CLI headers } \examples{ cli_h1("Main title") cli_h2("Subtitle") cli_text("And some regular text....") } cliapp/man/start_app.Rd0000644000176200001440000000242114521235275014566 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/package.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("message", "stdout"), .auto_close = TRUE, .envir = parent.frame() ) stop_app(app = NULL) default_app() } \arguments{ \item{theme}{Theme to use, passed to the cliapp initializer.} \item{output}{How to print the output, passed to cliapp initializer.} \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. } cliapp/man/cli_div.Rd0000644000176200001440000000251014521233614014174 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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") } cliapp/man/cli_alert.Rd0000644000176200001440000000252214521233614014524 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/client.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 cliapp} installed successfully.") cli_alert_danger("Could not download {pkg cliapp}.") cli_alert_warning("Internet seems to be unreacheable.") cli_alert_info("Downloaded 1.45MiB of data") } cliapp/DESCRIPTION0000644000176200001440000000206114612270172013231 0ustar liggesusersPackage: cliapp Title: Create Rich Command Line Applications Version: 0.1.2 Authors@R: c( person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre")), person("Posit Software, PBC", role = c("cph", "fnd")) ) Description: Create rich command line applications, with colors, headings, lists, alerts, progress bars, etc. It uses CSS for custom themes. This package is now superseded by the 'cli' package. Please use 'cli' instead in new projects. License: MIT + file LICENSE URL: https://github.com/r-lib/cliapp#readme BugReports: https://github.com/r-lib/cliapp/issues Depends: R (>= 3.6) Imports: cli, crayon, fansi, glue (>= 1.3.0), prettycode, progress (>= 1.2.0), R6, selectr, utils, withr, xml2 Suggests: callr, covr, rstudioapi, testthat Encoding: UTF-8 RoxygenNote: 7.2.3 NeedsCompilation: no Packaged: 2024-04-24 20:40:33 UTC; gaborcsardi Author: Gábor Csárdi [aut, cre], Posit Software, PBC [cph, fnd] Maintainer: Gábor Csárdi Repository: CRAN Date/Publication: 2024-04-24 20:50:02 UTC cliapp/tests/0000755000176200001440000000000014521235425012667 5ustar liggesuserscliapp/tests/testthat/0000755000176200001440000000000014612270172014526 5ustar liggesuserscliapp/tests/testthat/test-containers.R0000644000176200001440000000544714521235606020007 0ustar liggesusers context("cli containers") setup(start_app()) teardown(stop_app()) test_that("auto closing", { cli_div(theme = list(".xx .emph::before" = list(content = "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::before" = list(content = "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::before" = list(content = "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::before" = list(content = "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::before" = list(content ="!!!") )) out <- capt0(cli_alert_success("{pkg foobar} is good")) expect_match(out, "!!!", fixed = TRUE) }) cliapp/tests/testthat/test-subprocess.R0000644000176200001440000000636414521235606020031 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() { cliapp::cli_div() cliapp::cli_h1("title") cliapp::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("muffleMessage"))) { invokeRestart("muffleMessage") } } withCallingHandlers( rs$run(do), cliapp_message = handler) expect_equal(length(msgs), 4) lapply(msgs, expect_s3_class, "cliapp_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() { cliapp::cli_div() cliapp::cli_h1("title") cliapp::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("muffleMessage"))) { invokeRestart("muffleMessage") } }), rs$run(do) ) expect_equal(length(msgs), 4) lapply(msgs, expect_s3_class, "cliapp_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") do <- function() { options(crayon.enabled = TRUE) options(crayon.colors = 256) crayon::num_colors(forget = TRUE) withCallingHandlers( cliapp_message = function(msg) { withCallingHandlers( cliapp:::cli_server_default(msg), message = function(mmsg) { class(mmsg) <- c("callr_message", "message", "condition") signalCondition(mmsg) invokeRestart("muffleMessage") } ) invokeRestart("muffleMessage") }, { cliapp::start_app(theme = cliapp::simple_theme()) cliapp::cli_h1("Title") cliapp::cli_text("This is generated in the {emph subprocess}.") "foobar" } ) } rs <- callr::r_session$new() on.exit(rs$kill(), add = TRUE) msgs <- list() result <- withCallingHandlers( callr_message = function(msg) { msgs <<- c(msgs, list(msg)) if (!is.null(findRestart("muffleMessage"))) { invokeRestart("muffleMessage") } }, rs$run_with_output(do) ) 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() }) cliapp/tests/testthat/test-lists.R0000644000176200001440000001236514521235606016775 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_it("foo") cli_it(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_it("foo") cli_it(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" = "-"), it = list("margin-left" = 2) )) lid <- cli_ul() out <- capt0({ cli_it("1") lid2 <- cli_ul() cli_it("1 1") cli_it(c("1 2", "1 3")) cli_end(lid2) cli_it("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" = "*"), it = list("margin-left" = 2) )) lid <- cli_ul() out <- capt0({ cli_it("1") lid2 <- cli_ol() cli_it("1 1") cli_it(c("1 2", "1 3")) cli_end(lid2) cli_it("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( it = list("margin-left" = 2) )) lid <- cli_ol() out <- capt0({ cli_it("1") lid2 <- cli_ol() cli_it("1 1") cli_it(c("1 2", "1 3")) cli_end(lid2) cli_it("2") }, strip_style = TRUE) expect_equal(out, "1. 1\n 1. 1 1\n 2. 1 2\n 3. 1 3\n2. 2\n") cli_end(lid) }) test_that("ol ul", { cli_div(theme = list( ul = list("list-style-type" = "*"), it = list("margin-left" = 2) )) lid <- cli_ol() out <- capt0({ cli_it("1") lid2 <- cli_ul() cli_it("1 1") cli_it(c("1 2", "1 3")) cli_end(lid2) cli_it("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_it("foo") cli_it(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_it(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_it(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_it(c(this = "foo")) cli_it(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( it = list("margin-left" = 2) )) lid <- cli_dl() out <- capt0({ cli_it(c(a = "1")) lid2 <- cli_dl() cli_it(c("a-a" = "1 1")) cli_it(c("a-b" = "1 2", "a-c" = "1 3")) cli_end(lid2) cli_it(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\nb: 2\n") cli_end(lid) }) test_that("dl ol", { cli_div(theme = list( it = list("margin-left" = 2) )) lid <- cli_dl() out <- capt0({ cli_it(c(a = "1")) lid2 <- cli_ol() cli_it(c("1 1")) cli_it(c("1 2", "1 3")) cli_end(lid2) cli_it(c(b = "2")) }, strip_style = TRUE) expect_equal(out, "a: 1\n 1. 1 1\n 2. 1 2\n 3. 1 3\nb: 2\n") cli_end(lid) }) test_that("dl ul", { cli_div(theme = list( ul = list("list-style-type" = "*"), it = list("margin-left" = 2) )) lid <- cli_dl() out <- capt0({ cli_it(c(a = "1")) lid2 <- cli_ul() cli_it(c("1 1")) cli_it(c("1 2", "1 3")) cli_end(lid2) cli_it(c(b = "2")) }, strip_style = TRUE) expect_equal(out, "a: 1\n * 1 1\n * 1 2\n * 1 3\nb: 2\n") cli_end(lid) }) test_that("ol dl", { cli_div(theme = list( it = list("margin-left" = 2) )) lid <- cli_ol() out <- capt0({ cli_it("1") lid2 <- cli_dl() cli_it(c("a-a" = "1 1")) cli_it(c("a-b" = "1 2", "a-c" = "1 3")) cli_end(lid2) cli_it("2") }, strip_style = TRUE) expect_equal(out, "1. 1\n a-a: 1 1\n a-b: 1 2\n a-c: 1 3\n2. 2\n") cli_end(lid) }) test_that("ul dl", { cli_div(theme = list( ul = list("list-style-type" = "*"), it = list("margin-left" = 2) )) lid <- cli_ul() out <- capt0({ cli_it("1") lid2 <- cli_dl() cli_it(c("a-a" = "1 1")) cli_it(c("a-b" = "1 2", "a-c" = "1 3")) cli_end(lid2) cli_it("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_it(c(that = "bar", other = "foobar")) }, strip_style = TRUE) expect_equal(out, "this: foo\nthat: bar\nother: foobar\n") cli_end(lid) }) cliapp/tests/testthat/test-alerts.R0000644000176200001440000000163314521235606017125 0ustar liggesusers context("cli alerts") setup(start_app()) teardown(stop_app()) test_that("generic", { cli_div(theme = list(".alert::before" = list(content = "GENERIC! "))) out <- capt0(cli_alert("wow")) expect_match(out, "GENERIC") }) test_that("success", { cli_div(theme = list(".alert-success::before" = list(content = "SUCCESS! "))) out <- capt0(cli_alert_success("wow")) expect_match(out, "SUCCESS") }) test_that("danger", { cli_div(theme = list(".alert-danger::before" = list(content = "DANGER! "))) out <- capt0(cli_alert_danger("wow")) expect_match(out, "DANGER") }) test_that("warning", { cli_div(theme = list(".alert-warning::before" = list(content = "WARNING! "))) out <- capt0(cli_alert_warning("wow")) expect_match(out, "WARNING") }) test_that("info", { cli_div(theme = list(".alert-info::before" = list(content = "INFO! "))) out <- capt0(cli_alert_info("wow")) expect_match(out, "INFO") }) cliapp/tests/testthat/test-substitution.R0000644000176200001440000000030514521235606020402 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()}}")) }) cliapp/tests/testthat/test-headers.R0000644000176200001440000000140414521235606017242 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") }) }) cliapp/tests/testthat/test-custom-handler.R0000644000176200001440000000070314521235606020555 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, "cliapp_message") expect_equal(conds[[1]]$type, "h1") expect_equal(conds[[2]]$type, "h2") expect_equal(conds[[3]]$type, "text") }) cliapp/tests/testthat/helper.R0000644000176200001440000000157714521233614016141 0ustar liggesusers test_style <- function() { list( ".testcli" = list( h1 = list( "font-weight" = "bold", "font-style" = "italic", "margin-top" = 1, "margin-bottom" = 1), h2 = list( "font-weight" = "bold", "margin-top" = 1, "margin-bottom" = 1), 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 } cliapp/tests/testthat/test-text.R0000644000176200001440000000134614521235606016620 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")) }) }) cliapp/tests/testthat/test-themes.R0000644000176200001440000000346014521233614017115 0ustar liggesusers 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, ")))")), "span.emph::before" = list(content = "<<"), "span.emph::after" = list(content = ">>") )) 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::before" = list(content = "custom:")) override <- list(".alert::before" = list(content = "override:")) start_app(theme = custom) out <- capt0(cli_alert("Alert!")) expect_match(out, "custom:") withr::with_options(list(cli.user_theme = override), { start_app(theme = custom) out <- capt0(cli_alert("Alert!")) expect_match(out, "override:") }) }) cliapp/tests/testthat/test-inline.R0000644000176200001440000000200014521235606017076 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(content = "<<<"), list(content =">>>")), names = c( paste0("span.", class), paste0("span.", class, "::before"), paste0("span.", class, "::after") ) ) 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) }) cliapp/tests/testthat/test-progress-bars.R0000644000176200001440000000257414521235606020431 0ustar liggesusers context("cli progress bars") setup(start_app()) teardown(stop_app()) test_that("progress bars", { x <- 100 withr::with_options( list(cli.width = 40, crayon.enabled = FALSE, crayon.colors = 1), { out <- capt0({ cli_text("so far so good: {x}") bar <- cli_progress_bar(total = 5, force = TRUE, show_after = 0) bar$tick() bar$tick() cli_text("still very good: {x}!") bar$tick() cli_text(strrep("1234567890 ", 6)) bar$tick() bar$tick() cli_text("aaaaand we are done") }) }) out <- strsplit(out, "\n", fixed = TRUE)[[1]] exp <- c( "so far so good: 100", paste0("\r[======>--------------------------] 20%", "\r[============>--------------------] 40%", "\r ", "\rstill very good: 100!"), paste0("[============>--------------------] 40%", "\r[===================>-------------] 60%", "\r ", "\r1234567890 1234567890 1234567890"), "1234567890 1234567890 1234567890", paste0("[===================>-------------] 60%", "\r[=========================>-------] 80%", "\r[=================================] 100%", "\r ", "\raaaaand we are done") ) expect_equal(out, exp) }) cliapp/tests/testthat.R0000644000176200001440000000130114521235425014645 0ustar liggesuserslibrary(testthat) library(cliapp) ## 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("cliapp") ) withr::with_options( list(cli.unicode = TRUE), test_check("cliapp") ) 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("cliapp") ) } cliapp/R/0000755000176200001440000000000014521235273011727 5ustar liggesuserscliapp/R/cli.R0000644000176200001440000001701114521233614012616 0ustar liggesusers #' @importFrom R6 R6Class cliapp <- R6Class( "cliapp", public = list( initialize = function(theme = getOption("cli.theme"), user_theme = getOption("cli.user_theme"), output = c("message", "stdout")) clii_init(self, private, theme, user_theme, match.arg(output)), ## Themes list_themes = function() clii_list_themes(self, private), add_theme = function(theme) clii_add_theme(self, private, theme), remove_theme = function(id) clii_remove_theme(self, private, id), ## Close container(s) end = function(id) clii_end(self, private, id), ## Generic container div = function(id, class = NULL, theme = NULL) clii_div(self, private, id, class, theme), ## Paragraphs par = function(id, class = NULL) clii_par(self, private, id, class), ## Text, wrapped text = function(...) clii_text(self, private, ...), ## Text, not wrapped verbatim = function(...) clii_verbatim(self, private, ...), ## Markdow(ish) text, wrapped: emphasis, strong emphasis, links, code md_text = function(...) clii_md_text(self, private, ...), ## Headers h1 = function(text, id, class = NULL) clii_h1(self, private, text, id, class), h2 = function(text, id, class = NULL) clii_h2(self, private, text, id, class), h3 = function(text, id, class = NULL) clii_h3(self, private, text, id, class), ## Block quote blockquote = function(quote, citation = NULL, id, class = NULL) clii_blockquote(self, private, quote, citation, id, class), ## Lists ul = function(items = NULL, id = NULL, class = NULL, .close = TRUE) clii_ul(self, private, items, id, class, .close), ol = function(items = NULL, id, class = NULL, .close = TRUE) clii_ol(self, private, items, id, class, .close), dl = function(items = NULL, id, class = NULL, .close = TRUE) clii_dl(self, private, items, id, class, .close), it = function(items = NULL, id = NULL, class = NULL) clii_it(self, private, items, id, class), ## Code code = function(lines, id, class = NULL) clii_code(self, private, lines, class), ## Tables table = function(cells, id, class = NULL) clii_table(self, private, cells, class), ## Alerts alert = function(text, id, class = NULL, wrap = FALSE) clii_alert(self, private, "alert", text, id, class, wrap), alert_success = function(text, id, class = NULL, wrap = FALSE) clii_alert(self, private, "alert-success", text, id, class, wrap), alert_danger = function(text, id, class = NULL, wrap = FALSE) clii_alert(self, private, "alert-danger", text, id, class, wrap), alert_warning = function(text, id, class = NULL, wrap = FALSE) clii_alert(self, private, "alert-warning", text, id, class, wrap), alert_info = function(text, id, class = NULL, wrap = FALSE) clii_alert(self, private, "alert-info", text, id, class, wrap), ## Progress bars progress_bar = function(id, ...) clii_progress_bar(self, private, id, ...), progress = function(id, operation, ...) clii_progress(self, private, id, operation, ...) ), private = list( raw_themes = NULL, theme = NULL, margin = 0, state = NULL, output = NULL, get_matching_styles = function() tail(private$state$matching_styles, 1)[[1]], get_style = function() tail(private$state$styles, 1)[[1]], xtext = function(..., .list = NULL, indent = 0) clii__xtext(self, private, ..., .list = .list, indent = indent), vspace = function(n = 1) clii__vspace(self, private, n), inline = function(..., .list = NULL) clii__inline(self, private, ..., .list = .list), item_text = function(type, name, cnt_id, ..., .list = NULL) clii__item_text(self, private, type, name, cnt_id, ..., .list = .list), get_width = function() clii__get_width(self, private), cat = function(lines) clii__cat(self, private, lines), cat_ln = function(lines, indent = 0) clii__cat_ln(self, private, lines, indent), match_theme = function(element_path) clii__match_theme(self, private, element_path), progress_bars = list(), get_progress_bar = function() clii__get_progress_bar(self, private), cleanup_progress_bars = function() clii__cleanup_progress_bars(self, private) ) ) #' @importFrom xml2 read_html xml_find_first clii_init <- function(self, private, theme, user_theme, output) { private$output <- output private$raw_themes <- list( default = builtin_theme(), optional = theme) private$theme <- theme_create(private$raw_themes) private$state <- list(doc = read_html("")) private$state$current <- xml_find_first(private$state$doc, "./body") private$state$matching_styles <- list(body = private$match_theme("./body")) root_styles <- private$theme[private$state$matching_styles[[1]], ] root_style <- list(main = list(), before = list(), after = list()) for (i in seq_len(nrow(root_styles))) { root_style <- merge_styles(root_style, root_styles[i,]) } private$state$styles <- list(body = root_style) private$state$xstyles <- character() self$add_theme(user_theme) invisible(self) } ## Text ------------------------------------------------------------- #' @importFrom fansi strwrap_ctl clii_text <- function(self, private, ...) { private$xtext(...) } clii_verbatim <- function(self, private, ..., .envir) { style <- private$get_style()$main text <- paste(unlist(list(...), use.names = FALSE), collapse = "\n") if (!is.null(style$fmt)) text <- style$fmt(text) private$cat_ln(text) invisible(self) } clii_md_text <- function(self, private, ...) { stop("Markdown text is not implemented yet") } ## Headers ---------------------------------------------------------- clii_h1 <- function(self, private, text, id, class) { clii__header(self, private, "h1", text, id, class) } clii_h2 <- function(self, private, text, id, class) { clii__header(self, private, "h2", text, id, class) } clii_h3 <- function(self, private, text, id, class) { clii__header(self, private, "h3", text, id, class) } clii__header <- function(self, private, type, text, id, class) { id <- new_uuid() clii__container_start(self, private, type, id = id, class = class) on.exit(clii__container_end(self, private, id), add = TRUE) text <- private$inline(text) style <- private$get_style()$main if (is.function(style$fmt)) text <- style$fmt(text) private$cat_ln(text) invisible(self) } ## Block quote ------------------------------------------------------ clii_blockquote <- function(self, private, quote, citation, id, class) { stop("Quotes are not implemented yet") } ## Table ------------------------------------------------------------ clii_table <- function(self, private, cells, id, class) { stop("Tables are not implemented yet") } ## Alerts ----------------------------------------------------------- clii_alert <- function(self, private, type, text, id, class, wrap) { clii__container_start(self, private, "div", id = id, class = paste(class, "alert", type)) on.exit(clii__container_end(self, private, id), add = TRUE) text <- private$inline(text) style <- private$get_style() text[1] <- paste0(style$before$content, text[1]) text[length(text)] <- paste0(text[length(text)], style$after$content) if (is.function(style$main$fmt)) text <- style$main$fmt(text) if (wrap) text <- strwrap_ctl(text, exdent = 2) private$cat_ln(text) invisible(self) } cliapp/R/html-readme.R0000644000176200001440000000250714521233614014252 0ustar liggesusers knit_print.html <- function(x, zoom = 2, ...) { x <- paste(x, collapse = "\n") html <- html_page(fansi::sgr_to_html(x)) html_file <- tempfile(fileext = ".html") font_file <- file.path(dirname(html_file), "Menlo-Regular.ttf") file.copy( system.file(package = "cli", "Menlo-Regular.ttf"), font_file ) on.exit(unlink(c(html_file, font_file)), add = TRUE) image_file <- tempfile(fileext = ".png") on.exit(unlink(image_file), add = TRUE) cat(html, file = html_file) asNamespace("webshot")$webshot(html_file, image_file, selector = "#content", zoom = zoom) img <- readBin(image_file, "raw", file.info(image_file)[, "size"]) structure( list(image = img, extension = ".png", url = NULL), class = "html_screenshot" ) } html_page <- function(content) { html <- paste0( '
\n',
    content, '
         
' ) structure(html, class = "html") } cliapp/R/cliapp-package.R0000644000176200001440000000133614521235130014706 0ustar liggesusers#' Create Rich Command Line Applications #' #' Create rich command line applications, with colors, headings, lists, #' alerts, progress bars, etc. It uses CSS for theming. #' #' See [themes] for theming, [containers] for container elements, #' [inline-markup] for more about command substitution and inline markup. #' #' See also the various CLI elements: #' * Text elements: [cli_text()], [cli_verbatim()], [cli_h1()]. #' * Containers: [cli_div()], [cli_par()], [cli_end()]. #' * Lists: [cli_ul()], [cli_ol()], [cli_dl()], [cli_it()]. #' * Alerts: [cli_alert()]. #' * Progress bars: [cli_progress_bar()]. #' #' @aliases cliapp-package NULL #' @keywords internal "_PACKAGE" ## usethis namespace: start ## usethis namespace: end NULL cliapp/R/inline.R0000644000176200001440000000455214521233614013333 0ustar liggesusers #' @importFrom glue glue glue_collapse inline_list <- NULL #' @importFrom utils globalVariables if (getRversion() >= "2.15.1") globalVariables(c("self", "private")) inline_generic <- function(self, private, class, x) { id <- clii__container_start(self, private, "span", class = class) on.exit(clii__container_end(self, private, id), add = TRUE) style <- private$get_style() xx <- paste0(style$before$content, x, style$after$content) if (!is.null(style$main$fmt)) xx <- style$main$fmt(xx) xx } inline_transformer <- function(code, envir) { res <- tryCatch({ expr <- parse(text = code, keep.source = FALSE) eval(expr, envir = envir) }, error = function(e) e) if (!inherits(res, "error")) return(res) code <- glue_collapse(code, "\n") m <- regexpr("(?s)^([[:alnum:]_]+)[[:space:]]+(.+)", 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 = inline_transformer) inline_generic(self, private, funname, out) } cmd_transformer <- function(code, envir) { res <- tryCatch({ expr <- parse(text = code, keep.source = FALSE) eval(expr, envir = envir) }, error = function(e) e) if (!inherits(res, "error")) return(res) code <- glue_collapse(code, "\n") m <- regexpr("(?s)^([[:alnum:]_]+)[[:space:]]+(.+)", 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 = cmd_transformer) paste0("{", funname, " ", out, "}") } glue_cmd <- function(..., .envir) { ## This makes a copy that can refer to self and private str <- unlist(list(...), use.names = FALSE) environment(cmd_transformer) <- environment() args <- c(str, list(.envir = .envir, .transformer = cmd_transformer)) do.call(glue, args) } clii__inline <- function(self, private, ..., .list) { ## This makes a copy that can refer to self and private environment(inline_transformer) <- environment() args <- c(list(...), .list, list(.transformer = inline_transformer)) do.call(glue, args) } cliapp/R/utils.R0000644000176200001440000000270714521233614013215 0ustar liggesusers `%||%` <- function(l, r) if (is.null(l)) r else l 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) } new_uuid <- (function() { cnt <- 0 function() { cnt <<- cnt + 1 paste0("cli-", cliappenv$pid, "-", cnt) } })() #' @importFrom utils tail tail_na <- function(x, n = 1) { tail(c(rep(NA, n), x), n) } #' @importFrom crayon col_substr 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)) } 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() ) unlist(r, use.names = FALSE) } is_cran_check <- function() { if (identical(Sys.getenv("NOT_CRAN"), "true")) { FALSE } else { Sys.getenv("_R_CHECK_PACKAGE_NAME_", "") != "" } } cliapp/R/simple-theme.R0000644000176200001440000001353514521233614014447 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 = cliapp::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 = cliapp::simple_theme()) #' ``` #' #' @param dark Whether the theme should be optiomized for a dark #' background. If `"auto"`, then cliapp 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 #' @examplesIf !cliapp:::is_cran_check() #' cli_div(theme = cliapp::simple_theme()) #' #' cli_h1("Header 1") #' cli_h2("Header 2") #' cli_h3("Header 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 cliapp} {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: {fun cliapp::simple_theme} and {arg arguments}.") #' #' cli_text("Files: {file /usr/bin/env}") #' cli_text("URLs: {url https://r-project.org}") #' #' cli_h2("Longer code chunk") #' cli_par(class = "r-code") #' 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 = "r-code") #' cli_verbatim(format(ls)) #' cli_end() #' #' cli_end() simple_theme <- function(dark = "auto") { 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 } } 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" ), ".alert-danger::before" = list( content = paste0(symbol$cross, " ") ), ".alert-warning" = list( color = "orange", "font-weight" = "bold"), ".alert-warning::before" = list( content = paste0("!", " ") ), ".alert-success::before" = list( content = paste0(crayon::green(symbol$tick), " ") ), ".alert-info::before" = list( content = paste0(crayon::cyan(symbol$info), " ") ), ".alert-start::before" = list( content = paste0(symbol$arrow_right, " ")), span.pkg = list( color = "blue", "font-weight" = "bold"), span.version = list(color = "blue"), .code = simple_theme_code(dark), "span.code::before" = list(content = "`"), "span.code::after" = list(content = "`"), ".r-code" = list( fmt = simple_theme_r_code(dark), "margin-top" = 1, "margin-bottom" = 1), span.emph = simple_theme_emph(), span.strong = list("font-weight" = "bold", "font-style" = "italic"), span.fun = simple_theme_code(dark), "span.fun::after" = list(content = "()"), span.arg = simple_theme_code(dark), span.key = simple_theme_code(dark), "span.key::before" = list(content = "<"), "span.key::after" = list(content = ">"), span.file = simple_theme_file(), span.path = simple_theme_file(), span.email = simple_theme_url(), span.url = simple_theme_url(), "span.url::before" = list(content = "<"), "span.url::after" = list(content = ">"), span.var = simple_theme_code(dark), span.envvar = simple_theme_code(dark), span.timestamp = list(color = "grey"), "span.timestamp::before" = list( content = "["), "span.timestamp::after" = list( content = "]") ) } 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::combine_styles( crayon::make_style("#232323", bg = TRUE), crayon::make_style("#f0f0f0") ) } else { crayon::combine_styles( crayon::make_style("#f8f8f8", bg = TRUE), crayon::make_style("#202020") ) } function(x) { lines <- strsplit(x, "\n", fixed = TRUE)[[1]] code <- tryCatch(prettycode::highlight(lines), error = function(x) lines) len <- fansi::nchar_ctl(code) padded <- paste0(" ", code, strrep(" ", max(len) - len), " ") style(padded) } } 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 }) } cliapp/R/lorem.R0000644000176200001440000000310614521233614013165 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 } cliapp/R/containers.R0000644000176200001440000001372214521233614014221 0ustar liggesusers #' @importFrom withr defer #' @importFrom xml2 xml_add_child #' @importFrom glue glue clii__container_start <- function(self, private, tag, class = NULL, id = NULL) { id <- id %||% new_uuid() if (is.null(class)) { private$state$current <- xml_add_child( private$state$current, tag, id = id) } else { private$state$current <- xml_add_child( private$state$current, tag, id = id, class = class) } matching_styles <- private$match_theme( glue("descendant-or-self::*[@id = '{id}']")) new_styles <- private$theme[ setdiff(matching_styles, private$get_matching_styles()), ] private$state$matching_styles <- c(private$state$matching_styles, structure(list(matching_styles), names = id)) new_style <- list(main = list(), before = list(), after = list()) for (i in seq_len(nrow(new_styles))) { new_style <- merge_styles(new_style, new_styles[i, ]) } new_style <- merge_embedded_styles(private$get_style(), new_style) private$state$styles <- c(private$state$styles, structure(list(new_style), names = id)) ## Top margin, if any private$vspace(new_style$main$`margin-top` %||% 0) invisible(id) } #' @importFrom xml2 xml_find_first xml_name xml_remove xml_parent #' xml_attr #' @importFrom utils head #' @importFrom stats na.omit clii__container_end <- function(self, private, id) { ## Do not remove the if (xml_name(private$state$current) == "body") return(invisible(self)) ## Defaults to last container if (is.null(id) || is.na(id)) { id <- xml_attr(private$state$current, "id") } ## Do we have 'id' at all? cnt <- xml_find_first( private$state$doc, glue("descendant-or-self::*[@id = '{id}']")) if (is.na(xml_name(cnt))) return(invisible(self)) ## Remove the whole subtree of 'cnt', pointer is on its parent private$state$current <- xml_parent(cnt) xml_remove(cnt, free = TRUE) rm(cnt) ## Bottom margin del_from <- match(id, names(private$state$matching_styles) %||% character()) bottom <- max(viapply( private$state$styles[del_from:length(private$state$styles)], function(x) as.integer(x$main$`margin-bottom` %||% 0L) )) private$vspace(bottom) ## Remove styles as well deleted_styles <- tail(names(private$state$matching_styles), -(del_from - 1)) private$state$matching_styles <- head(private$state$matching_styles, del_from - 1) private$state$styles <- head(private$state$styles, del_from - 1) ## Styles that are to be removed when the container ends xstyles <- na.omit(private$state$xstyles[deleted_styles]) if (length(xstyles)) { private$raw_themes[xstyles] <- NULL private$theme <- theme_create(private$raw_themes) private$state$xstyles <- setdiff(private$state$xstyles, xstyles) } invisible(self) } ## div -------------------------------------------------------------- clii_div <- function(self, private, id, class, theme) { theme_id <- self$add_theme(theme) clii__container_start(self, private, "div", class, id) private$state$xstyles <- c(private$state$xstyles, structure(theme_id, names = id)) id } ## Paragraph -------------------------------------------------------- clii_par <- function(self, private, id, class) { clii__container_start(self, private, "par", class, id) } ## Lists ------------------------------------------------------------ clii_ul <- function(self, private, items, id, class, .close) { id <- clii__container_start(self, private, "ul", id = id, class = class) if (length(items)) { self$it(items); if (.close) self$end(id) } invisible(id) } clii_ol <- function(self, private, items, id, class, .close) { id <- clii__container_start(self, private, "ol", id = id, class = class) if (length(items)) { self$it(items); if (.close) self$end(id) } invisible(id) } clii_dl <- function(self, private, items, id, class, .close) { id <- clii__container_start(self, private, "dl", id = id, class = class) if (length(items)) { self$it(items); if (.close) self$end(id) } invisible(id) } #' @importFrom xml2 xml_parent xml_path xml_attr clii_it <- function(self, private, items, id, class) { id <- id %||% new_uuid() ## check the last active list container last <- private$state$current while (! xml_name(last) %in% c("ul", "ol", "dl", "body")) { prev <- last last <- xml_parent(last) } ## if not the last container, close the ones below it if (xml_name(last) != "body" && xml_path(last) != xml_path(private$state$current)) { self$end(xml_attr(prev, "id")) } ## if none, then create an ul container if (xml_name(last) == "body") { cnt_id <- self$ul() type <- "ul" } else { cnt_id <- xml_attr(last, "id") type <- xml_name(last) } if (length(items) > 0) { for (i in seq_along(items)) { id <- clii__container_start(self, private, "it", id = id, class = class) private$item_text(type, names(items)[i], cnt_id, items[[i]]) if (i < length(items)) self$end(id) } } else { private$state$delayed_item <- list(type = type, cnt_id = cnt_id) id <- clii__container_start(self, private, "it", id = id, class = class) } invisible(id) } clii__item_text <- function(self, private, type, name, cnt_id, ..., .list) { style <- private$get_style()$main head <- if (type == "ul") { paste0(style$`list-style-type` %||% "*", " ") } else if (type == "ol") { res <- paste0(private$state$styles[[cnt_id]]$main$start %||% 1L, ". ") private$state$styles[[cnt_id]]$main$start <- (private$state$styles[[cnt_id]]$main$start %||% 1L) + 1L res } else if (type == "dl") { paste0(name, ": ") } private$xtext(.list = c(list(head), list(...), .list), indent = -2) } ## Code ------------------------------------------------------------- clii_code <- function(self, private, lines, id, class) { stop("Code is not implemented yet") } ## Close container(s) ----------------------------------------------- clii_end <- function(self, private, id) { clii__container_end(self, private, id) } cliapp/R/server.R0000644000176200001440000000045414521233614013360 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() do.call(app[[type]], msg$args) } cli_server_callr_handler <- function(msg) { cli_server_default(msg) } cliapp/R/width.R0000644000176200001440000000072014521233614013165 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) } cliapp/R/internals.R0000644000176200001440000000420314521233614014045 0ustar liggesusers #' @importFrom fansi strwrap_ctl clii__xtext <- function(self, private, ..., .list, indent) { style <- private$get_style()$main text <- private$inline(..., .list = .list) text <- strwrap_ctl(text, width = private$get_width()) if (!is.null(style$fmt)) text <- style$fmt(text) private$cat_ln(text, indent = indent) invisible(self) } clii__get_width <- function(self, private) { style <- private$get_style()$main left <- style$`margin-left` %||% 0 right <- style$`margin-right` %||% 0 console_width() - left - right } clii__cat <- function(self, private, lines) { if (private$output == "message") { clii__message(lines, appendLF = FALSE) } else { cat(lines, sep = "") } private$margin <- 0 } clii__cat_ln <- function(self, private, lines, indent) { if (!is.null(item <- private$state$delayed_item)) { private$state$delayed_item <- NULL return(private$item_text(item$type, NULL, item$cnt_id, .list = lines)) } style <- private$get_style()$main ## left margin left <- style$`margin-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 private$margin <- 0 bar <- private$get_progress_bar() if (is.null(bar)) { if (private$output == "message") { clii__message(paste0(lines, "\n"), appendLF = FALSE) } else { cat(paste0(lines, "\n"), sep = "") } } else { msg <- paste(lines, sep = "\n") msg <- crayon::reset(msg) bar$message(msg, set_width = FALSE) } } clii__vspace <- function(self, private, n) { if (private$margin < n) { sp <- strrep("\n", n - private$margin) if (private$output == "message") { clii__message(sp, appendLF = FALSE) } else { cat(sp) } private$margin <- n } } clii__message <- function(..., domain = NULL, appendLF = TRUE) { msg <- .makeMessage(..., domain = domain, appendLF = appendLF) msg <- crayon::reset(msg) message(msg, appendLF = FALSE) } cliapp/R/client.R0000644000176200001440000003130314521233614013325 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("cliapp")$Description) #' #' ## Arguments are concatenated #' cli_text("this", "that") #' #' ## Command substitution #' greeting <- "Hello" #' subject <- "world" #' cli_text("{greeting} {subject}!") #' #' ## Inline theming #' cli_text("The {fun cli_text} function in the {pkg cliapp} package") #' #' ## Use within container elements #' ul <- cli_ul() #' cli_it() #' cli_text("{emph First} item") #' cli_it() #' cli_text("{emph Second} item") #' cli_end(ul) cli_text <- function(..., .envir = parent.frame()) { cli__message("text", as.list(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 headers #' #' @param text Text of the header. It can contain inline markup. #' @param id Id of the header element, string. It can be used in themes. #' @param class Class of the header 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_it("one") #' cli_it("two") #' cli_it("three") #' cli_end() #' #' ## Complex item, added gradually. #' cli_ul() #' cli_it() #' cli_verbatim("Beginning of the {emph first} item") #' cli_text("Still the first item") #' cli_end() #' cli_it("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 = vcapply(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_it("one") #' cli_it("two") #' cli_it("three") #' cli_end() #' #' ## Nested lists #' cli_div(theme = list(ol = list("margin-left" = 2))) #' cli_ul() #' cli_it("one") #' cli_ol(c("foo", "bar", "foobar")) #' cli_it("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 = vcapply(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_it(c(foo = "one")) #' cli_it(c(bar = "two")) #' cli_it(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 = vcapply(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_it("one") #' cli_it("two") #' cli_it("three") #' cli_end() #' #' ## Complex item, added gradually. #' cli_ul() #' cli_it() #' cli_verbatim("Beginning of the {emph first} item") #' cli_text("Still the first item") #' cli_end() #' cli_it("Second item") #' cli_end() cli_it <- function(items = NULL, id = NULL, class = NULL, .auto_close = TRUE, .envir = parent.frame()) { cli__message( "it", list( items = vcapply(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 cliapp} installed successfully.") #' cli_alert_danger("Could not download {pkg cliapp}.") #' 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 progress bar #' #' A progress bar using the progress package #' #' @param ... All arguments are passed to the constuctor of the #' [progress::progress_bar] class. #' @return A remote progress bar object that can be used the same way #' as [progress::progress_bar], see examples below. #' #' @export #' @examplesIf !cliapp:::is_cran_check() #' { #' p <- cli_progress_bar(total = 10) #' cli_alert_info("Starting computation") #' for (i in 1:10) { p$tick(); Sys.sleep(0.2) } #' cli_alert_success("Done") #' } cli_progress_bar <- function(...) { id <- cli__message("progress_bar", list(id = NULL, ...)) cli__remote_progress_bar(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)) { defer(cli_end(id = args$id), envir = .envir, priority = "first") } cond <- list(message = paste("cli message", type), type = type, args = args, pid = cliappenv$pid) class(cond) <- c("cliapp_message", "callr_message", "condition") withRestarts({ signalCondition(cond) cli__default_handler(cond) }, muffleMessage = 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) } } cliapp/R/docs.R0000644000176200001440000001522014521233614012777 0ustar liggesusers #' CLI inline markup #' #' @section Command substitution: #' #' All text emitted by cliapp supports glue interpolation. Expressions #' enclosed by braces will be evaluated as R code. See [glue::glue()] for #' details. #' #' In addition to regular glue interpolation, cliapp 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 #' cases the string within the braces is 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. #' #' See examples below. #' #' You can simply add new classes by defining them in the theme, and then #' using them, see the example below. #' #' @name inline-markup #' @examples #' ## Some inline markup examples #' cli_ul() #' cli_it("{emph Emphasized} text") #' cli_it("{strong Strong} importance") #' cli_it("A piece of code: {code sum(a) / length(a)}") #' cli_it("A package name: {pkg cliapp}") #' cli_it("A function name: {fun cli_text}") #' cli_it("A function argument: {arg text}") #' cli_it("A keyboard key: press {key ENTER}") #' cli_it("A file name: {file /usr/bin/env}") #' cli_it("An email address: {email bugs.bunny@acme.com}") #' cli_it("A URL: {url https://acme.com}") #' cli_it("A variable name: {var mtcars}") #' cli_it("An environment variable: {envvar R_LIBS}") #' cli_end() #' #' ## Adding a new class #' cli_div(theme = list( #' span.myclass = list(color = "lightgrey"), #' "span.myclass::before" = list(content = "["), #' "span.myclass::after" = list(content = "]"))) #' cli_text("This is {myclass in brackets}.") #' cli_end() 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_it()]. #' #' 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. Note that while most of the CSS3 language is supported, #' a lot visual properties cannot be implemented on a terminal, so these #' will be ignored. #' #' @section Adding themes: #' The style of an element is calculated from themes from four sources. #' These form a stack, and the styles on the top of the stack take #' precedence, over styles in the bottom. #' #' 1. The cliapp 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. Most features of CSS selectors are supported here:, for a #' complete reference, see the selectr package. #' #' 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 CSS pseudo elements: #' Currently only the `::before` and `::after` pseudo elements are #' supported. #' #' @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 `content` property is #' supported to insert text via `::before` and `::after` selectors. #' #' More properties might be adder later. #' #' Please see the example themes and the source code for now for the #' details. #' #' @section Examples: #' Color of headers, 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::before" = list(content = symbol$play), #' ".alert-stop::before" = list(content = symbol$stop) #' ) #' ``` #' @name themes NULL cliapp/R/package.R0000644000176200001440000000453314521235273013452 0ustar liggesusers cliappenv <- new.env() cliappenv$stack <- list() cliappenv$pid <- Sys.getpid() .onLoad <- function(libname, pkgname) { if (is.null(getOption("callr.condition_handler_cliapp_message"))) { options(callr.condition_handler_cliapp_message = cli__default_handler) } } #' 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, passed to the cliapp initializer. #' @param output How to print the output, passed to cliapp initializer. #' @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("message", "stdout"), .auto_close = TRUE, .envir = parent.frame()) { app <- cliapp$new( theme = theme, user_theme = getOption("cli.user_theme"), output = match.arg(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 } cliapp/R/themes.R0000644000176200001440000001264214521233614013341 0ustar liggesusers #' @importFrom cli symbol clii_list_themes <- function(self, private) { private$raw_themes } clii_add_theme <- function(self, private, theme) { id <- new_uuid() private$raw_themes <- c(private$raw_themes, structure(list(theme), names = id)) private$theme <- theme_create(private$raw_themes) id } clii_remove_theme <- function(self, private, id) { if (! id %in% names(private$raw_themes)) return(invisible(FALSE)) private$raw_themes[[id]] <- NULL private$theme <- theme_create(private$raw_themes) 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. #' #' @export builtin_theme <- function() { list( body = list(), h1 = list( "font-weight" = "bold", "font-style" = "italic", "margin-top" = 1, "margin-bottom" = 1), h2 = list( "font-weight" = "bold", "margin-top" = 1, "margin-bottom" = 1), h3 = list( "text-decoration" = "underline", "margin-top" = 1), ".alert::before" = list( content = paste0(symbol$arrow_right, " ") ), ".alert-success::before" = list( content = paste0(crayon::green(symbol$tick), " ") ), ".alert-danger::before" = list( content = paste0(crayon::red(symbol$cross), " ") ), ".alert-warning::before" = list( content = paste0(crayon::yellow("!"), " ") ), ".alert-info::before" = list( content = paste0(crayon::cyan(symbol$info), " ") ), par = list("margin-top" = 1, "margin-bottom" = 1), ul = list("list-style-type" = symbol$bullet), "ul ul" = list("list-style-type" = symbol$circle), "ul ul ul" = list("list-style-type" = symbol$line), ol = list(), dl = list(), it = list("margin-left" = 2), .code = list(), span.emph = list("font-style" = "italic"), span.strong = list("font-weight" = "bold"), span.code = list(color = "magenta"), "span.code::before" = list(content = "`"), "span.code::after" = list(content = "`"), span.pkg = list(color = "magenta"), span.fun = list(color = "magenta"), "span.fun::after" = list(content = "()"), span.arg = list(color = "magenta"), span.key = list(color = "magenta"), "span.key::before" = list(content = "<"), "span.key::after" = list(content = ">"), span.file = list(color = "magenta"), span.path = list(color = "magenta"), span.email = list(color = "magenta"), span.url = list(color = "blue"), "span.url::before" = list(content = "<"), "span.url::after" = list(content = ">"), span.var = list(color = "magenta"), span.envvar = list(color = "magenta") ) } #' @importFrom selectr css_to_xpath to_xpath <- function(sel) { sel2 <- sub(":?:before$", "", sel) sel3 <- sub(":?:after$", "", sel2) css_to_xpath(sel3) } get_selector_mode <- function(sel) { ifelse(grepl(":?:before$", sel), "before", ifelse(grepl(":?:after$", sel), "after", "main")) } theme_create <- function(theme) { mtheme <- unlist(theme, recursive = FALSE, use.names = FALSE) mtheme[] <- lapply(mtheme, create_formatter) selectors <- unlist(lapply(theme, names)) res <- data.frame( stringsAsFactors = FALSE, selector = selectors, xpath = to_xpath(selectors), mode = get_selector_mode(selectors), style = I(mtheme) ) 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 xml2 xml_path xml_find_all clii__match_theme <- function(self, private, element_path) { el <- xml_find_first(private$state$doc, element_path) paths <- lapply( private$theme$xpath, function(xp) { vcapply(xml_find_all(private$state$doc, xp), xml_path) } ) which(vlapply(paths, `%in%`, x = xml_path(el))) } #' @importFrom utils modifyList merge_styles <- function(old, new) { old[[new$mode]] <- modifyList(as.list(old[[new$mode]]), new$style[[1]]) old } merge_embedded_styles <- function(oldstyle, newstyle) { old <- oldstyle$main new <- newstyle$main ## side margins are additive, rest is updated, counter is reset 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) start <- new$start %||% 1L mrg <- modifyList(old, new) mrg[c("margin-top", "margin-bottom", "margin-left", "margin-right", "start")] <- list(top, bottom, left, right, start) oldstyle$main <- mrg oldstyle$before <- newstyle$before oldstyle$after <- newstyle$after oldstyle } cliapp/R/progress-bar.R0000644000176200001440000000323114521233614014454 0ustar liggesusers #' @importFrom progress progress_bar clii_progress_bar <- function(self, private, id, ...) { stream <- stderr() if (!nzchar(stream)) stream <- stdout() bar <- progress_bar$new( ..., stream = stream, width = private$get_width()) stbar <- list(bar) names(stbar) <- id private$progress_bars <- c(private$progress_bars, stbar) private$cleanup_progress_bars() invisible() } clii__get_progress_bar <- function(self, private) { finished <- vlapply(private$progress_bars, function(x) x$finished) last <- tail_na(which(!finished)) if (is.na(last)) NULL else private$progress_bars[[last]] } clii__cleanup_progress_bars <- function(self, private) { finished <- vlapply(private$progress_bars, function(x) x$finished) private$progress_bars <- private$progress_bars[!finished] } cli__remote_progress_bar <- function(id) { id <- id bar <- list( tick = function(len = 1, tokens = list()) cli__message("progress", list(id, "tick", len, tokens)), update = function(ratio, tokens = list()) cli__message("progress", list(id, "update", ratio, tokens)), message = function(msg, set_width = TRUE) cli__message("progress", list(id, "message", msg, set_width)), terminate = function() cli__message("progress", list(id, "terminate")), finished = FALSE ) class(bar) <- "cli_remote_progress_bar" bar } clii_progress <- function(self, private, id, operation, ...) { if (!id %in% names(private$progress_bars)) return() bar <- private$progress_bars[[id]] if (bar$finished) { private$progress_bars[[id]] <- NULL } else { bar[[operation]](...) } if (bar$finished) private$progress_bars[[id]] <- NULL } cliapp/NEWS.md0000644000176200001440000000032514612267042012624 0ustar liggesusers# cliapp 0.1.2 No changes. # 0.1.1 * cliapp is superseded, and we focus on the cli package now. * Fix a potential error when text is outputted without an enclosing container. # 0.1.0 First public release. cliapp/MD50000644000176200001440000000707014612270172012040 0ustar liggesusers27d4e462b96fdc4e922567c8be35a402 *DESCRIPTION 5de8c8943bcfac9cda8b01d4536dbd14 *LICENSE 3419b99d49f09d8236514a372f51d79d *NAMESPACE f4461507b62054bbf4388bd295ab6b0f *NEWS.md 9f680c4d6576d82ce38d96654bcc2a38 *R/cli.R 38e1ec578547867f251899244db63c38 *R/cliapp-package.R d7747b37918ed2365990a293e1e9209d *R/client.R d4e9589c86b07135a9478bcd2434b3de *R/containers.R 9be2a5f01ef3c3fac94127fe1acc5e4d *R/docs.R 1e699fbc79237e169a98b02c984d8d4d *R/html-readme.R af68fa2fd00027a3faa8b3071233f92f *R/inline.R c6b93e66659e6b80afbbd4d36ab83bd7 *R/internals.R ff681a26e738a802bd685bd2961a5039 *R/lorem.R e6fcdeb4a56a2f68425ea249aacc0cb3 *R/package.R 159057ceced58d35220d687c6c328224 *R/progress-bar.R 35592b45641d2b147f2d369e5eca46df *R/server.R 0226be96b882de5eca3a866d89ce4f24 *R/simple-theme.R 356cb8effff00bf781f1d8a725210596 *R/themes.R b81e8b7131656c4fe2d2a0a1188b0a07 *R/utils.R f854e6ecceade5a650a56a9a87a49ba2 *R/width.R ffafb8b4765a30cdec1e6bc1be67e782 *README.md d0a0726da9ccde178a89b7b47a7bda38 *inst/scripts/news.R 8d879471914c9df43ab46d42e5b81ec2 *inst/scripts/outdated.R 0ca0af9956bd9fc2b073053942e092be *inst/scripts/search.R f8e88581365bd3ac8cc9702c44cfd4b5 *inst/scripts/up.R ee02f4443aa4522aaa539e6e7a6a485f *man/builtin_theme.Rd 200a9d6d2f27d8a6bde72d3d972b7fdb *man/cli_alert.Rd 84f579174918a544813da3c55986f19a *man/cli_div.Rd ae396610eb934093458370c7b5b96ae4 *man/cli_dl.Rd c6127837fd5c80513c82b4073e689054 *man/cli_end.Rd e963177a506eac0c1a00c03014bcc2d5 *man/cli_h1.Rd a2d80e78fb97a72e707b47b6f1ce8b9b *man/cli_it.Rd 01050712ceba74aadbc31d31c17ea719 *man/cli_ol.Rd 79b5e0b999a5d4357276bd47801023e5 *man/cli_par.Rd 66278bd25d1974972c346343520d882b *man/cli_progress_bar.Rd 77e203ddd2cfd65b0b1886799d39d867 *man/cli_text.Rd 0a7218fd7febe0d3c5cb6b3d1b5a6e9e *man/cli_ul.Rd d025a5e0a425a2952a079b4052dc517b *man/cli_verbatim.Rd 114f48bdeb52e64e1d5dbc1cc4db9e8e *man/cliapp-package.Rd af2b8af7935f0dacce4f41f646885a85 *man/console_width.Rd ef46676fd502a3c043229a85623870b0 *man/containers.Rd 5c317cd7c601a237dd8d050dbdbeae7c *man/figures/alerts-1.png 123fa2791de861cc7b1aea42ceaccd6e *man/figures/alerts-2.png 5de86fc300ed9b898b93491d9ed205f1 *man/figures/alerts-3.png 546385190d3ca75fde1d7d4d27f60d90 *man/figures/alerts-4.png 6b36335c351e2714b886c6ff8da915c0 *man/figures/alerts-5.png bffb147000ac04f7ffdfa99e5e4793cf *man/figures/headings-1.png 46053838120342dfab960a74150588a0 *man/figures/headings-2.png d6f003cd105b7ebd5115fd19b05b6665 *man/figures/headings-3.png dfa924577d3ecbfad57d06ee1daadc21 *man/figures/inline-markup-1.png a7a54373cf52b94bc9dd9f4d970c4592 *man/figures/lists-1.png c9261a4aae51565a34c4f48806cdbfd3 *man/inline-markup.Rd 3da4392d0846f493e590efc8103a5a4f *man/simple_theme.Rd c1e22ffb1490aa41af7536233bf27d72 *man/start_app.Rd e8e5b33095742b0c12b83b0cf20cf0df *man/themes.Rd 7cc6a3e1700de623eea1f2835d5f4438 *tests/testthat.R 8f2e27f155bd4812490a2ad59af15281 *tests/testthat/helper.R 754ba49173a37dc068cc151e0798039d *tests/testthat/test-alerts.R 248a2e02daeffef74e7ffb29f829ef5f *tests/testthat/test-containers.R 5dc5cd95dcccf12605d07373678c42c7 *tests/testthat/test-custom-handler.R fc56969e627760ac89ea7ba6bc79893b *tests/testthat/test-headers.R 1f58b7718493e1ce169795ef8148db42 *tests/testthat/test-inline.R ab342b9ef5e8a6e33bec158aa5e9895f *tests/testthat/test-lists.R 6ec716a7608bd65a6c27dd7fbe73b6f0 *tests/testthat/test-progress-bars.R d233d66c7d64c75b861c99f83c820862 *tests/testthat/test-subprocess.R c971bd6c1ec5e354df75164b4586249d *tests/testthat/test-substitution.R bbb435c5bd6e5e70276ad3f4c48e7905 *tests/testthat/test-text.R 1fdee2be356fd981d96cd6bef6334840 *tests/testthat/test-themes.R cliapp/inst/0000755000176200001440000000000014521233614012500 5ustar liggesuserscliapp/inst/scripts/0000755000176200001440000000000014521233614014167 5ustar liggesuserscliapp/inst/scripts/search.R0000755000176200001440000000402414521233614015562 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_it() 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)) } cliapp/inst/scripts/news.R0000755000176200001440000000564514521233614015303 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(cliapp) 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_it() 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) } cliapp/inst/scripts/outdated.R0000755000176200001440000000400114521233614016121 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(cliapp) 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) } cliapp/inst/scripts/up.R0000755000176200001440000000277114521233614014750 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)) }