grass/src/selector/attribute.rs

186 lines
5.9 KiB
Rust
Raw Normal View History

2020-04-02 20:59:37 -04:00
use std::fmt::{self, Display};
use std::iter::Peekable;
2020-04-12 19:37:12 -04:00
use codemap::Span;
2020-04-02 20:59:37 -04:00
use super::{Selector, SelectorKind};
use crate::error::SassResult;
use crate::scope::Scope;
2020-04-03 13:28:37 -04:00
use crate::utils::{
devour_whitespace, eat_ident, is_ident_char, parse_interpolation, parse_quoted_string,
};
2020-04-02 20:59:37 -04:00
use crate::Token;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Attribute {
2020-04-02 21:10:45 -04:00
attr: String,
value: String,
modifier: Option<char>,
kind: AttributeKind,
2020-04-02 20:59:37 -04:00
}
impl Attribute {
pub fn from_tokens<I: Iterator<Item = Token>>(
toks: &mut Peekable<I>,
scope: &Scope,
super_selector: &Selector,
2020-04-12 19:37:12 -04:00
mut start: Span,
2020-04-02 20:59:37 -04:00
) -> SassResult<SelectorKind> {
devour_whitespace(toks);
2020-04-12 19:37:12 -04:00
let next_tok = toks.peek().ok_or(("Expected identifier.", start))?;
let attr = match next_tok.kind {
c if is_ident_char(c) => {
let i = eat_ident(toks, scope, super_selector)?;
start = i.span;
i.node
}
2020-04-02 21:44:26 -04:00
'#' => {
2020-04-12 19:37:12 -04:00
start.merge(toks.next().unwrap().pos());
if toks.next().ok_or(("Expected expression.", start))?.kind == '{' {
let interpolation = parse_interpolation(toks, scope, super_selector)?;
interpolation.node.to_css_string(interpolation.span)?
2020-04-02 21:44:26 -04:00
} else {
2020-04-12 19:37:12 -04:00
return Err(("Expected expression.", start).into());
2020-04-02 21:44:26 -04:00
}
}
2020-04-12 19:37:12 -04:00
_ => return Err(("Expected identifier.", start).into()),
2020-04-02 20:59:37 -04:00
};
devour_whitespace(toks);
2020-04-12 19:37:12 -04:00
let next = toks.next().ok_or(("expected \"]\".", start))?;
let kind = match next.kind {
c if is_ident_char(c) => return Err(("Expected \"]\".", next.pos()).into()),
']' => {
return Ok(SelectorKind::Attribute(Attribute {
kind: AttributeKind::Any,
attr,
value: String::new(),
modifier: None,
}));
}
'=' => AttributeKind::Equals,
'~' => AttributeKind::Include,
'|' => AttributeKind::Dash,
'^' => AttributeKind::Prefix,
'$' => AttributeKind::Suffix,
'*' => AttributeKind::Contains,
2020-04-12 19:37:12 -04:00
_ => return Err(("expected \"]\".", next.pos()).into()),
2020-04-02 20:59:37 -04:00
};
if kind != AttributeKind::Equals {
2020-04-12 19:37:12 -04:00
let next = toks.next().ok_or(("expected \"=\".", next.pos()))?;
match next.kind {
2020-04-02 20:59:37 -04:00
'=' => {}
2020-04-12 19:37:12 -04:00
_ => return Err(("expected \"=\".", next.pos()).into()),
2020-04-02 20:59:37 -04:00
}
}
devour_whitespace(toks);
2020-04-12 19:37:12 -04:00
let next = toks.next().ok_or(("Expected identifier.", next.pos()))?;
let value = match next.kind {
v @ 'a'..='z' | v @ 'A'..='Z' | v @ '-' | v @ '_' => {
2020-04-12 19:37:12 -04:00
format!("{}{}", v, eat_ident(toks, scope, super_selector)?.node)
}
q @ '"' | q @ '\'' => {
parse_quoted_string(toks, scope, q, super_selector)?.to_css_string(next.pos())?
2020-04-02 20:59:37 -04:00
}
2020-04-12 19:37:12 -04:00
_ => return Err(("Expected identifier.", next.pos()).into()),
2020-04-02 20:59:37 -04:00
};
devour_whitespace(toks);
2020-04-12 19:37:12 -04:00
let next = toks.next().ok_or(("expected \"]\".", next.pos()))?;
let modifier = match next.kind {
']' => {
return Ok(SelectorKind::Attribute(Attribute {
kind,
attr,
value,
modifier: None,
}))
}
v @ 'a'..='z' | v @ 'A'..='Z' => {
2020-04-12 19:37:12 -04:00
let next = toks.next().ok_or(("expected \"]\".", next.pos()))?;
match next.kind {
']' => {}
2020-04-12 19:37:12 -04:00
_ => return Err(("expected \"]\".", next.pos()).into()),
2020-04-02 20:59:37 -04:00
}
Some(v)
2020-04-02 20:59:37 -04:00
}
2020-04-12 19:37:12 -04:00
_ => return Err(("expected \"]\".", next.pos()).into()),
2020-04-02 20:59:37 -04:00
};
Ok(SelectorKind::Attribute(Attribute {
kind,
attr,
value,
modifier,
}))
}
}
impl Display for Attribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2020-04-02 21:10:45 -04:00
let modifier = if let Some(c) = self.modifier {
format!(" {}", c)
} else {
String::new()
};
2020-04-02 20:59:37 -04:00
match self.kind {
2020-04-02 21:10:45 -04:00
AttributeKind::Any => write!(f, "[{}{}]", self.attr, modifier),
AttributeKind::Equals => write!(f, "[{}={}{}]", self.attr, self.value, modifier),
AttributeKind::Include => write!(f, "[{}~={}{}]", self.attr, self.value, modifier),
AttributeKind::Dash => write!(f, "[{}|={}{}]", self.attr, self.value, modifier),
AttributeKind::Prefix => write!(f, "[{}^={}{}]", self.attr, self.value, modifier),
AttributeKind::Suffix => write!(f, "[{}$={}{}]", self.attr, self.value, modifier),
AttributeKind::Contains => write!(f, "[{}*={}{}]", self.attr, self.value, modifier),
2020-04-02 20:59:37 -04:00
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
2020-04-02 21:10:45 -04:00
enum AttributeKind {
2020-04-04 19:07:00 -04:00
/// \[attr\]
2020-04-02 21:10:45 -04:00
///
2020-04-02 20:59:37 -04:00
/// Represents elements with an attribute name of `attr`
Any,
2020-04-02 21:10:45 -04:00
2020-04-02 20:59:37 -04:00
/// [attr=value]
2020-04-02 21:10:45 -04:00
///
/// Represents elements with an attribute name of `attr`
/// whose value is exactly `value`
2020-04-02 20:59:37 -04:00
Equals,
2020-04-02 21:10:45 -04:00
2020-04-02 20:59:37 -04:00
/// [attr~=value]
2020-04-02 21:10:45 -04:00
///
/// Represents elements with an attribute name of `attr`
/// whose value is a whitespace-separated list of words,
/// one of which is exactly `value`
Include,
2020-04-02 20:59:37 -04:00
/// [attr|=value]
2020-04-02 21:10:45 -04:00
///
/// Represents elements with an attribute name of `attr`
/// whose value can be exactly value or can begin with
/// `value` immediately followed by a hyphen (`-`)
Dash,
2020-04-02 20:59:37 -04:00
/// [attr^=value]
2020-04-02 21:10:45 -04:00
Prefix,
2020-04-02 20:59:37 -04:00
/// [attr$=value]
2020-04-02 21:10:45 -04:00
Suffix,
2020-04-02 20:59:37 -04:00
/// [attr*=value]
2020-04-02 21:10:45 -04:00
///
/// Represents elements with an attribute name of `attr`
/// whose value contains at least one occurrence of
/// `value` within the string
2020-04-02 20:59:37 -04:00
Contains,
}