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