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

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/toml.rs6.2 KiBsourcehistory
1use std::borrow::Cow;23use jrsonnet_evaluator::{4	manifest::{escape_string_json_buf, ManifestFormat},5	throw,6	val::ArrValue,7	IStr, ObjValue, Result, Val,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		Val::Arr(a) => {107			if a.is_empty() {108				buf.push_str("[]");109				return Ok(());110			}111			for (i, e) in a.iter().enumerate() {112				let e = e?;113				if i != 0 {114					buf.push(',');115				} else {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				}125				manifest_value(&e, true, buf, "", options)?;126			}127			if inline {128				buf.push(' ');129			} else {130				buf.push('\n');131				buf.push_str(cur_padding);132			}133			buf.push(']');134		}135		Val::Obj(o) => {136			if o.is_empty() {137				buf.push_str("{}");138			}139			buf.push_str("{ ");140			for (i, (k, v)) in o141				.iter(142					#[cfg(feature = "exp-preserve-order")]143					options.preserve_order,144				)145				.enumerate()146			{147				let v = v?;148				if i != 0 {149					buf.push_str(", ");150				}151				escape_key_toml_buf(&k, buf);152				buf.push_str(" = ");153				manifest_value(&v, true, buf, "", options)?;154			}155			buf.push_str(" }");156		}157		Val::Null => {158			throw!("tried to manifest null")159		}160		Val::Func(_) => {161			throw!("tried to manifest function")162		}163	}164	Ok(())165}166167fn manifest_table_internal(168	obj: &ObjValue,169	path: &mut Vec<IStr>,170	buf: &mut String,171	cur_padding: &mut String,172	options: &TomlFormat<'_>,173) -> Result<()> {174	let mut sections = Vec::new();175	let mut first = true;176	for (key, value) in obj.iter(177		#[cfg(feature = "exp-preserve-order")]178		options.preserve_order,179	) {180		let value = value?;181		if !is_section(&value)? {182			if !first {183				buf.push('\n');184			}185			first = false;186			buf.push_str(cur_padding);187			escape_key_toml_buf(&key, buf);188			buf.push_str(" = ");189			manifest_value(&value, false, buf, cur_padding, options)?;190		} else {191			sections.push((key, value));192		}193	}194	for (k, v) in sections {195		if !first {196			buf.push_str("\n\n");197		}198		first = false;199		path.push(k);200		match v {201			Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options)?,202			Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options)?,203			_ => unreachable!("iterating over sections"),204		}205		path.pop();206	}207	Ok(())208}209210fn manifest_table(211	obj: &ObjValue,212	path: &mut Vec<IStr>,213	buf: &mut String,214	cur_padding: &mut String,215	options: &TomlFormat<'_>,216) -> Result<()> {217	if options.skip_empty_sections218		&& !obj.is_empty()219		&& obj220			.iter(221				#[cfg(feature = "exp-preserve-order")]222				false,223			)224			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?225	{226		manifest_table_internal(obj, path, buf, cur_padding, options)?;227		return Ok(());228	}229	buf.push_str(cur_padding);230	buf.push('[');231	for (i, k) in path.iter().enumerate() {232		if i != 0 {233			buf.push('.');234		}235		escape_key_toml_buf(k, buf);236	}237	buf.push(']');238	if obj.is_empty() {239		return Ok(());240	}241	buf.push('\n');242	let prev_len = cur_padding.len();243	cur_padding.push_str(&options.padding);244	manifest_table_internal(obj, path, buf, cur_padding, options)?;245	cur_padding.truncate(prev_len);246	Ok(())247}248fn manifest_table_array(249	arr: &ArrValue,250	path: &mut Vec<IStr>,251	buf: &mut String,252	cur_padding: &mut String,253	options: &TomlFormat<'_>,254) -> Result<()> {255	let mut formatted_path = String::new();256	{257		formatted_path.push_str(cur_padding);258		formatted_path.push_str("[[");259		for (i, k) in path.iter().enumerate() {260			if i != 0 {261				formatted_path.push('.');262			}263			escape_key_toml_buf(k, &mut formatted_path);264		}265		formatted_path.push_str("]]");266	}267	let prev_len = cur_padding.len();268	cur_padding.push_str(&options.padding);269	for (i, e) in arr.iter().enumerate() {270		let obj = e.expect("already tested").as_obj().expect("already tested");271		if i != 0 {272			buf.push_str("\n\n");273		}274		buf.push_str(&formatted_path);275		if obj.is_empty() {276			continue;277		}278		buf.push('\n');279		manifest_table_internal(&obj, path, buf, cur_padding, options)?;280	}281	cur_padding.truncate(prev_len);282	Ok(())283}284285impl ManifestFormat for TomlFormat<'_> {286	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {287		match val {288			Val::Obj(obj) => {289				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)290			}291			_ => throw!("toml body should be object"),292		}293	}294}