diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2023-07-21 10:37:36 +0200 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2023-07-22 13:04:54 +0200 |
commit | a68fff8ea021c3867139d82e858be1aa6343f3a3 (patch) | |
tree | 4fc6721cb78ff19bce8db4bc8a5bb3dfbaf98a5e /src/main.rs | |
download | rust-clap-output-formats-master.tar.gz |
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 250 |
1 files changed, 250 insertions, 0 deletions
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<X> { + fn next_fmt(&self, x: &X) -> Option<String>; + 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<X> DisplayType<X> for NoDisplay { + fn next_fmt(&self, _: &X) -> Option<String> { + None + } + fn fmt(&self, _: &X) -> String { + String::new() + } +} + +#[derive(Parser)] +struct TextDisplay<T: clap::Args> { + /// Display as text + #[clap(long, group = "fmt_type")] + text: bool, + #[clap(flatten)] + next: T, +} + +impl<X, T> DisplayType<X> for TextDisplay<T> +where + X: fmt::Display, + T: DisplayType<X> + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option<String> { + 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<T: clap::Args> { + /// Display as internal debug representation + #[clap(long, group = "fmt_type")] + debug: bool, + #[clap(flatten)] + next: T, +} + +impl<X, T> DisplayType<X> for DebugDisplay<T> +where + X: fmt::Debug, + T: DisplayType<X> + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option<String> { + 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<T: clap::Args> { + /// Display as unformatted JSON + #[clap(long, group = "fmt_type")] + api: bool, + #[clap(flatten)] + next: T, +} + +impl<X, T> DisplayType<X> for ApiDisplay<T> +where + X: serde::Serialize, + T: DisplayType<X> + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option<String> { + 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<T: clap::Args> { + /// Display as pretty formatted JSON + #[clap(long, group = "fmt_type")] + json: bool, + #[clap(flatten)] + next: T, +} + +impl<X, T> DisplayType<X> for JsonDisplay<T> +where + X: serde::Serialize, + T: DisplayType<X> + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option<String> { + 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<T: clap::Args> { + /// Display as YAML + #[clap(long, group = "fmt_type")] + yaml: bool, + #[clap(flatten)] + next: T, +} + +impl<X, T> DisplayType<X> for YamlDisplay<T> +where + X: serde::Serialize, + T: DisplayType<X> + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option<String> { + 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<T: clap::Args> { + /// Display as table + #[clap(long, group = "fmt_type", alias = "tabular")] + table: bool, + #[clap(flatten)] + next: T, +} + +impl<X, T> DisplayType<X> for TableDisplay<T> +where + X: ToTable, + T: DisplayType<X> + clap::Args, +{ + fn next_fmt(&self, x: &X) -> Option<String> { + 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<TextDisplay<ApiDisplay<NoDisplay>>>, + }, + /// Text output by default + Text { + #[clap(flatten)] + output: TextDisplay<DebugDisplay<ApiDisplay<NoDisplay>>>, + }, + /// Unformatted JSON output by default + Api { + #[clap(flatten)] + output: ApiDisplay<TextDisplay<DebugDisplay<NoDisplay>>>, + }, + /// Pretty formatted JSON output by default + Json { + #[clap(flatten)] + output: JsonDisplay<ApiDisplay<TextDisplay<DebugDisplay<NoDisplay>>>>, + }, + /// YAML output by default + Yaml { + #[clap(flatten)] + output: YamlDisplay<JsonDisplay<ApiDisplay<TextDisplay<DebugDisplay<NoDisplay>>>>>, + }, + /// Table output by default + Table { + #[clap(flatten)] + output: TableDisplay< + YamlDisplay<JsonDisplay<ApiDisplay<TextDisplay<DebugDisplay<NoDisplay>>>>>, + >, + }, +} + +#[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)), + } +} |