use core::borrow::Borrow;
use core::cmp::Ordering;
#[cfg(feature = "alloc")]
use core::iter::FromIterator;
#[cfg(feature = "alloc")]
use core::str::FromStr;
use litemap::LiteMap;
use super::Key;
use super::Value;
#[cfg(feature = "alloc")]
use crate::parser::ParseError;
#[cfg(feature = "alloc")]
use crate::parser::SubtagIterator;
use crate::shortvec::ShortBoxSlice;
#[derive(Clone, PartialEq, Eq, Debug, Default, Hash, PartialOrd, Ord)]
pub struct Keywords(LiteMap<Key, Value, ShortBoxSlice<(Key, Value)>>);
impl Keywords {
#[inline]
pub const fn new() -> Self {
Self(LiteMap::new())
}
#[inline]
pub const fn new_single(key: Key, value: Value) -> Self {
Self(LiteMap::from_sorted_store_unchecked(
ShortBoxSlice::new_single((key, value)),
))
}
#[inline]
#[cfg(feature = "alloc")]
pub fn try_from_str(s: &str) -> Result<Self, ParseError> {
Self::try_from_utf8(s.as_bytes())
}
#[cfg(feature = "alloc")]
pub fn try_from_utf8(code_units: &[u8]) -> Result<Self, ParseError> {
let mut iter = SubtagIterator::new(code_units);
Self::try_from_iter(&mut iter)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
Key: Borrow<Q>,
Q: Ord,
{
self.0.contains_key(key)
}
pub fn get<Q>(&self, key: &Q) -> Option<&Value>
where
Key: Borrow<Q>,
Q: Ord,
{
self.0.get(key)
}
#[cfg(feature = "alloc")]
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut Value>
where
Key: Borrow<Q>,
Q: Ord,
{
self.0.get_mut(key)
}
#[cfg(feature = "alloc")]
pub fn set(&mut self, key: Key, value: Value) -> Option<Value> {
self.0.insert(key, value)
}
#[cfg(feature = "alloc")]
pub fn remove<Q: Borrow<Key>>(&mut self, key: Q) -> Option<Value> {
self.0.remove(key.borrow())
}
pub fn clear(&mut self) -> Self {
core::mem::take(self)
}
#[cfg(feature = "alloc")]
pub fn retain_by_key<F>(&mut self, mut predicate: F)
where
F: FnMut(&Key) -> bool,
{
self.0.retain(|k, _| predicate(k))
}
pub fn strict_cmp(&self, other: &[u8]) -> Ordering {
writeable::cmp_utf8(self, other)
}
#[cfg(feature = "alloc")]
pub(crate) fn try_from_iter(iter: &mut SubtagIterator) -> Result<Self, ParseError> {
let mut keywords = LiteMap::new();
let mut current_keyword = None;
let mut current_value = ShortBoxSlice::new();
while let Some(subtag) = iter.peek() {
let slen = subtag.len();
if slen == 2 {
if let Some(kw) = current_keyword.take() {
keywords.try_insert(kw, Value::from_short_slice_unchecked(current_value));
current_value = ShortBoxSlice::new();
}
current_keyword = Some(Key::try_from_utf8(subtag)?);
} else if current_keyword.is_some() {
match Value::parse_subtag_from_utf8(subtag) {
Ok(Some(t)) => current_value.push(t),
Ok(None) => {}
Err(_) => break,
}
} else {
break;
}
iter.next();
}
if let Some(kw) = current_keyword.take() {
keywords.try_insert(kw, Value::from_short_slice_unchecked(current_value));
}
Ok(keywords.into())
}
pub fn iter(&self) -> impl Iterator<Item = (&Key, &Value)> {
self.0.iter()
}
pub(crate) fn for_each_subtag_str<E, F>(&self, f: &mut F) -> Result<(), E>
where
F: FnMut(&str) -> Result<(), E>,
{
for (k, v) in self.0.iter() {
f(k.as_str())?;
v.for_each_subtag_str(f)?;
}
Ok(())
}
#[cfg(feature = "alloc")]
pub fn extend_from_keywords(&mut self, other: Keywords) {
for (key, value) in other.0 {
self.0.insert(key, value);
}
}
#[cfg(test)]
pub(crate) fn from_tuple_vec(v: Vec<(Key, Value)>) -> Self {
v.into_iter().collect()
}
}
impl From<LiteMap<Key, Value, ShortBoxSlice<(Key, Value)>>> for Keywords {
fn from(map: LiteMap<Key, Value, ShortBoxSlice<(Key, Value)>>) -> Self {
Self(map)
}
}
#[cfg(feature = "alloc")]
impl FromIterator<(Key, Value)> for Keywords {
fn from_iter<I: IntoIterator<Item = (Key, Value)>>(iter: I) -> Self {
LiteMap::from_iter(iter).into()
}
}
#[cfg(feature = "alloc")]
impl FromStr for Keywords {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from_str(s)
}
}
impl_writeable_for_key_value!(Keywords, "ca", "islamic-civil", "mm", "mm");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keywords_fromstr() {
let kw: Keywords = "hc-h12".parse().expect("Failed to parse Keywords");
assert_eq!(kw.to_string(), "hc-h12");
}
}