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}