unic_locale_impl/extensions/
private.rs

1use crate::errors::LocaleError;
2use crate::parser::ParserError;
3
4use tinystr::TinyStr8;
5
6/// A list of [`Unicode Private Extensions`] as defined in [`Unicode Locale
7/// Identifier`] specification.
8///
9/// Those extensions are intended for `pass-through` use.
10///
11/// # Examples
12///
13/// ```
14/// use unic_locale_impl::Locale;
15///
16/// let mut loc: Locale = "en-US-x-foo-faa".parse()
17///     .expect("Parsing failed.");
18///
19/// assert_eq!(loc.extensions.private.has_tag("faa"), Ok(true));
20/// assert_eq!(loc.extensions.private.tags().next(), Some("faa")); // tags got sorted
21/// loc.extensions.private.clear_tags();
22/// assert_eq!(loc.to_string(), "en-US");
23/// ```
24///
25/// [`Unicode Private Extensions`]: https://unicode.org/reports/tr35/#pu_extensions
26/// [`Unicode Locale Identifier`]: https://unicode.org/reports/tr35/#Unicode_locale_identifier
27#[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
28pub struct PrivateExtensionList(Vec<TinyStr8>);
29
30fn parse_value(t: &[u8]) -> Result<TinyStr8, ParserError> {
31    let s = TinyStr8::try_from_utf8(t).map_err(|_| ParserError::InvalidSubtag)?;
32    if t.is_empty() || t.len() > 8 || !s.is_ascii_alphanumeric() {
33        return Err(ParserError::InvalidSubtag);
34    }
35
36    Ok(s.to_ascii_lowercase())
37}
38
39impl PrivateExtensionList {
40    /// Returns `true` if there are no tags in the PrivateExtensionList`.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use unic_locale_impl::Locale;
46    ///
47    /// let mut loc: Locale = "en-US-x-foo".parse()
48    ///     .expect("Parsing failed.");
49    ///
50    /// assert_eq!(loc.extensions.private.is_empty(), false);
51    /// ```
52    pub fn is_empty(&self) -> bool {
53        self.0.is_empty()
54    }
55
56    /// Returns `true` if tag is included in the `PrivateExtensionList`.
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use unic_locale_impl::Locale;
62    ///
63    /// let mut loc: Locale = "en-US-x-foo".parse()
64    ///     .expect("Parsing failed.");
65    ///
66    /// assert_eq!(loc.extensions.private.has_tag("foo")
67    ///               .expect("Getting tag failed."),
68    ///            true);
69    /// ```
70    pub fn has_tag<S: AsRef<[u8]>>(&self, tag: S) -> Result<bool, LocaleError> {
71        Ok(self.0.contains(&parse_value(tag.as_ref())?))
72    }
73
74    /// Returns an iterator over all tags in the `PrivateExtensionList`.
75    ///
76    /// # Examples
77    ///
78    /// ```
79    /// use unic_locale_impl::Locale;
80    ///
81    /// let mut loc: Locale = "en-US-x-foo-bar".parse()
82    ///     .expect("Parsing failed.");
83    ///
84    /// assert_eq!(loc.extensions.private.tags().collect::<Vec<_>>(),
85    ///            &["bar", "foo"]);
86    /// ```
87    pub fn tags(&self) -> impl ExactSizeIterator<Item = &str> {
88        self.0.iter().map(|s| s.as_ref())
89    }
90
91    /// Adds a tag to the `PrivateExtensionList`.
92    ///
93    /// # Examples
94    ///
95    /// ```
96    /// use unic_locale_impl::Locale;
97    ///
98    /// let mut loc: Locale = "en-US".parse()
99    ///     .expect("Parsing failed.");
100    ///
101    /// loc.extensions.private.add_tag("foo")
102    ///     .expect("Adding tag failed.");
103    ///
104    /// assert_eq!(loc.to_string(), "en-US-x-foo");
105    /// ```
106    pub fn add_tag<S: AsRef<[u8]>>(&mut self, tag: S) -> Result<(), LocaleError> {
107        self.0.push(parse_value(tag.as_ref())?);
108        self.0.sort_unstable();
109        Ok(())
110    }
111
112    /// Removes a tag from the `PrivateExtensionList`.
113    ///
114    /// Returns `true` if tag was included in the `PrivateExtensionList` before
115    /// removal.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// use unic_locale_impl::Locale;
121    ///
122    /// let mut loc: Locale = "en-US-x-foo".parse()
123    ///     .expect("Parsing failed.");
124    ///
125    /// assert_eq!(loc.extensions.private.remove_tag("foo")
126    ///               .expect("Removing tag failed."),
127    ///            true);
128    ///
129    /// assert_eq!(loc.to_string(), "en-US");
130    /// ```
131    pub fn remove_tag<S: AsRef<[u8]>>(&mut self, tag: S) -> Result<bool, LocaleError> {
132        let value = parse_value(tag.as_ref())?;
133        match self.0.binary_search(&value) {
134            Ok(idx) => {
135                self.0.remove(idx);
136                Ok(true)
137            }
138            Err(_) => Ok(false),
139        }
140    }
141
142    /// Clears all tags from the `PrivateExtensionList`.
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// use unic_locale_impl::Locale;
148    ///
149    /// let mut loc: Locale = "en-US-x-foo".parse()
150    ///     .expect("Parsing failed.");
151    ///
152    /// loc.extensions.private.clear_tags();
153    /// assert_eq!(loc.to_string(), "en-US");
154    /// ```
155    pub fn clear_tags(&mut self) {
156        self.0.clear();
157    }
158
159    pub(crate) fn try_from_iter<'a>(
160        iter: &mut impl Iterator<Item = &'a [u8]>,
161    ) -> Result<Self, ParserError> {
162        let mut pext = Self::default();
163
164        for subtag in iter {
165            pext.0.push(parse_value(subtag)?);
166        }
167        pext.0.sort_unstable();
168
169        Ok(pext)
170    }
171}
172
173impl std::fmt::Display for PrivateExtensionList {
174    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
175        if self.is_empty() {
176            return Ok(());
177        }
178
179        f.write_str("-x")?;
180
181        for subtag in &self.0 {
182            write!(f, "-{}", subtag)?;
183        }
184        Ok(())
185    }
186}