1use anyhow::{anyhow, Result};
2use clap::{ArgAction, Parser, ValueEnum};
3use cql2::{Expr, ToSqlAst, Validator};
4use std::io::Read;
5
6#[derive(Debug, Parser)]
8#[command(version, about, long_about = None)]
9pub struct Cli {
10 #[arg(short, long)]
12 filter: Option<String>,
13
14 input: Option<String>,
20
21 #[arg(short, long)]
25 input_format: Option<InputFormat>,
26
27 #[arg(short, long)]
31 output_format: Option<OutputFormat>,
32
33 #[arg(long, default_value_t = true, action = ArgAction::Set)]
35 validate: bool,
36
37 #[arg(long, default_value_t = false, action = ArgAction::Set)]
39 reduce: bool,
40
41 #[arg(short, long, action = ArgAction::Count)]
45 verbose: u8,
46}
47
48#[derive(Debug, ValueEnum, Clone)]
50pub enum InputFormat {
51 Json,
53
54 Text,
56}
57
58#[derive(Debug, ValueEnum, Clone)]
60enum OutputFormat {
61 JsonPretty,
63
64 Json,
66
67 Text,
69
70 Sql,
72}
73
74impl Cli {
75 pub fn run(self) {
87 if let Err(err) = self.run_inner() {
88 eprintln!("{}", err);
89 std::process::exit(1)
90 }
91 }
92
93 pub fn run_inner(self) -> Result<()> {
94 if let Some(filter_path) = self.filter.as_ref() {
95 use std::fs::File;
96 use std::io::{BufRead, BufReader};
97 let expr_str = self.input.as_ref().ok_or_else(|| {
99 anyhow!("CQL2 expression required as positional argument when using --filter")
100 })?;
101 let expr: Expr = if expr_str.trim_start().starts_with('{') {
102 cql2::parse_json(expr_str)?
103 } else {
104 cql2::parse_text(expr_str)?
105 };
106 let file = File::open(filter_path)?;
107 let reader = BufReader::new(file);
108 reader
109 .lines()
110 .map(|line| {
111 let line = line?;
112 Ok(serde_json::from_str(&line)?)
113 })
114 .collect::<Result<Vec<_>, anyhow::Error>>()?
115 .into_iter()
116 .filter_map(|value| {
117 expr.filter(&[value])
118 .ok()
119 .and_then(|mut v| v.pop().cloned())
120 })
121 .for_each(|v| println!("{}", serde_json::to_string(&v).unwrap()));
122 return Ok(());
123 }
124 let input = self
125 .input
126 .and_then(|input| if input == "-" { None } else { Some(input) })
127 .map(Ok)
128 .unwrap_or_else(read_stdin)?;
129 let input_format = self.input_format.unwrap_or_else(|| {
130 if input.starts_with('{') {
131 InputFormat::Json
132 } else {
133 InputFormat::Text
134 }
135 });
136 let mut expr: Expr = match input_format {
137 InputFormat::Json => cql2::parse_json(&input)?,
138 InputFormat::Text => match cql2::parse_text(&input) {
139 Ok(expr) => expr,
140 Err(err) => {
141 return Err(anyhow!("[ERROR] Parsing error: {input}\n{err}"));
142 }
143 },
144 };
145 if self.reduce {
146 expr = expr.reduce(None)?;
147 }
148 if self.validate {
149 let validator = Validator::new().unwrap();
150 let value = serde_json::to_value(&expr).unwrap();
151 if let Err(error) = validator.validate(&value) {
152 return Err(anyhow!(
153 "[ERROR] Invalid CQL2: {input}\n{}",
154 match self.verbose {
155 0 => "For more detailed validation information, use -v".to_string(),
156 1 => format!("For more detailed validation information, use -vv\n{error}"),
157 _ => format!("{error:#}"),
158 }
159 ));
160 }
161 }
162 let output_format = self.output_format.unwrap_or(match input_format {
163 InputFormat::Json => OutputFormat::Json,
164 InputFormat::Text => OutputFormat::Text,
165 });
166 match output_format {
167 OutputFormat::JsonPretty => serde_json::to_writer_pretty(std::io::stdout(), &expr)?,
168 OutputFormat::Json => serde_json::to_writer(std::io::stdout(), &expr)?,
169 OutputFormat::Text => print!("{}", expr.to_text()?),
170 OutputFormat::Sql => {
171 let sql_ast = expr.to_sql_ast()?;
172 println!("{}", sql_ast);
173 }
174 }
175 println!();
176 Ok(())
177 }
178}
179
180fn read_stdin() -> Result<String> {
181 let mut buf = String::new();
182 std::io::stdin().read_to_string(&mut buf)?;
183 Ok(buf)
184}