git.delta.rocks / jrsonnet / refs/commits / b8427047424c

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,5	in_description_frame,6	manifest::{ManifestFormat, escape_string_json_buf},7	val::ArrValue,8};910pub struct TomlFormat<'s> {11	/// Padding before fields, i.e12	/// ```toml13	/// [a]14	///   b = 115	/// ## <- this16	/// ```17	padding: Cow<'s, str>,18	/// Do not emit sections for objects, consisting only from sections:19	/// ```toml20	/// # false21	/// [a]22	/// [a.b]23	///24	/// # true25	/// [a.b]26	/// ```27	skip_empty_sections: bool,28	/// If true - then order of fields is preserved as written,29	/// instead of sorting alphabetically30	#[cfg(feature = "exp-preserve-order")]31	preserve_order: bool,32}33impl TomlFormat<'_> {34	pub fn cli(35		padding: usize,36		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,37	) -> Self {38		let padding = " ".repeat(padding);39		Self {40			padding: Cow::Owned(padding),41			skip_empty_sections: true,42			#[cfg(feature = "exp-preserve-order")]43			preserve_order,44		}45	}46	pub fn std_to_toml(47		padding: String,48		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,49	) -> Self {50		Self {51			padding: Cow::Owned(padding),52			skip_empty_sections: false,53			#[cfg(feature = "exp-preserve-order")]54			preserve_order,55		}56	}57}5859fn bare_allowed(s: &str) -> bool {60	s.bytes()61		.all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))62}6364fn escape_key_toml_buf(key: &str, buf: &mut String) {65	if bare_allowed(key) {66		buf.push_str(key);67	} else {68		escape_string_json_buf(key, buf);69	}70}7172fn is_section(val: &Val) -> Result<bool> {73	Ok(match val {74		Val::Arr(a) => {75			if a.is_empty() {76				return Ok(false);77			}78			for e in a.iter() {79				let e = e?;80				if !matches!(e, Val::Obj(_)) {81					return Ok(false);82				}83			}84			true85		}86		Val::Obj(_) => true,87		_ => false,88	})89}9091fn manifest_value(92	val: &Val,93	inline: bool,94	buf: &mut String,95	cur_padding: &str,96	options: &TomlFormat<'_>,97) -> Result<()> {98	use std::fmt::Write;99	match val {100		Val::Bool(true) => buf.push_str("true"),101		Val::Bool(false) => buf.push_str("false"),102		Val::Str(s) => {103			escape_string_json_buf(&s.clone().into_flat(), buf);104		}105		Val::Num(n) => write!(buf, "{n}").unwrap(),106		#[cfg(feature = "exp-bigint")]107		Val::BigInt(n) => write!(buf, "{n}").unwrap(),108		Val::Arr(a) => ensure_sufficient_stack(|| {109			buf.push('[');110111			let mut had_items = false;112			for (i, e) in a.iter().enumerate() {113				had_items = true;114				let e = e.with_description(|| format!("elem <{i}> evaluation"))?;115116				if i != 0 {117					buf.push(',');118				}119				if inline {120					buf.push(' ');121				} else {122					buf.push('\n');123					buf.push_str(cur_padding);124					buf.push_str(&options.padding);125				}126127				in_description_frame(128					|| format!("elem <{i}> manifestification"),129					|| manifest_value(&e, true, buf, "", options),130				)?;131			}132133			if !had_items {134			} else if inline {135				buf.push(' ');136			} else {137				buf.push('\n');138				buf.push_str(cur_padding);139			}140			buf.push(']');141			Ok::<_, Error>(())142		})?,143		Val::Obj(o) => ensure_sufficient_stack(|| {144			o.run_assertions()?;145			buf.push('{');146147			let mut had_fields = false;148			for (i, (k, v)) in o149				.iter(150					#[cfg(feature = "exp-preserve-order")]151					options.preserve_order,152				)153				.enumerate()154			{155				had_fields = true;156				let v = v.with_description(|| format!("field <{k}> evaluation"))?;157158				if i != 0 {159					buf.push(',');160				}161				buf.push(' ');162163				escape_key_toml_buf(&k, buf);164				buf.push_str(" = ");165				in_description_frame(166					|| format!("field <{k}> manifestification"),167					|| manifest_value(&v, true, buf, "", options),168				)?;169			}170171			if had_fields {172				buf.push(' ');173			}174175			buf.push('}');176			Ok::<_, Error>(())177		})?,178		Val::Null => {179			bail!("tried to manifest null")180		}181		Val::Func(_) => {182			bail!("tried to manifest function")183		}184	}185	Ok(())186}187188fn manifest_table_internal(189	obj: &ObjValue,190	path: &mut Vec<IStr>,191	buf: &mut String,192	cur_padding: &mut String,193	options: &TomlFormat<'_>,194) -> Result<()> {195	let mut sections = Vec::new();196	let mut first = true;197	for (key, value) in obj.iter(198		#[cfg(feature = "exp-preserve-order")]199		options.preserve_order,200	) {201		let value = value.with_description(|| format!("field <{key}> evaluation"))?;202		if is_section(&value)? {203			sections.push((key, value));204		} else {205			if !first {206				buf.push('\n');207			}208			first = false;209			buf.push_str(cur_padding);210			escape_key_toml_buf(&key, buf);211			buf.push_str(" = ");212			in_description_frame(213				|| format!("table <{key}> manifestification"),214				|| manifest_value(&value, false, buf, cur_padding, options),215			)?;216		}217	}218	for (k, v) in sections {219		if !first {220			buf.push_str("\n\n");221		}222		first = false;223		path.push(k.clone());224		ensure_sufficient_stack(|| {225			in_description_frame(226				|| format!("section <{k}> manifestification"),227				|| match v {228					Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options),229					Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options),230					_ => unreachable!("iterating over sections"),231				},232			)233		})?;234		path.pop();235	}236	Ok(())237}238239fn manifest_table(240	obj: &ObjValue,241	path: &mut Vec<IStr>,242	buf: &mut String,243	cur_padding: &mut String,244	options: &TomlFormat<'_>,245) -> Result<()> {246	if options.skip_empty_sections247		&& !obj.is_empty()248		&& obj249			.iter(250				#[cfg(feature = "exp-preserve-order")]251				false,252			)253			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?254	{255		manifest_table_internal(obj, path, buf, cur_padding, options)?;256		return Ok(());257	}258	buf.push_str(cur_padding);259	buf.push('[');260	for (i, k) in path.iter().enumerate() {261		if i != 0 {262			buf.push('.');263		}264		escape_key_toml_buf(k, buf);265	}266	buf.push(']');267	if obj.is_empty() {268		return Ok(());269	}270	buf.push('\n');271	let prev_len = cur_padding.len();272	cur_padding.push_str(&options.padding);273	manifest_table_internal(obj, path, buf, cur_padding, options)?;274	cur_padding.truncate(prev_len);275	Ok(())276}277fn manifest_table_array(278	arr: &ArrValue,279	path: &mut Vec<IStr>,280	buf: &mut String,281	cur_padding: &mut String,282	options: &TomlFormat<'_>,283) -> Result<()> {284	let mut formatted_path = String::new();285	{286		formatted_path.push_str(cur_padding);287		formatted_path.push_str("[[");288		for (i, k) in path.iter().enumerate() {289			if i != 0 {290				formatted_path.push('.');291			}292			escape_key_toml_buf(k, &mut formatted_path);293		}294		formatted_path.push_str("]]");295	}296	let prev_len = cur_padding.len();297	cur_padding.push_str(&options.padding);298	for (i, e) in arr.iter().enumerate() {299		let obj = e.expect("already tested").as_obj().expect("already tested");300		if i != 0 {301			buf.push_str("\n\n");302		}303		buf.push_str(&formatted_path);304		if obj.is_empty() {305			continue;306		}307		buf.push('\n');308		manifest_table_internal(&obj, path, buf, cur_padding, options)?;309	}310	cur_padding.truncate(prev_len);311	Ok(())312}313314impl ManifestFormat for TomlFormat<'_> {315	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {316		match val {317			Val::Obj(obj) => {318				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)319			}320			_ => bail!("toml body should be object"),321		}322	}323}