use crate::errors::LocaleError;
use crate::parser::ParserError;
use std::collections::BTreeMap;
use std::iter::Peekable;
use std::ops::RangeInclusive;
use tinystr::{TinyStr4, TinyStr8};
const KEY_LENGTH: usize = 2;
const TYPE_LENGTH: RangeInclusive<usize> = 3..=8;
const ATTR_LENGTH: RangeInclusive<usize> = 3..=8;
#[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
pub struct UnicodeExtensionList {
keywords: BTreeMap<TinyStr4, Vec<TinyStr8>>,
attributes: Vec<TinyStr8>,
}
fn parse_key(key: &[u8]) -> Result<TinyStr4, ParserError> {
if key.len() != KEY_LENGTH || !key[0].is_ascii_alphanumeric() || !key[1].is_ascii_alphabetic() {
return Err(ParserError::InvalidSubtag);
}
let key = TinyStr4::try_from_utf8(key).map_err(|_| ParserError::InvalidSubtag)?;
Ok(key.to_ascii_lowercase())
}
const TRUE_TYPE: TinyStr8 = tinystr::tinystr!(8, "true");
fn parse_type(t: &[u8]) -> Result<Option<TinyStr8>, ParserError> {
let s = TinyStr8::try_from_utf8(t).map_err(|_| ParserError::InvalidSubtag)?;
if !TYPE_LENGTH.contains(&t.len()) || !s.is_ascii_alphanumeric() {
return Err(ParserError::InvalidSubtag);
}
let s = s.to_ascii_lowercase();
if s == TRUE_TYPE {
Ok(None)
} else {
Ok(Some(s))
}
}
fn parse_attribute(t: &[u8]) -> Result<TinyStr8, ParserError> {
let s = TinyStr8::try_from_utf8(t).map_err(|_| ParserError::InvalidSubtag)?;
if !ATTR_LENGTH.contains(&t.len()) || !s.is_ascii_alphanumeric() {
return Err(ParserError::InvalidSubtag);
}
Ok(s.to_ascii_lowercase())
}
fn is_type(t: &[u8]) -> bool {
let slen = t.len();
TYPE_LENGTH.contains(&slen) && !t.iter().any(|c: &u8| !c.is_ascii_alphanumeric())
}
fn is_attribute(t: &[u8]) -> bool {
let slen = t.len();
ATTR_LENGTH.contains(&slen) && !t.iter().any(|c: &u8| !c.is_ascii_alphanumeric())
}
impl UnicodeExtensionList {
pub fn is_empty(&self) -> bool {
self.keywords.is_empty() && self.attributes.is_empty()
}
pub fn keyword<S: AsRef<[u8]>>(
&self,
key: S,
) -> Result<impl ExactSizeIterator<Item = &str>, LocaleError> {
let keywords: &[_] = match self.keywords.get(&parse_key(key.as_ref())?) {
Some(v) => v,
None => &[],
};
Ok(keywords.iter().map(|s| s.as_ref()))
}
pub fn keyword_keys(&self) -> impl ExactSizeIterator<Item = &str> {
self.keywords.keys().map(|s| s.as_ref())
}
pub fn set_keyword<S: AsRef<[u8]>>(&mut self, key: S, value: &[S]) -> Result<(), LocaleError> {
let key = parse_key(key.as_ref())?;
let t = value
.iter()
.filter_map(|t| parse_type(t.as_ref()).transpose())
.collect::<Result<Vec<_>, _>>()?;
self.keywords.insert(key, t);
Ok(())
}
pub fn remove_keyword<S: AsRef<[u8]>>(&mut self, key: S) -> Result<bool, LocaleError> {
Ok(self.keywords.remove(&parse_key(key.as_ref())?).is_some())
}
pub fn clear_keywords(&mut self) {
self.keywords.clear();
}
pub fn has_attribute<S: AsRef<[u8]>>(&self, attribute: S) -> Result<bool, LocaleError> {
Ok(self
.attributes
.contains(&parse_attribute(attribute.as_ref())?))
}
pub fn attributes(&self) -> impl ExactSizeIterator<Item = &str> {
self.attributes.iter().map(|s| s.as_ref())
}
pub fn set_attribute<S: AsRef<[u8]>>(&mut self, attribute: S) -> Result<(), LocaleError> {
let attribute = parse_attribute(attribute.as_ref())?;
if let Err(idx) = self.attributes.binary_search(&attribute) {
self.attributes.insert(idx, attribute);
}
Ok(())
}
pub fn remove_attribute<S: AsRef<[u8]>>(&mut self, attribute: S) -> Result<bool, LocaleError> {
let attribute = parse_attribute(attribute.as_ref())?;
match self.attributes.binary_search(&attribute) {
Ok(idx) => {
self.attributes.remove(idx);
Ok(true)
}
Err(_) => Ok(false),
}
}
pub fn clear_attributes(&mut self) {
self.attributes.clear();
}
pub(crate) fn try_from_iter<'a>(
iter: &mut Peekable<impl Iterator<Item = &'a [u8]>>,
) -> Result<Self, ParserError> {
let mut uext = Self::default();
let mut st_peek = iter.peek();
let mut current_keyword = None;
let mut current_types = vec![];
while let Some(subtag) = st_peek {
let slen = subtag.len();
if slen == 2 {
if let Some(current_keyword) = current_keyword {
uext.keywords.insert(current_keyword, current_types);
current_types = vec![];
}
current_keyword = Some(parse_key(subtag)?);
iter.next();
} else if current_keyword.is_some() && is_type(subtag) {
if let Some(ty) = parse_type(subtag)? {
current_types.push(ty);
}
iter.next();
} else if is_attribute(subtag) {
uext.attributes.push(parse_attribute(subtag)?);
iter.next();
} else {
break;
}
st_peek = iter.peek();
}
if let Some(current_keyword) = current_keyword {
uext.keywords.insert(current_keyword, current_types);
}
uext.attributes.sort_unstable();
uext.attributes.dedup();
Ok(uext)
}
}
impl std::fmt::Display for UnicodeExtensionList {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.is_empty() {
return Ok(());
}
f.write_str("-u")?;
for attr in &self.attributes {
write!(f, "-{}", attr)?;
}
for (k, t) in &self.keywords {
write!(f, "-{}", k)?;
for v in t {
write!(f, "-{}", v)?;
}
}
Ok(())
}
}