use crate::errors::LocaleError;
use crate::parser::ParserError;
use unic_langid_impl::LanguageIdentifier;
use std::collections::BTreeMap;
use std::iter::Peekable;
use tinystr::{TinyStr4, TinyStr8};
#[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
pub struct TransformExtensionList {
tlang: Option<LanguageIdentifier>,
tfields: BTreeMap<TinyStr4, Vec<TinyStr8>>,
}
fn parse_tkey(key: &[u8]) -> Result<TinyStr4, ParserError> {
if key.len() != 2 || !key[0].is_ascii_alphabetic() || !key[1].is_ascii_digit() {
return Err(ParserError::InvalidSubtag);
}
let tkey = TinyStr4::try_from_utf8(key).map_err(|_| ParserError::InvalidSubtag)?;
Ok(tkey.to_ascii_lowercase())
}
const TRUE_TVALUE: TinyStr8 = tinystr::tinystr!(8, "true");
fn parse_tvalue(t: &[u8]) -> Result<Option<TinyStr8>, ParserError> {
let s = TinyStr8::try_from_utf8(t).map_err(|_| ParserError::InvalidSubtag)?;
if t.len() < 3 || t.len() > 8 || !s.is_ascii_alphanumeric() {
return Err(ParserError::InvalidSubtag);
}
let s = s.to_ascii_lowercase();
if s == TRUE_TVALUE {
Ok(None)
} else {
Ok(Some(s))
}
}
fn is_language_subtag(t: &[u8]) -> bool {
let slen = t.len();
((2..=8).contains(&slen) || slen == 4) && !t.iter().any(|c: &u8| !c.is_ascii_alphabetic())
}
impl TransformExtensionList {
pub fn is_empty(&self) -> bool {
self.tlang.is_none() && self.tfields.is_empty()
}
pub fn tlang(&self) -> Option<&LanguageIdentifier> {
self.tlang.as_ref()
}
pub fn set_tlang(&mut self, tlang: LanguageIdentifier) -> Result<(), LocaleError> {
self.tlang = Some(tlang);
Ok(())
}
pub fn clear_tlang(&mut self) {
self.tlang = None;
}
pub fn tfield<S: AsRef<[u8]>>(
&self,
tkey: S,
) -> Result<impl ExactSizeIterator<Item = &str>, LocaleError> {
let tfields: &[_] = match self.tfields.get(&parse_tkey(tkey.as_ref())?) {
Some(v) => v,
None => &[],
};
Ok(tfields.iter().map(|s| s.as_ref()))
}
pub fn tfield_keys(&self) -> impl ExactSizeIterator<Item = &str> {
self.tfields.keys().map(|s| s.as_ref())
}
pub fn set_tfield<S: AsRef<[u8]>>(&mut self, tkey: S, tvalue: &[S]) -> Result<(), LocaleError> {
let tkey = parse_tkey(tkey.as_ref())?;
let t = tvalue
.iter()
.filter_map(|val| parse_tvalue(val.as_ref()).transpose())
.collect::<Result<Vec<_>, _>>()?;
self.tfields.insert(tkey, t);
Ok(())
}
pub fn remove_tfield<S: AsRef<[u8]>>(&mut self, tkey: S) -> Result<bool, LocaleError> {
Ok(self.tfields.remove(&parse_tkey(tkey.as_ref())?).is_some())
}
pub fn clear_tfields(&mut self) {
self.tfields.clear();
}
pub(crate) fn try_from_iter<'a>(
iter: &mut Peekable<impl Iterator<Item = &'a [u8]>>,
) -> Result<Self, ParserError> {
let mut text = Self::default();
let mut st_peek = iter.peek();
let mut current_tkey = None;
let mut current_tvalue = vec![];
while let Some(subtag) = st_peek {
let slen = subtag.len();
if slen == 2 && subtag[0].is_ascii_alphabetic() && subtag[1].is_ascii_digit() {
if let Some(current_tkey) = current_tkey {
text.tfields.insert(current_tkey, current_tvalue);
current_tvalue = vec![];
}
current_tkey = Some(parse_tkey(subtag)?);
iter.next();
} else if current_tkey.is_some() {
if let Some(tval) = parse_tvalue(subtag)? {
current_tvalue.push(tval);
}
iter.next();
} else if is_language_subtag(subtag) {
text.tlang = Some(
LanguageIdentifier::try_from_iter(iter, true)
.map_err(|_| ParserError::InvalidLanguage)?,
);
} else {
break;
}
st_peek = iter.peek();
}
if let Some(current_keyword) = current_tkey {
text.tfields.insert(current_keyword, current_tvalue);
}
Ok(text)
}
}
impl std::fmt::Display for TransformExtensionList {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.is_empty() {
return Ok(());
}
f.write_str("-t")?;
if let Some(tlang) = &self.tlang {
write!(f, "-{}", tlang)?;
}
for (k, t) in &self.tfields {
write!(f, "-{}", k)?;
for v in t {
write!(f, "-{}", v)?;
}
}
Ok(())
}
}