Skip to main content

one_brackets/
writer.rs

1//! Writing in the platform layout: a nested list starts on a new line,
2//! and the closing bracket moves to a new line if the last item is a list. No BOM is written.
3
4use std::fmt;
5use std::io;
6
7use crate::{Node, Value};
8
9impl Node {
10    /// Writes the bracket file text to any [`io::Write`].
11    pub fn write_to<W: io::Write>(&self, writer: W) -> io::Result<()> {
12        let mut adapter = IoAdapter {
13            inner: writer,
14            error: None,
15        };
16        match write(self, &mut adapter) {
17            Ok(()) => Ok(()),
18            Err(fmt::Error) => Err(adapter
19                .error
20                .unwrap_or_else(|| io::Error::other("formatter error"))),
21        }
22    }
23}
24
25impl fmt::Display for Node {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write(self, f)
28    }
29}
30
31impl From<&Node> for String {
32    fn from(node: &Node) -> Self {
33        node.to_string()
34    }
35}
36
37impl From<Node> for String {
38    fn from(node: Node) -> Self {
39        node.to_string()
40    }
41}
42
43impl From<&Node> for Vec<u8> {
44    fn from(node: &Node) -> Self {
45        node.to_string().into_bytes()
46    }
47}
48
49impl From<Node> for Vec<u8> {
50    fn from(node: Node) -> Self {
51        node.to_string().into_bytes()
52    }
53}
54
55/// Not recursive: forms can be deeply nested.
56fn write(root: &Node, out: &mut impl fmt::Write) -> fmt::Result {
57    // (list items, index of the next item)
58    let mut stack: Vec<(&[Node], usize)> = Vec::new();
59    open(root, out, &mut stack)?;
60    while let Some(top) = stack.last_mut() {
61        let (items, next) = *top;
62        top.1 += 1;
63        if let Some(item) = items.get(next) {
64            if next > 0 {
65                out.write_char(',')?;
66            }
67            if item.is_list() {
68                out.write_str("\r\n")?;
69            }
70            open(item, out, &mut stack)?;
71        } else {
72            if items.last().is_some_and(Node::is_list) {
73                out.write_str("\r\n")?;
74            }
75            out.write_char('}')?;
76            stack.pop();
77        }
78    }
79    Ok(())
80}
81
82/// Writes a scalar entirely, or opens a list and pushes it onto the stack.
83fn open<'a>(
84    node: &'a Node,
85    out: &mut impl fmt::Write,
86    stack: &mut Vec<(&'a [Node], usize)>,
87) -> fmt::Result {
88    match node.value() {
89        Value::List(items) => {
90            out.write_char('{')?;
91            stack.push((items, 0));
92        }
93        Value::Str(s) => {
94            out.write_char('"')?;
95            for (i, part) in s.split('"').enumerate() {
96                if i > 0 {
97                    out.write_str("\"\"")?;
98                }
99                out.write_str(part)?;
100            }
101            out.write_char('"')?;
102        }
103        Value::Atom(s) => out.write_str(s)?,
104    }
105    Ok(())
106}
107
108struct IoAdapter<W> {
109    inner: W,
110    error: Option<io::Error>,
111}
112
113impl<W: io::Write> fmt::Write for IoAdapter<W> {
114    fn write_str(&mut self, s: &str) -> fmt::Result {
115        self.inner.write_all(s.as_bytes()).map_err(|e| {
116            self.error = Some(e);
117            fmt::Error
118        })
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn round_trips_platform_layout() {
128        let text = "{27,\r\n{18,\r\n{\r\n{1,1,\r\n{\"ru\",\"Форма\"}\r\n},25,4294967295},\
129                    \r\n{0}\r\n},\"\",-1,\r\n{2,c2f34349-b1e0-4060-8abf-3c59f38ac69f,}\r\n}";
130        assert_eq!(Node::try_from(text).unwrap().to_string(), text);
131    }
132
133    #[test]
134    fn keeps_base64_with_line_breaks() {
135        let text = "{1,\r\n{#base64:QUJD\r\r\nREVG\r\r\n},2}";
136        assert_eq!(String::from(Node::try_from(text).unwrap()), text);
137    }
138
139    #[test]
140    fn builds_and_writes() {
141        let node = Node::from(vec![
142            Node::from(1),
143            Node::string("say \"hi\""),
144            Node::from(vec![Node::from(true), Node::atom("")]),
145        ]);
146        insta::assert_snapshot!(node.to_string().replace("\r\n", "⏎"), @r#"{1,"say ""hi""",⏎{1,}⏎}"#);
147
148        let mut bytes = Vec::new();
149        node.write_to(&mut bytes).unwrap();
150        assert_eq!(bytes, Vec::<u8>::from(&node));
151        assert_eq!(Node::try_from(bytes).unwrap(), node);
152    }
153
154    #[test]
155    fn deep_nesting_without_recursion() {
156        let depth = 5_000;
157        let text = format!("{}{}", "{".repeat(depth), "}".repeat(depth));
158        let node = Node::try_from(text.as_str()).unwrap();
159        assert_eq!(node.to_string().replace("\r\n", ""), text);
160    }
161}