bw/
render.rs

1use bitwarden_cli::Color;
2use clap::ValueEnum;
3
4use crate::command::Cli;
5
6#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
7#[allow(clippy::upper_case_acronyms)]
8pub(crate) enum Output {
9    JSON,
10    YAML,
11    Table,
12    TSV,
13    None,
14}
15
16pub enum CommandOutput {
17    Plain(String),
18    Object(Box<dyn erased_serde::Serialize>),
19}
20pub type CommandResult = color_eyre::eyre::Result<CommandOutput>;
21
22impl From<&str> for CommandOutput {
23    fn from(text: &str) -> Self {
24        CommandOutput::Plain(text.to_owned())
25    }
26}
27impl From<String> for CommandOutput {
28    fn from(text: String) -> Self {
29        CommandOutput::Plain(text)
30    }
31}
32impl From<()> for CommandOutput {
33    fn from(_: ()) -> Self {
34        CommandOutput::Plain(String::new())
35    }
36}
37
38pub struct RenderConfig {
39    pub output: Output,
40    pub color: Color,
41    pub cleanexit: bool,
42    pub quiet: bool,
43}
44
45impl RenderConfig {
46    pub fn new(cli: &Cli) -> Self {
47        Self {
48            output: cli.output,
49            color: cli.color,
50            cleanexit: cli.cleanexit,
51            quiet: cli.quiet,
52        }
53    }
54
55    pub fn render_result(&self, result: CommandResult) -> color_eyre::eyre::Result<()> {
56        if self.quiet || self.output == Output::None {
57            return Ok(());
58        }
59
60        fn pretty_print(language: &str, data: &str, color: Color) {
61            if color.is_enabled() {
62                bat::PrettyPrinter::new()
63                    .input_from_bytes(data.as_bytes())
64                    .language(language)
65                    .print()
66                    .expect("Input is valid");
67            } else {
68                print!("{}", data);
69            }
70        }
71
72        match result {
73            // Errors will be passed through to the caller, and rendered by the main function
74            Err(e) => Err(e),
75
76            // With cleanexit, we don't print anything on success
77            Ok(_) if self.cleanexit => Ok(()),
78
79            // Plain text is just output as is
80            Ok(CommandOutput::Plain(text)) => {
81                println!("{}", text);
82                Ok(())
83            }
84
85            // For objects, we serialize them based on the output format,
86            Ok(CommandOutput::Object(obj)) => {
87                match self.output {
88                    Output::JSON => {
89                        let mut json = serde_json::to_string_pretty(&*obj)?;
90                        // Yaml/table/tsv serializations add a newline at the end, so we do the same
91                        // here for consistency
92                        json.push('\n');
93                        pretty_print("json", &json, self.color);
94                    }
95                    Output::YAML => {
96                        let yaml = serde_yaml::to_string(&*obj)?;
97                        pretty_print("yaml", &yaml, self.color);
98                    }
99                    Output::Table => {
100                        todo!()
101                    }
102                    Output::TSV => {
103                        todo!()
104                    }
105                    Output::None => unreachable!(),
106                }
107                Ok(())
108            }
109        }
110    }
111}