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