2020-01-05 12:52:50 -05:00
|
|
|
//! # Convert from SCSS AST to CSS
|
2020-03-01 14:53:52 -05:00
|
|
|
use std::io::Write;
|
|
|
|
|
2020-04-24 22:57:39 -04:00
|
|
|
use codemap::{CodeMap, Span};
|
|
|
|
|
2020-01-26 15:27:38 -05:00
|
|
|
use crate::atrule::AtRule;
|
2020-02-16 10:08:45 -05:00
|
|
|
use crate::error::SassResult;
|
2020-02-28 18:32:11 -05:00
|
|
|
use crate::{RuleSet, Selector, Stmt, Style, StyleSheet};
|
2020-01-05 12:45:51 -05:00
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2020-01-20 13:15:47 -05:00
|
|
|
enum Toplevel {
|
2020-01-08 20:39:05 -05:00
|
|
|
RuleSet(Selector, Vec<BlockEntry>),
|
|
|
|
MultilineComment(String),
|
2020-01-26 15:27:38 -05:00
|
|
|
AtRule(AtRule),
|
2020-01-29 21:25:07 -05:00
|
|
|
Newline,
|
2020-04-12 21:47:32 -04:00
|
|
|
Style(Box<Style>),
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
|
2020-01-08 20:39:05 -05:00
|
|
|
#[derive(Debug, Clone)]
|
2020-01-20 13:15:47 -05:00
|
|
|
enum BlockEntry {
|
2020-02-14 18:28:09 -05:00
|
|
|
Style(Box<Style>),
|
2020-01-08 20:39:05 -05:00
|
|
|
MultilineComment(String),
|
|
|
|
}
|
|
|
|
|
2020-04-12 19:37:12 -04:00
|
|
|
impl BlockEntry {
|
|
|
|
pub fn to_string(&self) -> SassResult<String> {
|
2020-01-08 20:39:05 -05:00
|
|
|
match self {
|
2020-04-12 19:37:12 -04:00
|
|
|
BlockEntry::Style(s) => s.to_string(),
|
|
|
|
BlockEntry::MultilineComment(s) => Ok(format!("/*{}*/", s)),
|
2020-01-08 20:39:05 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Toplevel {
|
|
|
|
const fn new_rule(selector: Selector) -> Self {
|
|
|
|
Toplevel::RuleSet(selector, Vec::new())
|
|
|
|
}
|
|
|
|
|
2020-04-01 17:37:07 -04:00
|
|
|
fn push_style(&mut self, mut s: Style) -> SassResult<()> {
|
2020-04-12 19:37:12 -04:00
|
|
|
s = s.eval()?;
|
2020-04-19 00:39:18 -04:00
|
|
|
if s.value.is_null(s.value.span)? {
|
2020-04-01 17:37:07 -04:00
|
|
|
return Ok(());
|
2020-02-15 09:58:41 -05:00
|
|
|
}
|
2020-01-08 20:39:05 -05:00
|
|
|
if let Toplevel::RuleSet(_, entries) = self {
|
2020-02-14 18:28:09 -05:00
|
|
|
entries.push(BlockEntry::Style(Box::new(s)));
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
2020-04-01 17:37:07 -04:00
|
|
|
Ok(())
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
|
2020-01-08 20:39:05 -05:00
|
|
|
fn push_comment(&mut self, s: String) {
|
|
|
|
if let Toplevel::RuleSet(_, entries) = self {
|
|
|
|
entries.push(BlockEntry::MultilineComment(s));
|
|
|
|
}
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Css {
|
2020-01-08 20:39:05 -05:00
|
|
|
blocks: Vec<Toplevel>,
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Css {
|
2020-01-05 12:52:50 -05:00
|
|
|
pub const fn new() -> Self {
|
2020-01-19 19:27:52 -05:00
|
|
|
Css { blocks: Vec::new() }
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
|
2020-04-01 17:37:07 -04:00
|
|
|
pub fn from_stylesheet(s: StyleSheet) -> SassResult<Self> {
|
2020-01-08 20:39:05 -05:00
|
|
|
Css::new().parse_stylesheet(s)
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
|
2020-04-01 17:37:07 -04:00
|
|
|
fn parse_stmt(&mut self, stmt: Stmt) -> SassResult<Vec<Toplevel>> {
|
|
|
|
Ok(match stmt {
|
2020-01-05 12:45:51 -05:00
|
|
|
Stmt::RuleSet(RuleSet {
|
|
|
|
selector,
|
|
|
|
super_selector,
|
|
|
|
rules,
|
|
|
|
}) => {
|
2020-05-31 04:51:41 -04:00
|
|
|
let selector = selector
|
|
|
|
.resolve_parent_selectors(&super_selector, true)
|
|
|
|
.remove_placeholders();
|
2020-02-29 17:23:17 -05:00
|
|
|
if selector.is_empty() {
|
2020-04-01 17:37:07 -04:00
|
|
|
return Ok(Vec::new());
|
2020-02-29 17:23:17 -05:00
|
|
|
}
|
|
|
|
let mut vals = vec![Toplevel::new_rule(selector)];
|
2020-01-19 19:27:52 -05:00
|
|
|
for rule in rules {
|
2020-04-12 19:37:12 -04:00
|
|
|
match rule.node {
|
|
|
|
Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule.node)?),
|
2020-05-24 16:57:07 -04:00
|
|
|
Stmt::Style(s) => vals.get_mut(0).unwrap().push_style(*s)?,
|
|
|
|
Stmt::MultilineComment(s) => vals.get_mut(0).unwrap().push_comment(s),
|
2020-04-06 13:13:03 -04:00
|
|
|
Stmt::AtRule(AtRule::AtRoot(stmts)) => stmts
|
|
|
|
.into_iter()
|
2020-04-12 19:37:12 -04:00
|
|
|
.map(|r| Ok(vals.extend(self.parse_stmt(r.node)?)))
|
2020-04-06 13:13:03 -04:00
|
|
|
.collect::<SassResult<()>>()?,
|
2020-02-22 15:34:32 -05:00
|
|
|
Stmt::AtRule(r) => vals.push(Toplevel::AtRule(r)),
|
2020-01-19 19:27:52 -05:00
|
|
|
};
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
2020-01-19 19:27:52 -05:00
|
|
|
vals
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
2020-01-19 19:27:52 -05:00
|
|
|
Stmt::MultilineComment(s) => vec![Toplevel::MultilineComment(s)],
|
2020-04-12 21:47:32 -04:00
|
|
|
Stmt::Style(s) => vec![Toplevel::Style(s)],
|
2020-01-26 15:27:38 -05:00
|
|
|
Stmt::AtRule(r) => vec![Toplevel::AtRule(r)],
|
2020-04-01 17:37:07 -04:00
|
|
|
})
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
|
2020-04-01 17:37:07 -04:00
|
|
|
fn parse_stylesheet(mut self, s: StyleSheet) -> SassResult<Css> {
|
2020-01-29 21:25:07 -05:00
|
|
|
let mut is_first = true;
|
2020-01-18 14:57:56 -05:00
|
|
|
for stmt in s.0 {
|
2020-04-12 19:37:12 -04:00
|
|
|
let v = self.parse_stmt(stmt.node)?;
|
2020-01-29 21:25:07 -05:00
|
|
|
// this is how we print newlines between unrelated styles
|
|
|
|
// it could probably be refactored
|
|
|
|
if !v.is_empty() {
|
2020-04-30 19:59:13 -04:00
|
|
|
if let Some(Toplevel::MultilineComment(..)) = v.get(0) {
|
2020-02-01 19:25:44 -05:00
|
|
|
} else if is_first {
|
2020-01-29 21:25:07 -05:00
|
|
|
is_first = false;
|
|
|
|
} else {
|
|
|
|
self.blocks.push(Toplevel::Newline);
|
|
|
|
}
|
2020-03-01 14:53:52 -05:00
|
|
|
self.blocks.extend(v);
|
2020-01-29 21:25:07 -05:00
|
|
|
}
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
2020-04-01 17:37:07 -04:00
|
|
|
Ok(self)
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
|
2020-04-24 22:57:39 -04:00
|
|
|
pub fn pretty_print(self, map: &CodeMap) -> SassResult<String> {
|
2020-04-05 23:20:47 -04:00
|
|
|
let mut string = Vec::new();
|
2020-04-24 22:57:39 -04:00
|
|
|
self._inner_pretty_print(&mut string, map, 0)?;
|
2020-04-05 23:20:47 -04:00
|
|
|
if string.iter().any(|s| !s.is_ascii()) {
|
2020-05-01 19:24:26 -04:00
|
|
|
return Ok(format!("@charset \"UTF-8\";\n{}", unsafe {
|
|
|
|
String::from_utf8_unchecked(string)
|
|
|
|
}));
|
2020-02-28 18:27:32 -05:00
|
|
|
}
|
2020-05-01 19:24:26 -04:00
|
|
|
Ok(unsafe { String::from_utf8_unchecked(string) })
|
2020-04-05 23:20:47 -04:00
|
|
|
}
|
|
|
|
|
2020-04-24 22:57:39 -04:00
|
|
|
fn debug(map: &CodeMap, span: Span, message: &str) {
|
|
|
|
let loc = map.look_up_span(span);
|
|
|
|
eprintln!(
|
|
|
|
"{}:{} Debug: {}",
|
|
|
|
loc.file.name(),
|
|
|
|
loc.begin.line + 1,
|
|
|
|
message
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn warn(map: &CodeMap, span: Span, message: &str) {
|
|
|
|
let loc = map.look_up_span(span);
|
|
|
|
eprintln!(
|
|
|
|
"Warning: {}\n {} {}:{} root stylesheet",
|
|
|
|
message,
|
|
|
|
loc.file.name(),
|
|
|
|
loc.begin.line + 1,
|
|
|
|
loc.begin.column + 1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn _inner_pretty_print(
|
|
|
|
self,
|
|
|
|
buf: &mut Vec<u8>,
|
|
|
|
map: &CodeMap,
|
|
|
|
nesting: usize,
|
|
|
|
) -> SassResult<()> {
|
2020-04-05 23:20:47 -04:00
|
|
|
let mut has_written = false;
|
|
|
|
let padding = vec![' '; nesting * 2].iter().collect::<String>();
|
2020-01-05 12:45:51 -05:00
|
|
|
for block in self.blocks {
|
2020-01-08 20:39:05 -05:00
|
|
|
match block {
|
|
|
|
Toplevel::RuleSet(selector, styles) => {
|
|
|
|
if styles.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
2020-01-29 21:25:07 -05:00
|
|
|
has_written = true;
|
2020-02-22 11:59:16 -05:00
|
|
|
writeln!(buf, "{}{} {{", padding, selector)?;
|
2020-01-08 20:39:05 -05:00
|
|
|
for style in styles {
|
2020-04-12 19:37:12 -04:00
|
|
|
writeln!(buf, "{} {}", padding, style.to_string()?)?;
|
2020-01-08 20:39:05 -05:00
|
|
|
}
|
2020-02-22 11:59:16 -05:00
|
|
|
writeln!(buf, "{}}}", padding)?;
|
2020-01-08 20:39:05 -05:00
|
|
|
}
|
|
|
|
Toplevel::MultilineComment(s) => {
|
2020-01-29 21:25:07 -05:00
|
|
|
has_written = true;
|
2020-02-22 11:59:16 -05:00
|
|
|
writeln!(buf, "{}/*{}*/", padding, s)?;
|
2020-01-26 15:27:38 -05:00
|
|
|
}
|
2020-04-24 22:57:39 -04:00
|
|
|
Toplevel::AtRule(r) => {
|
|
|
|
match r {
|
|
|
|
AtRule::Unknown(u) => {
|
2020-05-20 20:13:53 -04:00
|
|
|
if u.params.is_empty() {
|
|
|
|
write!(buf, "{}@{}", padding, u.name)?;
|
|
|
|
} else {
|
|
|
|
write!(buf, "{}@{} {}", padding, u.name, u.params)?;
|
|
|
|
}
|
|
|
|
|
2020-04-24 22:57:39 -04:00
|
|
|
if u.body.is_empty() {
|
2020-05-20 20:13:53 -04:00
|
|
|
writeln!(buf, ";")?;
|
2020-04-24 22:57:39 -04:00
|
|
|
continue;
|
|
|
|
} else {
|
2020-05-20 20:13:53 -04:00
|
|
|
writeln!(buf, " {{")?;
|
2020-04-24 22:57:39 -04:00
|
|
|
}
|
2020-05-20 20:13:53 -04:00
|
|
|
|
2020-04-24 22:57:39 -04:00
|
|
|
Css::from_stylesheet(StyleSheet::from_stmts(u.body))?
|
|
|
|
._inner_pretty_print(buf, map, nesting + 1)?;
|
|
|
|
writeln!(buf, "{}}}", padding)?;
|
2020-02-22 17:09:15 -05:00
|
|
|
}
|
2020-05-20 20:13:53 -04:00
|
|
|
AtRule::Media(m) => {
|
|
|
|
if m.body.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
writeln!(buf, "{}@media {} {{", padding, m.params)?;
|
|
|
|
Css::from_stylesheet(StyleSheet::from_stmts(m.body))?
|
|
|
|
._inner_pretty_print(buf, map, nesting + 1)?;
|
|
|
|
writeln!(buf, "{}}}", padding)?;
|
|
|
|
}
|
2020-04-24 22:57:39 -04:00
|
|
|
AtRule::Debug(e) => Self::debug(map, e.span, &e.node),
|
|
|
|
AtRule::Warn(e) => Self::warn(map, e.span, &e.node),
|
|
|
|
_ => todo!("at-rule other than unknown at toplevel: {:?}", r),
|
2020-02-22 11:59:16 -05:00
|
|
|
}
|
2020-04-24 22:57:39 -04:00
|
|
|
}
|
2020-04-12 21:47:32 -04:00
|
|
|
Toplevel::Style(s) => {
|
|
|
|
writeln!(buf, "{}{}", padding, s.to_string()?)?;
|
|
|
|
}
|
2020-02-01 19:25:44 -05:00
|
|
|
Toplevel::Newline => {
|
|
|
|
if has_written {
|
|
|
|
writeln!(buf)?
|
|
|
|
}
|
2020-01-29 21:25:07 -05:00
|
|
|
}
|
2020-01-05 12:45:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-01-05 12:52:50 -05:00
|
|
|
}
|