git.delta.rocks / jrsonnet / refs/commits / 1dbd15d5102e

difftreelog

feat import support

Лач2020-06-10parent: #ff03a2a.patch.diff
in: master

3 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
before · cmds/jrsonnet/src/main.rs
1pub mod location;23use clap::Clap;4use jsonnet_evaluator::{EvaluationState, LocError, StackTrace, Val};5use location::{offset_to_location, CodeLocation};6use std::env::current_dir;7use std::{path::PathBuf, str::FromStr};89enum Format {10	None,11	Json,12	Yaml,13}1415impl FromStr for Format {16	type Err = &'static str;17	fn from_str(s: &str) -> Result<Self, Self::Err> {18		Ok(match s {19			"none" => Format::None,20			"json" => Format::Json,21			"yaml" => Format::Yaml,22			_ => return Err("no such format"),23		})24	}25}2627#[derive(PartialEq)]28enum TraceFormat {29	CppJsonnet,30	GoJsonnet,31	Custom,32}33impl FromStr for TraceFormat {34	type Err = &'static str;35	fn from_str(s: &str) -> Result<Self, Self::Err> {36		Ok(match s {37			"cpp" => TraceFormat::CppJsonnet,38			"go" => TraceFormat::GoJsonnet,39			"default" => TraceFormat::Custom,40			_ => return Err("no such format"),41		})42	}43}4445#[derive(Clone)]46struct ExtStr {47	name: String,48	value: String,49}50impl FromStr for ExtStr {51	type Err = &'static str;52	fn from_str(s: &str) -> Result<Self, Self::Err> {53		let out: Vec<_> = s.split('=').collect();54		match out.len() {55			1 => Ok(ExtStr {56				name: out[0].to_owned(),57				value: std::env::var(out[0]).or(Err("missing env var"))?,58			}),59			2 => Ok(ExtStr {60				name: out[0].to_owned(),61				value: out[1].to_owned(),62			}),63			_ => Err("bad ext-str syntax"),64		}65	}66}6768#[derive(Clap)]69#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]70struct Opts {71	#[clap(long, about = "Disable global std variable")]72	no_stdlib: bool,73	#[clap(long, about = "Add external string")]74	ext_str: Vec<ExtStr>,75	#[clap(long, about = "Add external string from code")]76	ext_code: Vec<ExtStr>,77	#[clap(long, about = "Add TLA")]78	tla_str: Vec<ExtStr>,79	#[clap(long, about = "Add TLA from code")]80	tla_code: Vec<ExtStr>,81	#[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]82	format: Format,83	#[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]84	trace_format: TraceFormat,8586	#[clap(87		long,88		short = "s",89		default_value = "200",90		about = "Number of allowed stack frames"91	)]92	max_stack: usize,93	#[clap(94		long,95		short = "t",96		default_value = "20",97		about = "Max length of stack trace before cropping"98	)]99	max_trace: usize,100101	#[clap(102		long,103		default_value = "3",104		about = "When using --format, this option specifies string to pad output with"105	)]106	line_padding: usize,107108	#[clap(about = "File to compile", index = 1)]109	input: String,110}111112fn main() {113	let opts: Opts = Opts::parse();114	let evaluator = jsonnet_evaluator::EvaluationState::default();115	if !opts.no_stdlib {116		evaluator.with_stdlib();117	}118	for ExtStr { name, value } in opts.ext_str.iter().cloned() {119		evaluator.add_ext_var(name, Val::Str(value));120	}121	for ExtStr { name, value } in opts.ext_code.iter().cloned() {122		evaluator.add_ext_var(name, evaluator.parse_evaluate_raw(&value).unwrap());123	}124	let mut input = current_dir().unwrap();125	input.push(opts.input.clone());126	let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();127	if let Err(e) = evaluator.add_file(input.clone(), code_string.clone()) {128		print_syntax_error(e, &input, &code_string);129		std::process::exit(1);130	}131	let result = evaluator.evaluate_file(&input);132	match result {133		Ok(v) => {134			let v = match opts.format {135				Format::Json => {136					if opts.no_stdlib {137						evaluator.with_stdlib();138					}139					evaluator.add_global("__tmp__to_json__".to_owned(), v);140					let v = evaluator.parse_evaluate_raw(&format!(141						"std.manifestJsonEx(__tmp__to_json__, \"{}\")",142						" ".repeat(opts.line_padding),143					));144					match v {145						Ok(v) => v,146						Err(err) => {147							print_error(&err, evaluator, &opts);148							std::process::exit(1);149						}150					}151				}152				Format::Yaml => {153					if opts.no_stdlib {154						evaluator.with_stdlib();155					}156					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);157					let v = evaluator158						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");159					match v {160						Ok(v) => v,161						Err(err) => {162							print_error(&err, evaluator, &opts);163							std::process::exit(1);164						}165					}166				}167				_ => v,168			};169			match v {170				Val::Str(s) => println!("{}", s),171				Val::Num(n) => println!("{}", n),172				_v => eprintln!(173					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"174				),175			}176		}177		Err(err) => {178			print_error(&err, evaluator, &opts);179			std::process::exit(1);180		}181	}182}183184fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {185	println!("Error: {:?}", err.0);186	print_trace(&(err.1), evaluator, &opts);187}188189fn print_syntax_error(error: jsonnet_parser::ParseError, file: &PathBuf, code: &str) {190	use annotate_snippets::{191		display_list::{DisplayList, FormatOptions},192		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},193	};194	//&("Expected: ".to_owned() + error.expected)195	let origin = file.to_str().unwrap();196	let error_message = format!("Expected: {}", error.expected);197	let snippet = Snippet {198		opt: FormatOptions {199			color: true,200			..Default::default()201		},202		title: Some(Annotation {203			label: Some(&error_message),204			id: None,205			annotation_type: AnnotationType::Error,206		}),207		footer: vec![],208		slices: vec![Slice {209			source: &code,210			line_start: 1,211			origin: Some(origin),212			fold: false,213			annotations: vec![SourceAnnotation {214				label: "At this position",215				annotation_type: AnnotationType::Error,216				range: (error.location.offset, error.location.offset + 1),217			}],218		}],219	};220221	let dl = DisplayList::from(snippet);222	println!("{}", dl);223}224225fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {226	use annotate_snippets::{227		display_list::{DisplayList, FormatOptions},228		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},229	};230	for item in trace.0.iter() {231		let desc = &item.1;232		if (item.0).1.is_none() {233			continue;234		}235		let source = (item.0).1.clone().unwrap();236		let code = evaluator.get_source(&source.0);237		if code.is_none() {238			continue;239		}240		let code = code.unwrap();241		let start_end = offset_to_location(&code, &[source.1, source.2]);242		if opts.trace_format == TraceFormat::Custom {243			let source_fragment: String = code244				.chars()245				.skip(start_end[0].line_start_offset)246				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)247				.collect();248			let snippet = Snippet {249				opt: FormatOptions {250					color: true,251					..Default::default()252				},253				title: Some(Annotation {254					label: Some(&item.1),255					id: None,256					annotation_type: AnnotationType::Error,257				}),258				footer: vec![],259				slices: vec![Slice {260					source: &source_fragment,261					line_start: start_end[0].line,262					origin: Some(&source.0.to_str().unwrap()),263					fold: false,264					annotations: vec![SourceAnnotation {265						label: desc,266						annotation_type: AnnotationType::Error,267						range: (268							source.1 - start_end[0].line_start_offset,269							source.2 - start_end[0].line_start_offset,270						),271					}],272				}],273			};274275			let dl = DisplayList::from(snippet);276			println!("{}", dl);277		} else {278			print_jsonnet_pair(279				source.0.to_str().unwrap(),280				&start_end[0],281				&start_end[1],282				opts.trace_format == TraceFormat::GoJsonnet,283			);284		}285	}286}287288fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {289	if is_go {290		print!("        ");291	} else {292		print!("  ");293	}294	print!("{}:", file);295	if start.line == end.line {296		// IDK why, but this is the behavior original jsonnet cpp impl shows297		if start.column == end.column || !is_go && start.column + 1 == end.column {298			println!("{}:{}", start.line, end.column)299		} else {300			println!("{}:{}-{}", start.line, start.column, end.column);301		}302	} else {303		println!(304			"({}:{})-({}:{})",305			start.line, end.column, start.line, end.column306		);307	}308}
after · cmds/jrsonnet/src/main.rs
1pub mod location;23use clap::Clap;4use jsonnet_evaluator::{EvaluationSettings, EvaluationState, LocError, StackTrace, Val};5use location::{offset_to_location, CodeLocation};6use std::env::current_dir;7use std::{path::PathBuf, str::FromStr};89enum Format {10	None,11	Json,12	Yaml,13}1415impl FromStr for Format {16	type Err = &'static str;17	fn from_str(s: &str) -> Result<Self, Self::Err> {18		Ok(match s {19			"none" => Format::None,20			"json" => Format::Json,21			"yaml" => Format::Yaml,22			_ => return Err("no such format"),23		})24	}25}2627#[derive(PartialEq)]28enum TraceFormat {29	CppJsonnet,30	GoJsonnet,31	Custom,32}33impl FromStr for TraceFormat {34	type Err = &'static str;35	fn from_str(s: &str) -> Result<Self, Self::Err> {36		Ok(match s {37			"cpp" => TraceFormat::CppJsonnet,38			"go" => TraceFormat::GoJsonnet,39			"default" => TraceFormat::Custom,40			_ => return Err("no such format"),41		})42	}43}4445#[derive(Clone)]46struct ExtStr {47	name: String,48	value: String,49}50impl FromStr for ExtStr {51	type Err = &'static str;52	fn from_str(s: &str) -> Result<Self, Self::Err> {53		let out: Vec<_> = s.split('=').collect();54		match out.len() {55			1 => Ok(ExtStr {56				name: out[0].to_owned(),57				value: std::env::var(out[0]).or(Err("missing env var"))?,58			}),59			2 => Ok(ExtStr {60				name: out[0].to_owned(),61				value: out[1].to_owned(),62			}),63			_ => Err("bad ext-str syntax"),64		}65	}66}6768#[derive(Clap)]69#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]70struct Opts {71	#[clap(long, about = "Disable global std variable")]72	no_stdlib: bool,73	#[clap(long, about = "Add external string")]74	ext_str: Vec<ExtStr>,75	#[clap(long, about = "Add external string from code")]76	ext_code: Vec<ExtStr>,77	#[clap(long, about = "Add TLA")]78	tla_str: Vec<ExtStr>,79	#[clap(long, about = "Add TLA from code")]80	tla_code: Vec<ExtStr>,81	#[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]82	format: Format,83	#[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]84	trace_format: TraceFormat,8586	#[clap(87		long,88		short = "s",89		default_value = "200",90		about = "Number of allowed stack frames"91	)]92	max_stack: usize,93	#[clap(94		long,95		short = "t",96		default_value = "20",97		about = "Max length of stack trace before cropping"98	)]99	max_trace: usize,100101	#[clap(102		long,103		default_value = "3",104		about = "When using --format, this option specifies string to pad output with"105	)]106	line_padding: usize,107108	#[clap(about = "File to compile", index = 1)]109	input: String,110}111112fn main() {113	let opts: Opts = Opts::parse();114	let evaluator = jsonnet_evaluator::EvaluationState::new(EvaluationSettings {115		import_resolver: Box::new(|path| String::from_utf8(std::fs::read(path).unwrap()).unwrap()),116		..Default::default()117	});118	if !opts.no_stdlib {119		evaluator.with_stdlib();120	}121	for ExtStr { name, value } in opts.ext_str.iter().cloned() {122		evaluator.add_ext_var(name, Val::Str(value));123	}124	for ExtStr { name, value } in opts.ext_code.iter().cloned() {125		evaluator.add_ext_var(name, evaluator.parse_evaluate_raw(&value).unwrap());126	}127	let mut input = current_dir().unwrap();128	input.push(opts.input.clone());129	let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();130	if let Err(e) = evaluator.add_file(input.clone(), code_string.clone()) {131		print_syntax_error(e, &input, &code_string);132		std::process::exit(1);133	}134	let result = evaluator.evaluate_file(&input);135	match result {136		Ok(v) => {137			let v = match opts.format {138				Format::Json => {139					if opts.no_stdlib {140						evaluator.with_stdlib();141					}142					evaluator.add_global("__tmp__to_json__".to_owned(), v);143					let v = evaluator.parse_evaluate_raw(&format!(144						"std.manifestJsonEx(__tmp__to_json__, \"{}\")",145						" ".repeat(opts.line_padding),146					));147					match v {148						Ok(v) => v,149						Err(err) => {150							print_error(&err, evaluator, &opts);151							std::process::exit(1);152						}153					}154				}155				Format::Yaml => {156					if opts.no_stdlib {157						evaluator.with_stdlib();158					}159					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);160					let v = evaluator161						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");162					match v {163						Ok(v) => v,164						Err(err) => {165							print_error(&err, evaluator, &opts);166							std::process::exit(1);167						}168					}169				}170				_ => v,171			};172			match v {173				Val::Str(s) => println!("{}", s),174				Val::Num(n) => println!("{}", n),175				_v => eprintln!(176					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"177				),178			}179		}180		Err(err) => {181			print_error(&err, evaluator, &opts);182			std::process::exit(1);183		}184	}185}186187fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {188	println!("Error: {:?}", err.0);189	print_trace(&(err.1), evaluator, &opts);190}191192fn print_syntax_error(error: jsonnet_parser::ParseError, file: &PathBuf, code: &str) {193	use annotate_snippets::{194		display_list::{DisplayList, FormatOptions},195		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},196	};197	//&("Expected: ".to_owned() + error.expected)198	let origin = file.to_str().unwrap();199	let error_message = format!("Expected: {}", error.expected);200	let snippet = Snippet {201		opt: FormatOptions {202			color: true,203			..Default::default()204		},205		title: Some(Annotation {206			label: Some(&error_message),207			id: None,208			annotation_type: AnnotationType::Error,209		}),210		footer: vec![],211		slices: vec![Slice {212			source: &code,213			line_start: 1,214			origin: Some(origin),215			fold: false,216			annotations: vec![SourceAnnotation {217				label: "At this position",218				annotation_type: AnnotationType::Error,219				range: (error.location.offset, error.location.offset + 1),220			}],221		}],222	};223224	let dl = DisplayList::from(snippet);225	println!("{}", dl);226}227228fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {229	use annotate_snippets::{230		display_list::{DisplayList, FormatOptions},231		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},232	};233	for item in trace.0.iter() {234		let desc = &item.1;235		if (item.0).1.is_none() {236			continue;237		}238		let source = (item.0).1.clone().unwrap();239		let code = evaluator.get_source(&source.0);240		if code.is_none() {241			continue;242		}243		let code = code.unwrap();244		let start_end = offset_to_location(&code, &[source.1, source.2]);245		if opts.trace_format == TraceFormat::Custom {246			let source_fragment: String = code247				.chars()248				.skip(start_end[0].line_start_offset)249				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)250				.collect();251			let snippet = Snippet {252				opt: FormatOptions {253					color: true,254					..Default::default()255				},256				title: Some(Annotation {257					label: Some(&item.1),258					id: None,259					annotation_type: AnnotationType::Error,260				}),261				footer: vec![],262				slices: vec![Slice {263					source: &source_fragment,264					line_start: start_end[0].line,265					origin: Some(&source.0.to_str().unwrap()),266					fold: false,267					annotations: vec![SourceAnnotation {268						label: desc,269						annotation_type: AnnotationType::Error,270						range: (271							source.1 - start_end[0].line_start_offset,272							source.2 - start_end[0].line_start_offset,273						),274					}],275				}],276			};277278			let dl = DisplayList::from(snippet);279			println!("{}", dl);280		} else {281			print_jsonnet_pair(282				source.0.to_str().unwrap(),283				&start_end[0],284				&start_end[1],285				opts.trace_format == TraceFormat::GoJsonnet,286			);287		}288	}289}290291fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {292	if is_go {293		print!("        ");294	} else {295		print!("  ");296	}297	print!("{}:", file);298	if start.line == end.line {299		// IDK why, but this is the behavior original jsonnet cpp impl shows300		if start.column == end.column || !is_go && start.column + 1 == end.column {301			println!("{}:{}", start.line, end.column)302		} else {303			println!("{}:{}-{}", start.line, start.column, end.column);304		}305	} else {306		println!(307			"({}:{})-({}:{})",308			start.line, end.column, start.line, end.column309		);310	}311}
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -1,5 +1,5 @@
 use crate::{
-	binding, context_creator, create_error, future_wrapper, lazy_val, push, Context,
+	binding, context_creator, create_error, future_wrapper, lazy_val, push, with_state, Context,
 	ContextCreator, FuncDesc, LazyBinding, ObjMember, ObjValue, Result, Val,
 };
 use closure::closure;
