use displaydoc::Display;
use icu_locale::ParseError;
use icu_provider::DataLocale;
use std::hash::Hash;
use std::str::FromStr;
use writeable::Writeable;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DataLocaleFamily {
pub(crate) locale: Option<DataLocale>,
pub(crate) annotations: DataLocaleFamilyAnnotations,
}
impl DataLocaleFamily {
pub fn with_descendants(locale: DataLocale) -> Self {
let annotations = if locale.is_default() {
DataLocaleFamilyAnnotations::single()
} else {
DataLocaleFamilyAnnotations::with_descendants()
};
Self {
locale: Some(locale),
annotations,
}
}
pub fn without_descendants(locale: DataLocale) -> Self {
let annotations = if locale.is_default() {
DataLocaleFamilyAnnotations::single()
} else {
DataLocaleFamilyAnnotations::without_descendants()
};
Self {
locale: Some(locale),
annotations,
}
}
pub fn without_ancestors(locale: DataLocale) -> Self {
let annotations = if locale.is_default() {
DataLocaleFamilyAnnotations::single()
} else {
DataLocaleFamilyAnnotations::without_ancestors()
};
Self {
locale: Some(locale),
annotations,
}
}
pub const fn single(locale: DataLocale) -> Self {
Self {
locale: Some(locale),
annotations: DataLocaleFamilyAnnotations::single(),
}
}
pub const FULL: Self = Self {
locale: None,
annotations: DataLocaleFamilyAnnotations {
include_ancestors: false,
include_descendants: true,
},
};
}
impl Writeable for DataLocaleFamily {
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
if let Some(locale) = self.locale.as_ref() {
self.annotations.write_to(sink)?;
locale.write_to(sink)
} else {
sink.write_str("full")
}
}
fn writeable_length_hint(&self) -> writeable::LengthHint {
if let Some(locale) = self.locale.as_ref() {
self.annotations.writeable_length_hint() + locale.writeable_length_hint()
} else {
writeable::LengthHint::exact(4)
}
}
}
writeable::impl_display_with_writeable!(DataLocaleFamily);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) struct DataLocaleFamilyAnnotations {
pub(crate) include_ancestors: bool,
pub(crate) include_descendants: bool,
}
impl DataLocaleFamilyAnnotations {
#[inline]
pub(crate) const fn with_descendants() -> Self {
Self {
include_ancestors: true,
include_descendants: true,
}
}
#[inline]
pub(crate) const fn without_descendants() -> Self {
Self {
include_ancestors: true,
include_descendants: false,
}
}
#[inline]
pub(crate) const fn without_ancestors() -> Self {
Self {
include_ancestors: false,
include_descendants: true,
}
}
#[inline]
pub(crate) const fn single() -> Self {
Self {
include_ancestors: false,
include_descendants: false,
}
}
}
impl Writeable for DataLocaleFamilyAnnotations {
fn write_to<W: core::fmt::Write + ?Sized>(&self, sink: &mut W) -> core::fmt::Result {
match (self.include_ancestors, self.include_descendants) {
(true, true) => Ok(()),
(true, false) => sink.write_char('^'),
(false, true) => sink.write_char('%'),
(false, false) => sink.write_char('@'),
}
}
fn writeable_length_hint(&self) -> writeable::LengthHint {
writeable::LengthHint::exact(match (self.include_ancestors, self.include_descendants) {
(true, true) => 0,
_ => 1,
})
}
}
#[derive(Debug, Copy, Clone, PartialEq, Display)]
#[non_exhaustive]
pub enum DataLocaleFamilyParseError {
#[displaydoc("{0}")]
Locale(ParseError),
#[displaydoc("Invalid locale family")]
InvalidFamily,
}
impl From<ParseError> for DataLocaleFamilyParseError {
fn from(err: ParseError) -> Self {
Self::Locale(err)
}
}
impl std::error::Error for DataLocaleFamilyParseError {}
impl DataLocaleFamily {
pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, DataLocaleFamilyParseError> {
if code_units == b"full" {
return Ok(Self::FULL);
}
let (annotation, mut locale) = code_units
.split_first()
.ok_or(DataLocaleFamilyParseError::InvalidFamily)?;
let annotations = match annotation {
b'^' => DataLocaleFamilyAnnotations::without_descendants(),
b'%' => DataLocaleFamilyAnnotations::without_ancestors(),
b'@' => DataLocaleFamilyAnnotations::single(),
_ => {
locale = code_units;
DataLocaleFamilyAnnotations::with_descendants()
}
};
Ok(Self {
locale: Some(DataLocale::try_from_utf8(locale)?),
annotations,
})
}
#[inline]
pub fn try_from_str(s: &str) -> Result<Self, DataLocaleFamilyParseError> {
Self::try_from_utf8(s.as_bytes())
}
}
impl FromStr for DataLocaleFamily {
type Err = DataLocaleFamilyParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
#[test]
fn test_locale_family_parsing() {
let valid_families = ["und", "de-CH", "^es", "@pt-BR", "%en-001", "full"];
let invalid_families = ["invalid", "@invalid", "-foo", "@full", "full-001"];
for family_str in valid_families {
let family = family_str.parse::<DataLocaleFamily>().unwrap();
let family_to_str = family.to_string();
assert_eq!(family_str, family_to_str);
}
for family_str in invalid_families {
assert!(family_str.parse::<DataLocaleFamily>().is_err());
}
}