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_receiver_is_total_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    /// v.push_subtag(subtag!("bar"));
143    /// assert_eq!(v, "foo-bar");
144    /// ```
145    #[cfg(feature = "alloc")]
146    pub fn push_subtag(&mut self, subtag: Subtag) {
147        self.0.push(subtag);
148    }
149
150    /// Returns the number of subtags in the [`Value`].
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
156    ///
157    /// let mut v = Value::default();
158    /// assert_eq!(v.subtag_count(), 0);
159    /// v.push_subtag(subtag!("foo"));
160    /// assert_eq!(v.subtag_count(), 1);
161    /// ```
162    pub fn subtag_count(&self) -> usize {
163        self.0.len()
164    }
165
166    /// Creates an empty [`Value`], which corresponds to a "true" value.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use icu::locale::extensions::unicode::{value, Value};
172    ///
173    /// assert_eq!(value!("true"), Value::new_empty());
174    /// ```
175    pub const fn new_empty() -> Self {
176        Self(ShortBoxSlice::new())
177    }
178
179    /// Returns `true` if the Value has no subtags.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
185    ///
186    /// let mut v = Value::default();
187    /// assert!(v.is_empty());
188    /// ```
189    pub fn is_empty(&self) -> bool {
190        self.0.is_empty()
191    }
192
193    /// Removes and returns the subtag at position `index` within the value,
194    /// shifting all subtags after it to the left.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
200    /// let mut v = Value::default();
201    /// v.push_subtag(subtag!("foo"));
202    /// v.push_subtag(subtag!("bar"));
203    /// v.push_subtag(subtag!("baz"));
204    ///
205    /// assert_eq!(v.remove_subtag(1), Some(subtag!("bar")));
206    /// assert_eq!(v, "foo-baz");
207    /// ```
208    pub fn remove_subtag(&mut self, idx: usize) -> Option<Subtag> {
209        if self.0.len() < idx {
210            None
211        } else {
212            let item = self.0.remove(idx);
213            Some(item)
214        }
215    }
216
217    /// Returns a reference to a subtag at index.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// use icu::locale::{extensions::unicode::Value, subtags::subtag};
223    /// let mut v = Value::default();
224    /// v.push_subtag(subtag!("foo"));
225    /// v.push_subtag(subtag!("bar"));
226    /// v.push_subtag(subtag!("baz"));
227    ///
228    /// assert_eq!(v.get_subtag(1), Some(&subtag!("bar")));
229    /// assert_eq!(v.get_subtag(3), None);
230    /// ```
231    pub fn get_subtag(&self, idx: usize) -> Option<&Subtag> {
232        self.0.get(idx)
233    }
234
235    #[doc(hidden)]
236    pub const fn from_subtag(subtag: Option<Subtag>) -> Self {
237        match subtag {
238            None | Some(TRUE_VALUE) => Self(ShortBoxSlice::new()),
239            Some(val) => Self(ShortBoxSlice::new_single(val)),
240        }
241    }
242
243    #[doc(hidden)]
244    pub fn from_two_subtags(f: Subtag, s: Subtag) -> Self {
245        Self(ShortBoxSlice::new_double(f, s))
246    }
247
248    /// A constructor which takes a pre-sorted list of [`Value`] elements.
249    ///
250    /// ✨ *Enabled with the `alloc` Cargo feature.*
251    ///
252    /// # Examples
253    ///
254    /// ```
255    /// use icu::locale::extensions::unicode::Value;
256    /// use icu::locale::subtags::subtag;
257    ///
258    /// let subtag1 = subtag!("foobar");
259    /// let subtag2 = subtag!("testing");
260    /// let mut v = vec![subtag1, subtag2];
261    /// v.sort();
262    /// v.dedup();
263    ///
264    /// let value = Value::from_vec_unchecked(v);
265    /// ```
266    ///
267    /// Notice: For performance- and memory-constrained environments, it is recommended
268    /// for the caller to use [`binary_search`](slice::binary_search) instead of [`sort`](slice::sort)
269    /// and [`dedup`](Vec::dedup()).
270    #[cfg(feature = "alloc")]
271    pub fn from_vec_unchecked(input: Vec<Subtag>) -> Self {
272        Self(input.into())
273    }
274
275    #[allow(dead_code)]
276    pub(crate) fn from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self {
277        Self(input)
278    }
279
280    pub(crate) const fn parse_subtag_from_utf8(t: &[u8]) -> Result<Option<Subtag>, ParseError> {
281        match Subtag::try_from_utf8(t) {
282            Ok(TRUE_VALUE) => Ok(None),
283            Ok(s) => Ok(Some(s)),
284            Err(_) => Err(ParseError::InvalidSubtag),
285        }
286    }
287
288    pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
289    where
290        F: FnMut(&str) -> Result<(), E>,
291    {
292        self.0.iter().map(Subtag::as_str).try_for_each(f)
293    }
294}
295
296impl IntoIterator for Value {
297    type Item = Subtag;
298
299    type IntoIter = ShortBoxSliceIntoIter<Subtag>;
300
301    fn into_iter(self) -> Self::IntoIter {
302        self.0.into_iter()
303    }
304}
305
306/// ✨ *Enabled with the `alloc` Cargo feature.*
307#[cfg(feature = "alloc")]
308impl FromIterator<Subtag> for Value {
309    fn from_iter<T: IntoIterator<Item = Subtag>>(iter: T) -> Self {
310        Self(ShortBoxSlice::from_iter(iter))
311    }
312}
313
314/// ✨ *Enabled with the `alloc` Cargo feature.*
315#[cfg(feature = "alloc")]
316impl Extend<Subtag> for Value {
317    fn extend<T: IntoIterator<Item = Subtag>>(&mut self, iter: T) {
318        for i in iter {
319            self.0.push(i);
320        }
321    }
322}
323
324/// ✨ *Enabled with the `alloc` Cargo feature.*
325#[cfg(feature = "alloc")]
326impl FromStr for Value {
327    type Err = ParseError;
328
329    #[inline]
330    fn from_str(s: &str) -> Result<Self, Self::Err> {
331        Self::try_from_str(s)
332    }
333}
334
335impl PartialEq<&str> for Value {
336    fn eq(&self, other: &&str) -> bool {
337        writeable::cmp_utf8(self, other.as_bytes()).is_eq()
338    }
339}
340
341impl 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");
342
343/// A macro allowing for compile-time construction of valid Unicode [`Value`] subtag.
344///
345/// The macro only supports single-subtag values.
346///
347/// # Examples
348///
349/// ```
350/// use icu::locale::extensions::unicode::{key, value};
351/// use icu::locale::Locale;
352///
353/// let loc: Locale = "de-u-ca-buddhist".parse().unwrap();
354///
355/// assert_eq!(
356///     loc.extensions.unicode.keywords.get(&key!("ca")),
357///     Some(&value!("buddhist"))
358/// );
359/// ```
360///
361/// [`Value`]: crate::extensions::unicode::Value
362#[macro_export]
363#[doc(hidden)] // macro
364macro_rules! extensions_unicode_value {
365    ($value:literal) => {
366        const {
367            $crate::extensions::unicode::Value::from_subtag(
368                match $crate::subtags::Subtag::try_from_utf8($value.as_bytes()) {
369                    Ok(r) => Some(r),
370                    _ => panic!(concat!("Invalid Unicode extension value: ", $value)),
371                },
372            )
373        }
374    };
375}
376#[doc(inline)]
377pub use extensions_unicode_value as value;