1use std::marker::PhantomData;
9
10use super::output::OutputRendererOptions;
11use super::render::Ancestor;
12use super::render::GraphRow;
13use super::render::LinkLine;
14use super::render::NodeLine;
15use super::render::PadLine;
16use super::render::Renderer;
17use crate::pad::pad_lines;
18
19pub struct AsciiRenderer<N, R>
20where
21 R: Renderer<N, Output = GraphRow<N>> + Sized,
22{
23 inner: R,
24 options: OutputRendererOptions,
25 extra_pad_line: Option<String>,
26 _phantom: PhantomData<N>,
27}
28
29impl<N, R> AsciiRenderer<N, R>
30where
31 R: Renderer<N, Output = GraphRow<N>> + Sized,
32{
33 pub(crate) fn new(inner: R, options: OutputRendererOptions) -> Self {
34 AsciiRenderer {
35 inner,
36 options,
37 extra_pad_line: None,
38 _phantom: PhantomData,
39 }
40 }
41}
42
43impl<N, R> Renderer<N> for AsciiRenderer<N, R>
44where
45 N: Clone + Eq,
46 R: Renderer<N, Output = GraphRow<N>> + Sized,
47{
48 type Output = String;
49
50 fn width(&self, node: Option<&N>, parents: Option<&Vec<Ancestor<N>>>) -> u64 {
51 self.inner
52 .width(node, parents)
53 .saturating_mul(2)
54 .saturating_add(1)
55 }
56
57 fn reserve(&mut self, node: N) {
58 self.inner.reserve(node);
59 }
60
61 fn next_row(
62 &mut self,
63 node: N,
64 parents: Vec<Ancestor<N>>,
65 glyph: String,
66 message: String,
67 ) -> String {
68 let line = self.inner.next_row(node, parents, glyph, message);
69 let mut out = String::new();
70 let mut message_lines = pad_lines(line.message.lines(), self.options.min_row_height);
71 let mut need_extra_pad_line = false;
72
73 if let Some(extra_pad_line) = self.extra_pad_line.take() {
75 out.push_str(extra_pad_line.trim_end());
76 out.push('\n');
77 }
78
79 let mut node_line = String::new();
81 for entry in line.node_line.iter() {
82 match entry {
83 NodeLine::Node => {
84 node_line.push_str(&line.glyph);
85 node_line.push(' ');
86 }
87 NodeLine::Parent => node_line.push_str("| "),
88 NodeLine::Ancestor => node_line.push_str(". "),
89 NodeLine::Blank => node_line.push_str(" "),
90 }
91 }
92 if let Some(msg) = message_lines.next() {
93 node_line.push(' ');
94 node_line.push_str(msg);
95 }
96 out.push_str(node_line.trim_end());
97 out.push('\n');
98
99 if let Some(link_row) = line.link_line {
101 let mut link_line = String::new();
102 let any_horizontal = link_row
103 .iter()
104 .any(|cur| cur.intersects(LinkLine::HORIZONTAL));
105 let mut iter = link_row
106 .iter()
107 .copied()
108 .chain(std::iter::once(LinkLine::empty()))
109 .peekable();
110 while let Some(cur) = iter.next() {
111 let next = match iter.peek() {
112 Some(&v) => v,
113 None => break,
114 };
115 if cur.intersects(LinkLine::HORIZONTAL) {
117 if cur.intersects(LinkLine::CHILD | LinkLine::ANY_FORK_OR_MERGE) {
118 link_line.push('+');
119 } else {
120 link_line.push('-');
121 }
122 } else if cur.intersects(LinkLine::VERTICAL) {
123 if cur.intersects(LinkLine::ANY_FORK_OR_MERGE) && any_horizontal {
124 link_line.push('+');
125 } else if cur.intersects(LinkLine::VERT_PARENT) {
126 link_line.push('|');
127 } else {
128 link_line.push('.');
129 }
130 } else if cur.intersects(LinkLine::ANY_MERGE) && any_horizontal {
131 link_line.push('\'');
132 } else if cur.intersects(LinkLine::ANY_FORK) && any_horizontal {
133 link_line.push('.');
134 } else {
135 link_line.push(' ');
136 }
137
138 if cur.intersects(LinkLine::HORIZONTAL) {
140 link_line.push('-');
141 } else if cur.intersects(LinkLine::RIGHT_MERGE) {
142 if next.intersects(LinkLine::LEFT_FORK) && !any_horizontal {
143 link_line.push('\\');
144 } else {
145 link_line.push('-');
146 }
147 } else if cur.intersects(LinkLine::RIGHT_FORK) {
148 if next.intersects(LinkLine::LEFT_MERGE) && !any_horizontal {
149 link_line.push('/');
150 } else {
151 link_line.push('-');
152 }
153 } else {
154 link_line.push(' ');
155 }
156 }
157 if let Some(msg) = message_lines.next() {
158 link_line.push(' ');
159 link_line.push_str(msg);
160 }
161 out.push_str(link_line.trim_end());
162 out.push('\n');
163 }
164
165 if let Some(term_row) = line.term_line {
167 let term_strs = ["| ", "~ "];
168 for term_str in term_strs.iter() {
169 let mut term_line = String::new();
170 for (i, term) in term_row.iter().enumerate() {
171 if *term {
172 term_line.push_str(term_str);
173 } else {
174 term_line.push_str(match line.pad_lines[i] {
175 PadLine::Parent => "| ",
176 PadLine::Ancestor => ". ",
177 PadLine::Blank => " ",
178 });
179 }
180 }
181 if let Some(msg) = message_lines.next() {
182 term_line.push(' ');
183 term_line.push_str(msg);
184 }
185 out.push_str(term_line.trim_end());
186 out.push('\n');
187 }
188 need_extra_pad_line = true;
189 }
190
191 let mut base_pad_line = String::new();
192 for entry in line.pad_lines.iter() {
193 base_pad_line.push_str(match entry {
194 PadLine::Parent => "| ",
195 PadLine::Ancestor => ". ",
196 PadLine::Blank => " ",
197 });
198 }
199
200 for msg in message_lines {
202 let mut pad_line = base_pad_line.clone();
203 pad_line.push(' ');
204 pad_line.push_str(msg);
205 out.push_str(pad_line.trim_end());
206 out.push('\n');
207 need_extra_pad_line = false;
208 }
209
210 if need_extra_pad_line {
211 self.extra_pad_line = Some(base_pad_line);
212 }
213
214 out
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::super::test_fixtures;
221 use super::super::test_fixtures::TestFixture;
222 use super::super::test_utils::render_string;
223 use crate::GraphRowRenderer;
224
225 fn render(fixture: &TestFixture) -> String {
226 let mut renderer = GraphRowRenderer::new().output().build_ascii();
227 render_string(fixture, &mut renderer)
228 }
229
230 #[test]
231 fn basic() {
232 assert_eq!(
233 render(&test_fixtures::BASIC),
234 r#"
235 o C
236 |
237 o B
238 |
239 o A"#
240 );
241 }
242
243 #[test]
244 fn branches_and_merges() {
245 assert_eq!(
246 render(&test_fixtures::BRANCHES_AND_MERGES),
247 r#"
248 o W
249 |
250 o V
251 |\
252 | o U
253 | |\
254 | | o T
255 | | |
256 | o | S
257 | |
258 o | R
259 | |
260 o | Q
261 |\ |
262 | o | P
263 | +---.
264 | | | o O
265 | | | |
266 | | | o N
267 | | | |\
268 | o | | | M
269 | | | | |
270 | o | | | L
271 | | | | |
272 o | | | | K
273 +-------'
274 o | | | J
275 | | | |
276 o | | | I
277 |/ | |
278 o | | H
279 | | |
280 o | | G
281 +-----+
282 | | o F
283 | |/
284 | o E
285 | |
286 o | D
287 | |
288 o | C
289 +---'
290 o B
291 |
292 o A"#
293 );
294 }
295
296 #[test]
297 fn octopus_branch_and_merge() {
298 assert_eq!(
299 render(&test_fixtures::OCTOPUS_BRANCH_AND_MERGE),
300 r#"
301 o J
302 +-+-.
303 | | o I
304 | | |
305 | o | H
306 +-+-+-+-.
307 | | | | o G
308 | | | | |
309 | | | o | E
310 | | | |/
311 | | o | D
312 | | |\|
313 | o | | C
314 | +---'
315 o | | F
316 |/ |
317 o | B
318 +---'
319 o A"#
320 );
321 }
322
323 #[test]
324 fn reserved_column() {
325 assert_eq!(
326 render(&test_fixtures::RESERVED_COLUMN),
327 r#"
328 o Z
329 |
330 o Y
331 |
332 o X
333 /
334 | o W
335 |/
336 o G
337 |
338 o F
339 |\
340 | o E
341 | |
342 | o D
343 |
344 o C
345 |
346 o B
347 |
348 o A"#
349 );
350 }
351
352 #[test]
353 fn ancestors() {
354 assert_eq!(
355 render(&test_fixtures::ANCESTORS),
356 r#"
357 o Z
358 |
359 o Y
360 /
361 o F
362 .
363 . o X
364 ./
365 | o W
366 |/
367 o E
368 .
369 o D
370 |\
371 | o C
372 | .
373 o . B
374 |/
375 o A"#
376 );
377 }
378
379 #[test]
380 fn split_parents() {
381 assert_eq!(
382 render(&test_fixtures::SPLIT_PARENTS),
383 r#"
384 o E
385 .-+-+-+
386 . o | . D
387 ./ \| .
388 | o . C
389 | |/
390 o | B
391 +---'
392 o A"#
393 );
394 }
395
396 #[test]
397 fn terminations() {
398 assert_eq!(
399 render(&test_fixtures::TERMINATIONS),
400 r#"
401 o K
402 |
403 | o J
404 |/
405 o I
406 /|\
407 | | |
408 | ~ |
409 | |
410 | o H
411 | |
412 o | E
413 +---'
414 o D
415 |
416 ~
417
418 o C
419 |
420 o B
421 |
422 ~"#
423 );
424 }
425
426 #[test]
427 fn long_messages() {
428 assert_eq!(
429 render(&test_fixtures::LONG_MESSAGES),
430 r#"
431 o F
432 +-+-. very long message 1
433 | | | very long message 2
434 | | ~ very long message 3
435 | |
436 | | very long message 4
437 | | very long message 5
438 | | very long message 6
439 | |
440 | o E
441 | |
442 | o D
443 | |
444 o | C
445 |/ long message 1
446 | long message 2
447 | long message 3
448 |
449 o B
450 |
451 o A
452 | long message 1
453 ~ long message 2
454 long message 3"#
455 );
456 }
457}