use clap::ArgGroup;
use clap::Parser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputFormat {
Json,
SlurpJson,
Json5,
Toml,
Yaml,
Xml,
Gron,
}
#[derive(Debug, Clone)]
pub struct Argument {
pub name: String,
pub type_name: String,
pub value: String,
}
impl std::str::FromStr for Argument {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.splitn(2, ':').collect();
if parts.len() != 2 {
return Err(format!(
"Invalid argument format '{}'. Expected 'name:type=value'",
s
));
}
let name = parts[0].to_string();
let type_and_value = parts[1];
let eq_pos = type_and_value.find('=').ok_or_else(|| {
format!(
"Missing value for argument '{}'. Expected 'name:type=value'",
name
)
})?;
let (type_name, value_with_eq) = type_and_value.split_at(eq_pos);
let value = value_with_eq[1..].to_string();
Ok(Argument {
name,
type_name: type_name.to_string(),
value,
})
}
}
#[derive(Parser, Debug)]
#[command(name = "celq")]
#[command(
name = "celq",
about = "A CEL command-line query tool for JSON data",
version,
long_about = None,
group(
ArgGroup::new("program")
.args(&["expression", "from_file"])
),
group(
ArgGroup::new("input_format")
.args(&[
"slurp",
"from_json5",
"from_toml",
"from_yaml",
"from_xml",
"from_gron",
])
),
group(
ArgGroup::new("output_style")
.args(&["pretty_print", "greppable"])
),
group(
ArgGroup::new("output_options")
.args(&["raw_output", "greppable"])
)
)]
pub struct Cli {
#[arg(short = 'a', long = "arg", value_name = "name:type=value")]
pub args: Vec<Argument>,
#[arg(short = 'b', long = "boolean")]
pub boolean: bool,
#[arg(short = 'n', long = "null-input")]
pub null_input: bool,
#[arg(long = "void")]
pub void: bool,
#[arg(short = 's', long = "slurp")]
pub slurp: bool,
#[arg(long = "from-json5")]
pub from_json5: bool,
#[arg(long = "from-toml")]
pub from_toml: bool,
#[arg(long = "from-yaml")]
pub from_yaml: bool,
#[arg(long = "from-xml")]
pub from_xml: bool,
#[arg(long = "from-gron")]
pub from_gron: bool,
#[arg(
short = 'j',
long = "jobs",
value_name = "N",
default_value = "1",
value_parser = parse_parallelism
)]
pub parallelism: i32,
#[arg(short = 'R', long = "root-var", default_value = "this")]
pub root_var: String,
#[arg(short = 'r', long = "raw-output")]
pub raw_output: bool,
#[arg(short = 'S', long = "sort-keys")]
pub sort_keys: bool,
#[arg(short = 'f', long = "from-file", value_name = "FILE")]
pub from_file: Option<std::path::PathBuf>,
#[arg(short = 'p', long = "pretty-print")]
pub pretty_print: bool,
#[arg(short = 'g', long = "greppable")]
pub greppable: bool,
#[arg(long = "no-extensions")]
pub no_extensions: bool,
#[arg(value_name = "expr", default_value = "this")]
pub expression: Option<String>,
}
impl Cli {
pub fn input_format(&self) -> InputFormat {
if self.from_gron {
InputFormat::Gron
} else if self.from_yaml {
InputFormat::Yaml
} else if self.from_toml {
InputFormat::Toml
} else if self.from_xml {
InputFormat::Xml
} else if self.from_json5 {
InputFormat::Json5
} else if self.slurp {
InputFormat::SlurpJson
} else {
InputFormat::Json
}
}
}
fn parse_parallelism(s: &str) -> Result<i32, String> {
let value: i32 = s
.parse()
.map_err(|_| format!("'{}' is not a valid integer", s))?;
if value == 0 {
Err("parallelism level cannot be 0".to_string())
} else if value < 0 {
Ok(-1)
} else {
Ok(value)
}
}
#[derive(Clone, Debug)]
pub struct InputParameters {
pub root_var: String,
pub null_input: bool,
pub input_format: InputFormat,
pub parallelism: i32,
pub sort_keys: bool,
pub pretty_print: bool,
pub raw_output: bool,
pub greppable: bool,
pub no_extensions: bool,
}
#[cfg(test)]
#[path = "cli_test.rs"]
mod tests;