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 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        // The first column is only 2 characters wide.
52        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        // Render the previous extra pad line
76        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        // Render the nodeline
82        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        // Render the link line
105        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                // Top left
110                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                // Top center
125                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                // Top right
140                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                // Bottom left
153                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                // Bottom center
164                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                // Bottom Right
173                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        // Render the term line
196        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        // Render any pad lines
252        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}