1use std::marker::PhantomData;
9
10use itertools::Itertools;
11
12use super::output::OutputRendererOptions;
13use super::render::Ancestor;
14use super::render::GraphRow;
15use super::render::LinkLine;
16use super::render::NodeLine;
17use super::render::PadLine;
18use super::render::Renderer;
19
20pub struct AsciiLargeRenderer<N, R>
21where
22 R: Renderer<N, Output = GraphRow<N>> + Sized,
23{
24 inner: R,
25 options: OutputRendererOptions,
26 extra_pad_line: Option<String>,
27 _phantom: PhantomData<N>,
28}
29
30impl<N, R> AsciiLargeRenderer<N, R>
31where
32 R: Renderer<N, Output = GraphRow<N>> + Sized,
33{
34 pub(crate) fn new(inner: R, options: OutputRendererOptions) -> Self {
35 AsciiLargeRenderer {
36 inner,
37 options,
38 extra_pad_line: None,
39 _phantom: PhantomData,
40 }
41 }
42}
43
44impl<N, R> Renderer<N> for AsciiLargeRenderer<N, R>
45where
46 N: Clone + Eq,
47 R: Renderer<N, Output = GraphRow<N>> + Sized,
48{
49 type Output = String;
50
51 fn width(&self, node: Option<&N>, parents: Option<&Vec<Ancestor<N>>>) -> u64 {
52 self.inner
54 .width(node, parents)
55 .saturating_mul(3)
56 .saturating_sub(1)
57 .saturating_add(1)
58 }
59
60 fn reserve(&mut self, node: N) {
61 self.inner.reserve(node);
62 }
63
64 fn next_row(
65 &mut self,
66 node: N,
67 parents: Vec<Ancestor<N>>,
68 glyph: String,
69 message: String,
70 ) -> String {
71 let line = self.inner.next_row(node, parents, glyph, message);
72 let mut out = String::new();
73 let mut message_lines = line
74 .message
75 .lines()
76 .pad_using(self.options.min_row_height, |_| "");
77 let mut need_extra_pad_line = false;
78
79 if let Some(extra_pad_line) = self.extra_pad_line.take() {
81 out.push_str(extra_pad_line.trim_end());
82 out.push('\n');
83 }
84
85 let mut node_line = String::new();
87 for (i, entry) in line.node_line.iter().enumerate() {
88 match entry {
89 NodeLine::Node => {
90 if i > 0 {
91 node_line.push(' ');
92 }
93 node_line.push_str(&line.glyph);
94 node_line.push(' ');
95 }
96 NodeLine::Parent => node_line.push_str(if i > 0 { " | " } else { "| " }),
97 NodeLine::Ancestor => node_line.push_str(if i > 0 { " . " } else { ". " }),
98 NodeLine::Blank => node_line.push_str(if i > 0 { " " } else { " " }),
99 }
100 }
101 if let Some(msg) = message_lines.next() {
102 node_line.push(' ');
103 node_line.push_str(msg);
104 }
105 out.push_str(node_line.trim_end());
106 out.push('\n');
107
108 if let Some(link_row) = line.link_line {
110 let mut top_link_line = String::new();
111 let mut bot_link_line = String::new();
112 for (i, cur) in link_row.iter().enumerate() {
113 if i > 0 {
115 if cur.intersects(LinkLine::LEFT_MERGE_PARENT) {
116 top_link_line.push('/');
117 } else if cur.intersects(LinkLine::LEFT_MERGE_ANCESTOR) {
118 top_link_line.push('.');
119 } else if cur.intersects(LinkLine::HORIZ_PARENT) {
120 top_link_line.push('_');
121 } else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
122 top_link_line.push('.');
123 } else {
124 top_link_line.push(' ');
125 }
126 }
127
128 if cur.intersects(LinkLine::VERT_PARENT) {
130 top_link_line.push('|');
131 } else if cur.intersects(LinkLine::VERT_ANCESTOR) {
132 top_link_line.push('.');
133 } else if cur.intersects(LinkLine::ANY_MERGE) {
134 top_link_line.push(' ');
135 } else if cur.intersects(LinkLine::HORIZ_PARENT) {
136 top_link_line.push('_');
137 } else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
138 top_link_line.push('.');
139 } else {
140 top_link_line.push(' ');
141 }
142
143 if cur.intersects(LinkLine::RIGHT_MERGE_PARENT) {
145 top_link_line.push('\\');
146 } else if cur.intersects(LinkLine::RIGHT_MERGE_ANCESTOR) {
147 top_link_line.push('.');
148 } else if cur.intersects(LinkLine::HORIZ_PARENT) {
149 top_link_line.push('_');
150 } else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
151 top_link_line.push('.');
152 } else {
153 top_link_line.push(' ');
154 }
155
156 if i > 0 {
158 if cur.intersects(LinkLine::LEFT_FORK_PARENT) {
159 bot_link_line.push('\\');
160 } else if cur.intersects(LinkLine::LEFT_FORK_ANCESTOR) {
161 bot_link_line.push('.');
162 } else {
163 bot_link_line.push(' ');
164 }
165 }
166
167 if cur.intersects(LinkLine::VERT_PARENT) {
169 bot_link_line.push('|');
170 } else if cur.intersects(LinkLine::VERT_ANCESTOR) {
171 bot_link_line.push('.');
172 } else {
173 bot_link_line.push(' ');
174 }
175
176 if cur.intersects(LinkLine::RIGHT_FORK_PARENT) {
178 bot_link_line.push('/');
179 } else if cur.intersects(LinkLine::RIGHT_FORK_ANCESTOR) {
180 bot_link_line.push('.');
181 } else {
182 bot_link_line.push(' ');
183 }
184 }
185 if let Some(msg) = message_lines.next() {
186 top_link_line.push(' ');
187 top_link_line.push_str(msg);
188 }
189 if let Some(msg) = message_lines.next() {
190 bot_link_line.push(' ');
191 bot_link_line.push_str(msg);
192 }
193 out.push_str(top_link_line.trim_end());
194 out.push('\n');
195 out.push_str(bot_link_line.trim_end());
196 out.push('\n');
197 }
198
199 if let Some(term_row) = line.term_line {
201 let term_strs = ["| ", "~ "];
202 for term_str in term_strs.iter() {
203 let mut term_line = String::new();
204 for (i, term) in term_row.iter().enumerate() {
205 if i > 0 {
206 term_line.push(' ');
207 }
208 if *term {
209 term_line.push_str(term_str);
210 } else {
211 term_line.push_str(match line.pad_lines[i] {
212 PadLine::Parent => "| ",
213 PadLine::Ancestor => ". ",
214 PadLine::Blank => " ",
215 });
216 }
217 }
218 if let Some(msg) = message_lines.next() {
219 term_line.push(' ');
220 term_line.push_str(msg);
221 }
222 out.push_str(term_line.trim_end());
223 out.push('\n');
224 }
225 need_extra_pad_line = true;
226 }
227
228 let mut base_pad_line = String::new();
229 for (i, entry) in line.pad_lines.iter().enumerate() {
230 base_pad_line.push_str(match entry {
231 PadLine::Parent => {
232 if i > 0 {
233 " | "
234 } else {
235 "| "
236 }
237 }
238 PadLine::Ancestor => {
239 if i > 0 {
240 " . "
241 } else {
242 ". "
243 }
244 }
245 PadLine::Blank => {
246 if i > 0 {
247 " "
248 } else {
249 " "
250 }
251 }
252 });
253 }
254
255 for msg in message_lines {
257 let mut pad_line = base_pad_line.clone();
258 pad_line.push(' ');
259 pad_line.push_str(msg);
260 out.push_str(pad_line.trim_end());
261 out.push('\n');
262 need_extra_pad_line = false;
263 }
264
265 if need_extra_pad_line {
266 self.extra_pad_line = Some(base_pad_line);
267 }
268
269 out
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::super::test_fixtures;
276 use super::super::test_fixtures::TestFixture;
277 use super::super::test_utils::render_string;
278 use crate::GraphRowRenderer;
279
280 fn render(fixture: &TestFixture) -> String {
281 let mut renderer = GraphRowRenderer::new()
282 .output()
283 .with_min_row_height(3)
284 .build_ascii_large();
285 render_string(fixture, &mut renderer)
286 }
287
288 #[test]
289 fn basic() {
290 assert_eq!(
291 render(&test_fixtures::BASIC),
292 r#"
293 o C
294 |
295 |
296 o B
297 |
298 |
299 o A"#
300 );
301 }
302
303 #[test]
304 fn branches_and_merges() {
305 assert_eq!(
306 render(&test_fixtures::BRANCHES_AND_MERGES),
307 r#"
308 o W
309 |
310 |
311 o V
312 |\
313 | \
314 | o U
315 | |\
316 | | \
317 | | o T
318 | | |
319 | | |
320 | o | S
321 | |
322 | |
323 o | R
324 | |
325 | |
326 o | Q
327 |\ |
328 | \ |
329 | o | P
330 | |\_|_
331 | | | \
332 | | | o O
333 | | | |
334 | | | |
335 | | | o N
336 | | | |\
337 | | | | \
338 | o | | | M
339 | | | | |
340 | | | | |
341 | o | | | L
342 | | | | |
343 | | | | |
344 o | | | | K
345 | _|__|__|_/
346 |/ | | |
347 o | | | J
348 | | | |
349 | | | |
350 o | | | I
351 | / | |
352 |/ | |
353 o | | H
354 | | |
355 | | |
356 o | | G
357 |\____|_ |
358 | | \|
359 | | o F
360 | | /
361 | |/
362 | o E
363 | |
364 | |
365 o | D
366 | |
367 | |
368 o | C
369 | ___/
370 |/
371 o B
372 |
373 |
374 o A"#
375 );
376 }
377
378 #[test]
379 fn octopus_branch_and_merge() {
380 assert_eq!(
381 render(&test_fixtures::OCTOPUS_BRANCH_AND_MERGE),
382 r#"
383 o J
384 |\___
385 | \ \
386 | | o I
387 | | |
388 | | |
389 | o | H
390 | /|\_|____
391 |/ | \| \ \
392 | | | | o G
393 | | | | |
394 | | | | |
395 | | | o | E
396 | | | | /
397 | | | |/
398 | | o | D
399 | | |\ |
400 | | | \|
401 | o | | C
402 | | _|_/
403 | |/ |
404 o | | F
405 | / |
406 |/ |
407 o | B
408 | ___/
409 |/
410 o A"#
411 );
412 }
413
414 #[test]
415 fn reserved_column() {
416 assert_eq!(
417 render(&test_fixtures::RESERVED_COLUMN),
418 r#"
419 o Z
420 |
421 |
422 o Y
423 |
424 |
425 o X
426 /
427 /
428 | o W
429 | /
430 |/
431 o G
432 |
433 |
434 o F
435 |\
436 | \
437 | o E
438 | |
439 | |
440 | o D
441 |
442 |
443 o C
444 |
445 |
446 o B
447 |
448 |
449 o A"#
450 );
451 }
452
453 #[test]
454 fn ancestors() {
455 assert_eq!(
456 render(&test_fixtures::ANCESTORS),
457 r#"
458 o Z
459 |
460 |
461 o Y
462 /
463 /
464 o F
465 .
466 .
467 . o X
468 . /
469 ./
470 | o W
471 | /
472 |/
473 o E
474 .
475 .
476 o D
477 |.
478 | .
479 | o C
480 | .
481 | .
482 o . B
483 | .
484 |.
485 o A"#
486 );
487 }
488
489 #[test]
490 fn split_parents() {
491 assert_eq!(
492 render(&test_fixtures::SPLIT_PARENTS),
493 r#"
494 o E
495 ...___/.
496 . / / .
497 . o | . D
498 . / \ | .
499 ./ \| .
500 | o . C
501 | | .
502 | |.
503 o | B
504 | ___/
505 |/
506 o A"#
507 );
508 }
509
510 #[test]
511 fn terminations() {
512 assert_eq!(
513 render(&test_fixtures::TERMINATIONS),
514 r#"
515 o K
516 |
517 |
518 | o J
519 | /
520 |/
521 o I
522 /|\
523 / | \
524 | | |
525 | ~ |
526 | |
527 o | E
528 | |
529 | |
530 | o H
531 | ___/
532 |/
533 o D
534 |
535 ~
536
537 o C
538 |
539 |
540 o B
541 |
542 ~"#
543 );
544 }
545
546 #[test]
547 fn long_messages() {
548 assert_eq!(
549 render(&test_fixtures::LONG_MESSAGES),
550 r#"
551 o F
552 |\___ very long message 1
553 | \ \ very long message 2
554 | | | very long message 3
555 | | ~
556 | | very long message 4
557 | | very long message 5
558 | | very long message 6
559 | |
560 | o E
561 | |
562 | |
563 | o D
564 | |
565 | |
566 o | C
567 | / long message 1
568 |/ long message 2
569 | long message 3
570 |
571 o B
572 |
573 |
574 o A
575 | long message 1
576 ~ long message 2
577 long message 3"#
578 );
579 }
580}