use crate::compiler::TargetValueRef;
use std::{
collections::BTreeMap,
fs::File,
io::{self, Read},
iter::IntoIterator,
path::PathBuf,
};
use crate::compiler::TimeZone;
use crate::compiler::runtime::Runtime;
use crate::compiler::state::RuntimeState;
use crate::compiler::{
CompilationResult, CompileConfig, Function, Program, Target, TypeState, VrlRuntime,
compile_with_state,
};
use crate::diagnostic::Formatter;
use crate::owned_metadata_path;
use crate::value::Secrets;
use crate::value::Value;
use clap::Parser;
use super::Error;
use super::repl::Repl;
#[derive(Parser, Debug)]
#[command(name = "VRL", about = "Vector Remap Language CLI")]
pub struct Opts {
#[arg(id = "PROGRAM")]
program: Option<String>,
#[arg(short, long = "input")]
input_file: Option<PathBuf>,
#[arg(short, long = "program", conflicts_with("PROGRAM"))]
program_file: Option<PathBuf>,
#[arg(short = 'q', long)]
quiet: bool,
#[arg(short = 'o', long)]
print_object: bool,
#[arg(short = 'z', long)]
timezone: Option<String>,
#[arg(short, long = "runtime", default_value_t)]
runtime: VrlRuntime,
#[arg(long = "print-warnings")]
print_warnings: bool,
}
impl Opts {
fn timezone(&self) -> Result<TimeZone, Error> {
if let Some(ref tz) = self.timezone {
TimeZone::parse(tz)
.ok_or_else(|| Error::Parse(format!("unable to parse timezone: {tz}")))
} else {
Ok(TimeZone::default())
}
}
fn read_program(&self) -> Result<String, Error> {
match self.program.as_ref() {
Some(source) => Ok(source.clone()),
None => match self.program_file.as_ref() {
Some(path) => read(File::open(path)?),
None => Ok(String::new()),
},
}
}
fn read_into_objects(&self) -> Result<Vec<Value>, Error> {
let input = match self.input_file.as_ref() {
Some(path) => read(File::open(path)?),
None => read(io::stdin()),
}?;
match input.as_str() {
"" => Ok(vec![Value::Object(BTreeMap::default())]),
_ => input
.lines()
.map(|line| Ok(serde_to_vrl(serde_json::from_str(line)?)))
.collect::<Result<Vec<Value>, Error>>(),
}
}
fn should_open_repl(&self) -> bool {
self.program.is_none() && self.program_file.is_none()
}
}
#[must_use]
pub fn cmd(opts: &Opts, stdlib_functions: Vec<Box<dyn Function>>) -> exitcode::ExitCode {
match run(opts, stdlib_functions) {
Ok(()) => exitcode::OK,
Err(err) => {
#[allow(clippy::print_stderr)]
{
eprintln!("{err}");
}
exitcode::SOFTWARE
}
}
}
fn run(opts: &Opts, stdlib_functions: Vec<Box<dyn Function>>) -> Result<(), Error> {
let tz = opts.timezone()?;
if opts.should_open_repl() {
let repl_objects = if opts.input_file.is_some() {
opts.read_into_objects()?
} else {
default_objects()
};
repl(opts.quiet, repl_objects, tz, opts.runtime, stdlib_functions)
} else {
let objects = opts.read_into_objects()?;
let source = opts.read_program()?;
let mut config = CompileConfig::default();
config.set_read_only_path(owned_metadata_path!("vector"), true);
let state = TypeState::default();
let CompilationResult {
program,
warnings,
config: _,
} = compile_with_state(&source, &stdlib_functions, &state, CompileConfig::default())
.map_err(|diagnostics| {
Error::Parse(Formatter::new(&source, diagnostics).colored().to_string())
})?;
#[allow(clippy::print_stderr)]
if opts.print_warnings {
let warnings = Formatter::new(&source, warnings).colored().to_string();
eprintln!("{warnings}");
}
for mut object in objects {
let mut metadata = Value::Object(BTreeMap::new());
let mut secrets = Secrets::new();
let mut target = TargetValueRef {
value: &mut object,
metadata: &mut metadata,
secrets: &mut secrets,
};
let state = RuntimeState::default();
let runtime = Runtime::new(state);
let result = execute(&mut target, &program, tz, runtime, opts.runtime).map(|v| {
if opts.print_object {
object.to_string()
} else {
v.to_string()
}
});
#[allow(clippy::print_stdout)]
#[allow(clippy::print_stderr)]
match result {
Ok(ok) => println!("{ok}"),
Err(err) => eprintln!("{err}"),
}
}
Ok(())
}
}
fn repl(
quiet: bool,
objects: Vec<Value>,
timezone: TimeZone,
vrl_runtime: VrlRuntime,
stdlib_functions: Vec<Box<dyn Function>>,
) -> Result<(), Error> {
use crate::compiler::TargetValue;
let objects = objects
.into_iter()
.map(|value| TargetValue {
value,
metadata: Value::Object(BTreeMap::new()),
secrets: Secrets::new(),
})
.collect();
Repl::new(quiet, objects, timezone, vrl_runtime, stdlib_functions)
.run()
.map_err(Into::into)
}
fn execute(
object: &mut impl Target,
program: &Program,
timezone: TimeZone,
mut runtime: Runtime,
vrl_runtime: VrlRuntime,
) -> Result<Value, Error> {
match vrl_runtime {
VrlRuntime::Ast => runtime
.resolve(object, program, &timezone)
.map_err(Error::Runtime),
}
}
fn serde_to_vrl(value: serde_json::Value) -> Value {
use serde_json::Value as JsonValue;
match value {
JsonValue::Null => crate::value::Value::Null,
JsonValue::Object(v) => v
.into_iter()
.map(|(k, v)| (k.into(), serde_to_vrl(v)))
.collect::<BTreeMap<_, _>>()
.into(),
JsonValue::Bool(v) => v.into(),
JsonValue::Number(v) if v.is_f64() => Value::from_f64_or_zero(v.as_f64().unwrap()),
JsonValue::Number(v) => v.as_i64().unwrap_or(i64::MAX).into(),
JsonValue::String(v) => v.into(),
JsonValue::Array(v) => v.into_iter().map(serde_to_vrl).collect::<Vec<_>>().into(),
}
}
fn read<R: Read>(mut reader: R) -> Result<String, Error> {
let mut buffer = String::new();
reader.read_to_string(&mut buffer)?;
Ok(buffer)
}
fn default_objects() -> Vec<Value> {
vec![Value::Object(BTreeMap::new())]
}