renderdag/
ascii_large.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8use 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        // The first column is only 2 characters wide.
53        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        // Render the previous extra pad line
80        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        // Render the nodeline
86        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        // Render the link line
109        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                // Top left
114                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                // Top center
129                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                // Top right
144                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                // Bottom left
157                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                // Bottom center
168                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                // Bottom Right
177                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        // Render the term line
200        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        // Render any pad lines
256        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}