Merge pull request #64 from chaaz/chyh1990
Arbitrarily-nested hashes/arrays emitted as hash keys
This commit is contained in:
commit
20a28bc16b
3 changed files with 188 additions and 91 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.3.6"
|
version = "0.3.7"
|
||||||
authors = ["Yuheng Chen <yuhengchen@sensetime.com>"]
|
authors = ["Yuheng Chen <yuhengchen@sensetime.com>"]
|
||||||
homepage = "http://chyh1990.github.io/yaml-rust/"
|
homepage = "http://chyh1990.github.io/yaml-rust/"
|
||||||
documentation = "http://chyh1990.github.io/yaml-rust/doc/yaml_rust/"
|
documentation = "http://chyh1990.github.io/yaml-rust/doc/yaml_rust/"
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::convert::From;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use yaml::{Hash, Yaml};
|
use yaml::{Hash, Yaml};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum EmitError {
|
pub enum EmitError {
|
||||||
FmtError(fmt::Error),
|
FmtError(fmt::Error),
|
||||||
|
@ -36,6 +37,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,
|
||||||
|
compact: bool,
|
||||||
|
|
||||||
level: isize,
|
level: isize,
|
||||||
}
|
}
|
||||||
|
@ -110,11 +112,29 @@ impl<'a> YamlEmitter<'a> {
|
||||||
YamlEmitter {
|
YamlEmitter {
|
||||||
writer: writer,
|
writer: writer,
|
||||||
best_indent: 2,
|
best_indent: 2,
|
||||||
|
compact: true,
|
||||||
|
|
||||||
level: -1
|
level: -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set 'compact inline notation' on or off, as described for block
|
||||||
|
/// [sequences](http://www.yaml.org/spec/1.2/spec.html#id2797382)
|
||||||
|
/// and
|
||||||
|
/// [mappings](http://www.yaml.org/spec/1.2/spec.html#id2798057).
|
||||||
|
///
|
||||||
|
/// In this form, blocks cannot have any properties (such as anchors
|
||||||
|
/// or tags), which should be OK, because this emitter doesn't
|
||||||
|
/// (currently) emit those anyways.
|
||||||
|
pub fn compact(&mut self, compact: bool) {
|
||||||
|
self.compact = compact;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine if this emitter is using 'compact inline notation'.
|
||||||
|
pub fn is_compact(&self) -> bool {
|
||||||
|
self.compact
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dump(&mut self, doc: &Yaml) -> EmitResult {
|
pub fn dump(&mut self, doc: &Yaml) -> EmitResult {
|
||||||
// write DocumentStart
|
// write DocumentStart
|
||||||
try!(write!(self.writer, "---\n"));
|
try!(write!(self.writer, "---\n"));
|
||||||
|
@ -132,22 +152,15 @@ impl<'a> YamlEmitter<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_node_compact(&mut self, node: &Yaml) -> EmitResult {
|
|
||||||
match *node {
|
|
||||||
Yaml::Array(ref v) => self.emit_array_compact(v),
|
|
||||||
Yaml::Hash(ref h) => self.emit_hash_compact(h),
|
|
||||||
_ => self.emit_node(node),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_node(&mut self, node: &Yaml) -> EmitResult {
|
fn emit_node(&mut self, node: &Yaml) -> EmitResult {
|
||||||
match *node {
|
match *node {
|
||||||
Yaml::Array(ref v) => self.emit_array(v, !node.is_array()),
|
Yaml::Array(ref v) => self.emit_array(v),
|
||||||
Yaml::Hash(ref h) => self.emit_hash(h),
|
Yaml::Hash(ref h) => self.emit_hash(h),
|
||||||
Yaml::String(ref v) => {
|
Yaml::String(ref v) => {
|
||||||
if need_quotes(v) {
|
if need_quotes(v) {
|
||||||
try!(escape_str(self.writer, v));
|
try!(escape_str(self.writer, v));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
try!(write!(self.writer, "{}", v));
|
try!(write!(self.writer, "{}", v));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -177,40 +190,21 @@ impl<'a> YamlEmitter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_array(&mut self, v: &[Yaml], indent_first: bool) -> EmitResult {
|
fn emit_array(&mut self, v: &[Yaml]) -> EmitResult {
|
||||||
if v.is_empty() {
|
if v.is_empty() {
|
||||||
try!(write!(self.writer, "[]"));
|
try!(write!(self.writer, "[]"));
|
||||||
} else {
|
} else {
|
||||||
for (cnt, x) in v.iter().enumerate() {
|
|
||||||
self.level += 1;
|
self.level += 1;
|
||||||
|
for (cnt, x) in v.iter().enumerate() {
|
||||||
if cnt > 0 {
|
if cnt > 0 {
|
||||||
try!(write!(self.writer, "\n"));
|
try!(write!(self.writer, "\n"));
|
||||||
}
|
|
||||||
if cnt > 0 || indent_first {
|
|
||||||
try!(self.write_indent());
|
try!(self.write_indent());
|
||||||
}
|
}
|
||||||
try!(write!(self.writer, "- "));
|
try!(write!(self.writer, "-"));
|
||||||
if self.level > 2 {
|
try!(self.emit_val(true, x));
|
||||||
try!(self.emit_node_compact(x));
|
|
||||||
} else {
|
|
||||||
try!(self.emit_node(x));
|
|
||||||
}
|
}
|
||||||
self.level -= 1;
|
self.level -= 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_array_compact(&mut self, v: &[Yaml]) -> EmitResult {
|
|
||||||
try!(write!(self.writer, "["));
|
|
||||||
if self.level >= 0 {
|
|
||||||
try!(write!(self.writer, ""));
|
|
||||||
}
|
|
||||||
for (cnt, x) in v.iter().enumerate() {
|
|
||||||
if cnt > 0 { try!(write!(self.writer, ", ")); }
|
|
||||||
try!(self.emit_node(x));
|
|
||||||
}
|
|
||||||
try!(write!(self.writer, "]"));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,71 +214,66 @@ impl<'a> YamlEmitter<'a> {
|
||||||
} else {
|
} else {
|
||||||
self.level += 1;
|
self.level += 1;
|
||||||
for (cnt, (k, v)) in h.iter().enumerate() {
|
for (cnt, (k, v)) in h.iter().enumerate() {
|
||||||
|
let complex_key = match *k {
|
||||||
|
Yaml::Hash(_) | Yaml::Array(_) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
if cnt > 0 {
|
if cnt > 0 {
|
||||||
try!(write!(self.writer, "\n"));
|
try!(write!(self.writer, "\n"));
|
||||||
try!(self.write_indent());
|
try!(self.write_indent());
|
||||||
}
|
}
|
||||||
match *k {
|
if complex_key {
|
||||||
Yaml::Array(_) | Yaml::Hash(_) => {
|
try!(write!(self.writer, "?"));
|
||||||
try!(self.emit_node_compact(k));
|
try!(self.emit_val(true, k));
|
||||||
}
|
try!(write!(self.writer, "\n"));
|
||||||
_ => {
|
try!(self.write_indent());
|
||||||
|
try!(write!(self.writer, ":"));
|
||||||
|
try!(self.emit_val(true, v));
|
||||||
|
} else {
|
||||||
try!(self.emit_node(k));
|
try!(self.emit_node(k));
|
||||||
|
try!(write!(self.writer, ":"));
|
||||||
|
try!(self.emit_val(false, v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match *v {
|
self.level -= 1;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit a yaml as a hash or array value: i.e., which should appear
|
||||||
|
/// following a ":" or "-", either after a space, or on a new line.
|
||||||
|
/// If `inline` is true, then the preceeding characters are distinct
|
||||||
|
/// and short enough to respect the compact flag.
|
||||||
|
fn emit_val(&mut self, inline: bool, val: &Yaml) -> EmitResult {
|
||||||
|
match *val {
|
||||||
Yaml::Array(ref v) => {
|
Yaml::Array(ref v) => {
|
||||||
if v.is_empty() {
|
if (inline && self.compact) || v.is_empty() {
|
||||||
try!(write!(self.writer, ": "));
|
try!(write!(self.writer, " "));
|
||||||
} else {
|
} else {
|
||||||
try!(write!(self.writer, ":\n"));
|
try!(write!(self.writer, "\n"));
|
||||||
}
|
|
||||||
try!(self.emit_array(v, true));
|
|
||||||
}
|
|
||||||
Yaml::Hash(ref h) => {
|
|
||||||
if h.is_empty() {
|
|
||||||
try!(write!(self.writer, ": "));
|
|
||||||
} else {
|
|
||||||
try!(write!(self.writer, ":\n"));
|
|
||||||
self.level += 1;
|
self.level += 1;
|
||||||
try!(self.write_indent());
|
try!(self.write_indent());
|
||||||
self.level -= 1;
|
self.level -= 1;
|
||||||
}
|
}
|
||||||
try!(self.emit_hash(h));
|
self.emit_array(v)
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
try!(write!(self.writer, ": "));
|
|
||||||
try!(self.emit_node(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.level -= 1;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_hash_compact(&mut self, h: &Hash) -> EmitResult {
|
|
||||||
try!(self.writer.write_str("{"));
|
|
||||||
self.level += 1;
|
|
||||||
for (cnt, (k, v)) in h.iter().enumerate() {
|
|
||||||
if cnt > 0 {
|
|
||||||
try!(write!(self.writer, ", "));
|
|
||||||
}
|
|
||||||
match *k {
|
|
||||||
// complex key is not supported
|
|
||||||
Yaml::Array(_) | Yaml::Hash(_) => {
|
|
||||||
return Err(EmitError::BadHashmapKey);
|
|
||||||
},
|
},
|
||||||
_ => { try!(self.emit_node(k)); }
|
Yaml::Hash(ref h) => {
|
||||||
}
|
if (inline && self.compact) || h.is_empty() {
|
||||||
try!(write!(self.writer, ": "));
|
try!(write!(self.writer, " "));
|
||||||
try!(self.emit_node(v));
|
} else {
|
||||||
}
|
try!(write!(self.writer, "\n"));
|
||||||
try!(self.writer.write_str("}"));
|
self.level += 1;
|
||||||
|
try!(self.write_indent());
|
||||||
self.level -= 1;
|
self.level -= 1;
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
self.emit_hash(h)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
try!(write!(self.writer, " "));
|
||||||
|
self.emit_node(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the string requires quoting.
|
/// Check if the string requires quoting.
|
||||||
|
@ -466,8 +455,30 @@ string2: "true"
|
||||||
string3: "false"
|
string3: "false"
|
||||||
string4: "~"
|
string4: "~"
|
||||||
null0: ~
|
null0: ~
|
||||||
[true, false]: real_bools
|
? - true
|
||||||
["True", "TRUE", "False", "FALSE", "y", "Y", "yes", "Yes", "YES", "n", "N", "no", "No", "NO", "on", "On", "ON", "off", "Off", "OFF"]: false_bools
|
- false
|
||||||
|
: real_bools
|
||||||
|
? - "True"
|
||||||
|
- "TRUE"
|
||||||
|
- "False"
|
||||||
|
- "FALSE"
|
||||||
|
- "y"
|
||||||
|
- "Y"
|
||||||
|
- "yes"
|
||||||
|
- "Yes"
|
||||||
|
- "YES"
|
||||||
|
- "n"
|
||||||
|
- "N"
|
||||||
|
- "no"
|
||||||
|
- "No"
|
||||||
|
- "NO"
|
||||||
|
- "on"
|
||||||
|
- "On"
|
||||||
|
- "ON"
|
||||||
|
- "off"
|
||||||
|
- "Off"
|
||||||
|
- "OFF"
|
||||||
|
: false_bools
|
||||||
bool0: true
|
bool0: true
|
||||||
bool1: false"#;
|
bool1: false"#;
|
||||||
|
|
||||||
|
@ -484,7 +495,16 @@ bool1: false"#;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_empty_and_nested() {
|
fn test_empty_and_nested() {
|
||||||
let s = r#"---
|
test_empty_and_nested_flag(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_and_nested_compact() {
|
||||||
|
test_empty_and_nested_flag(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_empty_and_nested_flag(compact: bool) {
|
||||||
|
let s = if compact { r#"---
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
c: hello
|
c: hello
|
||||||
|
@ -492,13 +512,23 @@ a:
|
||||||
e:
|
e:
|
||||||
- f
|
- f
|
||||||
- g
|
- g
|
||||||
- h: []"#;
|
- h: []"# } else { r#"---
|
||||||
|
a:
|
||||||
|
b:
|
||||||
|
c: hello
|
||||||
|
d: {}
|
||||||
|
e:
|
||||||
|
- f
|
||||||
|
- g
|
||||||
|
-
|
||||||
|
h: []"# };
|
||||||
|
|
||||||
let docs = YamlLoader::load_from_str(&s).unwrap();
|
let docs = YamlLoader::load_from_str(&s).unwrap();
|
||||||
let doc = &docs[0];
|
let doc = &docs[0];
|
||||||
let mut writer = String::new();
|
let mut writer = String::new();
|
||||||
{
|
{
|
||||||
let mut emitter = YamlEmitter::new(&mut writer);
|
let mut emitter = YamlEmitter::new(&mut writer);
|
||||||
|
emitter.compact(compact);
|
||||||
emitter.dump(doc).unwrap();
|
emitter.dump(doc).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,7 +566,8 @@ a:
|
||||||
- - c
|
- - c
|
||||||
- d
|
- d
|
||||||
- - e
|
- - e
|
||||||
- [f, e]"#;
|
- - f
|
||||||
|
- - e"#;
|
||||||
|
|
||||||
let docs = YamlLoader::load_from_str(&s).unwrap();
|
let docs = YamlLoader::load_from_str(&s).unwrap();
|
||||||
let doc = &docs[0];
|
let doc = &docs[0];
|
||||||
|
|
|
@ -73,3 +73,69 @@ include!("spec_test.rs.inc");
|
||||||
//#[test]
|
//#[test]
|
||||||
//fn test_hc_alias() {
|
//fn test_hc_alias() {
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mapvec_legal() {
|
||||||
|
use yaml_rust::yaml::{Array, Hash, Yaml};
|
||||||
|
use yaml_rust::{YamlLoader, YamlEmitter};
|
||||||
|
|
||||||
|
// Emitting a `map<map<seq<_>>, _>` should result in legal yaml that
|
||||||
|
// we can parse.
|
||||||
|
|
||||||
|
let mut key = Array::new();
|
||||||
|
key.push(Yaml::Integer(1));
|
||||||
|
key.push(Yaml::Integer(2));
|
||||||
|
key.push(Yaml::Integer(3));
|
||||||
|
|
||||||
|
let mut keyhash = Hash::new();
|
||||||
|
keyhash.insert(Yaml::String("key".into()), Yaml::Array(key));
|
||||||
|
|
||||||
|
let mut val = Array::new();
|
||||||
|
val.push(Yaml::Integer(4));
|
||||||
|
val.push(Yaml::Integer(5));
|
||||||
|
val.push(Yaml::Integer(6));
|
||||||
|
|
||||||
|
let mut hash = Hash::new();
|
||||||
|
hash.insert(Yaml::Hash(keyhash), Yaml::Array(val));
|
||||||
|
|
||||||
|
let mut out_str = String::new();
|
||||||
|
{
|
||||||
|
let mut emitter = YamlEmitter::new(&mut out_str);
|
||||||
|
emitter.dump(&Yaml::Hash(hash)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we are tempted to naively render like this:
|
||||||
|
//
|
||||||
|
// ```yaml
|
||||||
|
// ---
|
||||||
|
// {key:
|
||||||
|
// - 1
|
||||||
|
// - 2
|
||||||
|
// - 3}:
|
||||||
|
// - 4
|
||||||
|
// - 5
|
||||||
|
// - 6
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// However, this doesn't work, because the key sequence [1, 2, 3] is
|
||||||
|
// rendered in block mode, which is not legal (as far as I can tell)
|
||||||
|
// inside the flow mode of the key. We need to either fully render
|
||||||
|
// everything that's in a key in flow mode (which may make for some
|
||||||
|
// long lines), or use the explicit map identifier '?':
|
||||||
|
//
|
||||||
|
// ```yaml
|
||||||
|
// ---
|
||||||
|
// ?
|
||||||
|
// key:
|
||||||
|
// - 1
|
||||||
|
// - 2
|
||||||
|
// - 3
|
||||||
|
// :
|
||||||
|
// - 4
|
||||||
|
// - 5
|
||||||
|
// - 6
|
||||||
|
// ```
|
||||||
|
|
||||||
|
YamlLoader::load_from_str(&out_str).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue