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
22mod subtag;
23
24use crate::helpers::ShortSlice;
25use crate::parser::ParserError;
26use crate::parser::SubtagIterator;
27use alloc::vec::Vec;
28#[doc(inline)]
29pub 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)]
52pub struct Other {
53 ext: u8,
54 keys: ShortSlice<Subtag>,
55}
56
57impl 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
156writeable::impl_display_with_writeable!(Other);
157
158impl 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