difftreelog
feat library paths
in: master
5 files changed
cmds/jrsonnet/src/main.rsdiffbeforeafterboth1pub mod location;23use clap::Clap;4use jsonnet_evaluator::{EvaluationSettings, EvaluationState, LocError, StackTrace, Val};5use jsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};6use location::{offset_to_location, CodeLocation};7use std::env::current_dir;8use std::{collections::HashMap, path::PathBuf, str::FromStr};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(103 long,104 default_value = "3",105 about = "When using --format, this option specifies string to pad output with"106 )]107 line_padding: usize,108109 #[clap(about = "File to compile", index = 1)]110 input: String,111}112113fn main() {114 let opts: Opts = Opts::parse();115 let evaluator = jsonnet_evaluator::EvaluationState::new(EvaluationSettings {116 import_resolver: Box::new(|path| String::from_utf8(std::fs::read(path).unwrap()).unwrap()),117 ..Default::default()118 });119 if !opts.no_stdlib {120 evaluator.with_stdlib();121 }122 for ExtStr { name, value } in opts.ext_str.iter().cloned() {123 evaluator.add_ext_var(name, Val::Str(value));124 }125 for ExtStr { name, value } in opts.ext_code.iter().cloned() {126 evaluator.add_ext_var(name, evaluator.parse_evaluate_raw(&value).unwrap());127 }128 let mut input = current_dir().unwrap();129 input.push(opts.input.clone());130 let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();131 if let Err(e) = evaluator.add_file(input.clone(), code_string.clone()) {132 print_syntax_error(e, &input, &code_string);133 std::process::exit(1);134 }135 let result = evaluator.evaluate_file(&input);136 match result {137 Ok(v) => {138 let v = match v {139 Val::Func(f) => {140 let mut desc_map = HashMap::new();141 for ExtStr { name, value } in opts.tla_str.iter().cloned() {142 desc_map.insert(name, el!(Expr::Str(value)));143 }144 for ExtStr { name, value } in opts.tla_code.iter().cloned() {145 desc_map.insert(146 name,147 jsonnet_parser::parse(148 &value,149 &ParserSettings {150 file_name: PathBuf::new(),151 loc_data: false,152 },153 )154 .unwrap(),155 );156 }157 evaluator.add_global("__tmp__tlf__".to_owned(), Val::Func(f));158 evaluator159 .evaluate_raw(el!(Expr::Apply(160 el!(Expr::Var("__tmp__tlf__".to_owned())),161 ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),162 false,163 )))164 .unwrap()165 }166 v => v,167 };168 let v = match opts.format {169 Format::Json => {170 if opts.no_stdlib {171 evaluator.with_stdlib();172 }173 evaluator.add_global("__tmp__to_json__".to_owned(), v);174 let v = evaluator.parse_evaluate_raw(&format!(175 "std.manifestJsonEx(__tmp__to_json__, \"{}\")",176 " ".repeat(opts.line_padding),177 ));178 match v {179 Ok(v) => v,180 Err(err) => {181 print_error(&err, evaluator, &opts);182 std::process::exit(1);183 }184 }185 }186 Format::Yaml => {187 if opts.no_stdlib {188 evaluator.with_stdlib();189 }190 evaluator.add_global("__tmp__to_yaml__".to_owned(), v);191 let v = evaluator192 .parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \" \")");193 match v {194 Ok(v) => v,195 Err(err) => {196 print_error(&err, evaluator, &opts);197 std::process::exit(1);198 }199 }200 }201 _ => v,202 };203 match v {204 Val::Str(s) => println!("{}", s),205 Val::Num(n) => println!("{}", n),206 _v => eprintln!(207 "jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"208 ),209 }210 }211 Err(err) => {212 print_error(&err, evaluator, &opts);213 std::process::exit(1);214 }215 }216}217218fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {219 println!("Error: {:?}", err.0);220 print_trace(&(err.1), evaluator, &opts);221}222223fn print_syntax_error(error: jsonnet_parser::ParseError, file: &PathBuf, code: &str) {224 use annotate_snippets::{225 display_list::{DisplayList, FormatOptions},226 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},227 };228 //&("Expected: ".to_owned() + error.expected)229 let origin = file.to_str().unwrap();230 let error_message = format!("Expected: {}", error.expected);231 let snippet = Snippet {232 opt: FormatOptions {233 color: true,234 ..Default::default()235 },236 title: Some(Annotation {237 label: Some(&error_message),238 id: None,239 annotation_type: AnnotationType::Error,240 }),241 footer: vec![],242 slices: vec![Slice {243 source: &code,244 line_start: 1,245 origin: Some(origin),246 fold: false,247 annotations: vec![SourceAnnotation {248 label: "At this position",249 annotation_type: AnnotationType::Error,250 range: (error.location.offset, error.location.offset + 1),251 }],252 }],253 };254255 let dl = DisplayList::from(snippet);256 println!("{}", dl);257}258259fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {260 use annotate_snippets::{261 display_list::{DisplayList, FormatOptions},262 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},263 };264 for item in trace.0.iter() {265 let desc = &item.1;266 if (item.0).1.is_none() {267 continue;268 }269 let source = (item.0).1.clone().unwrap();270 let code = evaluator.get_source(&source.0);271 if code.is_none() {272 continue;273 }274 let code = code.unwrap();275 let start_end = offset_to_location(&code, &[source.1, source.2]);276 if opts.trace_format == TraceFormat::Custom {277 let source_fragment: String = code278 .chars()279 .skip(start_end[0].line_start_offset)280 .take(start_end[1].line_end_offset - start_end[0].line_start_offset)281 .collect();282 let snippet = Snippet {283 opt: FormatOptions {284 color: true,285 ..Default::default()286 },287 title: Some(Annotation {288 label: Some(&item.1),289 id: None,290 annotation_type: AnnotationType::Error,291 }),292 footer: vec![],293 slices: vec![Slice {294 source: &source_fragment,295 line_start: start_end[0].line,296 origin: Some(&source.0.to_str().unwrap()),297 fold: false,298 annotations: vec![SourceAnnotation {299 label: desc,300 annotation_type: AnnotationType::Error,301 range: (302 source.1 - start_end[0].line_start_offset,303 source.2 - start_end[0].line_start_offset,304 ),305 }],306 }],307 };308309 let dl = DisplayList::from(snippet);310 println!("{}", dl);311 } else {312 print_jsonnet_pair(313 source.0.to_str().unwrap(),314 &start_end[0],315 &start_end[1],316 opts.trace_format == TraceFormat::GoJsonnet,317 );318 }319 }320}321322fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {323 if is_go {324 print!(" ");325 } else {326 print!(" ");327 }328 print!("{}:", file);329 if start.line == end.line {330 // IDK why, but this is the behavior original jsonnet cpp impl shows331 if start.column == end.column || !is_go && start.column + 1 == end.column {332 println!("{}:{}", start.line, end.column)333 } else {334 println!("{}:{}-{}", start.line, start.column, end.column);335 }336 } else {337 println!(338 "({}:{})-({}:{})",339 start.line, end.column, start.line, end.column340 );341 }342}crates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/error.rs
+++ b/crates/jsonnet-evaluator/src/error.rs
@@ -1,5 +1,6 @@
use crate::ValType;
use jsonnet_parser::LocExpr;
+use std::path::PathBuf;
#[derive(Debug)]
pub enum Error {
@@ -24,6 +25,12 @@
StandaloneSuper,
+ ImportFileNotFound(PathBuf, PathBuf),
+ ResolvedFileNotFound(PathBuf),
+ ImportBadFileUtf8(PathBuf),
+ ImportNotSupported(PathBuf, PathBuf),
+ ImportSyntaxError(jsonnet_parser::ParseError),
+
RuntimeError(String),
StackOverflow,
FractionalIndex,
crates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -785,24 +785,22 @@
}
}
Import(path) => {
- let mut lib_path = loc
+ let mut import_location = 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))?
+ import_location.pop();
+ with_state(|s| s.import_file(&import_location, path))?
}
ImportStr(path) => {
- let mut file_path = loc
+ let mut import_location = loc
.clone()
.expect("imports can't be used without loc_data")
.0
.clone();
- file_path.pop();
- file_path.push(path);
- Val::Str(with_state(|s| s.import_file_str(&file_path))?)
+ import_location.pop();
+ Val::Str(with_state(|s| s.import_file_str(&import_location, path))?)
}
Literal(LiteralType::Super) => return create_error(crate::error::Error::StandaloneSuper),
})
crates/jsonnet-evaluator/src/import.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jsonnet-evaluator/src/import.rs
@@ -0,0 +1,85 @@
+use crate::create_error;
+use crate::error::{Error, Result};
+use fs::File;
+use std::fs;
+use std::io::Read;
+use std::{cell::RefCell, collections::HashMap, path::PathBuf};
+
+pub trait ImportResolver {
+ fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<PathBuf>;
+ fn load_file_contents(&self, resolved: &PathBuf) -> Result<String>;
+}
+
+pub struct DummyImportResolver;
+impl ImportResolver for DummyImportResolver {
+ fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<PathBuf> {
+ create_error(Error::ImportNotSupported(from.clone(), path.clone()))
+ }
+ fn load_file_contents(&self, _resolved: &PathBuf) -> Result<String> {
+ // Can be only caused by library direct consumer, not by supplied jsonnet
+ panic!("dummy resolver can't load any file")
+ }
+}
+impl Default for Box<dyn ImportResolver> {
+ fn default() -> Self {
+ Box::new(DummyImportResolver)
+ }
+}
+
+pub struct FileImportResolver {
+ pub library_paths: Vec<PathBuf>,
+}
+impl ImportResolver for FileImportResolver {
+ fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<PathBuf> {
+ let mut new_path = from.clone();
+ new_path.push(path);
+ if new_path.exists() {
+ Ok(new_path)
+ } else {
+ for library_path in self.library_paths.iter() {
+ let mut cloned = library_path.clone();
+ cloned.push(path);
+ if cloned.exists() {
+ return Ok(cloned);
+ }
+ }
+ create_error(Error::ImportFileNotFound(from.clone(), path.clone()))
+ }
+ }
+ fn load_file_contents(&self, id: &PathBuf) -> Result<String> {
+ let mut file = File::open(id).map_err(|_e| {
+ create_error::<()>(Error::ResolvedFileNotFound(id.clone()))
+ .err()
+ .unwrap()
+ })?;
+ let mut out = String::new();
+ file.read_to_string(&mut out).map_err(|_e| {
+ create_error::<()>(Error::ImportBadFileUtf8(id.clone()))
+ .err()
+ .unwrap()
+ })?;
+ Ok(out)
+ }
+}
+
+pub struct CachingImportResolver {
+ resolution_cache: RefCell<HashMap<(PathBuf, PathBuf), Result<PathBuf>>>,
+ loading_cache: RefCell<HashMap<PathBuf, Result<String>>>,
+ inner: Box<dyn ImportResolver>,
+}
+impl ImportResolver for CachingImportResolver {
+ fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<PathBuf> {
+ self.resolution_cache
+ .borrow_mut()
+ .entry((from.clone(), path.clone()))
+ .or_insert_with(|| self.inner.resolve_file(from, path))
+ .clone()
+ }
+ fn load_file_contents(&self, resolved: &PathBuf) -> Result<String> {
+ self.loading_cache
+ .borrow_mut()
+ .entry(resolved.clone())
+ .or_insert_with(|| self.inner.load_file_contents(resolved))
+ .clone()
+ }
+}
crates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -8,6 +8,7 @@
mod error;
mod evaluate;
mod function;
+mod import;
mod map;
mod obj;
mod val;
@@ -17,6 +18,7 @@
pub use error::*;
pub use evaluate::*;
pub use function::parse_function_call;
+pub use import::*;
use jsonnet_parser::*;
pub use obj::*;
use std::{cell::RefCell, collections::HashMap, fmt::Debug, path::PathBuf, rc::Rc};
@@ -46,16 +48,12 @@
pub struct EvaluationSettings {
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)
- }),
}
}
}
@@ -75,6 +73,7 @@
ext_vars: RefCell<HashMap<String, Val>>,
settings: EvaluationSettings,
+ import_resolver: Box<dyn ImportResolver>,
}
thread_local! {
@@ -101,9 +100,10 @@
#[derive(Default, Clone)]
pub struct EvaluationState(Rc<EvaluationStateInternals>);
impl EvaluationState {
- pub fn new(settings: EvaluationSettings) -> Self {
+ pub fn new(settings: EvaluationSettings, import_resolver: Box<dyn ImportResolver>) -> Self {
EvaluationState(Rc::new(EvaluationStateInternals {
settings,
+ import_resolver,
..Default::default()
}))
}
@@ -171,19 +171,29 @@
}
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();
+ pub(crate) fn import_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Val> {
+ let file_path = self.0.import_resolver.resolve_file(from, path)?;
+ {
+ let files = self.0.files.borrow();
+ if files.contains_key(&file_path) {
+ return self.evaluate_file(&file_path);
+ }
}
- self.evaluate_file_in_current_state(path)
+ let contents = self.0.import_resolver.load_file_contents(&file_path)?;
+ self.add_file(file_path.clone(), contents).map_err(|e| {
+ create_error::<()>(Error::ImportSyntaxError(e))
+ .err()
+ .unwrap()
+ })?;
+ self.evaluate_file(&file_path)
}
- pub(crate) fn import_file_str(&self, path: &PathBuf) -> Result<String> {
- if !self.0.str_files.borrow().contains_key(path) {
- let file_str = (self.0.settings.import_resolver)(path);
+ pub(crate) fn import_file_str(&self, from: &PathBuf, path: &PathBuf) -> Result<String> {
+ let path = self.0.import_resolver.resolve_file(from, path)?;
+ if !self.0.str_files.borrow().contains_key(&path) {
+ let file_str = self.0.import_resolver.load_file_contents(&path)?;
self.0.str_files.borrow_mut().insert(path.clone(), file_str);
}
- Ok(self.0.str_files.borrow().get(path).cloned().unwrap())
+ Ok(self.0.str_files.borrow().get(&path).cloned().unwrap())
}
pub fn parse_evaluate_raw(&self, code: &str) -> Result<Val> {