]> git.proxmox.com Git - rustc.git/blob - vendor/icu_locid/src/extensions/unicode/keywords.rs
New upstream version 1.69.0+dfsg1
[rustc.git] / vendor / icu_locid / src / extensions / unicode / keywords.rs
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 core::borrow::Borrow;
6 use core::cmp::Ordering;
7 use core::iter::FromIterator;
8 use litemap::LiteMap;
9
10 use super::Key;
11 use super::Value;
12 use crate::helpers::ShortVec;
13 use crate::ordering::SubtagOrderingResult;
14
15 /// A list of [`Key`]-[`Value`] pairs representing functional information
16 /// about locale's internationnalization preferences.
17 ///
18 /// Here are examples of fields used in Unicode:
19 /// - `hc` - Hour Cycle (`h11`, `h12`, `h23`, `h24`)
20 /// - `ca` - Calendar (`buddhist`, `gregory`, ...)
21 /// - `fw` - First Day Of the Week (`sun`, `mon`, `sat`, ...)
22 ///
23 /// You can find the full list in [`Unicode BCP 47 U Extension`] section of LDML.
24 ///
25 /// [`Unicode BCP 47 U Extension`]: https://unicode.org/reports/tr35/tr35.html#Key_And_Type_Definitions_
26 ///
27 /// # Examples
28 ///
29 /// Manually build up a [`Keywords`] object:
30 ///
31 /// ```
32 /// use icu::locid::{
33 /// extensions::unicode::Keywords, extensions_unicode_key as key,
34 /// extensions_unicode_value as value, locale,
35 /// };
36 ///
37 /// let keywords = vec![(key!("hc"), value!("h23"))]
38 /// .into_iter()
39 /// .collect::<Keywords>();
40 ///
41 /// assert_eq!(&keywords.to_string(), "hc-h23");
42 /// ```
43 ///
44 /// Access a [`Keywords`] object from a [`Locale`]:
45 ///
46 /// ```
47 /// use icu::locid::{
48 /// extensions_unicode_key as key, extensions_unicode_value as value,
49 /// Locale,
50 /// };
51 ///
52 /// let loc: Locale = "und-u-hc-h23-kc-true".parse().expect("Valid BCP-47");
53 ///
54 /// assert_eq!(loc.extensions.unicode.keywords.get(&key!("ca")), None);
55 /// assert_eq!(
56 /// loc.extensions.unicode.keywords.get(&key!("hc")),
57 /// Some(&value!("h23"))
58 /// );
59 /// assert_eq!(
60 /// loc.extensions.unicode.keywords.get(&key!("kc")),
61 /// Some(&value!("true"))
62 /// );
63 ///
64 /// assert_eq!(loc.extensions.unicode.keywords.to_string(), "hc-h23-kc");
65 /// ```
66 ///
67 /// [`Locale`]: crate::Locale
68 #[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
69 pub struct Keywords(LiteMap<Key, Value, ShortVec<(Key, Value)>>);
70
71 impl Keywords {
72 /// Returns a new empty list of key-value pairs. Same as [`default()`](Default::default()), but is `const`.
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// use icu::locid::extensions::unicode::Keywords;
78 ///
79 /// assert_eq!(Keywords::new(), Keywords::default());
80 /// ```
81 #[inline]
82 pub const fn new() -> Self {
83 Self(LiteMap::new())
84 }
85
86 /// Create a new list of key-value pairs having exactly one pair, callable in a `const` context.
87 #[inline]
88 pub const fn new_single(key: Key, value: Value) -> Self {
89 Self(LiteMap::from_sorted_store_unchecked(ShortVec::new_single(
90 (key, value),
91 )))
92 }
93
94 /// Returns `true` if there are no keywords.
95 ///
96 /// # Examples
97 ///
98 /// ```
99 /// use icu::locid::extensions::unicode::Keywords;
100 /// use icu::locid::locale;
101 /// use icu::locid::Locale;
102 ///
103 /// let loc1 = Locale::try_from_bytes(b"und-t-h0-hybrid").unwrap();
104 /// let loc2 = locale!("und-u-ca-buddhist");
105 ///
106 /// assert!(loc1.extensions.unicode.keywords.is_empty());
107 /// assert!(!loc2.extensions.unicode.keywords.is_empty());
108 /// ```
109 pub fn is_empty(&self) -> bool {
110 self.0.is_empty()
111 }
112
113 /// Returns `true` if the list contains a [`Value`] for the specified [`Key`].
114 ///
115 ///
116 /// # Examples
117 ///
118 /// ```
119 /// use icu::locid::{
120 /// extensions::unicode::Keywords, extensions_unicode_key as key,
121 /// extensions_unicode_value as value,
122 /// };
123 ///
124 /// let keywords = vec![(key!("ca"), value!("gregory"))]
125 /// .into_iter()
126 /// .collect::<Keywords>();
127 ///
128 /// assert!(&keywords.contains_key(&key!("ca")));
129 /// ```
130 pub fn contains_key<Q>(&self, key: &Q) -> bool
131 where
132 Key: Borrow<Q>,
133 Q: Ord,
134 {
135 self.0.contains_key(key)
136 }
137
138 /// Returns a reference to the [`Value`] corresponding to the [`Key`].
139 ///
140 ///
141 /// # Examples
142 ///
143 /// ```
144 /// use icu::locid::{
145 /// extensions::unicode::Keywords, extensions_unicode_key as key,
146 /// extensions_unicode_value as value,
147 /// };
148 ///
149 /// let keywords = vec![(key!("ca"), value!("buddhist"))]
150 /// .into_iter()
151 /// .collect::<Keywords>();
152 ///
153 /// assert_eq!(keywords.get(&key!("ca")), Some(&value!("buddhist")));
154 /// ```
155 pub fn get<Q>(&self, key: &Q) -> Option<&Value>
156 where
157 Key: Borrow<Q>,
158 Q: Ord,
159 {
160 self.0.get(key)
161 }
162
163 /// Returns a mutable reference to the [`Value`] corresponding to the [`Key`].
164 ///
165 /// Returns `None` if the key doesn't exist or if the key has no value.
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// use icu::locid::{
171 /// extensions::unicode::Keywords, extensions_unicode_key as key,
172 /// extensions_unicode_value as value,
173 /// };
174 ///
175 /// let mut keywords = vec![(key!("ca"), value!("buddhist"))]
176 /// .into_iter()
177 /// .collect::<Keywords>();
178 ///
179 /// if let Some(value) = keywords.get_mut(&key!("ca")) {
180 /// *value = value!("gregory");
181 /// }
182 /// assert_eq!(keywords.get(&key!("ca")), Some(&value!("gregory")));
183 /// ```
184 pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Value>
185 where
186 Key: Borrow<Q>,
187 Q: Ord,
188 {
189 self.0.get_mut(key)
190 }
191
192 /// Sets the specified keyword, returning the old value if it already existed.
193 ///
194 /// # Examples
195 ///
196 /// ```
197 /// use icu::locid::extensions::unicode::Key;
198 /// use icu::locid::extensions::unicode::Value;
199 /// use icu::locid::Locale;
200 /// use icu::locid::{
201 /// extensions_unicode_key as key, extensions_unicode_value as value,
202 /// };
203 ///
204 /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12"
205 /// .parse()
206 /// .expect("valid BCP-47 identifier");
207 /// let old_value = loc
208 /// .extensions
209 /// .unicode
210 /// .keywords
211 /// .set(key!("ca"), value!("japanese"));
212 ///
213 /// assert_eq!(old_value, Some(value!("buddhist")));
214 /// assert_eq!(loc, "und-u-hello-ca-japanese-hc-h12".parse().unwrap());
215 /// ```
216 pub fn set(&mut self, key: Key, value: Value) -> Option<Value> {
217 self.0.insert(key, value)
218 }
219
220 /// Removes the specified keyword, returning the old value if it existed.
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// use icu::locid::extensions::unicode::Key;
226 /// use icu::locid::extensions_unicode_key as key;
227 /// use icu::locid::Locale;
228 ///
229 /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12"
230 /// .parse()
231 /// .expect("valid BCP-47 identifier");
232 /// loc.extensions.unicode.keywords.remove(key!("ca"));
233 /// assert_eq!(loc, "und-u-hello-hc-h12".parse().unwrap());
234 /// ```
235 pub fn remove<Q: Borrow<Key>>(&mut self, key: Q) -> Option<Value> {
236 self.0.remove(key.borrow())
237 }
238
239 /// Clears all Unicode extension keywords, leaving Unicode attributes.
240 ///
241 /// Returns the old Unicode extension keywords.
242 ///
243 /// # Example
244 ///
245 /// ```
246 /// use icu::locid::Locale;
247 ///
248 /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12".parse().unwrap();
249 /// loc.extensions.unicode.keywords.clear();
250 /// assert_eq!(loc, "und-u-hello".parse().unwrap());
251 /// ```
252 pub fn clear(&mut self) -> Self {
253 core::mem::take(self)
254 }
255
256 /// Retains a subset of keywords as specified by the predicate function.
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use icu::locid::extensions_unicode_key as key;
262 /// use icu::locid::Locale;
263 ///
264 /// let mut loc: Locale = "und-u-ca-buddhist-hc-h12-ms-metric".parse().unwrap();
265 ///
266 /// loc.extensions
267 /// .unicode
268 /// .keywords
269 /// .retain_by_key(|&k| k == key!("hc"));
270 /// assert_eq!(loc, "und-u-hc-h12".parse().unwrap());
271 ///
272 /// loc.extensions
273 /// .unicode
274 /// .keywords
275 /// .retain_by_key(|&k| k == key!("ms"));
276 /// assert_eq!(loc, Locale::UND);
277 /// ```
278 pub fn retain_by_key<F>(&mut self, mut predicate: F)
279 where
280 F: FnMut(&Key) -> bool,
281 {
282 self.0.retain(|k, _| predicate(k))
283 }
284
285 /// Compare this [`Keywords`] with BCP-47 bytes.
286 ///
287 /// The return value is equivalent to what would happen if you first converted this
288 /// [`Keywords`] to a BCP-47 string and then performed a byte comparison.
289 ///
290 /// This function is case-sensitive and results in a *total order*, so it is appropriate for
291 /// binary search. The only argument producing [`Ordering::Equal`] is `self.to_string()`.
292 ///
293 /// # Examples
294 ///
295 /// ```
296 /// use icu::locid::extensions::unicode::Keywords;
297 /// use icu::locid::Locale;
298 /// use std::cmp::Ordering;
299 ///
300 /// let bcp47_strings: &[&str] =
301 /// &["ca-hebrew", "ca-japanese", "ca-japanese-nu-latn", "nu-latn"];
302 ///
303 /// for ab in bcp47_strings.windows(2) {
304 /// let a = ab[0];
305 /// let b = ab[1];
306 /// assert!(a.cmp(b) == Ordering::Less);
307 /// let a_kwds = format!("und-u-{}", a)
308 /// .parse::<Locale>()
309 /// .unwrap()
310 /// .extensions
311 /// .unicode
312 /// .keywords;
313 /// assert!(a_kwds.strict_cmp(a.as_bytes()) == Ordering::Equal);
314 /// assert!(a_kwds.strict_cmp(b.as_bytes()) == Ordering::Less);
315 /// }
316 /// ```
317 pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
318 self.strict_cmp_iter(other.split(|b| *b == b'-')).end()
319 }
320
321 /// Compare this [`Keywords`] with an iterator of BCP-47 subtags.
322 ///
323 /// This function has the same equality semantics as [`Keywords::strict_cmp`]. It is intended as
324 /// a more modular version that allows multiple subtag iterators to be chained together.
325 ///
326 /// For an additional example, see [`SubtagOrderingResult`].
327 ///
328 /// # Examples
329 ///
330 /// ```
331 /// use icu::locid::extensions::unicode::Keywords;
332 /// use icu::locid::locale;
333 /// use std::cmp::Ordering;
334 ///
335 /// let subtags: &[&[u8]] = &[b"ca", b"buddhist"];
336 ///
337 /// let kwds = locale!("und-u-ca-buddhist").extensions.unicode.keywords;
338 /// assert_eq!(
339 /// Ordering::Equal,
340 /// kwds.strict_cmp_iter(subtags.iter().copied()).end()
341 /// );
342 ///
343 /// let kwds = locale!("und").extensions.unicode.keywords;
344 /// assert_eq!(
345 /// Ordering::Less,
346 /// kwds.strict_cmp_iter(subtags.iter().copied()).end()
347 /// );
348 ///
349 /// let kwds = locale!("und-u-nu-latn").extensions.unicode.keywords;
350 /// assert_eq!(
351 /// Ordering::Greater,
352 /// kwds.strict_cmp_iter(subtags.iter().copied()).end()
353 /// );
354 /// ```
355 pub fn strict_cmp_iter<'l, I>(&self, mut subtags: I) -> SubtagOrderingResult<I>
356 where
357 I: Iterator<Item = &'l [u8]>,
358 {
359 let r = self.for_each_subtag_str(&mut |subtag| {
360 if let Some(other) = subtags.next() {
361 match subtag.as_bytes().cmp(other) {
362 Ordering::Equal => Ok(()),
363 not_equal => Err(not_equal),
364 }
365 } else {
366 Err(Ordering::Greater)
367 }
368 });
369 match r {
370 Ok(_) => SubtagOrderingResult::Subtags(subtags),
371 Err(o) => SubtagOrderingResult::Ordering(o),
372 }
373 }
374
375 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
376 where
377 F: FnMut(&str) -> Result<(), E>,
378 {
379 for (k, v) in self.0.iter() {
380 f(k.as_str())?;
381 v.for_each_subtag_str(f)?;
382 }
383 Ok(())
384 }
385
386 /// This needs to be its own method to help with type inference in helpers.rs
387 #[cfg(test)]
388 pub(crate) fn from_tuple_vec(v: Vec<(Key, Value)>) -> Self {
389 v.into_iter().collect()
390 }
391 }
392
393 impl From<LiteMap<Key, Value, ShortVec<(Key, Value)>>> for Keywords {
394 fn from(map: LiteMap<Key, Value, ShortVec<(Key, Value)>>) -> Self {
395 Self(map)
396 }
397 }
398
399 impl FromIterator<(Key, Value)> for Keywords {
400 fn from_iter<I: IntoIterator<Item = (Key, Value)>>(iter: I) -> Self {
401 LiteMap::from_iter(iter).into()
402 }
403 }
404
405 impl_writeable_for_key_value!(Keywords, "ca", "islamic-civil", "mm", "mm");