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