• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 crate::parser::ParseError;
6 #[cfg(feature = "alloc")]
7 use crate::parser::SubtagIterator;
8 use crate::shortvec::{ShortBoxSlice, ShortBoxSliceIntoIter};
9 use crate::subtags::{subtag, Subtag};
10 #[cfg(feature = "alloc")]
11 use alloc::vec::Vec;
12 #[cfg(feature = "alloc")]
13 use 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)]
39 pub struct Value(ShortBoxSlice<Subtag>);
40 
41 const TRUE_VALUE: Subtag = subtag!("true");
42 
43 impl 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")]
try_from_str(s: &str) -> Result<Self, ParseError>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")]
try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError>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     /// ```
as_single_subtag(&self) -> Option<&Subtag>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     /// ```
into_single_subtag(self) -> Option<Subtag>110     pub fn into_single_subtag(self) -> Option<Subtag> {
111         self.0.into_single()
112     }
113 
114     #[doc(hidden)]
as_subtags_slice(&self) -> &[Subtag]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")]
push_subtag(&mut self, subtag: Subtag)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     /// ```
subtag_count(&self) -> usize148     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     /// ```
new_empty() -> Self161     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     /// ```
is_empty(&self) -> bool175     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     /// ```
remove_subtag(&mut self, idx: usize) -> Option<Subtag>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     /// ```
get_subtag(&self, idx: usize) -> Option<&Subtag>217     pub fn get_subtag(&self, idx: usize) -> Option<&Subtag> {
218         self.0.get(idx)
219     }
220 
221     #[doc(hidden)]
from_subtag(subtag: Option<Subtag>) -> Self222     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")]
from_vec_unchecked(input: Vec<Subtag>) -> Self251     pub fn from_vec_unchecked(input: Vec<Subtag>) -> Self {
252         Self(input.into())
253     }
254 
255     #[allow(dead_code)]
from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self256     pub(crate) fn from_short_slice_unchecked(input: ShortBoxSlice<Subtag>) -> Self {
257         Self(input)
258     }
259 
parse_subtag_from_utf8(t: &[u8]) -> Result<Option<Subtag>, ParseError>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 
for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E> where F: FnMut(&str) -> Result<(), E>,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 
276 impl IntoIterator for Value {
277     type Item = Subtag;
278 
279     type IntoIter = ShortBoxSliceIntoIter<Subtag>;
280 
into_iter(self) -> Self::IntoIter281     fn into_iter(self) -> Self::IntoIter {
282         self.0.into_iter()
283     }
284 }
285 
286 #[cfg(feature = "alloc")]
287 impl FromIterator<Subtag> for Value {
from_iter<T: IntoIterator<Item = Subtag>>(iter: T) -> Self288     fn from_iter<T: IntoIterator<Item = Subtag>>(iter: T) -> Self {
289         Self(ShortBoxSlice::from_iter(iter))
290     }
291 }
292 
293 #[cfg(feature = "alloc")]
294 impl Extend<Subtag> for Value {
extend<T: IntoIterator<Item = Subtag>>(&mut self, iter: T)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")]
303 impl FromStr for Value {
304     type Err = ParseError;
305 
306     #[inline]
from_str(s: &str) -> Result<Self, Self::Err>307     fn from_str(s: &str) -> Result<Self, Self::Err> {
308         Self::try_from_str(s)
309     }
310 }
311 
312 impl PartialEq<&str> for Value {
eq(&self, other: &&str) -> bool313     fn eq(&self, other: &&str) -> bool {
314         writeable::cmp_utf8(self, other.as_bytes()).is_eq()
315     }
316 }
317 
318 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
341 macro_rules! extensions_unicode_value {
342     ($value:literal) => {{
343         // What we want:
344         // const R: $crate::extensions::unicode::Value =
345         //     match $crate::extensions::unicode::Value::try_from_single_subtag($value.as_bytes()) {
346         //         Ok(r) => r,
347         //         #[allow(clippy::panic)] // const context
348         //         _ => panic!(concat!("Invalid Unicode extension value: ", $value)),
349         //     };
350         // Workaround until https://github.com/rust-lang/rust/issues/73255 lands:
351         const R: $crate::extensions::unicode::Value =
352             $crate::extensions::unicode::Value::from_subtag(
353                 match $crate::subtags::Subtag::try_from_utf8($value.as_bytes()) {
354                     Ok(r) => Some(r),
355                     _ => panic!(concat!("Invalid Unicode extension value: ", $value)),
356                 },
357             );
358         R
359     }};
360 }
361 #[doc(inline)]
362 pub use extensions_unicode_value as value;
363