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