From 6b9b987e4fa14c0ae3fb6f1f548cc56c8cd2b72e Mon Sep 17 00:00:00 2001 From: tinger Date: Fri, 23 Aug 2024 12:58:13 +0200 Subject: [PATCH] feat(stdx): Add `stdx::fmt::Separator` for sequence formatting --- crates/typst-test-stdx/src/fmt.rs | 163 ++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/crates/typst-test-stdx/src/fmt.rs b/crates/typst-test-stdx/src/fmt.rs index 26e6a72..1ac9e30 100644 --- a/crates/typst-test-stdx/src/fmt.rs +++ b/crates/typst-test-stdx/src/fmt.rs @@ -1,5 +1,6 @@ //! Helper functions and types for formatting. +use std::cell::RefCell; use std::fmt::Display; /// Types which affect the plurality of a word. Mostly numbers. @@ -110,3 +111,165 @@ impl Display for PluralDisplay<'_> { } } } + +/// Displays a sequence of elements as comma separated list with a final +/// separator. +/// +/// # Examples +/// ``` +/// # use typst_test_stdx::fmt::Separators; +/// assert_eq!( +/// Separators::new(", ", " or ").with(["a", "b", "c"]).to_string(), +/// "a, b or c", +/// ); +/// assert_eq!( +/// Separators::comma_or().with(["a", "b", "c"]).to_string(), +/// "a, b or c", +/// ); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Separators { + separator: &'static str, + terminal_separator: Option<&'static str>, +} + +impl Separators { + /// Creates a new sequence to display. + /// + /// # Examples + /// ``` + /// # use typst_test_stdx::fmt::Separators; + /// assert_eq!( + /// Separators::new("-", None).with(["a", "b", "c"]).to_string(), + /// "a-b-c", + /// ); + /// assert_eq!( + /// Separators::new("-", "/").with(["a", "b", "c"]).to_string(), + /// "a-b/c", + /// ); + /// ``` + pub fn new( + separator: &'static str, + terminal_separator: impl Into>, + ) -> Self { + Self { + separator, + terminal_separator: terminal_separator.into(), + } + } + + /// Creates a new sequence to display using only `, ` as separtor. + /// + /// # Examples + /// ``` + /// # use typst_test_stdx::fmt::Separators; + /// assert_eq!( + /// Separators::comma().with(["a", "b"]).to_string(), + /// "a, b", + /// ); + /// assert_eq!( + /// Separators::comma().with(["a", "b", "c"]).to_string(), + /// "a, b, c", + /// ); + /// ``` + pub fn comma() -> Self { + Self::new(", ", None) + } + + /// Creates a new sequence to display using `, ` and ` or ` as the separtors. + /// + /// # Examples + /// ``` + /// # use typst_test_stdx::fmt::Separators; + /// assert_eq!( + /// Separators::comma_or().with(["a", "b"]).to_string(), + /// "a or b", + /// ); + /// assert_eq!( + /// Separators::comma_or().with(["a", "b", "c"]).to_string(), + /// "a, b or c", + /// ); + /// ``` + pub fn comma_or() -> Self { + Self::new(", ", " or ") + } + + /// Creates a new sequence to display using `, ` and ` and ` as the separtors. + /// + /// # Examples + /// ``` + /// # use typst_test_stdx::fmt::Separators; + /// assert_eq!( + /// Separators::comma_and().with(["a", "b"]).to_string(), + /// "a and b", + /// ); + /// assert_eq!( + /// Separators::comma_and().with(["a", "b", "c"]).to_string(), + /// "a, b and c", + /// ); + /// ``` + pub fn comma_and() -> Self { + Self::new(", ", " and ") + } + + /// Formats th given items with this sequence's separators. + /// + /// # Examples + /// ``` + /// # use typst_test_stdx::fmt::Separators; + /// assert_eq!( + /// Separators::new(", ", " or ").with(["a", "b", "c"]).to_string(), + /// "a, b or c", + /// ); + /// assert_eq!( + /// Separators::comma_or().with(["a", "b", "c"]).to_string(), + /// "a, b or c", + /// ); + /// ``` + pub fn with(self, items: S) -> impl Display + where + S: IntoIterator, + S::IntoIter: ExactSizeIterator, + S::Item: Display, + { + SequenceDisplay { + seq: self, + items: RefCell::new(items.into_iter()), + } + } +} + +struct SequenceDisplay { + seq: Separators, + items: RefCell, +} + +impl Display for SequenceDisplay +where + I: ExactSizeIterator, + I::Item: Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut items = self.items.try_borrow_mut().expect("is not Sync"); + let mut items = items.by_ref().enumerate(); + let len = items.len(); + + if let Some((_, item)) = items.next() { + write!(f, "{item}")?; + } else { + return Ok(()); + } + + for (idx, item) in items { + let sep = if idx == len - 1 { + self.seq.terminal_separator.unwrap_or(self.seq.separator) + } else { + self.seq.separator + }; + + write!(f, "{sep}{item}")?; + } + + Ok(()) + } +}