cql2_cli/
lib.rs

1use anyhow::{anyhow, Result};
2use clap::{ArgAction, Parser, ValueEnum};
3use cql2::{Expr, Validator};
4use std::io::Read;
5
6/// The CQL2 command-line interface.
7#[derive(Debug, Parser)]
8#[command(version, about, long_about = None)]
9pub struct Cli {
10    /// The input CQL2
11    ///
12    /// If not provided, or `-`, the CQL2 will be read from standard input. The
13    /// type (json or text) will be auto-detected. To specify a format, use
14    /// --input-format.
15    input: Option<String>,
16
17    /// The input format.
18    ///
19    /// If not provided, the format will be auto-detected from the input.
20    #[arg(short, long)]
21    input_format: Option<InputFormat>,
22
23    /// The output format.
24    ///
25    /// If not provided, the format will be the same as the input.
26    #[arg(short, long)]
27    output_format: Option<OutputFormat>,
28
29    /// Validate the CQL2
30    #[arg(long, default_value_t = true, action = ArgAction::Set)]
31    validate: bool,
32
33    /// Reduce the CQL2
34    #[arg(long, default_value_t = false, action = ArgAction::Set)]
35    reduce: bool,
36
37    /// Verbosity.
38    ///
39    /// Provide this argument several times to turn up the chatter.
40    #[arg(short, long, action = ArgAction::Count)]
41    verbose: u8,
42}
43
44/// The input CQL2 format.
45#[derive(Debug, ValueEnum, Clone)]
46pub enum InputFormat {
47    /// cql2-json
48    Json,
49
50    /// cql2-text
51    Text,
52}
53
54/// The output CQL2 format.
55#[derive(Debug, ValueEnum, Clone)]
56enum OutputFormat {
57    /// cql2-json, pretty-printed
58    JsonPretty,
59
60    /// cql2-json, compact
61    Json,
62
63    /// cql2-text
64    Text,
65
66    /// SQL
67    Sql,
68}
69
70impl Cli {
71    /// Runs the cli.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use cql2_cli::Cli;
77    /// use clap::Parser;
78    ///
79    /// let cli = Cli::try_parse_from(&["cql2", "landsat:scene_id = 'LC82030282019133LGN00'"]).unwrap();
80    /// cli.run();
81    /// ```
82    pub fn run(self) {
83        if let Err(err) = self.run_inner() {
84            eprintln!("{}", err);
85            std::process::exit(1)
86        }
87    }
88
89    pub fn run_inner(self) -> Result<()> {
90        let input = self
91            .input
92            .and_then(|input| if input == "-" { None } else { Some(input) })
93            .map(Ok)
94            .unwrap_or_else(read_stdin)?;
95        let input_format = self.input_format.unwrap_or_else(|| {
96            if input.starts_with('{') {
97                InputFormat::Json
98            } else {
99                InputFormat::Text
100            }
101        });
102        let mut expr: Expr = match input_format {
103            InputFormat::Json => cql2::parse_json(&input)?,
104            InputFormat::Text => match cql2::parse_text(&input) {
105                Ok(expr) => expr,
106                Err(err) => {
107                    return Err(anyhow!("[ERROR] Parsing error: {input}\n{err}"));
108                }
109            },
110        };
111        if self.reduce {
112            expr = expr.reduce(None)?;
113        }
114        if self.validate {
115            let validator = Validator::new().unwrap();
116            let value = serde_json::to_value(&expr).unwrap();
117            if let Err(error) = validator.validate(&value) {
118                return Err(anyhow!(
119                    "[ERROR] Invalid CQL2: {input}\n{}",
120                    match self.verbose {
121                        0 => "For more detailed validation information, use -v".to_string(),
122                        1 => format!("For more detailed validation information, use -vv\n{error}"),
123                        _ => format!("{error:#}"),
124                    }
125                ));
126            }
127        }
128        let output_format = self.output_format.unwrap_or(match input_format {
129            InputFormat::Json => OutputFormat::Json,
130            InputFormat::Text => OutputFormat::Text,
131        });
132        match output_format {
133            OutputFormat::JsonPretty => serde_json::to_writer_pretty(std::io::stdout(), &expr)?,
134            OutputFormat::Json => serde_json::to_writer(std::io::stdout(), &expr)?,
135            OutputFormat::Text => print!("{}", expr.to_text()?),
136            OutputFormat::Sql => serde_json::to_writer_pretty(std::io::stdout(), &expr.to_sql()?)?,
137        }
138        println!();
139        Ok(())
140    }
141}
142
143fn read_stdin() -> Result<String> {
144    let mut buf = String::new();
145    std::io::stdin().read_to_string(&mut buf)?;
146    Ok(buf)
147}