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

difftreelog

feat show only source code slice on error

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

2 files changed

addedcmds/jsonnet/src/location.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/jsonnet/src/location.rs
@@ -0,0 +1,99 @@
+#[derive(Clone, PartialEq, Debug)]
+pub struct CodeLocation {
+	pub line: usize,
+	pub column: usize,
+
+	pub line_start_offset: usize,
+	pub line_end_offset: usize,
+}
+
+pub fn offset_to_location(file: &str, offsets: &[usize]) -> Vec<CodeLocation> {
+	if offsets.is_empty() {
+		return vec![];
+	}
+	let mut line = 1;
+	let mut column = 0;
+	let max_offset = *offsets.iter().max().unwrap();
+
+	let mut offset_map = offsets
+		.iter()
+		.enumerate()
+		.map(|(pos, offset)| (*offset, pos))
+		.collect::<Vec<_>>();
+	offset_map.sort_by_key(|v| v.0);
+	offset_map.reverse();
+
+	let mut out = vec![
+		CodeLocation {
+			column: 0,
+			line: 0,
+			line_start_offset: 0,
+			line_end_offset: 0
+		};
+		offsets.len()
+	];
+	let mut with_no_known_line_ending = vec![];
+	let mut this_line_offset = 0;
+	for (pos, ch) in file.chars().enumerate() {
+		column += 1;
+		match offset_map.last() {
+			Some(x) if x.0 == pos => {
+				let out_idx = x.1;
+				with_no_known_line_ending.push(out_idx);
+				out[out_idx].line = line;
+				out[out_idx].column = column;
+				out[out_idx].line_start_offset = this_line_offset + 1;
+				offset_map.pop();
+			}
+			_ => {}
+		}
+		if ch == '\n' {
+			line += 1;
+			column = 0;
+
+			for idx in with_no_known_line_ending.drain(..) {
+				out[idx].line_end_offset = pos;
+			}
+			this_line_offset = pos;
+
+			if pos == max_offset + 1 {
+				break;
+			}
+		}
+	}
+	let file_end = file.chars().count();
+	for idx in with_no_known_line_ending {
+		out[idx].line_end_offset = file_end;
+	}
+
+	out
+}
+
+#[cfg(test)]
+pub mod tests {
+	use super::{offset_to_location, CodeLocation};
+
+	#[test]
+	fn test() {
+		assert_eq!(
+			offset_to_location(
+				"hello world\n_______________________________________________________",
+				&[0, 14]
+			),
+			vec![
+				CodeLocation {
+					line: 1,
+					column: 1,
+					line_start_offset: 0,
+					line_end_offset: 11
+				},
+				CodeLocation {
+					line: 2,
+					column: 3,
+					line_start_offset: 11,
+					line_end_offset: 67
+				}
+			]
+		)
+	}
+}
modifiedcmds/jsonnet/src/main.rsdiffbeforeafterboth
before · cmds/jsonnet/src/main.rs
1use clap::Clap;2use jsonnet_evaluator::{EvaluationState, LocError, StackTrace, Val};3use std::env::current_dir;4use std::str::FromStr;56enum Format {7	None,8	Json,9	Yaml,10}1112impl FromStr for Format {13	type Err = &'static str;14	fn from_str(s: &str) -> Result<Self, Self::Err> {15		Ok(match s {16			"none" => Format::None,17			"json" => Format::Json,18			"yaml" => Format::Yaml,19			_ => return Err("no such format"),20		})21	}22}2324#[derive(PartialEq)]25enum TraceFormat {26	CppJsonnet,27	GoJsonnet,28	Custom,29}30impl FromStr for TraceFormat {31	type Err = &'static str;32	fn from_str(s: &str) -> Result<Self, Self::Err> {33		Ok(match s {34			"cpp" => TraceFormat::CppJsonnet,35			"go" => TraceFormat::GoJsonnet,36			"default" => TraceFormat::Custom,37			_ => return Err("no such format"),38		})39	}40}4142#[derive(Clap)]43#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]44struct Opts {45	#[clap(long, about = "Disable global std variable")]46	no_stdlib: bool,47	#[clap(long, about = "Add external string")]48	ext_str: Option<Vec<String>>,49	#[clap(long, about = "Add external string from code")]50	ext_code: Option<Vec<String>>,51	#[clap(long, about = "Add TLA")]52	tla_str: Option<Vec<String>>,53	#[clap(long, about = "Add TLA from code")]54	tla_code: Option<Vec<String>>,55	#[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]56	format: Format,57	#[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]58	trace_format: TraceFormat,5960	#[clap(61		long,62		short = "s",63		default_value = "200",64		about = "Number of allowed stack frames"65	)]66	max_stack: usize,67	#[clap(68		long,69		short = "t",70		default_value = "20",71		about = "Max length of stack trace before cropping"72	)]73	max_trace: usize,7475	#[clap(about = "File to compile", index = 1)]76	input: String,77}7879fn main() {80	let opts: Opts = Opts::parse();81	let evaluator = jsonnet_evaluator::EvaluationState::default();82	if !opts.no_stdlib {83		evaluator.add_stdlib();84	}85	let mut input = current_dir().unwrap();86	input.push(opts.input.clone());87	evaluator88		.add_file(89			input.clone(),90			String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap(),91		)92		.unwrap();93	let result = evaluator.evaluate_file(&input);94	match result {95		Ok(v) => {96			let v = match opts.format {97				Format::Json => {98					if opts.no_stdlib {99						evaluator.add_stdlib();100					}101					evaluator.add_global("__tmp__to_json__".to_owned(), v);102					let v = evaluator103						.parse_evaluate_raw("std.manifestJsonEx(__tmp__to_json__, \"  \")");104					match v {105						Ok(v) => v,106						Err(err) => {107							print_error(&err, evaluator, &opts);108							std::process::exit(2);109						}110					}111				}112				Format::Yaml => {113					if opts.no_stdlib {114						evaluator.add_stdlib();115					}116					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);117					let v = evaluator118						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");119					match v {120						Ok(v) => v,121						Err(err) => {122							print_error(&err, evaluator, &opts);123							std::process::exit(2);124						}125					}126				}127				_ => v,128			};129			match v {130				Val::Str(s) => println!("{}", s),131				Val::Num(n) => println!("{}", n),132				_v => eprintln!(133					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"134				),135			}136		}137		Err(err) => {138			print_error(&err, evaluator, &opts);139		}140	}141}142143fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {144	println!("Error: {:?}", err.0);145	print_trace(&(err.1), evaluator, &opts);146}147148fn line_columns(file: &str, offsets: &[usize]) -> Vec<(usize, usize)> {149	if offsets.is_empty() {150		return vec![];151	}152	let mut line = 0;153	let mut column = 0;154	let max_offset = *offsets.iter().max().unwrap();155156	let mut offset_map = offsets157		.iter()158		.enumerate()159		.map(|(pos, offset)| (*offset, pos))160		.collect::<Vec<_>>();161	offset_map.sort_by_key(|v| v.0);162	offset_map.reverse();163164	let mut out = vec![(0usize, 0usize); offsets.len()];165	for (pos, ch) in (0..max_offset + 1).zip(file.chars()) {166		column += 1;167		if offset_map.last().unwrap().0 == pos {168			out[offset_map.last().unwrap().1] = (line, column);169			offset_map.pop();170		}171		if ch == '\n' {172			line += 1;173			column = 0;174		}175	}176177	out178}179180fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {181	use annotate_snippets::{182		display_list::{DisplayList, FormatOptions},183		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},184	};185	for item in trace.0.iter() {186		let desc = &item.1;187		if (item.0).1.is_none() {188			continue;189		}190		let source = (item.0).1.clone().unwrap();191		let code = evaluator.get_source(&source.0);192		if code.is_none() {193			continue;194		}195		let code = code.unwrap();196		let start_end = line_columns(&code, &[source.1, source.2]);197		if opts.trace_format == TraceFormat::Custom {198			let snippet = Snippet {199				opt: FormatOptions {200					color: true,201					..Default::default()202				},203				title: Some(Annotation {204					label: Some(&item.1),205					id: None,206					annotation_type: AnnotationType::Error,207				}),208				footer: vec![],209				slices: vec![Slice {210					source: &code,211					line_start: 1,212					origin: Some(&source.0.to_str().unwrap()),213					fold: false,214					annotations: vec![SourceAnnotation {215						label: desc,216						annotation_type: AnnotationType::Error,217						range: (source.1, source.2),218					}],219				}],220			};221222			let dl = DisplayList::from(snippet);223			println!("{}", dl);224		} else {225			if opts.trace_format == TraceFormat::CppJsonnet {226				print!("  ");227			} else {228				print!("        ");229			}230			print!("{}:", source.0.to_str().unwrap());231			if start_end[0].0 == start_end[1].0 {232				// IDK why, but this is the behavior original jsonnet shows233				if start_end[0].1 == start_end[1].1234					|| opts.trace_format == TraceFormat::CppJsonnet235						&& start_end[0].1 + 1 == start_end[1].1236				{237					println!("{}:{}", start_end[0].0 + 1, start_end[0].1)238				} else {239					println!(240						"{}:{}-{}",241						start_end[0].0 + 1,242						start_end[0].1,243						start_end[1].1244					);245				}246			} else {247				println!(248					"({}:{})-({}:{})",249					start_end[0].0 + 1,250					start_end[0].1,251					start_end[1].0 + 1,252					start_end[1].1253				);254			}255		}256	}257}
after · cmds/jsonnet/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::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(about = "File to compile", index = 1)]79	input: String,80}8182fn main() {83	let opts: Opts = Opts::parse();84	let evaluator = jsonnet_evaluator::EvaluationState::default();85	if !opts.no_stdlib {86		evaluator.add_stdlib();87	}88	let mut input = current_dir().unwrap();89	input.push(opts.input.clone());90	evaluator91		.add_file(92			input.clone(),93			String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap(),94		)95		.unwrap();96	let result = evaluator.evaluate_file(&input);97	match result {98		Ok(v) => {99			let v = match opts.format {100				Format::Json => {101					if opts.no_stdlib {102						evaluator.add_stdlib();103					}104					evaluator.add_global("__tmp__to_json__".to_owned(), v);105					let v = evaluator106						.parse_evaluate_raw("std.manifestJsonEx(__tmp__to_json__, \"  \")");107					match v {108						Ok(v) => v,109						Err(err) => {110							print_error(&err, evaluator, &opts);111							std::process::exit(2);112						}113					}114				}115				Format::Yaml => {116					if opts.no_stdlib {117						evaluator.add_stdlib();118					}119					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);120					let v = evaluator121						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");122					match v {123						Ok(v) => v,124						Err(err) => {125							print_error(&err, evaluator, &opts);126							std::process::exit(2);127						}128					}129				}130				_ => v,131			};132			match v {133				Val::Str(s) => println!("{}", s),134				Val::Num(n) => println!("{}", n),135				_v => eprintln!(136					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"137				),138			}139		}140		Err(err) => {141			print_error(&err, evaluator, &opts);142		}143	}144}145146fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {147	println!("Error: {:?}", err.0);148	print_trace(&(err.1), evaluator, &opts);149}150151fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {152	use annotate_snippets::{153		display_list::{DisplayList, FormatOptions},154		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},155	};156	for item in trace.0.iter() {157		let desc = &item.1;158		if (item.0).1.is_none() {159			continue;160		}161		let source = (item.0).1.clone().unwrap();162		let code = evaluator.get_source(&source.0);163		if code.is_none() {164			continue;165		}166		let code = code.unwrap();167		let start_end = offset_to_location(&code, &[source.1, source.2]);168		if opts.trace_format == TraceFormat::Custom {169			let source_fragment: String = code170				.chars()171				.skip(start_end[0].line_start_offset)172				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)173				.collect();174			let snippet = Snippet {175				opt: FormatOptions {176					color: true,177					..Default::default()178				},179				title: Some(Annotation {180					label: Some(&item.1),181					id: None,182					annotation_type: AnnotationType::Error,183				}),184				footer: vec![],185				slices: vec![Slice {186					source: &source_fragment,187					line_start: start_end[0].line,188					origin: Some(&source.0.to_str().unwrap()),189					fold: false,190					annotations: vec![SourceAnnotation {191						label: desc,192						annotation_type: AnnotationType::Error,193						range: (194							source.1 - start_end[0].line_start_offset,195							source.2 - start_end[0].line_start_offset,196						),197					}],198				}],199			};200201			let dl = DisplayList::from(snippet);202			println!("{}", dl);203		} else {204			print_jsonnet_pair(205				source.0.to_str().unwrap(),206				&start_end[0],207				&start_end[1],208				opts.trace_format == TraceFormat::GoJsonnet,209			);210		}211	}212}213214fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {215	if is_go {216		print!("        ");217	} else {218		print!("  ");219	}220	print!("{}:", file);221	if start.line == end.line {222		// IDK why, but this is the behavior original jsonnet cpp impl shows223		if start.column == end.column || !is_go && start.column + 1 == end.column {224			println!("{}:{}", start.line, end.column)225		} else {226			println!("{}:{}-{}", start.line, start.column, end.column);227		}228	} else {229		println!(230			"({}:{})-({}:{})",231			start.line, end.column, start.line, end.column232		);233	}234}