git.delta.rocks / jrsonnet / refs/commits / 0d5cefbba5bd

difftreelog

fix missing yaml string padding

Yaroslav Bolyukin2022-04-21parent: #e533491.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/builtin/manifest.rs
1use crate::{2	error::{Error::*, Result},3	push_description_frame, throw, Val,4};56#[derive(PartialEq, 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(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {29	let mut out = String::new();30	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;31	Ok(out)32}33fn manifest_json_ex_buf(34	val: &Val,35	buf: &mut String,36	cur_padding: &mut String,37	options: &ManifestJsonOptions<'_>,38) -> Result<()> {39	use std::fmt::Write;40	let mtype = options.mtype;41	match val {42		Val::Bool(v) => {43			if *v {44				buf.push_str("true");45			} else {46				buf.push_str("false");47			}48		}49		Val::Null => buf.push_str("null"),50		Val::Str(s) => escape_string_json_buf(s, buf),51		Val::Num(n) => write!(buf, "{}", n).unwrap(),52		Val::Arr(items) => {53			buf.push('[');54			if !items.is_empty() {55				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {56					buf.push_str(options.newline);57				}5859				let old_len = cur_padding.len();60				cur_padding.push_str(options.padding);61				for (i, item) in items.iter().enumerate() {62					if i != 0 {63						buf.push(',');64						if mtype == ManifestType::ToString {65							buf.push(' ');66						} else if mtype != ManifestType::Minify {67							buf.push_str(options.newline);68						}69					}70					buf.push_str(cur_padding);71					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;72				}73				cur_padding.truncate(old_len);7475				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {76					buf.push_str(options.newline);77					buf.push_str(cur_padding);78				}79			} else if mtype == ManifestType::Std {80				buf.push_str("\n\n");81				buf.push_str(cur_padding);82			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {83				buf.push(' ');84			}85			buf.push(']');86		}87		Val::Obj(obj) => {88			obj.run_assertions()?;89			buf.push('{');90			let fields = obj.fields(91				#[cfg(feature = "exp-preserve-order")]92				options.preserve_order,93			);94			if !fields.is_empty() {95				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {96					buf.push_str(options.newline);97				}9899				let old_len = cur_padding.len();100				cur_padding.push_str(options.padding);101				for (i, field) in fields.into_iter().enumerate() {102					if i != 0 {103						buf.push(',');104						if mtype == ManifestType::ToString {105							buf.push(' ');106						} else if mtype != ManifestType::Minify {107							buf.push_str(options.newline);108						}109					}110					buf.push_str(cur_padding);111					escape_string_json_buf(&field, buf);112					buf.push_str(options.key_val_sep);113					push_description_frame(114						|| format!("field <{}> manifestification", field.clone()),115						|| {116							let value = obj.get(field.clone())?.unwrap();117							manifest_json_ex_buf(&value, buf, cur_padding, options)?;118							Ok(Val::Null)119						},120					)?;121				}122				cur_padding.truncate(old_len);123124				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {125					buf.push_str(options.newline);126					buf.push_str(cur_padding);127				}128			} else if mtype == ManifestType::Std {129				buf.push_str("\n\n");130				buf.push_str(cur_padding);131			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {132				buf.push(' ');133			}134			buf.push('}');135		}136		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),137	};138	Ok(())139}140141pub fn escape_string_json(s: &str) -> String {142	let mut buf = String::new();143	escape_string_json_buf(s, &mut buf);144	buf145}146147fn escape_string_json_buf(s: &str, buf: &mut String) {148	use std::fmt::Write;149	buf.push('"');150	for c in s.chars() {151		match c {152			'"' => buf.push_str("\\\""),153			'\\' => buf.push_str("\\\\"),154			'\u{0008}' => buf.push_str("\\b"),155			'\u{000c}' => buf.push_str("\\f"),156			'\n' => buf.push_str("\\n"),157			'\r' => buf.push_str("\\r"),158			'\t' => buf.push_str("\\t"),159			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {160				write!(buf, "\\u{:04x}", c as u32).unwrap()161			}162			c => buf.push(c),163		}164	}165	buf.push('"');166}167168pub struct ManifestYamlOptions<'s> {169	/// Padding before fields, i.e170	/// ```yaml171	/// a:172	///   b:173	/// ## <- this174	/// ```175	pub padding: &'s str,176	/// Padding before array elements in objects177	/// ```yaml178	/// a:179	///   - 1180	/// ## <- this181	/// ```182	pub arr_element_padding: &'s str,183	/// Should yaml keys appear unescaped, when possible184	/// ```yaml185	/// "safe_key": 1186	/// # vs187	/// safe_key: 1188	/// ```189	pub quote_keys: bool,190	/// If true - then order of fields is preserved as written,191	/// instead of sorting alphabetically192	#[cfg(feature = "exp-preserve-order")]193	pub preserve_order: bool,194}195196/// From https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289197/// With added date check198fn yaml_needs_quotes(string: &str) -> bool {199	fn need_quotes_spaces(string: &str) -> bool {200		string.starts_with(' ') || string.ends_with(' ')201	}202203	string.is_empty()204		|| need_quotes_spaces(string)205		|| string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))206		|| string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))207		|| [208			// http://yaml.org/type/bool.html209			// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse210			// them as string, not booleans, although it is violating the YAML 1.1 specification.211			// See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.212			"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",213			"on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html214			"null", "Null", "NULL", "~",215		].contains(&string)216		|| (string.chars().all(|c| matches!(c, '0'..='9' | '-'))217			&& string.chars().filter(|c| *c == '-').count() == 2)218		|| string.starts_with('.')219		|| string.starts_with("0x")220		|| string.parse::<i64>().is_ok()221		|| string.parse::<f64>().is_ok()222}223224pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {225	let mut out = String::new();226	manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;227	Ok(out)228}229fn manifest_yaml_ex_buf(230	val: &Val,231	buf: &mut String,232	cur_padding: &mut String,233	options: &ManifestYamlOptions<'_>,234) -> Result<()> {235	use std::fmt::Write;236	match val {237		Val::Bool(v) => {238			if *v {239				buf.push_str("true")240			} else {241				buf.push_str("false")242			}243		}244		Val::Null => buf.push_str("null"),245		Val::Str(s) => {246			if s.is_empty() {247				buf.push_str("\"\"");248			} else if let Some(s) = s.strip_suffix('\n') {249				buf.push('|');250				for line in s.split('\n') {251					buf.push('\n');252					buf.push_str(options.padding);253					buf.push_str(line);254				}255			} else if !options.quote_keys && !yaml_needs_quotes(s) {256				buf.push_str(s);257			} else {258				escape_string_json_buf(s, buf);259			}260		}261		Val::Num(n) => write!(buf, "{}", *n).unwrap(),262		Val::Arr(a) => {263			if a.is_empty() {264				buf.push_str("[]");265			} else {266				for (i, item) in a.iter().enumerate() {267					if i != 0 {268						buf.push('\n');269						buf.push_str(cur_padding);270					}271					let item = item?;272					buf.push('-');273					match &item {274						Val::Arr(a) if !a.is_empty() => {275							buf.push('\n');276							buf.push_str(cur_padding);277							buf.push_str(options.padding);278						}279						_ => buf.push(' '),280					}281					let extra_padding = match &item {282						Val::Arr(a) => !a.is_empty(),283						Val::Obj(o) => !o.is_empty(),284						_ => false,285					};286					let prev_len = cur_padding.len();287					if extra_padding {288						cur_padding.push_str(options.padding);289					}290					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;291					cur_padding.truncate(prev_len);292				}293			}294		}295		Val::Obj(o) => {296			if o.is_empty() {297				buf.push_str("{}");298			} else {299				for (i, key) in o300					.fields(301						#[cfg(feature = "exp-preserve-order")]302						options.preserve_order,303					)304					.iter()305					.enumerate()306				{307					if i != 0 {308						buf.push('\n');309						buf.push_str(cur_padding);310					}311					if !options.quote_keys && !yaml_needs_quotes(key) {312						buf.push_str(key);313					} else {314						escape_string_json_buf(key, buf);315					}316					buf.push(':');317					let prev_len = cur_padding.len();318					let item = o.get(key.clone())?.expect("field exists");319					match &item {320						Val::Arr(a) if !a.is_empty() => {321							buf.push('\n');322							buf.push_str(cur_padding);323							buf.push_str(options.arr_element_padding);324							cur_padding.push_str(options.arr_element_padding);325						}326						Val::Obj(o) if !o.is_empty() => {327							buf.push('\n');328							buf.push_str(cur_padding);329							buf.push_str(options.padding);330							cur_padding.push_str(options.padding);331						}332						_ => buf.push(' '),333					}334					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;335					cur_padding.truncate(prev_len);336				}337			}338		}339		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),340	}341	Ok(())342}
after · crates/jrsonnet-evaluator/src/builtin/manifest.rs
1use crate::{2	error::{Error::*, Result},3	push_description_frame, throw, Val,4};56#[derive(PartialEq, 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(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {29	let mut out = String::new();30	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;31	Ok(out)32}33fn manifest_json_ex_buf(34	val: &Val,35	buf: &mut String,36	cur_padding: &mut String,37	options: &ManifestJsonOptions<'_>,38) -> Result<()> {39	use std::fmt::Write;40	let mtype = options.mtype;41	match val {42		Val::Bool(v) => {43			if *v {44				buf.push_str("true");45			} else {46				buf.push_str("false");47			}48		}49		Val::Null => buf.push_str("null"),50		Val::Str(s) => escape_string_json_buf(s, buf),51		Val::Num(n) => write!(buf, "{}", n).unwrap(),52		Val::Arr(items) => {53			buf.push('[');54			if !items.is_empty() {55				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {56					buf.push_str(options.newline);57				}5859				let old_len = cur_padding.len();60				cur_padding.push_str(options.padding);61				for (i, item) in items.iter().enumerate() {62					if i != 0 {63						buf.push(',');64						if mtype == ManifestType::ToString {65							buf.push(' ');66						} else if mtype != ManifestType::Minify {67							buf.push_str(options.newline);68						}69					}70					buf.push_str(cur_padding);71					manifest_json_ex_buf(&item?, buf, cur_padding, options)?;72				}73				cur_padding.truncate(old_len);7475				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {76					buf.push_str(options.newline);77					buf.push_str(cur_padding);78				}79			} else if mtype == ManifestType::Std {80				buf.push_str("\n\n");81				buf.push_str(cur_padding);82			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {83				buf.push(' ');84			}85			buf.push(']');86		}87		Val::Obj(obj) => {88			obj.run_assertions()?;89			buf.push('{');90			let fields = obj.fields(91				#[cfg(feature = "exp-preserve-order")]92				options.preserve_order,93			);94			if !fields.is_empty() {95				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {96					buf.push_str(options.newline);97				}9899				let old_len = cur_padding.len();100				cur_padding.push_str(options.padding);101				for (i, field) in fields.into_iter().enumerate() {102					if i != 0 {103						buf.push(',');104						if mtype == ManifestType::ToString {105							buf.push(' ');106						} else if mtype != ManifestType::Minify {107							buf.push_str(options.newline);108						}109					}110					buf.push_str(cur_padding);111					escape_string_json_buf(&field, buf);112					buf.push_str(options.key_val_sep);113					push_description_frame(114						|| format!("field <{}> manifestification", field.clone()),115						|| {116							let value = obj.get(field.clone())?.unwrap();117							manifest_json_ex_buf(&value, buf, cur_padding, options)?;118							Ok(Val::Null)119						},120					)?;121				}122				cur_padding.truncate(old_len);123124				if mtype != ManifestType::ToString && mtype != ManifestType::Minify {125					buf.push_str(options.newline);126					buf.push_str(cur_padding);127				}128			} else if mtype == ManifestType::Std {129				buf.push_str("\n\n");130				buf.push_str(cur_padding);131			} else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {132				buf.push(' ');133			}134			buf.push('}');135		}136		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),137	};138	Ok(())139}140141pub fn escape_string_json(s: &str) -> String {142	let mut buf = String::new();143	escape_string_json_buf(s, &mut buf);144	buf145}146147fn escape_string_json_buf(s: &str, buf: &mut String) {148	use std::fmt::Write;149	buf.push('"');150	for c in s.chars() {151		match c {152			'"' => buf.push_str("\\\""),153			'\\' => buf.push_str("\\\\"),154			'\u{0008}' => buf.push_str("\\b"),155			'\u{000c}' => buf.push_str("\\f"),156			'\n' => buf.push_str("\\n"),157			'\r' => buf.push_str("\\r"),158			'\t' => buf.push_str("\\t"),159			c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {160				write!(buf, "\\u{:04x}", c as u32).unwrap()161			}162			c => buf.push(c),163		}164	}165	buf.push('"');166}167168pub struct ManifestYamlOptions<'s> {169	/// Padding before fields, i.e170	/// ```yaml171	/// a:172	///   b:173	/// ## <- this174	/// ```175	pub padding: &'s str,176	/// Padding before array elements in objects177	/// ```yaml178	/// a:179	///   - 1180	/// ## <- this181	/// ```182	pub arr_element_padding: &'s str,183	/// Should yaml keys appear unescaped, when possible184	/// ```yaml185	/// "safe_key": 1186	/// # vs187	/// safe_key: 1188	/// ```189	pub quote_keys: bool,190	/// If true - then order of fields is preserved as written,191	/// instead of sorting alphabetically192	#[cfg(feature = "exp-preserve-order")]193	pub preserve_order: bool,194}195196/// From https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289197/// With added date check198fn yaml_needs_quotes(string: &str) -> bool {199	fn need_quotes_spaces(string: &str) -> bool {200		string.starts_with(' ') || string.ends_with(' ')201	}202203	string.is_empty()204		|| need_quotes_spaces(string)205		|| string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))206		|| string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))207		|| [208			// http://yaml.org/type/bool.html209			// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse210			// them as string, not booleans, although it is violating the YAML 1.1 specification.211			// See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.212			"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",213			"on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html214			"null", "Null", "NULL", "~",215		].contains(&string)216		|| (string.chars().all(|c| matches!(c, '0'..='9' | '-'))217			&& string.chars().filter(|c| *c == '-').count() == 2)218		|| string.starts_with('.')219		|| string.starts_with("0x")220		|| string.parse::<i64>().is_ok()221		|| string.parse::<f64>().is_ok()222}223224pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {225	let mut out = String::new();226	manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;227	Ok(out)228}229fn manifest_yaml_ex_buf(230	val: &Val,231	buf: &mut String,232	cur_padding: &mut String,233	options: &ManifestYamlOptions<'_>,234) -> Result<()> {235	use std::fmt::Write;236	match val {237		Val::Bool(v) => {238			if *v {239				buf.push_str("true")240			} else {241				buf.push_str("false")242			}243		}244		Val::Null => buf.push_str("null"),245		Val::Str(s) => {246			if s.is_empty() {247				buf.push_str("\"\"");248			} else if let Some(s) = s.strip_suffix('\n') {249				buf.push('|');250				for line in s.split('\n') {251					buf.push('\n');252					buf.push_str(&cur_padding);253					buf.push_str(options.padding);254					buf.push_str(line);255				}256			} else if !options.quote_keys && !yaml_needs_quotes(s) {257				buf.push_str(s);258			} else {259				escape_string_json_buf(s, buf);260			}261		}262		Val::Num(n) => write!(buf, "{}", *n).unwrap(),263		Val::Arr(a) => {264			if a.is_empty() {265				buf.push_str("[]");266			} else {267				for (i, item) in a.iter().enumerate() {268					if i != 0 {269						buf.push('\n');270						buf.push_str(cur_padding);271					}272					let item = item?;273					buf.push('-');274					match &item {275						Val::Arr(a) if !a.is_empty() => {276							buf.push('\n');277							buf.push_str(cur_padding);278							buf.push_str(options.padding);279						}280						_ => buf.push(' '),281					}282					let extra_padding = match &item {283						Val::Arr(a) => !a.is_empty(),284						Val::Obj(o) => !o.is_empty(),285						_ => false,286					};287					let prev_len = cur_padding.len();288					if extra_padding {289						cur_padding.push_str(options.padding);290					}291					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;292					cur_padding.truncate(prev_len);293				}294			}295		}296		Val::Obj(o) => {297			if o.is_empty() {298				buf.push_str("{}");299			} else {300				for (i, key) in o301					.fields(302						#[cfg(feature = "exp-preserve-order")]303						options.preserve_order,304					)305					.iter()306					.enumerate()307				{308					if i != 0 {309						buf.push('\n');310						buf.push_str(cur_padding);311					}312					if !options.quote_keys && !yaml_needs_quotes(key) {313						buf.push_str(key);314					} else {315						escape_string_json_buf(key, buf);316					}317					buf.push(':');318					let prev_len = cur_padding.len();319					let item = o.get(key.clone())?.expect("field exists");320					match &item {321						Val::Arr(a) if !a.is_empty() => {322							buf.push('\n');323							buf.push_str(cur_padding);324							buf.push_str(options.arr_element_padding);325							cur_padding.push_str(options.arr_element_padding);326						}327						Val::Obj(o) if !o.is_empty() => {328							buf.push('\n');329							buf.push_str(cur_padding);330							buf.push_str(options.padding);331							cur_padding.push_str(options.padding);332						}333						_ => buf.push(' '),334					}335					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;336					cur_padding.truncate(prev_len);337				}338			}339		}340		Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),341	}342	Ok(())343}