#[cfg(any(test, feature = "test-utils"))]
pub mod output;
#[cfg(not(any(test, feature = "test-utils")))]
mod output;
mod ansi;
mod ansi_stripper;
mod file_writer;
mod io;
mod logger_wrapper;
mod progress;
mod runtime;
mod stdout_writer;
pub use ansi::strip_ansi_codes;
pub use output::{argv_requests_json, format_generic_json_for_display, Loggable, Logger};
pub use progress::print_progress;
pub use crate::logger::file_writer::append_to_file;
pub use crate::logger::logger_wrapper::LoggerIoWrapper;
pub trait ColorEnvironment {
fn get_var(&self, name: &str) -> Option<String>;
fn is_terminal(&self) -> bool;
}
pub struct RealColorEnvironment;
impl ColorEnvironment for RealColorEnvironment {
fn get_var(&self, name: &str) -> Option<String> {
runtime::get_color_env_var(name)
}
fn is_terminal(&self) -> bool {
runtime_color_env_is_terminal()
}
}
pub fn stdout_write(buf: &[u8]) -> std::io::Result<usize> {
runtime::stdout_write(buf)
}
pub fn stdout_write_line(s: &str) -> std::io::Result<()> {
runtime::stdout_write_line(s)
}
pub fn stderr_write_line(s: &str) -> std::io::Result<()> {
runtime::stderr_write_line(s)
}
pub fn stdout_flush() -> std::io::Result<()> {
runtime::stdout_flush()
}
#[must_use]
pub fn stdout_is_terminal() -> bool {
runtime::stdout_is_terminal()
}
fn runtime_color_env_is_terminal() -> bool {
let env = runtime::RealColorEnvironment;
let _ = runtime::ColorEnvironment::get_var(&env, "TERM");
runtime::ColorEnvironment::is_terminal(&env)
}
pub fn colors_enabled_with_env(env: &dyn ColorEnvironment) -> bool {
if env.get_var("NO_COLOR").is_some() {
return false;
}
if let Some(val) = env.get_var("CLICOLOR_FORCE") {
if !val.is_empty() && val != "0" {
return true;
}
}
if let Some(val) = env.get_var("CLICOLOR") {
if val == "0" {
return false;
}
}
if let Some(term) = env.get_var("TERM") {
if term.to_lowercase() == "dumb" {
return false;
}
}
env.is_terminal()
}
#[must_use]
pub fn colors_enabled() -> bool {
colors_enabled_with_env(&RealColorEnvironment)
}
#[derive(Clone, Copy)]
pub struct Colors {
pub(crate) enabled: bool,
}
impl Colors {
#[must_use]
pub fn new() -> Self {
Self {
enabled: colors_enabled(),
}
}
#[cfg(any(test, feature = "test-utils"))]
#[must_use]
pub const fn with_enabled(enabled: bool) -> Self {
Self { enabled }
}
#[must_use]
pub const fn bold(self) -> &'static str {
if self.enabled {
"\x1b[1m"
} else {
""
}
}
#[must_use]
pub const fn dim(self) -> &'static str {
if self.enabled {
"\x1b[2m"
} else {
""
}
}
#[must_use]
pub const fn reset(self) -> &'static str {
if self.enabled {
"\x1b[0m"
} else {
""
}
}
#[must_use]
pub const fn red(self) -> &'static str {
if self.enabled {
"\x1b[31m"
} else {
""
}
}
#[must_use]
pub const fn green(self) -> &'static str {
if self.enabled {
"\x1b[32m"
} else {
""
}
}
#[must_use]
pub const fn yellow(self) -> &'static str {
if self.enabled {
"\x1b[33m"
} else {
""
}
}
#[must_use]
pub const fn blue(self) -> &'static str {
if self.enabled {
"\x1b[34m"
} else {
""
}
}
#[must_use]
pub const fn magenta(self) -> &'static str {
if self.enabled {
"\x1b[35m"
} else {
""
}
}
#[must_use]
pub const fn cyan(self) -> &'static str {
if self.enabled {
"\x1b[36m"
} else {
""
}
}
#[must_use]
pub const fn white(self) -> &'static str {
if self.enabled {
"\x1b[37m"
} else {
""
}
}
}
impl Default for Colors {
fn default() -> Self {
Self::new()
}
}
pub const BOX_TL: char = '╭';
pub const BOX_TR: char = '╮';
pub const BOX_BL: char = '╰';
pub const BOX_BR: char = '╯';
pub const BOX_H: char = '─';
pub const BOX_V: char = '│';
pub const ARROW: char = '→';
pub const CHECK: char = '✓';
pub const CROSS: char = '✗';
pub const WARN: char = '⚠';
pub const INFO: char = 'ℹ';
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct MockColorEnvironment {
vars: HashMap<String, String>,
is_tty: bool,
}
impl MockColorEnvironment {
fn new() -> Self {
Self {
vars: HashMap::new(),
is_tty: true,
}
}
fn with_var(mut self, name: &str, value: &str) -> Self {
self.vars.insert(name.to_string(), value.to_string());
self
}
fn not_tty(mut self) -> Self {
self.is_tty = false;
self
}
}
impl ColorEnvironment for MockColorEnvironment {
fn get_var(&self, name: &str) -> Option<String> {
self.vars.get(name).cloned()
}
fn is_terminal(&self) -> bool {
self.is_tty
}
}
#[test]
fn test_colors_disabled_struct() {
let c = Colors { enabled: false };
assert_eq!(c.bold(), "");
assert_eq!(c.red(), "");
assert_eq!(c.reset(), "");
}
#[test]
fn test_colors_enabled_struct() {
let c = Colors { enabled: true };
assert_eq!(c.bold(), "\x1b[1m");
assert_eq!(c.red(), "\x1b[31m");
assert_eq!(c.reset(), "\x1b[0m");
}
#[test]
fn test_box_chars() {
assert_eq!(BOX_TL, '╭');
assert_eq!(BOX_TR, '╮');
assert_eq!(BOX_H, '─');
}
#[test]
fn test_colors_enabled_respects_no_color() {
let env = MockColorEnvironment::new().with_var("NO_COLOR", "1");
assert!(!colors_enabled_with_env(&env));
}
#[test]
fn test_colors_enabled_respects_clicolor_force() {
let env = MockColorEnvironment::new()
.with_var("CLICOLOR_FORCE", "1")
.not_tty();
assert!(colors_enabled_with_env(&env));
}
#[test]
fn test_colors_enabled_respects_clicolor_zero() {
let env = MockColorEnvironment::new().with_var("CLICOLOR", "0");
assert!(!colors_enabled_with_env(&env));
}
#[test]
fn test_colors_enabled_respects_term_dumb() {
let env = MockColorEnvironment::new().with_var("TERM", "dumb");
assert!(!colors_enabled_with_env(&env));
}
#[test]
fn test_colors_enabled_no_color_takes_precedence() {
let env = MockColorEnvironment::new()
.with_var("NO_COLOR", "1")
.with_var("CLICOLOR_FORCE", "1");
assert!(!colors_enabled_with_env(&env));
}
#[test]
fn test_colors_enabled_term_dumb_case_insensitive() {
assert!(["dumb", "DUMB", "Dumb", "DuMb"].iter().all(|&term| {
let env = MockColorEnvironment::new().with_var("TERM", term);
!colors_enabled_with_env(&env)
}));
}
#[test]
fn test_colors_enabled_default_tty() {
let env = MockColorEnvironment::new();
assert!(colors_enabled_with_env(&env));
}
#[test]
fn test_colors_enabled_default_not_tty() {
let env = MockColorEnvironment::new().not_tty();
assert!(!colors_enabled_with_env(&env));
}
}