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}