git.delta.rocks / jrsonnet / refs/commits / 6fbff53f4c1d

difftreelog

feat proper section descriptions for toml

prnxvsoxYaroslav Bolyukin2026-04-25parent: #830d8fb.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth
before · crates/jrsonnet-stdlib/src/manifest/toml.rs
1use std::borrow::Cow;23use jrsonnet_evaluator::{4	IStr, ObjValue, Result, ResultExt, Val, bail, in_description_frame,5	manifest::{ManifestFormat, escape_string_json_buf},6	val::ArrValue,7};89pub struct TomlFormat<'s> {10	/// Padding before fields, i.e11	/// ```toml12	/// [a]13	///   b = 114	/// ## <- this15	/// ```16	padding: Cow<'s, str>,17	/// Do not emit sections for objects, consisting only from sections:18	/// ```toml19	/// # false20	/// [a]21	/// [a.b]22	///23	/// # true24	/// [a.b]25	/// ```26	skip_empty_sections: bool,27	/// If true - then order of fields is preserved as written,28	/// instead of sorting alphabetically29	#[cfg(feature = "exp-preserve-order")]30	preserve_order: bool,31}32impl TomlFormat<'_> {33	pub fn cli(34		padding: usize,35		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,36	) -> Self {37		let padding = " ".repeat(padding);38		Self {39			padding: Cow::Owned(padding),40			skip_empty_sections: true,41			#[cfg(feature = "exp-preserve-order")]42			preserve_order,43		}44	}45	pub fn std_to_toml(46		padding: String,47		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,48	) -> Self {49		Self {50			padding: Cow::Owned(padding),51			skip_empty_sections: false,52			#[cfg(feature = "exp-preserve-order")]53			preserve_order,54		}55	}56}5758fn bare_allowed(s: &str) -> bool {59	s.bytes()60		.all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))61}6263fn escape_key_toml_buf(key: &str, buf: &mut String) {64	if bare_allowed(key) {65		buf.push_str(key);66	} else {67		escape_string_json_buf(key, buf);68	}69}7071fn is_section(val: &Val) -> Result<bool> {72	Ok(match val {73		Val::Arr(a) => {74			if a.is_empty() {75				return Ok(false);76			}77			for e in a.iter() {78				let e = e?;79				if !matches!(e, Val::Obj(_)) {80					return Ok(false);81				}82			}83			true84		}85		Val::Obj(_) => true,86		_ => false,87	})88}8990fn manifest_value(91	val: &Val,92	inline: bool,93	buf: &mut String,94	cur_padding: &str,95	options: &TomlFormat<'_>,96) -> Result<()> {97	use std::fmt::Write;98	match val {99		Val::Bool(true) => buf.push_str("true"),100		Val::Bool(false) => buf.push_str("false"),101		Val::Str(s) => {102			escape_string_json_buf(&s.clone().into_flat(), buf);103		}104		Val::Num(n) => write!(buf, "{n}").unwrap(),105		#[cfg(feature = "exp-bigint")]106		Val::BigInt(n) => write!(buf, "{n}").unwrap(),107		Val::Arr(a) => {108			buf.push('[');109110			let mut had_items = false;111			for (i, e) in a.iter().enumerate() {112				had_items = true;113				let e = e.with_description(|| format!("elem <{i}> evaluation"))?;114115				if i != 0 {116					buf.push(',');117				}118				if inline {119					buf.push(' ');120				} else {121					buf.push('\n');122					buf.push_str(cur_padding);123					buf.push_str(&options.padding);124				}125126				in_description_frame(127					|| format!("elem <{i}> manifestification"),128					|| manifest_value(&e, true, buf, "", options),129				)?;130			}131132			if !had_items {133			} else if inline {134				buf.push(' ');135			} else {136				buf.push('\n');137				buf.push_str(cur_padding);138			}139			buf.push(']');140		}141		Val::Obj(o) => {142			o.run_assertions()?;143			buf.push('{');144145			let mut had_fields = false;146			for (i, (k, v)) in o147				.iter(148					#[cfg(feature = "exp-preserve-order")]149					options.preserve_order,150				)151				.enumerate()152			{153				had_fields = true;154				let v = v.with_description(|| format!("field <{k}> evaluation"))?;155156				if i != 0 {157					buf.push(',');158				}159				buf.push(' ');160161				escape_key_toml_buf(&k, buf);162				buf.push_str(" = ");163				in_description_frame(164					|| format!("field <{k}> manifestification"),165					|| manifest_value(&v, true, buf, "", options),166				)?;167			}168169			if had_fields {170				buf.push(' ');171			}172173			buf.push('}');174		}175		Val::Null => {176			bail!("tried to manifest null")177		}178		Val::Func(_) => {179			bail!("tried to manifest function")180		}181	}182	Ok(())183}184185fn manifest_table_internal(186	obj: &ObjValue,187	path: &mut Vec<IStr>,188	buf: &mut String,189	cur_padding: &mut String,190	options: &TomlFormat<'_>,191) -> Result<()> {192	let mut sections = Vec::new();193	let mut first = true;194	for (key, value) in obj.iter(195		#[cfg(feature = "exp-preserve-order")]196		options.preserve_order,197	) {198		let value = value.with_description(|| format!("field <{key}> evaluation"))?;199		if is_section(&value)? {200			sections.push((key, value));201		} else {202			if !first {203				buf.push('\n');204			}205			first = false;206			buf.push_str(cur_padding);207			escape_key_toml_buf(&key, buf);208			buf.push_str(" = ");209			manifest_value(&value, false, buf, cur_padding, options)?;210		}211	}212	for (k, v) in sections {213		if !first {214			buf.push_str("\n\n");215		}216		first = false;217		path.push(k);218		match v {219			Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options)?,220			Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options)?,221			_ => unreachable!("iterating over sections"),222		}223		path.pop();224	}225	Ok(())226}227228fn manifest_table(229	obj: &ObjValue,230	path: &mut Vec<IStr>,231	buf: &mut String,232	cur_padding: &mut String,233	options: &TomlFormat<'_>,234) -> Result<()> {235	if options.skip_empty_sections236		&& !obj.is_empty()237		&& obj238			.iter(239				#[cfg(feature = "exp-preserve-order")]240				false,241			)242			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?243	{244		manifest_table_internal(obj, path, buf, cur_padding, options)?;245		return Ok(());246	}247	buf.push_str(cur_padding);248	buf.push('[');249	for (i, k) in path.iter().enumerate() {250		if i != 0 {251			buf.push('.');252		}253		escape_key_toml_buf(k, buf);254	}255	buf.push(']');256	if obj.is_empty() {257		return Ok(());258	}259	buf.push('\n');260	let prev_len = cur_padding.len();261	cur_padding.push_str(&options.padding);262	manifest_table_internal(obj, path, buf, cur_padding, options)?;263	cur_padding.truncate(prev_len);264	Ok(())265}266fn manifest_table_array(267	arr: &ArrValue,268	path: &mut Vec<IStr>,269	buf: &mut String,270	cur_padding: &mut String,271	options: &TomlFormat<'_>,272) -> Result<()> {273	let mut formatted_path = String::new();274	{275		formatted_path.push_str(cur_padding);276		formatted_path.push_str("[[");277		for (i, k) in path.iter().enumerate() {278			if i != 0 {279				formatted_path.push('.');280			}281			escape_key_toml_buf(k, &mut formatted_path);282		}283		formatted_path.push_str("]]");284	}285	let prev_len = cur_padding.len();286	cur_padding.push_str(&options.padding);287	for (i, e) in arr.iter().enumerate() {288		let obj = e.expect("already tested").as_obj().expect("already tested");289		if i != 0 {290			buf.push_str("\n\n");291		}292		buf.push_str(&formatted_path);293		if obj.is_empty() {294			continue;295		}296		buf.push('\n');297		manifest_table_internal(&obj, path, buf, cur_padding, options)?;298	}299	cur_padding.truncate(prev_len);300	Ok(())301}302303impl ManifestFormat for TomlFormat<'_> {304	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {305		match val {306			Val::Obj(obj) => {307				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)308			}309			_ => bail!("toml body should be object"),310		}311	}312}