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