difftreelog
refactor extract shared CLI code to jrsonnet-cli
in: master
8 files changed
cmds/jrsonnet/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -11,9 +11,10 @@
[dependencies]
jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "1.0.0" }
jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "1.0.0" }
-jrsonnet-trace = { path = "../../crates/jrsonnet-trace", version = "1.0.0" }
+jrsonnet-cli = { path = "../../crates/jrsonnet-cli", version = "0.1.0" }
# TODO: Fix mimalloc compile errors, and use them
mimallocator = "0.1.3"
[dependencies.clap]
-version = "3.0.0-beta.1"
+git = "https://github.com/clap-rs/clap"
+rev = "48d308a8ab9e96d4b21336e428feee420dbac51d"
cmds/jrsonnet/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -1,237 +1,68 @@
use clap::Clap;
-use jrsonnet_evaluator::Val;
-use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};
-use jrsonnet_trace::{CompactFormat, ExplainingFormat, PathResolver, TraceFormat};
-use std::env::current_dir;
-use std::{collections::HashMap, path::PathBuf, rc::Rc, str::FromStr};
+use jrsonnet_cli::{ConfigureState, GeneralOpts, InputOpts, ManifestOpts};
+use jrsonnet_evaluator::{EvaluationState, Result};
+use std::{path::PathBuf, rc::Rc};
#[global_allocator]
static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;
-enum Format {
- None,
- Json,
- Yaml,
-}
-
-impl FromStr for Format {
- type Err = &'static str;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(match s {
- "none" => Format::None,
- "json" => Format::Json,
- "yaml" => Format::Yaml,
- _ => return Err("no such format"),
- })
- }
-}
-
-#[derive(PartialEq)]
-enum TraceFormatName {
- Compact,
- Explaining,
-}
-impl FromStr for TraceFormatName {
- type Err = &'static str;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(match s {
- "compact" => TraceFormatName::Compact,
- "explaining" => TraceFormatName::Explaining,
- _ => return Err("no such format"),
- })
- }
-}
-
-#[derive(Clone)]
-struct ExtStr {
- name: String,
- value: String,
-}
-impl FromStr for ExtStr {
- type Err = &'static str;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let out: Vec<_> = s.split('=').collect();
- match out.len() {
- 1 => Ok(ExtStr {
- name: out[0].to_owned(),
- value: std::env::var(out[0]).or(Err("missing env var"))?,
- }),
- 2 => Ok(ExtStr {
- name: out[0].to_owned(),
- value: out[1].to_owned(),
- }),
- _ => Err("bad ext-str syntax"),
- }
- }
+#[derive(Clap)]
+// #[clap(help_heading = "DEBUG")]
+struct DebugOpts {
+ /// Required OS stack size, probally you shouldn't change it, unless jrsonnet is failing with stack overflow
+ #[clap(long, name = "size")]
+ pub os_stack: Option<usize>,
}
#[derive(Clap)]
-#[clap(name = "jrsonnet", version, author)]
-pub struct Opts {
- #[clap(long, about = "Disable global std variable")]
- no_stdlib: bool,
- #[clap(long, about = "Add external string", number_of_values = 1)]
- ext_str: Vec<ExtStr>,
- #[clap(long, about = "Add external string from code", number_of_values = 1)]
- ext_code: Vec<ExtStr>,
- #[clap(long, about = "Add TLA", number_of_values = 1)]
- tla_str: Vec<ExtStr>,
- #[clap(long, about = "Add TLA from code", number_of_values = 1)]
- tla_code: Vec<ExtStr>,
- #[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]
- format: Format,
- #[clap(long, default_value = "compact", possible_values = &["compact", "explaining"], about = "Choose format of displayed stacktraces")]
- trace_format: TraceFormatName,
-
- #[clap(
- long,
- short = "s",
- default_value = "200",
- about = "Number of allowed stack frames"
- )]
- max_stack: usize,
- #[clap(
- long,
- short = "t",
- default_value = "20",
- about = "Max length of stack trace before cropping"
- )]
- max_trace: usize,
-
- #[clap(
- long,
- about = "Required os stack size, probally you shouldn't change it"
- )]
- thread_stack_size: Option<usize>,
-
- #[clap(long, short = "J", about = "Library search dir")]
- jpath: Vec<PathBuf>,
-
- #[clap(
- long,
- default_value = "3",
- about = "When using --format, this option specifies string to pad output with"
- )]
- line_padding: usize,
-
- #[clap(about = "File to compile")]
- input: String,
+struct Opts {
+ #[clap(flatten)]
+ input: InputOpts,
+ #[clap(flatten)]
+ general: GeneralOpts,
+ #[clap(flatten)]
+ manifest: ManifestOpts,
+ #[clap(flatten)]
+ debug: DebugOpts,
}
fn main() {
let opts: Opts = Opts::parse();
- if let Some(size) = opts.thread_stack_size {
+ if let Some(size) = opts.debug.os_stack {
std::thread::Builder::new()
.stack_size(size * 1024 * 1024)
- .spawn(|| main_real(opts))
- .unwrap()
+ .spawn(|| main_catch(opts))
+ .expect("new thread spawned")
.join()
- .unwrap();
+ .expect("thread finished successfully");
} else {
- main_real(opts)
+ main_catch(opts)
}
}
-fn main_real(opts: Opts) {
- let evaluator = jrsonnet_evaluator::EvaluationState::default();
- {
- let mut settings = evaluator.settings_mut();
- settings.max_stack = opts.max_stack;
- settings.max_trace = opts.max_trace;
- settings.import_resolver = Box::new(jrsonnet_evaluator::FileImportResolver {
- library_paths: opts.jpath.clone(),
- });
+fn main_catch(opts: Opts) {
+ let state = EvaluationState::default();
+ if let Err(e) = main_real(&state, opts) {
+ println!("{}", state.stringify_err(&e));
}
- if !opts.no_stdlib {
- evaluator.with_stdlib();
- }
- for ExtStr { name, value } in opts.ext_str.iter().cloned() {
- evaluator
- .settings_mut()
- .ext_vars
- .insert(name.into(), Val::Str(value.into()));
- }
- for ExtStr { name, value } in opts.ext_code.iter().cloned() {
- evaluator.settings_mut().ext_vars.insert(
- name.clone().into(),
- evaluator
- .parse_evaluate_raw(PathBuf::from(format!("ext_code {}", name)).into(), &value)
- .unwrap(),
- );
- }
+}
+
+fn main_real(state: &EvaluationState, opts: Opts) -> Result<()> {
+ opts.general.configure(&state)?;
+ opts.manifest.configure(&state)?;
- let resolver = PathResolver::Relative(std::env::current_dir().unwrap());
- let trace_format: Box<dyn TraceFormat> = match opts.trace_format {
- TraceFormatName::Compact => Box::new(CompactFormat { resolver }),
- TraceFormatName::Explaining => Box::new(ExplainingFormat { resolver }),
+ let val = if opts.input.evaluate {
+ state.evaluate_snippet_raw(
+ Rc::new(PathBuf::from("args")),
+ (&opts.input.input as &str).into(),
+ )?
+ } else {
+ state.evaluate_file_raw(&PathBuf::from(opts.input.input))?
};
- let mut input = current_dir().unwrap();
- input.push(opts.input.clone());
- let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();
- if let Err(e) = evaluator.add_file(Rc::new(input.clone()), code_string.into()) {
- trace_format.print_trace(&evaluator, &e).unwrap();
- std::process::exit(1);
- }
- let result = evaluator.evaluate_file(&input);
- match result {
- Ok(v) => {
- let v = match v {
- Val::Func(f) => {
- let mut desc_map = HashMap::new();
- for ExtStr { name, value } in opts.tla_str.iter().cloned() {
- desc_map.insert(name, el!(Expr::Str(value.into())));
- }
- for ExtStr { name, value } in opts.tla_code.iter().cloned() {
- desc_map.insert(
- name,
- jrsonnet_parser::parse(
- &value,
- &ParserSettings {
- file_name: Rc::new(PathBuf::new()),
- loc_data: false,
- },
- )
- .unwrap(),
- );
- }
- evaluator
- .settings_mut()
- .globals
- .insert("__tmp__tlf__".into(), Val::Func(f));
- evaluator
- .evaluate_raw(el!(Expr::Apply(
- el!(Expr::Var("__tmp__tlf__".into())),
- ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),
- false,
- )))
- .unwrap()
- }
- v => v,
- };
- let v = evaluator.run_in_state(|| match opts.format {
- Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),
- Format::Yaml => Ok(Val::Str(v.into_yaml(opts.line_padding)?)),
- _ => Ok(v),
- });
- let v = match v {
- Ok(v) => v,
- Err(err) => {
- trace_format.print_trace(&evaluator, &err).unwrap();
- std::process::exit(1);
- }
- };
- match v {
- Val::Str(s) => println!("{}", s),
- Val::Num(n) => println!("{}", n),
- _v => eprintln!(
- "jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"
- ),
- }
- }
- Err(err) => {
- trace_format.print_trace(&evaluator, &err).unwrap();
- std::process::exit(1);
- }
- }
+ let val = state.with_tla(val)?;
+
+ println!("{}", state.manifest(val)?);
+
+ Ok(())
}
crates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "jrsonnet-cli"
+description = "Utilities for building jrsonnet CLIs"
+version = "0.1.0"
+authors = ["Лач <iam@lach.pw>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "1.0.0", features = ["explaining-traces"] }
+jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "1.0.0" }
+
+[dependencies.clap]
+git = "https://github.com/clap-rs/clap"
+rev = "48d308a8ab9e96d4b21336e428feee420dbac51d"
crates/jrsonnet-cli/src/ext.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-cli/src/ext.rs
@@ -0,0 +1,59 @@
+use crate::ConfigureState;
+use clap::Clap;
+use jrsonnet_evaluator::{EvaluationState, Result};
+use std::str::FromStr;
+
+#[derive(Clone)]
+pub struct ExtStr {
+ pub name: String,
+ pub value: String,
+}
+
+impl FromStr for ExtStr {
+ type Err = &'static str;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ let out: Vec<_> = s.split('=').collect();
+ match out.len() {
+ 1 => Ok(ExtStr {
+ name: out[0].to_owned(),
+ value: std::env::var(out[0]).or(Err("missing env var"))?,
+ }),
+ 2 => Ok(ExtStr {
+ name: out[0].to_owned(),
+ value: out[1].to_owned(),
+ }),
+
+ _ => Err("bad ext-str syntax"),
+ }
+ }
+}
+
+#[derive(Clap)]
+// #[clap(help_heading = "EXTERNAL VARIABLES")]
+pub struct ExtVarOpts {
+ /// Add string external variable.
+ /// External variables are globally available, so prefer to use top level arguments where possible.
+ /// If [=data] is not set, then it will be read from `name` env variable.
+ /// Can be accessed from code via `std.extVar("name")`
+ #[clap(long, short = 'V', name = "name[=var data]", number_of_values = 1)]
+ ext_str: Vec<ExtStr>,
+ /// Read string external variable from file.
+ /// See also `--ext-str`
+ // #[clap(long, name = "name[=var path]", number_of_values = 1)]
+ // ext_str_file: Vec<ExtStr>,
+ /// Add external variable from code.
+ /// See also `--ext-str`
+ #[clap(long, name = "name[=var source]", number_of_values = 1)]
+ ext_code: Vec<ExtStr>,
+}
+impl ConfigureState for ExtVarOpts {
+ fn configure(&self, state: &EvaluationState) -> Result<()> {
+ for ext in self.ext_str.iter() {
+ state.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
+ }
+ for ext in self.ext_code.iter() {
+ state.add_ext_code((&ext.name as &str).into(), (&ext.value as &str).into())?;
+ }
+ Ok(())
+ }
+}
crates/jrsonnet-cli/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-cli/src/lib.rs
@@ -0,0 +1,90 @@
+mod ext;
+mod manifest;
+mod tla;
+mod trace;
+
+pub use ext::*;
+pub use manifest::*;
+pub use tla::*;
+pub use trace::*;
+
+use clap::Clap;
+use jrsonnet_evaluator::{EvaluationState, FileImportResolver, Result};
+use std::path::PathBuf;
+
+pub trait ConfigureState {
+ fn configure(&self, state: &EvaluationState) -> Result<()>;
+}
+
+#[derive(Clap)]
+// #[clap(help_heading = "INPUT")]
+pub struct InputOpts {
+ #[clap(
+ long,
+ short = 'e',
+ about = "Threat input as code, evaluate them instead of reading file"
+ )]
+ pub evaluate: bool,
+
+ #[clap(about = "File to compile (Or code directly, if --evaluate is specified)")]
+ pub input: String,
+}
+
+#[derive(Clap)]
+// #[clap(help_heading = "OPTIONS")]
+pub struct MiscOpts {
+ /// Disable standard library. By default, standard library will be available via global `std` variable.
+ /// Beware that standard library will still be loaded if choosen manifestification method is not `none`
+ #[clap(long)]
+ no_stdlib: bool,
+
+ /// Number of allowed stack frames, stack overflow error will be returned if reached
+ #[clap(long, short = 's', default_value = "200")]
+ max_stack: usize,
+
+ /// Library search dirs. Any not found `imported` file will be searched in them.
+ /// Can also be specified via JSONNET_PATH, which should contain colon (semicolon on Windows) delimited list of directories
+ #[clap(long, short = 'J')]
+ jpath: Vec<PathBuf>,
+}
+impl ConfigureState for MiscOpts {
+ fn configure(&self, state: &EvaluationState) -> Result<()> {
+ if !self.no_stdlib {
+ state.with_stdlib();
+ }
+
+ state.set_import_resolver(Box::new(FileImportResolver {
+ library_paths: self.jpath.clone(),
+ }));
+
+ state.set_max_stack(self.max_stack);
+ Ok(())
+ }
+}
+
+/// For general configuration of jsonnet
+#[derive(Clap)]
+#[clap(name = "jrsonnet", version, author)]
+pub struct GeneralOpts {
+ #[clap(flatten)]
+ misc: MiscOpts,
+
+ #[clap(flatten)]
+ tla: TLAOpts,
+ #[clap(flatten)]
+ ext: ExtVarOpts,
+
+ #[clap(flatten)]
+ trace: TraceOpts,
+}
+
+impl ConfigureState for GeneralOpts {
+ fn configure(&self, state: &EvaluationState) -> Result<()> {
+ // Configure trace first, because tla-code/ext-code can throw
+ self.trace.configure(state)?;
+ self.misc.configure(state)?;
+ self.tla.configure(state)?;
+ self.ext.configure(state)?;
+ Ok(())
+ }
+}
crates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -0,0 +1,59 @@
+use crate::ConfigureState;
+use clap::Clap;
+use jrsonnet_evaluator::{EvaluationState, ManifestFormat, Result};
+use std::str::FromStr;
+
+pub enum ManifestFormatName {
+ /// Expect string as output, and write them directly
+ None,
+ Json,
+ Yaml,
+}
+
+impl FromStr for ManifestFormatName {
+ type Err = &'static str;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ Ok(match s {
+ "none" => ManifestFormatName::None,
+ "json" => ManifestFormatName::Json,
+ "yaml" => ManifestFormatName::Yaml,
+ _ => return Err("no such format"),
+ })
+ }
+}
+
+#[derive(Clap)]
+// #[clap(group = clap::ArgGroup::new("output_format"), help_heading = "MANIFESTIFICATION OUTPUT")]
+pub struct ManifestOpts {
+ /// Output format, wraps resulting value to corresponding std.manifest call.
+ /// If none - then jsonnet file is expected to return plain string value, otherwise
+ /// output will be serialized to specified format
+ #[clap(long, short = 'f', default_value = "json", possible_values = &["none", "json", "yaml"], group = "output_format")]
+ format: ManifestFormatName,
+ /// Expect string as output, and write them directly.
+ /// Shortcut for --format=none, and can't be set with format together
+ #[clap(long, short = 'S', group = "output_format")]
+ string: bool,
+ /// Numbed of spaces to pad output manifest with.
+ /// 0 for hard tabs, -1 for single line output
+ #[clap(long, default_value = "4")]
+ line_padding: usize,
+}
+impl ConfigureState for ManifestOpts {
+ fn configure(&self, state: &EvaluationState) -> Result<()> {
+ if self.string {
+ state.set_manifest_format(ManifestFormat::None);
+ } else {
+ match self.format {
+ ManifestFormatName::None => state.set_manifest_format(ManifestFormat::None),
+ ManifestFormatName::Json => {
+ state.set_manifest_format(ManifestFormat::Json(self.line_padding))
+ }
+ ManifestFormatName::Yaml => {
+ state.set_manifest_format(ManifestFormat::Yaml(self.line_padding))
+ }
+ }
+ }
+ Ok(())
+ }
+}
crates/jrsonnet-cli/src/tla.rsdiffbeforeafterboth1use crate::{ConfigureState, ExtStr};2use clap::Clap;3use jrsonnet_evaluator::{EvaluationState, Result};45#[derive(Clap)]6// #[clap(help_heading = "TOP LEVEL ARGUMENTS")]7pub struct TLAOpts {8 /// Add top level string argument.9 /// Top level arguments will be passed to function before manifestification stage.10 /// prefer using them instead of ExtVars.11 /// If [=data] is not set, then it will be read from `name` env variable.12 #[clap(long, short = 'A', name = "name[=tla data]", number_of_values = 1)]13 tla_str: Vec<ExtStr>,14 /// Read top level argument string from file.15 /// See also `--tla-str`16 // #[clap(long, name = "name[=tla path]", number_of_values = 1)]17 // tla_str_file: Vec<ExtStr>,18 /// Add top level argument from code.19 /// See also `--tla-str`20 #[clap(long, name = "name[=tla source]", number_of_values = 1)]21 tla_code: Vec<ExtStr>,22}23impl ConfigureState for TLAOpts {24 fn configure(&self, state: &EvaluationState) -> Result<()> {25 for tla in self.tla_str.iter() {26 state.add_tla_str((&tla.name as &str).into(), (&tla.value as &str).into());27 }28 for tla in self.tla_code.iter() {29 state.add_tla_code((&tla.name as &str).into(), (&tla.value as &str).into())?;30 }31 Ok(())32 }33}crates/jrsonnet-cli/src/trace.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-cli/src/trace.rs
@@ -0,0 +1,53 @@
+use crate::ConfigureState;
+use clap::Clap;
+use jrsonnet_evaluator::{
+ trace::{CompactFormat, ExplainingFormat, PathResolver},
+ EvaluationState, Result,
+};
+use std::str::FromStr;
+
+#[derive(PartialEq)]
+pub enum TraceFormatName {
+ Compact,
+ Explaining,
+}
+
+impl FromStr for TraceFormatName {
+ type Err = &'static str;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ Ok(match s {
+ "compact" => TraceFormatName::Compact,
+ "explaining" => TraceFormatName::Explaining,
+ _ => return Err("no such format"),
+ })
+ }
+}
+
+#[derive(Clap)]
+// #[clap(help_heading = "STACK TRACE VISUAL")]
+pub struct TraceOpts {
+ /// How stack traces should be displayed in console.
+ /// compact format only shows `filename:line:column`s, where explaining displays source code
+ /// with attached trace annotations, so it is more verbose
+ #[clap(long, possible_values = &["compact", "explaining"])]
+ trace_format: Option<TraceFormatName>,
+ /// Amount of stack trace elements to be displayed, if zero - then full stack trace will be displayed
+ #[clap(long, short = 't', default_value = "20")]
+ max_trace: usize,
+}
+impl ConfigureState for TraceOpts {
+ fn configure(&self, state: &EvaluationState) -> Result<()> {
+ let resolver = PathResolver::Absolute;
+ match self.trace_format.as_ref().unwrap_or(&TraceFormatName::Compact) {
+ TraceFormatName::Compact => state.set_trace_format(Box::new(CompactFormat {
+ resolver,
+ padding: 4,
+ })),
+ TraceFormatName::Explaining => {
+ state.set_trace_format(Box::new(ExplainingFormat { resolver }))
+ }
+ }
+ state.set_max_trace(self.max_trace);
+ Ok(())
+ }
+}