2020-01-04 22:55:04 -05:00
|
|
|
use std::io::{self, Write};
|
|
|
|
|
|
|
|
use crate::{RuleSet, Stmt, Style, StyleSheet};
|
|
|
|
|
|
|
|
pub(crate) struct PrettyPrinter<W: Write> {
|
|
|
|
buf: W,
|
|
|
|
scope: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<W: Write> PrettyPrinter<W> {
|
|
|
|
pub(crate) fn new(buf: W) -> Self {
|
|
|
|
PrettyPrinter { buf, scope: 0 }
|
|
|
|
}
|
|
|
|
|
2020-01-05 12:35:37 -05:00
|
|
|
/// Pretty print `crate::Stmt`
|
2020-01-05 19:09:46 -05:00
|
|
|
/// Throws away super selectors and variables
|
2020-01-04 22:55:04 -05:00
|
|
|
fn pretty_print_stmt(&mut self, stmt: &Stmt) -> io::Result<()> {
|
|
|
|
let padding = vec![' '; self.scope * 2].iter().collect::<String>();
|
|
|
|
match stmt {
|
|
|
|
Stmt::RuleSet(RuleSet {
|
|
|
|
selector, rules, ..
|
|
|
|
}) => {
|
|
|
|
writeln!(self.buf, "{}{} {{", padding, selector)?;
|
|
|
|
self.scope += 1;
|
|
|
|
for rule in rules {
|
|
|
|
self.pretty_print_stmt(rule)?;
|
|
|
|
}
|
|
|
|
writeln!(self.buf, "{}}}", padding)?;
|
|
|
|
self.scope -= 1;
|
|
|
|
}
|
|
|
|
Stmt::Style(Style { property, value }) => {
|
|
|
|
writeln!(
|
|
|
|
self.buf,
|
|
|
|
"{}{}: {};",
|
|
|
|
padding,
|
|
|
|
property,
|
|
|
|
value
|
|
|
|
.iter()
|
|
|
|
.map(ToString::to_string)
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-01-05 12:35:37 -05:00
|
|
|
/// Pretty print SCSS
|
|
|
|
///
|
|
|
|
/// The result should be an exact copy of the SCSS input
|
|
|
|
/// Empty rules are included
|
|
|
|
pub fn pretty_print(&mut self, s: &StyleSheet) -> io::Result<()> {
|
2020-01-04 22:55:04 -05:00
|
|
|
for rule in &s.rules {
|
|
|
|
self.pretty_print_stmt(rule)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-01-05 12:22:10 -05:00
|
|
|
|
2020-01-05 12:35:37 -05:00
|
|
|
/// Pretty print `crate::Stmt`
|
|
|
|
/// Keeps super selectors
|
|
|
|
fn pretty_print_stmt_preserve_super_selectors(&mut self, stmt: &Stmt) -> io::Result<()> {
|
2020-01-05 12:22:10 -05:00
|
|
|
let padding = vec![' '; self.scope * 2].iter().collect::<String>();
|
|
|
|
match stmt {
|
|
|
|
Stmt::RuleSet(RuleSet {
|
|
|
|
selector,
|
|
|
|
rules,
|
|
|
|
super_selector,
|
|
|
|
}) => {
|
|
|
|
writeln!(
|
|
|
|
self.buf,
|
|
|
|
"{}{} {{",
|
|
|
|
padding,
|
|
|
|
super_selector.clone().zip(selector.clone())
|
|
|
|
)?;
|
|
|
|
self.scope += 1;
|
|
|
|
for rule in rules {
|
|
|
|
self.pretty_print_stmt(rule)?;
|
|
|
|
}
|
|
|
|
writeln!(self.buf, "{}}}", padding)?;
|
|
|
|
self.scope -= 1;
|
|
|
|
}
|
|
|
|
Stmt::Style(Style { property, value }) => {
|
|
|
|
writeln!(
|
|
|
|
self.buf,
|
|
|
|
"{}{}: {};",
|
|
|
|
padding,
|
|
|
|
property,
|
|
|
|
value
|
|
|
|
.iter()
|
|
|
|
.map(ToString::to_string)
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(" ")
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-01-05 12:35:37 -05:00
|
|
|
/// Pretty print a special form of SCSS that shows what the full selectors are for children
|
|
|
|
/// Meant for debugging
|
|
|
|
/// Empty rules are included
|
2020-01-05 12:52:50 -05:00
|
|
|
pub(crate) fn pretty_print_preserve_super_selectors(
|
|
|
|
&mut self,
|
|
|
|
s: &StyleSheet,
|
|
|
|
) -> io::Result<()> {
|
2020-01-05 12:22:10 -05:00
|
|
|
for rule in &s.rules {
|
|
|
|
self.pretty_print_stmt(rule)?;
|
|
|
|
}
|
|
|
|
writeln!(self.buf)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-04 22:55:04 -05:00
|
|
|
#[cfg(test)]
|
2020-01-05 20:23:35 -05:00
|
|
|
mod test_scss {
|
2020-01-04 22:55:04 -05:00
|
|
|
use super::StyleSheet;
|
|
|
|
macro_rules! test {
|
|
|
|
($func:ident, $input:literal) => {
|
|
|
|
#[test]
|
|
|
|
fn $func() {
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
StyleSheet::new($input)
|
|
|
|
.pretty_print(&mut buf)
|
|
|
|
.expect(concat!("failed to pretty print on ", $input));
|
|
|
|
assert_eq!(
|
|
|
|
String::from($input),
|
|
|
|
String::from_utf8(buf).expect("produced invalid utf8")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
($func:ident, $input:literal, $output:literal) => {
|
|
|
|
#[test]
|
|
|
|
fn $func() {
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
StyleSheet::new($input)
|
|
|
|
.pretty_print(&mut buf)
|
|
|
|
.expect(concat!("failed to pretty print on ", $input));
|
|
|
|
assert_eq!(
|
|
|
|
String::from($output),
|
|
|
|
String::from_utf8(buf).expect("produced invalid utf8")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
test!(empty, "");
|
|
|
|
test!(basic_nesting, "a {\n b {\n }\n}\n");
|
2020-01-05 20:23:35 -05:00
|
|
|
test!(mul_nesting, "a, b {\n a, b {\n }\n}\n");
|
2020-01-04 22:55:04 -05:00
|
|
|
test!(ident_with_num, "el1 {\n}\n");
|
|
|
|
|
|
|
|
test!(selector_element, "a {\n}\n");
|
|
|
|
test!(selector_id, "#id {\n}\n");
|
|
|
|
test!(selector_class, ".class {\n}\n");
|
|
|
|
test!(selector_el_descendant, "a a {\n}\n");
|
|
|
|
test!(selector_universal, "* {\n}\n");
|
|
|
|
test!(selector_el_class_and, "a.class {\n}\n");
|
|
|
|
test!(selector_el_id_and, "a#class {\n}\n");
|
|
|
|
test!(selector_el_class_descendant, "a .class {\n}\n");
|
|
|
|
test!(selector_el_id_descendant, "a #class {\n}\n");
|
|
|
|
test!(selector_el_universal_descendant, "a * {\n}\n");
|
|
|
|
test!(selector_universal_el_descendant, "* a {\n}\n");
|
|
|
|
test!(selector_attribute_any, "[attr] {\n}\n");
|
|
|
|
test!(selector_attribute_equals, "[attr=val] {\n}\n");
|
|
|
|
test!(selector_attribute_in, "[attr~=val] {\n}\n");
|
|
|
|
test!(
|
|
|
|
selector_attribute_begins_hyphen_or_exact,
|
|
|
|
"[attr|=val] {\n}\n"
|
|
|
|
);
|
|
|
|
test!(selector_attribute_starts_with, "[attr^=val] {\n}\n");
|
|
|
|
test!(selector_attribute_ends_with, "[attr$=val] {\n}\n");
|
|
|
|
test!(selector_attribute_contains, "[attr*=val] {\n}\n");
|
|
|
|
test!(selector_el_attribute_and, "a[attr] {\n}\n");
|
|
|
|
test!(selector_el_attribute_descendant, "a [attr] {\n}\n");
|
|
|
|
test!(selector_el_mul_el, "a, b {\n}\n");
|
|
|
|
test!(selector_el_immediate_child_el, "a > b {\n}\n");
|
|
|
|
test!(selector_el_following_el, "a + b {\n}\n");
|
|
|
|
test!(selector_el_preceding_el, "a ~ b {\n}\n");
|
|
|
|
test!(selector_pseudo, ":pseudo {\n}\n");
|
|
|
|
test!(selector_el_pseudo_and, "a:pseudo {\n}\n");
|
|
|
|
test!(selector_el_pseudo_descendant, "a :pseudo {\n}\n");
|
|
|
|
|
|
|
|
test!(basic_style, "a {\n color: red;\n}\n");
|
|
|
|
test!(two_styles, "a {\n color: red;\n color: blue;\n}\n");
|
2020-01-05 12:22:10 -05:00
|
|
|
test!(
|
|
|
|
multiline_style,
|
|
|
|
"a {\n color: red\n blue;\n}\n",
|
|
|
|
"a {\n color: red blue;\n}\n"
|
|
|
|
);
|
2020-01-04 22:55:04 -05:00
|
|
|
test!(hyphenated_style_property, "a {\n font-family: Arial;\n}\n");
|
|
|
|
test!(hyphenated_style_value, "a {\n color: Open-Sans;\n}\n");
|
|
|
|
test!(
|
|
|
|
space_separated_style_value,
|
|
|
|
"a {\n border: solid red;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
single_quoted_style_value,
|
|
|
|
"a {\n font: 'Open-Sans';\n}\n",
|
|
|
|
"a {\n font: Open-Sans;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
double_quoted_style_value,
|
|
|
|
"a {\n font: \"Open-Sans\";\n}\n",
|
|
|
|
"a {\n font: Open-Sans;\n}\n"
|
|
|
|
);
|
|
|
|
// test!(comma_style_value, "a {\n font: Open-Sans, sans-serif;\n}\n");
|
|
|
|
test!(
|
|
|
|
nested_style_in_parent,
|
|
|
|
"a {\n color: red;\n b {\n }\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
nested_style_in_child,
|
|
|
|
"a {\n b {\n color: red;\n }\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
nested_style_in_both,
|
|
|
|
"a {\n color: red;\n b {\n color: red;\n }\n}\n"
|
|
|
|
);
|
|
|
|
|
2020-01-05 19:09:46 -05:00
|
|
|
test!(
|
|
|
|
basic_variable,
|
|
|
|
"$height: 1px;\na {\n height: $height;\n}\n",
|
|
|
|
"a {\n height: 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_redeclaration,
|
|
|
|
"$a: 1px;\n$a: 2px;\na {\n height: $a;\n}\n",
|
|
|
|
"a {\n height: 2px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_shadowing,
|
|
|
|
"$a: 1px;\n$b: $a;\na {\n height: $b;\n}\n",
|
|
|
|
"a {\n height: 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
variable_whitespace,
|
|
|
|
"$a : 1px ;\na {\n height: $a;\n}\n",
|
|
|
|
"a {\n height: 1px;\n}\n"
|
|
|
|
);
|
2020-01-05 19:47:24 -05:00
|
|
|
test!(
|
|
|
|
literal_and_variable_as_val,
|
|
|
|
"$a: 1px;\na {\n height: 1 $a;\n}\n",
|
|
|
|
"a {\n height: 1 1px;\n}\n"
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
literal_and_variable_as_var,
|
|
|
|
"$a: 1px;\n$b: 1 $a;\na {\n height: $b;\n}\n",
|
|
|
|
"a {\n height: 1 1px;\n}\n"
|
|
|
|
);
|
2020-01-05 19:09:46 -05:00
|
|
|
|
2020-01-04 22:55:04 -05:00
|
|
|
test!(unit_none, "a {\n height: 1;\n}\n");
|
|
|
|
test!(unit_not_attached, "a {\n height: 1 px;\n}\n");
|
|
|
|
test!(unit_px, "a {\n height: 1px;\n}\n");
|
|
|
|
test!(unit_em, "a {\n height: 1em;\n}\n");
|
|
|
|
test!(unit_rem, "a {\n height: 1rem;\n}\n");
|
|
|
|
test!(unit_percent, "a {\n height: 1%;\n}\n");
|
2020-01-05 12:22:10 -05:00
|
|
|
test!(
|
|
|
|
deeply_nested_selector,
|
|
|
|
"\
|
|
|
|
a {
|
|
|
|
b {
|
|
|
|
c {
|
|
|
|
d {
|
|
|
|
e {
|
|
|
|
f {
|
|
|
|
g {
|
|
|
|
h {
|
|
|
|
i {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"
|
|
|
|
);
|
2020-01-04 22:55:04 -05:00
|
|
|
}
|