Added an option to avoid emitting quotes.

This commit is contained in:
Antoni Boucher 2016-11-01 22:03:35 -04:00
parent 3862e488e8
commit ef59ea712a

View file

@ -17,6 +17,7 @@ impl From<fmt::Error> for EmitError {
pub struct YamlEmitter<'a> { pub struct YamlEmitter<'a> {
writer: &'a mut fmt::Write, writer: &'a mut fmt::Write,
best_indent: usize, best_indent: usize,
avoid_quotes: bool,
level: isize, level: isize,
} }
@ -86,11 +87,39 @@ fn escape_str(wr: &mut fmt::Write, v: &str) -> Result<(), fmt::Error> {
Ok(()) 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> { impl<'a> YamlEmitter<'a> {
pub fn new(writer: &'a mut fmt::Write) -> YamlEmitter { pub fn new(writer: &'a mut fmt::Write) -> YamlEmitter {
YamlEmitter { YamlEmitter {
writer: writer, writer: writer,
best_indent: 2, best_indent: 2,
avoid_quotes: false,
level: -1 level: -1
} }
@ -207,7 +236,12 @@ impl<'a> YamlEmitter<'a> {
} }
}, },
Yaml::String(ref v) => { Yaml::String(ref v) => {
if !self.avoid_quotes || need_quotes(v) {
try!(escape_str(self.writer, v)); try!(escape_str(self.writer, v));
}
else {
try!(write!(self.writer, "{}", v));
}
Ok(()) Ok(())
}, },
Yaml::Boolean(v) => { 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::<i64>().is_ok() ||
string.parse::<f64>().is_ok()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -309,4 +367,45 @@ products:
let doc_new = &docs_new[0]; let doc_new = &docs_new[0];
assert_eq!(doc, doc_new); 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);
}
} }