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(#[automatically_derived]
impl ::core::clone::Clone for Private {
    #[inline]
    fn clone(&self) -> Private {
        Private(::core::clone::Clone::clone(&self.0))
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for Private {
    #[inline]
    fn eq(&self, other: &Private) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Private {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) -> () {
        let _: ::core::cmp::AssertParamIsEq<ShortBoxSlice<Subtag>>;
    }
}Eq, #[automatically_derived]
impl ::core::fmt::Debug for Private {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Private",
            &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for Private {
    #[inline]
    fn default() -> Private { Private(::core::default::Default::default()) }
}Default, #[automatically_derived]
impl ::core::hash::Hash for Private {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash, #[automatically_derived]
impl ::core::cmp::PartialOrd for Private {
    #[inline]
    fn partial_cmp(&self, other: &Private)
        -> ::core::option::Option<::core::cmp::Ordering> {
        ::core::cmp::PartialOrd::partial_cmp(&self.0, &other.0)
    }
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for Private {
    #[inline]
    fn cmp(&self, other: &Private) -> ::core::cmp::Ordering {
        ::core::cmp::Ord::cmp(&self.0, &other.0)
    }
}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
210/// This trait is implemented for compatibility with [`fmt!`](alloc::fmt).
/// To create a string, [`Writeable::write_to_string`] is usually more efficient.
impl core::fmt::Display for Private {
    #[inline]
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        ::writeable::Writeable::write_to(&self, f)
    }
}writeable::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}