git.delta.rocks / jrsonnet / refs/commits / ff03a2a1eb62

difftreelog

feat std.extVar support

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

4 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(Clap)]46#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]47struct Opts {48	#[clap(long, about = "Disable global std variable")]49	no_stdlib: bool,50	#[clap(long, about = "Add external string")]51	ext_str: Option<Vec<String>>,52	#[clap(long, about = "Add external string from code")]53	ext_code: Option<Vec<String>>,54	#[clap(long, about = "Add TLA")]55	tla_str: Option<Vec<String>>,56	#[clap(long, about = "Add TLA from code")]57	tla_code: Option<Vec<String>>,58	#[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]59	format: Format,60	#[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]61	trace_format: TraceFormat,6263	#[clap(64		long,65		short = "s",66		default_value = "200",67		about = "Number of allowed stack frames"68	)]69	max_stack: usize,70	#[clap(71		long,72		short = "t",73		default_value = "20",74		about = "Max length of stack trace before cropping"75	)]76	max_trace: usize,7778	#[clap(79		long,80		default_value = "3",81		about = "When using --format, this option specifies string to pad output with"82	)]83	line_padding: usize,8485	#[clap(about = "File to compile", index = 1)]86	input: String,87}8889fn main() {90	let opts: Opts = Opts::parse();91	let evaluator = jsonnet_evaluator::EvaluationState::default();92	if !opts.no_stdlib {93		evaluator.with_stdlib();94	}95	let mut input = current_dir().unwrap();96	input.push(opts.input.clone());97	let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();98	if let Err(e) = evaluator.add_file(input.clone(), code_string.clone()) {99		print_syntax_error(e, &input, &code_string);100		std::process::exit(1);101	}102	let result = evaluator.evaluate_file(&input);103	match result {104		Ok(v) => {105			let v = match opts.format {106				Format::Json => {107					if opts.no_stdlib {108						evaluator.with_stdlib();109					}110					evaluator.add_global("__tmp__to_json__".to_owned(), v);111					let v = evaluator.parse_evaluate_raw(&format!(112						"std.manifestJsonEx(__tmp__to_json__, \"{}\")",113						" ".repeat(opts.line_padding),114					));115					match v {116						Ok(v) => v,117						Err(err) => {118							print_error(&err, evaluator, &opts);119							std::process::exit(1);120						}121					}122				}123				Format::Yaml => {124					if opts.no_stdlib {125						evaluator.with_stdlib();126					}127					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);128					let v = evaluator129						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");130					match v {131						Ok(v) => v,132						Err(err) => {133							print_error(&err, evaluator, &opts);134							std::process::exit(1);135						}136					}137				}138				_ => v,139			};140			match v {141				Val::Str(s) => println!("{}", s),142				Val::Num(n) => println!("{}", n),143				_v => eprintln!(144					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"145				),146			}147		}148		Err(err) => {149			print_error(&err, evaluator, &opts);150			std::process::exit(1);151		}152	}153}154155fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {156	println!("Error: {:?}", err.0);157	print_trace(&(err.1), evaluator, &opts);158}159160fn print_syntax_error(error: jsonnet_parser::ParseError, file: &PathBuf, code: &str) {161	use annotate_snippets::{162		display_list::{DisplayList, FormatOptions},163		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},164	};165	//&("Expected: ".to_owned() + error.expected)166	let origin = file.to_str().unwrap();167	let error_message = format!("Expected: {}", error.expected);168	let snippet = Snippet {169		opt: FormatOptions {170			color: true,171			..Default::default()172		},173		title: Some(Annotation {174			label: Some(&error_message),175			id: None,176			annotation_type: AnnotationType::Error,177		}),178		footer: vec![],179		slices: vec![Slice {180			source: &code,181			line_start: 1,182			origin: Some(origin),183			fold: false,184			annotations: vec![SourceAnnotation {185				label: "At this position",186				annotation_type: AnnotationType::Error,187				range: (error.location.offset, error.location.offset + 1),188			}],189		}],190	};191192	let dl = DisplayList::from(snippet);193	println!("{}", dl);194}195196fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {197	use annotate_snippets::{198		display_list::{DisplayList, FormatOptions},199		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},200	};201	for item in trace.0.iter() {202		let desc = &item.1;203		if (item.0).1.is_none() {204			continue;205		}206		let source = (item.0).1.clone().unwrap();207		let code = evaluator.get_source(&source.0);208		if code.is_none() {209			continue;210		}211		let code = code.unwrap();212		let start_end = offset_to_location(&code, &[source.1, source.2]);213		if opts.trace_format == TraceFormat::Custom {214			let source_fragment: String = code215				.chars()216				.skip(start_end[0].line_start_offset)217				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)218				.collect();219			let snippet = Snippet {220				opt: FormatOptions {221					color: true,222					..Default::default()223				},224				title: Some(Annotation {225					label: Some(&item.1),226					id: None,227					annotation_type: AnnotationType::Error,228				}),229				footer: vec![],230				slices: vec![Slice {231					source: &source_fragment,232					line_start: start_end[0].line,233					origin: Some(&source.0.to_str().unwrap()),234					fold: false,235					annotations: vec![SourceAnnotation {236						label: desc,237						annotation_type: AnnotationType::Error,238						range: (239							source.1 - start_end[0].line_start_offset,240							source.2 - start_end[0].line_start_offset,241						),242					}],243				}],244			};245246			let dl = DisplayList::from(snippet);247			println!("{}", dl);248		} else {249			print_jsonnet_pair(250				source.0.to_str().unwrap(),251				&start_end[0],252				&start_end[1],253				opts.trace_format == TraceFormat::GoJsonnet,254			);255		}256	}257}258259fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {260	if is_go {261		print!("        ");262	} else {263		print!("  ");264	}265	print!("{}:", file);266	if start.line == end.line {267		// IDK why, but this is the behavior original jsonnet cpp impl shows268		if start.column == end.column || !is_go && start.column + 1 == end.column {269			println!("{}:{}", start.line, end.column)270		} else {271			println!("{}:{}-{}", start.line, start.column, end.column);272		}273	} else {274		println!(275			"({}:{})-({}:{})",276			start.line, end.column, start.line, end.column277		);278	}279}
after · 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}
modifiedcrates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/error.rs
+++ b/crates/jsonnet-evaluator/src/error.rs
@@ -12,6 +12,8 @@
 	TooManyArgsFunctionHas(usize),
 	FunctionParameterNotBoundInCall(String),
 
