fixup! block attributes
This commit is contained in:
		
					parent
					
						
							
								82e1fd74f5
							
						
					
				
			
			
				commit
				
					
						4cb9c07cfc
					
				
			
		
					 1 changed files with 31 additions and 17 deletions
				
			
		
							
								
								
									
										48
									
								
								src/attr.rs
									
										
									
									
									
								
							
							
						
						
									
										48
									
								
								src/attr.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -24,28 +24,30 @@ pub fn valid<I: Iterator<Item = char>>(chars: I) -> (usize, bool) {
 | 
			
		|||
    (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
 | 
			
		||||
// indirection instead of always 24 bytes.
 | 
			
		||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
 | 
			
		||||
pub struct Attributes<'s>(Option<Box<Vec<(&'s str, CowStr<'s>)>>>);
 | 
			
		||||
 | 
			
		||||
impl<'s> Attributes<'s> {
 | 
			
		||||
    /// Create an empty collection.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self::default()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn take(&mut self) -> Self {
 | 
			
		||||
    pub(crate) fn take(&mut self) -> Self {
 | 
			
		||||
        Self(self.0.take())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn parse<S: DiscontinuousString<'s>>(&mut self, input: S) -> bool {
 | 
			
		||||
        for elem in Parser::new(input.chars()) {
 | 
			
		||||
            match elem {
 | 
			
		||||
                Element::Class(c) => self.add("class", input.src(c)),
 | 
			
		||||
                Element::Identifier(i) => self.add("id", input.src(i)),
 | 
			
		||||
                Element::Attribute(a, v) => self.add(
 | 
			
		||||
                Element::Class(c) => self.insert("class", input.src(c)),
 | 
			
		||||
                Element::Identifier(i) => self.insert("id", input.src(i)),
 | 
			
		||||
                Element::Attribute(a, v) => self.insert(
 | 
			
		||||
                    match input.src(a) {
 | 
			
		||||
                        CowStr::Owned(_) => panic!(),
 | 
			
		||||
                        CowStr::Borrowed(s) => s,
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +61,7 @@ impl<'s> Attributes<'s> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    /// 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(mut attrs1) = other.0 {
 | 
			
		||||
                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() {
 | 
			
		||||
            self.0 = Some(Vec::new().into());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
            if attr == "class" {
 | 
			
		||||
            if key == "class" {
 | 
			
		||||
                *prev = format!("{} {}", prev, val).into();
 | 
			
		||||
            } else {
 | 
			
		||||
                *prev = val;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            attrs.push((attr, val));
 | 
			
		||||
            attrs.push((key, val));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns true if the collection contains no attributes.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn is_empty(&self) -> bool {
 | 
			
		||||
        if let Some(v) = &self.0 {
 | 
			
		||||
            v.is_empty()
 | 
			
		||||
        } else {
 | 
			
		||||
            true
 | 
			
		||||
        }
 | 
			
		||||
        self.0.as_ref().map_or(true, |v| v.is_empty())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns a reference to the value corresponding to the attribute key.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn get(&self, key: &str) -> Option<&str> {
 | 
			
		||||
        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)> + '_ {
 | 
			
		||||
        self.0
 | 
			
		||||
            .iter()
 | 
			
		||||
| 
						 | 
				
			
			@ -147,6 +151,7 @@ enum State {
 | 
			
		|||
struct Parser<I> {
 | 
			
		||||
    chars: I,
 | 
			
		||||
    pos: usize,
 | 
			
		||||
    pos_prev: usize,
 | 
			
		||||
    state: State,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -155,12 +160,14 @@ impl<I: Iterator<Item = char>> Parser<I> {
 | 
			
		|||
        Parser {
 | 
			
		||||
            chars,
 | 
			
		||||
            pos: 0,
 | 
			
		||||
            pos_prev: 0,
 | 
			
		||||
            state: Start,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn step_char(&mut self) -> Option<State> {
 | 
			
		||||
        self.chars.next().map(|c| {
 | 
			
		||||
            self.pos_prev = self.pos;
 | 
			
		||||
            self.pos += c.len_utf8();
 | 
			
		||||
            match self.state {
 | 
			
		||||
                Start => match c {
 | 
			
		||||
| 
						 | 
				
			
			@ -236,7 +243,7 @@ impl<I: Iterator<Item = char>> Parser<I> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    fn step(&mut self) -> (State, Span) {
 | 
			
		||||
        let start = self.pos.saturating_sub(1);
 | 
			
		||||
        let start = self.pos_prev;
 | 
			
		||||
 | 
			
		||||
        if self.state == Done {
 | 
			
		||||
            return (Done, Span::empty_at(start));
 | 
			
		||||
| 
						 | 
				
			
			@ -250,14 +257,14 @@ impl<I: Iterator<Item = char>> Parser<I> {
 | 
			
		|||
            if self.state != state_next {
 | 
			
		||||
                return (
 | 
			
		||||
                    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 },
 | 
			
		||||
            Span::new(start, self.pos.saturating_sub(1)),
 | 
			
		||||
            Span::new(start, self.pos_prev),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -342,6 +349,13 @@ mod test {
 | 
			
		|||
            ("class", "some_class"),
 | 
			
		||||
            ("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]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue