use std::collections::{BTreeMap, HashMap};
use std::io::{Write, self};
use std::time::Duration;
pub mod colors {
pub const RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const UNDERLINE: &str = "\x1b[4m";
pub const RED: &str = "\x1b[31m";
pub const GREEN: &str = "\x1b[32m";
pub const YELLOW: &str = "\x1b[33m";
pub const BLUE: &str = "\x1b[34m";
pub const MAGENTA: &str = "\x1b[35m";
pub const CYAN: &str = "\x1b[36m";
pub const WHITE: &str = "\x1b[37m";
pub const BRIGHT_RED: &str = "\x1b[91m";
pub const BRIGHT_GREEN: &str = "\x1b[92m";
pub const BRIGHT_YELLOW: &str = "\x1b[93m";
pub const BRIGHT_BLUE: &str = "\x1b[94m";
pub const BRIGHT_CYAN: &str = "\x1b[96m";
}
pub struct OutputFormatter {
pub color_enabled: bool,
pub verbose: bool,
}
impl Default for OutputFormatter {
fn default() -> Self {
Self {
color_enabled: Self::detect_color_support(),
verbose: false,
}
}
}
impl OutputFormatter {
pub fn new() -> Self {
Self::default()
}
pub fn with_verbose(verbose: bool) -> Self {
Self {
color_enabled: Self::detect_color_support(),
verbose,
}
}
fn detect_color_support() -> bool {
if std::env::var("NO_COLOR").is_ok() {
return false;
}
if let Ok(term) = std::env::var("TERM") {
if term == "dumb" {
return false;
}
}
atty::is(atty::Stream::Stdout)
}
fn colorize(&self, text: &str, color: &str) -> String {
if self.color_enabled {
format!("{}{}{}", color, text, colors::RESET)
} else {
text.to_string()
}
}
pub fn success(&self, message: &str) {
println!(
"{} {}",
self.colorize("✓", colors::GREEN),
self.colorize(message, colors::GREEN)
);
}
pub fn error(&self, message: &str) {
eprintln!(
"{} {}",
self.colorize("✗", colors::RED),
self.colorize(message, colors::RED)
);
}
pub fn warning(&self, message: &str) {
println!(
"{} {}",
self.colorize("⚠", colors::YELLOW),
self.colorize(message, colors::YELLOW)
);
}
pub fn info(&self, message: &str) {
println!("{} {}", self.colorize("ℹ", colors::BLUE), message);
}
pub fn debug(&self, message: &str) {
if self.verbose {
println!(
"{} {}",
self.colorize("🔍", colors::DIM),
self.colorize(message, colors::DIM)
);
}
}
pub fn title(&self, title: &str) {
println!();
println!(
"{}",
self.colorize(
&format!(" {} ", title),
&format!("{}{}", colors::BOLD, colors::BRIGHT_CYAN)
)
);
println!(
"{}",
self.colorize(&"─".repeat(title.len() + 4), colors::DIM)
);
}
pub fn subtitle(&self, subtitle: &str) {
println!();
println!(" {}", self.colorize(subtitle, colors::BOLD));
}
pub fn key_value(&self, key: &str, value: &str) {
let formatted_key: _ = self.colorize(key, colors::CYAN);
println!(" {}: {}", formatted_key, value);
}
pub fn key_value_with_icon(&self, icon: &str, key: &str, value: &str) {
let formatted_key: _ = self.colorize(key, colors::CYAN);
println!(" {} {}: {}", icon, formatted_key, value);
}
pub fn list_item(&self, item: &str) {
println!(" {} {}", self.colorize("•", colors::DIM), item);
}
pub fn numbered_item(&self, num: usize, item: &str) {
println!(
" {}. {}",
self.colorize(&num.to_string(), colors::YELLOW),
item
);
}
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)
}
}
pub fn format_duration(duration: Duration) -> String {
let secs: _ = duration.as_secs();
let millis: _ = duration.subsec_millis();
if secs >= 60 {
let mins: _ = secs / 60;
let remaining_secs: _ = secs % 60;
format!("{}m {}s", mins, remaining_secs)
} else if secs > 0 {
format!("{}.{:03}s", secs, millis)
} else if millis > 0 {
format!("{}ms", millis)
} else {
format!("{}μs", duration.subsec_micros())
}
}
pub fn table(&self, headers: &[&str], rows: &[Vec<String>]) {
let mut col_widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
for row in rows {
for (i, cell) in row.iter().enumerate() {
if i < col_widths.len() && cell.len() > col_widths[i] {
col_widths[i] = cell.len();
}
}
}
let header_line: String = headers
.iter()
.zip(&col_widths)
.map(|(h, w)| format!("{:width$}", h, width = *w))
.collect::<Vec<_>>()
.join(" ");
println!(" {}", self.colorize(&header_line, colors::BOLD));
let separator: String = col_widths
.iter()
.map(|w| "─".repeat(*w))
.collect::<Vec<_>>()
.join("──");
println!(" {}", self.colorize(&separator, colors::DIM));
for row in rows {
let row_line: String = row
.iter()
.zip(&col_widths)
.map(|(cell, w)| format!("{:width$}", cell, width = *w))
.collect::<Vec<_>>()
.join(" ");
println!(" {}", row_line);
}
}
pub fn progress_start(&self, message: &str) {
print!("{} {} ", self.colorize("⏳", colors::YELLOW), message);
let _: _ = io::stdout().flush();
}
pub fn progress_done(&self) {
println!("{}", self.colorize("✓", colors::GREEN));
}
pub fn progress_fail(&self) {
println!("{}", self.colorize("✗", colors::RED));
}
const SPINNER_FRAMES: &'static [&'static str] =
&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub fn spinner_frame(index: usize) -> &'static str {
Self::SPINNER_FRAMES[index % Self::SPINNER_FRAMES.len()]
}
pub fn box_message(&self, title: &str, content: &[&str]) {
let max_len: _ = content
.iter()
.map(|s| s.len())
.max()
.unwrap_or(0)
.max(title.len());
let border_width: _ = max_len + 4;
println!(" ╭{}╮", "─".repeat(border_width));
println!(
" │ {}{}{} │",
self.colorize(title, colors::BOLD),
" ".repeat(max_len - title.len()),
" ".repeat(2)
);
println!(" ├{}┤", "─".repeat(border_width));
for line in content {
println!(" │ {}{} │", line, " ".repeat(max_len - line.len() + 1));
}
println!(" ╰{}╯", "─".repeat(border_width));
}
pub fn print_banner(&self) {
let banner: _ = r#"
____ _
| _ \ (_)
| |_) | ___ ___ _ ___
| _ < / _ \/ _ \ / __|
| |_) | __/ __/ \__ \
|____/ \___|\___| |___/
_/ |
|__/
"#;
println!("{}", self.colorize(banner, colors::BRIGHT_CYAN));
println!(
" {} {} - {}",
self.colorize("Amber", colors::BOLD),
self.colorize("v0.1.0", colors::BRIGHT_GREEN),
self.colorize(
"High-performance JavaScript/TypeScript Runtime",
colors::DIM
)
);
println!();
}
}
mod atty {
pub enum Stream {
Stdout,
}
pub fn is(_stream: Stream) -> bool {
#[cfg(unix)]
{
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
}
#[cfg(windows)]
{
true
}
#[cfg(not(any(unix, windows)))]
{
true
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_format_size() {
assert_eq!(OutputFormatter::format_size(512), "512 B");
assert_eq!(OutputFormatter::format_size(1024), "1.00 KB");
assert_eq!(OutputFormatter::format_size(1536), "1.50 KB");
assert_eq!(OutputFormatter::format_size(1048576), "1.00 MB");
assert_eq!(OutputFormatter::format_size(1073741824), "1.00 GB");
}
#[test]
fn test_format_duration() {
assert_eq!(
OutputFormatter::format_duration(Duration::from_micros(500)),
"500μs"
);
assert_eq!(
OutputFormatter::format_duration(Duration::from_millis(100)),
"100ms"
);
assert_eq!(
OutputFormatter::format_duration(Duration::from_secs(5)),
"5.000s"
);
assert_eq!(
OutputFormatter::format_duration(Duration::from_secs(65)),
"1m 5s"
);
}
#[test]
fn test_output_formatter_new() {
let formatter: _ = OutputFormatter::new();
assert!(!formatter.verbose);
}
#[test]
fn test_output_formatter_with_verbose() {
let formatter: _ = OutputFormatter::with_verbose(true);
assert!(formatter.verbose);
}
#[test]
fn test_spinner_frames() {
assert_eq!(OutputFormatter::spinner_frame(0), "⠋");
assert_eq!(OutputFormatter::spinner_frame(10), "⠋"); }
}