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