diff --git a/Cargo.lock b/Cargo.lock index 04950840..b6418f74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,9 +37,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "annotate-snippets" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b9d411ecbaf79885c6df4d75fff75858d5995ff25385657a28af47e82f9c36" +checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" dependencies = [ "unicode-width", "yansi-term", @@ -344,7 +344,7 @@ checksum = "7227b28d24aafee21ff72512336c797fa00bb3ea803186b1b105a68abc97660b" dependencies = [ "anyhow", "bumpalo", - "indexmap 2.0.2", + "indexmap 2.1.0", "rustc-hash", "serde", "unicode-width", @@ -493,9 +493,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.2", @@ -1240,9 +1240,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1251,9 +1251,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", @@ -1746,7 +1746,7 @@ name = "xtask" version = "0.1.0" dependencies = [ "anyhow", - "indexmap 2.0.2", + "indexmap 2.1.0", "itertools", "proc-macro2", "quote", @@ -1774,18 +1774,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.18" +version = "0.7.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7d7c7970ca2215b8c1ccf4d4f354c4733201dfaaba72d44ae5b37472e4901" +checksum = "686b7e407015242119c33dab17b8f61ba6843534de936d94368856528eae4dcc" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.18" +version = "0.7.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b27b1bb92570f989aac0ab7e9cbfbacdd65973f7ee920d9f0e71ebac878fd0b" +checksum = "020f3dfe25dfc38dfea49ce62d5d45ecdd7f0d8a724fa63eb36b6eba4ec76806" dependencies = [ "proc-macro2", "quote", diff --git a/cmds/jrsonnet/src/main.rs b/cmds/jrsonnet/src/main.rs index 2ecc5215..db272298 100644 --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -186,7 +186,7 @@ fn main_real(s: &State, opts: Opts) -> Result<(), Error> { s.import(&input)? }; - let tla = opts.tla.tla_opts()?; + let tla = opts.tla.into_args_in(s)?; #[allow(unused_mut)] let mut val = apply_tla(s.clone(), &tla, val)?; diff --git a/crates/jrsonnet-cli/src/tla.rs b/crates/jrsonnet-cli/src/tla.rs index ddd906f3..51bb8ab8 100644 --- a/crates/jrsonnet-cli/src/tla.rs +++ b/crates/jrsonnet-cli/src/tla.rs @@ -1,13 +1,22 @@ +use std::{ + ffi::{OsStr, OsString}, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, +}; + use clap::Parser; use jrsonnet_evaluator::{ + bail, error::{ErrorKind, Result}, function::TlaArg, gc::GcHashMap, - IStr, + val::ThunkValue, + IStr, State, Thunk, Val, }; +use jrsonnet_gcmodule::Trace; use jrsonnet_parser::{ParserSettings, Source}; -use crate::{ExtFile, ExtStr}; +use crate::ExtStr; #[derive(Parser)] #[clap(next_help_heading = "TOP LEVEL ARGUMENTS")] @@ -21,7 +30,7 @@ pub struct TlaOpts { /// Read top level argument string from file. /// See also `--tla-str` #[clap(long, name = "name=tla path", number_of_values = 1)] - tla_str_file: Vec, + tla_str_file: Vec, /// Add top level argument from code. /// See also `--tla-str` #[clap(long, name = "name[=tla source]", number_of_values = 1)] @@ -29,28 +38,28 @@ pub struct TlaOpts { /// Read top level argument code from file. /// See also `--tla-str` #[clap(long, name = "name=tla code path", number_of_values = 1)] - tla_code_file: Vec, + tla_code_file: Vec, } impl TlaOpts { - pub fn tla_opts(&self) -> Result> { + pub fn into_args_in(self, state: &State) -> Result> { let mut out = GcHashMap::new(); - for (name, value) in self - .tla_str - .iter() - .map(|c| (&c.name, &c.value)) - .chain(self.tla_str_file.iter().map(|c| (&c.name, &c.value))) - { + for (name, value) in self.tla_str.iter().map(|c| (&c.name, &c.value)) { out.insert(name.into(), TlaArg::String(value.into())); } - for (name, code) in self - .tla_code - .iter() - .map(|c| (&c.name, &c.value)) - .chain(self.tla_code_file.iter().map(|c| (&c.name, &c.value))) - { + for file in self.tla_str_file { + let (key, path) = parse_named_tla_path(&file)?; + out.insert( + key.into(), + TlaArg::Lazy(Thunk::new(ImportStrThunk { + state: state.clone(), + path, + })), + ); + } + for (name, code) in self.tla_code.iter().map(|c| (&c.name, &c.value)) { let source = Source::new_virtual(format!("").into(), code.into()); out.insert( - (name as &str).into(), + name.into(), TlaArg::Code( jrsonnet_parser::parse( code, @@ -65,6 +74,58 @@ impl TlaOpts { ), ); } + for file in self.tla_code_file { + let (key, path) = parse_named_tla_path(&file)?; + out.insert( + key.into(), + TlaArg::Lazy(Thunk::new(ImportCodeThunk { + state: state.clone(), + path, + })), + ); + } Ok(out) } } + +fn parse_named_tla_path(raw: &OsString) -> Result<(&str, PathBuf)> { + let mut parts = raw.as_bytes().splitn(2, |&byte| byte == b'='); + let Some(key) = parts.next() else { + bail!("No TLA key was specified"); + }; + + let Ok(key) = std::str::from_utf8(key) else { + bail!("Invalid TLA map"); + }; + Ok(if let Some(value) = parts.next() { + (key, Path::new(OsStr::from_bytes(value)).to_owned()) + } else { + (key, std::env::var_os(key).unwrap_or_default().into()) + }) +} + +#[derive(Trace)] +struct ImportStrThunk { + path: PathBuf, + state: State, +} +impl ThunkValue for ImportStrThunk { + type Output = Val; + + fn get(self: Box) -> Result { + self.state.import_str(self.path).map(|s| Val::Str(s.into())) + } +} + +#[derive(Trace)] +struct ImportCodeThunk { + path: PathBuf, + state: State, +} +impl ThunkValue for ImportCodeThunk { + type Output = Val; + + fn get(self: Box) -> Result { + self.state.import(self.path) + } +} diff --git a/crates/jrsonnet-evaluator/src/evaluate/mod.rs b/crates/jrsonnet-evaluator/src/evaluate/mod.rs index 79567e3f..1787883f 100644 --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -667,7 +667,7 @@ pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result { IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)? } i @ (Import(path) | ImportStr(path) | ImportBin(path)) => { - let Expr::Str(path) = &*path.0 else { + let Str(path) = &*path.0 else { bail!("computed imports are not supported") }; let tmp = loc.clone().0; diff --git a/crates/jrsonnet-evaluator/src/lib.rs b/crates/jrsonnet-evaluator/src/lib.rs index ce0692fa..ab3e6d26 100644 --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -346,7 +346,7 @@ impl State { let file_name = Source::new(path.clone(), code.clone()); if file.parsed.is_none() { file.parsed = Some( - jrsonnet_parser::parse( + parse( &code, &ParserSettings { source: file_name.clone(), @@ -393,6 +393,10 @@ impl State { let resolved = self.resolve(path)?; self.import_resolved(resolved) } + pub fn import_str(&self, path: impl AsRef) -> Result { + let resolved = self.resolve(path)?; + self.import_resolved_str(resolved) + } /// Creates context with all passed global variables pub fn create_default_context(&self, source: Source) -> Context { @@ -518,7 +522,7 @@ impl State { pub fn evaluate_snippet(&self, name: impl Into, code: impl Into) -> Result { let code = code.into(); let source = Source::new_virtual(name.into(), code.clone()); - let parsed = jrsonnet_parser::parse( + let parsed = parse( &code, &ParserSettings { source: source.clone(), @@ -539,7 +543,7 @@ impl State { ) -> Result { let code = code.into(); let source = Source::new_virtual(name.into(), code.clone()); - let parsed = jrsonnet_parser::parse( + let parsed = parse( &code, &ParserSettings { source: source.clone(), @@ -558,13 +562,17 @@ impl State { /// Settings utilities impl State { - // Only panics in case of [`ImportResolver`] contract violation + // # Panics + // + // If [`ImportResolver`] contract is violated. #[allow(clippy::missing_panics_doc)] pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result { self.import_resolver().resolve_from(from, path.as_ref()) } - // Only panics in case of [`ImportResolver`] contract violation + // # Panics + // + // If [`ImportResolver`] contract is violated. #[allow(clippy::missing_panics_doc)] pub fn resolve(&self, path: impl AsRef) -> Result { self.import_resolver().resolve(path.as_ref()) diff --git a/crates/jrsonnet-stdlib/src/lib.rs b/crates/jrsonnet-stdlib/src/lib.rs index c5dcea12..7c53dee5 100644 --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -352,8 +352,6 @@ impl jrsonnet_evaluator::ContextInitializer for ContextInitializer { } #[cfg(feature = "legacy-this-file")] fn populate(&self, source: Source, builder: &mut ContextBuilder) { - use jrsonnet_evaluator::val::StrValue; - let mut std = ObjValueBuilder::new(); std.with_super(self.stdlib_obj.clone()); std.field("thisFile")