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 super::Attribute; 6 7 #[cfg(feature = "alloc")] 8 use crate::parser::SubtagIterator; 9 use crate::shortvec::ShortBoxSlice; 10 #[cfg(feature = "alloc")] 11 use crate::ParseError; 12 #[cfg(feature = "alloc")] 13 use alloc::vec::Vec; 14 use core::ops::Deref; 15 #[cfg(feature = "alloc")] 16 use core::str::FromStr; 17 18 /// A set of [`Attribute`] elements as defined in [`Unicode Extension Attributes`]. 19 /// 20 /// [`Unicode Extension Attributes`]: https://unicode.org/reports/tr35/tr35.html#u_Extension 21 /// 22 /// # Examples 23 /// 24 /// ``` 25 /// use icu::locale::extensions::unicode::{Attribute, Attributes}; 26 /// 27 /// let attribute1: Attribute = 28 /// "foobar".parse().expect("Failed to parse a variant subtag."); 29 /// 30 /// let attribute2: Attribute = "testing" 31 /// .parse() 32 /// .expect("Failed to parse a variant subtag."); 33 /// let mut v = vec![attribute1, attribute2]; 34 /// v.sort(); 35 /// v.dedup(); 36 /// 37 /// let attributes: Attributes = Attributes::from_vec_unchecked(v); 38 /// assert_eq!(attributes.to_string(), "foobar-testing"); 39 /// ``` 40 #[derive(Default, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)] 41 pub struct Attributes(ShortBoxSlice<Attribute>); 42 43 impl Attributes { 44 /// Returns a new empty set of attributes. Same as [`default()`](Default::default()), but is `const`. 45 /// 46 /// # Examples 47 /// 48 /// ``` 49 /// use icu::locale::extensions::unicode::Attributes; 50 /// 51 /// assert_eq!(Attributes::new(), Attributes::default()); 52 /// ``` 53 #[inline] new() -> Self54 pub const fn new() -> Self { 55 Self(ShortBoxSlice::new()) 56 } 57 58 /// A constructor which takes a str slice, parses it and 59 /// produces a well-formed [`Attributes`]. 60 #[inline] 61 #[cfg(feature = "alloc")] try_from_str(s: &str) -> Result<Self, ParseError>62 pub fn try_from_str(s: &str) -> Result<Self, ParseError> { 63 Self::try_from_utf8(s.as_bytes()) 64 } 65 66 /// See [`Self::try_from_str`] 67 #[cfg(feature = "alloc")] try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError>68 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> { 69 let mut iter = SubtagIterator::new(code_units); 70 Self::try_from_iter(&mut iter) 71 } 72 73 /// A constructor which takes a pre-sorted list of [`Attribute`] elements. 74 /// 75 /// 76 /// # Examples 77 /// 78 /// ``` 79 /// use icu::locale::extensions::unicode::{Attribute, Attributes}; 80 /// 81 /// let attribute1: Attribute = "foobar".parse().expect("Parsing failed."); 82 /// let attribute2: Attribute = "testing".parse().expect("Parsing failed."); 83 /// let mut v = vec![attribute1, attribute2]; 84 /// v.sort(); 85 /// v.dedup(); 86 /// 87 /// let attributes = Attributes::from_vec_unchecked(v); 88 /// ``` 89 /// 90 /// Notice: For performance- and memory-constrained environments, it is recommended 91 /// for the caller to use [`binary_search`](slice::binary_search) instead of [`sort`](slice::sort) 92 /// and [`dedup`](Vec::dedup()). 93 #[cfg(feature = "alloc")] from_vec_unchecked(input: Vec<Attribute>) -> Self94 pub fn from_vec_unchecked(input: Vec<Attribute>) -> Self { 95 Self(input.into()) 96 } 97 98 /// Empties the [`Attributes`] list. 99 /// 100 /// Returns the old list. 101 /// 102 /// # Examples 103 /// 104 /// ``` 105 /// use icu::locale::extensions::unicode::{attribute, Attributes}; 106 /// use writeable::assert_writeable_eq; 107 /// 108 /// let mut attributes = Attributes::from_vec_unchecked(vec![ 109 /// attribute!("foobar"), 110 /// attribute!("testing"), 111 /// ]); 112 /// 113 /// assert_writeable_eq!(attributes, "foobar-testing"); 114 /// 115 /// attributes.clear(); 116 /// 117 /// assert_writeable_eq!(attributes, ""); 118 /// ``` clear(&mut self) -> Self119 pub fn clear(&mut self) -> Self { 120 core::mem::take(self) 121 } 122 123 #[cfg(feature = "alloc")] try_from_iter(iter: &mut SubtagIterator) -> Result<Self, ParseError>124 pub(crate) fn try_from_iter(iter: &mut SubtagIterator) -> Result<Self, ParseError> { 125 let mut attributes = ShortBoxSlice::new(); 126 127 while let Some(subtag) = iter.peek() { 128 if let Ok(attr) = Attribute::try_from_utf8(subtag) { 129 if let Err(idx) = attributes.binary_search(&attr) { 130 attributes.insert(idx, attr); 131 } 132 } else { 133 break; 134 } 135 iter.next(); 136 } 137 Ok(Self(attributes)) 138 } 139 for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E> where F: FnMut(&str) -> Result<(), E>,140 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E> 141 where 142 F: FnMut(&str) -> Result<(), E>, 143 { 144 self.deref().iter().map(|t| t.as_str()).try_for_each(f) 145 } 146 } 147 148 #[cfg(feature = "alloc")] 149 impl FromStr for Attributes { 150 type Err = ParseError; 151 152 #[inline] from_str(s: &str) -> Result<Self, Self::Err>153 fn from_str(s: &str) -> Result<Self, Self::Err> { 154 Self::try_from_str(s) 155 } 156 } 157 158 impl_writeable_for_subtag_list!(Attributes, "foobar", "testing"); 159 160 impl Deref for Attributes { 161 type Target = [Attribute]; 162 deref(&self) -> &[Attribute]163 fn deref(&self) -> &[Attribute] { 164 self.0.deref() 165 } 166 } 167 168 #[cfg(test)] 169 mod tests { 170 use super::*; 171 172 #[test] test_attributes_fromstr()173 fn test_attributes_fromstr() { 174 let attrs: Attributes = "foo-bar".parse().expect("Failed to parse Attributes"); 175 assert_eq!(attrs.to_string(), "bar-foo"); 176 } 177 } 178