use clap::Parser;
use jsonata_core::evaluator::{Context, Evaluator};
use jsonata_core::parser;
use jsonata_core::value::JValue;
use std::io::Read;
use std::process::ExitCode;
mod bindings;
mod error_format;
mod resolve;
#[derive(Parser, Debug)]
#[command(
name = "jsonata",
version,
about = "Evaluate JSONata expressions against JSON data"
)]
pub(crate) struct Cli {
#[arg(short = 'c', long)]
pub(crate) compact: bool,
#[arg(short = 'r', long = "raw-output")]
pub(crate) raw_output: bool,
#[arg(short = 'n', long = "null-input")]
pub(crate) null_input: bool,
#[arg(short = 'f', long = "from-file", value_name = "FILE")]
pub(crate) from_file: Option<String>,
#[arg(long = "arg", value_name = "NAME=VALUE", action = clap::ArgAction::Append)]
pub(crate) arg: Vec<String>,
#[arg(long = "argjson", value_name = "NAME=JSON", action = clap::ArgAction::Append)]
pub(crate) argjson: Vec<String>,
#[arg(value_name = "EXPRESSION_OR_FILE")]
pub(crate) positional1: Option<String>,
#[arg(value_name = "FILE")]
pub(crate) positional2: Option<String>,
}
fn main() -> ExitCode {
let cli = Cli::parse();
run(cli)
}
fn run(cli: Cli) -> ExitCode {
let (expr_source, input_source) = match resolve::resolve(&cli) {
Ok(v) => v,
Err(msg) => {
eprintln!("error: {}", msg);
return ExitCode::from(2);
}
};
let var_bindings = match bindings::parse_bindings(&cli.arg, &cli.argjson) {
Ok(b) => b,
Err(msg) => {
eprintln!("error: {}", msg);
return ExitCode::from(2);
}
};
let expression = match expr_source {
resolve::ExpressionSource::Inline(s) => s,
resolve::ExpressionSource::File(path) => match std::fs::read_to_string(&path) {
Ok(s) => s,
Err(e) => {
eprintln!("error: could not read expression file {}: {}", path, e);
return ExitCode::from(2);
}
},
};
let data = match input_source {
resolve::InputSource::Null => JValue::Undefined,
resolve::InputSource::Stdin => match read_input(None) {
Ok(data) => data,
Err((msg, code)) => {
eprintln!("{}", msg);
return ExitCode::from(code);
}
},
resolve::InputSource::File(path) => match read_input(Some(&path)) {
Ok(data) => data,
Err((msg, code)) => {
eprintln!("{}", msg);
return ExitCode::from(code);
}
},
};
let ast = match parser::parse(&expression) {
Ok(ast) => ast,
Err(e) => {
eprintln!("{}", e.display_message());
return ExitCode::from(1);
}
};
let mut context = Context::new();
for (name, value) in var_bindings {
context.bind(name, value);
}
let mut evaluator = Evaluator::with_context(context);
let result = match evaluator.evaluate(&ast, &data) {
Ok(v) => v,
Err(e) => {
eprintln!("{}", error_format::format_evaluator_error(&e));
return ExitCode::from(1);
}
};
if result.is_undefined() {
return ExitCode::SUCCESS;
}
print_result(&result, cli.compact, cli.raw_output)
}
fn print_result(result: &JValue, compact: bool, raw_output: bool) -> ExitCode {
if raw_output {
if let JValue::String(s) = result {
println!("{}", s);
return ExitCode::SUCCESS;
}
}
let text = if compact {
result.to_json_string()
} else {
result.to_json_string_pretty()
};
match text {
Ok(s) => {
println!("{}", s);
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("error: could not serialize result: {}", e);
ExitCode::from(1)
}
}
}
fn read_input(path: Option<&str>) -> Result<JValue, (String, u8)> {
let raw = match path {
Some(p) => std::fs::read_to_string(p)
.map_err(|e| (format!("error: could not read input file {}: {}", p, e), 2))?,
None => {
let mut buf = String::new();
std::io::stdin()
.read_to_string(&mut buf)
.map_err(|e| (format!("error: could not read stdin: {}", e), 2))?;
buf
}
};
JValue::from_json_str(&raw).map_err(|e| (format!("error: invalid JSON input: {}", e), 3))
}