fixup! block attributes

This commit is contained in:
Noah Hellman 2023-02-01 21:55:51 +01:00
parent 82e1fd74f5
commit 4cb9c07cfc

View file

@ -24,28 +24,30 @@ pub fn valid<I: Iterator<Item = char>>(chars: I) -> (usize, bool) {
(p.pos, has_attr) (p.pos, has_attr)
} }
/// A collection of attributes, i.e. a key-value map.
// Attributes are relatively rare, we choose to pay 8 bytes always and sometimes an extra // Attributes are relatively rare, we choose to pay 8 bytes always and sometimes an extra
// indirection instead of always 24 bytes. // indirection instead of always 24 bytes.
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Attributes<'s>(Option<Box<Vec<(&'s str, CowStr<'s>)>>>); pub struct Attributes<'s>(Option<Box<Vec<(&'s str, CowStr<'s>)>>>);
impl<'s> Attributes<'s> { impl<'s> Attributes<'s> {
/// Create an empty collection.
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
#[must_use] #[must_use]
pub fn take(&mut self) -> Self { pub(crate) fn take(&mut self) -> Self {
Self(self.0.take()) Self(self.0.take())
} }
pub(crate) fn parse<S: DiscontinuousString<'s>>(&mut self, input: S) -> bool { pub(crate) fn parse<S: DiscontinuousString<'s>>(&mut self, input: S) -> bool {
for elem in Parser::new(input.chars()) { for elem in Parser::new(input.chars()) {
match elem { match elem {
Element::Class(c) => self.add("class", input.src(c)), Element::Class(c) => self.insert("class", input.src(c)),
Element::Identifier(i) => self.add("id", input.src(i)), Element::Identifier(i) => self.insert("id", input.src(i)),
Element::Attribute(a, v) => self.add( Element::Attribute(a, v) => self.insert(
match input.src(a) { match input.src(a) {
CowStr::Owned(_) => panic!(), CowStr::Owned(_) => panic!(),
CowStr::Borrowed(s) => s, CowStr::Borrowed(s) => s,
@ -59,7 +61,7 @@ impl<'s> Attributes<'s> {
} }
/// Combine all attributes from both objects, prioritizing self on conflicts. /// Combine all attributes from both objects, prioritizing self on conflicts.
pub fn union(&mut self, other: Self) { pub(crate) fn union(&mut self, other: Self) {
if let Some(attrs0) = &mut self.0 { if let Some(attrs0) = &mut self.0 {
if let Some(mut attrs1) = other.0 { if let Some(mut attrs1) = other.0 {
for (key, val) in attrs1.drain(..) { for (key, val) in attrs1.drain(..) {
@ -73,38 +75,40 @@ impl<'s> Attributes<'s> {
} }
} }
fn add(&mut self, attr: &'s str, val: CowStr<'s>) { /// Insert an attribute. If the attribute already exists, the previous value will be
/// overwritten, unless it is a "class" attribute. In that case the provided value will be
/// appended to the existing value.
pub fn insert(&mut self, key: &'s str, val: CowStr<'s>) {
if self.0.is_none() { if self.0.is_none() {
self.0 = Some(Vec::new().into()); self.0 = Some(Vec::new().into());
}; };
let attrs = self.0.as_mut().unwrap(); let attrs = self.0.as_mut().unwrap();
if let Some(i) = attrs.iter().position(|(a, _)| *a == attr) { if let Some(i) = attrs.iter().position(|(k, _)| *k == key) {
let prev = &mut attrs[i].1; let prev = &mut attrs[i].1;
if attr == "class" { if key == "class" {
*prev = format!("{} {}", prev, val).into(); *prev = format!("{} {}", prev, val).into();
} else { } else {
*prev = val; *prev = val;
} }
} else { } else {
attrs.push((attr, val)); attrs.push((key, val));
} }
} }
/// Returns true if the collection contains no attributes.
#[must_use] #[must_use]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
if let Some(v) = &self.0 { self.0.as_ref().map_or(true, |v| v.is_empty())
v.is_empty()
} else {
true
}
} }
/// Returns a reference to the value corresponding to the attribute key.
#[must_use] #[must_use]
pub fn get(&self, key: &str) -> Option<&str> { pub fn get(&self, key: &str) -> Option<&str> {
self.iter().find(|(k, _)| *k == key).map(|(_, v)| v) self.iter().find(|(k, _)| *k == key).map(|(_, v)| v)
} }
/// Returns an iterator over the attributes in undefined order.
pub fn iter(&self) -> impl Iterator<Item = (&'s str, &str)> + '_ { pub fn iter(&self) -> impl Iterator<Item = (&'s str, &str)> + '_ {
self.0 self.0
.iter() .iter()
@ -147,6 +151,7 @@ enum State {
struct Parser<I> { struct Parser<I> {
chars: I, chars: I,
pos: usize, pos: usize,
pos_prev: usize,
state: State, state: State,
} }
@ -155,12 +160,14 @@ impl<I: Iterator<Item = char>> Parser<I> {
Parser { Parser {
chars, chars,
pos: 0, pos: 0,
pos_prev: 0,
state: Start, state: Start,
} }
} }
fn step_char(&mut self) -> Option<State> { fn step_char(&mut self) -> Option<State> {
self.chars.next().map(|c| { self.chars.next().map(|c| {
self.pos_prev = self.pos;
self.pos += c.len_utf8(); self.pos += c.len_utf8();
match self.state { match self.state {
Start => match c { Start => match c {
@ -236,7 +243,7 @@ impl<I: Iterator<Item = char>> Parser<I> {
} }
fn step(&mut self) -> (State, Span) { fn step(&mut self) -> (State, Span) {
let start = self.pos.saturating_sub(1); let start = self.pos_prev;
if self.state == Done { if self.state == Done {
return (Done, Span::empty_at(start)); return (Done, Span::empty_at(start));
@ -250,14 +257,14 @@ impl<I: Iterator<Item = char>> Parser<I> {
if self.state != state_next { if self.state != state_next {
return ( return (
std::mem::replace(&mut self.state, state_next), std::mem::replace(&mut self.state, state_next),
Span::new(start, self.pos - 1), Span::new(start, self.pos_prev),
); );
} }
} }
( (
if self.state == Done { Done } else { Invalid }, if self.state == Done { Done } else { Invalid },
Span::new(start, self.pos.saturating_sub(1)), Span::new(start, self.pos_prev),
) )
} }
} }
@ -342,6 +349,13 @@ mod test {
("class", "some_class"), ("class", "some_class"),
("id", "some_id"), ("id", "some_id"),
); );
test_attr!("{.a .b}", ("class", "a b"));
test_attr!("{#a #b}", ("id", "b"));
}
#[test]
fn unicode_whitespace() {
test_attr!("{.a .b}", ("class", "a b"));
} }
#[test] #[test]