git.delta.rocks / jrsonnet / refs/commits / 0831da3ed8d9

difftreelog

source

crates/jrsonnet-evaluator/src/stdlib/manifest.rs9.4 KiBsourcehistory
1use crate::{2	error::{Error::*, Result},3	throw, State, Val,4};56#[derive(PartialEq, Eq, Clone, Copy)]7pub enum ManifestType {8	// Applied in manifestification9	Manifest,10	/// Used for std.manifestJson11	/// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest12	Std,13	/// No line breaks, used in `obj+''`14	ToString,15	/// Minified json16	Minify,17}1819pub struct ManifestJsonOptions<'s> {20	pub padding: &'s str,21	pub mtype: ManifestType,22	pub newline: &'s str,23	pub key_val_sep: &'s str,24	#[cfg(feature = "exp-preserve-order")]25	pub preserve_order: bool,26}2728pub fn manifest_json_ex(s: State, val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {29	let mut out = String::new();30	manifest_json_ex_buf(s, val, &mut out, &mut String::new(), options)?;31	Ok(out)32}33fn manifest_json_ex_buf(34	s: State,35	val: &Val,36	buf: &mut String,37	cur_padding: &mut String,38	options: &ManifestJsonOptions<'_>,39) -> Result<()> {40	use std::fmt::Write;41	let mtype = options.mtype;42	match val {43		Val::Bool(v) => {44			if *v {45				buf.push_str("true");46			} else {47				buf.push_str("false");48			}49		}50		Val::Null => buf.push_str("null"),51		Val::Str(s) => escape_string_json_buf(s, buf),52		Val::Num(n) => write!(buf, "{}", n).unwrap(),53		Val::Arr(items) => {54			buf.push('[');55			if !items.is_empty() {56				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {57					buf.push_str(options.newline);58				}5960				let old_len = cur_padding.len();61				cur_padding.push_str(options.padding);62				for (i, item) in items.iter(s.clone()).enumerate() {63					if i != 0 {64						buf.push(',');65						if mtype == ManifestType::ToString {66							buf.push(' ');67						} else if mtype != ManifestType::Minify {68							buf.push_str(options.newline);69						}70					}71					buf.push_str(cur_padding);72					manifest_json_ex_buf(s.clone(), &item?, buf, cur_padding, options)?;73				}74				cur_padding.truncate(old_len);7576				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {77					buf.push_str(options.newline);78					buf.push_str(cur_padding);79				}80			} else if mtype == ManifestType::Std {81				buf.push_str("\n\n");82				buf.push_str(cur_padding);83			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {84				buf.push(' ');85			}86			buf.push(']');87		}88		Val::Obj(obj) => {89			obj.run_assertions(s.clone())?;90			buf.push('{');91			let fields = obj.fields(92				#[cfg(feature = "exp-preserve-order")]93				options.preserve_order,94			);95			if !fields.is_empty() {96				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {97					buf.push_str(options.newline);98				}99100				let old_len = cur_padding.len();101				cur_padding.push_str(options.padding);102				for (i, field) in fields.into_iter().enumerate() {103					if i != 0 {104						buf.push(',');105						if mtype == ManifestType::ToString {106							buf.push(' ');107						} else if mtype != ManifestType::Minify {108							buf.push_str(options.newline);109						}110					}111					buf.push_str(cur_padding);112					escape_string_json_buf(&field, buf);113					buf.push_str(options.key_val_sep);114					s.push_description(115						|| format!("field <{}> manifestification", field.clone()),116						|| {117							let value = obj.get(s.clone(), field.clone())?.unwrap();118							manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?;119							Ok(Val::Null)120						},121					)?;122				}123				cur_padding.truncate(old_len);124125				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {126					buf.push_str(options.newline);127					buf.push_str(cur_padding);128				}129			} else if mtype == ManifestType::Std {130				buf.push_str("\n\n");131				buf.push_str(cur_padding);132			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {133				buf.push(' ');134			}135			buf.push('}');136		}137		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),138	};139	Ok(())140}141142pub fn escape_string_json(s: &str) -> String {143	let mut buf = String::new();144	escape_string_json_buf(s, &mut buf);145	buf146}147148fn escape_string_json_buf(s: &str, buf: &mut String) {149	use std::fmt::Write;150	buf.push('"');151	for c in s.chars() {152		match c {153			'"' => buf.push_str("\\\""),154			'\\' => buf.push_str("\\\\"),155			'\u{0008}' => buf.push_str("\\b"),156			'\u{000c}' => buf.push_str("\\f"),157			'\n' => buf.push_str("\\n"),158			'\r' => buf.push_str("\\r"),159			'\t' => buf.push_str("\\t"),160			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {161				write!(buf, "\\u{:04x}", c as u32).unwrap();162			}163			c => buf.push(c),164		}165	}166	buf.push('"');167}168169pub struct ManifestYamlOptions<'s> {170	/// Padding before fields, i.e171	/// ```yaml172	/// a:173	///   b:174	/// ## <- this175	/// ```176	pub padding: &'s str,177	/// Padding before array elements in objects178	/// ```yaml179	/// a:180	///   - 1181	/// ## <- this182	/// ```183	pub arr_element_padding: &'s str,184	/// Should yaml keys appear unescaped, when possible185	/// ```yaml186	/// "safe_key": 1187	/// # vs188	/// safe_key: 1189	/// ```190	pub quote_keys: bool,191	/// If true - then order of fields is preserved as written,192	/// instead of sorting alphabetically193	#[cfg(feature = "exp-preserve-order")]194	pub preserve_order: bool,195}196197/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>198/// With added date check199fn yaml_needs_quotes(string: &str) -> bool {200	fn need_quotes_spaces(string: &str) -> bool {201		string.starts_with(' ') || string.ends_with(' ')202	}203204	string.is_empty()205		|| need_quotes_spaces(string)206		|| string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))207		|| string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))208		|| [209			// http://yaml.org/type/bool.html210			// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse211			// them as string, not booleans, although it is violating the YAML 1.1 specification.212			// See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.213			"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",214			"on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html215			"null", "Null", "NULL", "~",216		].contains(&string)217		|| (string.chars().all(|c| matches!(c, '0'..='9' | '-'))218			&& string.chars().filter(|c| *c == '-').count() == 2)219		|| string.starts_with('.')220		|| string.starts_with("0x")221		|| string.parse::<i64>().is_ok()222		|| string.parse::<f64>().is_ok()223}224225pub fn manifest_yaml_ex(s: State, val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {226	let mut out = String::new();227	manifest_yaml_ex_buf(s, val, &mut out, &mut String::new(), options)?;228	Ok(out)229}230231#[allow(clippy::too_many_lines)]232fn manifest_yaml_ex_buf(233	s: State,234	val: &Val,235	buf: &mut String,236	cur_padding: &mut String,237	options: &ManifestYamlOptions<'_>,238) -> Result<()> {239	use std::fmt::Write;240	match val {241		Val::Bool(v) => {242			if *v {243				buf.push_str("true");244			} else {245				buf.push_str("false");246			}247		}248		Val::Null => buf.push_str("null"),249		Val::Str(s) => {250			if s.is_empty() {251				buf.push_str("\"\"");252			} else if let Some(s) = s.strip_suffix('\n') {253				buf.push('|');254				for line in s.split('\n') {255					buf.push('\n');256					buf.push_str(cur_padding);257					buf.push_str(options.padding);258					buf.push_str(line);259				}260			} else if !options.quote_keys && !yaml_needs_quotes(s) {261				buf.push_str(s);262			} else {263				escape_string_json_buf(s, buf);264			}265		}266		Val::Num(n) => write!(buf, "{}", *n).unwrap(),267		Val::Arr(a) => {268			if a.is_empty() {269				buf.push_str("[]");270			} else {271				for (i, item) in a.iter(s.clone()).enumerate() {272					if i != 0 {273						buf.push('\n');274						buf.push_str(cur_padding);275					}276					let item = item?;277					buf.push('-');278					match &item {279						Val::Arr(a) if !a.is_empty() => {280							buf.push('\n');281							buf.push_str(cur_padding);282							buf.push_str(options.padding);283						}284						_ => buf.push(' '),285					}286					let extra_padding = match &item {287						Val::Arr(a) => !a.is_empty(),288						Val::Obj(o) => !o.is_empty(),289						_ => false,290					};291					let prev_len = cur_padding.len();292					if extra_padding {293						cur_padding.push_str(options.padding);294					}295					manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;296					cur_padding.truncate(prev_len);297				}298			}299		}300		Val::Obj(o) => {301			if o.is_empty() {302				buf.push_str("{}");303			} else {304				for (i, key) in o305					.fields(306						#[cfg(feature = "exp-preserve-order")]307						options.preserve_order,308					)309					.iter()310					.enumerate()311				{312					if i != 0 {313						buf.push('\n');314						buf.push_str(cur_padding);315					}316					if !options.quote_keys && !yaml_needs_quotes(key) {317						buf.push_str(key);318					} else {319						escape_string_json_buf(key, buf);320					}321					buf.push(':');322					let prev_len = cur_padding.len();323					let item = o.get(s.clone(), key.clone())?.expect("field exists");324					match &item {325						Val::Arr(a) if !a.is_empty() => {326							buf.push('\n');327							buf.push_str(cur_padding);328							buf.push_str(options.arr_element_padding);329							cur_padding.push_str(options.arr_element_padding);330						}331						Val::Obj(o) if !o.is_empty() => {332							buf.push('\n');333							buf.push_str(cur_padding);334							buf.push_str(options.padding);335							cur_padding.push_str(options.padding);336						}337						_ => buf.push(' '),338					}339					manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;340					cur_padding.truncate(prev_len);341				}342			}343		}344		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),345	}346	Ok(())347}