1use crate::{ColorMode, ASCII_RESET};
4use std::fmt;
5use std::fmt::{Display, Formatter, Write};
6
7pub struct AsciiText {
9 color: Option<String>,
10 text: String,
11}
12
13impl AsciiText {
14 pub fn new(text: &str) -> Self {
16 Self {
17 color: None,
18 text: text.to_string(),
19 }
20 }
21
22 pub fn with_color(text: &str, color: &str) -> Self {
24 Self {
25 color: Some(color.to_string()),
26 text: text.to_string(),
27 }
28 }
29
30 pub fn mode(&self, color_mode: &ColorMode) -> Self {
32 AsciiText {
33 color: match color_mode {
34 ColorMode::On => self.color.clone(),
35 _ => None,
36 },
37 text: self.text.clone(),
38 }
39 }
40}
41
42impl Display for AsciiText {
43 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
45 if let Some(color) = &self.color {
46 write!(f, "{}{}{}", color, self.text, ASCII_RESET)
47 } else {
48 write!(f, "{}", self.text)
49 }
50 }
51}
52
53pub struct AsciiLine(Vec<AsciiText>);
55
56impl AsciiLine {
57 pub fn builder() -> AsciiLineBuilder {
58 AsciiLineBuilder(vec![])
59 }
60
61 pub fn mode(&self, color_mode: &ColorMode) -> Self {
62 AsciiLine(self.0.iter().map(|ascii_text| ascii_text.mode(color_mode)).collect())
63 }
64}
65
66impl Display for AsciiLine {
67 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
68 for text in &self.0 {
69 write!(f, "{}", text)?
70 }
71 Ok(())
72 }
73}
74
75pub struct AsciiLineBuilder(Vec<AsciiText>);
77
78impl AsciiLineBuilder {
79 pub fn text(mut self, text: &str) -> Self {
80 self.0.push(AsciiText::new(text));
81 self
82 }
83
84 pub fn with_color(mut self, text: &str, color: &str) -> Self {
85 self.0.push(AsciiText::with_color(text, color));
86 self
87 }
88
89 pub fn indent(mut self) -> Self {
90 self.0.push(AsciiText::new(" "));
91 self
92 }
93
94 pub fn colon(mut self) -> Self {
95 self.0.push(AsciiText::new(":"));
96 self
97 }
98
99 pub fn colon_space(mut self) -> Self {
100 self.0.push(AsciiText::new(": "));
101 self
102 }
103
104 pub fn build(self) -> AsciiLine {
105 AsciiLine(self.0)
106 }
107}
108
109pub enum AsciiNode {
111 Node(AsciiLine, Vec<AsciiNode>),
113 Leaf(Vec<AsciiLine>),
115}
116
117impl AsciiNode {
118 pub fn leaf_builder() -> AsciiLeafBuilder {
119 AsciiLeafBuilder(vec![])
120 }
121
122 pub fn node_builder(line: AsciiLine) -> AsciiNodeBuilder {
123 AsciiNodeBuilder(line, vec![])
124 }
125}
126
127pub struct AsciiLeafBuilder(Vec<AsciiLine>);
129
130impl AsciiLeafBuilder {
131 pub fn line(mut self, line: AsciiLine) -> Self {
132 self.0.push(line);
133 self
134 }
135
136 pub fn add_line(&mut self, line: AsciiLine) {
137 self.0.push(line);
138 }
139
140 pub fn build(self) -> AsciiNode {
141 AsciiNode::Leaf(self.0)
142 }
143}
144
145pub struct AsciiNodeBuilder(AsciiLine, Vec<AsciiNode>);
147
148impl AsciiNodeBuilder {
149 pub fn child(mut self, child: AsciiNode) -> Self {
150 self.1.push(child);
151 self
152 }
153
154 pub fn opt_child(mut self, opt_child: Option<AsciiNode>) -> Self {
155 if let Some(child) = opt_child {
156 self.1.push(child);
157 }
158 self
159 }
160
161 pub fn add_child(&mut self, child: AsciiNode) {
162 self.1.push(child);
163 }
164
165 pub fn build(self) -> AsciiNode {
166 AsciiNode::Node(self.0, self.1)
167 }
168}
169
170pub fn write(f: &mut dyn Write, node: &AsciiNode, color_mode: &ColorMode) -> fmt::Result {
172 write_node(f, node, &[], color_mode)
173}
174
175pub fn write_indented(f: &mut dyn Write, node: &AsciiNode, color_mode: &ColorMode, indent: usize) -> fmt::Result {
177 let mut buffer = String::new();
178 let _ = write_node(&mut buffer, node, &[], color_mode);
179 let indent = " ".repeat(indent);
180 let mut tree = String::new();
181 for line in buffer.lines() {
182 let _ = writeln!(&mut tree, "{}{}", indent, line);
183 }
184 write!(f, "{}", tree)
185}
186
187fn write_node(f: &mut dyn Write, tree: &AsciiNode, level: &[usize], color_mode: &ColorMode) -> fmt::Result {
189 const NONE: &str = " ";
190 const EDGE: &str = " └─";
191 const PIPE: &str = " │ ";
192 const FORK: &str = " ├─";
193
194 let max_pos = level.len();
195 let mut second_line = String::new();
196 for (pos, lev) in level.iter().enumerate() {
197 let last_row = pos == max_pos - 1;
198 if *lev == 1 {
199 if !last_row {
200 write!(f, "{}", NONE)?
201 } else {
202 write!(f, "{}", EDGE)?
203 }
204 second_line.push_str(NONE);
205 } else {
206 if !last_row {
207 write!(f, "{}", PIPE)?
208 } else {
209 write!(f, "{}", FORK)?
210 }
211 second_line.push_str(PIPE);
212 }
213 }
214 match tree {
215 AsciiNode::Node(title, children) => {
216 let mut deep = children.len();
217 writeln!(f, " {}", title.mode(color_mode))?;
218 for node in children {
219 let mut level_next = level.to_vec();
220 level_next.push(deep);
221 deep -= 1;
222 write_node(f, node, &level_next, color_mode)?;
223 }
224 }
225 AsciiNode::Leaf(lines) => {
226 for (i, line) in lines.iter().enumerate() {
227 match i {
228 0 => writeln!(f, " {}", line.mode(color_mode))?,
229 _ => writeln!(f, "{} {}", second_line, line.mode(color_mode))?,
230 }
231 }
232 }
233 }
234 Ok(())
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 const EXPECTED: &str = r#"
242 node 4
243 ├─ node 1
244 │ ├─ line 1_1
245 │ │ line 1_2
246 │ │ line 1_3
247 │ │ line 1_4
248 │ └─ only one line
249 ├─ node 2
250 │ ├─ only one line
251 │ ├─ line 2_1
252 │ │ line 2_2
253 │ │ line 2_3
254 │ │ line 2_4
255 │ └─ only one line
256 └─ node 3
257 ├─ node 1
258 │ ├─ line 3_1_1
259 │ │ line 3_1_2
260 │ │ line 3_1_3
261 │ │ line 3_1_4
262 │ └─ only one line
263 ├─ line 3_1
264 │ line 3_2
265 │ line 3_3
266 │ line 3_4
267 └─ only one line
268"#;
269
270 #[test]
271 fn test_ascii_tree() {
272 let root = AsciiNode::node_builder(AsciiLine::builder().text("node 4").build())
273 .child(
274 AsciiNode::node_builder(AsciiLine::builder().text("node 1").build())
275 .child(
276 AsciiNode::leaf_builder()
277 .line(AsciiLine::builder().text("line 1_1").build())
278 .line(AsciiLine::builder().text("line 1_2").build())
279 .line(AsciiLine::builder().text("line 1_3").build())
280 .line(AsciiLine::builder().text("line 1_4").build())
281 .build(),
282 )
283 .child(AsciiNode::leaf_builder().line(AsciiLine::builder().text("only one line").build()).build())
284 .build(),
285 )
286 .child(
287 AsciiNode::node_builder(AsciiLine::builder().text("node 2").build())
288 .child(AsciiNode::leaf_builder().line(AsciiLine::builder().text("only one line").build()).build())
289 .child(
290 AsciiNode::leaf_builder()
291 .line(AsciiLine::builder().text("line 2_1").build())
292 .line(AsciiLine::builder().text("line 2_2").build())
293 .line(AsciiLine::builder().text("line 2_3").build())
294 .line(AsciiLine::builder().text("line 2_4").build())
295 .build(),
296 )
297 .child(AsciiNode::leaf_builder().line(AsciiLine::builder().text("only one line").build()).build())
298 .build(),
299 )
300 .child(
301 AsciiNode::node_builder(AsciiLine::builder().text("node 3").build())
302 .child(
303 AsciiNode::node_builder(AsciiLine::builder().text("node 1").build())
304 .child(
305 AsciiNode::leaf_builder()
306 .line(AsciiLine::builder().text("line 3_1_1").build())
307 .line(AsciiLine::builder().text("line 3_1_2").build())
308 .line(AsciiLine::builder().text("line 3_1_3").build())
309 .line(AsciiLine::builder().text("line 3_1_4").build())
310 .build(),
311 )
312 .child(AsciiNode::leaf_builder().line(AsciiLine::builder().text("only one line").build()).build())
313 .build(),
314 )
315 .child(
316 AsciiNode::leaf_builder()
317 .line(AsciiLine::builder().text("line 3_1").build())
318 .line(AsciiLine::builder().text("line 3_2").build())
319 .line(AsciiLine::builder().text("line 3_3").build())
320 .line(AsciiLine::builder().text("line 3_4").build())
321 .build(),
322 )
323 .child(AsciiNode::leaf_builder().line(AsciiLine::builder().text("only one line").build()).build())
324 .build(),
325 )
326 .build();
327
328 let mut output = String::new();
329 let _ = writeln!(&mut output);
330 let _ = write(&mut output, &root, &ColorMode::Off);
331 assert_eq!(EXPECTED, output);
332 }
333}