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::{
96 fs::File,
97 io::{BufRead, BufReader},
98 };
99 let expr_str = self.input.as_ref().ok_or_else(|| {
101 anyhow!("CQL2 expression required as positional argument when using --filter")
102 })?;
103 let expr: Expr = if expr_str.trim_start().starts_with('{') {
104 cql2::parse_json(expr_str)?
105 } else {
106 cql2::parse_text(expr_str)?
107 };
108 let file = File::open(filter_path)?;
109 let reader = BufReader::new(file);
110 reader
111 .lines()
112 .map(|line| {
113 let line = line?;
114 Ok(serde_json::from_str(&line)?)
115 })
116 .collect::<Result<Vec<_>, anyhow::Error>>()?
117 .into_iter()
118 .filter_map(|value| {
119 expr.filter(&[value])
120 .ok()
121 .and_then(|mut v| v.pop().cloned())
122 })
123 .for_each(|v| println!("{}", serde_json::to_string(&v).unwrap()));
124 return Ok(());
125 }
126 let input = self
127 .input
128 .and_then(|input| if input == "-" { None } else { Some(input) })
129 .map(Ok)
130 .unwrap_or_else(read_stdin)?;
131 let input_format = self.input_format.unwrap_or_else(|| {
132 if input.starts_with('{') {
133 InputFormat::Json
134 } else {
135 InputFormat::Text
136 }
137 });
138 let mut expr: Expr = match input_format {
139 InputFormat::Json => cql2::parse_json(&input)?,
140 InputFormat::Text => match cql2::parse_text(&input) {
141 Ok(expr) => expr,
142 Err(err) => {
143 return Err(anyhow!("[ERROR] Parsing error: {input}\n{err}"));
144 }
145 },
146 };
147 if self.reduce {
148 expr = expr.reduce(None)?;
149 }
150 if self.validate {
151 let validator = Validator::new().unwrap();
152 let value = serde_json::to_value(&expr).unwrap();
153 if let Err(error) = validator.validate(&value) {
154 return Err(anyhow!(
155 "[ERROR] Invalid CQL2: {input}\n{}",
156 match self.verbose {
157 0 => "For more detailed validation information, use -v".to_string(),
158 1 => format!("For more detailed validation information, use -vv\n{error}"),
159 _ => format!("{error:#}"),
160 }
161 ));
162 }
163 }
164 let output_format = self.output_format.unwrap_or(match input_format {
165 InputFormat::Json => OutputFormat::Json,
166 InputFormat::Text => OutputFormat::Text,
167 });
168 match output_format {
169 OutputFormat::JsonPretty => serde_json::to_writer_pretty(std::io::stdout(), &expr)?,
170 OutputFormat::Json => serde_json::to_writer(std::io::stdout(), &expr)?,
171 OutputFormat::Text => print!("{}", expr.to_text()?),
172 OutputFormat::Sql => {
173 let sql_ast = expr.to_sql_ast()?;
174 println!("{}", sql_ast);
175 }
176 }
177 println!();
178 Ok(())
179 }
180}
181
182fn read_stdin() -> Result<String> {
183 let mut buf = String::new();
184 std::io::stdin().read_to_string(&mut buf)?;
185 Ok(buf)
186}