1use anyhow::{anyhow, Result};
2use clap::{ArgAction, Parser, ValueEnum};
3use cql2::{Expr, Validator};
4use std::io::Read;
5
6#[derive(Debug, Parser)]
8#[command(version, about, long_about = None)]
9pub struct Cli {
10 input: Option<String>,
16
17 #[arg(short, long)]
21 input_format: Option<InputFormat>,
22
23 #[arg(short, long)]
27 output_format: Option<OutputFormat>,
28
29 #[arg(long, default_value_t = true, action = ArgAction::Set)]
31 validate: bool,
32
33 #[arg(long, default_value_t = false, action = ArgAction::Set)]
35 reduce: bool,
36
37 #[arg(short, long, action = ArgAction::Count)]
41 verbose: u8,
42}
43
44#[derive(Debug, ValueEnum, Clone)]
46pub enum InputFormat {
47 Json,
49
50 Text,
52}
53
54#[derive(Debug, ValueEnum, Clone)]
56enum OutputFormat {
57 JsonPretty,
59
60 Json,
62
63 Text,
65
66 Sql,
68}
69
70impl Cli {
71 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}