use std::{
borrow::Cow,
fmt::{Display, Formatter, Result},
};
use crate::internal::description_renderer::{List, INDENTATION_SIZE};
#[derive(Debug, Default)]
pub struct Description {
elements: List,
initial_indentation: usize,
is_conjunction: bool,
is_disjunction: bool,
}
impl Description {
pub fn new() -> Self {
Default::default()
}
pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
self.elements.push_literal(text.into());
self
}
pub fn nested(mut self, inner: Description) -> Self {
self.elements.push_nested(inner.elements);
self
}
pub fn collect(self, inner: impl IntoIterator<Item = Description>) -> Self {
inner.into_iter().fold(self, |outer, inner| outer.nested(inner))
}
pub fn indent(self) -> Self {
Self { initial_indentation: INDENTATION_SIZE, ..self }
}
pub fn bullet_list(self) -> Self {
Self { elements: self.elements.bullet_list(), ..self }
}
pub fn enumerate(self) -> Self {
Self { elements: self.elements.enumerate(), ..self }
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub(crate) fn push_in_last_nested(mut self, inner: Description) -> Self {
self.elements.push_at_end(inner.elements);
self
}
pub(crate) fn conjunction_description(self) -> Self {
Self { is_conjunction: true, ..self }
}
pub(crate) fn is_conjunction_description(&self) -> bool {
self.is_conjunction
}
pub(crate) fn disjunction_description(self) -> Self {
Self { is_disjunction: true, ..self }
}
pub(crate) fn is_disjunction_description(&self) -> bool {
self.is_disjunction
}
}
impl Display for Description {
fn fmt(&self, f: &mut Formatter) -> Result {
self.elements.render(f, self.initial_indentation)
}
}
impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for Description {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = ElementT>,
{
Self { elements: iter.into_iter().map(ElementT::into).collect(), ..Default::default() }
}
}
impl FromIterator<Description> for Description {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = Description>,
{
Self { elements: iter.into_iter().map(|s| s.elements).collect(), ..Default::default() }
}
}
impl<T: Into<Cow<'static, str>>> From<T> for Description {
fn from(value: T) -> Self {
let mut elements = List::default();
elements.push_literal(value.into());
Self { elements, ..Default::default() }
}
}
#[cfg(test)]
mod tests {
use super::Description;
use crate::prelude::*;
use crate::Result;
use indoc::indoc;
#[test]
fn renders_single_fragment() -> Result<()> {
let description: Description = "A B C".into();
verify_that!(description, displays_as(eq("A B C")))
}
#[test]
fn renders_two_fragments() -> Result<()> {
let description =
["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>();
verify_that!(description, displays_as(eq("A B C\nD E F")))
}
#[test]
fn nested_description_is_indented() -> Result<()> {
let description = Description::new()
.text("Header")
.nested(["A B C".to_string()].into_iter().collect::<Description>());
verify_that!(description, displays_as(eq("Header\n A B C")))
}
#[test]
fn nested_description_indents_two_elements() -> Result<()> {
let description = Description::new().text("Header").nested(
["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>(),
);
verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
}
#[test]
fn nested_description_indents_one_element_on_two_lines() -> Result<()> {
let description = Description::new().text("Header").nested("A B C\nD E F".into());
verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
}
#[test]
fn single_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
let description = Description::new().text("A B C").bullet_list();
verify_that!(description, displays_as(eq("* A B C")))
}
#[test]
fn single_nested_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
let description = Description::new().nested("A B C".into()).bullet_list();
verify_that!(description, displays_as(eq("* A B C")))
}
#[test]
fn two_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
let description = Description::new().text("A B C").text("D E F").bullet_list();
verify_that!(description, displays_as(eq("* A B C\n* D E F")))
}
#[test]
fn two_nested_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
let description =
Description::new().nested("A B C".into()).nested("D E F".into()).bullet_list();
verify_that!(description, displays_as(eq("* A B C\n* D E F")))
}
#[test]
fn single_fragment_with_more_than_one_line_renders_with_one_bullet() -> Result<()> {
let description = Description::new().text("A B C\nD E F").bullet_list();
verify_that!(description, displays_as(eq("* A B C\n D E F")))
}
#[test]
fn single_fragment_renders_with_enumeration_when_enumerate_enabled() -> Result<()> {
let description = Description::new().text("A B C").enumerate();
verify_that!(description, displays_as(eq("0. A B C")))
}
#[test]
fn two_fragments_render_with_enumeration_when_enumerate_enabled() -> Result<()> {
let description = Description::new().text("A B C").text("D E F").enumerate();
verify_that!(description, displays_as(eq("0. A B C\n1. D E F")))
}
#[test]
fn single_fragment_with_two_lines_renders_with_one_enumeration_label() -> Result<()> {
let description = Description::new().text("A B C\nD E F").enumerate();
verify_that!(description, displays_as(eq("0. A B C\n D E F")))
}
#[test]
fn multi_digit_enumeration_renders_with_correct_offset() -> Result<()> {
let description = ["A B C\nD E F"; 11]
.into_iter()
.map(str::to_string)
.collect::<Description>()
.enumerate();
verify_that!(
description,
displays_as(eq(indoc!(
"
0. A B C
D E F
1. A B C
D E F
2. A B C
D E F
3. A B C
D E F
4. A B C
D E F
5. A B C
D E F
6. A B C
D E F
7. A B C
D E F
8. A B C
D E F
9. A B C
D E F
10. A B C
D E F"
)))
)
}
#[test]
fn new_is_empty() -> Result<()> {
verify_that!(Description::new(), predicate(Description::is_empty))
}
#[test]
fn text_is_not_empty() -> Result<()> {
verify_that!(Description::new().text("something"), not(predicate(Description::is_empty)))
}
#[test]
fn new_zero_length() -> Result<()> {
verify_that!(Description::new().len(), eq(0))
}
#[test]
fn text_one_length() -> Result<()> {
verify_that!(Description::new().text("something").len(), eq(1))
}
}