use std::collections::HashMap;
use std::fmt;
use std::ops::Index;
use serde::{Deserialize, Serialize};
use crate::text::TextHandler;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AttributesHandler {
data: HashMap<String, TextHandler>,
}
impl AttributesHandler {
pub fn new(attrs: impl IntoIterator<Item = (String, String)>) -> Self {
Self {
data: attrs
.into_iter()
.map(|(k, v)| (k, TextHandler::new(v)))
.collect(),
}
}
pub fn from_map(map: HashMap<String, String>) -> Self {
Self::new(map)
}
pub fn empty() -> Self {
Self {
data: HashMap::new(),
}
}
pub fn get(&self, key: &str) -> Option<&TextHandler> {
self.data.get(key)
}
pub fn contains_key(&self, key: &str) -> bool {
self.data.contains_key(key)
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &TextHandler)> {
self.data.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn keys(&self) -> impl Iterator<Item = &str> {
self.data.keys().map(|k| k.as_str())
}
pub fn values(&self) -> impl Iterator<Item = &TextHandler> {
self.data.values()
}
pub fn search_values(&self, keyword: &str, partial: bool) -> Vec<AttributesHandler> {
self.data
.iter()
.filter(|(_, v)| match partial {
true => v.contains(keyword),
false => v.as_ref() == keyword,
})
.map(|(k, v)| AttributesHandler::new(std::iter::once((k.clone(), v.to_string()))))
.collect()
}
pub fn json_string(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(&self.data)
}
pub fn json_value(&self) -> Result<serde_json::Value, serde_json::Error> {
serde_json::to_value(&self.data)
}
pub fn into_inner(self) -> HashMap<String, TextHandler> {
self.data
}
}
impl Index<&str> for AttributesHandler {
type Output = TextHandler;
fn index(&self, key: &str) -> &TextHandler {
&self.data[key]
}
}
impl<'a> IntoIterator for &'a AttributesHandler {
type Item = (&'a String, &'a TextHandler);
type IntoIter = std::collections::hash_map::Iter<'a, String, TextHandler>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
impl IntoIterator for AttributesHandler {
type Item = (String, TextHandler);
type IntoIter = std::collections::hash_map::IntoIter<String, TextHandler>;
fn into_iter(self) -> Self::IntoIter {
self.data.into_iter()
}
}
impl fmt::Display for AttributesHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
let mut first = true;
for (k, v) in &self.data {
if !first {
write!(f, ", ")?;
}
write!(f, "\"{k}\": \"{v}\"")?;
first = false;
}
write!(f, "}}")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> AttributesHandler {
AttributesHandler::new([
("class".to_owned(), "main-content".to_owned()),
("id".to_owned(), "article".to_owned()),
("data-page".to_owned(), "1".to_owned()),
])
}
#[test]
fn basic_access() {
let attrs = sample();
assert_eq!(attrs.get("id").unwrap().as_ref(), "article");
assert_eq!(attrs["class"].as_ref(), "main-content");
assert!(attrs.get("missing").is_none());
}
#[test]
fn len_and_contains() {
let attrs = sample();
assert_eq!(attrs.len(), 3);
assert!(attrs.contains_key("class"));
assert!(!attrs.contains_key("href"));
}
#[test]
fn search_values_exact() {
let attrs = sample();
let results = attrs.search_values("article", false);
assert_eq!(results.len(), 1);
assert!(results[0].contains_key("id"));
}
#[test]
fn search_values_partial() {
let attrs = sample();
let results = attrs.search_values("main", true);
assert_eq!(results.len(), 1);
assert!(results[0].contains_key("class"));
}
#[test]
fn json_roundtrip() {
let attrs = sample();
let json_str = attrs.json_string().unwrap();
let value: serde_json::Value = serde_json::from_str(&json_str).unwrap();
assert_eq!(value["id"], "article");
}
#[test]
fn empty() {
let attrs = AttributesHandler::empty();
assert!(attrs.is_empty());
assert_eq!(attrs.len(), 0);
}
#[test]
fn iteration() {
let attrs = sample();
let keys: Vec<&str> = attrs.keys().collect();
assert_eq!(keys.len(), 3);
}
#[test]
fn display() {
let attrs = AttributesHandler::new([("id".to_owned(), "test".to_owned())]);
let s = attrs.to_string();
assert!(s.contains("\"id\": \"test\""));
}
}