git.delta.rocks / jrsonnet / refs/commits / 112adb2810f2

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/toml.rs7.0 KiBsourcehistory
1use std::borrow::Cow;23use jrsonnet_evaluator::{4	Error, IStr, ObjValue, Result, ResultExt, Val, bail, ensure_sufficient_stack, in_description_frame, manifest::{ManifestFormat, escape_string_json_buf}, val::ArrValue5};67pub struct TomlFormat<'s> {8	/// Padding before fields, i.e9	/// ```toml10	/// [a]11	///   b = 112	/// ## <- this13	/// ```14	padding: Cow<'s, str>,15	/// Do not emit sections for objects, consisting only from sections:16	/// ```toml17	/// # false18	/// [a]19	/// [a.b]20	///21	/// # true22	/// [a.b]23	/// ```24	skip_empty_sections: bool,25	/// If true - then order of fields is preserved as written,26	/// instead of sorting alphabetically27	#[cfg(feature = "exp-preserve-order")]28	preserve_order: bool,29}30impl TomlFormat<'_> {31	pub fn cli(32		padding: usize,33		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,34	) -> Self {35		let padding = " ".repeat(padding);36		Self {37			padding: Cow::Owned(padding),38			skip_empty_sections: true,39			#[cfg(feature = "exp-preserve-order")]40			preserve_order,41		}42	}43	pub fn std_to_toml(44		padding: String,45		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,46	) -> Self {47		Self {48			padding: Cow::Owned(padding),49			skip_empty_sections: false,50			#[cfg(feature = "exp-preserve-order")]51			preserve_order,52		}53	}54}5556fn bare_allowed(s: &str) -> bool {57	s.bytes()58		.all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))59}6061fn escape_key_toml_buf(key: &str, buf: &mut String) {62	if bare_allowed(key) {63		buf.push_str(key);64	} else {65		escape_string_json_buf(key, buf);66	}67}6869fn is_section(val: &Val) -> Result<bool> {70	Ok(match val {71		Val::Arr(a) => {72			if a.is_empty() {73				return Ok(false);74			}75			for e in a.iter() {76				let e = e?;77				if !matches!(e, Val::Obj(_)) {78					return Ok(false);79				}80			}81			true82		}83		Val::Obj(_) => true,84		_ => false,85	})86}8788fn manifest_value(89	val: &Val,90	inline: bool,91	buf: &mut String,92	cur_padding: &str,93	options: &TomlFormat<'_>,94) -> Result<()> {95	use std::fmt::Write;96	match val {97		Val::Bool(true) => buf.push_str("true"),98		Val::Bool(false) => buf.push_str("false"),99		Val::Str(s) => {100			escape_string_json_buf(&s.clone().into_flat(), buf);101		}102		Val::Num(n) => write!(buf, "{n}").unwrap(),103		#[cfg(feature = "exp-bigint")]104		Val::BigInt(n) => write!(buf, "{n}").unwrap(),105		Val::Arr(a) => ensure_sufficient_stack(|| {106			buf.push('[');107108			let mut had_items = false;109			for (i, e) in a.iter().enumerate() {110				had_items = true;111				let e = e.with_description(|| format!("elem <{i}> evaluation"))?;112113				if i != 0 {114					buf.push(',');115				}116				if inline {117					buf.push(' ');118				} else {119					buf.push('\n');120					buf.push_str(cur_padding);121					buf.push_str(&options.padding);122				}123124				in_description_frame(125					|| format!("elem <{i}> manifestification"),126					|| manifest_value(&e, true, buf, "", options),127				)?;128			}129130			if !had_items {131			} else if inline {132				buf.push(' ');133			} else {134				buf.push('\n');135				buf.push_str(cur_padding);136			}137			buf.push(']');138			Ok::<_, Error>(())139		})?,140		Val::Obj(o) => ensure_sufficient_stack(|| {141			o.run_assertions()?;142			buf.push('{');143144			let mut had_fields = false;145			for (i, (k, v)) in o146				.iter(147					#[cfg(feature = "exp-preserve-order")]148					options.preserve_order,149				)150				.enumerate()151			{152				had_fields = true;153				let v = v.with_description(|| format!("field <{k}> evaluation"))?;154155				if i != 0 {156					buf.push(',');157				}158				buf.push(' ');159160				escape_key_toml_buf(&k, buf);161				buf.push_str(" = ");162				in_description_frame(163					|| format!("field <{k}> manifestification"),164					|| manifest_value(&v, true, buf, "", options),165				)?;166			}167168			if had_fields {169				buf.push(' ');170			}171172			buf.push('}');173			Ok::<_, Error>(())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			in_description_frame(210				|| format!("table <{key}> manifestification"),211				|| manifest_value(&value, false, buf, cur_padding, options),212			)?;213		}214	}215	for (k, v) in sections {216		if !first {217			buf.push_str("\n\n");218		}219		first = false;220		path.push(k.clone());221		ensure_sufficient_stack(|| in_description_frame(222			|| format!("section <{k}> manifestification"),223			|| match v {224				Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options),225				Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options),226				_ => unreachable!("iterating over sections"),227			},228		))?;229		path.pop();230	}231	Ok(())232}233234fn manifest_table(235	obj: &ObjValue,236	path: &mut Vec<IStr>,237	buf: &mut String,238	cur_padding: &mut String,239	options: &TomlFormat<'_>,240) -> Result<()> {241	if options.skip_empty_sections242		&& !obj.is_empty()243		&& obj244			.iter(245				#[cfg(feature = "exp-preserve-order")]246				false,247			)248			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?249	{250		manifest_table_internal(obj, path, buf, cur_padding, options)?;251		return Ok(());252	}253	buf.push_str(cur_padding);254	buf.push('[');255	for (i, k) in path.iter().enumerate() {256		if i != 0 {257			buf.push('.');258		}259		escape_key_toml_buf(k, buf);260	}261	buf.push(']');262	if obj.is_empty() {263		return Ok(());264	}265	buf.push('\n');266	let prev_len = cur_padding.len();267	cur_padding.push_str(&options.padding);268	manifest_table_internal(obj, path, buf, cur_padding, options)?;269	cur_padding.truncate(prev_len);270	Ok(())271}272fn manifest_table_array(273	arr: &ArrValue,274	path: &mut Vec<IStr>,275	buf: &mut String,276	cur_padding: &mut String,277	options: &TomlFormat<'_>,278) -> Result<()> {279	let mut formatted_path = String::new();280	{281		formatted_path.push_str(cur_padding);282		formatted_path.push_str("[[");283		for (i, k) in path.iter().enumerate() {284			if i != 0 {285				formatted_path.push('.');286			}287			escape_key_toml_buf(k, &mut formatted_path);288		}289		formatted_path.push_str("]]");290	}291	let prev_len = cur_padding.len();292	cur_padding.push_str(&options.padding);293	for (i, e) in arr.iter().enumerate() {294		let obj = e.expect("already tested").as_obj().expect("already tested");295		if i != 0 {296			buf.push_str("\n\n");297		}298		buf.push_str(&formatted_path);299		if obj.is_empty() {300			continue;301		}302		buf.push('\n');303		manifest_table_internal(&obj, path, buf, cur_padding, options)?;304	}305	cur_padding.truncate(prev_len);306	Ok(())307}308309impl ManifestFormat for TomlFormat<'_> {310	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {311		match val {312			Val::Obj(obj) => {313				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)314			}315			_ => bail!("toml body should be object"),316		}317	}318}