@@ -607,6 +607,16 @@
 				}
 			}
 		}
+		Import(path) => {
+			let mut lib_path = 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))?
+		}
 		_ => panic!(
 			"evaluation not implemented: {:?}",
 			LocExpr(expr.clone(), loc.clone())
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -50,14 +50,18 @@
 }
 
 pub struct EvaluationSettings {
-	max_stack_frames: usize,
-	max_stack_trace_size: usize,
+	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)
+			}),
 		}
 	}
 }
@@ -102,6 +106,12 @@
 #[derive(Default, Clone)]
 pub struct EvaluationState(Rc<EvaluationStateInternals>);
 impl EvaluationState {
+	pub fn new(settings: EvaluationSettings) -> Self {
+		EvaluationState(Rc::new(EvaluationStateInternals {
+			settings,
+			..Default::default()
+		}))
+	}
 	pub fn add_file(&self, name: PathBuf, code: String) -> std::result::Result<(), ParseError> {
 		self.0.files.borrow_mut().insert(
 			name.clone(),
@@ -139,6 +149,11 @@
 	}
 	pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {
 		self.begin_state();
+		let value = self.evaluate_file_in_current_state(name)?;
+		self.end_state();
+		Ok(value)
+	}
+	pub(crate) fn evaluate_file_in_current_state(&self, name: &PathBuf) -> Result<Val> {
 		let expr: LocExpr = {
 			let ro_map = self.0.files.borrow();
 			let value = ro_map
@@ -159,9 +174,15 @@
 				.2
 				.replace(value.clone());
 		}
-		self.end_state();
 		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();
+		}
+		self.evaluate_file_in_current_state(path)
+	}
 
 	pub fn parse_evaluate_raw(&self, code: &str) -> Result<Val> {
 		let parsed = parse(