Skip to main content

ctl_term/
lib.rs

1//! Terminal output for CLIs. It decides whether to write colour and at what
2//! depth, how wide the output is, and where an escape sequence is written.
3//!
4//! The crate works on the plain lines [`ctl_text`] produces, which it
5//! re-exports. A [`Terminal`] states what is known about one output stream. An
6//! [`Encoder`] turns lines into bytes, adding the styling after layout. A
7//! [`Console`] puts the two together: it renders any
8//! [`Renderable`](ctl_text::Renderable) at the width of its stream and writes
9//! it.
10//!
11//! ## Quick start
12//!
13//! Decide once for a stream, then print. `from_probe` takes the stream's
14//! facts and an environment lookup, so this example needs no terminal; a
15//! program calls `Console::from_process` instead and passes
16//! [`ColorMode::Auto`].
17//!
18//! ```
19//! use std::collections::HashMap;
20//!
21//! use ctl_term::ctl_text::anstyle::AnsiColor;
22//! use ctl_term::ctl_text::{Style, Text};
23//! use ctl_term::{ColorMode, Console, DetectOptions, Probe, Stream, Terminal};
24//!
25//! let variables = HashMap::from([("FORCE_COLOR", "3")]);
26//! let env = |name: &str| variables.get(name).map(|value| (*value).to_owned());
27//! let terminal = Terminal::from_probe(
28//!     Probe::Redirected,
29//!     ColorMode::Auto,
30//!     DetectOptions::new(),
31//!     &env,
32//! );
33//! let console = Console::new(Stream::Stdout, terminal);
34//!
35//! let red = Style::new().fg_color(Some(AnsiColor::Red.into()));
36//! let text = Text::styled("failed", red);
37//! assert_eq!(console.render(&text), "\u{1b}[31mfailed\u{1b}[0m\n");
38//! ```
39//!
40//! ## Colour
41//!
42//! [`ColorMode::Always`] writes colour and [`ColorMode::Never`] writes none,
43//! whatever `NO_COLOR`, `CLICOLOR`, `CLICOLOR_FORCE`, `FORCE_COLOR`, and
44//! `TERM=dumb` hold. They still read the variables that set the depth and the
45//! width, `COLORTERM`, `TERM`, `FORCE_COLOR`, and `COLUMNS`. Under
46//! [`ColorMode::Auto`] these rules apply in order, and the first that matches
47//! wins:
48//!
49//! 1. `NO_COLOR` set and non-empty: no colour.
50//! 2. `CLICOLOR_FORCE` set, non-empty, and not `0`: colour.
51//! 3. `FORCE_COLOR` set, non-empty, and not `0` or `false`: colour.
52//! 4. `CLICOLOR=0`: no colour.
53//! 5. `TERM=dumb`: no colour.
54//! 6. Otherwise colour only when the stream is a terminal.
55//!
56//! `FORCE_COLOR=0` and `FORCE_COLOR=false` are neutral: they neither force
57//! colour nor turn it off, so rules 4 to 6 still apply. When the crate reads
58//! the process environment, a value that is not valid UTF-8 counts as set, so
59//! such a `NO_COLOR` still turns colour off.
60//!
61//! When colour is on, the depth is 24-bit when `COLORTERM` is `truecolor` or
62//! `24bit` or `FORCE_COLOR=3`. It is 256 colours when `TERM` contains
63//! `256color` or `FORCE_COLOR=2`, and 16 colours otherwise.
64//! [`Terminal::at_depth`] overrides it.
65//!
66//! The [`Encoder`] converts every colour to the nearest colour at that depth.
67//! A 24-bit colour on a 16-colour terminal prints as the nearest named
68//! colour, and an underline colour is dropped at 16 colours.
69//!
70//! ## Width
71//!
72//! Widths count terminal cells: a column is one terminal cell. A width takes
73//! the first of these that applies:
74//!
75//! 1. An explicit width, which is exact.
76//! 2. The window of the stream being written.
77//! 3. `COLUMNS`, when it holds a number from 1 to 65535.
78//! 4. The fallback of [`DEFAULT_FALLBACK_WIDTH`], 80.
79//!
80//! A detected width loses a column buffer of [`DEFAULT_COLUMN_BUFFER`], 2
81//! cells, and never drops below [`DEFAULT_MINIMUM_WIDTH`], 20, unless the
82//! window itself is narrower. A width is at least 1.
83//!
84//! The buffer reads no variable until [`DetectOptions::column_buffer_envs`]
85//! names some. [`DetectOptions`] overrides each of these and can turn each
86//! off.
87//!
88//! ## A closed pipe
89//!
90//! When the reader closes the stream, as `head` does, a write fails with
91//! [`Error::BrokenPipe`], which is a variant of its own. This crate reports
92//! it and leaves the exit code to the caller. A caller that returns
93//! [`std::io::Result`] converts [`Error`] with `From`, and a closed pipe stays
94//! [`std::io::ErrorKind::BrokenPipe`].
95//!
96//! ## Platforms
97//!
98//! The crate supports and tests Unix only. Windows is not supported: the crate
99//! does not turn on virtual-terminal mode. On other targets the crate builds
100//! but reports no window width.
101//!
102//! ## Cargo features
103//!
104//! `detect`, on by default, asks the operating system about a stream. It adds
105//! `Probe::of`, `Terminal::detect`, the `from_process` constructors, and
106//! `Console::print`, and brings in `terminal_size`. Without it the crate holds
107//! the [`Encoder`], the colour conversion, and [`Terminal::from_probe`], which
108//! need no terminal.
109#![cfg_attr(docsrs, feature(doc_cfg))]
110
111mod color;
112mod console;
113mod encode;
114mod palette;
115mod stream;
116mod terminal;
117
118pub use color::{ColorDepth, ColorMode};
119pub use console::{Console, Error};
120#[doc(no_inline)]
121pub use ctl_text;
122pub use encode::{Encoder, RESET};
123pub use palette::{ansi256_to_ansi16, rgb_to_ansi16, rgb_to_ansi256};
124pub use stream::{Probe, Stream};
125pub use terminal::{
126    DEFAULT_COLUMN_BUFFER, DEFAULT_FALLBACK_WIDTH, DEFAULT_MINIMUM_WIDTH, DetectOptions, Terminal,
127};
128
129/// The README's examples, compiled and run as doctests.
130#[doc = include_str!("../README.md")]
131#[cfg(doctest)]
132pub struct ReadmeDoctests;