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