1 use std::convert::From;
2 use std::error::Error;
3 use std::fmt::{self, Display};
4 use crate::yaml::{Hash, Yaml};
5 
6 #[derive(Copy, Clone, Debug)]
7 pub enum EmitError {
8     FmtError(fmt::Error),
9     BadHashmapKey,
10 }
11 
12 impl Error for EmitError {
cause(&self) -> Option<&dyn Error>13     fn cause(&self) -> Option<&dyn Error> {
14         None
15     }
16 }
17 
18 impl Display for EmitError {
fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result19     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
20         match *self {
21             EmitError::FmtError(ref err) => Display::fmt(err, formatter),
22             EmitError::BadHashmapKey => formatter.write_str("bad hashmap key"),
23         }
24     }
25 }
26 
27 impl From<fmt::Error> for EmitError {
from(f: fmt::Error) -> Self28     fn from(f: fmt::Error) -> Self {
29         EmitError::FmtError(f)
30     }
31 }
32 
33 pub struct YamlEmitter<'a> {
34     writer: &'a mut dyn fmt::Write,
35     best_indent: usize,
36     compact: bool,
37 
38     level: isize,
39 }
40 
41 pub type EmitResult = Result<(), EmitError>;
42 
43 // from serialize::json
escape_str(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error>44 fn escape_str(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error> {
45     wr.write_str("\"")?;
46 
47     let mut start = 0;
48 
49     for (i, byte) in v.bytes().enumerate() {
50         let escaped = match byte {
51             b'"' => "\\\"",
52             b'\\' => "\\\\",
53             b'\x00' => "\\u0000",
54             b'\x01' => "\\u0001",
55             b'\x02' => "\\u0002",
56             b'\x03' => "\\u0003",
57             b'\x04' => "\\u0004",
58             b'\x05' => "\\u0005",
59             b'\x06' => "\\u0006",
60             b'\x07' => "\\u0007",
61             b'\x08' => "\\b",
62             b'\t' => "\\t",
63             b'\n' => "\\n",
64             b'\x0b' => "\\u000b",
65             b'\x0c' => "\\f",
66             b'\r' => "\\r",
67             b'\x0e' => "\\u000e",
68             b'\x0f' => "\\u000f",
69             b'\x10' => "\\u0010",
70             b'\x11' => "\\u0011",
71             b'\x12' => "\\u0012",
72             b'\x13' => "\\u0013",
73             b'\x14' => "\\u0014",
74             b'\x15' => "\\u0015",
75             b'\x16' => "\\u0016",
76             b'\x17' => "\\u0017",
77             b'\x18' => "\\u0018",
78             b'\x19' => "\\u0019",
79             b'\x1a' => "\\u001a",
80             b'\x1b' => "\\u001b",
81             b'\x1c' => "\\u001c",
82             b'\x1d' => "\\u001d",
83             b'\x1e' => "\\u001e",
84             b'\x1f' => "\\u001f",
85             b'\x7f' => "\\u007f",
86             _ => continue,
87         };
88 
89         if start < i {
90             wr.write_str(&v[start..i])?;
91         }
92 
93         wr.write_str(escaped)?;
94 
95         start = i + 1;
96     }
97 
98     if start != v.len() {
99         wr.write_str(&v[start..])?;
100     }
101 
102     wr.write_str("\"")?;
103     Ok(())
104 }
105 
106 impl<'a> YamlEmitter<'a> {
new(writer: &'a mut dyn fmt::Write) -> YamlEmitter107     pub fn new(writer: &'a mut dyn fmt::Write) -> YamlEmitter {
108         YamlEmitter {
109             writer,
110             best_indent: 2,
111             compact: true,
112             level: -1,
113         }
114     }
115 
116     /// Set 'compact inline notation' on or off, as described for block
117     /// [sequences](http://www.yaml.org/spec/1.2/spec.html#id2797382)
118     /// and
119     /// [mappings](http://www.yaml.org/spec/1.2/spec.html#id2798057).
120     ///
121     /// In this form, blocks cannot have any properties (such as anchors
122     /// or tags), which should be OK, because this emitter doesn't
123     /// (currently) emit those anyways.
compact(&mut self, compact: bool)124     pub fn compact(&mut self, compact: bool) {
125         self.compact = compact;
126     }
127 
128     /// Determine if this emitter is using 'compact inline notation'.
is_compact(&self) -> bool129     pub fn is_compact(&self) -> bool {
130         self.compact
131     }
132 
dump(&mut self, doc: &Yaml) -> EmitResult133     pub fn dump(&mut self, doc: &Yaml) -> EmitResult {
134         // write DocumentStart
135         writeln!(self.writer, "---")?;
136         self.level = -1;
137         self.emit_node(doc)
138     }
139 
write_indent(&mut self) -> EmitResult140     fn write_indent(&mut self) -> EmitResult {
141         if self.level <= 0 {
142             return Ok(());
143         }
144         for _ in 0..self.level {
145             for _ in 0..self.best_indent {
146                 write!(self.writer, " ")?;
147             }
148         }
149         Ok(())
150     }
151 
emit_node(&mut self, node: &Yaml) -> EmitResult152     fn emit_node(&mut self, node: &Yaml) -> EmitResult {
153         match *node {
154             Yaml::Array(ref v) => self.emit_array(v),
155             Yaml::Hash(ref h) => self.emit_hash(h),
156             Yaml::String(ref v) => {
157                 if need_quotes(v) {
158                     escape_str(self.writer, v)?;
159                 } else {
160                     write!(self.writer, "{}", v)?;
161                 }
162                 Ok(())
163             }
164             Yaml::Boolean(v) => {
165                 if v {
166                     self.writer.write_str("true")?;
167                 } else {
168                     self.writer.write_str("false")?;
169                 }
170                 Ok(())
171             }
172             Yaml::Integer(v) => {
173                 write!(self.writer, "{}", v)?;
174                 Ok(())
175             }
176             Yaml::Real(ref v) => {
177                 write!(self.writer, "{}", v)?;
178                 Ok(())
179             }
180             Yaml::Null | Yaml::BadValue => {
181                 write!(self.writer, "~")?;
182                 Ok(())
183             }
184             // XXX(chenyh) Alias
185             _ => Ok(()),
186         }
187     }
188 
emit_array(&mut self, v: &[Yaml]) -> EmitResult189     fn emit_array(&mut self, v: &[Yaml]) -> EmitResult {
190         if v.is_empty() {
191             write!(self.writer, "[]")?;
192         } else {
193             self.level += 1;
194             for (cnt, x) in v.iter().enumerate() {
195                 if cnt > 0 {
196                     writeln!(self.writer)?;
197                     self.write_indent()?;
198                 }
199                 write!(self.writer, "-")?;
200                 self.emit_val(true, x)?;
201             }
202             self.level -= 1;
203         }
204         Ok(())
205     }
206 
emit_hash(&mut self, h: &Hash) -> EmitResult207     fn emit_hash(&mut self, h: &Hash) -> EmitResult {
208         if h.is_empty() {
209             self.writer.write_str("{}")?;
210         } else {
211             self.level += 1;
212             for (cnt, (k, v)) in h.iter().enumerate() {
213                 let complex_key = match *k {
214                     Yaml::Hash(_) | Yaml::Array(_) => true,
215                     _ => false,
216                 };
217                 if cnt > 0 {
218                     writeln!(self.writer)?;
219                     self.write_indent()?;
220                 }
221                 if complex_key {
222                     write!(self.writer, "?")?;
223                     self.emit_val(true, k)?;
224                     writeln!(self.writer)?;
225                     self.write_indent()?;
226                     write!(self.writer, ":")?;
227                     self.emit_val(true, v)?;
228                 } else {
229                     self.emit_node(k)?;
230                     write!(self.writer, ":")?;
231                     self.emit_val(false, v)?;
232                 }
233             }
234             self.level -= 1;
235         }
236         Ok(())
237     }
238 
239     /// Emit a yaml as a hash or array value: i.e., which should appear
240     /// following a ":" or "-", either after a space, or on a new line.
241     /// If `inline` is true, then the preceding characters are distinct
242     /// and short enough to respect the compact flag.
emit_val(&mut self, inline: bool, val: &Yaml) -> EmitResult243     fn emit_val(&mut self, inline: bool, val: &Yaml) -> EmitResult {
244         match *val {
245             Yaml::Array(ref v) => {
246                 if (inline && self.compact) || v.is_empty() {
247                     write!(self.writer, " ")?;
248                 } else {
249                     writeln!(self.writer)?;
250                     self.level += 1;
251                     self.write_indent()?;
252                     self.level -= 1;
253                 }
254                 self.emit_array(v)
255             }
256             Yaml::Hash(ref h) => {
257                 if (inline && self.compact) || h.is_empty() {
258                     write!(self.writer, " ")?;
259                 } else {
260                     writeln!(self.writer)?;
261                     self.level += 1;
262                     self.write_indent()?;
263                     self.level -= 1;
264                 }
265                 self.emit_hash(h)
266             }
267             _ => {
268                 write!(self.writer, " ")?;
269                 self.emit_node(val)
270             }
271         }
272     }
273 }
274 
275 /// Check if the string requires quoting.
276 /// Strings starting with any of the following characters must be quoted.
277 /// :, &, *, ?, |, -, <, >, =, !, %, @
278 /// Strings containing any of the following characters must be quoted.
279 /// {, }, [, ], ,, #, `
280 ///
281 /// If the string contains any of the following control characters, it must be escaped with double quotes:
282 /// \0, \x01, \x02, \x03, \x04, \x05, \x06, \a, \b, \t, \n, \v, \f, \r, \x0e, \x0f, \x10, \x11, \x12, \x13, \x14, \x15, \x16, \x17, \x18, \x19, \x1a, \e, \x1c, \x1d, \x1e, \x1f, \N, \_, \L, \P
283 ///
284 /// Finally, there are other cases when the strings must be quoted, no matter if you're using single or double quotes:
285 /// * When the string is true or false (otherwise, it would be treated as a boolean value);
286 /// * When the string is null or ~ (otherwise, it would be considered as a null value);
287 /// * When the string looks like a number, such as integers (e.g. 2, 14, etc.), floats (e.g. 2.6, 14.9) and exponential numbers (e.g. 12e7, etc.) (otherwise, it would be treated as a numeric value);
288 /// * When the string looks like a date (e.g. 2014-12-31) (otherwise it would be automatically converted into a Unix timestamp).
need_quotes(string: &str) -> bool289 fn need_quotes(string: &str) -> bool {
290     fn need_quotes_spaces(string: &str) -> bool {
291         string.starts_with(' ') || string.ends_with(' ')
292     }
293 
294     string == ""
295         || need_quotes_spaces(string)
296         || string.starts_with(|character: char| match character {
297             '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@' => true,
298             _ => false,
299         })
300         || string.contains(|character: char| match character {
301             ':'
302             | '{'
303             | '}'
304             | '['
305             | ']'
306             | ','
307             | '#'
308             | '`'
309             | '\"'
310             | '\''
311             | '\\'
312             | '\0'..='\x06'
313             | '\t'
314             | '\n'
315             | '\r'
316             | '\x0e'..='\x1a'
317             | '\x1c'..='\x1f' => true,
318             _ => false,
319         })
320         || [
321             // http://yaml.org/type/bool.html
322             // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse
323             // them as string, not booleans, although it is violating the YAML 1.1 specification.
324             // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.
325             "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE",
326             "false", "on", "On", "ON", "off", "Off", "OFF",
327             // http://yaml.org/type/null.html
328             "null", "Null", "NULL", "~",
329         ]
330         .contains(&string)
331         || string.starts_with('.')
332         || string.starts_with("0x")
333         || string.parse::<i64>().is_ok()
334         || string.parse::<f64>().is_ok()
335 }
336 
337 #[cfg(test)]
338 mod test {
339     use super::*;
340     use crate::YamlLoader;
341 
342     #[test]
test_emit_simple()343     fn test_emit_simple() {
344         let s = "
345 # comment
346 a0 bb: val
347 a1:
348     b1: 4
349     b2: d
350 a2: 4 # i'm comment
351 a3: [1, 2, 3]
352 a4:
353     - [a1, a2]
354     - 2
355 ";
356 
357         let docs = YamlLoader::load_from_str(&s).unwrap();
358         let doc = &docs[0];
359         let mut writer = String::new();
360         {
361             let mut emitter = YamlEmitter::new(&mut writer);
362             emitter.dump(doc).unwrap();
363         }
364         println!("original:\n{}", s);
365         println!("emitted:\n{}", writer);
366         let docs_new = match YamlLoader::load_from_str(&writer) {
367             Ok(y) => y,
368             Err(e) => panic!(format!("{}", e)),
369         };
370         let doc_new = &docs_new[0];
371 
372         assert_eq!(doc, doc_new);
373     }
374 
375     #[test]
test_emit_complex()376     fn test_emit_complex() {
377         let s = r#"
378 cataloge:
379   product: &coffee   { name: Coffee,    price: 2.5  ,  unit: 1l  }
380   product: &cookies  { name: Cookies!,  price: 3.40 ,  unit: 400g}
381 
382 products:
383   *coffee:
384     amount: 4
385   *cookies:
386     amount: 4
387   [1,2,3,4]:
388     array key
389   2.4:
390     real key
391   true:
392     bool key
393   {}:
394     empty hash key
395             "#;
396         let docs = YamlLoader::load_from_str(&s).unwrap();
397         let doc = &docs[0];
398         let mut writer = String::new();
399         {
400             let mut emitter = YamlEmitter::new(&mut writer);
401             emitter.dump(doc).unwrap();
402         }
403         let docs_new = match YamlLoader::load_from_str(&writer) {
404             Ok(y) => y,
405             Err(e) => panic!(format!("{}", e)),
406         };
407         let doc_new = &docs_new[0];
408         assert_eq!(doc, doc_new);
409     }
410 
411     #[test]
test_emit_avoid_quotes()412     fn test_emit_avoid_quotes() {
413         let s = r#"---
414 a7: 你好
415 boolean: "true"
416 boolean2: "false"
417 date: 2014-12-31
418 empty_string: ""
419 empty_string1: " "
420 empty_string2: "    a"
421 empty_string3: "    a "
422 exp: "12e7"
423 field: ":"
424 field2: "{"
425 field3: "\\"
426 field4: "\n"
427 field5: "can't avoid quote"
428 float: "2.6"
429 int: "4"
430 nullable: "null"
431 nullable2: "~"
432 products:
433   "*coffee":
434     amount: 4
435   "*cookies":
436     amount: 4
437   ".milk":
438     amount: 1
439   "2.4": real key
440   "[1,2,3,4]": array key
441   "true": bool key
442   "{}": empty hash key
443 x: test
444 y: avoid quoting here
445 z: string with spaces"#;
446 
447         let docs = YamlLoader::load_from_str(&s).unwrap();
448         let doc = &docs[0];
449         let mut writer = String::new();
450         {
451             let mut emitter = YamlEmitter::new(&mut writer);
452             emitter.dump(doc).unwrap();
453         }
454 
455         assert_eq!(s, writer, "actual:\n\n{}\n", writer);
456     }
457 
458     #[test]
emit_quoted_bools()459     fn emit_quoted_bools() {
460         let input = r#"---
461 string0: yes
462 string1: no
463 string2: "true"
464 string3: "false"
465 string4: "~"
466 null0: ~
467 [true, false]: real_bools
468 [True, TRUE, False, FALSE, y,Y,yes,Yes,YES,n,N,no,No,NO,on,On,ON,off,Off,OFF]: false_bools
469 bool0: true
470 bool1: false"#;
471         let expected = r#"---
472 string0: "yes"
473 string1: "no"
474 string2: "true"
475 string3: "false"
476 string4: "~"
477 null0: ~
478 ? - true
479   - false
480 : real_bools
481 ? - "True"
482   - "TRUE"
483   - "False"
484   - "FALSE"
485   - y
486   - Y
487   - "yes"
488   - "Yes"
489   - "YES"
490   - n
491   - N
492   - "no"
493   - "No"
494   - "NO"
495   - "on"
496   - "On"
497   - "ON"
498   - "off"
499   - "Off"
500   - "OFF"
501 : false_bools
502 bool0: true
503 bool1: false"#;
504 
505         let docs = YamlLoader::load_from_str(&input).unwrap();
506         let doc = &docs[0];
507         let mut writer = String::new();
508         {
509             let mut emitter = YamlEmitter::new(&mut writer);
510             emitter.dump(doc).unwrap();
511         }
512 
513         assert_eq!(
514             expected, writer,
515             "expected:\n{}\nactual:\n{}\n",
516             expected, writer
517         );
518     }
519 
520     #[test]
test_empty_and_nested()521     fn test_empty_and_nested() {
522         test_empty_and_nested_flag(false)
523     }
524 
525     #[test]
test_empty_and_nested_compact()526     fn test_empty_and_nested_compact() {
527         test_empty_and_nested_flag(true)
528     }
529 
test_empty_and_nested_flag(compact: bool)530     fn test_empty_and_nested_flag(compact: bool) {
531         let s = if compact {
532             r#"---
533 a:
534   b:
535     c: hello
536   d: {}
537 e:
538   - f
539   - g
540   - h: []"#
541         } else {
542             r#"---
543 a:
544   b:
545     c: hello
546   d: {}
547 e:
548   - f
549   - g
550   -
551     h: []"#
552         };
553 
554         let docs = YamlLoader::load_from_str(&s).unwrap();
555         let doc = &docs[0];
556         let mut writer = String::new();
557         {
558             let mut emitter = YamlEmitter::new(&mut writer);
559             emitter.compact(compact);
560             emitter.dump(doc).unwrap();
561         }
562 
563         assert_eq!(s, writer);
564     }
565 
566     #[test]
test_nested_arrays()567     fn test_nested_arrays() {
568         let s = r#"---
569 a:
570   - b
571   - - c
572     - d
573     - - e
574       - f"#;
575 
576         let docs = YamlLoader::load_from_str(&s).unwrap();
577         let doc = &docs[0];
578         let mut writer = String::new();
579         {
580             let mut emitter = YamlEmitter::new(&mut writer);
581             emitter.dump(doc).unwrap();
582         }
583         println!("original:\n{}", s);
584         println!("emitted:\n{}", writer);
585 
586         assert_eq!(s, writer);
587     }
588 
589     #[test]
test_deeply_nested_arrays()590     fn test_deeply_nested_arrays() {
591         let s = r#"---
592 a:
593   - b
594   - - c
595     - d
596     - - e
597       - - f
598       - - e"#;
599 
600         let docs = YamlLoader::load_from_str(&s).unwrap();
601         let doc = &docs[0];
602         let mut writer = String::new();
603         {
604             let mut emitter = YamlEmitter::new(&mut writer);
605             emitter.dump(doc).unwrap();
606         }
607         println!("original:\n{}", s);
608         println!("emitted:\n{}", writer);
609 
610         assert_eq!(s, writer);
611     }
612 
613     #[test]
test_nested_hashes()614     fn test_nested_hashes() {
615         let s = r#"---
616 a:
617   b:
618     c:
619       d:
620         e: f"#;
621 
622         let docs = YamlLoader::load_from_str(&s).unwrap();
623         let doc = &docs[0];
624         let mut writer = String::new();
625         {
626             let mut emitter = YamlEmitter::new(&mut writer);
627             emitter.dump(doc).unwrap();
628         }
629         println!("original:\n{}", s);
630         println!("emitted:\n{}", writer);
631 
632         assert_eq!(s, writer);
633     }
634 
635 }
636