git.delta.rocks / jrsonnet / refs/commits / 78be61658f34

difftreelog

refactor remove ManifestFormat from state

Yaroslav Bolyukin2022-11-09parent: #5f620e2.patch.diff
in: master

7 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -93,7 +93,7 @@
 enum Error {
 	// Handled differently
 	#[error("evaluation error")]
-	Evaluation(jrsonnet_evaluator::error::LocError),
+	Evaluation(LocError),
 	#[error("io error")]
 	Io(#[from] std::io::Error),
 	#[error("input is not utf8 encoded")]
@@ -106,6 +106,11 @@
 		Self::Evaluation(e)
 	}
 }
+impl From<jrsonnet_evaluator::error::Error> for Error {
+	fn from(e: jrsonnet_evaluator::error::Error) -> Self {
+		Self::from(LocError::from(e))
+	}
+}
 
 fn main_catch(opts: Opts) -> bool {
 	let s = State::default();
@@ -144,9 +149,17 @@
 			dir.pop();
 			create_dir_all(dir)?;
 		}
-		for (file, data) in s.manifest_multi(val)?.iter() {
+		let Val::Obj(obj) = val else {
+			throw!("value should be object for --multi manifest, got {}", val.value_type())
+		};
+		for (field, data) in obj.iter(
+			#[cfg(feature = "exp-preserve-order")]
+			opts.manifest.preserve_order,
+		) {
+			let data = data.with_description(|| format!("getting field {field} for manifest"))?;
+
 			let mut path = multi.clone();
-			path.push(file as &str);
+			path.push(&field as &str);
 			if opts.output.create_output_dirs {
 				let mut dir = path.clone();
 				dir.pop();
@@ -154,7 +167,12 @@
 			}
 			println!("{}", path.to_str().expect("path"));
 			let mut file = File::create(path)?;
-			writeln!(file, "{}", data)?;
+			writeln!(
+				file,
+				"{}",
+				data.manifest(&manifest_format)
+					.with_description(|| format!("manifesting {field}"))?
+			)?;
 		}
 	} else if let Some(path) = opts.output.output_file {
 		if opts.output.create_output_dirs {
@@ -163,9 +181,9 @@
 			create_dir_all(dir)?;
 		}
 		let mut file = File::create(path)?;
-		writeln!(file, "{}", s.manifest(val)?)?;
+		writeln!(file, "{}", val.manifest(manifest_format)?)?;
 	} else {
-		let output = s.manifest(val)?;
+		let output = val.manifest(manifest_format)?;
 		if !output.is_empty() {
 			println!("{}", output);
 		}
modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -1,7 +1,11 @@
 use std::{path::PathBuf, str::FromStr};
 
 use clap::{Parser, ValueEnum};
-use jrsonnet_evaluator::{error::Result, ManifestFormat, State};
+use jrsonnet_evaluator::{
+	error::Result,
+	stdlib::manifest::{JsonFormat, StringFormat, ToStringFormat, YamlFormat, YamlStreamFormat},
+	ManifestFormat, State,
+};
 
 use crate::ConfigureState;
 
@@ -36,7 +40,7 @@
 	#[clap(long, short = 'S', conflicts_with = "format")]
 	string: bool,
 	/// Write output as YAML stream, can be used with --format json/yaml
-	#[clap(long, short = 'y')]
+	#[clap(long, short = 'y', conflicts_with = "string")]
 	yaml_stream: bool,
 	/// Number of spaces to pad output manifest with.
 	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]
@@ -45,34 +49,35 @@
 	/// Preserve order in object manifestification
 	#[cfg(feature = "exp-preserve-order")]
 	#[clap(long)]
