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
before · cmds/jrsonnet/src/main.rs
1pub mod location;23use clap::Clap;4use jsonnet_evaluator::{EvaluationSettings, EvaluationState, LocError, StackTrace, Val};5use jsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};6use location::{offset_to_location, CodeLocation};7use std::env::current_dir;8use std::{collections::HashMap, path::PathBuf, str::FromStr};910enum Format {11	None,12	Json,13	Yaml,14}1516impl FromStr for Format {17	type Err = &'static str;18	fn from_str(s: &str) -> Result<Self, Self::Err> {19		Ok(match s {20			"none" => Format::None,21			"json" => Format::Json,22			"yaml" => Format::Yaml,23			_ => return Err("no such format"),24		})25	}26}2728#[derive(PartialEq)]29enum TraceFormat {30	CppJsonnet,31	GoJsonnet,32	Custom,33}34impl FromStr for TraceFormat {35	type Err = &'static str;36	fn from_str(s: &str) -> Result<Self, Self::Err> {37		Ok(match s {38			"cpp" => TraceFormat::CppJsonnet,39			"go" => TraceFormat::GoJsonnet,40			"default" => TraceFormat::Custom,41			_ => return Err("no such format"),42		})43	}44}4546#[derive(Clone)]47struct ExtStr {48	name: String,49	value: String,50}51impl FromStr for ExtStr {52	type Err = &'static str;53	fn from_str(s: &str) -> Result<Self, Self::Err> {54		let out: Vec<_> = s.split('=').collect();55		match out.len() {56			1 => Ok(ExtStr {57				name: out[0].to_owned(),58				value: std::env::var(out[0]).or(Err("missing env var"))?,59			}),60			2 => Ok(ExtStr {61				name: out[0].to_owned(),62				value: out[1].to_owned(),63			}),64			_ => Err("bad ext-str syntax"),65		}66	}67}6869#[derive(Clap)]70#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]71struct Opts {72	#[clap(long, about = "Disable global std variable")]73	no_stdlib: bool,74	#[clap(long, about = "Add external string")]75	ext_str: Vec<ExtStr>,76	#[clap(long, about = "Add external string from code")]77	ext_code: Vec<ExtStr>,78	#[clap(long, about = "Add TLA")]79	tla_str: Vec<ExtStr>,80	#[clap(long, about = "Add TLA from code")]81	tla_code: Vec<ExtStr>,82	#[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]83	format: Format,84	#[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]85	trace_format: TraceFormat,8687	#[clap(88		long,89		short = "s",90		default_value = "200",91		about = "Number of allowed stack frames"92	)]93	max_stack: usize,94	#[clap(95		long,96		short = "t",97		default_value = "20",98		about = "Max length of stack trace before cropping"99	)]100	max_trace: usize,101102	#[clap(103		long,104		default_value = "3",105		about = "When using --format, this option specifies string to pad output with"106	)]107	line_padding: usize,108109	#[clap(about = "File to compile", index = 1)]110	input: String,111}112113fn main() {114	let opts: Opts = Opts::parse();115	let evaluator = jsonnet_evaluator::EvaluationState::new(EvaluationSettings {116		import_resolver: Box::new(|path| String::from_utf8(std::fs::read(path).unwrap()).unwrap()),117		..Default::default()118	});119	if !opts.no_stdlib {120		evaluator.with_stdlib();121	}122	for ExtStr { name, value } in opts.ext_str.iter().cloned() {123		evaluator.add_ext_var(name, Val::Str(value));124	}125	for ExtStr { name, value } in opts.ext_code.iter().cloned() {126		evaluator.add_ext_var(name, evaluator.parse_evaluate_raw(&value).unwrap());127	}128	let mut input = current_dir().unwrap();129	input.push(opts.input.clone());130	let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();131	if let Err(e) = evaluator.add_file(input.clone(), code_string.clone()) {132		print_syntax_error(e, &input, &code_string);133		std::process::exit(1);134	}135	let result = evaluator.evaluate_file(&input);136	match result {137		Ok(v) => {138			let v = match v {139				Val::Func(f) => {140					let mut desc_map = HashMap::new();141					for ExtStr { name, value } in opts.tla_str.iter().cloned() {142						desc_map.insert(name, el!(Expr::Str(value)));143					}144					for ExtStr { name, value } in opts.tla_code.iter().cloned() {145						desc_map.insert(146							name,147							jsonnet_parser::parse(148								&value,149								&ParserSettings {150									file_name: PathBuf::new(),151									loc_data: false,152								},153							)154							.unwrap(),155						);156					}157					evaluator.add_global("__tmp__tlf__".to_owned(), Val::Func(f));158					evaluator159						.evaluate_raw(el!(Expr::Apply(160							el!(Expr::Var("__tmp__tlf__".to_owned())),161							ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),162							false,163						)))164						.unwrap()165				}166				v => v,167			};168			let v = match opts.format {169				Format::Json => {170					if opts.no_stdlib {171						evaluator.with_stdlib();172					}173					evaluator.add_global("__tmp__to_json__".to_owned(), v);174					let v = evaluator.parse_evaluate_raw(&format!(175						"std.manifestJsonEx(__tmp__to_json__, \"{}\")",176						" ".repeat(opts.line_padding),177					));178					match v {179						Ok(v) => v,180						Err(err) => {181							print_error(&err, evaluator, &opts);182							std::process::exit(1);183						}184					}185				}186				Format::Yaml => {187					if opts.no_stdlib {188						evaluator.with_stdlib();189					}190					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);191					let v = evaluator192						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");193					match v {194						Ok(v) => v,195						Err(err) => {196							print_error(&err, evaluator, &opts);197							std::process::exit(1);198						}199					}200				}201				_ => v,202			};203			match v {204				Val::Str(s) => println!("{}", s),205				Val::Num(n) => println!("{}", n),206				_v => eprintln!(207					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"208				),209			}210		}211		Err(err) => {212			print_error(&err, evaluator, &opts);213			std::process::exit(1);214		}215	}216}217218fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {219	println!("Error: {:?}", err.0);220	print_trace(&(err.1), evaluator, &opts);221}222223fn print_syntax_error(error: jsonnet_parser::ParseError, file: &PathBuf, code: &str) {224	use annotate_snippets::{225		display_list::{DisplayList, FormatOptions},226		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},227	};228	//&("Expected: ".to_owned() + error.expected)229	let origin = file.to_str().unwrap();230	let error_message = format!("Expected: {}", error.expected);231	let snippet = Snippet {232		opt: FormatOptions {233			color: true,234			..Default::default()235		},236		title: Some(Annotation {237			label: Some(&error_message),238			id: None,239			annotation_type: AnnotationType::Error,240		}),241		footer: vec![],242		slices: vec![Slice {243			source: &code,244			line_start: 1,245			origin: Some(origin),246			fold: false,247			annotations: vec![SourceAnnotation {248				label: "At this position",249				annotation_type: AnnotationType::Error,250				range: (error.location.offset, error.location.offset + 1),251			}],252		}],253	};254255	let dl = DisplayList::from(snippet);256	println!("{}", dl);257}258259fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {260	use annotate_snippets::{261		display_list::{DisplayList, FormatOptions},262		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},263	};264	for item in trace.0.iter() {265		let desc = &item.1;266		if (item.0).1.is_none() {267			continue;268		}269		let source = (item.0).1.clone().unwrap();270		let code = evaluator.get_source(&source.0);271		if code.is_none() {272			continue;273		}274		let code = code.unwrap();275		let start_end = offset_to_location(&code, &[source.1, source.2]);276		if opts.trace_format == TraceFormat::Custom {277			let source_fragment: String = code278				.chars()279				.skip(start_end[0].line_start_offset)280				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)281				.collect();282			let snippet = Snippet {283				opt: FormatOptions {284					color: true,285					..Default::default()286				},287				title: Some(Annotation {288					label: Some(&item.1),289					id: None,290					annotation_type: AnnotationType::Error,291				}),292				footer: vec![],293				slices: vec![Slice {294					source: &source_fragment,295					line_start: start_end[0].line,296					origin: Some(&source.0.to_str().unwrap()),297					fold: false,298					annotations: vec![SourceAnnotation {299						label: desc,300						annotation_type: AnnotationType::Error,301						range: (302							source.1 - start_end[0].line_start_offset,303							source.2 - start_end[0].line_start_offset,304						),305					}],306				}],307			};308309			let dl = DisplayList::from(snippet);310			println!("{}", dl);311		} else {312			print_jsonnet_pair(313				source.0.to_str().unwrap(),314				&start_end[0],315				&start_end[1],316				opts.trace_format == TraceFormat::GoJsonnet,317			);318		}319	}320}321322fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {323	if is_go {324		print!("        ");325	} else {326		print!("  ");327	}328	print!("{}:", file);329	if start.line == end.line {330		// IDK why, but this is the behavior original jsonnet cpp impl shows331		if start.column == end.column || !is_go && start.column + 1 == end.column {332			println!("{}:{}", start.line, end.column)333		} else {334			println!("{}:{}-{}", start.line, start.column, end.column);335		}336	} else {337		println!(338			"({}:{})-({}:{})",339			start.line, end.column, start.line, end.column340		);341	}342}
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> {