use crate::extensions::unicode as unicode_ext;
use crate::subtags::{Language, Region, Script, Subtag, Variant};
#[cfg(feature = "alloc")]
use crate::ParseError;
use crate::{LanguageIdentifier, Locale};
use core::cmp::Ordering;
use core::default::Default;
use core::fmt;
use core::hash::Hash;
#[cfg(feature = "alloc")]
use core::str::FromStr;
#[derive(Clone, Copy, PartialEq, Hash, Eq)]
#[non_exhaustive]
pub struct DataLocale {
pub language: Language,
pub script: Option<Script>,
pub region: Option<Region>,
pub variant: Option<Variant>,
pub subdivision: Option<Subtag>,
}
impl Default for DataLocale {
fn default() -> Self {
Self {
language: Language::UNKNOWN,
script: None,
region: None,
variant: None,
subdivision: None,
}
}
}
impl DataLocale {
pub const fn default() -> Self {
DataLocale {
language: Language::UNKNOWN,
script: None,
region: None,
variant: None,
subdivision: None,
}
}
}
impl Default for &DataLocale {
fn default() -> Self {
static DEFAULT: DataLocale = DataLocale::default();
&DEFAULT
}
}
impl fmt::Debug for DataLocale {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DataLocale{{{self}}}")
}
}
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()));
impl From<LanguageIdentifier> for DataLocale {
fn from(langid: LanguageIdentifier) -> Self {
Self::from(&langid)
}
}
impl From<Locale> for DataLocale {
fn from(locale: Locale) -> Self {
Self::from(&locale)
}
}
impl From<&LanguageIdentifier> for DataLocale {
fn from(langid: &LanguageIdentifier) -> Self {
Self {
language: langid.language,
script: langid.script,
region: langid.region,
variant: langid.variants.iter().copied().next(),
subdivision: None,
}
}
}
impl From<&Locale> for DataLocale {
fn from(locale: &Locale) -> Self {
let mut r = Self::from(&locale.id);
r.subdivision = locale
.extensions
.unicode
.keywords
.get(&unicode_ext::key!("sd"))
.and_then(|v| v.as_single_subtag().copied());
r
}
}
#[cfg(feature = "alloc")]
impl FromStr for DataLocale {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
impl DataLocale {
#[inline]
#[cfg(feature = "alloc")]
pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(s.as_bytes())
}
#[cfg(feature = "alloc")]
pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
let locale = Locale::try_from_utf8(code_units)?;
if locale.id.variants.len() > 1
|| !locale.extensions.transform.is_empty()
|| !locale.extensions.private.is_empty()
|| !locale.extensions.other.is_empty()
|| !locale.extensions.unicode.attributes.is_empty()
{
return Err(ParseError::InvalidExtension);
}
let unicode_extensions_count = locale.extensions.unicode.keywords.iter().count();
if unicode_extensions_count != 0
&& (unicode_extensions_count != 1
|| !locale
.extensions
.unicode
.keywords
.contains_key(&unicode_ext::key!("sd")))
{
return Err(ParseError::InvalidExtension);
}
Ok(locale.into())
}
pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
where
F: FnMut(&str) -> Result<(), E>,
{
f(self.language.as_str())?;
if let Some(ref script) = self.script {
f(script.as_str())?;
}
if let Some(ref region) = self.region {
f(region.as_str())?;
}
if let Some(ref single_variant) = self.variant {
f(single_variant.as_str())?;
}
if let Some(ref subdivision) = self.subdivision {
f("u")?;
f("sd")?;
f(subdivision.as_str())?;
}
Ok(())
}
fn as_tuple(
&self,
) -> (
Language,
Option<Script>,
Option<Region>,
Option<Variant>,
Option<Subtag>,
) {
(
self.language,
self.script,
self.region,
self.variant,
self.subdivision,
)
}
pub fn total_cmp(&self, other: &Self) -> Ordering {
self.as_tuple().cmp(&other.as_tuple())
}
pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
writeable::cmp_utf8(self, other)
}
pub fn is_unknown(&self) -> bool {
self.language.is_unknown()
&& self.script.is_none()
&& self.region.is_none()
&& self.variant.is_none()
&& self.subdivision.is_none()
}
pub fn into_locale(self) -> Locale {
Locale {
id: LanguageIdentifier {
language: self.language,
script: self.script,
region: self.region,
variants: self
.variant
.map(crate::subtags::Variants::from_variant)
.unwrap_or_default(),
},
extensions: {
let mut extensions = crate::extensions::Extensions::default();
if let Some(sd) = self.subdivision {
extensions.unicode = unicode_ext::Unicode {
keywords: unicode_ext::Keywords::new_single(
unicode_ext::key!("sd"),
unicode_ext::Value::from_subtag(Some(sd)),
),
..Default::default()
}
}
extensions
},
}
}
}
#[test]
fn test_data_locale_to_string() {
struct TestCase {
pub locale: &'static str,
pub expected: &'static str,
}
for cas in [
TestCase {
locale: "und",
expected: "und",
},
TestCase {
locale: "und-u-sd-sdd",
expected: "und-u-sd-sdd",
},
TestCase {
locale: "en-ZA-u-sd-zaa",
expected: "en-ZA-u-sd-zaa",
},
] {
let locale = cas.locale.parse::<DataLocale>().unwrap();
writeable::assert_writeable_eq!(locale, cas.expected);
}
}
#[test]
fn test_data_locale_from_string() {
#[derive(Debug)]
struct TestCase {
pub input: &'static str,
pub success: bool,
}
for cas in [
TestCase {
input: "und",
success: true,
},
TestCase {
input: "und-u-cu-gbp",
success: false,
},
TestCase {
input: "en-ZA-u-sd-zaa",
success: true,
},
TestCase {
input: "en...",
success: false,
},
] {
let data_locale = match (DataLocale::from_str(cas.input), cas.success) {
(Ok(l), true) => l,
(Err(_), false) => {
continue;
}
(Ok(_), false) => {
panic!("DataLocale parsed but it was supposed to fail: {cas:?}");
}
(Err(_), true) => {
panic!("DataLocale was supposed to parse but it failed: {cas:?}");
}
};
writeable::assert_writeable_eq!(data_locale, cas.input);
}
}