1use crate::ParseError;
6use crate::extensions::unicode as unicode_ext;
7use crate::parser::{
8 ParserMode, SubtagIterator,
9 parse_locale_with_single_variant_single_keyword_unicode_extension_from_iter,
10};
11use crate::preferences::{LocalePreferences, extensions::unicode::keywords::RegionalSubdivision};
12use crate::subtags::{Language, Region, Script, Subtag, Variant};
13use crate::{LanguageIdentifier, Locale};
14use core::cmp::Ordering;
15use core::default::Default;
16use core::fmt;
17use core::hash::Hash;
18use core::str::FromStr;
19
20#[derive(#[automatically_derived]
impl ::core::clone::Clone for DataLocale {
#[inline]
fn clone(&self) -> DataLocale {
let _: ::core::clone::AssertParamIsClone<Language>;
let _: ::core::clone::AssertParamIsClone<Option<Script>>;
let _: ::core::clone::AssertParamIsClone<Option<Region>>;
let _: ::core::clone::AssertParamIsClone<Option<Variant>>;
let _: ::core::clone::AssertParamIsClone<Option<Subtag>>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for DataLocale { }Copy)]
73#[non_exhaustive]
74pub struct DataLocale {
75 pub language: Language,
77 pub script: Option<Script>,
79 pub region: Option<Region>,
81 pub variant: Option<Variant>,
83 pub subdivision: Option<Subtag>,
86}
87
88impl PartialEq for DataLocale {
89 fn eq(&self, other: &Self) -> bool {
90 self.as_tuple() == other.as_tuple()
91 }
92}
93
94impl Eq for DataLocale {}
95
96impl Hash for DataLocale {
97 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
98 self.as_tuple().hash(state);
99 }
100}
101
102impl Default for DataLocale {
103 fn default() -> Self {
104 Self {
105 language: Language::UNKNOWN,
106 script: None,
107 region: None,
108 variant: None,
109 subdivision: None,
110 }
111 }
112}
113
114impl DataLocale {
115 pub const fn default() -> Self {
117 DataLocale {
118 language: Language::UNKNOWN,
119 script: None,
120 region: None,
121 variant: None,
122 subdivision: None,
123 }
124 }
125}
126
127impl Default for &DataLocale {
128 fn default() -> Self {
129 static DEFAULT: DataLocale = DataLocale::default();
130 &DEFAULT
131 }
132}
133
134impl fmt::Debug for DataLocale {
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 f.write_fmt(format_args!("DataLocale{{{0}}}", self))write!(f, "DataLocale{{{self}}}")
137 }
138}
139
140impl 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
}
fn writeable_borrow(&self) -> Option<&str> {
let selff = self;
if selff.script.is_none() && selff.region.is_none() &&
selff.variant.is_none() && selff.subdivision.is_none() {
Some(selff.language.as_str())
} else { None }
}
}
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_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() => Some(selff.language.as_str()));
141
142impl From<LanguageIdentifier> for DataLocale {
143 fn from(langid: LanguageIdentifier) -> Self {
144 Self::from(&langid)
145 }
146}
147
148impl From<Locale> for DataLocale {
149 fn from(locale: Locale) -> Self {
150 Self::from(&locale)
151 }
152}
153
154impl From<&LanguageIdentifier> for DataLocale {
155 fn from(langid: &LanguageIdentifier) -> Self {
156 Self {
157 language: langid.language,
158 script: langid.script,
159 region: langid.region,
160 variant: langid.variants.iter().copied().next(),
161 subdivision: None,
162 }
163 }
164}
165
166impl From<&Locale> for DataLocale {
167 fn from(locale: &Locale) -> Self {
168 LocalePreferences::from(locale).to_data_locale_language_priority()
169 }
170}
171
172impl From<(Language, Option<Script>, Option<Region>)> for DataLocale {
173 fn from((l, s, r): (Language, Option<Script>, Option<Region>)) -> Self {
174 Self::from_parts(
175 l,
176 s,
177 r.map(|r| unicode_ext::SubdivisionId::new(r, unicode_ext::SubdivisionSuffix::UNKNOWN)),
178 None,
179 )
180 }
181}
182
183impl FromStr for DataLocale {
184 type Err = ParseError;
185 #[inline]
186 fn from_str(s: &str) -> Result<Self, Self::Err> {
187 Self::try_from_str(s)
188 }
189}
190
191impl DataLocale {
192 #[inline]
193 pub const fn try_from_str(s: &str) -> Result<Self, ParseError> {
195 Self::try_from_utf8(s.as_bytes())
196 }
197
198 pub const fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
200 let (language, script, region, variant, keyword) =
201 match parse_locale_with_single_variant_single_keyword_unicode_extension_from_iter(
202 SubtagIterator::new(code_units),
203 ParserMode::Locale,
204 ) {
205 Ok(o) => o,
206 Err(e) => return Err(e),
207 };
208
209 let subdivision = if let Some((key, value)) = keyword {
210 if let Some(value) = value
211 && let RegionalSubdivision::UNICODE_EXTENSION_KEY = key
212 {
213 let Ok(subdivision) = unicode_ext::SubdivisionId::try_from_subtag(value) else {
214 return Err(ParseError::InvalidExtension);
215 };
216 if let Some(region) = region
217 && (region.into_raw()[0] != subdivision.region.into_raw()[0]
218 || region.into_raw()[1] != subdivision.region.into_raw()[1]
219 || region.into_raw()[2] != subdivision.region.into_raw()[2])
220 {
221 Some(unicode_ext::SubdivisionId::new(
222 region,
223 unicode_ext::SubdivisionSuffix::UNKNOWN,
224 ))
225 } else {
226 Some(subdivision)
227 }
228 } else {
229 return Err(ParseError::InvalidExtension);
230 }
231 } else if let Some(region) = region {
232 Some(unicode_ext::SubdivisionId::new(
233 region,
234 unicode_ext::SubdivisionSuffix::UNKNOWN,
235 ))
236 } else {
237 None
238 };
239
240 Ok(Self::from_parts(language, script, subdivision, variant))
241 }
242
243 pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
244 where
245 F: FnMut(&str) -> Result<(), E>,
246 {
247 f(self.language.as_str())?;
248 if let Some(ref script) = self.script {
249 f(script.as_str())?;
250 }
251 if let Some(ref region) = self.region {
252 f(region.as_str())?;
253 }
254 if let Some(ref single_variant) = self.variant {
255 f(single_variant.as_str())?;
256 }
257 if let Some(extensions) = self.extensions() {
258 extensions.for_each_subtag_str(f)?;
259 }
260 Ok(())
261 }
262
263 fn region_and_subdivision(&self) -> Option<unicode_ext::SubdivisionId> {
264 self.subdivision
265 .and_then(|s| unicode_ext::SubdivisionId::try_from_str(s.as_str()).ok())
266 .or_else(|| {
267 self.region.map(|region| unicode_ext::SubdivisionId {
268 region,
269 suffix: unicode_ext::SubdivisionSuffix::UNKNOWN,
270 })
271 })
272 }
273
274 fn as_tuple(
275 &self,
276 ) -> (
277 Language,
278 Option<Script>,
279 Option<unicode_ext::SubdivisionId>,
280 Option<Variant>,
281 ) {
282 (
283 self.language,
284 self.script,
285 self.region_and_subdivision(),
286 self.variant,
287 )
288 }
289
290 pub(crate) const fn from_parts(
291 language: Language,
292 script: Option<Script>,
293 region: Option<unicode_ext::SubdivisionId>,
294 variant: Option<Variant>,
295 ) -> Self {
296 Self {
297 language,
298 script,
299 region: if let Some(r) = region {
300 Some(r.region)
301 } else {
302 None
303 },
304 variant,
305 subdivision: if let Some(r) = region {
306 Some(r.into_subtag())
307 } else {
308 None
309 },
310 }
311 }
312
313 pub fn total_cmp(&self, other: &Self) -> Ordering {
317 self.as_tuple().cmp(&other.as_tuple())
318 }
319
320 pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
406 writeable::cmp_utf8(self, other)
407 }
408
409 pub fn is_unknown(&self) -> bool {
421 self.language.is_unknown()
422 && self.script.is_none()
423 && self.region.is_none()
424 && self.variant.is_none()
425 && self.subdivision.is_none()
426 }
427
428 pub fn into_locale(self) -> Locale {
430 Locale {
431 id: LanguageIdentifier {
432 language: self.language,
433 script: self.script,
434 region: self.region,
435 variants: self
436 .variant
437 .map(crate::subtags::Variants::from_variant)
438 .unwrap_or_default(),
439 },
440 extensions: self.extensions().unwrap_or_default(),
441 }
442 }
443
444 fn extensions(&self) -> Option<crate::extensions::Extensions> {
445 Some(crate::extensions::Extensions {
446 unicode: unicode_ext::Unicode {
447 keywords: unicode_ext::Keywords::new_single(
448 RegionalSubdivision::UNICODE_EXTENSION_KEY,
449 RegionalSubdivision(
450 self.region_and_subdivision()
451 .filter(|sd| !sd.suffix.is_unknown())?,
452 )
453 .into(),
454 ),
455 ..Default::default()
456 },
457 ..Default::default()
458 })
459 }
460}
461
462#[test]
463fn test_data_locale_to_string() {
464 struct TestCase {
465 pub locale: &'static str,
466 pub expected: &'static str,
467 }
468
469 for cas in [
470 TestCase {
471 locale: "und",
472 expected: "und",
473 },
474 TestCase {
475 locale: "und-u-sd-sdd",
476 expected: "und-SD-u-sd-sdd",
477 },
478 TestCase {
479 locale: "en-ZA-u-sd-zaa",
480 expected: "en-ZA-u-sd-zaa",
481 },
482 TestCase {
483 locale: "en-ZA-u-sd-sdd",
484 expected: "en-ZA",
485 },
486 ] {
487 let locale = cas.locale.parse::<DataLocale>().unwrap();
488 writeable::assert_writeable_eq!(locale, cas.expected);
489 }
490}
491
492#[test]
493fn test_data_locale_from_string() {
494 #[derive(Debug)]
495 struct TestCase {
496 pub input: &'static str,
497 pub success: bool,
498 }
499
500 for cas in [
501 TestCase {
502 input: "und",
503 success: true,
504 },
505 TestCase {
506 input: "und-u-cu-gbp",
507 success: false,
508 },
509 TestCase {
510 input: "en-ZA-u-sd-zaa",
511 success: true,
512 },
513 TestCase {
514 input: "en...",
515 success: false,
516 },
517 ] {
518 let data_locale = match (DataLocale::from_str(cas.input), cas.success) {
519 (Ok(l), true) => l,
520 (Err(_), false) => {
521 continue;
522 }
523 (Ok(_), false) => {
524 panic!("DataLocale parsed but it was supposed to fail: {cas:?}");
525 }
526 (Err(_), true) => {
527 panic!("DataLocale was supposed to parse but it failed: {cas:?}");
528 }
529 };
530 writeable::assert_writeable_eq!(data_locale, cas.input);
531 }
532}