git.delta.rocks / jrsonnet / refs/commits / 3943cce16e87

difftreelog

refactor extract shared CLI code to jrsonnet-cli

Лач2020-07-19parent: #3100b67.patch.diff
in: master

8 files changed

modifiedcmds/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"
modifiedcmds/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(())
 }
addedcrates/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"
addedcrates/jrsonnet-cli/src/ext.rsdiffbeforeafterboth
after · crates/jrsonnet-cli/src/ext.rs
1use crate::ConfigureState;2use clap::Clap;3use jrsonnet_evaluator::{EvaluationState, Result};4use std::str::FromStr;56#[derive(Clone)]7pub struct ExtStr {8	pub name: String,9	pub value: String,10}1112impl FromStr for ExtStr {13	type Err = &'static str;14	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {15		let out: Vec<_> = s.split('=').collect();16		match out.len() {17			1 => Ok(ExtStr {18				name: out[0].to_owned(),19				value: std::env::var(out[0]).or(Err("missing env var"))?,20			}),21			2 => Ok(ExtStr {22				name: out[0].to_owned(),23				value: out[1].to_owned(),24			}),2526			_ => Err("bad ext-str syntax"),27		}28	}29}3031#[derive(Clap)]32// #[clap(help_heading = "EXTERNAL VARIABLES")]33pub struct ExtVarOpts {34	/// Add string external variable.35	/// External variables are globally available, so prefer to use top level arguments where possible.36	/// If [=data] is not set, then it will be read from `name` env variable.37	/// Can be accessed from code via `std.extVar("name")`38	#[clap(long, short = 'V', name = "name[=var data]", number_of_values = 1)]39	ext_str: Vec<ExtStr>,40	/// Read string external variable from file.41	/// See also `--ext-str`42	// #[clap(long, name = "name[=var path]", number_of_values = 1)]43	// ext_str_file: Vec<ExtStr>,44	/// Add external variable from code.45	/// See also `--ext-str`46	#[clap(long, name = "name[=var source]", number_of_values = 1)]47	ext_code: Vec<ExtStr>,48}49impl ConfigureState for ExtVarOpts {50	fn configure(&self, state: &EvaluationState) -> Result<()> {51		for ext in self.ext_str.iter() {52			state.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());53		}54		for ext in self.ext_code.iter() {55			state.add_ext_code((&ext.name as &str).into(), (&ext.value as &str).into())?;56		}57		Ok(())58	}59}
addedcrates/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(())
+	}
+}
addedcrates/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(())
+	}
+}
addedcrates/jrsonnet-cli/src/tla.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-cli/src/tla.rs
@@ -0,0 +1,33 @@
+use crate::{ConfigureState, ExtStr};
+use clap::Clap;
+use jrsonnet_evaluator::{EvaluationState, Result};
+
+#[derive(Clap)]
+// #[clap(help_heading = "TOP LEVEL ARGUMENTS")]
+pub struct TLAOpts {
+	/// Add top level string argument.
+	/// Top level arguments will be passed to function before manifestification stage.
+	/// prefer using them instead of ExtVars.
+	/// If [=data] is not set, then it will be read from `name` env variable.
+	#[clap(long, short = 'A', name = "name[=tla data]", number_of_values = 1)]
+	tla_str: Vec<ExtStr>,
+	/// Read top level argument string from file.
+	/// See also `--tla-str`
+	// #[clap(long, name = "name[=tla path]", number_of_values = 1)]
+	// tla_str_file: Vec<ExtStr>,
+	/// Add top level argument from code.
+	/// See also `--tla-str`
+	#[clap(long, name = "name[=tla source]", number_of_values = 1)]
+	tla_code: Vec<ExtStr>,
+}
+impl ConfigureState for TLAOpts {
+	fn configure(&self, state: &EvaluationState) -> Result<()> {
+		for tla in self.tla_str.iter() {
+			state.add_tla_str((&tla.name as &str).into(), (&tla.value as &str).into());
+		}
+		for tla in self.tla_code.iter() {
+			state.add_tla_code((&tla.name as &str).into(), (&tla.value as &str).into())?;
+		}
+		Ok(())
+	}
+}
addedcrates/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(())
+	}
+}