• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zbraniecki / icu4x / 9457158389

10 Jun 2024 11:45PM UTC coverage: 75.174% (+0.05%) from 75.121%
9457158389

push

github

web-flow
Add constructing TinyAsciiStr from utf16 (#5025)

Introduces TinyAsciiStr constructors from utf16 and converges on the
consensus from #4931.

---------

Co-authored-by: Robert Bastian <4706271+robertbastian@users.noreply.github.com>

65 of 82 new or added lines in 14 files covered. (79.27%)

3441 existing lines in 141 files now uncovered.

52850 of 70304 relevant lines covered (75.17%)

563298.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.67
/components/locale_core/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 core::str::FromStr;
9
use litemap::LiteMap;
10
use writeable::Writeable;
11

12
use super::Key;
13
use super::Value;
14
use crate::parser::ParseError;
15
use crate::parser::SubtagIterator;
16
use crate::shortvec::ShortBoxSlice;
17

18
/// A list of [`Key`]-[`Value`] pairs representing functional information
19
/// about locale's internationalization preferences.
20
///
21
/// Here are examples of fields used in Unicode:
22
/// - `hc` - Hour Cycle (`h11`, `h12`, `h23`, `h24`)
23
/// - `ca` - Calendar (`buddhist`, `gregory`, ...)
24
/// - `fw` - First Day Of the Week (`sun`, `mon`, `sat`, ...)
25
///
26
/// You can find the full list in [`Unicode BCP 47 U Extension`] section of LDML.
27
///
28
/// [`Unicode BCP 47 U Extension`]: https://unicode.org/reports/tr35/tr35.html#Key_And_Type_Definitions_
29
///
30
/// # Examples
31
///
32
/// Manually build up a [`Keywords`] object:
33
///
34
/// ```
35
/// use icu::locale::extensions::unicode::{key, value, Keywords};
36
///
37
/// let keywords = [(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::locale::{
48
///     extensions::unicode::{key, 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)]
658,128✔
69
pub struct Keywords(LiteMap<Key, Value, ShortBoxSlice<(Key, Value)>>);
329,064✔
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::locale::extensions::unicode::Keywords;
78
    ///
79
    /// assert_eq!(Keywords::new(), Keywords::default());
80
    /// ```
81
    #[inline]
82
    pub const fn new() -> Self {
31,824✔
83
        Self(LiteMap::new())
31,824✔
84
    }
31,824✔
85

86
    /// Create a new list of key-value pairs having exactly one pair, callable in a `const` context.
87
    #[inline]
UNCOV
88
    pub const fn new_single(key: Key, value: Value) -> Self {
×
UNCOV
89
        Self(LiteMap::from_sorted_store_unchecked(
×
90
            ShortBoxSlice::new_single((key, value)),
×
91
        ))
92
    }
×
93

94
    pub(crate) fn try_from_bytes(t: &[u8]) -> Result<Self, ParseError> {
1✔
95
        let mut iter = SubtagIterator::new(t);
1✔
96
        Self::try_from_iter(&mut iter)
1✔
97
    }
1✔
98

99
    /// Returns `true` if there are no keywords.
100
    ///
101
    /// # Examples
102
    ///
103
    /// ```
104
    /// use icu::locale::locale;
105
    /// use icu::locale::Locale;
106
    ///
107
    /// let loc1 = Locale::try_from_bytes(b"und-t-h0-hybrid").unwrap();
108
    /// let loc2 = locale!("und-u-ca-buddhist");
109
    ///
110
    /// assert!(loc1.extensions.unicode.keywords.is_empty());
111
    /// assert!(!loc2.extensions.unicode.keywords.is_empty());
112
    /// ```
113
    pub fn is_empty(&self) -> bool {
165,272✔
114
        self.0.is_empty()
165,272✔
115
    }
165,272✔
116

117
    /// Returns `true` if the list contains a [`Value`] for the specified [`Key`].
118
    ///
119
    ///
120
    /// # Examples
121
    ///
122
    /// ```
123
    /// use icu::locale::extensions::unicode::{key, value, Keywords};
124
    ///
125
    /// let keywords = [(key!("ca"), value!("gregory"))]
126
    ///     .into_iter()
127
    ///     .collect::<Keywords>();
128
    ///
129
    /// assert!(&keywords.contains_key(&key!("ca")));
130
    /// ```
131
    pub fn contains_key<Q>(&self, key: &Q) -> bool
1✔
132
    where
133
        Key: Borrow<Q>,
134
        Q: Ord,
135
    {
136
        self.0.contains_key(key)
1✔
137
    }
1✔
138

139
    /// Returns a reference to the [`Value`] corresponding to the [`Key`].
140
    ///
141
    ///
142
    /// # Examples
143
    ///
144
    /// ```
145
    /// use icu::locale::extensions::unicode::{key, value, Keywords};
146
    ///
147
    /// let keywords = [(key!("ca"), value!("buddhist"))]
148
    ///     .into_iter()
149
    ///     .collect::<Keywords>();
150
    ///
151
    /// assert_eq!(keywords.get(&key!("ca")), Some(&value!("buddhist")));
152
    /// ```
153
    pub fn get<Q>(&self, key: &Q) -> Option<&Value>
1,559✔
154
    where
155
        Key: Borrow<Q>,
156
        Q: Ord,
157
    {
158
        self.0.get(key)
1,559✔
159
    }
1,559✔
160

161
    /// Returns a mutable reference to the [`Value`] corresponding to the [`Key`].
162
    ///
163
    /// Returns `None` if the key doesn't exist or if the key has no value.
164
    ///
165
    /// # Examples
166
    ///
167
    /// ```
168
    /// use icu::locale::extensions::unicode::{key, value, Keywords};
169
    ///
170
    /// let mut keywords = [(key!("ca"), value!("buddhist"))]
171
    ///     .into_iter()
172
    ///     .collect::<Keywords>();
173
    ///
174
    /// if let Some(value) = keywords.get_mut(&key!("ca")) {
175
    ///     *value = value!("gregory");
176
    /// }
177
    /// assert_eq!(keywords.get(&key!("ca")), Some(&value!("gregory")));
178
    /// ```
179
    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Value>
33✔
180
    where
181
        Key: Borrow<Q>,
182
        Q: Ord,
183
    {
184
        self.0.get_mut(key)
33✔
185
    }
33✔
186

187
    /// Sets the specified keyword, returning the old value if it already existed.
188
    ///
189
    /// # Examples
190
    ///
191
    /// ```
192
    /// use icu::locale::extensions::unicode::{key, value};
193
    /// use icu::locale::Locale;
194
    ///
195
    /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12"
196
    ///     .parse()
197
    ///     .expect("valid BCP-47 identifier");
198
    /// let old_value = loc
199
    ///     .extensions
200
    ///     .unicode
201
    ///     .keywords
202
    ///     .set(key!("ca"), value!("japanese"));
203
    ///
204
    /// assert_eq!(old_value, Some(value!("buddhist")));
205
    /// assert_eq!(loc, "und-u-hello-ca-japanese-hc-h12".parse().unwrap());
206
    /// ```
207
    pub fn set(&mut self, key: Key, value: Value) -> Option<Value> {
712✔
208
        self.0.insert(key, value)
712✔
209
    }
712✔
210

211
    /// Removes the specified keyword, returning the old value if it existed.
212
    ///
213
    /// # Examples
214
    ///
215
    /// ```
216
    /// use icu::locale::extensions::unicode::key;
217
    /// use icu::locale::Locale;
218
    ///
219
    /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12"
220
    ///     .parse()
221
    ///     .expect("valid BCP-47 identifier");
222
    /// loc.extensions.unicode.keywords.remove(key!("ca"));
223
    /// assert_eq!(loc, "und-u-hello-hc-h12".parse().unwrap());
224
    /// ```
225
    pub fn remove<Q: Borrow<Key>>(&mut self, key: Q) -> Option<Value> {
6,797✔
226
        self.0.remove(key.borrow())
6,797✔
227
    }
6,797✔
228

229
    /// Clears all Unicode extension keywords, leaving Unicode attributes.
230
    ///
231
    /// Returns the old Unicode extension keywords.
232
    ///
233
    /// # Example
234
    ///
235
    /// ```
236
    /// use icu::locale::Locale;
237
    ///
238
    /// let mut loc: Locale = "und-u-hello-ca-buddhist-hc-h12".parse().unwrap();
239
    /// loc.extensions.unicode.keywords.clear();
240
    /// assert_eq!(loc, "und-u-hello".parse().unwrap());
241
    /// ```
242
    pub fn clear(&mut self) -> Self {
3✔
243
        core::mem::take(self)
3✔
244
    }
3✔
245

246
    /// Retains a subset of keywords as specified by the predicate function.
247
    ///
248
    /// # Examples
249
    ///
250
    /// ```
251
    /// use icu::locale::extensions::unicode::key;
252
    /// use icu::locale::Locale;
253
    ///
254
    /// let mut loc: Locale = "und-u-ca-buddhist-hc-h12-ms-metric".parse().unwrap();
255
    ///
256
    /// loc.extensions
257
    ///     .unicode
258
    ///     .keywords
259
    ///     .retain_by_key(|&k| k == key!("hc"));
260
    /// assert_eq!(loc, "und-u-hc-h12".parse().unwrap());
261
    ///
262
    /// loc.extensions
263
    ///     .unicode
264
    ///     .keywords
265
    ///     .retain_by_key(|&k| k == key!("ms"));
266
    /// assert_eq!(loc, Locale::UND);
267
    /// ```
268
    pub fn retain_by_key<F>(&mut self, mut predicate: F)
5,399✔
269
    where
270
        F: FnMut(&Key) -> bool,
271
    {
272
        self.0.retain(|k, _| predicate(k))
6,219✔
273
    }
5,399✔
274

275
    /// Compare this [`Keywords`] with BCP-47 bytes.
276
    ///
277
    /// The return value is equivalent to what would happen if you first converted this
278
    /// [`Keywords`] to a BCP-47 string and then performed a byte comparison.
279
    ///
280
    /// This function is case-sensitive and results in a *total order*, so it is appropriate for
281
    /// binary search. The only argument producing [`Ordering::Equal`] is `self.to_string()`.
282
    ///
283
    /// # Examples
284
    ///
285
    /// ```
286
    /// use icu::locale::Locale;
287
    /// use std::cmp::Ordering;
288
    ///
289
    /// let bcp47_strings: &[&str] =
290
    ///     &["ca-hebrew", "ca-japanese", "ca-japanese-nu-latn", "nu-latn"];
291
    ///
292
    /// for ab in bcp47_strings.windows(2) {
293
    ///     let a = ab[0];
294
    ///     let b = ab[1];
295
    ///     assert!(a.cmp(b) == Ordering::Less);
296
    ///     let a_kwds = format!("und-u-{}", a)
297
    ///         .parse::<Locale>()
298
    ///         .unwrap()
299
    ///         .extensions
300
    ///         .unicode
301
    ///         .keywords;
302
    ///     assert!(a_kwds.strict_cmp(a.as_bytes()) == Ordering::Equal);
303
    ///     assert!(a_kwds.strict_cmp(b.as_bytes()) == Ordering::Less);
304
    /// }
305
    /// ```
306
    pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
6✔
307
        self.writeable_cmp_bytes(other)
6✔
308
    }
