// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). use crate::Writeable; use core::cmp::Ordering; use core::fmt; struct WriteComparator<'a> { code_units: &'a [u8], result: Ordering, } /// This is an infallible impl. Functions always return Ok, not Err. impl fmt::Write for WriteComparator<'_> { #[inline] fn write_str(&mut self, other: &str) -> fmt::Result { if self.result != Ordering::Equal { return Ok(()); } let (this, remainder) = self .code_units .split_at_checked(other.len()) .unwrap_or((self.code_units, &[])); self.code_units = remainder; self.result = this.cmp(other.as_bytes()); Ok(()) } } impl<'a> WriteComparator<'a> { #[inline] fn new(code_units: &'a [u8]) -> Self { Self { code_units, result: Ordering::Equal, } } #[inline] fn finish(self) -> Ordering { if matches!(self.result, Ordering::Equal) && !self.code_units.is_empty() { // Self is longer than Other Ordering::Greater } else { self.result } } } /// Compares the contents of a [`Writeable`] to the given UTF-8 bytes without allocating memory. /// /// For more details, see: [`cmp_str`] pub fn cmp_utf8(writeable: &impl Writeable, other: &[u8]) -> Ordering { let mut wc = WriteComparator::new(other); let _ = writeable.write_to(&mut wc); wc.finish().reverse() } /// Compares the contents of a `Writeable` to the given bytes /// without allocating a String to hold the `Writeable` contents. /// /// This returns a lexicographical comparison, the same as if the Writeable /// were first converted to a String and then compared with `Ord`. For a /// string ordering suitable for display to end users, use a localized /// collation crate, such as `icu_collator`. /// /// # Examples /// /// ``` /// use core::cmp::Ordering; /// use core::fmt; /// use writeable::Writeable; /// /// struct WelcomeMessage<'s> { /// pub name: &'s str, /// } /// /// impl<'s> Writeable for WelcomeMessage<'s> { /// // see impl in Writeable docs /// # fn write_to(&self, sink: &mut W) -> fmt::Result { /// # sink.write_str("Hello, ")?; /// # sink.write_str(self.name)?; /// # sink.write_char('!')?; /// # Ok(()) /// # } /// } /// /// let message = WelcomeMessage { name: "Alice" }; /// let message_str = message.write_to_string(); /// /// assert_eq!(Ordering::Equal, writeable::cmp_str(&message, "Hello, Alice!")); /// /// assert_eq!(Ordering::Greater, writeable::cmp_str(&message, "Alice!")); /// assert_eq!(Ordering::Greater, (*message_str).cmp("Alice!")); /// /// assert_eq!(Ordering::Less, writeable::cmp_str(&message, "Hello, Bob!")); /// assert_eq!(Ordering::Less, (*message_str).cmp("Hello, Bob!")); /// ``` #[inline] pub fn cmp_str(writeable: &impl Writeable, other: &str) -> Ordering { cmp_utf8(writeable, other.as_bytes()) } #[cfg(test)] mod tests { use super::*; use core::fmt::Write; mod data { include!("../tests/data/data.rs"); } #[test] fn test_write_char() { for a in data::KEBAB_CASE_STRINGS { for b in data::KEBAB_CASE_STRINGS { let mut wc = WriteComparator::new(a.as_bytes()); for ch in b.chars() { wc.write_char(ch).unwrap(); } assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}"); } } } #[test] fn test_write_str() { for a in data::KEBAB_CASE_STRINGS { for b in data::KEBAB_CASE_STRINGS { let mut wc = WriteComparator::new(a.as_bytes()); wc.write_str(b).unwrap(); assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}"); } } } #[test] fn test_mixed() { for a in data::KEBAB_CASE_STRINGS { for b in data::KEBAB_CASE_STRINGS { let mut wc = WriteComparator::new(a.as_bytes()); let mut first = true; for substr in b.split('-') { if first { first = false; } else { wc.write_char('-').unwrap(); } wc.write_str(substr).unwrap(); } assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}"); } } } }