Skip to content

Commit b118a83

Browse files
committed
feat: quote_keys option for Yaml
Fixes: #68 Signed-off-by: Yaroslav Bolyukin <iam@lach.pw>
1 parent df9bc99 commit b118a83

5 files changed

Lines changed: 102 additions & 4 deletions

File tree

Cargo.lock

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/jrsonnet-evaluator/src/builtin/manifest.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,62 @@ pub struct ManifestYamlOptions<'s> {
173173
/// ## <- this
174174
/// ```
175175
pub arr_element_padding: &'s str,
176+
/// Should yaml keys appear unescaped, when possible
177+
/// ```yaml
178+
/// "safe_key": 1
179+
/// # vs
180+
/// safe_key: 1
181+
/// ```
182+
pub quote_keys: bool,
183+
}
184+
185+
/// From https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289
186+
/// With added date check
187+
fn yaml_needs_quotes(string: &str) -> bool {
188+
fn need_quotes_spaces(string: &str) -> bool {
189+
string.starts_with(' ') || string.ends_with(' ')
190+
}
191+
192+
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.html
218+
// Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse
219+
// 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.html
223+
"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()
176232
}
177233

178234
pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
@@ -206,8 +262,10 @@ fn manifest_yaml_ex_buf(
206262
buf.push_str(options.padding);
207263
buf.push_str(line);
208264
}
265+
} else if !options.quote_keys && !yaml_needs_quotes(&s) {
266+
buf.push_str(&s);
209267
} else {
210-
escape_string_json_buf(s, buf)
268+
escape_string_json_buf(s, buf);
211269
}
212270
}
213271
Val::Num(n) => write!(buf, "{}", *n).unwrap(),
@@ -253,7 +311,11 @@ fn manifest_yaml_ex_buf(
253311
buf.push('\n');
254312
buf.push_str(cur_padding);
255313
}
256-
escape_string_json_buf(key, buf);
314+
if !options.quote_keys && !yaml_needs_quotes(&key) {
315+
buf.push_str(&key);
316+
} else {
317+
escape_string_json_buf(key, buf);
318+
}
257319
buf.push(':');
258320
let prev_len = cur_padding.len();
259321
let item = o.get(key.clone())?.expect("field exists");

crates/jrsonnet-evaluator/src/builtin/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,13 +753,15 @@ fn builtin_manifest_yaml_doc(
753753
_loc: &ExprLocation,
754754
args: &ArgsDesc,
755755
) -> Result<Val> {
756-
parse_args!(context, "manifestYamlDoc", args, 2, [
756+
parse_args!(context, "manifestYamlDoc", args, 3, [
757757
0, value: ty!(any);
758758
1, indent_array_in_object: ty!(boolean) => Val::Bool;
759+
2, quote_keys: ty!(boolean) => Val::Bool;
759760
], {
760761
Ok(Val::Str(manifest_yaml_ex(&value, &ManifestYamlOptions {
761762
padding: " ",
762763
arr_element_padding: if indent_array_in_object { " " } else { "" },
764+
quote_keys,
763765
})?.into()))
764766
})
765767
}

crates/jrsonnet-evaluator/src/val.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ impl Val {
556556
&ManifestYamlOptions {
557557
padding,
558558
arr_element_padding: padding,
559+
quote_keys: false,
559560
},
560561
)
561562
.map(|s| s.into())

crates/jrsonnet-stdlib/src/std.jsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@
377377

378378
manifestYamlDocImpl:: $intrinsic(manifestYamlDocImpl),
379379

380-
manifestYamlDoc(value, indent_array_in_object=false):: std.manifestYamlDocImpl(value, indent_array_in_object),
380+
manifestYamlDoc(value, indent_array_in_object=false, quote_keys=true):: std.manifestYamlDocImpl(value, indent_array_in_object, quote_keys),
381381

382382
manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::
383383
if !std.isArray(value) then

0 commit comments

Comments
 (0)