render_tree/
document.rs

1use crate::stylesheet::WriteStyle;
2use crate::Stylesheet;
3use crate::{Combine, Render};
4use std::io;
5use termcolor::{ColorChoice, StandardStream, WriteColor};
6
7#[derive(Debug, Clone)]
8pub enum Node {
9    Text(String),
10    OpenSection(&'static str),
11    CloseSection,
12    Newline,
13}
14
15/// The `Document` is the root node in a render tree.
16///
17/// The [`tree!`] macro produces a `Document`, and you can also build
18/// one manually.
19///
20/// ```
21/// use render_tree::prelude::*;
22/// use render_tree::Render;
23///
24/// fn main() -> std::io::Result<()> {
25///     let document = Document::empty()
26///         // You can add a `Line` to a document, with nested content
27///         .add(Line(
28///             // Strings implement `Render`
29///             "Hello"
30///         ))
31///         .add(Line(
32///             1.add(".").add(10)
33///         ))
34///         .add(Section("code", |doc|
35///             doc.add("[E").add(1000).add("]")
36///         ));
37///
38///     assert_eq!(document.to_string()?, "Hello\n1.10\n[E1000]");
39///
40///     Ok(())
41/// }
42/// ```
43///
44/// The above example is equivalent to this use of the [`tree!`] macro:
45///
46/// ```
47/// #[macro_use]
48/// extern crate render_tree;
49/// use render_tree::prelude::*;
50///
51/// fn main() -> std::io::Result<()> {
52///     let document = tree! {
53///         <Line as { "Hello" }>
54///         <Line as {
55///             {1} "." {10}
56///         }>
57///         <Section name="code" as {
58///             "[E" {1000} "]"
59///         }>
60///     };
61///
62///     assert_eq!(document.to_string()?, "Hello\n1.10\n[E1000]");
63///
64///     Ok(())
65/// }
66/// ```
67#[derive(Debug, Clone)]
68pub struct Document {
69    // Make the inner tree optional so it's free to create empty documents
70    tree: Option<Vec<Node>>,
71}
72
73impl Document {
74    pub fn empty() -> Document {
75        Document { tree: None }
76    }
77
78    pub fn with(renderable: impl Render) -> Document {
79        renderable.render(Document::empty())
80    }
81
82    pub(crate) fn tree(&self) -> Option<&[Node]> {
83        match &self.tree {
84            None => None,
85            Some(vec) => Some(&vec[..]),
86        }
87    }
88
89    fn initialize_tree(&mut self) -> &mut Vec<Node> {
90        if self.tree.is_none() {
91            self.tree = Some(vec![]);
92        }
93
94        match &mut self.tree {
95            Some(value) => value,
96            None => unreachable!(),
97        }
98    }
99
100    pub fn add(self, renderable: impl Render) -> Document {
101        renderable.render(self)
102    }
103
104    pub(crate) fn add_node(mut self, node: Node) -> Document {
105        self.initialize_tree().push(node);
106        self
107    }
108
109    pub(crate) fn extend_nodes(mut self, other: Vec<Node>) -> Document {
110        if other.len() > 0 {
111            let tree = self.initialize_tree();
112
113            for item in other {
114                tree.push(item)
115            }
116        }
117
118        self
119    }
120
121    pub(crate) fn extend(self, fragment: Document) -> Document {
122        match (&self.tree, &fragment.tree) {
123            (Some(_), Some(_)) => self.extend_nodes(fragment.tree.unwrap()),
124            (Some(_), None) => self,
125            (None, Some(_)) => fragment,
126            (None, None) => self,
127        }
128    }
129
130    pub fn write(self) -> io::Result<()> {
131        let mut writer = StandardStream::stdout(ColorChoice::Always);
132
133        self.write_with(&mut writer, &Stylesheet::new())
134    }
135
136    pub fn to_string(self) -> io::Result<String> {
137        let mut writer = ::termcolor::Buffer::no_color();
138        let stylesheet = Stylesheet::new();
139
140        self.write_with(&mut writer, &stylesheet)?;
141
142        Ok(String::from_utf8_lossy(writer.as_slice()).into())
143    }
144
145    pub fn write_styled(self, stylesheet: &Stylesheet) -> io::Result<()> {
146        let mut writer = StandardStream::stdout(ColorChoice::Always);
147
148        self.write_with(&mut writer, stylesheet)
149    }
150
151    pub fn write_with(
152        self,
153        writer: &mut impl WriteColor,
154        stylesheet: &Stylesheet,
155    ) -> io::Result<()> {
156        let mut nesting = vec![];
157
158        writer.reset()?;
159
160        let tree = match self.tree {
161            None => return Ok(()),
162            Some(nodes) => nodes,
163        };
164
165        for item in tree {
166            match item {
167                Node::Text(string) => {
168                    if string.len() != 0 {
169                        let style = stylesheet.get(&nesting);
170
171                        match style {
172                            None => writer.reset()?,
173                            Some(style) => writer.set_style(&style)?,
174                        }
175
176                        write!(writer, "{}", string)?;
177                    }
178                }
179                Node::OpenSection(section) => nesting.push(section),
180                Node::CloseSection => {
181                    nesting.pop().expect("unbalanced push/pop");
182                }
183                Node::Newline => {
184                    writer.reset()?;
185                    write!(writer, "\n")?;
186                }
187            }
188        }
189
190        Ok(())
191    }
192}
193
194pub fn add<Left: Render, Right: Render>(left: Left, right: Right) -> Combine<Left, Right> {
195    Combine { left, right }
196}