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

difftreelog

source

crates/jrsonnet-stdlib/src/manifest/toml.rs6.7 KiBsourcehistory
1use std::borrow::Cow;23use jrsonnet_evaluator::{4	bail,5	manifest::{escape_string_json_buf, ManifestFormat},6	val::ArrValue,7	IStr, ObjValue, Result, ResultExt, State, 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		#[cfg(feature = "exp-bigint")]107		Val::BigInt(n) => write!(buf, "{n}").unwrap(),108		Val::Arr(a) => {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				State::push_description(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		}142		Val::Obj(o) => {143			o.run_assertions()?;144			buf.push('{');145146			let mut had_fields = false;147			for (i, (k, v)) in o148				.iter(149					#[cfg(feature = "exp-preserve-order")]150					options.preserve_order,151				)152				.enumerate()153			{154				had_fields = true;155				let v = v.with_description(|| format!("field <{k}> evaluation"))?;156157				if i != 0 {158					buf.push(',');159				}160				buf.push(' ');161162				escape_key_toml_buf(&k, buf);163				buf.push_str(" = ");164				State::push_description(165					|| format!("field <{k}> manifestification"),166					|| manifest_value(&v, true, buf, "", options),167				)?;168			}169170			if had_fields {171				buf.push(' ');172			}173174			buf.push('}');175		}176		Val::Null => {177			bail!("tried to manifest null")178		}179		Val::Func(_) => {180			bail!("tried to manifest function")181		}182	}183	Ok(())184}185186fn manifest_table_internal(187	obj: &ObjValue,188	path: &mut Vec<IStr>,189	buf: &mut String,190	cur_padding: &mut String,191	options: &TomlFormat<'_>,192) -> Result<()> {193	let mut sections = Vec::new();194	let mut first = true;195	for (key, value) in obj.iter(196		#[cfg(feature = "exp-preserve-order")]197		options.preserve_order,198	) {199		let value = value.with_description(|| format!("field <{key}> evaluation"))?;200		if is_section(&value)? {201			sections.push((key, value));202		} else {203			if !first {204				buf.push('\n');205			}206			first = false;207			buf.push_str(cur_padding);208			escape_key_toml_buf(&key, buf);209			buf.push_str(" = ");210			manifest_value(&value, false, buf, cur_padding, options)?;211		}212	}213	for (k, v) in sections {214		if !first {215			buf.push_str("\n\n");216		}217		first = false;218		path.push(k);219		match v {220			Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options)?,221			Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options)?,222			_ => unreachable!("iterating over sections"),223		}224		path.pop();225	}226	Ok(())227}228229fn manifest_table(230	obj: &ObjValue,231	path: &mut Vec<IStr>,232	buf: &mut String,233	cur_padding: &mut String,234	options: &TomlFormat<'_>,235) -> Result<()> {236	if options.skip_empty_sections237		&& !obj.is_empty()238		&& obj239			.iter(240				#[cfg(feature = "exp-preserve-order")]241				false,242			)243			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?244	{245		manifest_table_internal(obj, path, buf, cur_padding, options)?;246		return Ok(());247	}248	buf.push_str(cur_padding);249	buf.push('[');250	for (i, k) in path.iter().enumerate() {251		if i != 0 {252			buf.push('.');253		}254		escape_key_toml_buf(k, buf);255	}256	buf.push(']');257	if obj.is_empty() {258		return Ok(());259	}260	buf.push('\n');261	let prev_len = cur_padding.len();262	cur_padding.push_str(&options.padding);263	manifest_table_internal(obj, path, buf, cur_padding, options)?;264	cur_padding.truncate(prev_len);265	Ok(())266}267fn manifest_table_array(268	arr: &ArrValue,269	path: &mut Vec<IStr>,270	buf: &mut String,271	cur_padding: &mut String,272	options: &TomlFormat<'_>,273) -> Result<()> {274	let mut formatted_path = String::new();275	{276		formatted_path.push_str(cur_padding);277		formatted_path.push_str("[[");278		for (i, k) in path.iter().enumerate() {279			if i != 0 {280				formatted_path.push('.');281			}282			escape_key_toml_buf(k, &mut formatted_path);283		}284		formatted_path.push_str("]]");285	}286	let prev_len = cur_padding.len();287	cur_padding.push_str(&options.padding);288	for (i, e) in arr.iter().enumerate() {289		let obj = e.expect("already tested").as_obj().expect("already tested");290		if i != 0 {291			buf.push_str("\n\n");292		}293		buf.push_str(&formatted_path);294		if obj.is_empty() {295			continue;296		}297		buf.push('\n');298		manifest_table_internal(&obj, path, buf, cur_padding, options)?;299	}300	cur_padding.truncate(prev_len);301	Ok(())302}303304impl ManifestFormat for TomlFormat<'_> {305	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {306		match val {307			Val::Obj(obj) => {308				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)309			}310			_ => bail!("toml body should be object"),311		}312	}313}