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 #[inline]
93 #[cfg(feature = "alloc")]
94 pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
95 Self::try_from_utf8(s.as_bytes())
96 }
97
98 /// See [`Self::try_from_str`]
99 #[cfg(feature = "alloc")]
100 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
101 let mut iter = SubtagIterator::new(code_units);
102
103 let ext = iter.next().ok_or(ParseError::InvalidExtension)?;
104 if let ExtensionType::Private = ExtensionType::try_from_byte_slice(ext)? {
105 return Self::try_from_iter(&mut iter);
106 }
107
108 Err(ParseError::InvalidExtension)
109 }
110
111 /// A constructor which takes a pre-sorted list of [`Subtag`].
112 ///
113 /// # Examples
114 ///
115 /// ```
116 /// use icu::locale::extensions::private::{Private, Subtag};
117 ///
118 /// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
119 /// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
120 ///
121 /// let private = Private::from_vec_unchecked(vec![subtag1, subtag2]);
122 /// assert_eq!(&private.to_string(), "x-foo-bar");
123 /// ```
124 #[cfg(feature = "alloc")]
125 pub fn from_vec_unchecked(input: Vec<Subtag>) -> Self {
126 Self(input.into())
127 }
128
129 /// A constructor which takes a single [`Subtag`].
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use icu::locale::extensions::private::{Private, Subtag};
135 ///
136 /// let subtag: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
137 ///
138 /// let private = Private::new_single(subtag);
139 /// assert_eq!(&private.to_string(), "x-foo");
140 /// ```
141 pub const fn new_single(input: Subtag) -> Self {
142 Self(ShortBoxSlice::new_single(input))
143 }
144
145 /// Empties the [`Private`] list.
146 ///
147 /// # Examples
148 ///
149 /// ```
150 /// use icu::locale::extensions::private::{Private, Subtag};
151 ///
152 /// let subtag1: Subtag = "foo".parse().expect("Failed to parse a Subtag.");
153 /// let subtag2: Subtag = "bar".parse().expect("Failed to parse a Subtag.");
154 /// let mut private = Private::from_vec_unchecked(vec![subtag1, subtag2]);
155 ///
156 /// assert_eq!(&private.to_string(), "x-foo-bar");
157 ///
158 /// private.clear();
159 ///
160 /// assert_eq!(private, Private::new());
161 /// ```
162 pub fn clear(&mut self) {
163 self.0.clear();
164 }
165
166 #[cfg(feature = "alloc")]
167 pub(crate) fn try_from_iter(iter: &mut SubtagIterator) -> Result<Self, ParseError> {
168 let keys = iter
169 .map(Subtag::try_from_utf8)
170 .collect::<Result<ShortBoxSlice<_>, _>>()?;
171
172 if keys.is_empty() {
173 Err(ParseError::InvalidExtension)
174 } else {
175 Ok(Self(keys))
176 }
177 }
178
179 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F, with_ext: bool) -> Result<(), E>
180 where
181 F: FnMut(&str) -> Result<(), E>,
182 {
183 if self.is_empty() {
184 return Ok(());
185 }
186 if with_ext {
187 f(PRIVATE_EXT_STR)?;
188 }
189 self.deref().iter().map(|t| t.as_str()).try_for_each(f)
190 }
191}
192
193#[cfg(feature = "alloc")]
194impl FromStr for Private {
195 type Err = ParseError;
196
197 #[inline]
198 fn from_str(s: &str) -> Result<Self, Self::Err> {
199 Self::try_from_str(s)
200 }
201}
202
203writeable::impl_display_with_writeable!(Private);
204
205impl writeable::Writeable for Private {
206 fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
207 if self.is_empty() {
208 return Ok(());
209 }
210 sink.write_char(PRIVATE_EXT_CHAR)?;
211 for key in self.iter() {
212 sink.write_char('-')?;
213 writeable::Writeable::write_to(key, sink)?;
214 }
215 Ok(())
216 }
217
218 fn writeable_length_hint(&self) -> writeable::LengthHint {
219 if self.is_empty() {
220 return writeable::LengthHint::exact(0);
221 }
222 let mut result = writeable::LengthHint::exact(1);
223 for key in self.iter() {
224 result += writeable::Writeable::writeable_length_hint(key) + 1;
225 }
226 result
227 }
228}
229
230impl Deref for Private {
231 type Target = [Subtag];
232
233 fn deref(&self) -> &Self::Target {
234 self.0.deref()
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_private_extension_fromstr() {
244 let pe: Private = "x-foo-bar-l-baz".parse().expect("Failed to parse Private");
245 assert_eq!(pe.to_string(), "x-foo-bar-l-baz");
246
247 let pe: Result<Private, _> = "x".parse();
248 assert!(pe.is_err());
249 }
250}