#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Attribute {
name: String,
value: String,
}
impl Attribute {
#[must_use]
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn value(&self) -> &str {
&self.value
}
}
impl std::fmt::Display for Attribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.value.is_empty() {
write!(f, "{}", self.name)
} else {
write!(f, "{}=\"{}\"", self.name, self.value)
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Attributes {
items: Vec<Attribute>,
}
impl Attributes {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_iter_dedup<I: IntoIterator<Item = Attribute>>(items: I) -> Self {
let mut seen = std::collections::BTreeSet::new();
let deduped = items
.into_iter()
.filter(|a| seen.insert(a.name().to_owned()))
.collect();
Self { items: deduped }
}
#[must_use]
pub fn with(self, attribute: Attribute) -> Self {
if self.items.iter().any(|a| a.name() == attribute.name()) {
self
} else {
let extended: Vec<Attribute> = self
.items
.into_iter()
.chain(std::iter::once(attribute))
.collect();
Self { items: extended }
}
}
pub fn iter(&self) -> std::slice::Iter<'_, Attribute> {
self.items.iter()
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&Attribute> {
self.items
.iter()
.find(|a| a.name().eq_ignore_ascii_case(name))
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
}
impl<'a> IntoIterator for &'a Attributes {
type Item = &'a Attribute;
type IntoIter = std::slice::Iter<'a, Attribute>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
}
impl std::fmt::Display for Attributes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let body = self
.items
.iter()
.map(|a| format!("{a}"))
.collect::<Vec<_>>()
.join(" ");
f.write_str(&body)
}
}