From 30d0357ae3c25f64855b1b5489ca69a9bc6daa61 Mon Sep 17 00:00:00 2001 From: Лач Date: Thu, 25 Jun 2020 11:32:11 +0000 Subject: [PATCH] feat: library paths --- --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -99,6 +99,9 @@ )] max_trace: usize, + #[clap(long, short = "J", about = "Library search dir")] + jpath: Vec, + #[clap( long, default_value = "3", @@ -112,10 +115,15 @@ fn main() { let opts: Opts = Opts::parse(); - let evaluator = jsonnet_evaluator::EvaluationState::new(EvaluationSettings { - import_resolver: Box::new(|path| String::from_utf8(std::fs::read(path).unwrap()).unwrap()), - ..Default::default() - }); + let evaluator = jsonnet_evaluator::EvaluationState::new( + EvaluationSettings { + max_stack_trace_size: opts.max_trace, + max_stack_frames: opts.max_stack, + }, + Box::new(jsonnet_evaluator::FileImportResolver { + library_paths: opts.jpath.clone(), + }), + ); if !opts.no_stdlib { evaluator.with_stdlib(); } --- a/crates/jsonnet-evaluator/src/error.rs +++ b/crates/jsonnet-evaluator/src/error.rs @@ -1,5 +1,6 @@ use crate::ValType; use jsonnet_parser::LocExpr; +use std::path::PathBuf; #[derive(Debug)] pub enum Error { @@ -24,6 +25,12 @@ StandaloneSuper, + ImportFileNotFound(PathBuf, PathBuf), + ResolvedFileNotFound(PathBuf), + ImportBadFileUtf8(PathBuf), + ImportNotSupported(PathBuf, PathBuf), + ImportSyntaxError(jsonnet_parser::ParseError), + RuntimeError(String), StackOverflow, FractionalIndex, --- a/crates/jsonnet-evaluator/src/evaluate.rs +++ b/crates/jsonnet-evaluator/src/evaluate.rs @@ -785,24 +785,22 @@ } } Import(path) => { - let mut lib_path = loc + let mut import_location = loc .clone() .expect("imports can't be used without loc_data") .0 .clone(); - lib_path.pop(); - lib_path.push(path); - with_state(|s| s.import_file(&lib_path))? + import_location.pop(); + with_state(|s| s.import_file(&import_location, path))? } ImportStr(path) => { - let mut file_path = loc + let mut import_location = loc .clone() .expect("imports can't be used without loc_data") .0 .clone(); - file_path.pop(); - file_path.push(path); - Val::Str(with_state(|s| s.import_file_str(&file_path))?) + import_location.pop(); + Val::Str(with_state(|s| s.import_file_str(&import_location, path))?) } Literal(LiteralType::Super) => return create_error(crate::error::Error::StandaloneSuper), }) --- /dev/null +++ b/crates/jsonnet-evaluator/src/import.rs @@ -0,0 +1,85 @@ +use crate::create_error; +use crate::error::{Error, Result}; +use fs::File; +use std::fs; +use std::io::Read; +use std::{cell::RefCell, collections::HashMap, path::PathBuf}; + +pub trait ImportResolver { + fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result; + fn load_file_contents(&self, resolved: &PathBuf) -> Result; +} + +pub struct DummyImportResolver; +impl ImportResolver for DummyImportResolver { + fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result { + create_error(Error::ImportNotSupported(from.clone(), path.clone())) + } + fn load_file_contents(&self, _resolved: &PathBuf) -> Result { + // Can be only caused by library direct consumer, not by supplied jsonnet + panic!("dummy resolver can't load any file") + } +} +impl Default for Box { + fn default() -> Self { + Box::new(DummyImportResolver) + } +} + +pub struct FileImportResolver { + pub library_paths: Vec, +} +impl ImportResolver for FileImportResolver { + fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result { + let mut new_path = from.clone(); + new_path.push(path); + if new_path.exists() { + Ok(new_path) + } else { + for library_path in self.library_paths.iter() { + let mut cloned = library_path.clone(); + cloned.push(path); + if cloned.exists() { + return Ok(cloned); + } + } + create_error(Error::ImportFileNotFound(from.clone(), path.clone())) + } + } + fn load_file_contents(&self, id: &PathBuf) -> Result { + let mut file = File::open(id).map_err(|_e| { + create_error::<()>(Error::ResolvedFileNotFound(id.clone())) + .err() + .unwrap() + })?; + let mut out = String::new(); + file.read_to_string(&mut out).map_err(|_e| { + create_error::<()>(Error::ImportBadFileUtf8(id.clone())) + .err() + .unwrap() + })?; + Ok(out) + } +} + +pub struct CachingImportResolver { + resolution_cache: RefCell>>, + loading_cache: RefCell>>, + inner: Box, +} +impl ImportResolver for CachingImportResolver { + fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result { + self.resolution_cache + .borrow_mut() + .entry((from.clone(), path.clone())) + .or_insert_with(|| self.inner.resolve_file(from, path)) + .clone() + } + fn load_file_contents(&self, resolved: &PathBuf) -> Result { + self.loading_cache + .borrow_mut() + .entry(resolved.clone()) + .or_insert_with(|| self.inner.load_file_contents(resolved)) + .clone() + } +} --- a/crates/jsonnet-evaluator/src/lib.rs +++ b/crates/jsonnet-evaluator/src/lib.rs @@ -8,6 +8,7 @@ mod error; mod evaluate; mod function; +mod import; mod map; mod obj; mod val; @@ -17,6 +18,7 @@ pub use error::*; pub use evaluate::*; pub use function::parse_function_call; +pub use import::*; use jsonnet_parser::*; pub use obj::*; use std::{cell::RefCell, collections::HashMap, fmt::Debug, path::PathBuf, rc::Rc}; @@ -46,16 +48,12 @@ pub struct EvaluationSettings { pub max_stack_frames: usize, pub max_stack_trace_size: usize, - pub import_resolver: Box String>, } impl Default for EvaluationSettings { fn default() -> Self { EvaluationSettings { max_stack_frames: 200, max_stack_trace_size: 20, - import_resolver: Box::new(|path| { - panic!("default EvaluationSettings have no support for import resolution, can't import {:?}", path) - }), } } } @@ -75,6 +73,7 @@ ext_vars: RefCell>, settings: EvaluationSettings, + import_resolver: Box, } thread_local! { @@ -101,9 +100,10 @@ #[derive(Default, Clone)] pub struct EvaluationState(Rc); impl EvaluationState { - pub fn new(settings: EvaluationSettings) -> Self { + pub fn new(settings: EvaluationSettings, import_resolver: Box) -> Self { EvaluationState(Rc::new(EvaluationStateInternals { settings, + import_resolver, ..Default::default() })) } @@ -171,19 +171,29 @@ } Ok(value) } - pub(crate) fn import_file(&self, path: &PathBuf) -> Result { - if !self.0.files.borrow().contains_key(path) { - let file_str = (self.0.settings.import_resolver)(path); - self.add_file(path.clone(), file_str).unwrap(); + pub(crate) fn import_file(&self, from: &PathBuf, path: &PathBuf) -> Result { + let file_path = self.0.import_resolver.resolve_file(from, path)?; + { + let files = self.0.files.borrow(); + if files.contains_key(&file_path) { + return self.evaluate_file(&file_path); + } } - self.evaluate_file_in_current_state(path) + let contents = self.0.import_resolver.load_file_contents(&file_path)?; + self.add_file(file_path.clone(), contents).map_err(|e| { + create_error::<()>(Error::ImportSyntaxError(e)) + .err() + .unwrap() + })?; + self.evaluate_file(&file_path) } - pub(crate) fn import_file_str(&self, path: &PathBuf) -> Result { - if !self.0.str_files.borrow().contains_key(path) { - let file_str = (self.0.settings.import_resolver)(path); + pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result { + let path = self.0.import_resolver.resolve_file(from, path)?; + if !self.0.str_files.borrow().contains_key(&path) { + let file_str = self.0.import_resolver.load_file_contents(&path)?; self.0.str_files.borrow_mut().insert(path.clone(), file_str); } - Ok(self.0.str_files.borrow().get(path).cloned().unwrap()) + Ok(self.0.str_files.borrow().get(&path).cloned().unwrap()) } pub fn parse_evaluate_raw(&self, code: &str) -> Result { -- gitstuff