1 use linked_hash_map
::LinkedHashMap
;
3 use crate::scanner
::{Marker, ScanError, TScalarStyle, TokenType}
;
4 use std
::collections
::BTreeMap
;
12 /// A YAML node is stored as this `Yaml` enumeration, which provides an easy way to
13 /// access your YAML document.
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);
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());
28 #[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord, Hash)]
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.
33 /// YAML int is stored as i64.
36 String(string
::String
),
37 /// YAML bool, e.g. `true` or `false`.
39 /// YAML array, can be accessed as a `Vec`.
41 /// YAML hash, can be accessed as a `LinkedHashMap`.
43 /// Insertion order will match the order of insertion into the map.
45 /// Alias, not fully supported yet.
47 /// YAML null, e.g. `null` or `~`.
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`.
55 pub type Array
= Vec
<Yaml
>;
56 pub type Hash
= LinkedHashMap
<Yaml
, Yaml
>;
58 // parse f64 as Core schema
59 // See: https://github.com/chyh1990/yaml-rust/issues/51
60 fn parse_f64(v
: &str) -> Option
<f64> {
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(),
69 pub struct YamlLoader
{
72 // (current node, anchor_id) tuple
73 doc_stack
: Vec
<(Yaml
, usize)>,
75 anchor_map
: BTreeMap
<usize, Yaml
>,
78 impl MarkedEventReceiver
for YamlLoader
{
79 fn on_event(&mut self, ev
: Event
, _
: Marker
) {
80 // println!("EV {:?}", ev);
82 Event
::DocumentStart
=> {
85 Event
::DocumentEnd
=> {
86 match self.doc_stack
.len() {
88 0 => self.docs
.push(Yaml
::BadValue
),
89 1 => self.docs
.push(self.doc_stack
.pop().unwrap().0),
93 Event
::SequenceStart(aid
) => {
94 self.doc_stack
.push((Yaml
::Array(Vec
::new()), aid
));
96 Event
::SequenceEnd
=> {
97 let node
= self.doc_stack
.pop().unwrap();
98 self.insert_new_node(node
);
100 Event
::MappingStart(aid
) => {
101 self.doc_stack
.push((Yaml
::Hash(Hash
::new()), aid
));
102 self.key_stack
.push(Yaml
::BadValue
);
104 Event
::MappingEnd
=> {
105 self.key_stack
.pop().unwrap();
106 let node
= self.doc_stack
.pop().unwrap();
107 self.insert_new_node(node
);
109 Event
::Scalar(v
, style
, aid
, tag
) => {
110 let node
= if style
!= TScalarStyle
::Plain
{
112 } else if let Some(TokenType
::Tag(ref handle
, ref suffix
)) = tag
{
113 // XXX tag:yaml.org,2002:
115 match suffix
.as_ref() {
118 match v
.parse
::<bool
>() {
119 Err(_
) => Yaml
::BadValue
,
120 Ok(v
) => Yaml
::Boolean(v
),
123 "int" => match v
.parse
::<i64>() {
124 Err(_
) => Yaml
::BadValue
,
125 Ok(v
) => Yaml
::Integer(v
),
127 "float" => match parse_f64(&v
) {
128 Some(_
) => Yaml
::Real(v
),
129 None
=> Yaml
::BadValue
,
131 "null" => match v
.as_ref() {
132 "~" | "null" => Yaml
::Null
,
135 _
=> Yaml
::String(v
),
141 // Datatype is not specified, or unrecognized
145 self.insert_new_node((node
, aid
));
147 Event
::Alias(id
) => {
148 let n
= match self.anchor_map
.get(&id
) {
149 Some(v
) => v
.clone(),
150 None
=> Yaml
::BadValue
,
152 self.insert_new_node((n
, 0));
154 _
=> { /* ignore */ }
156 // println!("DOC {:?}", self.doc_stack);
161 fn insert_new_node(&mut self, node
: (Yaml
, usize)) {
162 // valid anchor id starts from 1
164 self.anchor_map
.insert(node
.1, node
.0.clone());
166 if self.doc_stack
.is_empty() {
167 self.doc_stack
.push(node
);
169 let parent
= self.doc_stack
.last_mut().unwrap();
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() {
177 // current node is a value
179 let mut newkey
= Yaml
::BadValue
;
180 mem
::swap(&mut newkey
, cur_key
);
181 h
.insert(newkey
, node
.0);
189 pub fn load_from_str(source
: &str) -> Result
<Vec
<Yaml
>, ScanError
> {
190 let mut loader
= YamlLoader
{
192 doc_stack
: Vec
::new(),
193 key_stack
: Vec
::new(),
194 anchor_map
: BTreeMap
::new(),
196 let mut parser
= Parser
::new(source
.chars());
197 parser
.load(&mut loader
, true)?
;
202 macro_rules
! define_as (
203 ($name
:ident
, $t
:ident
, $yt
:ident
) => (
204 pub fn $
name(&self) -> Option
<$t
> {
206 Yaml
::$
yt(v
) => Some(v
),
213 macro_rules
! define_as_ref (
214 ($name
:ident
, $t
:ty
, $yt
:ident
) => (
215 pub fn $
name(&self) -> Option
<$t
> {
217 Yaml
::$
yt(ref v
) => Some(v
),
224 macro_rules
! define_into (
225 ($name
:ident
, $t
:ty
, $yt
:ident
) => (
226 pub fn $
name(self) -> Option
<$t
> {
228 Yaml
::$
yt(v
) => Some(v
),
236 define_as
!(as_bool
, bool
, Boolean
);
237 define_as
!(as_i64
, i64, Integer
);
239 define_as_ref
!(as_str
, &str, String
);
240 define_as_ref
!(as_hash
, &Hash
, Hash
);
241 define_as_ref
!(as_vec
, &Array
, Array
);
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
);
249 pub fn is_null(&self) -> bool
{
256 pub fn is_badvalue(&self) -> bool
{
258 Yaml
::BadValue
=> true,
263 pub fn is_array(&self) -> bool
{
265 Yaml
::Array(_
) => true,
270 pub fn as_f64(&self) -> Option
<f64> {
272 Yaml
::Real(ref v
) => parse_f64(v
),
277 pub fn into_f64(self) -> Option
<f64> {
279 Yaml
::Real(ref v
) => parse_f64(v
),
285 #[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))]
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
);
295 if v
.starts_with("0o") {
296 if let Ok(i
) = i64::from_str_radix(&v
[2..], 8) {
297 return Yaml
::Integer(i
);
300 if v
.starts_with('
+'
) {
301 if let Ok(i
) = v
[1..].parse
::<i64>() {
302 return Yaml
::Integer(i
);
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()),
317 static BAD_VALUE
: Yaml
= Yaml
::BadValue
;
318 impl<'a
> Index
<&'a
str> for Yaml
{
321 fn index(&self, idx
: &'a
str) -> &Yaml
{
322 let key
= Yaml
::String(idx
.to_owned());
323 match self.as_hash() {
324 Some(h
) => h
.get(&key
).unwrap_or(&BAD_VALUE
),
330 impl Index
<usize> for Yaml
{
333 fn index(&self, idx
: usize) -> &Yaml
{
334 if let Some(v
) = self.as_vec() {
335 v
.get(idx
).unwrap_or(&BAD_VALUE
)
336 } else if let Some(v
) = self.as_hash() {
337 let key
= Yaml
::Integer(idx
as i64);
338 v
.get(&key
).unwrap_or(&BAD_VALUE
)
345 impl IntoIterator
for Yaml
{
347 type IntoIter
= YamlIter
;
349 fn into_iter(self) -> Self::IntoIter
{
351 yaml
: self.into_vec().unwrap_or_else(Vec
::new
).into_iter(),
356 pub struct YamlIter
{
357 yaml
: vec
::IntoIter
<Yaml
>,
360 impl Iterator
for YamlIter
{
363 fn next(&mut self) -> Option
<Yaml
> {
379 let out
= YamlLoader
::load_from_str(&s
).unwrap();
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());
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
);
410 a6: \"double_quoted\"
414 let out
= YamlLoader
::load_from_str(&s
).unwrap();
416 assert_eq
!(doc
["a7"].as_str().unwrap(), "ä½ å¥½");
420 fn test_multi_doc() {
428 let out
= YamlLoader
::load_from_str(&s
).unwrap();
429 assert_eq
!(out
.len(), 3);
440 let out
= YamlLoader
::load_from_str(&s
).unwrap();
442 assert_eq
!(doc
["a2"]["b1"].as_i64().unwrap(), 4);
446 fn test_bad_anchor() {
452 let out
= YamlLoader
::load_from_str(&s
).unwrap();
454 assert_eq
!(doc
["a1"]["b2"], Yaml
::BadValue
);
458 fn test_github_27() {
459 // https://github.com/chyh1990/yaml-rust/issues/27
461 let out
= YamlLoader
::load_from_str(&s
).unwrap();
463 assert_eq
!(doc
.as_str().unwrap(), "");
467 fn test_plain_datatype() {
497 let out
= YamlLoader
::load_from_str(&s
).unwrap();
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());
531 fn test_bad_hyphen() {
532 // See: https://github.com/chyh1990/yaml-rust/issues/23
534 assert
!(YamlLoader
::load_from_str(&s
).is_err());
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());
545 fn test_bad_docstart() {
546 assert
!(YamlLoader
::load_from_str("---This used to cause an infinite loop").is_ok());
548 YamlLoader
::load_from_str("----"),
549 Ok(vec
![Yaml
::String(String
::from("----"))])
552 YamlLoader
::load_from_str("--- #here goes a comment"),
556 YamlLoader
::load_from_str("---- #here goes a comment"),
557 Ok(vec
![Yaml
::String(String
::from("----"))])
562 fn test_plain_datatype_with_into_methods() {
585 let mut out
= YamlLoader
::load_from_str(&s
).unwrap().into_iter();
586 let mut doc
= out
.next().unwrap().into_iter();
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
);
611 fn test_hash_order() {
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();
621 Some((Yaml
::String("b".to_owned()), Yaml
::Null
)),
625 Some((Yaml
::String("a".to_owned()), Yaml
::Null
)),
629 Some((Yaml
::String("c".to_owned()), Yaml
::Null
)),
632 assert_eq
!(None
, iter
.next());
636 fn test_integer_key() {
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);
649 fn test_indentation_equality() {
650 let four_spaces
= YamlLoader
::load_from_str(
662 let two_spaces
= YamlLoader
::load_from_str(
674 let one_space
= YamlLoader
::load_from_str(
686 let mixed_spaces
= YamlLoader
::load_from_str(
698 assert_eq
!(four_spaces
, two_spaces
);
699 assert_eq
!(two_spaces
, one_space
);
700 assert_eq
!(four_spaces
, mixed_spaces
);
704 fn test_two_space_indentations() {
705 // https://github.com/kbknapp/clap-rs/issues/965
710 about: server related commands
713 about: server related commands
716 about: server related commands
719 let out
= YamlLoader
::load_from_str(&s
).unwrap();
720 let doc
= &out
.into_iter().next().unwrap();
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());
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());
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());