• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // This file is part of ICU4X. For terms of use, please see the file
2 // called LICENSE at the top level of the ICU4X source tree
3 // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4 
5 use crate::Writeable;
6 use core::cmp::Ordering;
7 use core::fmt;
8 
9 struct WriteComparator<'a> {
10     code_units: &'a [u8],
11     result: Ordering,
12 }
13 
14 /// This is an infallible impl. Functions always return Ok, not Err.
15 impl fmt::Write for WriteComparator<'_> {
16     #[inline]
write_str(&mut self, other: &str) -> fmt::Result17     fn write_str(&mut self, other: &str) -> fmt::Result {
18         if self.result != Ordering::Equal {
19             return Ok(());
20         }
21         let (this, remainder) = self
22             .code_units
23             .split_at_checked(other.len())
24             .unwrap_or((self.code_units, &[]));
25         self.code_units = remainder;
26         self.result = this.cmp(other.as_bytes());
27         Ok(())
28     }
29 }
30 
31 impl<'a> WriteComparator<'a> {
32     #[inline]
new(code_units: &'a [u8]) -> Self33     fn new(code_units: &'a [u8]) -> Self {
34         Self {
35             code_units,
36             result: Ordering::Equal,
37         }
38     }
39 
40     #[inline]
finish(self) -> Ordering41     fn finish(self) -> Ordering {
42         if matches!(self.result, Ordering::Equal) && !self.code_units.is_empty() {
43             // Self is longer than Other
44             Ordering::Greater
45         } else {
46             self.result
47         }
48     }
49 }
50 
51 /// Compares the contents of a [`Writeable`] to the given UTF-8 bytes without allocating memory.
52 ///
53 /// For more details, see: [`cmp_str`]
cmp_utf8(writeable: &impl Writeable, other: &[u8]) -> Ordering54 pub fn cmp_utf8(writeable: &impl Writeable, other: &[u8]) -> Ordering {
55     let mut wc = WriteComparator::new(other);
56     let _ = writeable.write_to(&mut wc);
57     wc.finish().reverse()
58 }
59 
60 /// Compares the contents of a `Writeable` to the given bytes
61 /// without allocating a String to hold the `Writeable` contents.
62 ///
63 /// This returns a lexicographical comparison, the same as if the Writeable
64 /// were first converted to a String and then compared with `Ord`. For a
65 /// string ordering suitable for display to end users, use a localized
66 /// collation crate, such as `icu_collator`.
67 ///
68 /// # Examples
69 ///
70 /// ```
71 /// use core::cmp::Ordering;
72 /// use core::fmt;
73 /// use writeable::Writeable;
74 ///
75 /// struct WelcomeMessage<'s> {
76 ///     pub name: &'s str,
77 /// }
78 ///
79 /// impl<'s> Writeable for WelcomeMessage<'s> {
80 ///     // see impl in Writeable docs
81 /// #    fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
82 /// #        sink.write_str("Hello, ")?;
83 /// #        sink.write_str(self.name)?;
84 /// #        sink.write_char('!')?;
85 /// #        Ok(())
86 /// #    }
87 /// }
88 ///
89 /// let message = WelcomeMessage { name: "Alice" };
90 /// let message_str = message.write_to_string();
91 ///
92 /// assert_eq!(Ordering::Equal, writeable::cmp_str(&message, "Hello, Alice!"));
93 ///
94 /// assert_eq!(Ordering::Greater, writeable::cmp_str(&message, "Alice!"));
95 /// assert_eq!(Ordering::Greater, (*message_str).cmp("Alice!"));
96 ///
97 /// assert_eq!(Ordering::Less, writeable::cmp_str(&message, "Hello, Bob!"));
98 /// assert_eq!(Ordering::Less, (*message_str).cmp("Hello, Bob!"));
99 /// ```
100 #[inline]
cmp_str(writeable: &impl Writeable, other: &str) -> Ordering101 pub fn cmp_str(writeable: &impl Writeable, other: &str) -> Ordering {
102     cmp_utf8(writeable, other.as_bytes())
103 }
104 
105 #[cfg(test)]
106 mod tests {
107     use super::*;
108     use core::fmt::Write;
109 
110     mod data {
111         include!("../tests/data/data.rs");
112     }
113 
114     #[test]
test_write_char()115     fn test_write_char() {
116         for a in data::KEBAB_CASE_STRINGS {
117             for b in data::KEBAB_CASE_STRINGS {
118                 let mut wc = WriteComparator::new(a.as_bytes());
119                 for ch in b.chars() {
120                     wc.write_char(ch).unwrap();
121                 }
122                 assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}");
123             }
124         }
125     }
126 
127     #[test]
test_write_str()128     fn test_write_str() {
129         for a in data::KEBAB_CASE_STRINGS {
130             for b in data::KEBAB_CASE_STRINGS {
131                 let mut wc = WriteComparator::new(a.as_bytes());
132                 wc.write_str(b).unwrap();
133                 assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}");
134             }
135         }
136     }
137 
138     #[test]
test_mixed()139     fn test_mixed() {
140         for a in data::KEBAB_CASE_STRINGS {
141             for b in data::KEBAB_CASE_STRINGS {
142                 let mut wc = WriteComparator::new(a.as_bytes());
143                 let mut first = true;
144                 for substr in b.split('-') {
145                     if first {
146                         first = false;
147                     } else {
148                         wc.write_char('-').unwrap();
149                     }
150                     wc.write_str(substr).unwrap();
151                 }
152                 assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}");
153             }
154         }
155     }
156 }
157