difftreelog
feat show only source code slice on error
in: master
2 files changed
cmds/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
+ }
+ ]
+ )
+ }
+}
cmds/jsonnet/src/main.rsdiffbeforeafterboth1use 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}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}