icu_locale_core/extensions/private/mod.rs
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//! Private Use Extensions is a list of extensions intended for
6//! private use.
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 [`Private`] which is a list of [`Subtag`]s.
12//!
13//! # Examples
14//!
15//! ```
16//! use icu::locale::extensions::private::subtag;
17//! use icu::locale::{locale, Locale};
18//!
19//! let mut loc: Locale = "en-US-x-foo-faa".parse().expect("Parsing failed.");
20//!
21//! assert!(loc.extensions.private.contains(&subtag!("foo")));
22//! assert_eq!(loc.extensions.private.iter().next(), Some(&subtag!("foo")));
23//!
24//! loc.extensions.private.clear();
25//!
26//! assert!(loc.extensions.private.is_empty());
27//! assert_eq!(loc, locale!("en-US"));
28//! ```
29
30mod other;
31
32#[cfg(feature = "alloc")]
33use alloc::vec::Vec;
34use core::ops::Deref;
35#[cfg(feature = "alloc")]
36use core::str::FromStr;
37
38#[doc(inline)]
39pub use other::{subtag, Subtag};
40
41#[cfg(feature = "alloc")]
42use super::ExtensionType;
43#[cfg(feature = "alloc")]
44use crate::parser::ParseError;
45#[cfg(feature = "alloc")]
46use crate::parser::SubtagIterator;
47use crate::shortvec::ShortBoxSlice;
48
49pub(crate) const PRIVATE_EXT_CHAR: char = 'x';
50pub(crate) const PRIVATE_EXT_STR: &str = "x";
51
52/// A list of [`Private Use Extensions`] as defined in [`Unicode Locale
53/// Identifier`] specification.
54///
55/// Those extensions are treated as a pass-through, and no Unicode related
56/// behavior depends on them.
57///
58/// # Examples
59///
60/// ```
61/// use icu::locale::extensions::private::{Private, Subtag};
62///
63/// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
64/// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
65///
66/// let private = Private::from_vec_unchecked(vec![subtag1, subtag2]);
67/// assert_eq!(&private.to_string(), "x-foo-bar");
68/// ```
69///
70/// [`Private Use Extensions`]: https://unicode.org/reports/tr35/#pu_extensions
71/// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/#Unicode_locale_identifier
72#[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
73pub struct Private(ShortBoxSlice<Subtag>);
74
75impl Private {
76 /// Returns a new empty list of private-use extensions. Same as [`default()`](Default::default()), but is `const`.
77 ///
78 /// # Examples
79 ///
80 /// ```
81 /// use icu::locale::extensions::private::Private;
82 ///
83 /// assert_eq!(Private::new(), Private::default());
84 /// ```
85 #[inline]
86 pub const fn new() -> Self {
87 Self(ShortBoxSlice::new())
88 }
89
90 /// A constructor which takes a str slice, parses it and
91 /// produces a well-formed [`Private`].
92 ///
93 /// ✨ *Enabled with the `alloc` Cargo feature.*
94 #[inline]
95 #[cfg(feature = "alloc")]
96 pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
97 Self::try_from_utf8(s.as_bytes())
98 }
99
100 /// See [`Self::try_from_str`]
101 ///
102 /// ✨ *Enabled with the `alloc` Cargo feature.*
103 #[cfg(feature = "alloc")]
104 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
105 let mut iter = SubtagIterator::new(code_units);
106
107 let ext = iter.next().ok_or(ParseError::InvalidExtension)?;
108 if let ExtensionType::Private = ExtensionType::try_from_byte_slice(ext)? {
109 return Self::try_from_iter(&mut iter);
110 }
111
112 Err(ParseError::InvalidExtension)
113 }
114
115 /// A constructor which takes a pre-sorted list of [`Subtag`].
116 ///
117 /// ✨ *Enabled with the `alloc` Cargo feature.*
118 ///
119 /// # Examples
120 ///
121 /// ```
122 /// use icu::locale::extensions::private::{Private, Subtag};
123 ///
124 /// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
125 /// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
126 ///
127 /// let private = Private::from_vec_unchecked(vec![subtag1, subtag2]);
128 /// assert_eq!(&private.to_string(), "x-foo-bar");
129 /// ```
130 #[cfg(feature = "alloc")]
131 pub fn from_vec_unchecked(input: Vec<Subtag>) -> Self {
132 Self(input.into())
133 }
134
135 /// A constructor which takes a single [`Subtag`].
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use icu::locale::extensions::private::{Private, Subtag};
141 ///
142 /// let subtag: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
143 ///
144 /// let private = Private::new_single(subtag);
145 /// assert_eq!(&private.to_string(), "x-foo");
146 /// ```
147 pub const fn new_single(input: Subtag) -> Self {
148 Self(ShortBoxSlice::new_single(input))
149 }
150
151 /// Empties the [`Private`] list.
152 ///
153 /// # Examples
154 ///
155 /// ```
156 /// use icu::locale::extensions::private::{Private, Subtag};
157 ///
158 /// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
159 /// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
160 /// let mut private = Private::from_vec_unchecked(vec![subtag1, subtag2]);
161 ///
162 /// assert_eq!(&private.to_string(), "x-foo-bar");
163 ///
164 /// private.clear();
165 ///
166 /// assert_eq!(private, Private::new());
167 /// ```
168 pub fn clear(&mut self) {
169 self.0.clear();
170 }
171
172 #[cfg(feature = "alloc")]
173 pub(crate) fn try_from_iter(iter: &mut SubtagIterator) -> Result<Self, ParseError> {
174 let keys = iter
175 .map(Subtag::try_from_utf8)
176 .collect::<Result<ShortBoxSlice<_>, _>>()?;
177
178 if keys.is_empty() {
179 Err(ParseError::InvalidExtension)
180 } else {
181 Ok(Self(keys))
182 }
183 }
184
185 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F, with_ext: bool) -> Result<(), E>
186 where
187 F: FnMut(&str) -> Result<(), E>,
188 {
189 if self.is_empty() {
190 return Ok(());
191 }
192 if with_ext {
193 f(PRIVATE_EXT_STR)?;
194 }
195 self.deref().iter().map(|t| t.as_str()).try_for_each(f)
196 }
197}
198
199/// ✨ *Enabled with the `alloc` Cargo feature.*
200#[cfg(feature = "alloc")]
201impl FromStr for Private {
202 type Err = ParseError;
203
204 #[inline]
205 fn from_str(s: &str) -> Result<Self, Self::Err> {
206 Self::try_from_str(s)
207 }
208}
209
210writeable::impl_display_with_writeable!(Private, #[cfg(feature = "alloc")]);
211
212impl writeable::Writeable for Private {
213 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
214 if self.is_empty() {
215 return Ok(());
216 }
217 sink.write_char(PRIVATE_EXT_CHAR)?;
218 for key in self.iter() {
219 sink.write_char('-')?;
220 writeable::Writeable::write_to(key, sink)?;
221 }
222 Ok(())
223 }
224
225 fn writeable_length_hint(&self) -> writeable::LengthHint {
226 if self.is_empty() {
227 return writeable::LengthHint::exact(0);
228 }
229 let mut result = writeable::LengthHint::exact(1);
230 for key in self.iter() {
231 result += writeable::Writeable::writeable_length_hint(key) + 1;
232 }
233 result
234 }
235}
236
237impl Deref for Private {
238 type Target = [Subtag];
239
240 fn deref(&self) -> &Self::Target {
241 self.0.deref()
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_private_extension_fromstr() {
251 let pe: Private = "x-foo-bar-l-baz".parse().expect("Failed to parse Private");
252 assert_eq!(pe.to_string(), "x-foo-bar-l-baz");
253
254 let pe: Result<Private, _> = "x".parse();
255 assert!(pe.is_err());
256 }
257}