use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Locale {
pub language: String,
pub script: Option<String>,
pub region: Option<String>,
pub variants: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseError {
Empty,
InvalidSubtag,
}
fn is_alpha(s: &str) -> bool {
s.bytes().all(|b| b.is_ascii_alphabetic())
}
fn is_digit(s: &str) -> bool {
s.bytes().all(|b| b.is_ascii_digit())
}
fn is_alnum(s: &str) -> bool {
s.bytes().all(|b| b.is_ascii_alphanumeric())
}
impl Locale {
pub fn parse(tag: &str) -> Result<Locale, ParseError> {
if tag.is_empty() {
return Err(ParseError::Empty);
}
let mut parts = tag.split(['-', '_']).peekable();
let mut loc = Locale::default();
let lang = parts.next().ok_or(ParseError::Empty)?;
if lang.is_empty() || !((2..=8).contains(&lang.len()) && is_alpha(lang)) {
return Err(ParseError::InvalidSubtag);
}
loc.language = if lang.eq_ignore_ascii_case("und") {
String::new()
} else {
lang.to_ascii_lowercase()
};
if let Some(&s) = parts.peek() {
if s.len() == 4 && is_alpha(s) {
loc.script = Some(titlecase_subtag(s));
parts.next();
}
}
if let Some(&s) = parts.peek() {
if (s.len() == 2 && is_alpha(s)) || (s.len() == 3 && is_digit(s)) {
loc.region = Some(s.to_ascii_uppercase());
parts.next();
}
}
while let Some(&s) = parts.peek() {
let is_variant = ((5..=8).contains(&s.len()) && is_alnum(s))
|| (s.len() == 4 && s.as_bytes()[0].is_ascii_digit() && is_alnum(s));
if !is_variant {
break;
}
loc.variants.push(s.to_ascii_lowercase());
parts.next();
}
if parts.next().is_some() {
return Err(ParseError::InvalidSubtag);
}
Ok(loc)
}
}
impl core::fmt::Display for Locale {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.language.is_empty() {
f.write_str("und")?;
} else {
f.write_str(&self.language)?;
}
if let Some(s) = &self.script {
write!(f, "-{s}")?;
}
if let Some(r) = &self.region {
write!(f, "-{r}")?;
}
for v in &self.variants {
write!(f, "-{v}")?;
}
Ok(())
}
}
fn titlecase_subtag(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for (i, b) in s.bytes().enumerate() {
out.push(if i == 0 {
b.to_ascii_uppercase() as char
} else {
b.to_ascii_lowercase() as char
});
}
out
}