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