1use crate::extensions::unicode as unicode_ext;
6use crate::subtags::{Language, Region, Script, Subtag, Variant};
7#[cfg(feature = "alloc")]
8use crate::ParseError;
9use crate::{LanguageIdentifier, Locale};
10use core::cmp::Ordering;
11use core::default::Default;
12use core::fmt;
13use core::hash::Hash;
14#[cfg(feature = "alloc")]
15use core::str::FromStr;
16
17)]
52#[non_exhaustive]
53pub struct DataLocale {
54 pub language: Language,
56 pub script: Option<Script>,
58 pub region: Option<Region>,
60 pub variant: Option<Variant>,
62 pub subdivision: Option<Subtag>,
64}
65
66impl Default for DataLocale {
67 fn default() -> Self {
68 Self {
69 language: Language::UNKNOWN,
70 script: None,
71 region: None,
72 variant: None,
73 subdivision: None,
74 }
75 }
76}
77
78impl DataLocale {
79 pub const fn default() -> Self {
81 DataLocale {
82 language: Language::UNKNOWN,
83 script: None,
84 region: None,
85 variant: None,
86 subdivision: None,
87 }
88 }
89}
90
91impl Default for &DataLocale {
92 fn default() -> Self {
93 static DEFAULT: DataLocale = DataLocale::default();
94 &DEFAULT
95 }
96}
97
98impl fmt::Debug for DataLocale {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100 f.write_fmt(format_args!("DataLocale{{{0}}}", self))write!(f, "DataLocale{{{self}}}")
101 }
102}
103
104impl writeable::Writeable for DataLocale {
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 DataLocale {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
::writeable::Writeable::write_to(&self, f)
}
}
impl DataLocale {
pub fn to_string(&self) -> ::writeable::_internal::String {
::writeable::Writeable::write_to_string(self).into_owned()
}
}impl_writeable_for_each_subtag_str_no_test!(DataLocale, selff, selff.script.is_none() && selff.region.is_none() && selff.variant.is_none() && selff.subdivision.is_none() => selff.language.write_to_string());
105
106impl From<LanguageIdentifier> for DataLocale {
107 fn from(langid: LanguageIdentifier) -> Self {
108 Self::from(&langid)
109 }
110}
111
112impl From<Locale> for DataLocale {
113 fn from(locale: Locale) -> Self {
114 Self::from(&locale)
115 }
116}
117
118impl From<&LanguageIdentifier> for DataLocale {
119 fn from(langid: &LanguageIdentifier) -> Self {
120 Self {
121 language: langid.language,
122 script: langid.script,
123 region: langid.region,
124 variant: langid.variants.iter().copied().next(),
125 subdivision: None,
126 }
127 }
128}
129
130impl From<&Locale> for DataLocale {
131 fn from(locale: &Locale) -> Self {
132 let mut r = Self::from(&locale.id);
133
134 r.subdivision = locale
135 .extensions
136 .unicode
137 .keywords
138 .get(&const {
use crate::extensions::unicode::Key;
match Key::try_from_utf8("sd".as_bytes()) {
Ok(r) =>
r,
#[allow(clippy :: panic)]
_ => {
::core::panicking::panic_fmt(format_args!("Invalid extensions::unicode::Key: sd"));
}
}
}unicode_ext::key!("sd"))
139 .and_then(|v| v.as_single_subtag().copied());
140 r
141 }
142}
143
144#[cfg(feature = "alloc")]
145impl FromStr for DataLocale {
146 type Err = ParseError;
147 #[inline]
148 fn from_str(s: &str) -> Result<Self, Self::Err> {
149 Self::try_from_str(s)
150 }
151}
152
153impl DataLocale {
154 #[inline]
155 #[cfg(feature = "alloc")]
157 pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
158 Self::try_from_utf8(s.as_bytes())
159 }
160
161 #[cfg(feature = "alloc")]
163 pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
164 let locale = Locale::try_from_utf8(code_units)?;
165 if locale.id.variants.len() > 1
166 || !locale.extensions.transform.is_empty()
167 || !locale.extensions.private.is_empty()
168 || !locale.extensions.other.is_empty()
169 || !locale.extensions.unicode.attributes.is_empty()
170 {
171 return Err(ParseError::InvalidExtension);
172 }
173
174 let unicode_extensions_count = locale.extensions.unicode.keywords.iter().count();
175
176 if unicode_extensions_count != 0
177 && (unicode_extensions_count != 1
178 || !locale
179 .extensions
180 .unicode
181 .keywords
182 .contains_key(&unicode_ext::key!("sd")))
183 {
184 return Err(ParseError::InvalidExtension);
185 }
186
187 Ok(locale.into())
188 }
189
190 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
191 where
192 F: FnMut(&str) -> Result<(), E>,
193 {
194 f(self.language.as_str())?;
195 if let Some(ref script) = self.script {
196 f(script.as_str())?;
197 }
198 if let Some(ref region) = self.region {
199 f(region.as_str())?;
200 }
201 if let Some(ref single_variant) = self.variant {
202 f(single_variant.as_str())?;
203 }
204 if let Some(ref subdivision) = self.subdivision {
205 f("u")?;
206 f("sd")?;
207 f(subdivision.as_str())?;
208 }
209 Ok(())
210 }
211
212 fn as_tuple(
213 &self,
214 ) -> (
215 Language,
216 Option<Script>,
217 Option<Region>,
218 Option<Variant>,
219 Option<Subtag>,
220 ) {
221 (
222 self.language,
223 self.script,
224 self.region,
225 self.variant,
226 self.subdivision,
227 )
228 }
229
230 pub fn total_cmp(&self, other: &Self) -> Ordering {
234 self.as_tuple().cmp(&other.as_tuple())
235 }
236
237 pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
323 writeable::cmp_utf8(self, other)
324 }
325
326 pub fn is_unknown(&self) -> bool {
338 self.language.is_unknown()
339 && self.script.is_none()
340 && self.region.is_none()
341 && self.variant.is_none()
342 && self.subdivision.is_none()
343 }
344
345 pub fn into_locale(self) -> Locale {
347 Locale {
348 id: LanguageIdentifier {
349 language: self.language,
350 script: self.script,
351 region: self.region,
352 variants: self
353 .variant
354 .map(crate::subtags::Variants::from_variant)
355 .unwrap_or_default(),
356 },
357 extensions: {
358 let mut extensions = crate::extensions::Extensions::default();
359 if let Some(sd) = self.subdivision {
360 extensions.unicode = unicode_ext::Unicode {
361 keywords: unicode_ext::Keywords::new_single(
362 const {
use crate::extensions::unicode::Key;
match Key::try_from_utf8("sd".as_bytes()) {
Ok(r) =>
r,
#[allow(clippy :: panic)]
_ => {
::core::panicking::panic_fmt(format_args!("Invalid extensions::unicode::Key: sd"));
}
}
}unicode_ext::key!("sd"),
363 unicode_ext::Value::from_subtag(Some(sd)),
364 ),
365 ..Default::default()
366 }
367 }
368 extensions
369 },
370 }
371 }
372}
373
374#[test]
375fn test_data_locale_to_string() {
376 struct TestCase {
377 pub locale: &'static str,
378 pub expected: &'static str,
379 }
380
381 for cas in [
382 TestCase {
383 locale: "und",
384 expected: "und",
385 },
386 TestCase {
387 locale: "und-u-sd-sdd",
388 expected: "und-u-sd-sdd",
389 },
390 TestCase {
391 locale: "en-ZA-u-sd-zaa",
392 expected: "en-ZA-u-sd-zaa",
393 },
394 ] {
395 let locale = cas.locale.parse::<DataLocale>().unwrap();
396 writeable::assert_writeable_eq!(locale, cas.expected);
397 }
398}
399
400#[test]
401fn test_data_locale_from_string() {
402 #[derive(Debug)]
403 struct TestCase {
404 pub input: &'static str,
405 pub success: bool,
406 }
407
408 for cas in [
409 TestCase {
410 input: "und",
411 success: true,
412 },
413 TestCase {
414 input: "und-u-cu-gbp",
415 success: false,
416 },
417 TestCase {
418 input: "en-ZA-u-sd-zaa",
419 success: true,
420 },
421 TestCase {
422 input: "en...",
423 success: false,
424 },
425 ] {
426 let data_locale = match (DataLocale::from_str(cas.input), cas.success) {
427 (Ok(l), true) => l,
428 (Err(_), false) => {
429 continue;
430 }
431 (Ok(_), false) => {
432 panic!("DataLocale parsed but it was supposed to fail: {cas:?}");
433 }
434 (Err(_), true) => {
435 panic!("DataLocale was supposed to parse but it failed: {cas:?}");
436 }
437 };
438 writeable::assert_writeable_eq!(data_locale, cas.input);
439 }
440}