difftreelog
feat import support
in: master
3 files changed
cmds/jrsonnet/src/main.rsdiffbeforeafterboth1pub 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}1pub mod location;23use clap::Clap;4use jsonnet_evaluator::{EvaluationSettings, 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::new(EvaluationSettings {115 import_resolver: Box::new(|path| String::from_utf8(std::fs::read(path).unwrap()).unwrap()),116 ..Default::default()117 });118 if !opts.no_stdlib {119 evaluator.with_stdlib();120 }121 for ExtStr { name, value } in opts.ext_str.iter().cloned() {122 evaluator.add_ext_var(name, Val::Str(value));123 }124 for ExtStr { name, value } in opts.ext_code.iter().cloned() {125 evaluator.add_ext_var(name, evaluator.parse_evaluate_raw(&value).unwrap());126 }127 let mut input = current_dir().unwrap();128 input.push(opts.input.clone());129 let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();130 if let Err(e) = evaluator.add_file(input.clone(), code_string.clone()) {131 print_syntax_error(e, &input, &code_string);132 std::process::exit(1);133 }134 let result = evaluator.evaluate_file(&input);135 match result {136 Ok(v) => {137 let v = match opts.format {138 Format::Json => {139 if opts.no_stdlib {140 evaluator.with_stdlib();141 }142 evaluator.add_global("__tmp__to_json__".to_owned(), v);143 let v = evaluator.parse_evaluate_raw(&format!(144 "std.manifestJsonEx(__tmp__to_json__, \"{}\")",145 " ".repeat(opts.line_padding),146 ));147 match v {148 Ok(v) => v,149 Err(err) => {150 print_error(&err, evaluator, &opts);151 std::process::exit(1);152 }153 }154 }155 Format::Yaml => {156 if opts.no_stdlib {157 evaluator.with_stdlib();158 }159 evaluator.add_global("__tmp__to_yaml__".to_owned(), v);160 let v = evaluator161 .parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \" \")");162 match v {163 Ok(v) => v,164 Err(err) => {165 print_error(&err, evaluator, &opts);166 std::process::exit(1);167 }168 }169 }170 _ => v,171 };172 match v {173 Val::Str(s) => println!("{}", s),174 Val::Num(n) => println!("{}", n),175 _v => eprintln!(176 "jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"177 ),178 }179 }180 Err(err) => {181 print_error(&err, evaluator, &opts);182 std::process::exit(1);183 }184 }185}186187fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {188 println!("Error: {:?}", err.0);189 print_trace(&(err.1), evaluator, &opts);190}191192fn print_syntax_error(error: jsonnet_parser::ParseError, file: &PathBuf, code: &str) {193 use annotate_snippets::{194 display_list::{DisplayList, FormatOptions},195 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},196 };197 //&("Expected: ".to_owned() + error.expected)198 let origin = file.to_str().unwrap();199 let error_message = format!("Expected: {}", error.expected);200 let snippet = Snippet {201 opt: FormatOptions {202 color: true,203 ..Default::default()204 },205 title: Some(Annotation {206 label: Some(&error_message),207 id: None,208 annotation_type: AnnotationType::Error,209 }),210 footer: vec![],211 slices: vec![Slice {212 source: &code,213 line_start: 1,214 origin: Some(origin),215 fold: false,216 annotations: vec![SourceAnnotation {217 label: "At this position",218 annotation_type: AnnotationType::Error,219 range: (error.location.offset, error.location.offset + 1),220 }],221 }],222 };223224 let dl = DisplayList::from(snippet);225 println!("{}", dl);226}227228fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {229 use annotate_snippets::{230 display_list::{DisplayList, FormatOptions},231 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},232 };233 for item in trace.0.iter() {234 let desc = &item.1;235 if (item.0).1.is_none() {236 continue;237 }238 let source = (item.0).1.clone().unwrap();239 let code = evaluator.get_source(&source.0);240 if code.is_none() {241 continue;242 }243 let code = code.unwrap();244 let start_end = offset_to_location(&code, &[source.1, source.2]);245 if opts.trace_format == TraceFormat::Custom {246 let source_fragment: String = code247 .chars()248 .skip(start_end[0].line_start_offset)249 .take(start_end[1].line_end_offset - start_end[0].line_start_offset)250 .collect();251 let snippet = Snippet {252 opt: FormatOptions {253 color: true,254 ..Default::default()255 },256 title: Some(Annotation {257 label: Some(&item.1),258 id: None,259 annotation_type: AnnotationType::Error,260 }),261 footer: vec![],262 slices: vec![Slice {263 source: &source_fragment,264 line_start: start_end[0].line,265 origin: Some(&source.0.to_str().unwrap()),266 fold: false,267 annotations: vec![SourceAnnotation {268 label: desc,269 annotation_type: AnnotationType::Error,270 range: (271 source.1 - start_end[0].line_start_offset,272 source.2 - start_end[0].line_start_offset,273 ),274 }],275 }],276 };277278 let dl = DisplayList::from(snippet);279 println!("{}", dl);280 } else {281 print_jsonnet_pair(282 source.0.to_str().unwrap(),283 &start_end[0],284 &start_end[1],285 opts.trace_format == TraceFormat::GoJsonnet,286 );287 }288 }289}290291fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {292 if is_go {293 print!(" ");294 } else {295 print!(" ");296 }297 print!("{}:", file);298 if start.line == end.line {299 // IDK why, but this is the behavior original jsonnet cpp impl shows300 if start.column == end.column || !is_go && start.column + 1 == end.column {301 println!("{}:{}", start.line, end.column)302 } else {303 println!("{}:{}-{}", start.line, start.column, end.column);304 }305 } else {306 println!(307 "({}:{})-({}:{})",308 start.line, end.column, start.line, end.column309 );310 }311}crates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -1,5 +1,5 @@
use crate::{
- binding, context_creator, create_error, future_wrapper, lazy_val, push, Context,
+ binding, context_creator, create_error, future_wrapper, lazy_val, push, with_state, Context,
ContextCreator, FuncDesc, LazyBinding, ObjMember, ObjValue, Result, Val,
};
use closure::closure;
@@ -607,6 +607,16 @@
}
}
}
+ Import(path) => {
+ let mut lib_path = loc
+ .clone()
+ .expect("imports can't be used without loc_data")
+ .0
+ .clone();
+ lib_path.pop();
+ lib_path.push(path);
+ with_state(|s| s.import_file(&lib_path))?
+ }
_ => panic!(
"evaluation not implemented: {:?}",
LocExpr(expr.clone(), loc.clone())
crates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -50,14 +50,18 @@
}
pub struct EvaluationSettings {
- max_stack_frames: usize,
- max_stack_trace_size: usize,
+ pub max_stack_frames: usize,
+ pub max_stack_trace_size: usize,
+ pub import_resolver: Box<dyn Fn(&PathBuf) -> String>,
}
impl Default for EvaluationSettings {
fn default() -> Self {
EvaluationSettings {
max_stack_frames: 200,
max_stack_trace_size: 20,
+ import_resolver: Box::new(|path| {
+ panic!("default EvaluationSettings have no support for import resolution, can't import {:?}", path)
+ }),
}
}
}
@@ -102,6 +106,12 @@
#[derive(Default, Clone)]
pub struct EvaluationState(Rc<EvaluationStateInternals>);
impl EvaluationState {
+ pub fn new(settings: EvaluationSettings) -> Self {
+ EvaluationState(Rc::new(EvaluationStateInternals {
+ settings,
+ ..Default::default()
+ }))
+ }
pub fn add_file(&self, name: PathBuf, code: String) -> std::result::Result<(), ParseError> {
self.0.files.borrow_mut().insert(
name.clone(),
@@ -139,6 +149,11 @@
}
pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {
self.begin_state();
+ let value = self.evaluate_file_in_current_state(name)?;
+ self.end_state();
+ Ok(value)
+ }
+ pub(crate) fn evaluate_file_in_current_state(&self, name: &PathBuf) -> Result<Val> {
let expr: LocExpr = {
let ro_map = self.0.files.borrow();
let value = ro_map
@@ -159,9 +174,15 @@
.2
.replace(value.clone());
}
- self.end_state();
Ok(value)
}
+ pub(crate) fn import_file(&self, path: &PathBuf) -> Result<Val> {
+ if !self.0.files.borrow().contains_key(path) {
+ let file_str = (self.0.settings.import_resolver)(path);
+ self.add_file(path.clone(), file_str).unwrap();
+ }
+ self.evaluate_file_in_current_state(path)
+ }
pub fn parse_evaluate_raw(&self, code: &str) -> Result<Val> {
let parsed = parse(