From ddff53005ac00fc02422aa3987beb9276cc87333 Mon Sep 17 00:00:00 2001 From: augustuswm Date: Wed, 3 Jun 2026 16:51:54 -0500 Subject: [PATCH] Continuing work on generalized printer --- Cargo.lock | 30 +++--------- Cargo.toml | 2 +- v-cli-sdk/Cargo.toml | 2 +- v-cli-sdk/src/printer/mod.rs | 88 ++++++++++++++++++++---------------- 4 files changed, 57 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59d7b25..dd1f9a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -755,7 +755,7 @@ dependencies = [ [[package]] name = "dropshot-authorization-header" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "async-trait", "base64", @@ -3126,15 +3126,6 @@ dependencies = [ "libc", ] -[[package]] -name = "tabwriter" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432" -dependencies = [ - "unicode-width", -] - [[package]] name = "take_mut" version = "0.2.2" @@ -3525,12 +3516,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -3589,7 +3574,7 @@ dependencies = [ [[package]] name = "v-api" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "anyhow", "async-trait", @@ -3637,7 +3622,7 @@ dependencies = [ [[package]] name = "v-api-param" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "secrecy", "serde", @@ -3648,7 +3633,7 @@ dependencies = [ [[package]] name = "v-api-permission-derive" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "heck", "newtype-uuid", @@ -3665,7 +3650,7 @@ dependencies = [ [[package]] name = "v-cli-sdk" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "anyhow", "clap", @@ -3681,14 +3666,13 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tabwriter", "tokio", "uuid", ] [[package]] name = "v-model" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "async-bb8-diesel", "async-trait", @@ -4282,7 +4266,7 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xtask" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" dependencies = [ "clap", "regex", diff --git a/Cargo.toml b/Cargo.toml index db054b3..06e45da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ resolver = "2" [workspace.package] publish = true edition = "2024" -version = "0.4.0-alpha.3" +version = "0.4.0-alpha.4" [workspace.dependencies] anyhow = "1.0" diff --git a/v-cli-sdk/Cargo.toml b/v-cli-sdk/Cargo.toml index 4a929df..7d88279 100644 --- a/v-cli-sdk/Cargo.toml +++ b/v-cli-sdk/Cargo.toml @@ -19,6 +19,6 @@ schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_urlencoded = { workspace = true } -tabwriter = { workspace = true } + tokio = { workspace = true, features = ["macros", "rt", "net", "sync"] } uuid = { workspace = true } diff --git a/v-cli-sdk/src/printer/mod.rs b/v-cli-sdk/src/printer/mod.rs index 44e2c84..09bd6a2 100644 --- a/v-cli-sdk/src/printer/mod.rs +++ b/v-cli-sdk/src/printer/mod.rs @@ -4,8 +4,7 @@ use owo_colors::{OwoColorize, Style}; use serde::Serialize; -use std::io::Write; -use tabwriter::TabWriter; +use std::fmt::Write; #[derive(Debug, Clone)] pub enum Printer { @@ -23,8 +22,8 @@ impl Printer { /// Print any serializable response object in the configured format. /// /// - `Json` mode emits compact, single-line JSON. - /// - `Tab` mode serializes to a `serde_json::Value` and pretty-prints it - /// with tab-aligned key/value pairs. + /// - `Tab` mode pretty-prints with indentation and aligned key/value + /// pairs within each object. pub fn print_response(&self, value: &T) where T: Serialize, @@ -42,10 +41,8 @@ impl Printer { } Printer::Tab => { let styles = TabStyles::default(); - let mut tw = TabWriter::new(vec![]).ansi(true); - pretty_print_value(&mut tw, &json_value, 0, &styles); - tw.flush().unwrap(); - let output = String::from_utf8(tw.into_inner().unwrap()).unwrap(); + let mut output = String::new(); + pretty_print_value(&mut output, &json_value, 0, &styles); print!("{}", output); } } @@ -97,9 +94,11 @@ impl CliOutput for Printer { } // --------------------------------------------------------------------------- -// Tab-indented pretty-printer for serde_json::Value +// Indented pretty-printer for serde_json::Value // --------------------------------------------------------------------------- +const INDENT_STR: &str = " "; + #[derive(Debug, Clone)] struct TabStyles { label: Style, @@ -117,92 +116,102 @@ impl Default for TabStyles { } } -fn indent(tw: &mut TabWriter>, depth: usize) { +fn write_indent(out: &mut String, depth: usize) { for _ in 0..depth { - let _ = write!(tw, "\t"); + out.push_str(INDENT_STR); } } fn pretty_print_value( - tw: &mut TabWriter>, + out: &mut String, value: &serde_json::Value, depth: usize, styles: &TabStyles, ) { match value { serde_json::Value::Object(map) => { + let max_key_len = map.keys().map(|k| k.len()).max().unwrap_or(0); for (key, val) in map { - pretty_print_field(tw, key, val, depth, styles); + pretty_print_field(out, key, val, depth, max_key_len, styles); } } serde_json::Value::Array(arr) => { for (i, val) in arr.iter().enumerate() { - indent(tw, depth); - let _ = writeln!(tw, "{}", format!("[{}]", i).style(styles.label),); - pretty_print_value(tw, val, depth + 1, styles); + write_indent(out, depth); + let _ = writeln!(out, "{}", format!("[{}]", i).style(styles.label)); + pretty_print_value(out, val, depth + 1, styles); } } _ => { - indent(tw, depth); - let _ = writeln!(tw, "{}", format_scalar(value, styles)); + write_indent(out, depth); + let _ = writeln!(out, "{}", format_scalar(value, styles)); } } } fn pretty_print_field( - tw: &mut TabWriter>, + out: &mut String, key: &str, value: &serde_json::Value, depth: usize, + max_key_len: usize, styles: &TabStyles, ) { + // Number of spaces after the colon so all sibling values align. + let padding = max_key_len - key.len() + 1; + match value { serde_json::Value::Object(_) => { - indent(tw, depth); - let _ = writeln!(tw, "{}:", key.style(styles.label)); - pretty_print_value(tw, value, depth + 1, styles); + write_indent(out, depth); + let _ = writeln!(out, "{}:", key.style(styles.label)); + pretty_print_value(out, value, depth + 1, styles); } serde_json::Value::Array(arr) if arr.is_empty() => { - indent(tw, depth); + write_indent(out, depth); let _ = writeln!( - tw, - "{}:\t{}", + out, + "{}:{:padding$}{}", key.style(styles.label), + "", "[]".style(styles.null), ); } serde_json::Value::Array(arr) if arr.iter().all(is_scalar) => { - // Print simple arrays inline, one value per line with the key on - // the first line only (mimics the existing TabDisplay list style). + // Print simple arrays inline: key on the first line, continuation + // lines aligned under the first value. for (i, val) in arr.iter().enumerate() { - indent(tw, depth); + write_indent(out, depth); if i == 0 { let _ = writeln!( - tw, - "{}:\t{}", + out, + "{}:{:padding$}{}", key.style(styles.label), + "", format_scalar(val, styles), ); } else { - let _ = writeln!(tw, "\t{}", format_scalar(val, styles)); + // Align under the first value: key length + colon + padding. + let lead = max_key_len + 2; + let _ = writeln!(out, "{:lead$}{}", "", format_scalar(val, styles)); } } } serde_json::Value::Array(arr) => { - indent(tw, depth); - let _ = writeln!(tw, "{}:", key.style(styles.label)); + write_indent(out, depth); + let _ = writeln!(out, "{}:", key.style(styles.label)); for (i, val) in arr.iter().enumerate() { - indent(tw, depth + 1); - let _ = writeln!(tw, "{}", format!("[{}]", i).style(styles.label),); - pretty_print_value(tw, val, depth + 2, styles); + write_indent(out, depth + 1); + let _ = writeln!(out, "{}", format!("[{}]", i).style(styles.label)); + pretty_print_value(out, val, depth + 2, styles); } } _ => { - indent(tw, depth); + write_indent(out, depth); let _ = writeln!( - tw, - "{}:\t{}", + out, + "{}:{:padding$}{}", key.style(styles.label), + "", format_scalar(value, styles), ); } @@ -225,7 +234,6 @@ fn format_scalar(value: &serde_json::Value, styles: &TabStyles) -> String { serde_json::Value::Bool(b) => format!("{}", b.style(styles.value)), serde_json::Value::Number(n) => format!("{}", n.style(styles.value)), serde_json::Value::String(s) => format!("{}", s.style(styles.value)), - // Fallback for non-scalars that end up here other => format!("{}", other.style(styles.value)), } }