2020-06-24 11:39:32 -04:00
|
|
|
use crate::{
|
|
|
|
error::SassResult,
|
2020-08-07 12:04:37 -04:00
|
|
|
utils::{is_name_start, peek_ident_no_interpolation},
|
2020-06-24 11:39:32 -04:00
|
|
|
{Cow, Token},
|
|
|
|
};
|
|
|
|
|
|
|
|
use super::Parser;
|
|
|
|
|
|
|
|
impl<'a> Parser<'a> {
|
|
|
|
pub fn scan_identifier(&mut self, ident: &str) -> SassResult<bool> {
|
|
|
|
let peeked_identifier =
|
|
|
|
match peek_ident_no_interpolation(self.toks, false, self.span_before) {
|
|
|
|
Ok(v) => v.node,
|
|
|
|
Err(..) => return Ok(false),
|
|
|
|
};
|
|
|
|
if peeked_identifier == ident {
|
2020-06-26 08:03:43 -04:00
|
|
|
self.toks.truncate_iterator_to_cursor();
|
|
|
|
self.toks.next();
|
2020-06-24 11:39:32 -04:00
|
|
|
return Ok(true);
|
|
|
|
}
|
|
|
|
self.toks.reset_cursor();
|
|
|
|
Ok(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn expression_until_comparison(&mut self) -> SassResult<Cow<'static, str>> {
|
2020-08-07 12:04:37 -04:00
|
|
|
let value = self.parse_value(false, &|toks| match toks.peek() {
|
|
|
|
Some(Token { kind: '>', .. })
|
|
|
|
| Some(Token { kind: '<', .. })
|
|
|
|
| Some(Token { kind: ':', .. })
|
|
|
|
| Some(Token { kind: ')', .. }) => true,
|
|
|
|
Some(Token { kind: '=', .. }) => {
|
|
|
|
if matches!(toks.peek_next(), Some(Token { kind: '=', .. })) {
|
|
|
|
toks.reset_cursor();
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
toks.reset_cursor();
|
|
|
|
false
|
2020-06-24 11:39:32 -04:00
|
|
|
}
|
|
|
|
}
|
2020-08-07 12:04:37 -04:00
|
|
|
_ => false,
|
|
|
|
})?;
|
|
|
|
|
|
|
|
value.node.unquote().to_css_string(value.span)
|
2020-06-24 11:39:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
pub(super) fn parse_media_query_list(&mut self) -> SassResult<String> {
|
|
|
|
let mut buf = String::new();
|
|
|
|
loop {
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
buf.push_str(&self.parse_single_media_query()?);
|
2020-08-07 13:00:55 -04:00
|
|
|
if !self.consume_char_if_exists(',') {
|
2020-06-24 11:39:32 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
buf.push(',');
|
|
|
|
buf.push(' ');
|
|
|
|
}
|
|
|
|
Ok(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_media_feature(&mut self) -> SassResult<String> {
|
|
|
|
if let Some(Token { kind: '#', .. }) = self.toks.peek() {
|
2020-08-07 12:04:37 -04:00
|
|
|
self.toks.next();
|
|
|
|
self.expect_char('{')?;
|
|
|
|
return Ok(self.parse_interpolation_as_string()?.into_owned());
|
2020-06-24 11:39:32 -04:00
|
|
|
}
|
|
|
|
let mut buf = String::with_capacity(2);
|
|
|
|
self.expect_char('(')?;
|
|
|
|
buf.push('(');
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
|
|
|
|
buf.push_str(&self.expression_until_comparison()?);
|
|
|
|
|
|
|
|
if let Some(Token { kind: ':', .. }) = self.toks.peek() {
|
|
|
|
self.toks.next();
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
|
|
|
|
buf.push(':');
|
|
|
|
buf.push(' ');
|
2020-08-07 12:04:37 -04:00
|
|
|
|
|
|
|
let value = self.parse_value(false, &|toks| match toks.peek() {
|
|
|
|
Some(Token { kind: ')', .. }) => true,
|
|
|
|
_ => false,
|
|
|
|
})?;
|
|
|
|
self.expect_char(')')?;
|
|
|
|
|
|
|
|
buf.push_str(&value.node.to_css_string(value.span)?);
|
2020-06-24 11:39:32 -04:00
|
|
|
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
buf.push(')');
|
|
|
|
return Ok(buf);
|
|
|
|
} else {
|
|
|
|
let next_tok = self.toks.peek().cloned();
|
|
|
|
let is_angle = next_tok.map_or(false, |t| t.kind == '<' || t.kind == '>');
|
|
|
|
if is_angle || matches!(next_tok, Some(Token { kind: '=', .. })) {
|
|
|
|
buf.push(' ');
|
|
|
|
// todo: remove this unwrap
|
|
|
|
buf.push(self.toks.next().unwrap().kind);
|
2020-08-07 13:00:55 -04:00
|
|
|
if is_angle && self.consume_char_if_exists('=') {
|
2020-06-24 11:39:32 -04:00
|
|
|
buf.push('=');
|
|
|
|
}
|
|
|
|
buf.push(' ');
|
|
|
|
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
|
|
|
|
buf.push_str(&self.expression_until_comparison()?);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.expect_char(')')?;
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
buf.push(')');
|
|
|
|
Ok(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_single_media_query(&mut self) -> SassResult<String> {
|
|
|
|
let mut buf = String::new();
|
|
|
|
|
|
|
|
if !matches!(self.toks.peek(), Some(Token { kind: '(', .. })) {
|
|
|
|
buf.push_str(&self.parse_identifier()?);
|
|
|
|
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
|
|
|
|
if let Some(tok) = self.toks.peek() {
|
|
|
|
if !is_name_start(tok.kind) {
|
|
|
|
return Ok(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-06 15:37:03 -04:00
|
|
|
buf.push(' ');
|
2020-06-24 11:39:32 -04:00
|
|
|
let ident = self.parse_identifier()?;
|
|
|
|
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
|
|
|
|
if ident.to_ascii_lowercase() == "and" {
|
2020-07-06 18:59:21 -04:00
|
|
|
buf.push_str("and ");
|
2020-06-24 11:39:32 -04:00
|
|
|
} else {
|
|
|
|
buf.push_str(&ident);
|
|
|
|
|
|
|
|
if self.scan_identifier("and")? {
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
buf.push_str(" and ");
|
|
|
|
} else {
|
|
|
|
return Ok(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loop {
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
buf.push_str(&self.parse_media_feature()?);
|
2020-08-07 11:39:14 -04:00
|
|
|
self.whitespace_or_comment();
|
2020-06-24 11:39:32 -04:00
|
|
|
if !self.scan_identifier("and")? {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
buf.push_str(" and ");
|
|
|
|
}
|
|
|
|
Ok(buf)
|
|
|
|
}
|
|
|
|
}
|