-	exp_preserve_order: bool,
+	preserve_order: bool,
 }
 impl ConfigureState for ManifestOpts {
-	type Guards = ();
-	fn configure(&self, s: &State) -> Result<()> {
-		if self.string {
-			s.set_manifest_format(ManifestFormat::String);
+	type Guards = Box<dyn ManifestFormat>;
+	fn configure(&self, _s: &State) -> Result<Self::Guards> {
+		let format: Box<dyn ManifestFormat> = if self.string {
+			Box::new(StringFormat)
 		} else {
 			#[cfg(feature = "exp-preserve-order")]
-			let preserve_order = self.exp_preserve_order;
+			let preserve_order = self.preserve_order;
 			match self.format {
-				ManifestFormatName::String => s.set_manifest_format(ManifestFormat::String),
-				ManifestFormatName::Json => s.set_manifest_format(ManifestFormat::Json {
-					padding: self.line_padding.unwrap_or(3),
+				ManifestFormatName::String => Box::new(ToStringFormat),
+				ManifestFormatName::Json => Box::new(JsonFormat::cli(
+					self.line_padding.unwrap_or(3),
 					#[cfg(feature = "exp-preserve-order")]
 					preserve_order,
-				}),
-				ManifestFormatName::Yaml => s.set_manifest_format(ManifestFormat::Yaml {
-					padding: self.line_padding.unwrap_or(2),
+				)),
+				ManifestFormatName::Yaml => Box::new(YamlFormat::cli(
+					self.line_padding.unwrap_or(2),
 					#[cfg(feature = "exp-preserve-order")]
 					preserve_order,
-				}),
+				)),
 			}
-		}
-		if self.yaml_stream {
-			s.set_manifest_format(ManifestFormat::YamlStream(Box::new(s.manifest_format())))
-		}
-		Ok(())
+		};
+		Ok(if self.yaml_stream {
+			Box::new(YamlStreamFormat(format))
+		} else {
+			format
+		})
 	}
 }
 
modifiedcrates/jrsonnet-cli/src/stdlib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/stdlib.rs
+++ b/crates/jrsonnet-cli/src/stdlib.rs
@@ -60,8 +60,6 @@
 pub struct StdOpts {
 	/// Disable standard library.
 	/// By default standard library will be available via global `std` variable.
-	/// Note that standard library will still be loaded
-	/// if chosen manifestification method is not `none`.
 	#[clap(long)]
 	no_stdlib: bool,
 	/// Add string external variable.
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -149,10 +149,6 @@
 	pub context_initializer: TraceBox<dyn ContextInitializer>,
 	/// Used to resolve file locations/contents
 	pub import_resolver: TraceBox<dyn ImportResolver>,
-	/// Used in manifestification functions
-	pub manifest_format: ManifestFormat,
-	/// Used for bindings
-	pub trace_format: TraceBox<dyn TraceFormat>,
 }
 impl Default for EvaluationSettings {
 	fn default() -> Self {
@@ -447,26 +443,5 @@
 	}
 	pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {
 		Ref::map(self.settings(), |s| &*s.context_initializer)
-	}
-
-	pub fn manifest_format(&self) -> ManifestFormat {
-		self.settings().manifest_format.clone()
-	}
-	pub fn set_manifest_format(&self, format: ManifestFormat) {
-		self.settings_mut().manifest_format = format;
-	}
-
-	pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> {
-		Ref::map(self.settings(), |s| &*s.trace_format)
-	}
-	pub fn set_trace_format(&self, format: impl TraceFormat) {
-		self.settings_mut().trace_format = tb!(format);
-	}
-
-	pub fn max_trace(&self) -> usize {
-		self.settings().max_trace
-	}
-	pub fn set_max_trace(&self, trace: usize) {
-		self.settings_mut().max_trace = trace;
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/stdlib/manifest.rs
1use crate::{2	error::{Error::*, Result},3	throw, State, Val,4};56#[derive(PartialEq, Eq, Clone, Copy)]7pub enum ManifestType {8	// Applied in manifestification9	Manifest,10	/// Used for std.manifestJson11	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest12	Std,13	/// No line breaks, used in `obj+''`14	ToString,15	/// Minified json16	Minify,17}1819pub struct ManifestJsonOptions<'s> {20	pub padding: &'s str,21	pub mtype: ManifestType,22	pub newline: &'s str,23	pub key_val_sep: &'s str,24	#[cfg(feature = "exp-preserve-order")]25	pub preserve_order: bool,26}2728pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {29	let mut out = String::new();30	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;31	Ok(out)32}33fn manifest_json_ex_buf(34	val: &Val,35	buf: &mut String,36	cur_padding: &mut String,37	options: &ManifestJsonOptions<'_>,38) -> Result<()> {39	use std::fmt::Write;40	let mtype = options.mtype;41	match val {42		Val::Bool(v) => {43			if *v {44				buf.push_str("true");45			} else {46				buf.push_str("false");47			}48		}49		Val::Null => buf.push_str("null"),50		Val::Str(s) => escape_string_json_buf(s, buf),51		Val::Num(n) => write!(buf, "{n}").unwrap(),52		Val::Arr(items) => {53			buf.push('[');54			if !items.is_empty() {55				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {56					buf.push_str(options.newline);57				}5859				let old_len = cur_padding.len();60				cur_padding.push_str(options.padding);61				for (i, item) in items.iter().enumerate() {62					if i != 0 {63						buf.push(',');64						if mtype == ManifestType::ToString {65							buf.push(' ');66						} else if mtype != ManifestType::Minify {67							buf.push_str(options.newline);68						}69					}70					buf.push_str(cur_padding);71					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;72				}73				cur_padding.truncate(old_len);7475				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {76					buf.push_str(options.newline);77					buf.push_str(cur_padding);78				}79			} else if mtype == ManifestType::Std {80				buf.push_str("\n\n");81				buf.push_str(cur_padding);82			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {83				buf.push(' ');84			}85			buf.push(']');86		}87		Val::Obj(obj) => {88			obj.run_assertions()?;89			buf.push('{');90			let fields = obj.fields(91				#[cfg(feature = "exp-preserve-order")]92				options.preserve_order,93			);94			if !fields.is_empty() {95				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {96					buf.push_str(options.newline);97				}9899				let old_len = cur_padding.len();100				cur_padding.push_str(options.padding);101				for (i, field) in fields.into_iter().enumerate() {102					if i != 0 {103						buf.push(',');104						if mtype == ManifestType::ToString {105							buf.push(' ');106						} else if mtype != ManifestType::Minify {107							buf.push_str(options.newline);108						}109					}110					buf.push_str(cur_padding);111					escape_string_json_buf(&field, buf);112					buf.push_str(options.key_val_sep);113					State::push_description(114						|| format!("field <{}> manifestification", field.clone()),115						|| {116							let value = obj.get(field.clone())?.unwrap();117							manifest_json_ex_buf(&value, buf, cur_padding, options)?;118							Ok(())119						},120					)?;121				}122				cur_padding.truncate(old_len);123124				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {125					buf.push_str(options.newline);126					buf.push_str(cur_padding);127				}128			} else if mtype == ManifestType::Std {129				buf.push_str("\n\n");130				buf.push_str(cur_padding);131			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {132				buf.push(' ');133			}134			buf.push('}');135		}136		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),137	};138	Ok(())139}140141pub fn escape_string_json(s: &str) -> String {142	let mut buf = String::new();143	escape_string_json_buf(s, &mut buf);144	buf145}146147fn escape_string_json_buf(s: &str, buf: &mut String) {148	use std::fmt::Write;149	buf.push('"');150	for c in s.chars() {151		match c {152			'"' => buf.push_str("\\\""),153			'\\' => buf.push_str("\\\\"),154			'\u{0008}' => buf.push_str("\\b"),155			'\u{000c}' => buf.push_str("\\f"),156			'\n' => buf.push_str("\\n"),157			'\r' => buf.push_str("\\r"),158			'\t' => buf.push_str("\\t"),159			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {160				write!(buf, "\\u{:04x}", c as u32).unwrap();161			}162			c => buf.push(c),163		}164	}165	buf.push('"');166}167168pub struct ManifestYamlOptions<'s> {169	/// Padding before fields, i.e170	/// ```yaml171	/// a:172	///   b:173	/// ## <- this174	/// ```175	pub padding: &'s str,176	/// Padding before array elements in objects177	/// ```yaml178	/// a:179	///   - 1180	/// ## <- this181	/// ```182	pub arr_element_padding: &'s str,183	/// Should yaml keys appear unescaped, when possible184	/// ```yaml185	/// "safe_key": 1186	/// # vs187	/// safe_key: 1188	/// ```189	pub quote_keys: bool,190	/// If true - then order of fields is preserved as written,191	/// instead of sorting alphabetically192	#[cfg(feature = "exp-preserve-order")]193	pub preserve_order: bool,194}195196/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>197/// With added date check198fn yaml_needs_quotes(string: &str) -> bool {199	fn need_quotes_spaces(string: &str) -> bool {200		string.starts_with(' ') || string.ends_with(' ')201	}202203	string.is_empty()204		|| need_quotes_spaces(string)205		|| string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))206		|| string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))207		|| [208			// http://yaml.org/type/bool.html209			// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse210			// them as string, not booleans, although it is violating the YAML 1.1 specification.211			// See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.212			"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",213			"on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html214			"null", "Null", "NULL", "~",215		].contains(&string)216		|| (string.chars().all(|c| matches!(c, '0'..='9' | '-'))217			&& string.chars().filter(|c| *c == '-').count() == 2)218		|| string.starts_with('.')219		|| string.starts_with("0x")220		|| string.parse::<i64>().is_ok()221		|| string.parse::<f64>().is_ok()222}223224pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {225	let mut out = String::new();226	manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;227	Ok(out)228}229230#[allow(clippy::too_many_lines)]231fn manifest_yaml_ex_buf(232	val: &Val,233	buf: &mut String,234	cur_padding: &mut String,235	options: &ManifestYamlOptions<'_>,236) -> Result<()> {237	use std::fmt::Write;238	match val {239		Val::Bool(v) => {240			if *v {241				buf.push_str("true");242			} else {243				buf.push_str("false");244			}245		}246		Val::Null => buf.push_str("null"),247		Val::Str(s) => {248			if s.is_empty() {249				buf.push_str("\"\"");250			} else if let Some(s) = s.strip_suffix('\n') {251				buf.push('|');252				for line in s.split('\n') {253					buf.push('\n');254					buf.push_str(cur_padding);255					buf.push_str(options.padding);256					buf.push_str(line);257				}258			} else if !options.quote_keys && !yaml_needs_quotes(s) {259				buf.push_str(s);260			} else {261				escape_string_json_buf(s, buf);262			}263		}264		Val::Num(n) => write!(buf, "{}", *n).unwrap(),265		Val::Arr(a) => {266			if a.is_empty() {267				buf.push_str("[]");268			} else {269				for (i, item) in a.iter().enumerate() {270					if i != 0 {271						buf.push('\n');272						buf.push_str(cur_padding);273					}274					let item = item?;275					buf.push('-');276					match &item {277						Val::Arr(a) if !a.is_empty() => {278							buf.push('\n');279							buf.push_str(cur_padding);280							buf.push_str(options.padding);281						}282						_ => buf.push(' '),283					}284					let extra_padding = match &item {285						Val::Arr(a) => !a.is_empty(),286						Val::Obj(o) => !o.is_empty(),287						_ => false,288					};289					let prev_len = cur_padding.len();290					if extra_padding {291						cur_padding.push_str(options.padding);292					}293					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;294					cur_padding.truncate(prev_len);295				}296			}297		}298		Val::Obj(o) => {299			if o.is_empty() {300				buf.push_str("{}");301			} else {302				for (i, key) in o303					.fields(304						#[cfg(feature = "exp-preserve-order")]305						options.preserve_order,306					)307					.iter()308					.enumerate()309				{310					if i != 0 {311						buf.push('\n');312						buf.push_str(cur_padding);313					}314					if !options.quote_keys && !yaml_needs_quotes(key) {315						buf.push_str(key);316					} else {317						escape_string_json_buf(key, buf);318					}319					buf.push(':');320					let prev_len = cur_padding.len();321					let item = o.get(key.clone())?.expect("field exists");322					match &item {323						Val::Arr(a) if !a.is_empty() => {324							buf.push('\n');325							buf.push_str(cur_padding);326							buf.push_str(options.arr_element_padding);327							cur_padding.push_str(options.arr_element_padding);328						}329						Val::Obj(o) if !o.is_empty() => {330							buf.push('\n');331							buf.push_str(cur_padding);332							buf.push_str(options.padding);333							cur_padding.push_str(options.padding);334						}335						_ => buf.push(' '),336					}337					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;338					cur_padding.truncate(prev_len);339				}340			}341		}342		Val::Func(_) => throw!("tried to manifest function"),343	}344	Ok(())345}
after · crates/jrsonnet-evaluator/src/stdlib/manifest.rs
1use std::{borrow::Cow, fmt::Write};23use crate::{4	error::{Error::*, Result},5	throw, ManifestFormat, State, Val,6};78#[derive(PartialEq, Eq, Clone, Copy)]9pub enum ManifestType {10	// Applied in manifestification11	Manifest,12	/// Used for std.manifestJson13	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest14	Std,15	/// No line breaks, used in `obj+''`16	ToString,17	/// Minified json18	Minify,19}2021pub struct JsonFormat<'s> {22	padding: Cow<'s, str>,23	mtype: ManifestType,24	newline: &'s str,25	key_val_sep: &'s str,26	#[cfg(feature = "exp-preserve-order")]27	preserve_order: bool,28}2930impl<'s> JsonFormat<'s> {31	// Minifying format32	pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {33		Self {34			padding: Cow::Borrowed(""),35			mtype: ManifestType::Minify,36			newline: "\n",37			key_val_sep: ":",38			#[cfg(feature = "exp-preserve-order")]39			preserve_order,40		}41	}42	// Same format as std.toString43	pub fn std_to_string() -> Self {44		Self {45			padding: Cow::Borrowed(""),46			mtype: ManifestType::ToString,47			newline: "\n",48			key_val_sep: ": ",49			#[cfg(feature = "exp-preserve-order")]50			preserve_order: false,51		}52	}53	pub fn std_to_json(54		padding: String,55		newline: &'s str,56		key_val_sep: &'s str,57		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,58	) -> Self {59		Self {60			padding: Cow::Owned(padding),61			mtype: ManifestType::Std,62			newline,63			key_val_sep,64			#[cfg(feature = "exp-preserve-order")]65			preserve_order,66		}67	}68	// Same format as CLI manifestification69	pub fn cli(70		padding: usize,71		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,72	) -> Self {73		if padding == 0 {74			return Self::minify(75				#[cfg(feature = "exp-preserve-order")]76				preserve_order,77			);78		}79		Self {80			padding: Cow::Owned(" ".repeat(padding)),81			mtype: ManifestType::Manifest,82			newline: "\n",83			key_val_sep: ": ",84			#[cfg(feature = "exp-preserve-order")]85			preserve_order,86		}87	}88}89impl Default for JsonFormat<'static> {90	fn default() -> Self {91		Self {92			padding: Cow::Borrowed("    "),93			mtype: ManifestType::Manifest,94			newline: "\n",95			key_val_sep: ": ",96			#[cfg(feature = "exp-preserve-order")]97			preserve_order: false,98		}99	}100}101102pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {103	let mut out = String::new();104	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;105	Ok(out)106}107fn manifest_json_ex_buf(108	val: &Val,109	buf: &mut String,110	cur_padding: &mut String,111	options: &JsonFormat<'_>,112) -> Result<()> {113	let mtype = options.mtype;114	match val {115		Val::Bool(v) => {116			if *v {117				buf.push_str("true");118			} else {119				buf.push_str("false");120			}121		}122		Val::Null => buf.push_str("null"),123		Val::Str(s) => escape_string_json_buf(s, buf),124		Val::Num(n) => write!(buf, "{n}").unwrap(),125		Val::Arr(items) => {126			buf.push('[');127			if !items.is_empty() {128				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {129					buf.push_str(options.newline);130				}131132				let old_len = cur_padding.len();133				cur_padding.push_str(&options.padding);134				for (i, item) in items.iter().enumerate() {135					if i != 0 {136						buf.push(',');137						if mtype == ManifestType::ToString {138							buf.push(' ');139						} else if mtype != ManifestType::Minify {140							buf.push_str(options.newline);141						}142					}143					buf.push_str(cur_padding);144					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;145				}146				cur_padding.truncate(old_len);147148				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {149					buf.push_str(options.newline);150					buf.push_str(cur_padding);151				}152			} else if mtype == ManifestType::Std {153				buf.push_str("\n\n");154				buf.push_str(cur_padding);155			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {156				buf.push(' ');157			}158			buf.push(']');159		}160		Val::Obj(obj) => {161			obj.run_assertions()?;162			buf.push('{');163			let fields = obj.fields(164				#[cfg(feature = "exp-preserve-order")]165				options.preserve_order,166			);167			if !fields.is_empty() {168				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {169					buf.push_str(options.newline);170				}171172				let old_len = cur_padding.len();173				cur_padding.push_str(&options.padding);174				for (i, field) in fields.into_iter().enumerate() {175					if i != 0 {176						buf.push(',');177						if mtype == ManifestType::ToString {178							buf.push(' ');179						} else if mtype != ManifestType::Minify {180							buf.push_str(options.newline);181						}182					}183					buf.push_str(cur_padding);184					escape_string_json_buf(&field, buf);185					buf.push_str(options.key_val_sep);186					State::push_description(187						|| format!("field <{}> manifestification", field.clone()),188						|| {189							let value = obj.get(field.clone())?.unwrap();190							manifest_json_ex_buf(&value, buf, cur_padding, options)?;191							Ok(())192						},193					)?;194				}195				cur_padding.truncate(old_len);196197				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {198					buf.push_str(options.newline);199					buf.push_str(cur_padding);200				}201			} else if mtype == ManifestType::Std {202				buf.push_str("\n\n");203				buf.push_str(cur_padding);204			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {205				buf.push(' ');206			}207			buf.push('}');208		}209		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),210	};211	Ok(())212}213214impl ManifestFormat for JsonFormat<'_> {215	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {216		manifest_json_ex_buf(&val, buf, &mut String::new(), &self)217	}218}219220pub struct ToStringFormat;221impl ManifestFormat for ToStringFormat {222	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {223		JsonFormat::std_to_string().manifest_buf(val, out)224	}225}226pub struct StringFormat;227impl ManifestFormat for StringFormat {228	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {229		let Val::Str(s) = val else {230			throw!("output should be string for string manifest format, got {}", val.value_type())231		};232		out.write_str(&s).unwrap();233		Ok(())234	}235}236237pub struct YamlStreamFormat<I>(pub I);238impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {239	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {240		let Val::Arr(arr) = val else {241			throw!("output should be array for yaml stream format, got {}", val.value_type())242		};243		if !arr.is_empty() {244			for v in arr.iter() {245				let v = v?;246				out.push_str("---\n");247				self.0.manifest_buf(v, out)?;248				out.push('\n');249			}250			out.push_str("...");251		}252		Ok(())253	}254}255256pub fn escape_string_json(s: &str) -> String {257	let mut buf = String::new();258	escape_string_json_buf(s, &mut buf);259	buf260}261262fn escape_string_json_buf(s: &str, buf: &mut String) {263	buf.push('"');264	for c in s.chars() {265		match c {266			'"' => buf.push_str("\\\""),267			'\\' => buf.push_str("\\\\"),268			'\u{0008}' => buf.push_str("\\b"),269			'\u{000c}' => buf.push_str("\\f"),270			'\n' => buf.push_str("\\n"),271			'\r' => buf.push_str("\\r"),272			'\t' => buf.push_str("\\t"),273			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {274				write!(buf, "\\u{:04x}", c as u32).unwrap();275			}276			c => buf.push(c),277		}278	}279	buf.push('"');280}281282pub struct YamlFormat<'s> {283	/// Padding before fields, i.e284	/// ```yaml285	/// a:286	///   b:287	/// ## <- this288	/// ```289	padding: Cow<'s, str>,290	/// Padding before array elements in objects291	/// ```yaml292	/// a:293	///   - 1294	/// ## <- this295	/// ```296	arr_element_padding: Cow<'s, str>,297	/// Should yaml keys appear unescaped, when possible298	/// ```yaml299	/// "safe_key": 1300	/// # vs301	/// safe_key: 1302	/// ```303	quote_keys: bool,304	/// If true - then order of fields is preserved as written,305	/// instead of sorting alphabetically306	#[cfg(feature = "exp-preserve-order")]307	preserve_order: bool,308}309impl YamlFormat<'_> {310	pub fn cli(311		padding: usize,312		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,313	) -> Self {314		let padding = " ".repeat(padding);315		Self {316			padding: Cow::Owned(padding.clone()),317			arr_element_padding: Cow::Owned(padding),318			quote_keys: false,319			#[cfg(feature = "exp-preserve-order")]320			preserve_order,321		}322	}323	pub fn std_to_yaml(324		indent_array_in_object: bool,325		quote_keys: bool,326		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,327	) -> Self {328		Self {329			padding: Cow::Borrowed("  "),330			arr_element_padding: Cow::Borrowed(if indent_array_in_object { "  " } else { "" }),331			quote_keys,332			#[cfg(feature = "exp-preserve-order")]333			preserve_order,334		}335	}336}337impl ManifestFormat for YamlFormat<'_> {338	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {339		manifest_yaml_ex_buf(&val, buf, &mut String::new(), self)340	}341}342343/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>344/// With added date check345fn yaml_needs_quotes(string: &str) -> bool {346	fn need_quotes_spaces(string: &str) -> bool {347		string.starts_with(' ') || string.ends_with(' ')348	}349350	string.is_empty()351		|| need_quotes_spaces(string)352		|| string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))353		|| string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))354		|| [355			// http://yaml.org/type/bool.html356			// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse357			// them as string, not booleans, although it is violating the YAML 1.1 specification.358			// See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.359			"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",360			"on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html361			"null", "Null", "NULL", "~",362		].contains(&string)363		|| (string.chars().all(|c| matches!(c, '0'..='9' | '-'))364			&& string.chars().filter(|c| *c == '-').count() == 2)365		|| string.starts_with('.')366		|| string.starts_with("0x")367		|| string.parse::<i64>().is_ok()368		|| string.parse::<f64>().is_ok()369}370371pub fn manifest_yaml_ex(val: &Val, options: &YamlFormat<'_>) -> Result<String> {372	let mut out = String::new();373	manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;374	Ok(out)375}376377#[allow(clippy::too_many_lines)]378fn manifest_yaml_ex_buf(379	val: &Val,380	buf: &mut String,381	cur_padding: &mut String,382	options: &YamlFormat<'_>,383) -> Result<()> {384	match val {385		Val::Bool(v) => {386			if *v {387				buf.push_str("true");388			} else {389				buf.push_str("false");390			}391		}392		Val::Null => buf.push_str("null"),393		Val::Str(s) => {394			if s.is_empty() {395				buf.push_str("\"\"");396			} else if let Some(s) = s.strip_suffix('\n') {397				buf.push('|');398				for line in s.split('\n') {399					buf.push('\n');400					buf.push_str(cur_padding);401					buf.push_str(&options.padding);402					buf.push_str(line);403				}404			} else if !options.quote_keys && !yaml_needs_quotes(s) {405				buf.push_str(s);406			} else {407				escape_string_json_buf(s, buf);408			}409		}410		Val::Num(n) => write!(buf, "{}", *n).unwrap(),411		Val::Arr(a) => {412			if a.is_empty() {413				buf.push_str("[]");414			} else {415				for (i, item) in a.iter().enumerate() {416					if i != 0 {417						buf.push('\n');418						buf.push_str(cur_padding);419					}420					let item = item?;421					buf.push('-');422					match &item {423						Val::Arr(a) if !a.is_empty() => {424							buf.push('\n');425							buf.push_str(cur_padding);426							buf.push_str(&options.padding);427						}428						_ => buf.push(' '),429					}430					let extra_padding = match &item {431						Val::Arr(a) => !a.is_empty(),432						Val::Obj(o) => !o.is_empty(),433						_ => false,434					};435					let prev_len = cur_padding.len();436					if extra_padding {437						cur_padding.push_str(&options.padding);438					}439					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;440					cur_padding.truncate(prev_len);441				}442			}443		}444		Val::Obj(o) => {445			if o.is_empty() {446				buf.push_str("{}");447			} else {448				for (i, key) in o449					.fields(450						#[cfg(feature = "exp-preserve-order")]451						options.preserve_order,452					)453					.iter()454					.enumerate()455				{456					if i != 0 {457						buf.push('\n');458						buf.push_str(cur_padding);459					}460					if !options.quote_keys && !yaml_needs_quotes(key) {461						buf.push_str(key);462					} else {463						escape_string_json_buf(key, buf);464					}465					buf.push(':');466					let prev_len = cur_padding.len();467					let item = o.get(key.clone())?.expect("field exists");468					match &item {469						Val::Arr(a) if !a.is_empty() => {470							buf.push('\n');471							buf.push_str(cur_padding);472							buf.push_str(&options.arr_element_padding);473							cur_padding.push_str(&options.arr_element_padding);474						}475						Val::Obj(o) if !o.is_empty() => {476							buf.push('\n');477							buf.push_str(cur_padding);478							buf.push_str(&options.padding);479							cur_padding.push_str(&options.padding);480						}481						_ => buf.push(' '),482					}483					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;484					cur_padding.truncate(prev_len);485				}486			}487		}488		Val::Func(_) => throw!("tried to manifest function"),489	}490	Ok(())491}
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -1,4 +1,4 @@
-use std::{cell::RefCell, fmt::Debug, rc::Rc};
+use std::{cell::RefCell, fmt::Debug};
 
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::{IBytes, IStr};
@@ -8,9 +8,6 @@
 	error::{Error::*, LocError},
 	function::FuncVal,
 	gc::{GcHashMap, TraceBox},
-	stdlib::manifest::{
-		manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,
-	},
 	throw,
 	typed::BoundedUsize,
 	ObjValue, Result, Unbound, WeakObjValue,
@@ -122,34 +119,32 @@
 	}
 }
 
-#[derive(Clone, Trace)]
-pub enum ManifestFormat {
-	YamlStream(Box<ManifestFormat>),
-	Yaml {
-		padding: usize,
-		#[cfg(feature = "exp-preserve-order")]
-		preserve_order: bool,
-	},
-	Json {
-		padding: usize,
-		#[cfg(feature = "exp-preserve-order")]
-		preserve_order: bool,
-	},
-	ToString,
-	String,
+pub trait ManifestFormat {
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;
+	fn manifest(&self, val: Val) -> Result<String> {
+		let mut out = String::new();
+		self.manifest_buf(val, &mut out)?;
+		Ok(out)
+	}
 }
-impl ManifestFormat {
-	#[cfg(feature = "exp-preserve-order")]
-	fn preserve_order(&self) -> bool {
-		match self {
-			ManifestFormat::YamlStream(s) => s.preserve_order(),
-			ManifestFormat::Yaml { preserve_order, .. } => *preserve_order,
-			ManifestFormat::Json { preserve_order, .. } => *preserve_order,
-			ManifestFormat::ToString => false,
-			ManifestFormat::String => false,
-		}
+impl<T> ManifestFormat for Box<T>
+where
+	T: ManifestFormat + ?Sized,
+{
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+		let inner = &**self;
+		inner.manifest_buf(val, buf)
 	}
 }
+impl<T> ManifestFormat for &'_ T
+where
+	T: ManifestFormat + ?Sized,
+{
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+		let inner = &**self;
+		inner.manifest_buf(val, buf)
+	}
+}
 
 #[derive(Debug, Clone, Trace)]
 pub struct Slice {
@@ -641,172 +636,25 @@
 		}
 	}
 
+	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {
+		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {
+			manifest.manifest(val.clone())
+		}
+		manifest_dyn(self, &format)
+	}
+
 	pub fn to_string(&self) -> Result<IStr> {
 		Ok(match self {
 			Self::Bool(true) => "true".into(),
 			Self::Bool(false) => "false".into(),
 			Self::Null => "null".into(),
 			Self::Str(s) => s.clone(),
-			v => manifest_json_ex(
-				v,
-				&ManifestJsonOptions {
-					padding: "",
-					mtype: ManifestType::ToString,
-					newline: "\n",
-					key_val_sep: ": ",
-					#[cfg(feature = "exp-preserve-order")]
-					preserve_order: false,
-				},
-			)?
-			.into(),
-		})
-	}
-
-	/// Expects value to be object, outputs (key, manifested value) pairs
-	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(IStr, IStr)>> {
-		let Self::Obj(obj) = self else {
-			throw!(MultiManifestOutputIsNotAObject);
-		};
-		let keys = obj.fields(
-			#[cfg(feature = "exp-preserve-order")]
-			ty.preserve_order(),
-		);
-		let mut out = Vec::with_capacity(keys.len());
-		for key in keys {
-			let value = obj
-				.get(key.clone())?
-				.expect("item in object")
-				.manifest(ty)?;
-			out.push((key, value));
-		}
-		Ok(out)
-	}
-
-	/// Expects value to be array, outputs manifested values
-	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<IStr>> {
-		let Self::Arr(arr) = self else {
-			throw!(StreamManifestOutputIsNotAArray);
-		};
-		let mut out = Vec::with_capacity(arr.len());
-		for i in arr.iter() {
-			out.push(i?.manifest(ty)?);
-		}
-		Ok(out)
-	}
-
-	pub fn manifest(&self, ty: &ManifestFormat) -> Result<IStr> {
-		Ok(match ty {
-			ManifestFormat::YamlStream(format) => {
-				let Self::Arr(arr) = self else {
-					throw!(StreamManifestOutputIsNotAArray)
-				};
-				let mut out = String::new();
-
-				match format as &ManifestFormat {
-					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),
-					ManifestFormat::String => throw!(StreamManifestCannotNestString),
-					_ => {}
-				};
-
-				if !arr.is_empty() {
-					for v in arr.iter() {
-						out.push_str("---\n");
-						out.push_str(&v?.manifest(format)?);
-						out.push('\n');
-					}
-					out.push_str("...");
-				}
-
-				out.into()
-			}
-			ManifestFormat::Yaml {
-				padding,
-				#[cfg(feature = "exp-preserve-order")]
-				preserve_order,
-			} => self.to_yaml(
-				*padding,
-				#[cfg(feature = "exp-preserve-order")]
-				*preserve_order,
-			)?,
-			ManifestFormat::Json {
-				padding,
-				#[cfg(feature = "exp-preserve-order")]
-				preserve_order,
-			} => self.to_json(
-				*padding,
-				#[cfg(feature = "exp-preserve-order")]
-				*preserve_order,
-			)?,
-			ManifestFormat::ToString => self.to_string()?,
-			ManifestFormat::String => match self {
-				Self::Str(s) => s.clone(),
-				_ => throw!(StringManifestOutputIsNotAString),
-			},
+			_ => self
+				.manifest(crate::stdlib::manifest::ToStringFormat)
+				.map(IStr::from)?,
 		})
-	}
-
-	/// For manifestification
-	pub fn to_json(
-		&self,
-		padding: usize,
-		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
-	) -> Result<IStr> {
-		manifest_json_ex(
-			self,
-			&ManifestJsonOptions {
-				padding: &" ".repeat(padding),
-				mtype: if padding == 0 {
-					ManifestType::Minify
-				} else {
-					ManifestType::Manifest
-				},
-				newline: "\n",
-				key_val_sep: ": ",
-				#[cfg(feature = "exp-preserve-order")]
-				preserve_order,
-			},
-		)
-		.map(Into::into)
 	}
 
