use crate::{Error, Result}; use std::collections::HashSet; use std::str; #[derive(Debug)] pub struct AttributePairs<'a> { input: &'a str, visited_keys: HashSet<&'a str>, } impl<'a> AttributePairs<'a> { pub fn parse(input: &'a str) -> Self { AttributePairs { input, visited_keys: HashSet::new(), } } fn parse_name(&mut self) -> Result<&'a str> { for i in 0..self.input.len() { match self.input.as_bytes()[i] { b'=' => { let (key, _) = self.input.split_at(i); let (_, rest) = self.input.split_at(i + 1); self.input = rest; return Ok(key); } b'A'..=b'Z' | b'0'..=b'9' | b'-' => {} _ => { return Err(Error::invalid_attribute(self.input.to_string())); } } } Err(Error::missing_value(self.input.to_string())) } fn parse_raw_value(&mut self) -> &'a str { let mut in_quote = false; let mut value_end = self.input.len(); let mut next = self.input.len(); for (i, c) in self.input.bytes().enumerate() { match c { b'"' => { in_quote = !in_quote; } b',' if !in_quote => { value_end = i; next = i + 1; break; } _ => {} } } let (value, _) = self.input.split_at(value_end); let (_, rest) = self.input.split_at(next); self.input = rest; value } } impl<'a> Iterator for AttributePairs<'a> { type Item = Result<(&'a str, &'a str)>; fn next(&mut self) -> Option { if self.input.is_empty() { return None; } let result = || -> Result<(&'a str, &'a str)> { let key = self.parse_name()?; self.visited_keys.insert(key); let value = self.parse_raw_value(); Ok((key, value)) }(); Some(result) } } #[cfg(test)] mod test { use super::*; #[test] fn it_works() { let mut pairs = AttributePairs::parse("FOO=BAR,BAR=\"baz,qux\",ABC=12.3"); assert_eq!(pairs.next().map(|x| x.ok()), Some(Some(("FOO", "BAR")))); assert_eq!( pairs.next().map(|x| x.ok()), Some(Some(("BAR", "\"baz,qux\""))) ); assert_eq!(pairs.next().map(|x| x.ok()), Some(Some(("ABC", "12.3")))); assert_eq!(pairs.next().map(|x| x.ok()), None) } }