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

difftreelog

source

crates/jrsonnet-evaluator/src/manifest.rs11.1 KiBsourcehistory
1use std::{borrow::Cow, fmt::Write};23use crate::{bail, Result, State, Val};45pub trait ManifestFormat {6	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;7	fn manifest(&self, val: Val) -> Result<String> {8		let mut out = String::new();9		self.manifest_buf(val, &mut out)?;10		Ok(out)11	}12	/// When outputing to file, is it safe to append a trailing newline (I.e newline won't change13	/// the meaning).14	///15	/// Default implementation returns `true`16	fn file_trailing_newline(&self) -> bool {17		true18	}19}20impl<T> ManifestFormat for Box<T>21where22	T: ManifestFormat + ?Sized,23{24	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {25		let inner = &**self;26		inner.manifest_buf(val, buf)27	}28	fn file_trailing_newline(&self) -> bool {29		let inner = &**self;30		inner.file_trailing_newline()31	}32}33impl<T> ManifestFormat for &'_ T34where35	T: ManifestFormat + ?Sized,36{37	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {38		let inner = &**self;39		inner.manifest_buf(val, buf)40	}41	fn file_trailing_newline(&self) -> bool {42		let inner = &**self;43		inner.file_trailing_newline()44	}45}4647#[derive(PartialEq, Eq, Clone, Copy)]48enum JsonFormatting {49	// Applied in manifestification50	Manifest,51	/// Used for std.manifestJson52	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest53	Std,54	/// No line breaks, used in `obj+''`55	ToString,56	/// Minified json57	Minify,58}5960pub struct JsonFormat<'s> {61	padding: Cow<'s, str>,62	mtype: JsonFormatting,63	newline: &'s str,64	key_val_sep: &'s str,65	#[cfg(feature = "exp-preserve-order")]66	preserve_order: bool,67	#[cfg(feature = "exp-bigint")]68	preserve_bigints: bool,69}7071impl<'s> JsonFormat<'s> {72	// Minifying format73	pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {74		Self {75			padding: Cow::Borrowed(""),76			mtype: JsonFormatting::Minify,77			newline: "\n",78			key_val_sep: ":",79			#[cfg(feature = "exp-preserve-order")]80			preserve_order,81			#[cfg(feature = "exp-bigint")]82			preserve_bigints: false,83		}84	}85	// Same format as std.toString86	pub fn std_to_string() -> Self {87		Self {88			padding: Cow::Borrowed(""),89			mtype: JsonFormatting::ToString,90			newline: "\n",91			key_val_sep: ": ",92			#[cfg(feature = "exp-preserve-order")]93			preserve_order: false,94			#[cfg(feature = "exp-bigint")]95			preserve_bigints: false,96		}97	}98	pub fn std_to_json(99		padding: String,100		newline: &'s str,101		key_val_sep: &'s str,102		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,103	) -> Self {104		Self {105			padding: Cow::Owned(padding),106			mtype: JsonFormatting::Std,107			newline,108			key_val_sep,109			#[cfg(feature = "exp-preserve-order")]110			preserve_order,111			#[cfg(feature = "exp-bigint")]112			preserve_bigints: false,113		}114	}115	// Same format as CLI manifestification116	pub fn cli(117		padding: usize,118		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,119	) -> Self {120		if padding == 0 {121			return Self::minify(122				#[cfg(feature = "exp-preserve-order")]123				preserve_order,124			);125		}126		Self {127			padding: Cow::Owned(" ".repeat(padding)),128			mtype: JsonFormatting::Manifest,129			newline: "\n",130			key_val_sep: ": ",131			#[cfg(feature = "exp-preserve-order")]132			preserve_order,133			#[cfg(feature = "exp-bigint")]134			preserve_bigints: false,135		}136	}137}138impl Default for JsonFormat<'static> {139	fn default() -> Self {140		Self {141			padding: Cow::Borrowed("    "),142			mtype: JsonFormatting::Manifest,143			newline: "\n",144			key_val_sep: ": ",145			#[cfg(feature = "exp-preserve-order")]146			preserve_order: false,147			#[cfg(feature = "exp-bigint")]148			preserve_bigints: false,149		}150	}151}152153pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {154	let mut out = String::new();155	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;156	Ok(out)157}158fn manifest_json_ex_buf(159	val: &Val,160	buf: &mut String,161	cur_padding: &mut String,162	options: &JsonFormat<'_>,163) -> Result<()> {164	let mtype = options.mtype;165	match val {166		Val::Bool(v) => {167			if *v {168				buf.push_str("true");169			} else {170				buf.push_str("false");171			}172		}173		Val::Null => buf.push_str("null"),174		Val::Str(s) => escape_string_json_buf(&s.clone().into_flat(), buf),175		Val::Num(n) => write!(buf, "{n}").unwrap(),176		#[cfg(feature = "exp-bigint")]177		Val::BigInt(n) => if options.preserve_bigints {178			write!(buf, "{n}").unwrap()179		} else {180			write!(buf, "{:?}", n.to_string()).unwrap()181		},182		Val::Arr(items) => {183			buf.push('[');184			if !items.is_empty() {185				if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {186					buf.push_str(options.newline);187				}188189				let old_len = cur_padding.len();190				cur_padding.push_str(&options.padding);191				for (i, item) in items.iter().enumerate() {192					if i != 0 {193						buf.push(',');194						if mtype == JsonFormatting::ToString {195							buf.push(' ');196						} else if mtype != JsonFormatting::Minify {197							buf.push_str(options.newline);198						}199					}200					buf.push_str(cur_padding);201					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;202				}203				cur_padding.truncate(old_len);204205				if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {206					buf.push_str(options.newline);207					buf.push_str(cur_padding);208				}209			} else if mtype == JsonFormatting::Std {210				buf.push_str(options.newline);211				buf.push_str(options.newline);212				buf.push_str(cur_padding);213			} else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {214				buf.push(' ');215			}216			buf.push(']');217		}218		Val::Obj(obj) => {219			obj.run_assertions()?;220			buf.push('{');221			let fields = obj.fields(222				#[cfg(feature = "exp-preserve-order")]223				options.preserve_order,224			);225			if !fields.is_empty() {226				if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {227					buf.push_str(options.newline);228				}229230				let old_len = cur_padding.len();231				cur_padding.push_str(&options.padding);232				for (i, field) in fields.into_iter().enumerate() {233					if i != 0 {234						buf.push(',');235						if mtype == JsonFormatting::ToString {236							buf.push(' ');237						} else if mtype != JsonFormatting::Minify {238							buf.push_str(options.newline);239						}240					}241					buf.push_str(cur_padding);242					escape_string_json_buf(&field, buf);243					buf.push_str(options.key_val_sep);244					State::push_description(245						|| format!("field <{}> manifestification", field.clone()),246						|| {247							let value = obj.get(field.clone())?.unwrap();248							manifest_json_ex_buf(&value, buf, cur_padding, options)?;249							Ok(())250						},251					)?;252				}253				cur_padding.truncate(old_len);254255				if mtype != JsonFormatting::ToString && mtype != JsonFormatting::Minify {256					buf.push_str(options.newline);257					buf.push_str(cur_padding);258				}259			} else if mtype == JsonFormatting::Std {260				buf.push_str(options.newline);261				buf.push_str(options.newline);262				buf.push_str(cur_padding);263			} else if mtype == JsonFormatting::ToString || mtype == JsonFormatting::Manifest {264				buf.push(' ');265			}266			buf.push('}');267		}268		Val::Func(_) => bail!("tried to manifest function"),269	};270	Ok(())271}272273impl ManifestFormat for JsonFormat<'_> {274	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {275		manifest_json_ex_buf(&val, buf, &mut String::new(), self)276	}277}278279pub struct ToStringFormat;280impl ManifestFormat for ToStringFormat {281	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {282		JsonFormat::std_to_string().manifest_buf(val, out)283	}284	fn file_trailing_newline(&self) -> bool {285		false286	}287}288pub struct StringFormat;289impl ManifestFormat for StringFormat {290	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {291		let Val::Str(s) = val else {292			bail!(293				"output should be string for string manifest format, got {}",294				val.value_type()295			)296		};297		write!(out, "{s}").unwrap();298		Ok(())299	}300	fn file_trailing_newline(&self) -> bool {301		false302	}303}304305pub struct YamlStreamFormat<I>(pub I);306impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {307	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {308		let Val::Arr(arr) = val else {309			bail!(310				"output should be array for yaml stream format, got {}",311				val.value_type()312			)313		};314		if !arr.is_empty() {315			for v in arr.iter() {316				let v = v?;317				out.push_str("---\n");318				self.0.manifest_buf(v, out)?;319				out.push('\n');320			}321			out.push_str("...");322		}323		Ok(())324	}325}326327pub fn escape_string_json(s: &str) -> String {328	let mut buf = String::new();329	escape_string_json_buf(s, &mut buf);330	buf331}332333// Json string encoding was borrowed from https://github.com/serde-rs/json334335const BB: u8 = b'b'; // \x08336const TT: u8 = b't'; // \x09337const NN: u8 = b'n'; // \x0A338const FF: u8 = b'f'; // \x0C339const RR: u8 = b'r'; // \x0D340const QU: u8 = b'"'; // \x22341const BS: u8 = b'\\'; // \x5C342const UU: u8 = b'u'; // \x00...\x1F except the ones above343const __: u8 = 0;344345// Lookup table of escape sequences. A value of b'x' at index i means that byte346// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.347static ESCAPE: [u8; 256] = [348	//   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F349	UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0350	UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1351	__, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2352	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3353	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4354	__, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5355	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6356	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7357	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8358	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9359	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A360	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B361	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C362	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D363	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E364	__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F365];366367pub fn escape_string_json_buf(value: &str, buf: &mut String) {368	// Safety: we only write correct utf-8 in this function369	let buf: &mut Vec<u8> = unsafe { &mut *(buf as *mut String).cast::<Vec<u8>>() };370	let bytes = value.as_bytes();371372	// Perfect for ascii strings, removes any reallocations373	buf.reserve(value.len() + 2);374375	buf.push(b'"');376377	let mut start = 0;378379	for (i, &byte) in bytes.iter().enumerate() {380		let escape = ESCAPE[byte as usize];381		if escape == __ {382			continue;383		}384385		if start < i {386			buf.extend_from_slice(&bytes[start..i]);387		}388		start = i + 1;389390		match escape {391			self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {392				buf.extend_from_slice(&[b'\\', escape]);393			}394			self::UU => {395				static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";396				let bytes = &[397					b'\\',398					b'u',399					b'0',400					b'0',401					HEX_DIGITS[(byte >> 4) as usize],402					HEX_DIGITS[(byte & 0xF) as usize],403				];404				buf.extend_from_slice(bytes);405			}406			_ => unreachable!(),407		}408	}409410	if start == bytes.len() {411		buf.push(b'"');412		return;413	}414415	buf.extend_from_slice(&bytes[start..]);416	buf.push(b'"');417}