//! Output formatting. use console::style; use serde::Serialize; use tabled::{builder::Builder, settings::Style as TableStyle}; /// Output format. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OutputFormat { Text, Json, } impl OutputFormat { pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "json" => OutputFormat::Json, _ => OutputFormat::Text, } } } /// Prints a value in the specified format. pub fn print_value(value: &T, format: OutputFormat) { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(value).unwrap()); } OutputFormat::Text => { println!("{:#?}", value); } } } /// Prints a success message. pub fn print_success(message: &str) { println!("{} {}", style("✓").green().bold(), message); } /// Prints an error message. pub fn print_error(message: &str) { eprintln!("{} {}", style("✗").red().bold(), message); } /// Prints a warning message. pub fn print_warning(message: &str) { println!("{} {}", style("⚠").yellow().bold(), message); } /// Prints info message. pub fn print_info(message: &str) { println!("{} {}", style("ℹ").blue().bold(), message); } /// Prints a key-value pair. pub fn print_kv(key: &str, value: &str) { println!("{}: {}", style(key).bold(), value); } /// Prints a header. pub fn print_header(title: &str) { println!(); println!("{}", style(title).bold().underlined()); println!(); } /// Prints a table. pub fn print_table(headers: Vec<&str>, rows: Vec>) { let mut builder = Builder::default(); builder.push_record(headers); for row in rows { builder.push_record(row); } let mut table = builder.build(); let styled = table.with(TableStyle::rounded()); println!("{}", styled); } /// Formats SYNOR amount. pub fn format_synor(sompi: u64) -> String { let synor = sompi as f64 / 100_000_000.0; format!("{:.8} SYNOR", synor) } /// Formats a hash for display. pub fn format_hash(hash: &str) -> String { if hash.len() > 16 { format!("{}...{}", &hash[..8], &hash[hash.len() - 8..]) } else { hash.to_string() } } /// Formats a timestamp. pub fn format_timestamp(ts: u64) -> String { use chrono::{DateTime, Utc}; let dt = DateTime::::from_timestamp_millis(ts as i64) .unwrap_or_else(|| DateTime::::from_timestamp(0, 0).unwrap()); dt.format("%Y-%m-%d %H:%M:%S UTC").to_string() } /// Formats duration. pub fn format_duration(seconds: u64) -> String { if seconds < 60 { format!("{}s", seconds) } else if seconds < 3600 { format!("{}m {}s", seconds / 60, seconds % 60) } else if seconds < 86400 { format!("{}h {}m", seconds / 3600, (seconds % 3600) / 60) } else { format!("{}d {}h", seconds / 86400, (seconds % 86400) / 3600) } } /// Formats hashrate. pub fn format_hashrate(hps: f64) -> String { const K: f64 = 1000.0; const M: f64 = K * 1000.0; const G: f64 = M * 1000.0; const T: f64 = G * 1000.0; if hps >= T { format!("{:.2} TH/s", hps / T) } else if hps >= G { format!("{:.2} GH/s", hps / G) } else if hps >= M { format!("{:.2} MH/s", hps / M) } else if hps >= K { format!("{:.2} KH/s", hps / K) } else { format!("{:.2} H/s", hps) } } /// Formats bytes size. pub fn format_size(bytes: u64) -> String { const KB: u64 = 1024; const MB: u64 = KB * 1024; const GB: u64 = MB * 1024; if bytes >= GB { format!("{:.2} GB", bytes as f64 / GB as f64) } else if bytes >= MB { format!("{:.2} MB", bytes as f64 / MB as f64) } else if bytes >= KB { format!("{:.2} KB", bytes as f64 / KB as f64) } else { format!("{} B", bytes) } } /// Progress bar for long operations. pub fn create_progress_bar(len: u64, message: &str) -> indicatif::ProgressBar { let pb = indicatif::ProgressBar::new(len); pb.set_style( indicatif::ProgressStyle::default_bar() .template("{msg} [{bar:40.cyan/blue}] {pos}/{len} ({eta})") .unwrap() .progress_chars("=>-"), ); pb.set_message(message.to_string()); pb } /// Spinner for indeterminate operations. pub fn create_spinner(message: &str) -> indicatif::ProgressBar { let sp = indicatif::ProgressBar::new_spinner(); sp.set_style( indicatif::ProgressStyle::default_spinner() .template("{spinner:.green} {msg}") .unwrap(), ); sp.set_message(message.to_string()); sp }