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(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Default)]
39pub struct Value(ShortBoxSlice<Subtag>);
40
41const TRUE_VALUE: Subtag = 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_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;