Files
attune/crates/cli/src/output.rs
2026-02-04 17:46:30 -06:00

168 lines
4.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use anyhow::Result;
use clap::ValueEnum;
use colored::Colorize;
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Cell, Color, Table};
use serde::Serialize;
use std::fmt::Display;
/// Output format for CLI commands
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
pub enum OutputFormat {
/// Human-readable table format
Table,
/// JSON format for scripting
Json,
/// YAML format
Yaml,
}
impl Display for OutputFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputFormat::Table => write!(f, "table"),
OutputFormat::Json => write!(f, "json"),
OutputFormat::Yaml => write!(f, "yaml"),
}
}
}
/// Print output in the specified format
pub fn print_output<T: Serialize>(data: &T, format: OutputFormat) -> Result<()> {
match format {
OutputFormat::Json => {
let json = serde_json::to_string_pretty(data)?;
println!("{}", json);
}
OutputFormat::Yaml => {
let yaml = serde_yaml_ng::to_string(data)?;
println!("{}", yaml);
}
OutputFormat::Table => {
// For table format, the caller should use specific table functions
let json = serde_json::to_string_pretty(data)?;
println!("{}", json);
}
}
Ok(())
}
/// Print a success message
pub fn print_success(message: &str) {
println!("{} {}", "".green().bold(), message);
}
/// Print an info message
pub fn print_info(message: &str) {
println!("{} {}", "".blue().bold(), message);
}
/// Print a warning message
pub fn print_warning(message: &str) {
eprintln!("{} {}", "".yellow().bold(), message);
}
/// Print an error message
pub fn print_error(message: &str) {
eprintln!("{} {}", "".red().bold(), message);
}
/// Create a new table with default styling
pub fn create_table() -> Table {
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.apply_modifier(UTF8_ROUND_CORNERS);
table
}
/// Add a header row to a table with styling
pub fn add_header(table: &mut Table, headers: Vec<&str>) {
let cells: Vec<Cell> = headers
.into_iter()
.map(|h| Cell::new(h).fg(Color::Cyan))
.collect();
table.set_header(cells);
}
/// Print a table of key-value pairs
pub fn print_key_value_table(pairs: Vec<(&str, String)>) {
let mut table = create_table();
add_header(&mut table, vec!["Key", "Value"]);
for (key, value) in pairs {
table.add_row(vec![Cell::new(key).fg(Color::Yellow), Cell::new(value)]);
}
println!("{}", table);
}
/// Print a simple list
pub fn print_list(items: Vec<String>) {
for item in items {
println!("{}", item);
}
}
/// Print a titled section
pub fn print_section(title: &str) {
println!("\n{}", title.bold().underline());
}
/// Format a boolean as a colored checkmark or cross
pub fn format_bool(value: bool) -> String {
if value {
"".green().to_string()
} else {
"".red().to_string()
}
}
/// Format a status with color
pub fn format_status(status: &str) -> String {
match status.to_lowercase().as_str() {
"succeeded" | "success" | "enabled" | "active" | "running" => status.green().to_string(),
"failed" | "error" | "disabled" | "inactive" => status.red().to_string(),
"pending" | "scheduled" | "queued" => status.yellow().to_string(),
"canceled" | "cancelled" => status.bright_black().to_string(),
_ => status.to_string(),
}
}
/// Truncate a string to a maximum length with ellipsis
pub fn truncate(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len.saturating_sub(3)])
}
}
/// Format a timestamp in a human-readable way
pub fn format_timestamp(timestamp: &str) -> String {
// Try to parse and format nicely, otherwise return as-is
if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(timestamp) {
dt.format("%Y-%m-%d %H:%M:%S").to_string()
} else {
timestamp.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate() {
assert_eq!(truncate("short", 10), "short");
assert_eq!(truncate("this is a long string", 10), "this is...");
assert_eq!(truncate("exactly10!", 10), "exactly10!");
}
#[test]
fn test_output_format_display() {
assert_eq!(OutputFormat::Table.to_string(), "table");
assert_eq!(OutputFormat::Json.to_string(), "json");
assert_eq!(OutputFormat::Yaml.to_string(), "yaml");
}
}