use crate::error::{Result, SunbeamError}; use serde::Serialize; // --------------------------------------------------------------------------- // OutputFormat // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, Default)] #[cfg_attr(feature = "cli", derive(clap::ValueEnum))] pub enum OutputFormat { #[default] Table, Json, Yaml, } /// Render a single serialisable value in the requested format. pub fn render(val: &T, format: OutputFormat) -> Result<()> { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(val)?); } OutputFormat::Yaml => { print!("{}", serde_yaml::to_string(val)?); } OutputFormat::Table => { // Fallback: pretty JSON when no table renderer is provided println!("{}", serde_json::to_string_pretty(val)?); } } Ok(()) } /// Render a list of items as table / json / yaml. /// /// `to_row` converts each item into a `Vec` of column values matching /// the order of `headers`. pub fn render_list( rows: &[T], headers: &[&str], to_row: fn(&T) -> Vec, format: OutputFormat, ) -> Result<()> { match format { OutputFormat::Json => { println!("{}", serde_json::to_string_pretty(rows)?); } OutputFormat::Yaml => { print!("{}", serde_yaml::to_string(rows)?); } OutputFormat::Table => { let table_rows: Vec> = rows.iter().map(to_row).collect(); println!("{}", table(table_rows.as_slice(), headers)); } } Ok(()) } /// Read JSON input from a `--data` flag value or stdin when the value is `"-"`. pub fn read_json_input(flag: Option<&str>) -> Result { let raw = match flag { Some("-") | None => { let mut buf = String::new(); std::io::Read::read_to_string(&mut std::io::stdin(), &mut buf)?; buf } Some(v) => v.to_string(), }; serde_json::from_str(&raw) .map_err(|e| SunbeamError::Other(format!("invalid JSON input: {e}"))) } // --------------------------------------------------------------------------- // Existing helpers // --------------------------------------------------------------------------- /// Print a step header. pub fn step(msg: &str) { println!("\n==> {msg}"); } /// Print a success/info line. pub fn ok(msg: &str) { println!(" {msg}"); } /// Print a warning to stderr. pub fn warn(msg: &str) { eprintln!(" WARN: {msg}"); } /// Return an aligned text table. Columns padded to max width. pub fn table(rows: &[Vec], headers: &[&str]) -> String { if headers.is_empty() { return String::new(); } let mut col_widths: Vec = headers.iter().map(|h| h.len()).collect(); for row in rows { for (i, cell) in row.iter().enumerate() { if i < col_widths.len() { col_widths[i] = col_widths[i].max(cell.len()); } } } let header_line: String = headers .iter() .enumerate() .map(|(i, h)| format!("{:>() .join(" "); let separator: String = col_widths .iter() .map(|&w| "-".repeat(w)) .collect::>() .join(" "); let mut lines = vec![header_line, separator]; for row in rows { let cells: Vec = (0..headers.len()) .map(|i| { let val = row.get(i).map(|s| s.as_str()).unwrap_or(""); format!("{: