prison_architect_savefile/
format.rs

1use std::fmt::{self, Write};
2
3use crate::Node;
4
5impl fmt::Display for Node {
6    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7        Writer { f, depth: 0 }.write_node(self)
8    }
9}
10
11struct Writer<F> {
12    f: F,
13    depth: u32,
14}
15
16impl<F> Writer<F>
17where
18    F: Write,
19{
20    fn write_node(&mut self, node: &Node) -> fmt::Result {
21        let mut first = true;
22        for (key, value) in node.properties() {
23            if !first {
24                self.write_newline()?;
25            }
26            first = false;
27
28            self.write_property(key, value)?;
29        }
30
31        for (key, value) in node.children() {
32            if !first {
33                self.write_newline()?;
34            }
35            first = false;
36
37            self.f.write_str("BEGIN ")?;
38            self.write_str(key)?;
39
40            if value.children.is_empty() {
41                self.f.write_char(' ')?;
42                for (key, value) in value.properties() {
43                    self.write_property(key, value)?;
44                    self.f.write_char(' ')?;
45                }
46                self.f.write_char(' ')?;
47            } else {
48                self.depth += 1;
49                self.write_newline()?;
50
51                self.write_node(value)?;
52
53                self.depth -= 1;
54                self.write_newline()?;
55            }
56
57            self.f.write_str("END")?;
58        }
59
60        Ok(())
61    }
62
63    fn write_property(&mut self, key: &str, value: &str) -> fmt::Result {
64        self.write_str(key)?;
65        self.f.write_char(' ')?;
66        self.write_str(value)?;
67        Ok(())
68    }
69
70    fn write_str(&mut self, value: &str) -> fmt::Result {
71        let mut special_chars = value.match_indices(&[' ', '"', '\n']).peekable();
72        if special_chars.peek().is_some() {
73            self.f.write_char('"')?;
74            let mut pos = 0;
75            for (start, ch) in special_chars {
76                self.f.write_str(&value[pos..start])?;
77                match ch {
78                    " " => self.f.write_char(' ')?,
79                    "\"" => self.f.write_str("\\\"")?,
80                    "\n" => self.f.write_str("\\n")?,
81                    _ => unreachable!(),
82                }
83                pos = start + ch.len();
84            }
85            self.f.write_str(&value[pos..])?;
86            self.f.write_char('"')?;
87            Ok(())
88        } else {
89            self.f.write_str(value)
90        }
91    }
92
93    fn write_newline(&mut self) -> fmt::Result {
94        self.f.write_char('\n')?;
95        for _ in 0..self.depth {
96            self.f.write_str("    ")?;
97        }
98        Ok(())
99    }
100}
101
102#[test]
103fn test() {
104    use std::collections::HashMap;
105
106    let node = Node {
107        properties: HashMap::from_iter([("foo".to_owned(), vec!["hello, world!".to_owned()])]),
108        children: HashMap::from_iter([(
109            "bar".to_owned(),
110            vec![Node {
111                properties: HashMap::from_iter([(
112                    "baz".to_owned(),
113                    vec!["one\n\"two\"".to_owned()],
114                )]),
115                children: HashMap::default(),
116            }],
117        )]),
118    };
119
120    assert_eq!(
121        node.to_string(),
122        "foo \"hello, world!\"\nBEGIN bar baz \"one\\n\\\"two\\\"\"  END"
123    )
124}