use std::io::{self, IsTerminal, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputMode {
Interactive,
CI,
Plain,
}
impl OutputMode {
pub fn detect() -> Self {
if is_ci::cached() {
return OutputMode::CI;
}
if io::stdout().is_terminal() {
OutputMode::Interactive
} else {
OutputMode::Plain
}
}
pub fn colors_enabled(&self) -> bool {
matches!(self, OutputMode::Interactive)
}
pub fn unicode_enabled(&self) -> bool {
matches!(self, OutputMode::Interactive)
}
pub fn progress_enabled(&self) -> bool {
matches!(self, OutputMode::Interactive)
}
}
impl Default for OutputMode {
fn default() -> Self {
Self::detect()
}
}
#[derive(Debug, Clone)]
pub struct Printer {
mode: OutputMode,
}
impl Default for Printer {
fn default() -> Self {
Self::new()
}
}
impl Printer {
pub fn new() -> Self {
Self {
mode: OutputMode::detect(),
}
}
pub fn with_mode(mode: OutputMode) -> Self {
Self { mode }
}
pub fn mode(&self) -> OutputMode {
self.mode
}
pub fn println(&self, message: &str) {
println!("{}", message);
}
#[allow(dead_code)]
pub fn print(&self, message: &str) {
print!("{}", message);
let _ = io::stdout().flush();
}
pub fn newline(&self) {
println!();
}
pub fn separator(&self) {
if self.mode.unicode_enabled() {
println!("{}", "━".repeat(60));
} else {
println!("{}", "-".repeat(60));
}
}
pub fn header(&self, text: &str) {
use colored::Colorize;
if self.mode.colors_enabled() {
println!("{}", text.cyan().bold());
} else {
println!("{}", text);
}
}
pub fn success(&self, message: &str) {
use colored::Colorize;
let symbol = if self.mode.unicode_enabled() {
"✓"
} else {
"[OK]"
};
if self.mode.colors_enabled() {
println!("{} {}", symbol.green(), message.green());
} else {
println!("{} {}", symbol, message);
}
}
pub fn error(&self, message: &str) {
use colored::Colorize;
let symbol = if self.mode.unicode_enabled() {
"✗"
} else {
"[ERROR]"
};
if self.mode.colors_enabled() {
eprintln!("{} {}", symbol.red(), message.red());
} else {
eprintln!("{} {}", symbol, message);
}
}
#[allow(dead_code)]
pub fn warning(&self, message: &str) {
use colored::Colorize;
let symbol = if self.mode.unicode_enabled() {
"⚠"
} else {
"[WARN]"
};
if self.mode.colors_enabled() {
println!("{} {}", symbol.yellow(), message.yellow());
} else {
println!("{} {}", symbol, message);
}
}
#[allow(dead_code)]
pub fn info(&self, message: &str) {
use colored::Colorize;
let symbol = if self.mode.unicode_enabled() {
"ℹ"
} else {
"[INFO]"
};
if self.mode.colors_enabled() {
println!("{} {}", symbol.cyan(), message);
} else {
println!("{} {}", symbol, message);
}
}
#[allow(dead_code)]
pub fn bullet(&self, message: &str) {
let symbol = if self.mode.unicode_enabled() {
"•"
} else {
"-"
};
println!(" {} {}", symbol, message);
}
pub fn kv(&self, key: &str, value: &str) {
use colored::Colorize;
if self.mode.colors_enabled() {
println!(" {}: {}", key.cyan(), value);
} else {
println!(" {}: {}", key, value);
}
}
#[allow(dead_code)]
pub fn section(&self, label: &str, value: &str) {
use colored::Colorize;
if self.mode.colors_enabled() {
println!("{} {}", label.cyan(), value.yellow().bold());
} else {
println!("{} {}", label, value);
}
}
#[allow(dead_code)]
pub fn dimmed(&self, message: &str) {
use colored::Colorize;
if self.mode.colors_enabled() {
println!("{}", message.dimmed());
} else {
println!("{}", message);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn output_mode_colors() {
assert!(OutputMode::Interactive.colors_enabled());
assert!(!OutputMode::CI.colors_enabled());
assert!(!OutputMode::Plain.colors_enabled());
}
#[test]
fn output_mode_unicode() {
assert!(OutputMode::Interactive.unicode_enabled());
assert!(!OutputMode::CI.unicode_enabled());
assert!(!OutputMode::Plain.unicode_enabled());
}
#[test]
fn output_mode_progress() {
assert!(OutputMode::Interactive.progress_enabled());
assert!(!OutputMode::CI.progress_enabled());
assert!(!OutputMode::Plain.progress_enabled());
}
#[test]
fn printer_with_mode() {
let printer = Printer::with_mode(OutputMode::CI);
assert_eq!(printer.mode(), OutputMode::CI);
}
#[test]
fn printer_default() {
let printer = Printer::default();
let _ = printer.mode();
}
#[test]
fn output_mode_detect() {
let _mode = OutputMode::detect();
}
#[test]
fn output_mode_default() {
let mode = OutputMode::default();
assert!(matches!(
mode,
OutputMode::Interactive | OutputMode::CI | OutputMode::Plain
));
}
#[test]
fn output_mode_equality() {
assert_eq!(OutputMode::Interactive, OutputMode::Interactive);
assert_eq!(OutputMode::CI, OutputMode::CI);
assert_eq!(OutputMode::Plain, OutputMode::Plain);
assert_ne!(OutputMode::Interactive, OutputMode::CI);
assert_ne!(OutputMode::CI, OutputMode::Plain);
}
#[test]
fn output_mode_debug() {
let mode = OutputMode::Interactive;
let debug_str = format!("{:?}", mode);
assert!(debug_str.contains("Interactive"));
}
#[test]
fn output_mode_clone_copy() {
let mode1 = OutputMode::Interactive;
let mode2 = mode1;
assert_eq!(mode1, mode2);
}
#[test]
fn printer_println() {
let printer = Printer::with_mode(OutputMode::CI);
printer.println("Test message");
}
#[test]
fn printer_print() {
let printer = Printer::with_mode(OutputMode::CI);
printer.print("Test message");
}
#[test]
fn printer_newline() {
let printer = Printer::with_mode(OutputMode::CI);
printer.newline();
}
#[test]
fn printer_separator_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.separator();
}
#[test]
fn printer_separator_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.separator();
}
#[test]
fn printer_header_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.header("Test Header");
}
#[test]
fn printer_header_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.header("Test Header");
}
#[test]
fn printer_success_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.success("Operation successful");
}
#[test]
fn printer_success_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.success("Operation successful");
}
#[test]
fn printer_error_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.error("Error occurred");
}
#[test]
fn printer_error_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.error("Error occurred");
}
#[test]
fn printer_warning_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.warning("Warning message");
}
#[test]
fn printer_warning_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.warning("Warning message");
}
#[test]
fn printer_info_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.info("Info message");
}
#[test]
fn printer_info_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.info("Info message");
}
#[test]
fn printer_bullet_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.bullet("Bullet point");
}
#[test]
fn printer_bullet_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.bullet("Bullet point");
}
#[test]
fn printer_kv_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.kv("Key", "Value");
}
#[test]
fn printer_kv_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.kv("Key", "Value");
}
#[test]
fn printer_section_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.section("Section", "Content");
}
#[test]
fn printer_section_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.section("Section", "Content");
}
#[test]
fn printer_dimmed_ci() {
let printer = Printer::with_mode(OutputMode::CI);
printer.dimmed("Dimmed text");
}
#[test]
fn printer_dimmed_interactive() {
let printer = Printer::with_mode(OutputMode::Interactive);
printer.dimmed("Dimmed text");
}
#[test]
fn printer_clone() {
let printer1 = Printer::with_mode(OutputMode::CI);
let printer2 = printer1.clone();
assert_eq!(printer1.mode(), printer2.mode());
}
#[test]
fn printer_debug() {
let printer = Printer::with_mode(OutputMode::Interactive);
let debug_str = format!("{:?}", printer);
assert!(debug_str.contains("Printer"));
}
#[test]
fn printer_new() {
let printer = Printer::new();
let mode = printer.mode();
assert!(matches!(
mode,
OutputMode::Interactive | OutputMode::CI | OutputMode::Plain
));
}
#[test]
fn printer_all_modes_complete_workflow() {
for mode in [OutputMode::Interactive, OutputMode::CI, OutputMode::Plain] {
let printer = Printer::with_mode(mode);
printer.header("Header");
printer.separator();
printer.success("Success");
printer.error("Error");
printer.warning("Warning");
printer.info("Info");
printer.bullet("Bullet");
printer.kv("Key", "Value");
printer.section("Section", "Content");
printer.dimmed("Dimmed");
printer.newline();
}
}
#[test]
fn output_mode_plain_flags() {
let mode = OutputMode::Plain;
assert!(!mode.colors_enabled());
assert!(!mode.unicode_enabled());
assert!(!mode.progress_enabled());
}
}