icu_locale_core/extensions/
mod.rs1pub mod other;
56pub mod private;
57pub mod transform;
58pub mod unicode;
59
60use core::cmp::Ordering;
61
62use other::Other;
63use private::{Private, PRIVATE_EXT_CHAR};
64use transform::{Transform, TRANSFORM_EXT_CHAR};
65use unicode::{Unicode, UNICODE_EXT_CHAR};
66
67#[cfg(feature = "alloc")]
68use alloc::vec::Vec;
69
70use crate::parser::ParseError;
71#[cfg(feature = "alloc")]
72use crate::parser::SubtagIterator;
73use crate::subtags;
74
75#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ExtensionType {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
ExtensionType::Transform =>
::core::fmt::Formatter::write_str(f, "Transform"),
ExtensionType::Unicode =>
::core::fmt::Formatter::write_str(f, "Unicode"),
ExtensionType::Private =>
::core::fmt::Formatter::write_str(f, "Private"),
ExtensionType::Other(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Other",
&__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for ExtensionType {
#[inline]
fn eq(&self, other: &ExtensionType) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(ExtensionType::Other(__self_0),
ExtensionType::Other(__arg1_0)) => __self_0 == __arg1_0,
_ => true,
}
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ExtensionType {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) -> () {
let _: ::core::cmp::AssertParamIsEq<u8>;
}
}Eq, #[automatically_derived]
impl ::core::clone::Clone for ExtensionType {
#[inline]
fn clone(&self) -> ExtensionType {
let _: ::core::clone::AssertParamIsClone<u8>;
*self
}
}Clone, #[automatically_derived]
impl ::core::hash::Hash for ExtensionType {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
let __self_discr = ::core::intrinsics::discriminant_value(self);
::core::hash::Hash::hash(&__self_discr, state);
match self {
ExtensionType::Other(__self_0) =>
::core::hash::Hash::hash(__self_0, state),
_ => {}
}
}
}Hash, #[automatically_derived]
impl ::core::cmp::PartialOrd for ExtensionType {
#[inline]
fn partial_cmp(&self, other: &ExtensionType)
-> ::core::option::Option<::core::cmp::Ordering> {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
match (self, other) {
(ExtensionType::Other(__self_0), ExtensionType::Other(__arg1_0))
=> ::core::cmp::PartialOrd::partial_cmp(__self_0, __arg1_0),
_ =>
::core::cmp::PartialOrd::partial_cmp(&__self_discr,
&__arg1_discr),
}
}
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for ExtensionType {
#[inline]
fn cmp(&self, other: &ExtensionType) -> ::core::cmp::Ordering {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
match ::core::cmp::Ord::cmp(&__self_discr, &__arg1_discr) {
::core::cmp::Ordering::Equal =>
match (self, other) {
(ExtensionType::Other(__self_0),
ExtensionType::Other(__arg1_0)) =>
::core::cmp::Ord::cmp(__self_0, __arg1_0),
_ => ::core::cmp::Ordering::Equal,
},
cmp => cmp,
}
}
}Ord, #[automatically_derived]
impl ::core::marker::Copy for ExtensionType { }Copy)]
77#[non_exhaustive]
78pub enum ExtensionType {
79 Transform,
81 Unicode,
83 Private,
85 Other(u8),
87}
88
89impl ExtensionType {
90 #[allow(dead_code)]
91 pub(crate) const fn try_from_byte_slice(key: &[u8]) -> Result<Self, ParseError> {
92 if let [b] = key {
93 Self::try_from_byte(*b)
94 } else {
95 Err(ParseError::InvalidExtension)
96 }
97 }
98
99 pub(crate) const fn try_from_byte(key: u8) -> Result<Self, ParseError> {
100 let key = key.to_ascii_lowercase();
101 match key as char {
102 UNICODE_EXT_CHAR => Ok(Self::Unicode),
103 TRANSFORM_EXT_CHAR => Ok(Self::Transform),
104 PRIVATE_EXT_CHAR => Ok(Self::Private),
105 'a'..='z' => Ok(Self::Other(key)),
106 _ => Err(ParseError::InvalidExtension),
107 }
108 }
109
110 pub(crate) const fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
111 let &[first] = code_units else {
112 return Err(ParseError::InvalidExtension);
113 };
114
115 Self::try_from_byte(first)
116 }
117}
118
119#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Extensions {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f, "Extensions",
"unicode", &self.unicode, "transform", &self.transform, "private",
&self.private, "other", &&self.other)
}
}Debug, #[automatically_derived]
impl ::core::default::Default for Extensions {
#[inline]
fn default() -> Extensions {
Extensions {
unicode: ::core::default::Default::default(),
transform: ::core::default::Default::default(),
private: ::core::default::Default::default(),
other: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]
impl ::core::cmp::PartialEq for Extensions {
#[inline]
fn eq(&self, other: &Extensions) -> bool {
self.unicode == other.unicode && self.transform == other.transform &&
self.private == other.private && self.other == other.other
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Extensions {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_receiver_is_total_eq(&self) -> () {
let _: ::core::cmp::AssertParamIsEq<Unicode>;
let _: ::core::cmp::AssertParamIsEq<Transform>;
let _: ::core::cmp::AssertParamIsEq<Private>;
let _: ::core::cmp::AssertParamIsEq<&'static [Other]>;
}
}Eq, #[automatically_derived]
impl ::core::clone::Clone for Extensions {
#[inline]
fn clone(&self) -> Extensions {
Extensions {
unicode: ::core::clone::Clone::clone(&self.unicode),
transform: ::core::clone::Clone::clone(&self.transform),
private: ::core::clone::Clone::clone(&self.private),
other: ::core::clone::Clone::clone(&self.other),
}
}
}Clone, #[automatically_derived]
impl ::core::hash::Hash for Extensions {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
::core::hash::Hash::hash(&self.unicode, state);
::core::hash::Hash::hash(&self.transform, state);
::core::hash::Hash::hash(&self.private, state);
::core::hash::Hash::hash(&self.other, state)
}
}Hash)]
121#[non_exhaustive]
122pub struct Extensions {
123 pub unicode: Unicode,
125 pub transform: Transform,
127 pub private: Private,
129 #[cfg(feature = "alloc")]
133 pub other: Vec<Other>,
134 #[cfg(not(feature = "alloc"))]
138 pub other: &'static [Other],
139}
140
141impl Extensions {
142 #[inline]
152 pub const fn new() -> Self {
153 Self {
154 unicode: Unicode::new(),
155 transform: Transform::new(),
156 private: Private::new(),
157 #[cfg(feature = "alloc")]
158 other: Vec::new(),
159 #[cfg(not(feature = "alloc"))]
160 other: &[],
161 }
162 }
163
164 #[inline]
167 pub const fn from_unicode(unicode: Unicode) -> Self {
168 Self {
169 unicode,
170 transform: Transform::new(),
171 private: Private::new(),
172 #[cfg(feature = "alloc")]
173 other: Vec::new(),
174 #[cfg(not(feature = "alloc"))]
175 other: &[],
176 }
177 }
178
179 pub fn is_empty(&self) -> bool {
191 self.unicode.is_empty()
192 && self.transform.is_empty()
193 && self.private.is_empty()
194 && self.other.is_empty()
195 }
196
197 #[expect(clippy::type_complexity)]
198 pub(crate) fn as_tuple(
199 &self,
200 ) -> (
201 (&unicode::Attributes, &unicode::Keywords),
202 (
203 Option<(
204 subtags::Language,
205 Option<subtags::Script>,
206 Option<subtags::Region>,
207 &subtags::Variants,
208 )>,
209 &transform::Fields,
210 ),
211 &private::Private,
212 &[other::Other],
213 ) {
214 (
215 self.unicode.as_tuple(),
216 self.transform.as_tuple(),
217 &self.private,
218 &self.other,
219 )
220 }
221
222 pub fn total_cmp(&self, other: &Self) -> Ordering {
229 self.as_tuple().cmp(&other.as_tuple())
230 }
231
232 #[cfg(feature = "alloc")]
258 pub fn retain_by_type<F>(&mut self, mut predicate: F)
259 where
260 F: FnMut(ExtensionType) -> bool,
261 {
262 if !predicate(ExtensionType::Unicode) {
263 self.unicode.clear();
264 }
265 if !predicate(ExtensionType::Transform) {
266 self.transform.clear();
267 }
268 if !predicate(ExtensionType::Private) {
269 self.private.clear();
270 }
271 #[cfg(feature = "alloc")]
272 self.other
273 .retain(|o| predicate(ExtensionType::Other(o.get_ext_byte())));
274 }
275
276 #[cfg(feature = "alloc")]
277 pub(crate) fn try_from_iter(iter: &mut SubtagIterator) -> Result<Self, ParseError> {
278 let mut unicode = None;
279 let mut transform = None;
280 let mut private = None;
281 let mut other = Vec::new();
282
283 while let Some(subtag) = iter.next() {
284 if subtag.is_empty() {
285 return Err(ParseError::InvalidExtension);
286 }
287
288 let &[subtag] = subtag else {
289 return Err(ParseError::InvalidExtension);
290 };
291
292 match ExtensionType::try_from_byte(subtag) {
293 Ok(ExtensionType::Unicode) => {
294 if unicode.is_some() {
295 return Err(ParseError::DuplicatedExtension);
296 }
297 unicode = Some(Unicode::try_from_iter(iter)?);
298 }
299 Ok(ExtensionType::Transform) => {
300 if transform.is_some() {
301 return Err(ParseError::DuplicatedExtension);
302 }
303 transform = Some(Transform::try_from_iter(iter)?);
304 }
305 Ok(ExtensionType::Private) => {
306 if private.is_some() {
307 return Err(ParseError::DuplicatedExtension);
308 }
309 private = Some(Private::try_from_iter(iter)?);
310 }
311 Ok(ExtensionType::Other(ext)) => {
312 if other.iter().any(|o: &Other| o.get_ext_byte() == ext) {
313 return Err(ParseError::DuplicatedExtension);
314 }
315 let parsed = Other::try_from_iter(ext, iter)?;
316 if let Err(idx) = other.binary_search(&parsed) {
317 other.insert(idx, parsed);
318 } else {
319 return Err(ParseError::InvalidExtension);
320 }
321 }
322 _ => return Err(ParseError::InvalidExtension),
323 }
324 }
325
326 Ok(Self {
327 unicode: unicode.unwrap_or_default(),
328 transform: transform.unwrap_or_default(),
329 private: private.unwrap_or_default(),
330 other,
331 })
332 }
333
334 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
335 where
336 F: FnMut(&str) -> Result<(), E>,
337 {
338 let mut wrote_tu = false;
339 self.other.iter().try_for_each(|other| {
341 if other.get_ext() > TRANSFORM_EXT_CHAR && !wrote_tu {
342 self.transform.for_each_subtag_str(f, true)?;
345 self.unicode.for_each_subtag_str(f, true)?;
346 wrote_tu = true;
347 }
348 other.for_each_subtag_str(f, true)?;
349 Ok(())
350 })?;
351
352 if !wrote_tu {
353 self.transform.for_each_subtag_str(f, true)?;
354 self.unicode.for_each_subtag_str(f, true)?;
355 }
356
357 self.private.for_each_subtag_str(f, true)?;
362 Ok(())
363 }
364}
365
366impl writeable::Writeable for Extensions {
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W)
-> core::fmt::Result {
let mut initial = true;
self.for_each_subtag_str(&mut |subtag|
{
if initial {
initial = false;
} else { sink.write_char('-')?; }
sink.write_str(subtag)
})
}
#[inline]
fn writeable_length_hint(&self) -> writeable::LengthHint {
let mut result = writeable::LengthHint::exact(0);
let mut initial = true;
self.for_each_subtag_str::<core::convert::Infallible,
_>(&mut |subtag|
{
if initial { initial = false; } else { result += 1; }
result += subtag.len();
Ok(())
}).expect("infallible");
result
}
}
impl core::fmt::Display for Extensions {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
::writeable::Writeable::write_to(&self, f)
}
}impl_writeable_for_each_subtag_str_no_test!(Extensions);
367
368#[test]
369fn test_writeable() {
370 use crate::Locale;
371 use writeable::assert_writeable_eq;
372 assert_writeable_eq!(Extensions::new(), "");
373 assert_writeable_eq!(
374 "my-t-my-d0-zawgyi".parse::<Locale>().unwrap().extensions,
375 "t-my-d0-zawgyi",
376 );
377 assert_writeable_eq!(
378 "ar-SA-u-ca-islamic-civil"
379 .parse::<Locale>()
380 .unwrap()
381 .extensions,
382 "u-ca-islamic-civil",
383 );
384 assert_writeable_eq!(
385 "en-001-x-foo-bar".parse::<Locale>().unwrap().extensions,
386 "x-foo-bar",
387 );
388 assert_writeable_eq!(
389 "und-t-m0-true".parse::<Locale>().unwrap().extensions,
390 "t-m0-true",
391 );
392 assert_writeable_eq!(
393 "und-a-foo-t-foo-u-foo-w-foo-z-foo-x-foo"
394 .parse::<Locale>()
395 .unwrap()
396 .extensions,
397 "a-foo-t-foo-u-foo-w-foo-z-foo-x-foo",
398 );
399}