Skip to main content

icu_locale_core/extensions/unicode/
value.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
5use crate::parser::ParseError;
6use crate::parser::SubtagIterator;
7use crate::shortvec::{ShortBoxSlice, ShortBoxSliceIntoIter};
8use crate::subtags::{subtag, Subtag};
9#[cfg(feature = "alloc")]
10use alloc::vec::Vec;
11#[cfg(feature = "alloc")]
12use core::str::FromStr;
13
14/// A value used in a list of [`Keywords`](super::Keywords).
15///
16/// The value has to be a sequence of one or more alphanumerical strings
17/// separated by `-`.
18/// Each part of the sequence has to be no shorter than three characters and no
19/// longer than 8.
20///
21///
22/// # Examples
23///
24/// ```
25/// use icu::locale::extensions::unicode::{value, Value};
26/// use writeable::assert_writeable_eq;
27///
28/// assert_writeable_eq!(value!("gregory"), "gregory");
29/// assert_writeable_eq!(
30///     "islamic-civil".parse::<Value>().unwrap(),
31///     "islamic-civil"
32/// );
33///
34/// // The value "true" has the special, empty string representation
35/// assert_eq!(value!("true").to_string(), "");
36/// ```
37#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Value {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Value",
            &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Value {
    #[inline]
    fn eq(&self, other: &Value) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Value {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<ShortBoxSlice<Subtag>>;
    }
}Eq, #[automatically_derived]
impl ::core::clone::Clone for Value {
    #[inline]
    fn clone(&self) -> Value { Value(::core::clone::Clone::clone(&self.0)) }
}Clone, #[automatically_derived]
impl ::core::hash::Hash for Value {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash, #[automatically_derived]
impl ::core::cmp::PartialOrd for Value {
    #[inline]
    fn partial_cmp(&self, other: &Value)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0)
    }
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for Value {
    #[inline]
    fn cmp(&self, other: &Value) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}Ord, #[automatically_derived]
impl ::core::default::Default for Value {
    #[inline]
    fn default() -> Value { Value(::core::default::Default::default()) }
}Default)]
38pub struct Value(ShortBoxSlice<Subtag>);
39
40const TRUE_VALUE: Subtag = const {
        use crate::subtags::Subtag;
        match Subtag::try_from_utf8("true".as_bytes()) {
            Ok(r) => r,
            _ => {
                ::core::panicking::panic_fmt(format_args!("Invalid subtags::Subtag: true"));
            }
        }
    }subtag!("true");
