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)
|
(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]
|
||||||
|
|
Loading…
Reference in a new issue