1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use std::{
16     borrow::Cow,
17     fmt::{Result, Write},
18 };
19 
20 /// Number of space used to indent lines when no alignement is required.
21 pub(crate) const INDENTATION_SIZE: usize = 2;
22 
23 /// A list of [`Block`] possibly rendered with a [`Decoration`].
24 ///
25 /// This is the top-level renderable component, corresponding to the description
26 /// or match explanation of a single matcher.
27 ///
28 /// The constituent [`Block`] of a `List` can be decorated with either bullets
29 /// (`* `) or enumeration (`0. `, `1. `, ...). This is controlled via the
30 /// methods [`List::bullet_list`] and [`List::enumerate`]. By default, there is
31 /// no decoration.
32 ///
33 /// A `List` can be constructed as follows:
34 ///
35 ///   * [`Default::default()`] constructs an empty `List`.
36 ///   * [`Iterator::collect()`] on an [`Iterator`] of [`Block`].
37 ///   * [`Iterator::collect()`] on an [`Iterator`] of `String`, which produces a
38 ///     [`Block::Literal`] for each `String`.
39 ///   * [`Iterator::collect()`] on an [`Iterator`] of `List`, which produces a
40 ///     [`Block::Nested`] for each `List`.
41 #[derive(Debug, Default)]
42 pub(crate) struct List(Vec<Block>, Decoration);
43 
44 impl List {
45     /// Render this instance using the formatter `f`.
46     ///
47     /// Indent each line of output by `indentation` spaces.
render(&self, f: &mut dyn Write, indentation: usize) -> Result48     pub(crate) fn render(&self, f: &mut dyn Write, indentation: usize) -> Result {
49         self.render_with_prefix(f, indentation, "".into())
50     }
51 
52     /// Append a new [`Block`] containing `literal`.
53     ///
54     /// The input `literal` is split into lines so that each line will be
55     /// indented correctly.
push_literal(&mut self, literal: Cow<'static, str>)56     pub(crate) fn push_literal(&mut self, literal: Cow<'static, str>) {
57         self.0.push(literal.into());
58     }
59 
60     /// Append a new [`Block`] containing `inner` as a nested [`List`].
push_nested(&mut self, inner: List)61     pub(crate) fn push_nested(&mut self, inner: List) {
62         self.0.push(Block::Nested(inner));
63     }
64 
65     /// Render each [`Block`] of this instance preceded with a bullet "* ".
bullet_list(self) -> Self66     pub(crate) fn bullet_list(self) -> Self {
67         Self(self.0, Decoration::Bullet)
68     }
69 
70     /// Render each [`Block`] of this instance preceded with its 0-based index.
enumerate(self) -> Self71     pub(crate) fn enumerate(self) -> Self {
72         Self(self.0, Decoration::Enumerate)
73     }
74 
75     /// Return the number of [`Block`] in this instance.
len(&self) -> usize76     pub(crate) fn len(&self) -> usize {
77         self.0.len()
78     }
79 
80     /// Return `true` if there are no [`Block`] in this instance, `false`
81     /// otherwise.
is_empty(&self) -> bool82     pub(crate) fn is_empty(&self) -> bool {
83         self.0.is_empty()
84     }
85 
render_with_prefix( &self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>, ) -> Result86     fn render_with_prefix(
87         &self,
88         f: &mut dyn Write,
89         indentation: usize,
90         prefix: Cow<'static, str>,
91     ) -> Result {
92         if self.0.is_empty() {
93             return Ok(());
94         }
95 
96         let enumeration_padding = self.enumeration_padding();
97 
98         self.0[0].render(
99             f,
100             indentation,
101             self.full_prefix(0, enumeration_padding, &prefix).into(),
102         )?;
103         for (index, block) in self.0[1..].iter().enumerate() {
104             writeln!(f)?;
105             block.render(
106                 f,
107                 indentation + prefix.len(),
108                 self.prefix(index + 1, enumeration_padding),
109             )?;
110         }
111         Ok(())
112     }
113 
full_prefix(&self, index: usize, enumeration_padding: usize, prior_prefix: &str) -> String114     fn full_prefix(&self, index: usize, enumeration_padding: usize, prior_prefix: &str) -> String {
115         format!("{prior_prefix}{}", self.prefix(index, enumeration_padding))
116     }
117 
prefix(&self, index: usize, enumeration_padding: usize) -> Cow<'static, str>118     fn prefix(&self, index: usize, enumeration_padding: usize) -> Cow<'static, str> {
119         match self.1 {
120             Decoration::None => "".into(),
121             Decoration::Bullet => "* ".into(),
122             Decoration::Enumerate => format!("{:>enumeration_padding$}. ", index).into(),
123         }
124     }
125 
enumeration_padding(&self) -> usize126     fn enumeration_padding(&self) -> usize {
127         match self.1 {
128             Decoration::None => 0,
129             Decoration::Bullet => 0,
130             Decoration::Enumerate => {
131                 if self.0.len() > 1 {
132                     ((self.0.len() - 1) as f64).log10().floor() as usize + 1
133                 } else {
134                     // Avoid negative logarithm when there is only 0 or 1 element.
135                     1
136                 }
137             }
138         }
139     }
140 }
141 
142 impl FromIterator<Block> for List {
from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = Block>,143     fn from_iter<T>(iter: T) -> Self
144     where
145         T: IntoIterator<Item = Block>,
146     {
147         Self(iter.into_iter().collect(), Decoration::None)
148     }
149 }
150 
151 impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for List {
from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = ElementT>,152     fn from_iter<T>(iter: T) -> Self
153     where
154         T: IntoIterator<Item = ElementT>,
155     {
156         Self(iter.into_iter().map(|b| b.into().into()).collect(), Decoration::None)
157     }
158 }
159 
160 impl FromIterator<List> for List {
from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = List>,161     fn from_iter<T>(iter: T) -> Self
162     where
163         T: IntoIterator<Item = List>,
164     {
165         Self(iter.into_iter().map(Block::nested).collect(), Decoration::None)
166     }
167 }
168 
169 /// A sequence of [`Fragment`] or a nested [`List`].
170 ///
171 /// This may be rendered with a prefix specified by the [`Decoration`] of the
172 /// containing [`List`]. In this case, all lines are indented to align with the
173 /// first character of the first line of the block.
174 #[derive(Debug)]
175 enum Block {
176     /// A block of text.
177     ///
178     /// Each constituent [`Fragment`] contains one line of text. The lines are
179     /// indented uniformly to the current indentation of this block when
180     /// rendered.
181     Literal(Vec<Fragment>),
182 
183     /// A nested [`List`].
184     ///
185     /// The [`List`] is rendered recursively at the next level of indentation.
186     Nested(List),
187 }
188 
189 impl Block {
nested(inner: List) -> Self190     fn nested(inner: List) -> Self {
191         Self::Nested(inner)
192     }
193 
render(&self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>) -> Result194     fn render(&self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>) -> Result {
195         match self {
196             Self::Literal(fragments) => {
197                 if fragments.is_empty() {
198                     return Ok(());
199                 }
200 
201                 write!(f, "{:indentation$}{prefix}", "")?;
202                 fragments[0].render(f)?;
203                 let block_indentation = indentation + prefix.as_ref().len();
204                 for fragment in &fragments[1..] {
205                     writeln!(f)?;
206                     write!(f, "{:block_indentation$}", "")?;
207                     fragment.render(f)?;
208                 }
209                 Ok(())
210             }
211             Self::Nested(inner) => inner.render_with_prefix(
212                 f,
213                 indentation + INDENTATION_SIZE.saturating_sub(prefix.len()),
214                 prefix,
215             ),
216         }
217     }
218 }
219 
220 impl From<String> for Block {
from(value: String) -> Self221     fn from(value: String) -> Self {
222         Block::Literal(value.lines().map(|v| Fragment(v.to_string().into())).collect())
223     }
224 }
225 
226 impl From<&'static str> for Block {
from(value: &'static str) -> Self227     fn from(value: &'static str) -> Self {
228         Block::Literal(value.lines().map(|v| Fragment(v.into())).collect())
229     }
230 }
231 
232 impl From<Cow<'static, str>> for Block {
from(value: Cow<'static, str>) -> Self233     fn from(value: Cow<'static, str>) -> Self {
234         match value {
235             Cow::Borrowed(value) => value.into(),
236             Cow::Owned(value) => value.into(),
237         }
238     }
239 }
240 
241 /// A string representing one line of a description or match explanation.
242 #[derive(Debug)]
243 struct Fragment(Cow<'static, str>);
244 
245 impl Fragment {
render(&self, f: &mut dyn Write) -> Result246     fn render(&self, f: &mut dyn Write) -> Result {
247         write!(f, "{}", self.0)
248     }
249 }
250 
251 /// The decoration which appears on [`Block`] of a [`List`] when rendered.
252 #[derive(Debug, Default)]
253 enum Decoration {
254     /// No decoration on each [`Block`]. The default.
255     #[default]
256     None,
257 
258     /// Each [`Block`] is preceded by a bullet (`* `).
259     Bullet,
260 
261     /// Each [`Block`] is preceded by its index in the [`List`] (`0. `, `1. `,
262     /// ...).
263     Enumerate,
264 }
265 
266 #[cfg(test)]
267 mod tests {
268     use super::{Block, Fragment, List};
269     use crate::prelude::*;
270     use indoc::indoc;
271 
272     #[test]
renders_fragment() -> Result<()>273     fn renders_fragment() -> Result<()> {
274         let fragment = Fragment("A fragment".into());
275         let mut result = String::new();
276 
277         fragment.render(&mut result)?;
278 
279         verify_that!(result, eq("A fragment"))
280     }
281 
282     #[test]
renders_empty_block() -> Result<()>283     fn renders_empty_block() -> Result<()> {
284         let block = Block::Literal(vec![]);
285         let mut result = String::new();
286 
287         block.render(&mut result, 0, "".into())?;
288 
289         verify_that!(result, eq(""))
290     }
291 
292     #[test]
renders_block_with_one_fragment() -> Result<()>293     fn renders_block_with_one_fragment() -> Result<()> {
294         let block: Block = "A fragment".into();
295         let mut result = String::new();
296 
297         block.render(&mut result, 0, "".into())?;
298 
299         verify_that!(result, eq("A fragment"))
300     }
301 
302     #[test]
renders_block_with_two_fragments() -> Result<()>303     fn renders_block_with_two_fragments() -> Result<()> {
304         let block: Block = "A fragment\nAnother fragment".into();
305         let mut result = String::new();
306 
307         block.render(&mut result, 0, "".into())?;
308 
309         verify_that!(result, eq("A fragment\nAnother fragment"))
310     }
311 
312     #[test]
renders_indented_block() -> Result<()>313     fn renders_indented_block() -> Result<()> {
314         let block: Block = "A fragment\nAnother fragment".into();
315         let mut result = String::new();
316 
317         block.render(&mut result, 2, "".into())?;
318 
319         verify_that!(result, eq("  A fragment\n  Another fragment"))
320     }
321 
322     #[test]
renders_block_with_prefix() -> Result<()>323     fn renders_block_with_prefix() -> Result<()> {
324         let block: Block = "A fragment\nAnother fragment".into();
325         let mut result = String::new();
326 
327         block.render(&mut result, 0, "* ".into())?;
328 
329         verify_that!(result, eq("* A fragment\n  Another fragment"))
330     }
331 
332     #[test]
renders_indented_block_with_prefix() -> Result<()>333     fn renders_indented_block_with_prefix() -> Result<()> {
334         let block: Block = "A fragment\nAnother fragment".into();
335         let mut result = String::new();
336 
337         block.render(&mut result, 2, "* ".into())?;
338 
339         verify_that!(result, eq("  * A fragment\n    Another fragment"))
340     }
341 
342     #[test]
renders_empty_list() -> Result<()>343     fn renders_empty_list() -> Result<()> {
344         let list = list(vec![]);
345         let mut result = String::new();
346 
347         list.render(&mut result, 0)?;
348 
349         verify_that!(result, eq(""))
350     }
351 
352     #[test]
renders_plain_list_with_one_block() -> Result<()>353     fn renders_plain_list_with_one_block() -> Result<()> {
354         let list = list(vec!["A fragment".into()]);
355         let mut result = String::new();
356 
357         list.render(&mut result, 0)?;
358 
359         verify_that!(result, eq("A fragment"))
360     }
361 
362     #[test]
renders_plain_list_with_two_blocks() -> Result<()>363     fn renders_plain_list_with_two_blocks() -> Result<()> {
364         let list = list(vec!["A fragment".into(), "A fragment in a second block".into()]);
365         let mut result = String::new();
366 
367         list.render(&mut result, 0)?;
368 
369         verify_that!(result, eq("A fragment\nA fragment in a second block"))
370     }
371 
372     #[test]
renders_plain_list_with_one_block_with_two_fragments() -> Result<()>373     fn renders_plain_list_with_one_block_with_two_fragments() -> Result<()> {
374         let list = list(vec!["A fragment\nA second fragment".into()]);
375         let mut result = String::new();
376 
377         list.render(&mut result, 0)?;
378 
379         verify_that!(result, eq("A fragment\nA second fragment"))
380     }
381 
382     #[test]
renders_nested_plain_list_with_one_block() -> Result<()>383     fn renders_nested_plain_list_with_one_block() -> Result<()> {
384         let list = list(vec![Block::nested(list(vec!["A fragment".into()]))]);
385         let mut result = String::new();
386 
387         list.render(&mut result, 0)?;
388 
389         verify_that!(result, eq("  A fragment"))
390     }
391 
392     #[test]
renders_nested_plain_list_with_two_blocks() -> Result<()>393     fn renders_nested_plain_list_with_two_blocks() -> Result<()> {
394         let list = list(vec![Block::nested(list(vec![
395             "A fragment".into(),
396             "A fragment in a second block".into(),
397         ]))]);
398         let mut result = String::new();
399 
400         list.render(&mut result, 0)?;
401 
402         verify_that!(result, eq("  A fragment\n  A fragment in a second block"))
403     }
404 
405     #[test]
renders_nested_plain_list_with_one_block_with_two_fragments() -> Result<()>406     fn renders_nested_plain_list_with_one_block_with_two_fragments() -> Result<()> {
407         let list = list(vec![Block::nested(list(vec!["A fragment\nA second fragment".into()]))]);
408         let mut result = String::new();
409 
410         list.render(&mut result, 0)?;
411 
412         verify_that!(result, eq("  A fragment\n  A second fragment"))
413     }
414 
415     #[test]
renders_bulleted_list_with_one_block() -> Result<()>416     fn renders_bulleted_list_with_one_block() -> Result<()> {
417         let list = list(vec!["A fragment".into()]).bullet_list();
418         let mut result = String::new();
419 
420         list.render(&mut result, 0)?;
421 
422         verify_that!(result, eq("* A fragment"))
423     }
424 
425     #[test]
renders_bulleted_list_with_two_blocks() -> Result<()>426     fn renders_bulleted_list_with_two_blocks() -> Result<()> {
427         let list =
428             list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list();
429         let mut result = String::new();
430 
431         list.render(&mut result, 0)?;
432 
433         verify_that!(result, eq("* A fragment\n* A fragment in a second block"))
434     }
435 
436     #[test]
renders_bulleted_list_with_one_block_with_two_fragments() -> Result<()>437     fn renders_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
438         let list = list(vec!["A fragment\nA second fragment".into()]).bullet_list();
439         let mut result = String::new();
440 
441         list.render(&mut result, 0)?;
442 
443         verify_that!(result, eq("* A fragment\n  A second fragment"))
444     }
445 
446     #[test]
renders_nested_bulleted_list_with_one_block() -> Result<()>447     fn renders_nested_bulleted_list_with_one_block() -> Result<()> {
448         let list = list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]);
449         let mut result = String::new();
450 
451         list.render(&mut result, 0)?;
452 
453         verify_that!(result, eq("  * A fragment"))
454     }
455 
456     #[test]
renders_nested_bulleted_list_with_two_blocks() -> Result<()>457     fn renders_nested_bulleted_list_with_two_blocks() -> Result<()> {
458         let list = list(vec![Block::nested(
459             list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list(),
460         )]);
461         let mut result = String::new();
462 
463         list.render(&mut result, 0)?;
464 
465         verify_that!(result, eq("  * A fragment\n  * A fragment in a second block"))
466     }
467 
468     #[test]
renders_nested_bulleted_list_with_one_block_with_two_fragments() -> Result<()>469     fn renders_nested_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
470         let list = list(vec![Block::nested(
471             list(vec!["A fragment\nA second fragment".into()]).bullet_list(),
472         )]);
473         let mut result = String::new();
474 
475         list.render(&mut result, 0)?;
476 
477         verify_that!(result, eq("  * A fragment\n    A second fragment"))
478     }
479 
480     #[test]
renders_enumerated_list_with_one_block() -> Result<()>481     fn renders_enumerated_list_with_one_block() -> Result<()> {
482         let list = list(vec!["A fragment".into()]).enumerate();
483         let mut result = String::new();
484 
485         list.render(&mut result, 0)?;
486 
487         verify_that!(result, eq("0. A fragment"))
488     }
489 
490     #[test]
renders_enumerated_list_with_two_blocks() -> Result<()>491     fn renders_enumerated_list_with_two_blocks() -> Result<()> {
492         let list =
493             list(vec!["A fragment".into(), "A fragment in a second block".into()]).enumerate();
494         let mut result = String::new();
495 
496         list.render(&mut result, 0)?;
497 
498         verify_that!(result, eq("0. A fragment\n1. A fragment in a second block"))
499     }
500 
501     #[test]
renders_enumerated_list_with_one_block_with_two_fragments() -> Result<()>502     fn renders_enumerated_list_with_one_block_with_two_fragments() -> Result<()> {
503         let list = list(vec!["A fragment\nA second fragment".into()]).enumerate();
504         let mut result = String::new();
505 
506         list.render(&mut result, 0)?;
507 
508         verify_that!(result, eq("0. A fragment\n   A second fragment"))
509     }
510 
511     #[test]
aligns_renders_enumerated_list_with_more_than_ten_blocks() -> Result<()>512     fn aligns_renders_enumerated_list_with_more_than_ten_blocks() -> Result<()> {
513         let list =
514             (0..11).map(|i| Block::from(format!("Fragment {i}"))).collect::<List>().enumerate();
515         let mut result = String::new();
516 
517         list.render(&mut result, 0)?;
518 
519         verify_that!(
520             result,
521             eq(indoc! {"
522                  0. Fragment 0
523                  1. Fragment 1
524                  2. Fragment 2
525                  3. Fragment 3
526                  4. Fragment 4
527                  5. Fragment 5
528                  6. Fragment 6
529                  7. Fragment 7
530                  8. Fragment 8
531                  9. Fragment 9
532                 10. Fragment 10"})
533         )
534     }
535 
536     #[test]
renders_fragment_plus_nested_plain_list_with_one_block() -> Result<()>537     fn renders_fragment_plus_nested_plain_list_with_one_block() -> Result<()> {
538         let list =
539             list(vec!["A fragment".into(), Block::nested(list(vec!["Another fragment".into()]))]);
540         let mut result = String::new();
541 
542         list.render(&mut result, 0)?;
543 
544         verify_that!(result, eq("A fragment\n  Another fragment"))
545     }
546 
547     #[test]
renders_double_nested_plain_list_with_one_block() -> Result<()>548     fn renders_double_nested_plain_list_with_one_block() -> Result<()> {
549         let list =
550             list(vec![Block::nested(list(vec![Block::nested(list(vec!["A fragment".into()]))]))]);
551         let mut result = String::new();
552 
553         list.render(&mut result, 0)?;
554 
555         verify_that!(result, eq("    A fragment"))
556     }
557 
558     #[test]
renders_headers_plus_double_nested_plain_list() -> Result<()>559     fn renders_headers_plus_double_nested_plain_list() -> Result<()> {
560         let list = list(vec![
561             "First header".into(),
562             Block::nested(list(vec![
563                 "Second header".into(),
564                 Block::nested(list(vec!["A fragment".into()])),
565             ])),
566         ]);
567         let mut result = String::new();
568 
569         list.render(&mut result, 0)?;
570 
571         verify_that!(result, eq("First header\n  Second header\n    A fragment"))
572     }
573 
574     #[test]
renders_double_nested_bulleted_list() -> Result<()>575     fn renders_double_nested_bulleted_list() -> Result<()> {
576         let list =
577             list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]).bullet_list();
578         let mut result = String::new();
579 
580         list.render(&mut result, 0)?;
581 
582         verify_that!(result, eq("* * A fragment"))
583     }
584 
585     #[test]
renders_nested_enumeration_with_two_blocks_inside_bulleted_list() -> Result<()>586     fn renders_nested_enumeration_with_two_blocks_inside_bulleted_list() -> Result<()> {
587         let list =
588             list(vec![Block::nested(list(vec!["Block 1".into(), "Block 2".into()]).enumerate())])
589                 .bullet_list();
590         let mut result = String::new();
591 
592         list.render(&mut result, 0)?;
593 
594         verify_that!(result, eq("* 0. Block 1\n  1. Block 2"))
595     }
596 
597     #[test]
renders_nested_enumeration_with_block_with_two_fragments_inside_bulleted_list() -> Result<()>598     fn renders_nested_enumeration_with_block_with_two_fragments_inside_bulleted_list() -> Result<()>
599     {
600         let list = list(vec![Block::nested(
601             list(vec!["A fragment\nAnother fragment".into()]).enumerate(),
602         )])
603         .bullet_list();
604         let mut result = String::new();
605 
606         list.render(&mut result, 0)?;
607 
608         verify_that!(result, eq("* 0. A fragment\n     Another fragment"))
609     }
610 
list(blocks: Vec<Block>) -> List611     fn list(blocks: Vec<Block>) -> List {
612         List(blocks, super::Decoration::None)
613     }
614 }
615