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