1use crate::yaml::{Hash, Yaml};
2
3use std::convert::From;
4use std::error::Error;
5use std::fmt::{self, Display};
6
7#[derive(Copy, Clone, Debug)]
8pub enum EmitError {
9 FmtError(fmt::Error),
10 BadHashmapKey,
11}
12
13impl Error for EmitError {
14 fn cause(&self) -> Option<&dyn Error> {
15 None
16 }
17}
18
19impl Display for EmitError {
20 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
21 match *self {
22 EmitError::FmtError(ref err) => Display::fmt(err, formatter),
23 EmitError::BadHashmapKey => formatter.write_str("bad hashmap key"),
24 }
25 }
26}
27
28impl From<fmt::Error> for EmitError {
29 fn from(f: fmt::Error) -> Self {
30 EmitError::FmtError(f)
31 }
32}
33
34pub struct YamlEmitter<'a> {
35 writer: &'a mut dyn fmt::Write,
36 best_indent: usize,
37 compact: bool,
38 multiline_strings: bool,
39
40 level: isize,
41}
42
43pub type EmitResult = Result<(), EmitError>;
44
45fn escape_str(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error> {
47 wr.write_str("\"")?;
48
49 let mut start = 0;
50
51 for (i, byte) in v.bytes().enumerate() {
52 let escaped = match byte {
53 b'"' => "\\\"",
54 b'\\' => "\\\\",
55 b'\x00' => "\\u0000",
56 b'\x01' => "\\u0001",
57 b'\x02' => "\\u0002",
58 b'\x03' => "\\u0003",
59 b'\x04' => "\\u0004",
60 b'\x05' => "\\u0005",
61 b'\x06' => "\\u0006",
62 b'\x07' => "\\u0007",
63 b'\x08' => "\\b",
64 b'\t' => "\\t",
65 b'\n' => "\\n",
66 b'\x0b' => "\\u000b",
67 b'\x0c' => "\\f",
68 b'\r' => "\\r",
69 b'\x0e' => "\\u000e",
70 b'\x0f' => "\\u000f",
71 b'\x10' => "\\u0010",
72 b'\x11' => "\\u0011",
73 b'\x12' => "\\u0012",
74 b'\x13' => "\\u0013",
75 b'\x14' => "\\u0014",
76 b'\x15' => "\\u0015",
77 b'\x16' => "\\u0016",
78 b'\x17' => "\\u0017",
79 b'\x18' => "\\u0018",
80 b'\x19' => "\\u0019",
81 b'\x1a' => "\\u001a",
82 b'\x1b' => "\\u001b",
83 b'\x1c' => "\\u001c",
84 b'\x1d' => "\\u001d",
85 b'\x1e' => "\\u001e",
86 b'\x1f' => "\\u001f",
87 b'\x7f' => "\\u007f",
88 _ => continue,
89 };
90
91 if start < i {
92 wr.write_str(&v[start..i])?;
93 }
94
95 wr.write_str(escaped)?;
96
97 start = i + 1;
98 }
99
100 if start != v.len() {
101 wr.write_str(&v[start..])?;
102 }
103
104 wr.write_str("\"")?;
105 Ok(())
106}
107
108impl<'a> YamlEmitter<'a> {
109 pub fn new(writer: &'a mut dyn fmt::Write) -> YamlEmitter {
110 YamlEmitter {
111 writer,
112 best_indent: 2,
113 compact: true,
114 level: -1,
115 multiline_strings: false,
116 }
117 }
118
119 pub fn compact(&mut self, compact: bool) {
128 self.compact = compact;
129 }
130
131 pub fn is_compact(&self) -> bool {
133 self.compact
134 }
135
136 pub fn multiline_strings(&mut self, multiline_strings: bool) {
164 self.multiline_strings = multiline_strings
165 }
166
167 pub fn is_multiline_strings(&self) -> bool {
169 self.multiline_strings
170 }
171
172 pub fn dump(&mut self, doc: &Yaml) -> EmitResult {
173 writeln!(self.writer, "---")?;
175 self.level = -1;
176 self.emit_node(doc)
177 }
178
179 fn write_indent(&mut self) -> EmitResult {
180 if self.level <= 0 {
181 return Ok(());
182 }
183 for _ in 0..self.level {
184 for _ in 0..self.best_indent {
185 write!(self.writer, " ")?;
186 }
187 }
188 Ok(())
189 }
190
191 fn emit_node(&mut self, node: &Yaml) -> EmitResult {
192 match *node {
193 Yaml::Array(ref v) => self.emit_array(v),
194 Yaml::Hash(ref h) => self.emit_hash(h),
195 Yaml::String(ref v) => {
196 if self.multiline_strings && v.contains('\n') {
197 write!(self.writer, "|")?;
198 self.level += 1;
199 for line in v.lines() {
200 writeln!(self.writer)?;
201 self.write_indent()?;
202 write!(self.writer, "{}", line)?;
204 }
205 self.level -= 1;
206 } else if need_quotes(v) {
207 escape_str(self.writer, v)?;
208 } else {
209 write!(self.writer, "{}", v)?;
210 }
211
212 Ok(())
213 }
214 Yaml::Boolean(v) => {
215 if v {
216 self.writer.write_str("true")?;
217 } else {
218 self.writer.write_str("false")?;
219 }
220 Ok(())
221 }
222 Yaml::Integer(v) => {
223 write!(self.writer, "{}", v)?;
224 Ok(())
225 }
226 Yaml::Real(ref v) => {
227 write!(self.writer, "{}", v)?;
228 Ok(())
229 }
230 Yaml::Null | Yaml::BadValue => {
231 write!(self.writer, "~")?;
232 Ok(())
233 }
234 _ => Ok(()),
236 }
237 }
238
239 fn emit_array(&mut self, v: &[Yaml]) -> EmitResult {
240 if v.is_empty() {
241 write!(self.writer, "[]")?;
242 } else {
243 self.level += 1;
244 for (cnt, x) in v.iter().enumerate() {
245 if cnt > 0 {
246 writeln!(self.writer)?;
247 self.write_indent()?;
248 }
249 write!(self.writer, "-")?;
250 self.emit_val(true, x)?;
251 }
252 self.level -= 1;
253 }
254 Ok(())
255 }
256
257 fn emit_hash(&mut self, h: &Hash) -> EmitResult {
258 if h.is_empty() {
259 self.writer.write_str("{}")?;
260 } else {
261 self.level += 1;
262 for (cnt, (k, v)) in h.iter().enumerate() {
263 let complex_key = matches!(*k, Yaml::Hash(_) | Yaml::Array(_));
264 if cnt > 0 {
265 writeln!(self.writer)?;
266 self.write_indent()?;
267 }
268 if complex_key {
269 write!(self.writer, "?")?;
270 self.emit_val(true, k)?;
271 writeln!(self.writer)?;
272 self.write_indent()?;
273 write!(self.writer, ":")?;
274 self.emit_val(true, v)?;
275 } else {
276 self.emit_node(k)?;
277 write!(self.writer, ":")?;
278 self.emit_val(false, v)?;
279 }
280 }
281 self.level -= 1;
282 }
283 Ok(())
284 }
285
286 fn emit_val(&mut self, inline: bool, val: &Yaml) -> EmitResult {
291 match *val {
292 Yaml::Array(ref v) => {
293 if (inline && self.compact) || v.is_empty() {
294 write!(self.writer, " ")?;
295 } else {
296 writeln!(self.writer)?;
297 self.level += 1;
298 self.write_indent()?;
299 self.level -= 1;
300 }
301 self.emit_array(v)
302 }
303 Yaml::Hash(ref h) => {
304 if (inline && self.compact) || h.is_empty() {
305 write!(self.writer, " ")?;
306 } else {
307 writeln!(self.writer)?;
308 self.level += 1;
309 self.write_indent()?;
310 self.level -= 1;
311 }
312 self.emit_hash(h)
313 }
314 _ => {
315 write!(self.writer, " ")?;
316 self.emit_node(val)
317 }
318 }
319 }
320}
321
322fn need_quotes(string: &str) -> bool {
337 fn need_quotes_spaces(string: &str) -> bool {
338 string.starts_with(' ') || string.ends_with(' ')
339 }
340
341 string.is_empty()
342 || need_quotes_spaces(string)
343 || string.starts_with(|character: char| {
344 matches!(
345 character,
346 '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'
347 )
348 })
349 || string.contains(|character: char| {
350 matches!(character, ':'
351 | '{'
352 | '}'
353 | '['
354 | ']'
355 | ','
356 | '#'
357 | '`'
358 | '\"'
359 | '\''
360 | '\\'
361 | '\0'..='\x06'
362 | '\t'
363 | '\n'
364 | '\r'
365 | '\x0e'..='\x1a'
366 | '\x1c'..='\x1f'
367 )
368 })
369 || [
370 "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE",
375 "false", "on", "On", "ON", "off", "Off", "OFF",
376 "null", "Null", "NULL", "~",
378 ]
379 .contains(&string)
380 || string.starts_with('.')
381 || string.starts_with("0x")
382 || string.parse::<i64>().is_ok()
383 || string.parse::<f64>().is_ok()
384}
385
386#[cfg(test)]
387mod test {
388 use super::*;
389 use crate::YamlLoader;
390
391 #[test]
392 fn test_emit_simple() {
393 let s = "
394# comment
395a0 bb: val
396a1:
397 b1: 4
398 b2: d
399a2: 4 # i'm comment
400a3: [1, 2, 3]
401a4:
402 - [a1, a2]
403 - 2
404";
405
406 let docs = YamlLoader::load_from_str(s).unwrap();
407 let doc = &docs[0];
408 let mut writer = String::new();
409 {
410 let mut emitter = YamlEmitter::new(&mut writer);
411 emitter.dump(doc).unwrap();
412 }
413 println!("original:\n{}", s);
414 println!("emitted:\n{}", writer);
415 let docs_new = match YamlLoader::load_from_str(&writer) {
416 Ok(y) => y,
417 Err(e) => panic!("{}", e),
418 };
419 let doc_new = &docs_new[0];
420
421 assert_eq!(doc, doc_new);
422 }
423
424 #[test]
425 fn test_emit_complex() {
426 let s = r#"
427cataloge:
428 product: &coffee { name: Coffee, price: 2.5 , unit: 1l }
429 product: &cookies { name: Cookies!, price: 3.40 , unit: 400g}
430
431products:
432 *coffee:
433 amount: 4
434 *cookies:
435 amount: 4
436 [1,2,3,4]:
437 array key
438 2.4:
439 real key
440 true:
441 bool key
442 {}:
443 empty hash key
444 "#;
445 let docs = YamlLoader::load_from_str(s).unwrap();
446 let doc = &docs[0];
447 let mut writer = String::new();
448 {
449 let mut emitter = YamlEmitter::new(&mut writer);
450 emitter.dump(doc).unwrap();
451 }
452 let docs_new = match YamlLoader::load_from_str(&writer) {
453 Ok(y) => y,
454 Err(e) => panic!("{}", e),
455 };
456 let doc_new = &docs_new[0];
457 assert_eq!(doc, doc_new);
458 }
459
460 #[test]
461 fn test_emit_avoid_quotes() {
462 let s = r#"---
463a7: 你好
464boolean: "true"
465boolean2: "false"
466date: 2014-12-31
467empty_string: ""
468empty_string1: " "
469empty_string2: " a"
470empty_string3: " a "
471exp: "12e7"
472field: ":"
473field2: "{"
474field3: "\\"
475field4: "\n"
476field5: "can't avoid quote"
477float: "2.6"
478int: "4"
479nullable: "null"
480nullable2: "~"
481products:
482 "*coffee":
483 amount: 4
484 "*cookies":
485 amount: 4
486 ".milk":
487 amount: 1
488 "2.4": real key
489 "[1,2,3,4]": array key
490 "true": bool key
491 "{}": empty hash key
492x: test
493y: avoid quoting here
494z: string with spaces"#;
495
496 let docs = YamlLoader::load_from_str(s).unwrap();
497 let doc = &docs[0];
498 let mut writer = String::new();
499 {
500 let mut emitter = YamlEmitter::new(&mut writer);
501 emitter.dump(doc).unwrap();
502 }
503
504 assert_eq!(s, writer, "actual:\n\n{}\n", writer);
505 }
506
507 #[test]
508 fn emit_quoted_bools() {
509 let input = r#"---
510string0: yes
511string1: no
512string2: "true"
513string3: "false"
514string4: "~"
515null0: ~
516[true, false]: real_bools
517[True, TRUE, False, FALSE, y,Y,yes,Yes,YES,n,N,no,No,NO,on,On,ON,off,Off,OFF]: false_bools
518bool0: true
519bool1: false"#;
520 let expected = r#"---
521string0: "yes"
522string1: "no"
523string2: "true"
524string3: "false"
525string4: "~"
526null0: ~
527? - true
528 - false
529: real_bools
530? - "True"
531 - "TRUE"
532 - "False"
533 - "FALSE"
534 - y
535 - Y
536 - "yes"
537 - "Yes"
538 - "YES"
539 - n
540 - N
541 - "no"
542 - "No"
543 - "NO"
544 - "on"
545 - "On"
546 - "ON"
547 - "off"
548 - "Off"
549 - "OFF"
550: false_bools
551bool0: true
552bool1: false"#;
553
554 let docs = YamlLoader::load_from_str(input).unwrap();
555 let doc = &docs[0];
556 let mut writer = String::new();
557 {
558 let mut emitter = YamlEmitter::new(&mut writer);
559 emitter.dump(doc).unwrap();
560 }
561
562 assert_eq!(
563 expected, writer,
564 "expected:\n{}\nactual:\n{}\n",
565 expected, writer
566 );
567 }
568
569 #[test]
570 fn test_empty_and_nested() {
571 test_empty_and_nested_flag(false)
572 }
573
574 #[test]
575 fn test_empty_and_nested_compact() {
576 test_empty_and_nested_flag(true)
577 }
578
579 fn test_empty_and_nested_flag(compact: bool) {
580 let s = if compact {
581 r#"---
582a:
583 b:
584 c: hello
585 d: {}
586e:
587 - f
588 - g
589 - h: []"#
590 } else {
591 r#"---
592a:
593 b:
594 c: hello
595 d: {}
596e:
597 - f
598 - g
599 -
600 h: []"#
601 };
602
603 let docs = YamlLoader::load_from_str(s).unwrap();
604 let doc = &docs[0];
605 let mut writer = String::new();
606 {
607 let mut emitter = YamlEmitter::new(&mut writer);
608 emitter.compact(compact);
609 emitter.dump(doc).unwrap();
610 }
611
612 assert_eq!(s, writer);
613 }
614
615 #[test]
616 fn test_nested_arrays() {
617 let s = r#"---
618a:
619 - b
620 - - c
621 - d
622 - - e
623 - f"#;
624
625 let docs = YamlLoader::load_from_str(s).unwrap();
626 let doc = &docs[0];
627 let mut writer = String::new();
628 {
629 let mut emitter = YamlEmitter::new(&mut writer);
630 emitter.dump(doc).unwrap();
631 }
632 println!("original:\n{}", s);
633 println!("emitted:\n{}", writer);
634
635 assert_eq!(s, writer);
636 }
637
638 #[test]
639 fn test_deeply_nested_arrays() {
640 let s = r#"---
641a:
642 - b
643 - - c
644 - d
645 - - e
646 - - f
647 - - e"#;
648
649 let docs = YamlLoader::load_from_str(s).unwrap();
650 let doc = &docs[0];
651 let mut writer = String::new();
652 {
653 let mut emitter = YamlEmitter::new(&mut writer);
654 emitter.dump(doc).unwrap();
655 }
656 println!("original:\n{}", s);
657 println!("emitted:\n{}", writer);
658
659 assert_eq!(s, writer);
660 }
661
662 #[test]
663 fn test_nested_hashes() {
664 let s = r#"---
665a:
666 b:
667 c:
668 d:
669 e: f"#;
670
671 let docs = YamlLoader::load_from_str(s).unwrap();
672 let doc = &docs[0];
673 let mut writer = String::new();
674 {
675 let mut emitter = YamlEmitter::new(&mut writer);
676 emitter.dump(doc).unwrap();
677 }
678 println!("original:\n{}", s);
679 println!("emitted:\n{}", writer);
680
681 assert_eq!(s, writer);
682 }
683}