git.delta.rocks / jrsonnet / refs/commits / 4be0ffe86b04

difftreelog

fix do not write trailing newline in --multi for -S

Yaroslav Bolyukin2023-05-04parent: #c2bc5cc.patch.diff
in: master

2 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
before · cmds/jrsonnet/src/main.rs
1use std::{2	fs::{create_dir_all, File},3	io::{Read, Write},4};56use clap::{CommandFactory, Parser};7use clap_complete::Shell;8use jrsonnet_cli::{GcOpts, ManifestOpts, MiscOpts, OutputOpts, StdOpts, TlaOpts, TraceOpts};9use jrsonnet_evaluator::{10	apply_tla,11	error::{Error as JrError, ErrorKind},12	throw, ResultExt, State, Val,13};1415#[cfg(feature = "mimalloc")]16#[global_allocator]17static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;1819#[derive(Parser)]20enum SubOpts {21	/// Generate completions for specified shell22	Generate {23		/// Target shell name24		shell: Shell,25	},26}2728#[derive(Parser)]29#[clap(next_help_heading = "DEBUG")]30struct DebugOpts {31	/// Required OS stack size.32	/// This shouldn't be changed unless jrsonnet is failing with stack overflow error.33	#[clap(long, name = "size")]34	pub os_stack: Option<usize>,35}3637#[derive(Parser)]38#[clap(next_help_heading = "INPUT")]39struct InputOpts {40	/// Treat input as code, evaluate them instead of reading file41	#[clap(long, short = 'e')]42	pub exec: bool,4344	/// Path to the file to be compiled if `--evaluate` is unset, otherwise code itself45	pub input: Option<String>,46}4748/// Jsonnet commandline interpreter (Rust implementation)49#[derive(Parser)]50#[clap(51	args_conflicts_with_subcommands = true,52	disable_version_flag = true,53	version,54	author55)]56struct Opts {57	#[clap(subcommand)]58	sub: Option<SubOpts>,5960	#[clap(flatten)]61	input: InputOpts,62	#[clap(flatten)]63	misc: MiscOpts,64	#[clap(flatten)]65	tla: TlaOpts,66	#[clap(flatten)]67	std: StdOpts,68	#[clap(flatten)]69	gc: GcOpts,7071	#[clap(flatten)]72	trace: TraceOpts,73	#[clap(flatten)]74	manifest: ManifestOpts,75	#[clap(flatten)]76	output: OutputOpts,77	#[clap(flatten)]78	debug: DebugOpts,79}8081fn main() {82	let opts: Opts = Opts::parse();8384	if let Some(sub) = opts.sub {85		match sub {86			SubOpts::Generate { shell } => {87				use clap_complete::generate;88				let app = &mut Opts::command();89				let buf = &mut std::io::stdout();90				generate(shell, app, "jrsonnet", buf);91				std::process::exit(0)92			}93		}94	}9596	let success = if let Some(size) = opts.debug.os_stack {97		std::thread::Builder::new()98			.stack_size(size * 1024 * 1024)99			.spawn(|| main_catch(opts))100			.expect("new thread spawned")101			.join()102			.expect("thread finished successfully")103	} else {104		main_catch(opts)105	};106	if !success {107		std::process::exit(1);108	}109}110111#[derive(thiserror::Error, Debug)]112enum Error {113	// Handled differently114	#[error("evaluation error")]115	Evaluation(JrError),116	#[error("io error")]117	Io(#[from] std::io::Error),118	#[error("input is not utf8 encoded")]119	Utf8(#[from] std::str::Utf8Error),120	#[error("missing input argument")]121	MissingInputArgument,122}123impl From<JrError> for Error {124	fn from(e: JrError) -> Self {125		Self::Evaluation(e)126	}127}128impl From<ErrorKind> for Error {129	fn from(e: ErrorKind) -> Self {130		Self::from(JrError::from(e))131	}132}133134fn main_catch(opts: Opts) -> bool {135	let s = State::default();136	let trace = opts.trace.trace_format();137	if let Err(e) = main_real(&s, opts) {138		if let Error::Evaluation(e) = e {139			let mut out = String::new();140			trace.write_trace(&mut out, &e).expect("format error");141			eprintln!("{out}")142		} else {143			eprintln!("{e}");144		}145		return false;146	}147	true148}149150fn main_real(s: &State, opts: Opts) -> Result<(), Error> {151	let _gc_leak_guard = opts.gc.leak_on_exit();152	let _gc_print_stats = opts.gc.stats_printer();153	let _stack_depth_override = opts.misc.stack_size_override();154155	let import_resolver = opts.misc.import_resolver();156	s.set_import_resolver(import_resolver);157158	let std = opts.std.context_initializer(s)?;159	if let Some(std) = std {160		s.set_context_initializer(std);161	}162163	let input = opts.input.input.ok_or(Error::MissingInputArgument)?;164	let val = if opts.input.exec {165		s.evaluate_snippet("<cmdline>".to_owned(), &input as &str)?166	} else if input == "-" {167		let mut input = Vec::new();168		std::io::stdin().read_to_end(&mut input)?;169		let input_str = std::str::from_utf8(&input)?;170		s.evaluate_snippet("<stdin>".to_owned(), input_str)?171	} else {172		s.import(&input)?173	};174175	let tla = opts.tla.tla_opts()?;176	let val = apply_tla(s.clone(), &tla, val)?;177178	let manifest_format = opts.manifest.manifest_format();179	if let Some(multi) = opts.output.multi {180		if opts.output.create_output_dirs {181			let mut dir = multi.clone();182			dir.pop();183			create_dir_all(dir)?;184		}185		let Val::Obj(obj) = val else {186			throw!("value should be object for --multi manifest, got {}", val.value_type())187		};188		for (field, data) in obj.iter(189			#[cfg(feature = "exp-preserve-order")]190			opts.manifest.preserve_order,191		) {192			let data = data.with_description(|| format!("getting field {field} for manifest"))?;193194			let mut path = multi.clone();195			path.push(&field as &str);196			if opts.output.create_output_dirs {197				let mut dir = path.clone();198				dir.pop();199				create_dir_all(dir)?;200			}201			println!("{}", path.to_str().expect("path"));202			let mut file = File::create(path)?;203			writeln!(204				file,205				"{}",206				data.manifest(&manifest_format)207					.with_description(|| format!("manifesting {field}"))?208			)?;209		}210	} else if let Some(path) = opts.output.output_file {211		if opts.output.create_output_dirs {212			let mut dir = path.clone();213			dir.pop();214			create_dir_all(dir)?;215		}216		let mut file = File::create(path)?;217		writeln!(file, "{}", val.manifest(manifest_format)?)?;218	} else {219		let output = val.manifest(manifest_format)?;220		if !output.is_empty() {221			println!("{output}");222		}223	}224225	Ok(())226}
modifiedcrates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -12,6 +12,13 @@
 		self.manifest_buf(val, &mut out)?;
 		Ok(out)
 	}
