From b118a83916a3756fa023595e4b72b9da098970af Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sat, 27 Nov 2021 22:09:21 +0000 Subject: [PATCH] feat: quote_keys option for Yaml --- --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,12 @@ ] [[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] name = "gcmodule" version = "0.3.3" source = "git+https://github.com/CertainLach/gcmodule?branch=jrsonnet#f72713c24c2b1bf5a78f1d01bee5a0f52bc2a094" @@ -216,6 +222,7 @@ "rustc-hash", "serde", "serde_json", + "serde_yaml", "thiserror", ] @@ -273,6 +280,12 @@ checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" [[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] name = "lock_api" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -467,6 +480,17 @@ ] [[package]] +name = "serde_yaml" +version = "0.8.21" +source = "git+https://github.com/CertainLach/serde-yaml?branch=feature/old-octals-quirk#4bf0e325243539fdeb419e8d727ed1c161cbe445" +dependencies = [ + "dtoa", + "indexmap", + "serde", + "yaml-rust", +] + +[[package]] name = "smallvec" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -601,6 +625,15 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] name = "yansi-term" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" --- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs +++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs @@ -173,6 +173,62 @@ /// ## <- this /// ``` pub arr_element_padding: &'s str, + /// Should yaml keys appear unescaped, when possible + /// ```yaml + /// "safe_key": 1 + /// # vs + /// safe_key: 1 + /// ``` + pub quote_keys: bool, +} + +/// From https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289 +/// With added date check +fn yaml_needs_quotes(string: &str) -> bool { + fn need_quotes_spaces(string: &str) -> bool { + string.starts_with(' ') || string.ends_with(' ') + } + + string == "" + || need_quotes_spaces(string) + || string.starts_with(|character: char| match character { + '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@' => true, + _ => false, + }) || string.contains(|character: char| match character { + ':' + | '{' + | '}' + | '[' + | ']' + | ',' + | '#' + | '`' + | '\"' + | '\'' + | '\\' + | '\0'..='\x06' + | '\t' + | '\n' + | '\r' + | '\x0e'..='\x1a' + | '\x1c'..='\x1f' => true, + _ => false, + }) || [ + // http://yaml.org/type/bool.html + // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse + // them as string, not booleans, although it is violating the YAML 1.1 specification. + // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088. + "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false", + "on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html + "null", "Null", "NULL", "~", + ] + .contains(&string) + || (string.chars().all(|c| matches!(c, '0'..='9' | '-')) + && string.chars().filter(|c| *c == '-').count() == 2) + || string.starts_with('.') + || string.starts_with("0x") + || string.parse::().is_ok() + || string.parse::().is_ok() } pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result { @@ -206,8 +262,10 @@ buf.push_str(options.padding); buf.push_str(line); } + } else if !options.quote_keys && !yaml_needs_quotes(&s) { + buf.push_str(&s); } else { - escape_string_json_buf(s, buf) + escape_string_json_buf(s, buf); } } Val::Num(n) => write!(buf, "{}", *n).unwrap(), @@ -253,7 +311,11 @@ buf.push('\n'); buf.push_str(cur_padding); } - escape_string_json_buf(key, buf); + if !options.quote_keys && !yaml_needs_quotes(&key) { + buf.push_str(&key); + } else { + escape_string_json_buf(key, buf); + } buf.push(':'); let prev_len = cur_padding.len(); let item = o.get(key.clone())?.expect("field exists"); --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -753,13 +753,15 @@ _loc: &ExprLocation, args: &ArgsDesc, ) -> Result { - parse_args!(context, "manifestYamlDoc", args, 2, [ + parse_args!(context, "manifestYamlDoc", args, 3, [ 0, value: ty!(any); 1, indent_array_in_object: ty!(boolean) => Val::Bool; + 2, quote_keys: ty!(boolean) => Val::Bool; ], { Ok(Val::Str(manifest_yaml_ex(&value, &ManifestYamlOptions { padding: " ", arr_element_padding: if indent_array_in_object { " " } else { "" }, + quote_keys, })?.into())) }) } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -556,6 +556,7 @@ &ManifestYamlOptions { padding, arr_element_padding: padding, + quote_keys: false, }, ) .map(|s| s.into()) --- a/crates/jrsonnet-stdlib/src/std.jsonnet +++ b/crates/jrsonnet-stdlib/src/std.jsonnet @@ -377,7 +377,7 @@ manifestYamlDocImpl:: $intrinsic(manifestYamlDocImpl), - manifestYamlDoc(value, indent_array_in_object=false):: std.manifestYamlDocImpl(value, indent_array_in_object), + manifestYamlDoc(value, indent_array_in_object=false, quote_keys=true):: std.manifestYamlDocImpl(value, indent_array_in_object, quote_keys), manifestYamlStream(value, indent_array_in_object=false, c_document_end=true):: if !std.isArray(value) then -- gitstuff