difftreelog
feat quote_keys option for Yaml
in: master
5 files changed
Cargo.lockdiffbeforeafterboth--- 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"
crates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth1use crate::error::Error::*;2use crate::error::Result;3use crate::push_description_frame;4use crate::{throw, Val};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}2324pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {25 let mut out = String::new();26 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;27 Ok(out)28}29fn manifest_json_ex_buf(30 val: &Val,31 buf: &mut String,32 cur_padding: &mut String,33 options: &ManifestJsonOptions<'_>,34) -> Result<()> {35 use std::fmt::Write;36 let mtype = options.mtype;37 match val {38 Val::Bool(v) => {39 if *v {40 buf.push_str("true");41 } else {42 buf.push_str("false");43 }44 }45 Val::Null => buf.push_str("null"),46 Val::Str(s) => escape_string_json_buf(s, buf),47 Val::Num(n) => write!(buf, "{}", n).unwrap(),48 Val::Arr(items) => {49 buf.push('[');50 if !items.is_empty() {51 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {52 buf.push('\n');53 }5455 let old_len = cur_padding.len();56 cur_padding.push_str(options.padding);57 for (i, item) in items.iter().enumerate() {58 if i != 0 {59 buf.push(',');60 if mtype == ManifestType::ToString {61 buf.push(' ');62 } else if mtype != ManifestType::Minify {63 buf.push('\n');64 }65 }66 buf.push_str(cur_padding);67 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;68 }69 cur_padding.truncate(old_len);7071 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {72 buf.push('\n');73 buf.push_str(cur_padding);74 }75 } else if mtype == ManifestType::Std {76 buf.push_str("\n\n");77 buf.push_str(cur_padding);78 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {79 buf.push(' ');80 }81 buf.push(']');82 }83 Val::Obj(obj) => {84 obj.run_assertions()?;85 buf.push('{');86 let fields = obj.fields();87 if !fields.is_empty() {88 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {89 buf.push('\n');90 }9192 let old_len = cur_padding.len();93 cur_padding.push_str(options.padding);94 for (i, field) in fields.into_iter().enumerate() {95 if i != 0 {96 buf.push(',');97 if mtype == ManifestType::ToString {98 buf.push(' ');99 } else if mtype != ManifestType::Minify {100 buf.push('\n');101 }102 }103 buf.push_str(cur_padding);104 escape_string_json_buf(&field, buf);105 buf.push_str(": ");106 push_description_frame(107 || format!("field <{}> manifestification", field.clone()),108 || {109 let value = obj.get(field.clone())?.unwrap();110 manifest_json_ex_buf(&value, buf, cur_padding, options)?;111 Ok(Val::Null)112 },113 )?;114 }115 cur_padding.truncate(old_len);116117 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {118 buf.push('\n');119 buf.push_str(cur_padding);120 }121 } else if mtype == ManifestType::Std {122 buf.push_str("\n\n");123 buf.push_str(cur_padding);124 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {125 buf.push(' ');126 }127 buf.push('}');128 }129 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),130 };131 Ok(())132}133134pub fn escape_string_json(s: &str) -> String {135 let mut buf = String::new();136 escape_string_json_buf(s, &mut buf);137 buf138}139140fn escape_string_json_buf(s: &str, buf: &mut String) {141 use std::fmt::Write;142 buf.push('"');143 for c in s.chars() {144 match c {145 '"' => buf.push_str("\\\""),146 '\\' => buf.push_str("\\\\"),147 '\u{0008}' => buf.push_str("\\b"),148 '\u{000c}' => buf.push_str("\\f"),149 '\n' => buf.push_str("\\n"),150 '\r' => buf.push_str("\\r"),151 '\t' => buf.push_str("\\t"),152 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {153 write!(buf, "\\u{:04x}", c as u32).unwrap()154 }155 c => buf.push(c),156 }157 }158 buf.push('"');159}160161pub struct ManifestYamlOptions<'s> {162 /// Padding before fields, i.e163 /// ```yaml164 /// a:165 /// b:166 /// ## <- this167 /// ```168 pub padding: &'s str,169 /// Padding before array elements in objects170 /// ```yaml171 /// a:172 /// - 1173 /// ## <- this174 /// ```175 pub arr_element_padding: &'s str,176}177178pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {179 let mut out = String::new();180 manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;181 Ok(out)182}183fn manifest_yaml_ex_buf(184 val: &Val,185 buf: &mut String,186 cur_padding: &mut String,187 options: &ManifestYamlOptions<'_>,188) -> Result<()> {189 use std::fmt::Write;190 match val {191 Val::Bool(v) => {192 if *v {193 buf.push_str("true")194 } else {195 buf.push_str("false")196 }197 }198 Val::Null => buf.push_str("null"),199 Val::Str(s) => {200 if s.is_empty() {201 buf.push_str("\"\"");202 } else if let Some(s) = s.strip_suffix('\n') {203 buf.push('|');204 for line in s.split('\n') {205 buf.push('\n');206 buf.push_str(options.padding);207 buf.push_str(line);208 }209 } else {210 escape_string_json_buf(s, buf)211 }212 }213 Val::Num(n) => write!(buf, "{}", *n).unwrap(),214 Val::Arr(a) => {215 if a.is_empty() {216 buf.push_str("[]");217 } else {218 for (i, item) in a.iter().enumerate() {219 if i != 0 {220 buf.push('\n');221 buf.push_str(cur_padding);222 }223 let item = item?;224 buf.push('-');225 match &item {226 Val::Arr(a) if !a.is_empty() => {227 buf.push('\n');228 buf.push_str(cur_padding);229 buf.push_str(options.padding);230 }231 _ => buf.push(' '),232 }233 let extra_padding = match &item {234 Val::Arr(a) => !a.is_empty(),235 Val::Obj(o) => !o.is_empty(),236 _ => false,237 };238 let prev_len = cur_padding.len();239 if extra_padding {240 cur_padding.push_str(options.padding);241 }242 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;243 cur_padding.truncate(prev_len);244 }245 }246 }247 Val::Obj(o) => {248 if o.is_empty() {249 buf.push_str("{}");250 } else {251 for (i, key) in o.fields().iter().enumerate() {252 if i != 0 {253 buf.push('\n');254 buf.push_str(cur_padding);255 }256 escape_string_json_buf(key, buf);257 buf.push(':');258 let prev_len = cur_padding.len();259 let item = o.get(key.clone())?.expect("field exists");260 match &item {261 Val::Arr(a) if !a.is_empty() => {262 buf.push('\n');263 buf.push_str(cur_padding);264 buf.push_str(options.arr_element_padding);265 cur_padding.push_str(options.arr_element_padding);266 }267 Val::Obj(o) if !o.is_empty() => {268 buf.push('\n');269 buf.push_str(cur_padding);270 buf.push_str(options.padding);271 cur_padding.push_str(options.padding);272 }273 _ => buf.push(' '),274 }275 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;276 cur_padding.truncate(prev_len);277 }278 }279 }280 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),281 }282 Ok(())283}1use crate::error::Error::*;2use crate::error::Result;3use crate::push_description_frame;4use crate::{throw, Val};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}2324pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {25 let mut out = String::new();26 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;27 Ok(out)28}29fn manifest_json_ex_buf(30 val: &Val,31 buf: &mut String,32 cur_padding: &mut String,33 options: &ManifestJsonOptions<'_>,34) -> Result<()> {35 use std::fmt::Write;36 let mtype = options.mtype;37 match val {38 Val::Bool(v) => {39 if *v {40 buf.push_str("true");41 } else {42 buf.push_str("false");43 }44 }45 Val::Null => buf.push_str("null"),46 Val::Str(s) => escape_string_json_buf(s, buf),47 Val::Num(n) => write!(buf, "{}", n).unwrap(),48 Val::Arr(items) => {49 buf.push('[');50 if !items.is_empty() {51 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {52 buf.push('\n');53 }5455 let old_len = cur_padding.len();56 cur_padding.push_str(options.padding);57 for (i, item) in items.iter().enumerate() {58 if i != 0 {59 buf.push(',');60 if mtype == ManifestType::ToString {61 buf.push(' ');62 } else if mtype != ManifestType::Minify {63 buf.push('\n');64 }65 }66 buf.push_str(cur_padding);67 manifest_json_ex_buf(&item?, buf, cur_padding, options)?;68 }69 cur_padding.truncate(old_len);7071 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {72 buf.push('\n');73 buf.push_str(cur_padding);74 }75 } else if mtype == ManifestType::Std {76 buf.push_str("\n\n");77 buf.push_str(cur_padding);78 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {79 buf.push(' ');80 }81 buf.push(']');82 }83 Val::Obj(obj) => {84 obj.run_assertions()?;85 buf.push('{');86 let fields = obj.fields();87 if !fields.is_empty() {88 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {89 buf.push('\n');90 }9192 let old_len = cur_padding.len();93 cur_padding.push_str(options.padding);94 for (i, field) in fields.into_iter().enumerate() {95 if i != 0 {96 buf.push(',');97 if mtype == ManifestType::ToString {98 buf.push(' ');99 } else if mtype != ManifestType::Minify {100 buf.push('\n');101 }102 }103 buf.push_str(cur_padding);104 escape_string_json_buf(&field, buf);105 buf.push_str(": ");106 push_description_frame(107 || format!("field <{}> manifestification", field.clone()),108 || {109 let value = obj.get(field.clone())?.unwrap();110 manifest_json_ex_buf(&value, buf, cur_padding, options)?;111 Ok(Val::Null)112 },113 )?;114 }115 cur_padding.truncate(old_len);116117 if mtype != ManifestType::ToString && mtype != ManifestType::Minify {118 buf.push('\n');119 buf.push_str(cur_padding);120 }121 } else if mtype == ManifestType::Std {122 buf.push_str("\n\n");123 buf.push_str(cur_padding);124 } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {125 buf.push(' ');126 }127 buf.push('}');128 }129 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),130 };131 Ok(())132}133134pub fn escape_string_json(s: &str) -> String {135 let mut buf = String::new();136 escape_string_json_buf(s, &mut buf);137 buf138}139140fn escape_string_json_buf(s: &str, buf: &mut String) {141 use std::fmt::Write;142 buf.push('"');143 for c in s.chars() {144 match c {145 '"' => buf.push_str("\\\""),146 '\\' => buf.push_str("\\\\"),147 '\u{0008}' => buf.push_str("\\b"),148 '\u{000c}' => buf.push_str("\\f"),149 '\n' => buf.push_str("\\n"),150 '\r' => buf.push_str("\\r"),151 '\t' => buf.push_str("\\t"),152 c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {153 write!(buf, "\\u{:04x}", c as u32).unwrap()154 }155 c => buf.push(c),156 }157 }158 buf.push('"');159}160161pub struct ManifestYamlOptions<'s> {162 /// Padding before fields, i.e163 /// ```yaml164 /// a:165 /// b:166 /// ## <- this167 /// ```168 pub padding: &'s str,169 /// Padding before array elements in objects170 /// ```yaml171 /// a:172 /// - 1173 /// ## <- this174 /// ```175 pub arr_element_padding: &'s str,176 /// Should yaml keys appear unescaped, when possible177 /// ```yaml178 /// "safe_key": 1179 /// # vs180 /// safe_key: 1181 /// ```182 pub quote_keys: bool,183}184185/// From https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289186/// With added date check187fn yaml_needs_quotes(string: &str) -> bool {188 fn need_quotes_spaces(string: &str) -> bool {189 string.starts_with(' ') || string.ends_with(' ')190 }191192 string == ""193 || need_quotes_spaces(string)194 || string.starts_with(|character: char| match character {195 '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@' => true,196 _ => false,197 }) || string.contains(|character: char| match character {198 ':'199 | '{'200 | '}'201 | '['202 | ']'203 | ','204 | '#'205 | '`'206 | '\"'207 | '\''208 | '\\'209 | '\0'..='\x06'210 | '\t'211 | '\n'212 | '\r'213 | '\x0e'..='\x1a'214 | '\x1c'..='\x1f' => true,215 _ => false,216 }) || [217 // http://yaml.org/type/bool.html218 // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse219 // them as string, not booleans, although it is violating the YAML 1.1 specification.220 // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.221 "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",222 "on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html223 "null", "Null", "NULL", "~",224 ]225 .contains(&string)226 || (string.chars().all(|c| matches!(c, '0'..='9' | '-'))227 && string.chars().filter(|c| *c == '-').count() == 2)228 || string.starts_with('.')229 || string.starts_with("0x")230 || string.parse::<i64>().is_ok()231 || string.parse::<f64>().is_ok()232}233234pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {235 let mut out = String::new();236 manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;237 Ok(out)238}239fn manifest_yaml_ex_buf(240 val: &Val,241 buf: &mut String,242 cur_padding: &mut String,243 options: &ManifestYamlOptions<'_>,244) -> Result<()> {245 use std::fmt::Write;246 match val {247 Val::Bool(v) => {248 if *v {249 buf.push_str("true")250 } else {251 buf.push_str("false")252 }253 }254 Val::Null => buf.push_str("null"),255 Val::Str(s) => {256 if s.is_empty() {257 buf.push_str("\"\"");258 } else if let Some(s) = s.strip_suffix('\n') {259 buf.push('|');260 for line in s.split('\n') {261 buf.push('\n');262 buf.push_str(options.padding);263 buf.push_str(line);264 }265 } else if !options.quote_keys && !yaml_needs_quotes(&s) {266 buf.push_str(&s);267 } else {268 escape_string_json_buf(s, buf);269 }270 }271 Val::Num(n) => write!(buf, "{}", *n).unwrap(),272 Val::Arr(a) => {273 if a.is_empty() {274 buf.push_str("[]");275 } else {276 for (i, item) in a.iter().enumerate() {277 if i != 0 {278 buf.push('\n');279 buf.push_str(cur_padding);280 }281 let item = item?;282 buf.push('-');283 match &item {284 Val::Arr(a) if !a.is_empty() => {285 buf.push('\n');286 buf.push_str(cur_padding);287 buf.push_str(options.padding);288 }289 _ => buf.push(' '),290 }291 let extra_padding = match &item {292 Val::Arr(a) => !a.is_empty(),293 Val::Obj(o) => !o.is_empty(),294 _ => false,295 };296 let prev_len = cur_padding.len();297 if extra_padding {298 cur_padding.push_str(options.padding);299 }300 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;301 cur_padding.truncate(prev_len);302 }303 }304 }305 Val::Obj(o) => {306 if o.is_empty() {307 buf.push_str("{}");308 } else {309 for (i, key) in o.fields().iter().enumerate() {310 if i != 0 {311 buf.push('\n');312 buf.push_str(cur_padding);313 }314 if !options.quote_keys && !yaml_needs_quotes(&key) {315 buf.push_str(&key);316 } else {317 escape_string_json_buf(key, buf);318 }319 buf.push(':');320 let prev_len = cur_padding.len();321 let item = o.get(key.clone())?.expect("field exists");322 match &item {323 Val::Arr(a) if !a.is_empty() => {324 buf.push('\n');325 buf.push_str(cur_padding);326 buf.push_str(options.arr_element_padding);327 cur_padding.push_str(options.arr_element_padding);328 }329 Val::Obj(o) if !o.is_empty() => {330 buf.push('\n');331 buf.push_str(cur_padding);332 buf.push_str(options.padding);333 cur_padding.push_str(options.padding);334 }335 _ => buf.push(' '),336 }337 manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;338 cur_padding.truncate(prev_len);339 }340 }341 }342 Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),343 }344 Ok(())345}crates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth--- 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<Val> {
- 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()))
})
}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- 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())
crates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth--- 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