From ef59ea712aea59e0d1cb36d597b8cd5789a12d56 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 1 Nov 2016 22:03:35 -0400 Subject: [PATCH 1/2] Added an option to avoid emitting quotes. --- saphyr/src/emitter.rs | 101 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) diff --git a/saphyr/src/emitter.rs b/saphyr/src/emitter.rs index 0631ec2..2bc6cd4 100644 --- a/saphyr/src/emitter.rs +++ b/saphyr/src/emitter.rs @@ -17,6 +17,7 @@ impl From for EmitError { pub struct YamlEmitter<'a> { writer: &'a mut fmt::Write, best_indent: usize, + avoid_quotes: bool, level: isize, } @@ -86,11 +87,39 @@ fn escape_str(wr: &mut fmt::Write, v: &str) -> Result<(), fmt::Error> { Ok(()) } +pub struct YamlEmitterBuilder { + avoid_quotes: bool, +} + +impl YamlEmitterBuilder { + pub fn new() -> Self { + YamlEmitterBuilder { + avoid_quotes: false, + } + } + + pub fn avoid_quotes(mut self) -> Self { + self.avoid_quotes = true; + self + } + + pub fn build<'a>(self, writer: &'a mut fmt::Write) -> YamlEmitter<'a> { + YamlEmitter { + writer: writer, + best_indent: 2, + avoid_quotes: self.avoid_quotes, + + level: -1, + } + } +} + impl<'a> YamlEmitter<'a> { pub fn new(writer: &'a mut fmt::Write) -> YamlEmitter { YamlEmitter { writer: writer, best_indent: 2, + avoid_quotes: false, level: -1 } @@ -207,7 +236,12 @@ impl<'a> YamlEmitter<'a> { } }, Yaml::String(ref v) => { - try!(escape_str(self.writer, v)); + if !self.avoid_quotes || need_quotes(v) { + try!(escape_str(self.writer, v)); + } + else { + try!(write!(self.writer, "{}", v)); + } Ok(()) }, Yaml::Boolean(v) => { @@ -236,6 +270,30 @@ impl<'a> YamlEmitter<'a> { } } +/// Check if the string requires quoting. +/// Strings containing any of the following characters must be quoted. +/// :, {, }, [, ], ,, &, *, #, ?, |, -, <, >, =, !, %, @, ` +/// +/// If the string contains any of the following control characters, it must be escaped with double quotes: +/// \0, \x01, \x02, \x03, \x04, \x05, \x06, \a, \b, \t, \n, \v, \f, \r, \x0e, \x0f, \x10, \x11, \x12, \x13, \x14, \x15, \x16, \x17, \x18, \x19, \x1a, \e, \x1c, \x1d, \x1e, \x1f, \N, \_, \L, \P +/// +/// Finally, there are other cases when the strings must be quoted, no matter if you're using single or double quotes: +/// * When the string is true or false (otherwise, it would be treated as a boolean value); +/// * When the string is null or ~ (otherwise, it would be considered as a null value); +/// * When the string looks like a number, such as integers (e.g. 2, 14, etc.), floats (e.g. 2.6, 14.9) and exponential numbers (e.g. 12e7, etc.) (otherwise, it would be treated as a numeric value); +/// * When the string looks like a date (e.g. 2014-12-31) (otherwise it would be automatically converted into a Unix timestamp). +fn need_quotes(string: &str) -> bool { + string.contains(|character: char| { + match character { + ':' | '{' | '}' | '[' | ']' | ',' | '&' | '*' | '#' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@' | '`' | '\\' | '\0' ... '\x06' | '\t' | '\n' | '\r' | '\x0e' ... '\x1a' | '\x1c' ... '\x1f' => true, + _ => false, + } + }) || + string == "true" || string == "false" || string == "null" || string == "~" || + string.parse::().is_ok() || + string.parse::().is_ok() +} + #[cfg(test)] mod tests { use super::*; @@ -309,4 +367,45 @@ products: let doc_new = &docs_new[0]; assert_eq!(doc, doc_new); } + + #[test] + fn test_emit_avoid_quotes() { + let s = r#"--- +a7: 你好 +boolean: "true" +boolean2: "false" +date: "2014-12-31" +exp: "12e7" +field: ":" +field2: "{" +field3: "\\" +field4: "\n" +float: "2.6" +int: "4" +nullable: "null" +nullable2: "~" +products: + "*coffee": + amount: 4 + "*cookies": + amount: 4 + "2.4": real key + "[1,2,3,4]": array key + "true": bool key + "{}": empty hash key +x: test +y: string with spaces"#; + + let docs = YamlLoader::load_from_str(&s).unwrap(); + let doc = &docs[0]; + let mut writer = String::new(); + { + let mut emitter = YamlEmitterBuilder::new() + .avoid_quotes() + .build(&mut writer); + emitter.dump(doc).unwrap(); + } + + assert_eq!(s, writer); + } } From e1e4ed9c05201abdfdcdc93551360d0108a9f7f4 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Wed, 2 Nov 2016 16:56:46 -0400 Subject: [PATCH 2/2] Fixed to always avoid quoting when possible. --- saphyr/src/emitter.rs | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/saphyr/src/emitter.rs b/saphyr/src/emitter.rs index 2bc6cd4..390bf53 100644 --- a/saphyr/src/emitter.rs +++ b/saphyr/src/emitter.rs @@ -17,7 +17,6 @@ impl From for EmitError { pub struct YamlEmitter<'a> { writer: &'a mut fmt::Write, best_indent: usize, - avoid_quotes: bool, level: isize, } @@ -87,39 +86,11 @@ fn escape_str(wr: &mut fmt::Write, v: &str) -> Result<(), fmt::Error> { Ok(()) } -pub struct YamlEmitterBuilder { - avoid_quotes: bool, -} - -impl YamlEmitterBuilder { - pub fn new() -> Self { - YamlEmitterBuilder { - avoid_quotes: false, - } - } - - pub fn avoid_quotes(mut self) -> Self { - self.avoid_quotes = true; - self - } - - pub fn build<'a>(self, writer: &'a mut fmt::Write) -> YamlEmitter<'a> { - YamlEmitter { - writer: writer, - best_indent: 2, - avoid_quotes: self.avoid_quotes, - - level: -1, - } - } -} - impl<'a> YamlEmitter<'a> { pub fn new(writer: &'a mut fmt::Write) -> YamlEmitter { YamlEmitter { writer: writer, best_indent: 2, - avoid_quotes: false, level: -1 } @@ -236,7 +207,7 @@ impl<'a> YamlEmitter<'a> { } }, Yaml::String(ref v) => { - if !self.avoid_quotes || need_quotes(v) { + if need_quotes(v) { try!(escape_str(self.writer, v)); } else { @@ -400,9 +371,7 @@ y: string with spaces"#; let doc = &docs[0]; let mut writer = String::new(); { - let mut emitter = YamlEmitterBuilder::new() - .avoid_quotes() - .build(&mut writer); + let mut emitter = YamlEmitter::new(&mut writer); emitter.dump(doc).unwrap(); }