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::locid::extensions::other::Other; |
17 | //! use icu::locid::Locale; |
18 | //! |
19 | //! let mut loc: Locale = "en-US-a-foo-faa" .parse().expect("Parsing failed." ); |
20 | //! ``` |
21 | |
22 | mod subtag; |
23 | |
24 | use crate::helpers::ShortSlice; |
25 | use crate::parser::ParserError; |
26 | use crate::parser::SubtagIterator; |
27 | use alloc::vec::Vec; |
28 | #[doc (inline)] |
29 | pub use subtag::{subtag, Subtag}; |
30 | |
31 | /// A list of [`Other Use Extensions`] as defined in [`Unicode Locale |
32 | /// Identifier`] specification. |
33 | /// |
34 | /// Those extensions are treated as a pass-through, and no Unicode related |
35 | /// behavior depends on them. |
36 | /// |
37 | /// # Examples |
38 | /// |
39 | /// ``` |
40 | /// use icu::locid::extensions::other::{Other, Subtag}; |
41 | /// |
42 | /// let subtag1: Subtag = "foo" .parse().expect("Failed to parse a Subtag." ); |
43 | /// let subtag2: Subtag = "bar" .parse().expect("Failed to parse a Subtag." ); |
44 | /// |
45 | /// let other = Other::from_vec_unchecked(b'a' , vec![subtag1, subtag2]); |
46 | /// assert_eq!(&other.to_string(), "a-foo-bar" ); |
47 | /// ``` |
48 | /// |
49 | /// [`Other Use Extensions`]: https://unicode.org/reports/tr35/#other_extensions |
50 | /// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/#Unicode_locale_identifier |
51 | #[derive (Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)] |
52 | pub struct Other { |
53 | ext: u8, |
54 | keys: ShortSlice<Subtag>, |
55 | } |
56 | |
57 | impl Other { |
58 | /// A constructor which takes a pre-sorted list of [`Subtag`]. |
59 | /// |
60 | /// # Panics |
61 | /// |
62 | /// Panics if `ext` is not ASCII alphabetic. |
63 | /// |
64 | /// # Examples |
65 | /// |
66 | /// ``` |
67 | /// use icu::locid::extensions::other::{Other, Subtag}; |
68 | /// |
69 | /// let subtag1: Subtag = "foo" .parse().expect("Failed to parse a Subtag." ); |
70 | /// let subtag2: Subtag = "bar" .parse().expect("Failed to parse a Subtag." ); |
71 | /// |
72 | /// let other = Other::from_vec_unchecked(b'a' , vec![subtag1, subtag2]); |
73 | /// assert_eq!(&other.to_string(), "a-foo-bar" ); |
74 | /// ``` |
75 | pub fn from_vec_unchecked(ext: u8, keys: Vec<Subtag>) -> Self { |
76 | Self::from_short_slice_unchecked(ext, keys.into()) |
77 | } |
78 | |
79 | pub(crate) fn from_short_slice_unchecked(ext: u8, keys: ShortSlice<Subtag>) -> Self { |
80 | assert!(ext.is_ascii_alphabetic()); |
81 | Self { ext, keys } |
82 | } |
83 | |
84 | pub(crate) fn try_from_iter(ext: u8, iter: &mut SubtagIterator) -> Result<Self, ParserError> { |
85 | debug_assert!(ext.is_ascii_alphabetic()); |
86 | |
87 | let mut keys = ShortSlice::new(); |
88 | while let Some(subtag) = iter.peek() { |
89 | if !Subtag::valid_key(subtag) { |
90 | break; |
91 | } |
92 | if let Ok(key) = Subtag::try_from_bytes(subtag) { |
93 | keys.push(key); |
94 | } |
95 | iter.next(); |
96 | } |
97 | |
98 | Ok(Self::from_short_slice_unchecked(ext, keys)) |
99 | } |
100 | |
101 | /// Gets the tag character for this extension as a &str. |
102 | /// |
103 | /// # Examples |
104 | /// |
105 | /// ``` |
106 | /// use icu::locid::Locale; |
107 | /// |
108 | /// let loc: Locale = "und-a-hello-world" .parse().unwrap(); |
109 | /// let other_ext = &loc.extensions.other[0]; |
110 | /// assert_eq!(other_ext.get_ext_str(), "a" ); |
111 | /// ``` |
112 | pub fn get_ext_str(&self) -> &str { |
113 | debug_assert!(self.ext.is_ascii_alphabetic()); |
114 | unsafe { core::str::from_utf8_unchecked(core::slice::from_ref(&self.ext)) } |
115 | } |
116 | |
117 | /// Gets the tag character for this extension as a char. |
118 | /// |
119 | /// # Examples |
120 | /// |
121 | /// ``` |
122 | /// use icu::locid::Locale; |
123 | /// |
124 | /// let loc: Locale = "und-a-hello-world" .parse().unwrap(); |
125 | /// let other_ext = &loc.extensions.other[0]; |
126 | /// assert_eq!(other_ext.get_ext(), 'a' ); |
127 | /// ``` |
128 | pub fn get_ext(&self) -> char { |
129 | self.ext as char |
130 | } |
131 | |
132 | /// Gets the tag character for this extension as a byte. |
133 | /// |
134 | /// # Examples |
135 | /// |
136 | /// ``` |
137 | /// use icu::locid::Locale; |
138 | /// |
139 | /// let loc: Locale = "und-a-hello-world" .parse().unwrap(); |
140 | /// let other_ext = &loc.extensions.other[0]; |
141 | /// assert_eq!(other_ext.get_ext_byte(), b'a' ); |
142 | /// ``` |
143 | pub fn get_ext_byte(&self) -> u8 { |
144 | self.ext |
145 | } |
146 | |
147 | pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E> |
148 | where |
149 | F: FnMut(&str) -> Result<(), E>, |
150 | { |
151 | f(self.get_ext_str())?; |
152 | self.keys.iter().map(|t| t.as_str()).try_for_each(f) |
153 | } |
154 | } |
155 | |
156 | writeable::impl_display_with_writeable!(Other); |
157 | |
158 | impl writeable::Writeable for Other { |
159 | fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result { |
160 | sink.write_str(self.get_ext_str())?; |
161 | for key in self.keys.iter() { |
162 | sink.write_char('-' )?; |
163 | writeable::Writeable::write_to(key, sink)?; |
164 | } |
165 | |
166 | Ok(()) |
167 | } |
168 | |
169 | fn writeable_length_hint(&self) -> writeable::LengthHint { |
170 | let mut result = writeable::LengthHint::exact(1); |
171 | for key in self.keys.iter() { |
172 | result += writeable::Writeable::writeable_length_hint(key) + 1; |
173 | } |
174 | result |
175 | } |
176 | |
177 | fn write_to_string(&self) -> alloc::borrow::Cow<str> { |
178 | if self.keys.is_empty() { |
179 | return alloc::borrow::Cow::Borrowed(self.get_ext_str()); |
180 | } |
181 | let mut string = |
182 | alloc::string::String::with_capacity(self.writeable_length_hint().capacity()); |
183 | let _ = self.write_to(&mut string); |
184 | alloc::borrow::Cow::Owned(string) |
185 | } |
186 | } |
187 | |