//! Shared presentation for the CLI. //! //! Styling is `anstyle`, the vocabulary `clap` itself is built on, printed //! through `anstream`, which strips the sequences when the output is not a //! terminal or `NO_COLOR` is set. Commands go through the wrappers below //! rather than styling by hand, so the palette stays in one place. //! //! Machine names and clipboard previews are cleaned up at the daemon boundary //! and print as they are; anything else coming from a peer or the daemon //! has to pass through [`crate::config::sanitize`] first. use std::fmt::{self, Display}; use anstyle::{AnsiColor, Style}; /// A section heading. pub fn heading(text: D) -> Painted { paint(Style::new().bold(), text) } /// Healthy, or done. pub fn good(text: D) -> Painted { paint(AnsiColor::Green.on_default(), text) } /// Worth a look, without being broken. pub fn warn(text: D) -> Painted { paint(AnsiColor::Yellow.on_default(), text) } /// Broken. pub fn bad(text: D) -> Painted { paint(AnsiColor::Red.on_default(), text) } /// Detail, secondary to what it sits beside. pub fn dim(text: D) -> Painted { paint(Style::new().dimmed(), text) } /// Something to print in a style. /// /// A wrapper rather than a pair of escape sequences around a `format!`, so /// a caller cannot forget the reset and colour the rest of the line. #[derive(Debug)] pub struct Painted { style: Style, value: D, } fn paint(style: Style, value: D) -> Painted { Painted { style, value } } impl Display for Painted { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // `{:#}` is how anstyle renders the reset for a style. write!(f, "{}{}{:#}", self.style, self.value, self.style) } } /// A duration in seconds, short enough for a column: `43s`, `12m`, `2h /// 4m`, `3d`. pub fn duration(secs: u64) -> String { match secs { s if s < 60 => format!("{s}s"), s if s < 3600 => format!("{}m", s / 60), s if s < 86400 => format!("{}h {}m", s / 3600, (s % 3600) / 60), s => format!("{}d", s / 86400), } } /// The width of the longest name, for lining columns up. Counts /// characters, to match what `{:width$}` pads. pub fn width<'a>(names: impl Iterator) -> usize { names.map(|name| name.chars().count()).max().unwrap_or(0) }