+	/// When outputing to file, is it safe to append a trailing newline (I.e newline won't change
+	/// the meaning).
+	///
+	/// Default implementation returns `true`
+	fn file_trailing_newline(&self) -> bool {
+		true
+	}
 }
 impl<T> ManifestFormat for Box<T>
 where
@@ -21,6 +28,10 @@
 		let inner = &**self;
 		inner.manifest_buf(val, buf)
 	}
+	fn file_trailing_newline(&self) -> bool {
+		let inner = &**self;
+		inner.file_trailing_newline()
+	}
 }
 impl<T> ManifestFormat for &'_ T
 where
@@ -30,6 +41,10 @@
 		let inner = &**self;
 		inner.manifest_buf(val, buf)
 	}
+	fn file_trailing_newline(&self) -> bool {
+		let inner = &**self;
+		inner.file_trailing_newline()
+	}
 }
 
 #[derive(PartialEq, Eq, Clone, Copy)]
@@ -253,6 +268,9 @@
 	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {
 		JsonFormat::std_to_string().manifest_buf(val, out)
 	}
+	fn file_trailing_newline(&self) -> bool {
+		false
+	}
 }
 pub struct StringFormat;
 impl ManifestFormat for StringFormat {
@@ -263,6 +281,9 @@
 		write!(out, "{s}").unwrap();
 		Ok(())
 	}
+	fn file_trailing_newline(&self) -> bool {
+		false
+	}
 }
 
 pub struct YamlStreamFormat<I>(pub I);