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;