1 | use linked_hash_map::LinkedHashMap; |
2 | use crate::parser::*; |
3 | use crate::scanner::{Marker, ScanError, TScalarStyle, TokenType}; |
4 | use std::collections::BTreeMap; |
5 | use std::f64; |
6 | use std::i64; |
7 | use std::mem; |
8 | use std::ops::Index; |
9 | use std::string; |
10 | use 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)] |
29 | pub 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 | |
55 | pub type Array = Vec<Yaml>; |
56 | pub type Hash = LinkedHashMap<Yaml, Yaml>; |
57 | |
58 | // parse f64 as Core schema |
59 | // See: https://github.com/chyh1990/yaml-rust/issues/51 |
60 | fn 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 | |
69 | pub 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 | |
78 | impl 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 | |
160 | impl 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 | |
202 | macro_rules! define_as ( |
203 | ($name:ident, $t:ident, $yt:ident) => ( |
204 | pub fn $name(&self) -> Option<$t> { |
205 | match *self { |
206 | Yaml::$yt(v) => Some(v), |
207 | _ => None |
208 | } |
209 | } |
210 | ); |
211 | ); |
212 | |
213 | macro_rules! define_as_ref ( |
214 | ($name:ident, $t:ty, $yt:ident) => ( |
215 | pub fn $name(&self) -> Option<$t> { |
216 | match *self { |
217 | Yaml::$yt(ref v) => Some(v), |
218 | _ => None |
219 | } |
220 | } |
221 | ); |
222 | ); |
223 | |
224 | macro_rules! define_into ( |
225 | ($name:ident, $t:ty, $yt:ident) => ( |
226 | pub fn $name(self) -> Option<$t> { |
227 | match self { |
228 | Yaml::$yt(v) => Some(v), |
229 | _ => None |
230 | } |
231 | } |
232 | ); |
233 | ); |
234 | |
235 | impl 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))] |
286 | impl 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 | |
317 | static BAD_VALUE: Yaml = Yaml::BadValue; |
318 | impl<'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 | |
330 | impl 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 | |
345 | impl 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 | |
356 | pub struct YamlIter { |
357 | yaml: vec::IntoIter<Yaml>, |
358 | } |
359 | |
360 | impl 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)] |
369 | mod test { |
370 | use std::f64; |
371 | use crate::yaml::*; |
372 | #[test ] |
373 | fn test_coerce() { |
374 | let s = "--- |
375 | a: 1 |
376 | b: 2.2 |
377 | c: [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 |
399 | a0 bb: val |
400 | a1: |
401 | b1: 4 |
402 | b2: d |
403 | a2: 4 # i'm comment |
404 | a3: [1, 2, 3] |
405 | a4: |
406 | - - a1 |
407 | - a2 |
408 | - 2 |
409 | a5: 'single_quoted' |
410 | a6: \"double_quoted \" |
411 | a7: 你好 |
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 = " |
435 | a1: &DEFAULT |
436 | b1: 4 |
437 | b2: d |
438 | a2: *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 = " |
448 | a1: &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 = "--- |
613 | b: ~ |
614 | a: ~ |
615 | c: ~ |
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 = " |
638 | 0: |
639 | important: true |
640 | 1: |
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#" |
652 | hash: |
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#" |
664 | hash: |
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#" |
676 | hash: |
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#" |
688 | hash: |
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#" |
708 | subcommands: |
709 | - server: |
710 | about: server related commands |
711 | subcommands2: |
712 | - server: |
713 | about: server related commands |
714 | subcommands3: |
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 | |