use crate::ordering::SubtagOrderingResult;
use crate::parser::{
parse_locale, parse_locale_with_single_variant_single_keyword_unicode_keyword_extension,
ParserError, ParserMode, SubtagIterator,
};
use crate::{extensions, subtags, LanguageIdentifier};
use alloc::string::String;
use core::cmp::Ordering;
use core::str::FromStr;
use tinystr::TinyAsciiStr;
use writeable::Writeable;
#[derive(Default, PartialEq, Eq, Clone, Hash)]
#[allow(clippy::exhaustive_structs)] pub struct Locale {
pub id: LanguageIdentifier,
pub extensions: extensions::Extensions,
}
#[test]
fn test_sizes() {
let forced_nightly = std::env::var("ICU4X_BUILDING_WITH_FORCED_NIGHTLY").is_ok();
assert_eq!(core::mem::size_of::<subtags::Language>(), 3);
assert_eq!(core::mem::size_of::<subtags::Script>(), 4);
assert_eq!(core::mem::size_of::<subtags::Region>(), 3);
assert_eq!(core::mem::size_of::<subtags::Variant>(), 8);
assert_eq!(core::mem::size_of::<subtags::Variants>(), 32);
assert_eq!(core::mem::size_of::<LanguageIdentifier>(), 48);
assert_eq!(core::mem::size_of::<extensions::transform::Transform>(), 72);
assert_eq!(core::mem::size_of::<Option<LanguageIdentifier>>(), 48);
assert_eq!(core::mem::size_of::<extensions::transform::Fields>(), 24);
assert_eq!(core::mem::size_of::<extensions::unicode::Attributes>(), 24);
assert_eq!(
core::mem::size_of::<extensions::unicode::Keywords>(),
if forced_nightly { 40 } else { 48 }
);
assert_eq!(core::mem::size_of::<Vec<extensions::other::Other>>(), 24);
assert_eq!(core::mem::size_of::<extensions::private::Private>(), 24);
assert_eq!(
core::mem::size_of::<extensions::Extensions>(),
if forced_nightly { 184 } else { 192 }
);
assert_eq!(
core::mem::size_of::<Locale>(),
if forced_nightly { 232 } else { 240 }
);
}
impl Locale {
pub fn try_from_bytes(v: &[u8]) -> Result<Self, ParserError> {
parse_locale(v)
}
pub const UND: Self = Self {
id: LanguageIdentifier::UND,
extensions: extensions::Extensions::new(),
};
pub fn canonicalize<S: AsRef<[u8]>>(input: S) -> Result<String, ParserError> {
let locale = Self::try_from_bytes(input.as_ref())?;
Ok(locale.write_to_string().into_owned())
}
pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
self.strict_cmp_iter(other.split(|b| *b == b'-')).end()
}
pub fn strict_cmp_iter<'l, I>(&self, mut subtags: I) -> SubtagOrderingResult<I>
where
I: Iterator<Item = &'l [u8]>,
{
let r = self.for_each_subtag_str(&mut |subtag| {
if let Some(other) = subtags.next() {
match subtag.as_bytes().cmp(other) {
Ordering::Equal => Ok(()),
not_equal => Err(not_equal),
}
} else {
Err(Ordering::Greater)
}
});
match r {
Ok(_) => SubtagOrderingResult::Subtags(subtags),
Err(o) => SubtagOrderingResult::Ordering(o),
}
}
pub fn normalizing_eq(&self, other: &str) -> bool {
macro_rules! subtag_matches {
($T:ty, $iter:ident, $expected:expr) => {
$iter
.next()
.map(|b| <$T>::try_from_bytes(b) == Ok($expected))
.unwrap_or(false)
};
}
let mut iter = SubtagIterator::new(other.as_bytes());
if !subtag_matches!(subtags::Language, iter, self.id.language) {
return false;
}
if let Some(ref script) = self.id.script {
if !subtag_matches!(subtags::Script, iter, *script) {
return false;
}
}
if let Some(ref region) = self.id.region {
if !subtag_matches!(subtags::Region, iter, *region) {
return false;
}
}
for variant in self.id.variants.iter() {
if !subtag_matches!(subtags::Variant, iter, *variant) {
return false;
}
}
if !self.extensions.is_empty() {
match extensions::Extensions::try_from_iter(&mut iter) {
Ok(exts) => {
if self.extensions != exts {
return false;
}
}
Err(_) => {
return false;
}
}
}
iter.next() == None
}
#[doc(hidden)]
#[allow(clippy::type_complexity)]
pub const fn try_from_bytes_with_single_variant_single_keyword_unicode_extension(
v: &[u8],
) -> Result<
(
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
Option<subtags::Variant>,
Option<(extensions::unicode::Key, Option<TinyAsciiStr<8>>)>,
),
ParserError,
> {
parse_locale_with_single_variant_single_keyword_unicode_keyword_extension(
v,
ParserMode::Locale,
)
}
pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
where
F: FnMut(&str) -> Result<(), E>,
{
self.id.for_each_subtag_str(f)?;
self.extensions.for_each_subtag_str(f)?;
Ok(())
}
}
impl FromStr for Locale {
type Err = ParserError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
Self::try_from_bytes(source.as_bytes())
}
}
impl From<LanguageIdentifier> for Locale {
fn from(id: LanguageIdentifier) -> Self {
Self {
id,
extensions: extensions::Extensions::default(),
}
}
}
impl From<Locale> for LanguageIdentifier {
fn from(loc: Locale) -> Self {
loc.id
}
}
impl AsRef<LanguageIdentifier> for Locale {
fn as_ref(&self) -> &LanguageIdentifier {
&self.id
}
}
impl AsMut<LanguageIdentifier> for Locale {
fn as_mut(&mut self) -> &mut LanguageIdentifier {
&mut self.id
}
}
impl core::fmt::Debug for Locale {
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!(Locale, selff, selff.extensions.is_empty() => selff.id.write_to_string());
#[test]
fn test_writeable() {
use writeable::assert_writeable_eq;
assert_writeable_eq!(Locale::UND, "und");
assert_writeable_eq!("und-001".parse::<Locale>().unwrap(), "und-001");
assert_writeable_eq!("und-Mymr".parse::<Locale>().unwrap(), "und-Mymr");
assert_writeable_eq!("my-Mymr-MM".parse::<Locale>().unwrap(), "my-Mymr-MM");
assert_writeable_eq!(
"my-Mymr-MM-posix".parse::<Locale>().unwrap(),
"my-Mymr-MM-posix",
);
assert_writeable_eq!(
"zh-macos-posix".parse::<Locale>().unwrap(),
"zh-macos-posix",
);
assert_writeable_eq!(
"my-t-my-d0-zawgyi".parse::<Locale>().unwrap(),
"my-t-my-d0-zawgyi",
);
assert_writeable_eq!(
"ar-SA-u-ca-islamic-civil".parse::<Locale>().unwrap(),
"ar-SA-u-ca-islamic-civil",
);
assert_writeable_eq!(
"en-001-x-foo-bar".parse::<Locale>().unwrap(),
"en-001-x-foo-bar",
);
assert_writeable_eq!("und-t-m0-true".parse::<Locale>().unwrap(), "und-t-m0-true",);
}
impl From<subtags::Language> for Locale {
fn from(language: subtags::Language) -> Self {
Self {
id: language.into(),
..Default::default()
}
}
}
impl From<Option<subtags::Script>> for Locale {
fn from(script: Option<subtags::Script>) -> Self {
Self {
id: script.into(),
..Default::default()
}
}
}
impl From<Option<subtags::Region>> for Locale {
fn from(region: Option<subtags::Region>) -> Self {
Self {
id: region.into(),
..Default::default()
}
}
}
impl
From<(
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
)> for Locale
{
fn from(
lsr: (
subtags::Language,
Option<subtags::Script>,
Option<subtags::Region>,
),
) -> Self {
Self {
id: lsr.into(),
..Default::default()
}
}
}