grass/src/css.rs

170 lines
5.5 KiB
Rust
Raw Normal View History

2020-01-05 12:52:50 -05:00
//! # Convert from SCSS AST to CSS
2020-03-01 14:53:52 -05:00
use std::fmt;
use std::io::Write;
use std::sync::atomic::Ordering;
2020-01-26 15:27:38 -05:00
use crate::atrule::AtRule;
use crate::error::SassResult;
2020-02-28 18:27:32 -05:00
use crate::lexer::IS_UTF8;
2020-02-28 18:32:11 -05:00
use crate::{RuleSet, Selector, Stmt, Style, StyleSheet};
#[derive(Debug, Clone)]
enum Toplevel {
RuleSet(Selector, Vec<BlockEntry>),
MultilineComment(String),
2020-01-26 15:27:38 -05:00
AtRule(AtRule),
Newline,
}
#[derive(Debug, Clone)]
enum BlockEntry {
2020-02-14 18:28:09 -05:00
Style(Box<Style>),
MultilineComment(String),
}
impl fmt::Display for BlockEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
2020-03-01 14:53:52 -05:00
BlockEntry::Style(s) => writeln!(f, "{}", s),
BlockEntry::MultilineComment(s) => writeln!(f, "/*{}*/", s),
}
}
}
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<()> {
s.value = s.value.eval()?;
if s.value.is_null() {
2020-04-01 17:37:07 -04:00
return Ok(());
}
if let Toplevel::RuleSet(_, entries) = self {
2020-02-14 18:28:09 -05:00
entries.push(BlockEntry::Style(Box::new(s)));
}
2020-04-01 17:37:07 -04:00
Ok(())
}
fn push_comment(&mut self, s: String) {
if let Toplevel::RuleSet(_, entries) = self {
entries.push(BlockEntry::MultilineComment(s));
}
}
}
#[derive(Debug, Clone)]
pub struct Css {
blocks: Vec<Toplevel>,
}
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-04-01 17:37:07 -04:00
pub fn from_stylesheet(s: StyleSheet) -> SassResult<Self> {
Css::new().parse_stylesheet(s)
}
2020-04-01 17:37:07 -04:00
fn parse_stmt(&mut self, stmt: Stmt) -> SassResult<Vec<Toplevel>> {
Ok(match stmt {
Stmt::RuleSet(RuleSet {
selector,
super_selector,
rules,
}) => {
let selector = super_selector.zip(&selector).remove_placeholders();
if selector.is_empty() {
2020-04-01 17:37:07 -04:00
return Ok(Vec::new());
}
let mut vals = vec![Toplevel::new_rule(selector)];
2020-01-19 19:27:52 -05:00
for rule in rules {
match rule {
2020-04-01 17:37:07 -04:00
Stmt::RuleSet(_) => vals.extend(self.parse_stmt(rule)?),
2020-01-19 19:27:52 -05:00
Stmt::Style(s) => vals
.get_mut(0)
.expect("expected block to exist")
2020-04-01 17:37:07 -04:00
.push_style(*s)?,
2020-01-19 19:27:52 -05:00
Stmt::MultilineComment(s) => vals
.get_mut(0)
.expect("expected block to exist")
.push_comment(s),
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-19 19:27:52 -05:00
vals
}
2020-01-19 19:27:52 -05:00
Stmt::MultilineComment(s) => vec![Toplevel::MultilineComment(s)],
Stmt::Style(_) => panic!("expected toplevel element, found style"),
2020-01-26 15:27:38 -05:00
Stmt::AtRule(r) => vec![Toplevel::AtRule(r)],
2020-04-01 17:37:07 -04:00
})
}
2020-04-01 17:37:07 -04:00
fn parse_stylesheet(mut self, s: StyleSheet) -> SassResult<Css> {
let mut is_first = true;
2020-01-18 14:57:56 -05:00
for stmt in s.0 {
2020-04-01 17:37:07 -04:00
let v = self.parse_stmt(stmt)?;
// this is how we print newlines between unrelated styles
// it could probably be refactored
if !v.is_empty() {
2020-02-01 19:25:44 -05:00
if let Toplevel::MultilineComment(..) = v[0] {
} else if is_first {
is_first = false;
} else {
self.blocks.push(Toplevel::Newline);
}
2020-03-01 14:53:52 -05:00
self.blocks.extend(v);
}
}
2020-04-01 17:37:07 -04:00
Ok(self)
}
pub fn pretty_print<W: Write>(self, buf: &mut W, nesting: usize) -> SassResult<()> {
let mut has_written = false;
let padding = vec![' '; nesting * 2].iter().collect::<String>();
2020-02-28 18:27:32 -05:00
if IS_UTF8.swap(false, Ordering::Relaxed) {
writeln!(buf, "@charset \"UTF-8\";")?;
}
for block in self.blocks {
match block {
Toplevel::RuleSet(selector, styles) => {
if styles.is_empty() {
continue;
}
has_written = true;
writeln!(buf, "{}{} {{", padding, selector)?;
for style in styles {
2020-03-01 14:53:52 -05:00
write!(buf, "{} {}", padding, style)?;
}
writeln!(buf, "{}}}", padding)?;
}
Toplevel::MultilineComment(s) => {
has_written = true;
writeln!(buf, "{}/*{}*/", padding, s)?;
2020-01-26 15:27:38 -05:00
}
Toplevel::AtRule(r) => match r {
AtRule::Unknown(u) => {
if u.params.is_empty() {
writeln!(buf, "{}@{} {{", padding, u.name)?;
} else {
writeln!(buf, "{}@{} {} {{", padding, u.name, u.params)?;
}
2020-04-01 17:37:07 -04:00
Css::from_stylesheet(StyleSheet::from_stmts(u.body))?
2020-02-29 15:28:48 -05:00
.pretty_print(buf, nesting + 1)?;
writeln!(buf, "{}}}", padding)?;
}
2020-03-22 02:14:01 -04:00
_ => todo!("at-rule other than unknown at toplevel"),
},
2020-02-01 19:25:44 -05:00
Toplevel::Newline => {
if has_written {
writeln!(buf)?
}
}
}
}
Ok(())
}
2020-01-05 12:52:50 -05:00
}