1use crate::compiler::TargetValueRef;
2use std::{
3 collections::BTreeMap,
4 fs::File,
5 io::{self, Read},
6 iter::IntoIterator,
7 path::PathBuf,
8};
9
10use crate::compiler::TimeZone;
11use crate::compiler::runtime::Runtime;
12use crate::compiler::state::RuntimeState;
13use crate::compiler::{
14 CompilationResult, CompileConfig, Function, Program, Target, TypeState, VrlRuntime,
15 compile_with_state,
16};
17use crate::diagnostic::Formatter;
18use crate::owned_metadata_path;
19use crate::value::Secrets;
20use crate::value::Value;
21use clap::Parser;
22
23use super::Error;
24use super::repl;
25
26#[derive(Parser, Debug)]
27#[command(name = "VRL", about = "Vector Remap Language CLI")]
28pub struct Opts {
29 #[arg(id = "PROGRAM")]
32 program: Option<String>,
33
34 #[arg(short, long = "input")]
36 input_file: Option<PathBuf>,
37
38 #[arg(short, long = "program", conflicts_with("PROGRAM"))]
40 program_file: Option<PathBuf>,
41
42 #[arg(short = 'q', long)]
44 quiet: bool,
45
46 #[arg(short = 'o', long)]
49 print_object: bool,
50
51 #[arg(short = 'z', long)]
53 timezone: Option<String>,
54
55 #[arg(short, long = "runtime", default_value_t)]
57 runtime: VrlRuntime,
58
59 #[arg(long = "print-warnings")]
61 print_warnings: bool,
62}
63
64impl Opts {
65 fn timezone(&self) -> Result<TimeZone, Error> {
66 if let Some(ref tz) = self.timezone {
67 TimeZone::parse(tz)
68 .ok_or_else(|| Error::Parse(format!("unable to parse timezone: {tz}")))
69 } else {
70 Ok(TimeZone::default())
71 }
72 }
73
74 fn read_program(&self) -> Result<String, Error> {
75 match self.program.as_ref() {
76 Some(source) => Ok(source.clone()),
77 None => match self.program_file.as_ref() {
78 Some(path) => read(File::open(path)?),
79 None => Ok(String::new()),
80 },
81 }
82 }
83
84 fn read_into_objects(&self) -> Result<Vec<Value>, Error> {
85 let input = match self.input_file.as_ref() {
86 Some(path) => read(File::open(path)?),
87 None => read(io::stdin()),
88 }?;
89
90 match input.as_str() {
91 "" => Ok(vec![Value::Object(BTreeMap::default())]),
92 _ => input
93 .lines()
94 .map(|line| Ok(serde_to_vrl(serde_json::from_str(line)?)))
95 .collect::<Result<Vec<Value>, Error>>(),
96 }
97 }
98
99 fn should_open_repl(&self) -> bool {
100 self.program.is_none() && self.program_file.is_none()
101 }
102}
103
104#[must_use]
105pub fn cmd(opts: &Opts, stdlib_functions: Vec<Box<dyn Function>>) -> exitcode::ExitCode {
106 match run(opts, stdlib_functions) {
107 Ok(()) => exitcode::OK,
108 Err(err) => {
109 #[allow(clippy::print_stderr)]
110 {
111 eprintln!("{err}");
112 }
113 exitcode::SOFTWARE
114 }
115 }
116}
117
118fn run(opts: &Opts, stdlib_functions: Vec<Box<dyn Function>>) -> Result<(), Error> {
119 let tz = opts.timezone()?;
120 if opts.should_open_repl() {
122 let repl_objects = if opts.input_file.is_some() {
125 opts.read_into_objects()?
126 } else {
127 default_objects()
128 };
129
130 repl(opts.quiet, repl_objects, tz, opts.runtime, stdlib_functions)
131 } else {
132 let objects = opts.read_into_objects()?;
133 let source = opts.read_program()?;
134
135 let mut config = CompileConfig::default();
137 config.set_read_only_path(owned_metadata_path!("vector"), true);
138
139 let state = TypeState::default();
140
141 let CompilationResult {
142 program,
143 warnings,
144 config: _,
145 } = compile_with_state(
146 &source,
147 &crate::stdlib::all(),
148 &state,
149 CompileConfig::default(),
150 )
151 .map_err(|diagnostics| {
152 Error::Parse(Formatter::new(&source, diagnostics).colored().to_string())
153 })?;
154
155 #[allow(clippy::print_stderr)]
156 if opts.print_warnings {
157 let warnings = Formatter::new(&source, warnings).colored().to_string();
158 eprintln!("{warnings}");
159 }
160
161 for mut object in objects {
162 let mut metadata = Value::Object(BTreeMap::new());
163 let mut secrets = Secrets::new();
164 let mut target = TargetValueRef {
165 value: &mut object,
166 metadata: &mut metadata,
167 secrets: &mut secrets,
168 };
169 let state = RuntimeState::default();
170 let runtime = Runtime::new(state);
171
172 let result = execute(&mut target, &program, tz, runtime, opts.runtime).map(|v| {
173 if opts.print_object {
174 object.to_string()
175 } else {
176 v.to_string()
177 }
178 });
179
180 #[allow(clippy::print_stdout)]
181 #[allow(clippy::print_stderr)]
182 match result {
183 Ok(ok) => println!("{ok}"),
184 Err(err) => eprintln!("{err}"),
185 }
186 }
187
188 Ok(())
189 }
190}
191
192#[allow(clippy::unnecessary_wraps)]
193fn repl(
194 quiet: bool,
195 objects: Vec<Value>,
196 timezone: TimeZone,
197 vrl_runtime: VrlRuntime,
198 stdlib_functions: Vec<Box<dyn Function>>,
199) -> Result<(), Error> {
200 use crate::compiler::TargetValue;
201
202 let objects = objects
203 .into_iter()
204 .map(|value| TargetValue {
205 value,
206 metadata: Value::Object(BTreeMap::new()),
207 secrets: Secrets::new(),
208 })
209 .collect();
210
211 repl::run(quiet, objects, timezone, vrl_runtime, stdlib_functions).map_err(Into::into)
212}
213
214fn execute(
215 object: &mut impl Target,
216 program: &Program,
217 timezone: TimeZone,
218 mut runtime: Runtime,
219 vrl_runtime: VrlRuntime,
220) -> Result<Value, Error> {
221 match vrl_runtime {
222 VrlRuntime::Ast => runtime
223 .resolve(object, program, &timezone)
224 .map_err(Error::Runtime),
225 }
226}
227
228fn serde_to_vrl(value: serde_json::Value) -> Value {
229 use serde_json::Value as JsonValue;
230
231 match value {
232 JsonValue::Null => crate::value::Value::Null,
233 JsonValue::Object(v) => v
234 .into_iter()
235 .map(|(k, v)| (k.into(), serde_to_vrl(v)))
236 .collect::<BTreeMap<_, _>>()
237 .into(),
238 JsonValue::Bool(v) => v.into(),
239 JsonValue::Number(v) if v.is_f64() => Value::from_f64_or_zero(v.as_f64().unwrap()),
240 JsonValue::Number(v) => v.as_i64().unwrap_or(i64::MAX).into(),
241 JsonValue::String(v) => v.into(),
242 JsonValue::Array(v) => v.into_iter().map(serde_to_vrl).collect::<Vec<_>>().into(),
243 }
244}
245
246fn read<R: Read>(mut reader: R) -> Result<String, Error> {
247 let mut buffer = String::new();
248 reader.read_to_string(&mut buffer)?;
249
250 Ok(buffer)
251}
252
253fn default_objects() -> Vec<Value> {
254 vec![Value::Object(BTreeMap::new())]
255}