git.delta.rocks / jrsonnet / refs/commits / 30d0357ae3c2

difftreelog

feat library paths

Лач2020-06-25parent: #d8c2588.patch.diff
in: master

5 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
99 )]99 )]
100 max_trace: usize,100 max_trace: usize,
101
102 #[clap(long, short = "J", about = "Library search dir")]
103 jpath: Vec<PathBuf>,
101104
102 #[clap(105 #[clap(
103 long,106 long,
112115
113fn main() {116fn main() {
114 let opts: Opts = Opts::parse();117 let opts: Opts = Opts::parse();
115 let evaluator = jsonnet_evaluator::EvaluationState::new(EvaluationSettings {118 let evaluator = jsonnet_evaluator::EvaluationState::new(
119 EvaluationSettings {
120 max_stack_trace_size: opts.max_trace,
121 max_stack_frames: opts.max_stack,
122 },
116 import_resolver: Box::new(|path| String::from_utf8(std::fs::read(path).unwrap()).unwrap()),123 Box::new(jsonnet_evaluator::FileImportResolver {
117 ..Default::default()124 library_paths: opts.jpath.clone(),
125 }),
118 });126 );
119 if !opts.no_stdlib {127 if !opts.no_stdlib {
120 evaluator.with_stdlib();128 evaluator.with_stdlib();
121 }129 }
modifiedcrates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- 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,
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- 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),
 	})
addedcrates/jsonnet-evaluator/src/import.rsdiffbeforeafterboth
--- /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<PathBuf>;
+	fn load_file_contents(&self, resolved: &PathBuf) -> Result<String>;
+}
+
+pub struct DummyImportResolver;
+impl ImportResolver for DummyImportResolver {
+	fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<PathBuf> {
+		create_error(Error::ImportNotSupported(from.clone(), path.clone()))
+	}
+	fn load_file_contents(&self, _resolved: &PathBuf) -> Result<String> {
+		// Can be only caused by library direct consumer, not by supplied jsonnet
+		panic!("dummy resolver can't load any file")
+	}
+}
+impl Default for Box<dyn ImportResolver> {
+	fn default() -> Self {
+		Box::new(DummyImportResolver)
+	}
+}
+
+pub struct FileImportResolver {
+	pub library_paths: Vec<PathBuf>,
+}
+impl ImportResolver for FileImportResolver {
+	fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<PathBuf> {
+		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<String> {
+		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<HashMap<(PathBuf, PathBuf), Result<PathBuf>>>,
+	loading_cache: RefCell<HashMap<PathBuf, Result<String>>>,
+	inner: Box<dyn ImportResolver>,
+}
+impl ImportResolver for CachingImportResolver {
+	fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<PathBuf> {
+		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<String> {
+		self.loading_cache
+			.borrow_mut()
+			.entry(resolved.clone())
+			.or_insert_with(|| self.inner.load_file_contents(resolved))
+			.clone()
+	}
+}
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- 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<dyn Fn(&PathBuf) -> 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<HashMap<String, Val>>,
 
 	settings: EvaluationSettings,
+	import_resolver: Box<dyn ImportResolver>,
 }
 
 thread_local! {
@@ -101,9 +100,10 @@
 #[derive(Default, Clone)]
 pub struct EvaluationState(Rc<EvaluationStateInternals>);
 impl EvaluationState {
-	pub fn new(settings: EvaluationSettings) -> Self {
+	pub fn new(settings: EvaluationSettings, import_resolver: Box<dyn ImportResolver>) -> 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<Val> {
-		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<Val> {
+		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<String> {
-		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<String> {
+		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<Val> {