• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
86     /// Append a new [`List`] in the last element which must be a
87     /// [`Block::Nested`]. Panic if `self` is empty or the last element is
88     /// not [`Block::Nested`].
push_at_end(&mut self, list: List)89     pub(crate) fn push_at_end(&mut self, list: List) {
90         if let Some(Block::Nested(self_list)) = self.0.last_mut() {
91             self_list.push_nested(list);
92         } else {
93             panic!("pushing elements at the end of {self:#?} which last element is not Nested")
94         }
95     }
96 
render_with_prefix( &self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>, ) -> Result97     fn render_with_prefix(
98         &self,
99         f: &mut dyn Write,
100         indentation: usize,
101         prefix: Cow<'static, str>,
102     ) -> Result {
103         if self.0.is_empty() {
104             return Ok(());
105         }
106 
107         let enumeration_padding = self.enumeration_padding();
108 
109         self.0[0].render(
110             f,
111             indentation,
112             self.full_prefix(0, enumeration_padding, &prefix).into(),
113         )?;
114         for (index, block) in self.0[1..].iter().enumerate() {
115             writeln!(f)?;
116             block.render(
117                 f,
118                 indentation + prefix.len(),
119                 self.prefix(index + 1, enumeration_padding),
120             )?;
121         }
122         Ok(())
123     }
124 
full_prefix(&self, index: usize, enumeration_padding: usize, prior_prefix: &str) -> String125     fn full_prefix(&self, index: usize, enumeration_padding: usize, prior_prefix: &str) -> String {
126         format!("{prior_prefix}{}", self.prefix(index, enumeration_padding))
127     }
128 
prefix(&self, index: usize, enumeration_padding: usize) -> Cow<'static, str>129     fn prefix(&self, index: usize, enumeration_padding: usize) -> Cow<'static, str> {
130         match self.1 {
131             Decoration::None => "".into(),
132             Decoration::Bullet => "* ".into(),
133             Decoration::Enumerate => format!("{:>enumeration_padding$}. ", index).into(),
134         }
135     }
136 
enumeration_padding(&self) -> usize137     fn enumeration_padding(&self) -> usize {
138         match self.1 {
139             Decoration::None => 0,
140             Decoration::Bullet => 0,
141             Decoration::Enumerate => {
142                 if self.0.len() > 1 {
143                     ((self.0.len() - 1) as f64).log10().floor() as usize + 1
144                 } else {
145                     // Avoid negative logarithm when there is only 0 or 1 element.
146                     1
147                 }
148             }
149         }
150     }
151 }
152 
153 impl FromIterator<Block> for List {
from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = Block>,154     fn from_iter<T>(iter: T) -> Self
155     where
156         T: IntoIterator<Item = Block>,
157     {
158         Self(iter.into_iter().collect(), Decoration::None)
159     }
160 }
161 
162 impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for List {
from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = ElementT>,163     fn from_iter<T>(iter: T) -> Self
164     where
165         T: IntoIterator<Item = ElementT>,
166     {
167         Self(iter.into_iter().map(|b| b.into().into()).collect(), Decoration::None)
168     }
169 }
170 
171 impl FromIterator<List> for List {
from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = List>,172     fn from_iter<T>(iter: T) -> Self
173     where
174         T: IntoIterator<Item = List>,
175     {
176         Self(iter.into_iter().map(Block::nested).collect(), Decoration::None)
177     }
178 }
179 
180 /// A sequence of [`Fragment`] or a nested [`List`].
181 ///
182 /// This may be rendered with a prefix specified by the [`Decoration`] of the
183 /// containing [`List`]. In this case, all lines are indented to align with the
184 /// first character of the first line of the block.
185 #[derive(Debug)]
186 enum Block {
187     /// A block of text.
188     ///
189     /// Each constituent [`Fragment`] contains one line of text. The lines are
190     /// indented uniformly to the current indentation of this block when
191     /// rendered.
192     Literal(Vec<Fragment>),
193 
194     /// A nested [`List`].
195     ///
196     /// The [`List`] is rendered recursively at the next level of indentation.
197     Nested(List),
198 }
199 
200 impl Block {
nested(inner: List) -> Self201     fn nested(inner: List) -> Self {
202         Self::Nested(inner)
203     }
204 
render(&self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>) -> Result205     fn render(&self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>) -> Result {
206         match self {
207             Self::Literal(fragments) => {
208                 if fragments.is_empty() {
209                     return Ok(());
210                 }
211 
212                 write!(f, "{:indentation$}{prefix}", "")?;
213                 fragments[0].render(f)?;
214                 let block_indentation = indentation + prefix.as_ref().len();
215                 for fragment in &fragments[1..] {
216                     writeln!(f)?;
217                     write!(f, "{:block_indentation$}", "")?;
218                     fragment.render(f)?;
219                 }
220                 Ok(())
221             }
222             Self::Nested(inner) => inner.render_with_prefix(
223                 f,
224                 indentation + INDENTATION_SIZE.saturating_sub(prefix.len()),
225                 prefix,
226             ),
227         }
228     }
229 }
230 
231 impl From<String> for Block {
from(value: String) -> Self232     fn from(value: String) -> Self {
233         Block::Literal(value.lines().map(|v| Fragment(v.to_string().into())).collect())
234     }
235 }
236 
237 impl From<&'static str> for Block {
from(value: &'static str) -> Self238     fn from(value: &'static str) -> Self {
239         Block::Literal(value.lines().map(|v| Fragment(v.into())).collect())
240     }
241 }
242 
243 impl From<Cow<'static, str>> for Block {
from(value: Cow<'static, str>) -> Self244     fn from(value: Cow<'static, str>) -> Self {
245         match value {
246             Cow::Borrowed(value) => value.into(),
247             Cow::Owned(value) => value.into(),
248         }
249     }
250 }
251 
252 /// A string representing one line of a description or match explanation.
253 #[derive(Debug)]
254 struct Fragment(Cow<'static, str>);
255 
256 impl Fragment {
render(&self, f: &mut dyn Write) -> Result257     fn render(&self, f: &mut dyn Write) -> Result {
258         write!(f, "{}", self.0)
259     }
260 }
261 
262 /// The decoration which appears on [`Block`] of a [`List`] when rendered.
263 #[derive(Debug, Default)]
264 enum Decoration {
265     /// No decoration on each [`Block`]. The default.
266     #[default]
267     None,
268 
269     /// Each [`Block`] is preceded by a bullet (`* `).
270     Bullet,
271 
272     /// Each [`Block`] is preceded by its index in the [`List`] (`0. `, `1. `,
273     /// ...).
274     Enumerate,
275 }
276 
277 #[cfg(test)]
278 mod tests {
279     use super::{Block, Fragment, List};
280     use crate::prelude::*;
281     use indoc::indoc;
282 
283     #[test]
renders_fragment() -> Result<()>284     fn renders_fragment() -> Result<()> {
285         let fragment = Fragment("A fragment".into());
286         let mut result = String::new();
287 
288         fragment.render(&mut result)?;
289 
290         verify_that!(result, eq("A fragment"))
291     }
292 
293     #[test]
renders_empty_block() -> Result<()>294     fn renders_empty_block() -> Result<()> {
295         let block = Block::Literal(vec![]);
296         let mut result = String::new();
297 
298         block.render(&mut result, 0, "".into())?;
299 
300         verify_that!(result, eq(""))
301     }
302 
303     #[test]
renders_block_with_one_fragment() -> Result<()>304     fn renders_block_with_one_fragment() -> Result<()> {
305         let block: Block = "A fragment".into();
306         let mut result = String::new();
307 
308         block.render(&mut result, 0, "".into())?;
309 
310         verify_that!(result, eq("A fragment"))
311     }
312 
313     #[test]
renders_block_with_two_fragments() -> Result<()>314     fn renders_block_with_two_fragments() -> Result<()> {
315         let block: Block = "A fragment\nAnother fragment".into();
316         let mut result = String::new();
317 
318         block.render(&mut result, 0, "".into())?;
319 
320         verify_that!(result, eq("A fragment\nAnother fragment"))
321     }
322 
323     #[test]
renders_indented_block() -> Result<()>324     fn renders_indented_block() -> Result<()> {
325         let block: Block = "A fragment\nAnother fragment".into();
326         let mut result = String::new();
327 
328         block.render(&mut result, 2, "".into())?;
329 
330         verify_that!(result, eq("  A fragment\n  Another fragment"))
331     }
332 
333     #[test]
renders_block_with_prefix() -> Result<()>334     fn renders_block_with_prefix() -> Result<()> {
335         let block: Block = "A fragment\nAnother fragment".into();
336         let mut result = String::new();
337 
338         block.render(&mut result, 0, "* ".into())?;
339 
340         verify_that!(result, eq("* A fragment\n  Another fragment"))
341     }
342 
343     #[test]
renders_indented_block_with_prefix() -> Result<()>344     fn renders_indented_block_with_prefix() -> Result<()> {
345         let block: Block = "A fragment\nAnother fragment".into();
346         let mut result = String::new();
347 
348         block.render(&mut result, 2, "* ".into())?;
349 
350         verify_that!(result, eq("  * A fragment\n    Another fragment"))
351     }
352 
353     #[test]
renders_empty_list() -> Result<()>354     fn renders_empty_list() -> Result<()> {
355         let list = list(vec![]);
356         let mut result = String::new();
357 
358         list.render(&mut result, 0)?;
359 
360         verify_that!(result, eq(""))
361     }
362 
363     #[test]
renders_plain_list_with_one_block() -> Result<()>364     fn renders_plain_list_with_one_block() -> Result<()> {
365         let list = list(vec!["A fragment".into()]);
366         let mut result = String::new();
367 
368         list.render(&mut result, 0)?;
369 
370         verify_that!(result, eq("A fragment"))
371     }
372 
373     #[test]
renders_plain_list_with_two_blocks() -> Result<()>374     fn renders_plain_list_with_two_blocks() -> Result<()> {
375         let list = list(vec!["A fragment".into(), "A fragment in a second block".into()]);
376         let mut result = String::new();
377 
378         list.render(&mut result, 0)?;
379 
380         verify_that!(result, eq("A fragment\nA fragment in a second block"))
381     }
382 
383     #[test]
renders_plain_list_with_one_block_with_two_fragments() -> Result<()>384     fn renders_plain_list_with_one_block_with_two_fragments() -> Result<()> {
385         let list = list(vec!["A fragment\nA second fragment".into()]);
386         let mut result = String::new();
387 
388         list.render(&mut result, 0)?;
389 
390         verify_that!(result, eq("A fragment\nA second fragment"))
391     }
392 
393     #[test]
renders_nested_plain_list_with_one_block() -> Result<()>394     fn renders_nested_plain_list_with_one_block() -> Result<()> {
395         let list = list(vec![Block::nested(list(vec!["A fragment".into()]))]);
396         let mut result = String::new();
397 
398         list.render(&mut result, 0)?;
399 
400         verify_that!(result, eq("  A fragment"))
401     }
402 
403     #[test]
renders_nested_plain_list_with_two_blocks() -> Result<()>404     fn renders_nested_plain_list_with_two_blocks() -> Result<()> {
405         let list = list(vec![Block::nested(list(vec![
406             "A fragment".into(),
407             "A fragment in a second block".into(),
408         ]))]);
409         let mut result = String::new();
410 
411         list.render(&mut result, 0)?;
412 
413         verify_that!(result, eq("  A fragment\n  A fragment in a second block"))
414     }
415 
416     #[test]
renders_nested_plain_list_with_one_block_with_two_fragments() -> Result<()>417     fn renders_nested_plain_list_with_one_block_with_two_fragments() -> Result<()> {
418         let list = list(vec![Block::nested(list(vec!["A fragment\nA second fragment".into()]))]);
419         let mut result = String::new();
420 
421         list.render(&mut result, 0)?;
422 
423         verify_that!(result, eq("  A fragment\n  A second fragment"))
424     }
425 
426     #[test]
renders_bulleted_list_with_one_block() -> Result<()>427     fn renders_bulleted_list_with_one_block() -> Result<()> {
428         let list = list(vec!["A fragment".into()]).bullet_list();
429         let mut result = String::new();
430 
431         list.render(&mut result, 0)?;
432 
433         verify_that!(result, eq("* A fragment"))
434     }
435 
436     #[test]
renders_bulleted_list_with_two_blocks() -> Result<()>437     fn renders_bulleted_list_with_two_blocks() -> Result<()> {
438         let list =
439             list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list();
440         let mut result = String::new();
441 
442         list.render(&mut result, 0)?;
443 
444         verify_that!(result, eq("* A fragment\n* A fragment in a second block"))
445     }
446 
447     #[test]
renders_bulleted_list_with_one_block_with_two_fragments() -> Result<()>448     fn renders_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
449         let list = list(vec!["A fragment\nA second fragment".into()]).bullet_list();
450         let mut result = String::new();
451 
452         list.render(&mut result, 0)?;
453 
454         verify_that!(result, eq("* A fragment\n  A second fragment"))
455     }
456 
457     #[test]
renders_nested_bulleted_list_with_one_block() -> Result<()>458     fn renders_nested_bulleted_list_with_one_block() -> Result<()> {
459         let list = list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]);
460         let mut result = String::new();
461 
462         list.render(&mut result, 0)?;
463 
464         verify_that!(result, eq("  * A fragment"))
465     }
466 
467     #[test]
renders_nested_bulleted_list_with_two_blocks() -> Result<()>468     fn renders_nested_bulleted_list_with_two_blocks() -> Result<()> {
469         let list = list(vec![Block::nested(
470             list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list(),
471         )]);
472         let mut result = String::new();
473 
474         list.render(&mut result, 0)?;
475 
476         verify_that!(result, eq("  * A fragment\n  * A fragment in a second block"))
477     }
478 
479     #[test]
renders_nested_bulleted_list_with_one_block_with_two_fragments() -> Result<()>480     fn renders_nested_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
481         let list = list(vec![Block::nested(
482             list(vec!["A fragment\nA second fragment".into()]).bullet_list(),
483         )]);
484         let mut result = String::new();
485 
486         list.render(&mut result, 0)?;
487 
488         verify_that!(result, eq("  * A fragment\n    A second fragment"))
489     }
490 
491     #[test]
renders_enumerated_list_with_one_block() -> Result<()>492     fn renders_enumerated_list_with_one_block() -> Result<()> {
493         let list = list(vec!["A fragment".into()]).enumerate();
494         let mut result = String::new();
495 
496         list.render(&mut result, 0)?;
497 
498         verify_that!(result, eq("0. A fragment"))
499     }
500 
501     #[test]
renders_enumerated_list_with_two_blocks() -> Result<()>502     fn renders_enumerated_list_with_two_blocks() -> Result<()> {
503         let list =
504             list(vec!["A fragment".into(), "A fragment in a second block".into()]).enumerate();
505         let mut result = String::new();
506 
507         list.render(&mut result, 0)?;
508 
509         verify_that!(result, eq("0. A fragment\n1. A fragment in a second block"))
510     }
511 
512     #[test]
renders_enumerated_list_with_one_block_with_two_fragments() -> Result<()>513     fn renders_enumerated_list_with_one_block_with_two_fragments() -> Result<()> {
514         let list = list(vec!["A fragment\nA second fragment".into()]).enumerate();
515         let mut result = String::new();
516 
517         list.render(&mut result, 0)?;
518 
519         verify_that!(result, eq("0. A fragment\n   A second fragment"))
520     }
521 
522     #[test]
aligns_renders_enumerated_list_with_more_than_ten_blocks() -> Result<()>523     fn aligns_renders_enumerated_list_with_more_than_ten_blocks() -> Result<()> {
524         let list =
525             (0..11).map(|i| Block::from(format!("Fragment {i}"))).collect::<List>().enumerate();
526         let mut result = String::new();
527 
528         list.render(&mut result, 0)?;
529 
530         verify_that!(
531             result,
532             eq(indoc! {"
533                  0. Fragment 0
534                  1. Fragment 1
535                  2. Fragment 2
536                  3. Fragment 3
537                  4. Fragment 4
538                  5. Fragment 5
539                  6. Fragment 6
540                  7. Fragment 7
541                  8. Fragment 8
542                  9. Fragment 9
543                 10. Fragment 10"})
544         )
545     }
546 
547     #[test]
renders_fragment_plus_nested_plain_list_with_one_block() -> Result<()>548     fn renders_fragment_plus_nested_plain_list_with_one_block() -> Result<()> {
549         let list =
550             list(vec!["A fragment".into(), Block::nested(list(vec!["Another fragment".into()]))]);
551         let mut result = String::new();
552 
553         list.render(&mut result, 0)?;
554 
555         verify_that!(result, eq("A fragment\n  Another fragment"))
556     }
557 
558     #[test]
renders_double_nested_plain_list_with_one_block() -> Result<()>559     fn renders_double_nested_plain_list_with_one_block() -> Result<()> {
560         let list =
561             list(vec![Block::nested(list(vec![Block::nested(list(vec!["A fragment".into()]))]))]);
562         let mut result = String::new();
563 
564         list.render(&mut result, 0)?;
565 
566         verify_that!(result, eq("    A fragment"))
567     }
568 
569     #[test]
renders_headers_plus_double_nested_plain_list() -> Result<()>570     fn renders_headers_plus_double_nested_plain_list() -> Result<()> {
571         let list = list(vec![
572             "First header".into(),
573             Block::nested(list(vec![
574                 "Second header".into(),
575                 Block::nested(list(vec!["A fragment".into()])),
576             ])),
577         ]);
578         let mut result = String::new();
579 
580         list.render(&mut result, 0)?;
581 
582         verify_that!(result, eq("First header\n  Second header\n    A fragment"))
583     }
584 
585     #[test]
renders_double_nested_bulleted_list() -> Result<()>586     fn renders_double_nested_bulleted_list() -> Result<()> {
587         let list =
588             list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]).bullet_list();
589         let mut result = String::new();
590 
591         list.render(&mut result, 0)?;
592 
593         verify_that!(result, eq("* * A fragment"))
594     }
595 
596     #[test]
renders_nested_enumeration_with_two_blocks_inside_bulleted_list() -> Result<()>597     fn renders_nested_enumeration_with_two_blocks_inside_bulleted_list() -> Result<()> {
598         let list =
599             list(vec![Block::nested(list(vec!["Block 1".into(), "Block 2".into()]).enumerate())])
600                 .bullet_list();
601         let mut result = String::new();
602 
603         list.render(&mut result, 0)?;
604 
605         verify_that!(result, eq("* 0. Block 1\n  1. Block 2"))
606     }
607 
608     #[test]
renders_nested_enumeration_with_block_with_two_fragments_inside_bulleted_list() -> Result<()>609     fn renders_nested_enumeration_with_block_with_two_fragments_inside_bulleted_list() -> Result<()>
610     {
611         let list = list(vec![Block::nested(
612             list(vec!["A fragment\nAnother fragment".into()]).enumerate(),
613         )])
614         .bullet_list();
615         let mut result = String::new();
616 
617         list.render(&mut result, 0)?;
618 
619         verify_that!(result, eq("* 0. A fragment\n     Another fragment"))
620     }
621 
list(blocks: Vec<Block>) -> List622     fn list(blocks: Vec<Block>) -> List {
623         List(blocks, super::Decoration::None)
624     }
625 }
626