+	UndefinedExternalVariable(String),
+
 	RuntimeError(String),
 	StackOverflow,
 	FractionalIndex,
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -533,6 +533,20 @@
 							panic!("bad pow call");
 						}
 					}
+					("std", "extVar") => {
+						assert_eq!(args.len(), 1);
+						if let Val::Str(a) = evaluate(context, &args[0].1)? {
+							with_state(|s| s.0.ext_vars.borrow().get(&a).cloned()).ok_or_else(
+								|| {
+									create_error::<()>(crate::Error::UndefinedExternalVariable(a))
+										.err()
+										.unwrap()
+								},
+							)?
+						} else {
+							panic!("bad extVar call");
+						}
+					}
 					(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
 				},
 				Val::Func(f) => {
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -72,6 +72,9 @@
 	files: RefCell<HashMap<PathBuf, FileData>>,
 	globals: RefCell<HashMap<String, Val>>,
 
+	/// Values to use with std.extVar
+	ext_vars: RefCell<HashMap<String, Val>>,
+
 	settings: EvaluationSettings,
 }
 
@@ -177,6 +180,9 @@
 	pub fn add_global(&self, name: String, value: Val) {
 		self.0.globals.borrow_mut().insert(name, value);
 	}
+	pub fn add_ext_var(&self, name: String, value: Val) {
+		self.0.ext_vars.borrow_mut().insert(name, value);
+	}
 
 	pub fn with_stdlib(&self) -> &Self {
 		self.begin_state();