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

difftreelog

source

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