Properly escape keys when printing TOML

Closes #53
This commit is contained in:
Alex Crichton 2015-02-02 22:54:46 -08:00
parent 68e6c34b90
commit b3e9653b7f
2 changed files with 62 additions and 25 deletions

View file

@ -8,25 +8,12 @@ struct Printer<'a, 'b:'a> {
stack: Vec<&'a str>, stack: Vec<&'a str>,
} }
struct Key<'a>(&'a [&'a str]);
impl fmt::Display for Value { impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
String(ref s) => { String(ref s) => write_str(f, s),
try!(write!(f, "\""));
for ch in s.chars() {
match ch {
'\u{8}' => try!(write!(f, "\\b")),
'\u{9}' => try!(write!(f, "\\t")),
'\u{a}' => try!(write!(f, "\\n")),
'\u{c}' => try!(write!(f, "\\f")),
'\u{d}' => try!(write!(f, "\\r")),
'\u{22}' => try!(write!(f, "\\\"")),
'\u{5c}' => try!(write!(f, "\\\\")),
ch => try!(write!(f, "{}", ch)),
}
}
write!(f, "\"")
}
Integer(i) => write!(f, "{}", i), Integer(i) => write!(f, "{}", i),
Float(fp) => { Float(fp) => {
try!(write!(f, "{}", fp)); try!(write!(f, "{}", fp));
@ -51,6 +38,23 @@ impl fmt::Display for Value {
} }
} }
fn write_str(f: &mut fmt::Formatter, s: &str) -> fmt::Result {
try!(write!(f, "\""));
for ch in s.chars() {
match ch {
'\u{8}' => try!(write!(f, "\\b")),
'\u{9}' => try!(write!(f, "\\t")),
'\u{a}' => try!(write!(f, "\\n")),
'\u{c}' => try!(write!(f, "\\f")),
'\u{d}' => try!(write!(f, "\\r")),
'\u{22}' => try!(write!(f, "\\\"")),
'\u{5c}' => try!(write!(f, "\\\\")),
ch => try!(write!(f, "{}", ch)),
}
}
write!(f, "\"")
}
impl<'a, 'b> Printer<'a, 'b> { impl<'a, 'b> Printer<'a, 'b> {
fn print(&mut self, table: &'a TomlTable) -> fmt::Result { fn print(&mut self, table: &'a TomlTable) -> fmt::Result {
for (k, v) in table.iter() { for (k, v) in table.iter() {
@ -64,14 +68,13 @@ impl<'a, 'b> Printer<'a, 'b> {
} }
_ => {} _ => {}
} }
try!(writeln!(self.output, "{} = {}", k, v)); try!(writeln!(self.output, "{} = {}", Key(&[k]), v));
} }
for (k, v) in table.iter() { for (k, v) in table.iter() {
match *v { match *v {
Table(ref inner) => { Table(ref inner) => {
self.stack.push(&**k); self.stack.push(k);
try!(writeln!(self.output, "\n[{}]", try!(writeln!(self.output, "\n[{}]", Key(&self.stack)));
self.stack.connect(".")));
try!(self.print(inner)); try!(self.print(inner));
self.stack.pop(); self.stack.pop();
} }
@ -80,10 +83,9 @@ impl<'a, 'b> Printer<'a, 'b> {
Some(&Table(..)) => {} Some(&Table(..)) => {}
_ => continue _ => continue
} }
self.stack.push(&**k); self.stack.push(k);
for inner in inner.iter() { for inner in inner.iter() {
try!(writeln!(self.output, "\n[[{}]]", try!(writeln!(self.output, "\n[[{}]]", Key(&self.stack)));
self.stack.connect(".")));
match *inner { match *inner {
Table(ref inner) => try!(self.print(inner)), Table(ref inner) => try!(self.print(inner)),
_ => panic!("non-heterogeneous toml array"), _ => panic!("non-heterogeneous toml array"),
@ -98,6 +100,29 @@ impl<'a, 'b> Printer<'a, 'b> {
} }
} }
impl<'a> fmt::Display for Key<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, part) in self.0.iter().enumerate() {
if i != 0 { try!(write!(f, ".")); }
let ok = part.chars().all(|c| {
match c {
'a' ... 'z' |
'A' ... 'Z' |
'0' ... '9' |
'-' | '_' => true,
_ => false,
}
});
if ok {
try!(write!(f, "{}", part));
} else {
try!(write_str(f, part));
}
}
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
#[allow(warnings)] #[allow(warnings)]
mod tests { mod tests {
@ -167,5 +192,11 @@ mod tests {
\n\ \n\
[[test2]]\n\ [[test2]]\n\
test = \"wut\"\n"); test = \"wut\"\n");
assert_eq!(Table(map! {
"foo.bar" => Integer(2),
"foo\"bar" => Integer(2)
}).to_string().as_slice(),
"\"foo\\\"bar\" = 2\n\
\"foo.bar\" = 2\n");
} }
} }

View file

@ -46,14 +46,20 @@ fn run(toml: &str, json: &str) {
(e.desc.clone(), toml.slice(e.lo - 5, e.hi + 5)) (e.desc.clone(), toml.slice(e.lo - 5, e.hi + 5))
}).collect::<Vec<(String, &str)>>()); }).collect::<Vec<(String, &str)>>());
assert!(table.is_some()); assert!(table.is_some());
let table = table.unwrap(); let toml = Table(table.unwrap());
let toml_string = format!("{}", toml);
let json = Json::from_str(json).unwrap(); let json = Json::from_str(json).unwrap();
let toml_json = to_json(Table(table)); let toml_json = to_json(toml.clone());
assert!(json == toml_json, assert!(json == toml_json,
"expected\n{}\ngot\n{}\n", "expected\n{}\ngot\n{}\n",
json.pretty(), json.pretty(),
toml_json.pretty()); toml_json.pretty());
let table2 = Parser::new(&toml_string).parse().unwrap();
// floats are a little lossy
if table2.values().any(|v| v.as_float().is_some()) { return }
assert_eq!(toml, Table(table2));
} }
macro_rules! test( ($name:ident, $toml:expr, $json:expr) => ( macro_rules! test( ($name:ident, $toml:expr, $json:expr) => (