• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 //! Other Use Extensions is a list of extensions other than unicode,
6 //! transform or private.
7 //!
8 //! Those extensions are treated as a pass-through, and no Unicode related
9 //! behavior depends on them.
10 //!
11 //! The main struct for this extension is [`Other`] which is a list of [`Subtag`]s.
12 //!
13 //! # Examples
14 //!
15 //! ```
16 //! use icu::locale::extensions::other::Other;
17 //! use icu::locale::Locale;
18 //!
19 //! let mut loc: Locale = "en-US-a-foo-faa".parse().expect("Parsing failed.");
20 //! ```
21 
22 #[cfg(feature = "alloc")]
23 use core::str::FromStr;
24 
25 #[cfg(feature = "alloc")]
26 use super::ExtensionType;
27 #[cfg(feature = "alloc")]
28 use crate::parser::ParseError;
29 #[cfg(feature = "alloc")]
30 use crate::parser::SubtagIterator;
31 use crate::shortvec::ShortBoxSlice;
32 use crate::subtags::Subtag;
33 #[cfg(feature = "alloc")]
34 use alloc::vec::Vec;
35 
36 /// A list of [`Other Use Extensions`] as defined in [`Unicode Locale
37 /// Identifier`] specification.
38 ///
39 /// Those extensions are treated as a pass-through, and no Unicode related
40 /// behavior depends on them.
41 ///
42 /// # Examples
43 ///
44 /// ```
45 /// use icu::locale::extensions::other::Other;
46 /// use icu::locale::subtags::Subtag;
47 ///
48 /// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
49 /// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
50 ///
51 /// let other = Other::from_vec_unchecked(b'a', vec![subtag1, subtag2]);
52 /// assert_eq!(&other.to_string(), "a-foo-bar");
53 /// ```
54 ///
55 /// [`Other Use Extensions`]: https://unicode.org/reports/tr35/#other_extensions
56 /// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/#Unicode_locale_identifier
57 #[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
58 pub struct Other {
59     // Safety invariant: must be ASCII
60     ext: u8,
61     keys: ShortBoxSlice<Subtag>,
62 }
63 
64 impl Other {
65     /// A constructor which takes a str slice, parses it and
66     /// produces a well-formed [`Other`].
67     #[inline]
68     #[cfg(feature = "alloc")]
try_from_str(s: &str) -> Result<Self, ParseError>69     pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
70         Self::try_from_utf8(s.as_bytes())
71     }
72 
73     /// See [`Self::try_from_str`]
74     #[cfg(feature = "alloc")]
try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError>75     pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
76         let mut iter = SubtagIterator::new(code_units);
77 
78         let ext = iter.next().ok_or(ParseError::InvalidExtension)?;
79         if let ExtensionType::Other(b) = ExtensionType::try_from_byte_slice(ext)? {
80             return Self::try_from_iter(b, &mut iter);
81         }
82 
83         Err(ParseError::InvalidExtension)
84     }
85 
86     /// A constructor which takes a pre-sorted list of [`Subtag`].
87     ///
88     /// # Panics
89     ///
90     /// Panics if `ext` is not ASCII alphabetic.
91     ///
92     /// # Examples
93     ///
94     /// ```
95     /// use icu::locale::extensions::other::Other;
96     /// use icu::locale::subtags::Subtag;
97     ///
98     /// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
99     /// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
100     ///
101     /// let other = Other::from_vec_unchecked(b'a', vec![subtag1, subtag2]);
102     /// assert_eq!(&other.to_string(), "a-foo-bar");
103     /// ```
104     #[cfg(feature = "alloc")]
from_vec_unchecked(ext: u8, keys: Vec<Subtag>) -> Self105     pub fn from_vec_unchecked(ext: u8, keys: Vec<Subtag>) -> Self {
106         Self::from_short_slice_unchecked(ext, keys.into())
107     }
108 
109     #[allow(dead_code)]
from_short_slice_unchecked(ext: u8, keys: ShortBoxSlice<Subtag>) -> Self110     pub(crate) fn from_short_slice_unchecked(ext: u8, keys: ShortBoxSlice<Subtag>) -> Self {
111         assert!(ext.is_ascii_alphabetic());
112         // Safety invariant upheld here: ext checked as ASCII above
113         Self { ext, keys }
114     }
115 
116     #[cfg(feature = "alloc")]
try_from_iter(ext: u8, iter: &mut SubtagIterator) -> Result<Self, ParseError>117     pub(crate) fn try_from_iter(ext: u8, iter: &mut SubtagIterator) -> Result<Self, ParseError> {
118         debug_assert!(matches!(
119             ExtensionType::try_from_byte(ext),
120             Ok(ExtensionType::Other(_)),
121         ));
122 
123         let mut keys = ShortBoxSlice::new();
124         while let Some(subtag) = iter.peek() {
125             if !Subtag::valid_key(subtag) {
126                 break;
127             }
128             if let Ok(key) = Subtag::try_from_utf8(subtag) {
129                 keys.push(key);
130             }
131             iter.next();
132         }
133 
134         if keys.is_empty() {
135             Err(ParseError::InvalidExtension)
136         } else {
137             Ok(Self::from_short_slice_unchecked(ext, keys))
138         }
139     }
140 
141     /// Gets the tag character for this extension as a &str.
142     ///
143     /// # Examples
144     ///
145     /// ```
146     /// use icu::locale::Locale;
147     ///
148     /// let loc: Locale = "und-a-hello-world".parse().unwrap();
149     /// let other_ext = &loc.extensions.other[0];
150     /// assert_eq!(other_ext.get_ext_str(), "a");
151     /// ```
get_ext_str(&self) -> &str152     pub fn get_ext_str(&self) -> &str {
153         debug_assert!(self.ext.is_ascii_alphabetic());
154         // Safety: from safety invariant on self.ext (that it is ASCII)
155         unsafe { core::str::from_utf8_unchecked(core::slice::from_ref(&self.ext)) }
156     }
157 
158     /// Gets the tag character for this extension as a char.
159     ///
160     /// # Examples
161     ///
162     /// ```
163     /// use icu::locale::Locale;
164     ///
165     /// let loc: Locale = "und-a-hello-world".parse().unwrap();
166     /// let other_ext = &loc.extensions.other[0];
167     /// assert_eq!(other_ext.get_ext(), 'a');
168     /// ```
get_ext(&self) -> char169     pub fn get_ext(&self) -> char {
170         self.ext as char
171     }
172 
173     /// Gets the tag character for this extension as a byte.
174     ///
175     /// # Examples
176     ///
177     /// ```
178     /// use icu::locale::Locale;
179     ///
180     /// let loc: Locale = "und-a-hello-world".parse().unwrap();
181     /// let other_ext = &loc.extensions.other[0];
182     /// assert_eq!(other_ext.get_ext_byte(), b'a');
183     /// ```
get_ext_byte(&self) -> u8184     pub fn get_ext_byte(&self) -> u8 {
185         self.ext
186     }
187 
for_each_subtag_str<E, F>(&self, f: &mut F, with_ext: bool) -> Result<(), E> where F: FnMut(&str) -> Result<(), E>,188     pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F, with_ext: bool) -> Result<(), E>
189     where
190         F: FnMut(&str) -> Result<(), E>,
191     {
192         if self.keys.is_empty() {
193             return Ok(());
194         }
195 
196         if with_ext {
197             f(self.get_ext_str())?;
198         }
199         self.keys.iter().map(|t| t.as_str()).try_for_each(f)
200     }
201 }
202 
203 #[cfg(feature = "alloc")]
204 impl FromStr for Other {
205     type Err = ParseError;
206 
207     #[inline]
from_str(s: &str) -> Result<Self, Self::Err>208     fn from_str(s: &str) -> Result<Self, Self::Err> {
209         Self::try_from_str(s)
210     }
211 }
212 
213 writeable::impl_display_with_writeable!(Other);
214 
215 impl writeable::Writeable for Other {
write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result216     fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
217         if self.keys.is_empty() {
218             return Ok(());
219         }
220         sink.write_str(self.get_ext_str())?;
221         for key in self.keys.iter() {
222             sink.write_char('-')?;
223             writeable::Writeable::write_to(key, sink)?;
224         }
225 
226         Ok(())
227     }
228 
writeable_length_hint(&self) -> writeable::LengthHint229     fn writeable_length_hint(&self) -> writeable::LengthHint {
230         if self.keys.is_empty() {
231             return writeable::LengthHint::exact(0);
232         };
233         let mut result = writeable::LengthHint::exact(1);
234         for key in self.keys.iter() {
235             result += writeable::Writeable::writeable_length_hint(key) + 1;
236         }
237         result
238     }
239 
240     #[cfg(feature = "alloc")]
write_to_string(&self) -> alloc::borrow::Cow<str>241     fn write_to_string(&self) -> alloc::borrow::Cow<str> {
242         if self.keys.is_empty() {
243             return alloc::borrow::Cow::Borrowed("");
244         }
245         let mut string =
246             alloc::string::String::with_capacity(self.writeable_length_hint().capacity());
247         let _ = self.write_to(&mut string);
248         alloc::borrow::Cow::Owned(string)
249     }
250 }
251 
252 #[cfg(test)]
253 mod tests {
254     use super::*;
255 
256     #[test]
test_other_extension_fromstr()257     fn test_other_extension_fromstr() {
258         let oe: Other = "o-foo-bar".parse().expect("Failed to parse Other");
259         assert_eq!(oe.to_string(), "o-foo-bar");
260 
261         let oe: Result<Other, _> = "o".parse();
262         assert!(oe.is_err());
263     }
264 }
265