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#[derive(Debug, Clone)]
68pub struct Document {
69 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}