41
42impl Value {
43    /// A constructor which str slice, parses it and
44    /// produces a well-formed [`Value`].
45    ///
46    /// # Examples
47    ///
48    /// ```
49    /// use icu::locale::extensions::unicode::Value;
50    ///
51    /// Value::try_from_str("buddhist").expect("Parsing failed.");
52    /// ```
53    ///
54    /// # `alloc` Cargo feature
55    ///
56    /// Without the `alloc` Cargo feature, this only supports parsing
57    /// up to two (non-`true`) subtags, and will return an error for
58    /// longer strings.
59    #[inline]
60    pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
61        Self::try_from_utf8(s.as_bytes())
62    }
63
64    /// See [`Self::try_from_str`]
65    pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
66        let mut v = ShortBoxSlice::new();
67
68        if !code_units.is_empty() {
69            for chunk in SubtagIterator::new(code_units) {
70                let subtag = Subtag::try_from_utf8(chunk)?;
71                if subtag != TRUE_VALUE {
72                    #[cfg(feature = "alloc")]
73                    v.push(subtag);
74                    #[cfg(not(feature = "alloc"))]
75                    if v.is_empty() {
76                        v = ShortBoxSlice::new_single(subtag);
77                    } else if let &[prev] = &*v {
78                        v = ShortBoxSlice::new_double(prev, subtag);
79                    } else {
80                        return Err(ParseError::InvalidSubtag);
81                    }
82                }
83            }
84        }
85        Ok(Self(v))
86    }
87
88    /// Returns a reference to a single [`Subtag`] if the [`Value`] contains exactly one
89    /// subtag, or `None` otherwise.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// use core::str::FromStr;
95    /// use icu::locale::extensions::unicode::Value;
96    ///
97    /// let value1 = Value::from_str("foo").expect("failed to parse a Value");
98    /// let value2 = Value::from_str("foo-bar").expect("failed to parse a Value");
99    ///
100    /// assert!(value1.as_single_subtag().is_some());
101    /// assert!(value2.as_single_subtag().is_none());
102    /// ```
103    pub const fn as_single_subtag(&self) -> Option<&Subtag> {
104        self.0.single()
105    }
106
107    /// Destructs into a single [`Subtag`] if the [`Value`] contains exactly one
108    /// subtag, or returns `None` otherwise.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use core::str::FromStr;
114    /// use icu::locale::extensions::unicode::Value;
115    ///
116    /// let value1 = Value::from_str("foo").expect("failed to parse a Value");
117    /// let value2 = Value::from_str("foo-bar").expect("failed to parse a Value");
118    ///
119    /// assert!(value1.into_single_subtag().is_some());
120    /// assert!(value2.into_single_subtag().is_none());
121    /// ```
122    pub fn into_single_subtag(self) -> Option<Subtag> {
123        self.0.into_single()
124    }
125
126    #[doc(hidden)]
127    pub fn as_subtags_slice(&self) -> &[Subtag] {
128        &self.0
129    }
130
131    /// Appends a subtag to the back of a [`Value`].
132    ///
133    /// ✨ *Enabled with the `alloc` Cargo feature.*
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
139    ///
140    /// let mut v = Value::default();
141    /// v.push_subtag(subtag!("foo"));
142    /// // The `true` subtag is ignored
143    /// v.push_subtag(subtag!("true"));
144    /// v.push_subtag(subtag!("bar"));
145    /// assert_eq!(v, "foo-bar");
146    /// ```
147    #[cfg(feature = "alloc")]
148    pub fn push_subtag(&mut self, subtag: Subtag) {
149        if subtag != TRUE_VALUE {
150            self.0.push(subtag);
151        }
152    }
153
154    /// Returns the number of subtags in the [`Value`].
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
160    ///
161    /// let mut v = Value::default();
162    /// assert_eq!(v.subtag_count(), 0);
163    /// v.push_subtag(subtag!("foo"));
164    /// assert_eq!(v.subtag_count(), 1);
165    /// ```
166    pub fn subtag_count(&self) -> usize {
167        self.0.len()
168    }
169
170    /// Creates an empty [`Value`], which corresponds to a "true" value.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use icu::locale::extensions::unicode::{value, Value};
176    ///
177    /// assert_eq!(value!("true"), Value::new_empty());
178    /// ```
179    pub const fn new_empty() -> Self {
180        Self(ShortBoxSlice::new())
181    }
182
183    /// Returns `true` if the Value has no subtags.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
189    ///
190    /// let mut v = Value::default();
191    /// assert!(v.is_empty());
192    /// ```
193    pub fn is_empty(&self) -> bool {
194        self.0.is_empty()
195    }
196
197    /// Removes and returns the subtag at position `index` within the value,
198    /// shifting all subtags after it to the left.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
204    /// let mut v = Value::default();
205    /// v.push_subtag(subtag!("foo"));
206    /// v.push_subtag(subtag!("bar"));
207    /// v.push_subtag(subtag!("baz"));
208    ///
209    /// assert_eq!(v.remove_subtag(1), Some(subtag!("bar")));
210    /// assert_eq!(v, "foo-baz");
211    /// ```
212    pub fn remove_subtag(&mut self, idx: usize) -> Option<Subtag> {
213        if self.0.len() < idx {
214            None
215        } else {
216            let item = self.0.remove(idx);
217            Some(item)
218        }
219    }
220
221    /// Returns a reference to a subtag at index.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
227    /// let mut v = Value::default();
228    /// v.push_subtag(subtag!("foo"));
229    /// v.push_subtag(subtag!("bar"));
230    /// v.push_subtag(subtag!("baz"));
231    ///
232    /// assert_eq!(v.get_subtag(1), Some(&subtag!("bar")));
233    /// assert_eq!(v.get_subtag(3), None);
234    /// ```
235    pub fn get_subtag(&self, idx: usize) -> Option<&Subtag> {
236        self.0.get(idx)
237    }
238
239    #[doc(hidden)]
240    pub const fn from_subtag(subtag: Option<Subtag>) -> Self {
241        match subtag {
242            None | Some(TRUE_VALUE) => Self(ShortBoxSlice::new()),
243            Some(val) => Self(ShortBoxSlice::new_single(val)),
244        }
245    }
246
247    #[doc(hidden)]
248    pub fn from_two_subtags(f: Subtag, s: Subtag) -> Self {
249        Self(ShortBoxSlice::new_double(f, s))
250    }
251
252    /// A constructor which takes a pre-sorted list of [`Value`] elements.
253    ///
254    /// ✨ *Enabled with the `alloc` Cargo feature.*
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// use icu::locale::extensions::unicode::Value;
260    /// use icu::locale::subtags::subtag;
261    ///
262    /// let subtag1 = subtag!("foobar");
263    /// let subtag2 = subtag!("testing");
264    /// let mut v = vec![subtag1, subtag2];
265    /// v.sort();
266    /// v.dedup();
267    ///
268    /// let value = Value::from_vec_unchecked(v);
269    /// ```
270    ///
271    /// Notice: For performance- and memory-constrained environments, it is recommended
272    /// for the caller to use [`binary_search`](slice::binary_search) instead of [`sort`](slice::sort)
273    /// and [`dedup`](Vec::dedup()).
274    #[cfg(feature = "alloc")]
275    pub fn from_vec_unchecked(input: Vec<Subtag>) -> Self {
276        Self(input.into())
277    }
278
279    #[allow(dead_code)]
280    pub(crate) fn from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self {
281        Self(input)
282    }
283
284    pub(crate) const fn parse_subtag_from_utf8(t: &[u8]) -> Result<Option<Subtag>, ParseError> {
285        match Subtag::try_from_utf8(t) {
286            Ok(TRUE_VALUE) => Ok(None),
287            Ok(s) => Ok(Some(s)),
288            Err(_) => Err(ParseError::InvalidSubtag),
289        }
290    }
291
292    pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
293    where
294        F: FnMut(&str) -> Result<(), E>,
295    {
296        self.0.iter().map(Subtag::as_str).try_for_each(f)
297    }
298}
299
300impl IntoIterator for Value {
301    type Item = Subtag;
302
303    type IntoIter = ShortBoxSliceIntoIter<Subtag>;
304
305    fn into_iter(self) -> Self::IntoIter {
306        self.0.into_iter()
307    }
308}
309
310/// ✨ *Enabled with the `alloc` Cargo feature.*
311#[cfg(feature = "alloc")]
312impl FromIterator<Subtag> for Value {
313    fn from_iter<T: IntoIterator<Item = Subtag>>(iter: T) -> Self {
314        Self(ShortBoxSlice::from_iter(iter))
315    }
316}
317
318/// ✨ *Enabled with the `alloc` Cargo feature.*
319#[cfg(feature = "alloc")]
320impl Extend<Subtag> for Value {
321    fn extend<T: IntoIterator<Item = Subtag>>(&mut self, iter: T) {
322        for i in iter {
323            self.0.push(i);
324        }
325    }
326}
327
328/// ✨ *Enabled with the `alloc` Cargo feature.*
329#[cfg(feature = "alloc")]
330impl FromStr for Value {
331    type Err = ParseError;
332
333    #[inline]
334    fn from_str(s: &str) -> Result<Self, Self::Err> {
335        Self::try_from_str(s)
336    }
337}
338
339impl PartialEq<&str> for Value {
340    fn eq(&self, other: &&str) -> bool {
341        writeable::cmp_utf8(self, other.as_bytes()).is_eq()
342    }
343}
344
345impl writeable::Writeable for Value {
    fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W)
        -> core::fmt::Result {
        let mut initial = true;
        self.for_each_subtag_str(&mut |subtag|
                    {
                        if initial {
                            initial = false;
                        } else { sink.write_char('-')?; }
                        sink.write_str(subtag)
                    })
    }
    #[inline]
    fn writeable_length_hint(&self) -> writeable::LengthHint {
        let mut result = writeable::LengthHint::exact(0);
        let mut initial = true;
        self.for_each_subtag_str::<core::convert::Infallible,
                _>(&mut |subtag|
                        {
                            if initial { initial = false; } else { result += 1; }
                            result += subtag.len();
                            Ok(())
                        }).expect("infallible");
        result
    }
    fn writeable_borrow(&self) -> Option<&str> {
        let selff = self;
        if selff.0.len() == 1 {

            #[allow(clippy :: unwrap_used)]
            { Some(selff.0.get(0).unwrap().as_str()) }
        } else { None }
    }
}
/// This trait is implemented for compatibility with [`fmt!`](alloc::fmt).
/// To create a string, [`Writeable::write_to_string`] is usually more efficient.
impl core::fmt::Display for Value {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        ::writeable::Writeable::write_to(&self, f)
    }
}impl_writeable_for_subtag_list!(Value, "islamic", "civil");
346
347/// A macro allowing for compile-time construction of valid Unicode [`Value`] subtag.
348///
349/// The macro only supports single-subtag values.
350///
351/// # Examples
352///
353/// ```
354/// use icu::locale::extensions::unicode::{key, value};
355/// use icu::locale::Locale;
356///
357/// let loc: Locale = "de-u-ca-buddhist".parse().unwrap();
358///
359/// assert_eq!(
360///     loc.extensions.unicode.keywords.get(&key!("ca")),
361///     Some(&value!("buddhist"))
362/// );
363/// ```
364///
365/// [`Value`]: crate::extensions::unicode::Value
366#[macro_export]
367#[doc(hidden)] // macro
368macro_rules! extensions_unicode_value {
369    ($value:literal) => {
370        const {
371            $crate::extensions::unicode::Value::from_subtag(
372                match $crate::subtags::Subtag::try_from_utf8($value.as_bytes()) {
373                    Ok(r) => Some(r),
374                    _ => panic!(concat!("Invalid Unicode extension value: ", $value)),
375                },
376            )
377        }
378    };
379}
380#[doc(inline)]
381pub use extensions_unicode_value as value;