-	/// Calls `std.manifestJson`
-	pub fn to_std_json(
-		&self,
-		padding: usize,
-		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
-	) -> Result<Rc<str>> {
-		manifest_json_ex(
-			self,
-			&ManifestJsonOptions {
-				padding: &" ".repeat(padding),
-				mtype: ManifestType::Std,
-				newline: "\n",
-				key_val_sep: ": ",
-				#[cfg(feature = "exp-preserve-order")]
-				preserve_order,
-			},
-		)
-		.map(Into::into)
-	}
-
-	pub fn to_yaml(
-		&self,
-		padding: usize,
-		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
-	) -> Result<IStr> {
-		let padding = &" ".repeat(padding);
-		manifest_yaml_ex(
-			self,
-			&ManifestYamlOptions {
-				padding,
-				arr_element_padding: padding,
-				quote_keys: false,
-				#[cfg(feature = "exp-preserve-order")]
-				preserve_order,
-			},
-		)
-		.map(Into::into)
-	}
 	pub fn into_indexable(self) -> Result<IndexableVal> {
 		Ok(match self {
 			Val::Str(s) => IndexableVal::Str(s),
modifiedcrates/jrsonnet-stdlib/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest.rs
+++ b/crates/jrsonnet-stdlib/src/manifest.rs
@@ -1,10 +1,7 @@
 use jrsonnet_evaluator::{
 	error::Result,
 	function::builtin,
-	stdlib::manifest::{
-		escape_string_json, manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType,
-		ManifestYamlOptions,
-	},
+	stdlib::manifest::{escape_string_json, JsonFormat, YamlFormat},
 	typed::Any,
 	IStr,
 };
@@ -24,17 +21,13 @@
 ) -> Result<String> {
 	let newline = newline.as_deref().unwrap_or("\n");
 	let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
-	manifest_json_ex(
-		&value.0,
-		&ManifestJsonOptions {
-			padding: &indent,
-			mtype: ManifestType::Std,
-			newline,
-			key_val_sep,
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order: preserve_order.unwrap_or(false),
-		},
-	)
+	value.0.manifest(JsonFormat::std_to_json(
+		indent.to_string(),
+		newline,
+		key_val_sep,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order.unwrap_or(false),
+	))
 }
 
 #[builtin]
@@ -44,18 +37,10 @@
 	quote_keys: Option<bool>,
 	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
 ) -> Result<String> {
-	manifest_yaml_ex(
-		&value.0,
-		&ManifestYamlOptions {
-			padding: "  ",
-			arr_element_padding: if indent_array_in_object.unwrap_or(false) {
-				"  "
-			} else {
-				""
-			},
-			quote_keys: quote_keys.unwrap_or(true),
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order: preserve_order.unwrap_or(false),
-		},
-	)
+	value.0.manifest(YamlFormat::std_to_yaml(
+		indent_array_in_object.unwrap_or(false),
+		quote_keys.unwrap_or(true),
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order.unwrap_or(false),
+	))
 }