git.delta.rocks / jrsonnet / refs/commits / 96d9345e6c0d

difftreelog

source

cmds/jsonnet/src/main.rs6.9 KiBsourcehistory
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(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	let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();91	if let Err(e) = evaluator.add_file(input.clone(), code_string.clone()) {92		print_syntax_error(e, &input, &code_string);93		std::process::exit(2);94	}95	let result = evaluator.evaluate_file(&input);96	match result {97		Ok(v) => {98			let v = match opts.format {99				Format::Json => {100					if opts.no_stdlib {101						evaluator.add_stdlib();102					}103					evaluator.add_global("__tmp__to_json__".to_owned(), v);104					let v = evaluator105						.parse_evaluate_raw("std.manifestJsonEx(__tmp__to_json__, \"  \")");106					match v {107						Ok(v) => v,108						Err(err) => {109							print_error(&err, evaluator, &opts);110							std::process::exit(2);111						}112					}113				}114				Format::Yaml => {115					if opts.no_stdlib {116						evaluator.add_stdlib();117					}118					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);119					let v = evaluator120						.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")");121					match v {122						Ok(v) => v,123						Err(err) => {124							print_error(&err, evaluator, &opts);125							std::process::exit(2);126						}127					}128				}129				_ => v,130			};131			match v {132				Val::Str(s) => println!("{}", s),133				Val::Num(n) => println!("{}", n),134				_v => eprintln!(135					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"136				),137			}138		}139		Err(err) => {140			print_error(&err, evaluator, &opts);141		}142	}143}144145fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {146	println!("Error: {:?}", err.0);147	print_trace(&(err.1), evaluator, &opts);148}149150fn print_syntax_error(error: jsonnet_parser::ParseError, file: &PathBuf, code: &str) {151	use annotate_snippets::{152		display_list::{DisplayList, FormatOptions},153		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},154	};155	//&("Expected: ".to_owned() + error.expected)156	let origin = file.to_str().unwrap();157	let error_message = format!("Expected: {}", error.expected);158	let snippet = Snippet {159		opt: FormatOptions {160			color: true,161			..Default::default()162		},163		title: Some(Annotation {164			label: Some(&error_message),165			id: None,166			annotation_type: AnnotationType::Error,167		}),168		footer: vec![],169		slices: vec![Slice {170			source: &code,171			line_start: 1,172			origin: Some(origin),173			fold: false,174			annotations: vec![SourceAnnotation {175				label: "At this position",176				annotation_type: AnnotationType::Error,177				range: (error.location.offset, error.location.offset + 1),178			}],179		}],180	};181182	let dl = DisplayList::from(snippet);183	println!("{}", dl);184}185186fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {187	use annotate_snippets::{188		display_list::{DisplayList, FormatOptions},189		snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},190	};191	for item in trace.0.iter() {192		let desc = &item.1;193		if (item.0).1.is_none() {194			continue;195		}196		let source = (item.0).1.clone().unwrap();197		let code = evaluator.get_source(&source.0);198		if code.is_none() {199			continue;200		}201		let code = code.unwrap();202		let start_end = offset_to_location(&code, &[source.1, source.2]);203		if opts.trace_format == TraceFormat::Custom {204			let source_fragment: String = code205				.chars()206				.skip(start_end[0].line_start_offset)207				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)208				.collect();209			let snippet = Snippet {210				opt: FormatOptions {211					color: true,212					..Default::default()213				},214				title: Some(Annotation {215					label: Some(&item.1),216					id: None,217					annotation_type: AnnotationType::Error,218				}),219				footer: vec![],220				slices: vec![Slice {221					source: &source_fragment,222					line_start: start_end[0].line,223					origin: Some(&source.0.to_str().unwrap()),224					fold: false,225					annotations: vec![SourceAnnotation {226						label: desc,227						annotation_type: AnnotationType::Error,228						range: (229							source.1 - start_end[0].line_start_offset,230							source.2 - start_end[0].line_start_offset,231						),232					}],233				}],234			};235236			let dl = DisplayList::from(snippet);237			println!("{}", dl);238		} else {239			print_jsonnet_pair(240				source.0.to_str().unwrap(),241				&start_end[0],242				&start_end[1],243				opts.trace_format == TraceFormat::GoJsonnet,244			);245		}246	}247}248249fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {250	if is_go {251		print!("        ");252	} else {253		print!("  ");254	}255	print!("{}:", file);256	if start.line == end.line {257		// IDK why, but this is the behavior original jsonnet cpp impl shows258		if start.column == end.column || !is_go && start.column + 1 == end.column {259			println!("{}:{}", start.line, end.column)260		} else {261			println!("{}:{}-{}", start.line, start.column, end.column);262		}263	} else {264		println!(265			"({}:{})-({}:{})",266			start.line, end.column, start.line, end.column267		);268	}269}