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