From a68fff8ea021c3867139d82e858be1aa6343f3a3 Mon Sep 17 00:00:00 2001 From: Igor Pashev Date: Fri, 21 Jul 2023 10:37:36 +0200 Subject: Initial commit --- src/main.rs | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 src/main.rs (limited to 'src') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..68894bf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,250 @@ +use clap::Parser; +use comfy_table::{ContentArrangement, Row, Table}; +use std::fmt; + +trait DisplayType { + fn next_fmt(&self, x: &X) -> Option; + fn fmt(&self, x: &X) -> String; + fn display(&self, x: &X) -> String { + self.next_fmt(x).unwrap_or_else(|| self.fmt(x)) + } +} + +trait ToTable { + fn to_table(&self) -> Table; +} + +#[derive(Parser)] +struct NoDisplay {} + +impl DisplayType for NoDisplay { + fn next_fmt(&self, _: &X) -> Option { + None + } + fn fmt(&self, _: &X) -> String { + String::new() + } +} + +#[derive(Parser)] +struct TextDisplay { + /// Display as text + #[clap(long, group = "fmt_type")] + text: bool, + #[clap(flatten)] + next: T, +} + +impl DisplayType for TextDisplay +where + X: fmt::Display, + T: DisplayType + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option { + self.text + .then(|| self.fmt(x)) + .or_else(|| self.next.next_fmt(x)) + } + fn fmt(&self, x: &X) -> String { + format!("{x}") + } +} + +#[derive(Parser)] +struct DebugDisplay { + /// Display as internal debug representation + #[clap(long, group = "fmt_type")] + debug: bool, + #[clap(flatten)] + next: T, +} + +impl DisplayType for DebugDisplay +where + X: fmt::Debug, + T: DisplayType + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option { + self.debug + .then(|| self.fmt(x)) + .or_else(|| self.next.next_fmt(x)) + } + fn fmt(&self, x: &X) -> String { + format!("{x:?}") + } +} + +#[derive(Parser)] +struct ApiDisplay { + /// Display as unformatted JSON + #[clap(long, group = "fmt_type")] + api: bool, + #[clap(flatten)] + next: T, +} + +impl DisplayType for ApiDisplay +where + X: serde::Serialize, + T: DisplayType + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option { + self.api + .then(|| self.fmt(x)) + .or_else(|| self.next.next_fmt(x)) + } + fn fmt(&self, x: &X) -> String { + serde_json::to_string(x).expect("Cannot serialize item to JSON") + } +} + +#[derive(Parser)] +struct JsonDisplay { + /// Display as pretty formatted JSON + #[clap(long, group = "fmt_type")] + json: bool, + #[clap(flatten)] + next: T, +} + +impl DisplayType for JsonDisplay +where + X: serde::Serialize, + T: DisplayType + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option { + self.json + .then(|| self.fmt(x)) + .or_else(|| self.next.next_fmt(x)) + } + fn fmt(&self, x: &X) -> String { + serde_json::to_string_pretty(x).expect("Cannot serialize item to JSON") + } +} + +#[derive(Parser)] +struct YamlDisplay { + /// Display as YAML + #[clap(long, group = "fmt_type")] + yaml: bool, + #[clap(flatten)] + next: T, +} + +impl DisplayType for YamlDisplay +where + X: serde::Serialize, + T: DisplayType + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option { + self.yaml + .then(|| self.fmt(x)) + .or_else(|| self.next.next_fmt(x)) + } + fn fmt(&self, x: &X) -> String { + serde_yaml::to_string(x).expect("Cannot serialize item to YAML") + } +} + +#[derive(Parser)] +struct TableDisplay { + /// Display as table + #[clap(long, group = "fmt_type", alias = "tabular")] + table: bool, + #[clap(flatten)] + next: T, +} + +impl DisplayType for TableDisplay +where + X: ToTable, + T: DisplayType + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option { + self.table + .then(|| self.fmt(x)) + .or_else(|| self.next.next_fmt(x)) + } + fn fmt(&self, x: &X) -> String { + x.to_table().to_string() + } +} + +#[derive(Parser)] +enum App { + /// Debug output by default + Debug { + #[clap(flatten)] + output: DebugDisplay>>, + }, + /// Text output by default + Text { + #[clap(flatten)] + output: TextDisplay>>, + }, + /// Unformatted JSON output by default + Api { + #[clap(flatten)] + output: ApiDisplay>>, + }, + /// Pretty formatted JSON output by default + Json { + #[clap(flatten)] + output: JsonDisplay>>>, + }, + /// YAML output by default + Yaml { + #[clap(flatten)] + output: YamlDisplay>>>>, + }, + /// Table output by default + Table { + #[clap(flatten)] + output: TableDisplay< + YamlDisplay>>>>, + >, + }, +} + +#[derive(Debug, serde::Serialize)] +struct Foo { + name: String, + value: String, +} + +impl fmt::Display for Foo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}={}", self.name, self.value) + } +} + +impl ToTable for Foo { + fn to_table(&self) -> Table { + let mut table = Table::new(); + + table + .set_content_arrangement(ContentArrangement::Dynamic) + .set_header(Row::from(vec!["Name", "Value"])) + .add_row(Row::from(vec![&self.name, &self.value])); + + table + } +} + +fn main() { + let app = App::parse(); + + let foo = Foo { + name: "Hello".to_string(), + value: "world".to_string(), + }; + + match app { + App::Debug { output } => println!("{}", output.display(&foo)), + App::Text { output } => println!("{}", output.display(&foo)), + App::Api { output } => println!("{}", output.display(&foo)), + App::Json { output } => println!("{}", output.display(&foo)), + App::Yaml { output } => println!("{}", output.display(&foo)), + App::Table { output } => println!("{}", output.display(&foo)), + } +} -- cgit v1.2.3