use std::io::{self, Write};
use crate::cli::command::CliOutput;
pub trait CliRenderer: Send + Sync {
fn render(&self, out: &CliOutput) -> io::Result<()>;
}
pub struct HumanRenderer;
impl CliRenderer for HumanRenderer {
fn render(&self, out: &CliOutput) -> io::Result<()> {
let stdout = io::stdout();
let mut w = stdout.lock();
match out {
CliOutput::Silent => Ok(()),
CliOutput::KeyValue(pairs) => {
let pad = pairs.iter().map(|(k, _)| k.len()).max().unwrap_or(0);
for (k, v) in pairs {
writeln!(w, "{k:<width$} {v}", width = pad)?;
}
Ok(())
}
CliOutput::Table { headers, rows } => {
let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
for row in rows {
for (i, cell) in row.iter().enumerate() {
if i >= widths.len() {
widths.push(cell.len());
} else if cell.len() > widths[i] {
widths[i] = cell.len();
}
}
}
let render_row = |row: &[String], w: &mut dyn Write| -> io::Result<()> {
for (i, cell) in row.iter().enumerate() {
let width = widths.get(i).copied().unwrap_or(cell.len());
if i + 1 == row.len() {
write!(w, "{cell}")?;
} else {
write!(w, "{cell:<width$} ")?;
}
}
writeln!(w)
};
render_row(headers, &mut w as &mut dyn Write)?;
let total: usize =
widths.iter().sum::<usize>() + widths.len().saturating_sub(1) * 2;
writeln!(w, "{}", "─".repeat(total.max(1)))?;
for row in rows {
render_row(row, &mut w as &mut dyn Write)?;
}
Ok(())
}
CliOutput::Json(v) => {
writeln!(w, "{}", serde_json::to_string_pretty(v).unwrap_or_default())
}
CliOutput::Lines(lines) => {
for line in lines {
writeln!(w, "{line}")?;
}
Ok(())
}
}
}
}
pub struct JsonRenderer;
impl CliRenderer for JsonRenderer {
fn render(&self, out: &CliOutput) -> io::Result<()> {
let stdout = io::stdout();
let mut w = stdout.lock();
let payload = match out {
CliOutput::Silent => serde_json::json!({"kind": "silent"}),
CliOutput::KeyValue(pairs) => {
let map: serde_json::Map<String, serde_json::Value> = pairs
.iter()
.map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
.collect();
serde_json::json!({"kind": "key_value", "data": map})
}
CliOutput::Table { headers, rows } => {
serde_json::json!({"kind": "table", "headers": headers, "rows": rows})
}
CliOutput::Json(v) => v.clone(),
CliOutput::Lines(lines) => {
serde_json::json!({"kind": "lines", "data": lines})
}
};
writeln!(w, "{}", serde_json::to_string(&payload).unwrap_or_default())
}
}