1use linked_hash_map::LinkedHashMap;
2use crate::parser::*;
3use crate::scanner::{Marker, ScanError, TScalarStyle, TokenType};
4use std::collections::BTreeMap;
5use std::f64;
6use std::i64;
7use std::mem;
8use std::ops::Index;
9use std::string;
10use std::vec;
11
12/// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to
13/// access your YAML document.
14///
15/// # Examples
16///
17/// ```
18/// use yaml_rust::Yaml;
19/// let foo = Yaml::from_str("-123"); // convert the string to the appropriate YAML type
20/// assert_eq!(foo.as_i64().unwrap(), -123);
21///
22/// // iterate over an Array
23/// let vec = Yaml::Array(vec![Yaml::Integer(1), Yaml::Integer(2)]);
24/// for v in vec.as_vec().unwrap() {
25/// assert!(v.as_i64().is_some());
26/// }
27/// ```
28#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
29pub enum Yaml {
30 /// Float types are stored as String and parsed on demand.
31 /// Note that f64 does NOT implement Eq trait and can NOT be stored in BTreeMap.
32 Real(string::String),
33 /// YAML int is stored as i64.
34 Integer(i64),
35 /// YAML scalar.
36 String(string::String),
37 /// YAML bool, e.g. `true` or `false`.
38 Boolean(bool),
39 /// YAML array, can be accessed as a `Vec`.
40 Array(self::Array),
41 /// YAML hash, can be accessed as a `LinkedHashMap`.
42 ///
43 /// Insertion order will match the order of insertion into the map.
44 Hash(self::Hash),
45 /// Alias, not fully supported yet.
46 Alias(usize),
47 /// YAML null, e.g. `null` or `~`.
48 Null,
49 /// Accessing a nonexistent node via the Index trait returns `BadValue`. This
50 /// simplifies error handling in the calling code. Invalid type conversion also
51 /// returns `BadValue`.
52 BadValue,
53}
54
55pub type Array = Vec<Yaml>;
56pub type Hash = LinkedHashMap<Yaml, Yaml>;
57
58// parse f64 as Core schema
59// See: https://github.com/chyh1990/yaml-rust/issues/51
60fn parse_f64(v: &str) -> Option<f64> {
61 match v {
62 ".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => Some(f64::INFINITY),
63 "-.inf" | "-.Inf" | "-.INF" => Some(f64::NEG_INFINITY),
64 ".nan" | "NaN" | ".NAN" => Some(f64::NAN),
65 _ => v.parse::<f64>().ok(),
66 }
67}
68
69pub struct YamlLoader {
70 docs: Vec<Yaml>,
71 // states
72 // (current node, anchor_id) tuple
73 doc_stack: Vec<(Yaml, usize)>,
74 key_stack: Vec<Yaml>,
75 anchor_map: BTreeMap<usize, Yaml>,
76}
77
78impl MarkedEventReceiver for YamlLoader {
79 fn on_event(&mut self, ev: Event, _: Marker) {
80 // println!("EV {:?}", ev);
81 match ev {
82 Event::DocumentStart => {
83 // do nothing
84 }
85 Event::DocumentEnd => {
86 match self.doc_stack.len() {
87 // empty document
88 0 => self.docs.push(Yaml::BadValue),
89 1 => self.docs.push(self.doc_stack.pop().unwrap().0),
90 _ => unreachable!(),
91 }
92 }
93 Event::SequenceStart(aid) => {
94 self.doc_stack.push((Yaml::Array(Vec::new()), aid));
95 }
96 Event::SequenceEnd => {
97 let node = self.doc_stack.pop().unwrap();
98 self.insert_new_node(node);
99 }
100 Event::MappingStart(aid) => {
101 self.doc_stack.push((Yaml::Hash(Hash::new()), aid));
102 self.key_stack.push(Yaml::BadValue);
103 }
104 Event::MappingEnd => {
105 self.key_stack.pop().unwrap();
106 let node = self.doc_stack.pop().unwrap();
107 self.insert_new_node(node);
108 }
109 Event::Scalar(v, style, aid, tag) => {
110 let node = if style != TScalarStyle::Plain {
111 Yaml::String(v)
112 } else if let Some(TokenType::Tag(ref handle, ref suffix)) = tag {
113 // XXX tag:yaml.org,2002:
114 if handle == "!!" {
115 match suffix.as_ref() {
116 "bool" => {
117 // "true" or "false"
118 match v.parse::<bool>() {
119 Err(_) => Yaml::BadValue,
120 Ok(v) => Yaml::Boolean(v),
121 }
122 }
123 "int" => match v.parse::<i64>() {
124 Err(_) => Yaml::BadValue,
125 Ok(v) => Yaml::Integer(v),
126 },
127 "float" => match parse_f64(&v) {
128 Some(_) => Yaml::Real(v),
129 None => Yaml::BadValue,
130 },
131 "null" => match v.as_ref() {
132 "~" | "null" => Yaml::Null,
133 _ => Yaml::BadValue,
134 },
135 _ => Yaml::String(v),
136 }
137 } else {
138 Yaml::String(v)
139 }
140 } else {
141 // Datatype is not specified, or unrecognized
142 Yaml::from_str(&v)
143 };
144
145 self.insert_new_node((node, aid));
146 }
147 Event::Alias(id) => {
148 let n = match self.anchor_map.get(&id) {
149 Some(v) => v.clone(),
150 None => Yaml::BadValue,
151 };
152 self.insert_new_node((n, 0));
153 }
154 _ => { /* ignore */ }
155 }
156 // println!("DOC {:?}", self.doc_stack);
157 }
158}
159
160impl YamlLoader {
161 fn insert_new_node(&mut self, node: (Yaml, usize)) {
162 // valid anchor id starts from 1
163 if node.1 > 0 {
164 self.anchor_map.insert(node.1, node.0.clone());
165 }
166 if self.doc_stack.is_empty() {
167 self.doc_stack.push(node);
168 } else {
169 let parent = self.doc_stack.last_mut().unwrap();
170 match *parent {
171 (Yaml::Array(ref mut v), _) => v.push(node.0),
172 (Yaml::Hash(ref mut h), _) => {
173 let cur_key = self.key_stack.last_mut().unwrap();
174 // current node is a key
175 if cur_key.is_badvalue() {
176 *cur_key = node.0;
177 // current node is a value
178 } else {
179 let mut newkey = Yaml::BadValue;
180 mem::swap(&mut newkey, cur_key);
181 h.insert(newkey, node.0);
182 }
183 }
184 _ => unreachable!(),
185 }
186 }
187 }
188
189 pub fn load_from_str(source: &str) -> Result<Vec<Yaml>, ScanError> {
190 let mut loader = YamlLoader {
191 docs: Vec::new(),
192 doc_stack: Vec::new(),
193 key_stack: Vec::new(),
194 anchor_map: BTreeMap::new(),
195 };
196 let mut parser = Parser::new(source.chars());
197 parser.load(&mut loader, true)?;
198 Ok(loader.docs)
199 }
200}
201
202macro_rules! define_as (
203 ($name:ident, $t:ident, $yt:ident) => (
204pub fn $name(&self) -> Option<$t> {
205 match *self {
206 Yaml::$yt(v) => Some(v),
207 _ => None
208 }
209}
210 );
211);
212
213macro_rules! define_as_ref (
214 ($name:ident, $t:ty, $yt:ident) => (
215pub fn $name(&self) -> Option<$t> {
216 match *self {
217 Yaml::$yt(ref v) => Some(v),
218 _ => None
219 }
220}
221 );
222);
223
224macro_rules! define_into (
225 ($name:ident, $t:ty, $yt:ident) => (
226pub fn $name(self) -> Option<$t> {
227 match self {
228 Yaml::$yt(v) => Some(v),
229 _ => None
230 }
231}
232 );
233);
234
235impl Yaml {
236 define_as!(as_bool, bool, Boolean);
237 define_as!(as_i64, i64, Integer);
238
239 define_as_ref!(as_str, &str, String);
240 define_as_ref!(as_hash, &Hash, Hash);
241 define_as_ref!(as_vec, &Array, Array);
242
243 define_into!(into_bool, bool, Boolean);
244 define_into!(into_i64, i64, Integer);
245 define_into!(into_string, String, String);
246 define_into!(into_hash, Hash, Hash);
247 define_into!(into_vec, Array, Array);
248
249 pub fn is_null(&self) -> bool {
250 match *self {
251 Yaml::Null => true,
252 _ => false,
253 }
254 }
255
256 pub fn is_badvalue(&self) -> bool {
257 match *self {
258 Yaml::BadValue => true,
259 _ => false,
260 }
261 }
262
263 pub fn is_array(&self) -> bool {
264 match *self {
265 Yaml::Array(_) => true,
266 _ => false,
267 }
268 }
269
270 pub fn as_f64(&self) -> Option<f64> {
271 match *self {
272 Yaml::Real(ref v) => parse_f64(v),
273 _ => None,
274 }
275 }
276
277 pub fn into_f64(self) -> Option<f64> {
278 match self {
279 Yaml::Real(ref v) => parse_f64(v),
280 _ => None,
281 }
282 }
283}
284
285#[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))]
286impl Yaml {
287 // Not implementing FromStr because there is no possibility of Error.
288 // This function falls back to Yaml::String if nothing else matches.
289 pub fn from_str(v: &str) -> Yaml {
290 if v.starts_with("0x") {
291 if let Ok(i) = i64::from_str_radix(&v[2..], 16) {
292 return Yaml::Integer(i);
293 }
294 }
295 if v.starts_with("0o") {
296 if let Ok(i) = i64::from_str_radix(&v[2..], 8) {
297 return Yaml::Integer(i);
298 }
299 }
300 if v.starts_with('+') {
301 if let Ok(i) = v[1..].parse::<i64>() {
302 return Yaml::Integer(i);
303 }
304 }
305 match v {
306 "~" | "null" => Yaml::Null,
307 "true" => Yaml::Boolean(true),
308 "false" => Yaml::Boolean(false),
309 _ if v.parse::<i64>().is_ok() => Yaml::Integer(v.parse::<i64>().unwrap()),
310 // try parsing as f64
311 _ if parse_f64(v).is_some() => Yaml::Real(v.to_owned()),
312 _ => Yaml::String(v.to_owned()),
313 }
314 }
315}
316
317static BAD_VALUE: Yaml = Yaml::BadValue;
318impl<'a> Index<&'a str> for Yaml {
319 type Output = Yaml;
320
321 fn index(&self, idx: &'a str) -> &Yaml {
322 let key: Yaml = Yaml::String(idx.to_owned());
323 match self.as_hash() {
324 Some(h: &LinkedHashMap) => h.get(&key).unwrap_or(&BAD_VALUE),
325 None => &BAD_VALUE,
326 }
327 }
328}
329
330impl Index<usize> for Yaml {
331 type Output = Yaml;
332
333 fn index(&self, idx: usize) -> &Yaml {
334 if let Some(v: &Vec) = self.as_vec() {
335 v.get(idx).unwrap_or(&BAD_VALUE)
336 } else if let Some(v: &LinkedHashMap) = self.as_hash() {
337 let key: Yaml = Yaml::Integer(idx as i64);
338 v.get(&key).unwrap_or(&BAD_VALUE)
339 } else {
340 &BAD_VALUE
341 }
342 }
343}
344
345impl IntoIterator for Yaml {
346 type Item = Yaml;
347 type IntoIter = YamlIter;
348
349 fn into_iter(self) -> Self::IntoIter {
350 YamlIter {
351 yaml: self.into_vec().unwrap_or_else(Vec::new).into_iter(),
352 }
353 }
354}
355
356pub struct YamlIter {
357 yaml: vec::IntoIter<Yaml>,
358}
359
360impl Iterator for YamlIter {
361 type Item = Yaml;
362
363 fn next(&mut self) -> Option<Yaml> {
364 self.yaml.next()
365 }
366}
367
368#[cfg(test)]
369mod test {
370 use std::f64;
371 use crate::yaml::*;
372 #[test]
373 fn test_coerce() {
374 let s = "---
375a: 1
376b: 2.2
377c: [1, 2]
378";
379 let out = YamlLoader::load_from_str(&s).unwrap();
380 let doc = &out[0];
381 assert_eq!(doc["a"].as_i64().unwrap(), 1i64);
382 assert_eq!(doc["b"].as_f64().unwrap(), 2.2f64);
383 assert_eq!(doc["c"][1].as_i64().unwrap(), 2i64);
384 assert!(doc["d"][0].is_badvalue());
385 }
386
387 #[test]
388 fn test_empty_doc() {
389 let s: String = "".to_owned();
390 YamlLoader::load_from_str(&s).unwrap();
391 let s: String = "---".to_owned();
392 assert_eq!(YamlLoader::load_from_str(&s).unwrap()[0], Yaml::Null);
393 }
394
395 #[test]
396 fn test_parser() {
397 let s: String = "
398# comment
399a0 bb: val
400a1:
401 b1: 4
402 b2: d
403a2: 4 # i'm comment
404a3: [1, 2, 3]
405a4:
406 - - a1
407 - a2
408 - 2
409a5: 'single_quoted'
410a6: \"double_quoted\"
411a7: 你好
412"
413 .to_owned();
414 let out = YamlLoader::load_from_str(&s).unwrap();
415 let doc = &out[0];
416 assert_eq!(doc["a7"].as_str().unwrap(), "你好");
417 }
418
419 #[test]
420 fn test_multi_doc() {
421 let s = "
422'a scalar'
423---
424'a scalar'
425---
426'a scalar'
427";
428 let out = YamlLoader::load_from_str(&s).unwrap();
429 assert_eq!(out.len(), 3);
430 }
431
432 #[test]
433 fn test_anchor() {
434 let s = "
435a1: &DEFAULT
436 b1: 4
437 b2: d
438a2: *DEFAULT
439";
440 let out = YamlLoader::load_from_str(&s).unwrap();
441 let doc = &out[0];
442 assert_eq!(doc["a2"]["b1"].as_i64().unwrap(), 4);
443 }
444
445 #[test]
446 fn test_bad_anchor() {
447 let s = "
448a1: &DEFAULT
449 b1: 4
450 b2: *DEFAULT
451";
452 let out = YamlLoader::load_from_str(&s).unwrap();
453 let doc = &out[0];
454 assert_eq!(doc["a1"]["b2"], Yaml::BadValue);
455 }
456
457 #[test]
458 fn test_github_27() {
459 // https://github.com/chyh1990/yaml-rust/issues/27
460 let s = "&a";
461 let out = YamlLoader::load_from_str(&s).unwrap();
462 let doc = &out[0];
463 assert_eq!(doc.as_str().unwrap(), "");
464 }
465
466 #[test]
467 fn test_plain_datatype() {
468 let s = "
469- 'string'
470- \"string\"
471- string
472- 123
473- -321
474- 1.23
475- -1e4
476- ~
477- null
478- true
479- false
480- !!str 0
481- !!int 100
482- !!float 2
483- !!null ~
484- !!bool true
485- !!bool false
486- 0xFF
487# bad values
488- !!int string
489- !!float string
490- !!bool null
491- !!null val
492- 0o77
493- [ 0xF, 0xF ]
494- +12345
495- [ true, false ]
496";
497 let out = YamlLoader::load_from_str(&s).unwrap();
498 let doc = &out[0];
499
500 assert_eq!(doc[0].as_str().unwrap(), "string");
501 assert_eq!(doc[1].as_str().unwrap(), "string");
502 assert_eq!(doc[2].as_str().unwrap(), "string");
503 assert_eq!(doc[3].as_i64().unwrap(), 123);
504 assert_eq!(doc[4].as_i64().unwrap(), -321);
505 assert_eq!(doc[5].as_f64().unwrap(), 1.23);
506 assert_eq!(doc[6].as_f64().unwrap(), -1e4);
507 assert!(doc[7].is_null());
508 assert!(doc[8].is_null());
509 assert_eq!(doc[9].as_bool().unwrap(), true);
510 assert_eq!(doc[10].as_bool().unwrap(), false);
511 assert_eq!(doc[11].as_str().unwrap(), "0");
512 assert_eq!(doc[12].as_i64().unwrap(), 100);
513 assert_eq!(doc[13].as_f64().unwrap(), 2.0);
514 assert!(doc[14].is_null());
515 assert_eq!(doc[15].as_bool().unwrap(), true);
516 assert_eq!(doc[16].as_bool().unwrap(), false);
517 assert_eq!(doc[17].as_i64().unwrap(), 255);
518 assert!(doc[18].is_badvalue());
519 assert!(doc[19].is_badvalue());
520 assert!(doc[20].is_badvalue());
521 assert!(doc[21].is_badvalue());
522 assert_eq!(doc[22].as_i64().unwrap(), 63);
523 assert_eq!(doc[23][0].as_i64().unwrap(), 15);
524 assert_eq!(doc[23][1].as_i64().unwrap(), 15);
525 assert_eq!(doc[24].as_i64().unwrap(), 12345);
526 assert!(doc[25][0].as_bool().unwrap());
527 assert!(!doc[25][1].as_bool().unwrap());
528 }
529
530 #[test]
531 fn test_bad_hyphen() {
532 // See: https://github.com/chyh1990/yaml-rust/issues/23
533 let s = "{-";
534 assert!(YamlLoader::load_from_str(&s).is_err());
535 }
536
537 #[test]
538 fn test_issue_65() {
539 // See: https://github.com/chyh1990/yaml-rust/issues/65
540 let b = "\n\"ll\\\"ll\\\r\n\"ll\\\"ll\\\r\r\r\rU\r\r\rU";
541 assert!(YamlLoader::load_from_str(&b).is_err());
542 }
543
544 #[test]
545 fn test_bad_docstart() {
546 assert!(YamlLoader::load_from_str("---This used to cause an infinite loop").is_ok());
547 assert_eq!(
548 YamlLoader::load_from_str("----"),
549 Ok(vec![Yaml::String(String::from("----"))])
550 );
551 assert_eq!(
552 YamlLoader::load_from_str("--- #here goes a comment"),
553 Ok(vec![Yaml::Null])
554 );
555 assert_eq!(
556 YamlLoader::load_from_str("---- #here goes a comment"),
557 Ok(vec![Yaml::String(String::from("----"))])
558 );
559 }
560
561 #[test]
562 fn test_plain_datatype_with_into_methods() {
563 let s = "
564- 'string'
565- \"string\"
566- string
567- 123
568- -321
569- 1.23
570- -1e4
571- true
572- false
573- !!str 0
574- !!int 100
575- !!float 2
576- !!bool true
577- !!bool false
578- 0xFF
579- 0o77
580- +12345
581- -.INF
582- .NAN
583- !!float .INF
584";
585 let mut out = YamlLoader::load_from_str(&s).unwrap().into_iter();
586 let mut doc = out.next().unwrap().into_iter();
587
588 assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
589 assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
590 assert_eq!(doc.next().unwrap().into_string().unwrap(), "string");
591 assert_eq!(doc.next().unwrap().into_i64().unwrap(), 123);
592 assert_eq!(doc.next().unwrap().into_i64().unwrap(), -321);
593 assert_eq!(doc.next().unwrap().into_f64().unwrap(), 1.23);
594 assert_eq!(doc.next().unwrap().into_f64().unwrap(), -1e4);
595 assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
596 assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
597 assert_eq!(doc.next().unwrap().into_string().unwrap(), "0");
598 assert_eq!(doc.next().unwrap().into_i64().unwrap(), 100);
599 assert_eq!(doc.next().unwrap().into_f64().unwrap(), 2.0);
600 assert_eq!(doc.next().unwrap().into_bool().unwrap(), true);
601 assert_eq!(doc.next().unwrap().into_bool().unwrap(), false);
602 assert_eq!(doc.next().unwrap().into_i64().unwrap(), 255);
603 assert_eq!(doc.next().unwrap().into_i64().unwrap(), 63);
604 assert_eq!(doc.next().unwrap().into_i64().unwrap(), 12345);
605 assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::NEG_INFINITY);
606 assert!(doc.next().unwrap().into_f64().is_some());
607 assert_eq!(doc.next().unwrap().into_f64().unwrap(), f64::INFINITY);
608 }
609
610 #[test]
611 fn test_hash_order() {
612 let s = "---
613b: ~
614a: ~
615c: ~
616";
617 let out = YamlLoader::load_from_str(&s).unwrap();
618 let first = out.into_iter().next().unwrap();
619 let mut iter = first.into_hash().unwrap().into_iter();
620 assert_eq!(
621 Some((Yaml::String("b".to_owned()), Yaml::Null)),
622 iter.next()
623 );
624 assert_eq!(
625 Some((Yaml::String("a".to_owned()), Yaml::Null)),
626 iter.next()
627 );
628 assert_eq!(
629 Some((Yaml::String("c".to_owned()), Yaml::Null)),
630 iter.next()
631 );
632 assert_eq!(None, iter.next());
633 }
634
635 #[test]
636 fn test_integer_key() {
637 let s = "
6380:
639 important: true
6401:
641 important: false
642";
643 let out = YamlLoader::load_from_str(&s).unwrap();
644 let first = out.into_iter().next().unwrap();
645 assert_eq!(first[0]["important"].as_bool().unwrap(), true);
646 }
647
648 #[test]
649 fn test_indentation_equality() {
650 let four_spaces = YamlLoader::load_from_str(
651 r#"
652hash:
653 with:
654 indentations
655"#,
656 )
657 .unwrap()
658 .into_iter()
659 .next()
660 .unwrap();
661
662 let two_spaces = YamlLoader::load_from_str(
663 r#"
664hash:
665 with:
666 indentations
667"#,
668 )
669 .unwrap()
670 .into_iter()
671 .next()
672 .unwrap();
673
674 let one_space = YamlLoader::load_from_str(
675 r#"
676hash:
677 with:
678 indentations
679"#,
680 )
681 .unwrap()
682 .into_iter()
683 .next()
684 .unwrap();
685
686 let mixed_spaces = YamlLoader::load_from_str(
687 r#"
688hash:
689 with:
690 indentations
691"#,
692 )
693 .unwrap()
694 .into_iter()
695 .next()
696 .unwrap();
697
698 assert_eq!(four_spaces, two_spaces);
699 assert_eq!(two_spaces, one_space);
700 assert_eq!(four_spaces, mixed_spaces);
701 }
702
703 #[test]
704 fn test_two_space_indentations() {
705 // https://github.com/kbknapp/clap-rs/issues/965
706
707 let s = r#"
708subcommands:
709 - server:
710 about: server related commands
711subcommands2:
712 - server:
713 about: server related commands
714subcommands3:
715 - server:
716 about: server related commands
717 "#;
718
719 let out = YamlLoader::load_from_str(&s).unwrap();
720 let doc = &out.into_iter().next().unwrap();
721
722 println!("{:#?}", doc);
723 assert_eq!(doc["subcommands"][0]["server"], Yaml::Null);
724 assert!(doc["subcommands2"][0]["server"].as_hash().is_some());
725 assert!(doc["subcommands3"][0]["server"].as_hash().is_some());
726 }
727
728 #[test]
729 fn test_recursion_depth_check_objects() {
730 let s = "{a:".repeat(10_000) + &"}".repeat(10_000);
731 assert!(YamlLoader::load_from_str(&s).is_err());
732 }
733
734 #[test]
735 fn test_recursion_depth_check_arrays() {
736 let s = "[".repeat(10_000) + &"]".repeat(10_000);
737 assert!(YamlLoader::load_from_str(&s).is_err());
738 }
739}
740