6✔
309

310
    pub(crate) fn try_from_iter(iter: &mut SubtagIterator) -> Result<Self, ParseError> {
621✔
311
        let mut keywords = LiteMap::new();
621✔
312

313
        let mut current_keyword = None;
621✔
314
        let mut current_value = ShortBoxSlice::new();
621✔
315

316
        while let Some(subtag) = iter.peek() {
2,120✔
317
            let slen = subtag.len();
1,561✔
318
            if slen == 2 {
2,333✔
319
                if let Some(kw) = current_keyword.take() {
983✔
320
                    keywords.try_insert(kw, Value::from_short_slice_unchecked(current_value));
212✔
321
                    current_value = ShortBoxSlice::new();
212✔
322
                }
323
                current_keyword = Some(Key::try_from_bytes(subtag)?);
773✔
324
            } else if current_keyword.is_some() {
790✔
325
                match Value::parse_subtag(subtag) {
759✔
326
                    Ok(Some(t)) => current_value.push(t),
707✔
327
                    Ok(None) => {}
328
                    Err(_) => break,
329
                }
330
            } else {
331
                break;
332
            }
333
            iter.next();
1,495✔
334
        }
335

336
        if let Some(kw) = current_keyword.take() {
617✔
337
            keywords.try_insert(kw, Value::from_short_slice_unchecked(current_value));
560✔
338
        }
339

340
        Ok(keywords.into())
60✔
341
    }
