git.delta.rocks / jrsonnet / refs/commits / 230dbe5dc16b

difftreelog

feat make CLI compatible with original jsonnet

Лач2020-07-01parent: #e0c62df.patch.diff
in: master

1 file changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
before · cmds/jrsonnet/src/main.rs
1use clap::Clap;2use jrsonnet_evaluator::{trace::CodeLocation, EvaluationState, LocError, StackTrace, Val};3use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};4use std::env::current_dir;5use std::{collections::HashMap, path::PathBuf, rc::Rc, str::FromStr};67#[global_allocator]8static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;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(long, short = "J", about = "Library search dir")]103	jpath: Vec<PathBuf>,104105	#[clap(106		long,107		default_value = "3",108		about = "When using --format, this option specifies string to pad output with"109	)]110	line_padding: usize,111112	#[clap(about = "File to compile", index = 1)]113	input: String,114}115116fn main() {117	let opts: Opts = Opts::parse();118	let evaluator = jrsonnet_evaluator::EvaluationState::default();119	evaluator.set_max_trace(opts.max_trace);120	evaluator.set_max_stack(opts.max_stack);121	evaluator.set_import_resolver(Box::new(jrsonnet_evaluator::FileImportResolver {122		library_paths: opts.jpath.clone(),123	}));124	if !opts.no_stdlib {125		evaluator.with_stdlib();126	}127	for ExtStr { name, value } in opts.ext_str.iter().cloned() {128		evaluator.add_ext_var(name.into(), Val::Str(value.into()));129	}130	for ExtStr { name, value } in opts.ext_code.iter().cloned() {131		evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());132	}133	let mut input = current_dir().unwrap();134	input.push(opts.input.clone());135	let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();136	if let Err(e) = evaluator.add_file(Rc::new(input.clone()), code_string.clone().into()) {137		print_syntax_error(e, &input, &code_string);138		std::process::exit(1);139	}140	let result = evaluator.evaluate_file(&input);141	match result {142		Ok(v) => {143			let v = match v {144				Val::Func(f) => {145					let mut desc_map = HashMap::new();146					for ExtStr { name, value } in opts.tla_str.iter().cloned() {147						desc_map.insert(name, el!(Expr::Str(value.into())));148					}149					for ExtStr { name, value } in opts.tla_code.iter().cloned() {150						desc_map.insert(151							name,152							jrsonnet_parser::parse(153								&value,154								&ParserSettings {155									file_name: Rc::new(PathBuf::new()),156									loc_data: false,157								},158							)159							.unwrap(),160						);161					}162					evaluator.add_global("__tmp__tlf__".into(), Val::Func(f));163					evaluator164						.evaluate_raw(el!(Expr::Apply(165							el!(Expr::Var("__tmp__tlf__".into())),166							ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),167							false,168						)))169						.unwrap()170				}171				v => v,172			};173			let v = evaluator.run_in_state(|| match opts.format {174				Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),175				Format::Yaml => {176					evaluator.add_global("__tmp__to_yaml__".into(), v);177					evaluator.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")")178				}179				_ => Ok(v),180			});181			let v = match v {182				Ok(v) => v,183				Err(err) => {184					print_error(&err, evaluator, &opts);185					std::process::exit(1);186				}187			};188			match v {189				Val::Str(s) => println!("{}", s),190				Val::Num(n) => println!("{}", n),191				_v => eprintln!(192					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"193				),194			}195		}196		Err(err) => {197			print_error(&err, evaluator, &opts);198			std::process::exit(1);199		}200	}201}202203fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {204	println!("Error: {:?}", err.0);205	print_trace(&(err.1), evaluator, &opts);206}207208fn print_syntax_error(error: jrsonnet_parser::ParseError, file: &PathBuf, code: &str) {209	use annotate_snippets::{210		display_list::{DisplayList, FormatOptions},211		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},212	};213	//&("Expected: ".to_owned() + error.expected)214	let origin = file.to_str().unwrap();215	let error_message = format!("Expected: {}", error.expected);216	let snippet = Snippet {217		opt: FormatOptions {218			color: true,219			..Default::default()220		},221		title: Some(Annotation {222			label: Some(&error_message),223			id: None,224			annotation_type: AnnotationType::Error,225		}),226		footer: vec![],227		slices: vec![Slice {228			source: &code,229			line_start: 1,230			origin: Some(origin),231			fold: false,232			annotations: vec![SourceAnnotation {233				label: "At this position",234				annotation_type: AnnotationType::Error,235				range: (error.location.offset, error.location.offset + 1),236			}],237		}],238	};239240	let dl = DisplayList::from(snippet);241	println!("{}", dl);242}243244fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {245	use annotate_snippets::{246		display_list::{DisplayList, FormatOptions},247		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},248	};249	for item in trace.0.iter() {250		let desc = &item.1;251		let source = item.0.clone();252		let start_end = evaluator.map_source_locations(&source.0, &[source.1, source.2]);253		if opts.trace_format == TraceFormat::Custom {254			let source_fragment: String = evaluator255				.get_source(&source.0)256				.unwrap()257				.chars()258				.skip(start_end[0].line_start_offset)259				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)260				.collect();261			let snippet = Snippet {262				opt: FormatOptions {263					color: true,264					..Default::default()265				},266				title: Some(Annotation {267					label: Some(&item.1),268					id: None,269					annotation_type: AnnotationType::Error,270				}),271				footer: vec![],272				slices: vec![Slice {273					source: &source_fragment,274					line_start: start_end[0].line,275					origin: Some(&source.0.to_str().unwrap()),276					fold: false,277					annotations: vec![SourceAnnotation {278						label: desc,279						annotation_type: AnnotationType::Error,280						range: (281							source.1 - start_end[0].line_start_offset,282							source.2 - start_end[0].line_start_offset,283						),284					}],285				}],286			};287288			let dl = DisplayList::from(snippet);289			println!("{}", dl);290		} else {291			print_jsonnet_pair(292				source.0.to_str().unwrap(),293				&start_end[0],294				&start_end[1],295				opts.trace_format == TraceFormat::GoJsonnet,296			);297		}298	}299}300301fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {302	if is_go {303		print!("        ");304	} else {305		print!("  ");306	}307	print!("{}:", file);308	if start.line == end.line {309		// IDK why, but this is the behavior original jsonnet cpp impl shows310		if start.column == end.column || !is_go && start.column + 1 == end.column {311			println!("{}:{}", start.line, end.column)312		} else {313			println!("{}:{}-{}", start.line, start.column, end.column);314		}315	} else {316		println!(317			"({}:{})-({}:{})",318			start.line, end.column, start.line, end.column319		);320	}321}
after · cmds/jrsonnet/src/main.rs
1use clap::Clap;2use jrsonnet_evaluator::{trace::CodeLocation, EvaluationState, LocError, StackTrace, Val};3use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};4use std::env::current_dir;5use std::{collections::HashMap, path::PathBuf, rc::Rc, str::FromStr};67#[global_allocator]8static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;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", number_of_values = 1)]75	ext_str: Vec<ExtStr>,76	#[clap(long, about = "Add external string from code", number_of_values = 1)]77	ext_code: Vec<ExtStr>,78	#[clap(long, about = "Add TLA", number_of_values = 1)]79	tla_str: Vec<ExtStr>,80	#[clap(long, about = "Add TLA from code", number_of_values = 1)]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(long, short = "J", about = "Library search dir")]103	jpath: Vec<PathBuf>,104105	#[clap(106		long,107		default_value = "3",108		about = "When using --format, this option specifies string to pad output with"109	)]110	line_padding: usize,111112	#[clap(about = "File to compile")]113	input: String,114}115116fn main() {117	let opts: Opts = Opts::parse();118	let evaluator = jrsonnet_evaluator::EvaluationState::default();119	evaluator.set_max_trace(opts.max_trace);120	evaluator.set_max_stack(opts.max_stack);121	evaluator.set_import_resolver(Box::new(jrsonnet_evaluator::FileImportResolver {122		library_paths: opts.jpath.clone(),123	}));124	if !opts.no_stdlib {125		evaluator.with_stdlib();126	}127	for ExtStr { name, value } in opts.ext_str.iter().cloned() {128		evaluator.add_ext_var(name.into(), Val::Str(value.into()));129	}130	for ExtStr { name, value } in opts.ext_code.iter().cloned() {131		evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());132	}133	let mut input = current_dir().unwrap();134	input.push(opts.input.clone());135	let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();136	if let Err(e) = evaluator.add_file(Rc::new(input.clone()), code_string.clone().into()) {137		print_syntax_error(e, &input, &code_string);138		std::process::exit(1);139	}140	let result = evaluator.evaluate_file(&input);141	match result {142		Ok(v) => {143			let v = match v {144				Val::Func(f) => {145					let mut desc_map = HashMap::new();146					for ExtStr { name, value } in opts.tla_str.iter().cloned() {147						desc_map.insert(name, el!(Expr::Str(value.into())));148					}149					for ExtStr { name, value } in opts.tla_code.iter().cloned() {150						desc_map.insert(151							name,152							jrsonnet_parser::parse(153								&value,154								&ParserSettings {155									file_name: Rc::new(PathBuf::new()),156									loc_data: false,157								},158							)159							.unwrap(),160						);161					}162					evaluator.add_global("__tmp__tlf__".into(), Val::Func(f));163					evaluator164						.evaluate_raw(el!(Expr::Apply(165							el!(Expr::Var("__tmp__tlf__".into())),166							ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),167							false,168						)))169						.unwrap()170				}171				v => v,172			};173			let v = evaluator.run_in_state(|| match opts.format {174				Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),175				Format::Yaml => {176					evaluator.add_global("__tmp__to_yaml__".into(), v);177					evaluator.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")")178				}179				_ => Ok(v),180			});181			let v = match v {182				Ok(v) => v,183				Err(err) => {184					print_error(&err, evaluator, &opts);185					std::process::exit(1);186				}187			};188			match v {189				Val::Str(s) => println!("{}", s),190				Val::Num(n) => println!("{}", n),191				_v => eprintln!(192					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"193				),194			}195		}196		Err(err) => {197			print_error(&err, evaluator, &opts);198			std::process::exit(1);199		}200	}201}202203fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {204	println!("Error: {:?}", err.0);205	print_trace(&(err.1), evaluator, &opts);206}207208fn print_syntax_error(error: jrsonnet_parser::ParseError, file: &PathBuf, code: &str) {209	use annotate_snippets::{210		display_list::{DisplayList, FormatOptions},211		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},212	};213	//&("Expected: ".to_owned() + error.expected)214	let origin = file.to_str().unwrap();215	let error_message = format!("Expected: {}", error.expected);216	let snippet = Snippet {217		opt: FormatOptions {218			color: true,219			..Default::default()220		},221		title: Some(Annotation {222			label: Some(&error_message),223			id: None,224			annotation_type: AnnotationType::Error,225		}),226		footer: vec![],227		slices: vec![Slice {228			source: &code,229			line_start: 1,230			origin: Some(origin),231			fold: false,232			annotations: vec![SourceAnnotation {233				label: "At this position",234				annotation_type: AnnotationType::Error,235				range: (error.location.offset, error.location.offset + 1),236			}],237		}],238	};239240	let dl = DisplayList::from(snippet);241	println!("{}", dl);242}243244fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {245	use annotate_snippets::{246		display_list::{DisplayList, FormatOptions},247		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},248	};249	for item in trace.0.iter() {250		let desc = &item.1;251		let source = item.0.clone();252		let start_end = evaluator.map_source_locations(&source.0, &[source.1, source.2]);253		if opts.trace_format == TraceFormat::Custom {254			let source_fragment: String = evaluator255				.get_source(&source.0)256				.unwrap()257				.chars()258				.skip(start_end[0].line_start_offset)259				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)260				.collect();261			let snippet = Snippet {262				opt: FormatOptions {263					color: true,264					..Default::default()265				},266				title: Some(Annotation {267					label: Some(&item.1),268					id: None,269					annotation_type: AnnotationType::Error,270				}),271				footer: vec![],272				slices: vec![Slice {273					source: &source_fragment,274					line_start: start_end[0].line,275					origin: Some(&source.0.to_str().unwrap()),276					fold: false,277					annotations: vec![SourceAnnotation {278						label: desc,279						annotation_type: AnnotationType::Error,280						range: (281							source.1 - start_end[0].line_start_offset,282							source.2 - start_end[0].line_start_offset,283						),284					}],285				}],286			};287288			let dl = DisplayList::from(snippet);289			println!("{}", dl);290		} else {291			print_jsonnet_pair(292				source.0.to_str().unwrap(),293				&start_end[0],294				&start_end[1],295				opts.trace_format == TraceFormat::GoJsonnet,296			);297		}298	}299}300301fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {302	if is_go {303		print!("        ");304	} else {305		print!("  ");306	}307	print!("{}:", file);308	if start.line == end.line {309		// IDK why, but this is the behavior original jsonnet cpp impl shows310		if start.column == end.column || !is_go && start.column + 1 == end.column {311			println!("{}:{}", start.line, end.column)312		} else {313			println!("{}:{}-{}", start.line, start.column, end.column);314		}315	} else {316		println!(317			"({}:{})-({}:{})",318			start.line, end.column, start.line, end.column319		);320	}321}