522✔
342

343
    /// Produce an ordered iterator over key-value pairs
UNCOV
344
    pub fn iter(&self) -> impl Iterator<Item = (&Key, &Value)> {
×
UNCOV
345
        self.0.iter()
×
UNCOV
346
    }
×
347

348
    pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
11,243✔
349
    where
350
        F: FnMut(&str) -> Result<(), E>,
351
    {
352
        for (k, v) in self.0.iter() {
22,865✔
353
            f(k.as_str())?;
11,622✔
354
            v.for_each_subtag_str(f)?;
22,865✔
355
        }
356
        Ok(())
10,446✔
357
    }
11,243✔
358

359
    /// This needs to be its own method to help with type inference in helpers.rs
360
    #[cfg(test)]
361
    pub(crate) fn from_tuple_vec(v: Vec<(Key, Value)>) -> Self {
2✔
362
        v.into_iter().collect()
2✔
363
    }
2✔
364
}
365

366
impl From<LiteMap<Key, Value, ShortBoxSlice<(Key, Value)>>> for Keywords {
367
    fn from(map: LiteMap<Key, Value, ShortBoxSlice<(Key, Value)>>) -> Self {
632✔
368
        Self(map)
632✔
369
    }
632✔
370
}
371

372
impl FromIterator<(Key, Value)> for Keywords {
373
    fn from_iter<I: IntoIterator<Item = (Key, Value)>>(iter: I) -> Self {
2✔
374
        LiteMap::from_iter(iter).into()
2✔
375
    }
2✔
376
}
377

378
impl FromStr for Keywords {
379
    type Err = ParseError;
380

381
    fn from_str(source: &str) -> Result<Self, Self::Err> {
1✔
382
        Self::try_from_bytes(source.as_bytes())
1✔
383
    }
1✔
384
}
385

386
impl_writeable_for_key_value!(Keywords, "ca", "islamic-civil", "mm", "mm");
387

388
#[cfg(test)]
389
mod tests {
390
    use super::*;
391

392
    #[test]
393
    fn test_keywords_fromstr() {
2✔
394
        let kw: Keywords = "hc-h12".parse().expect("Failed to parse Keywords");
1✔
395
        assert_eq!(kw.to_string(), "hc-h12");
1✔
396
    }
2✔
397
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc