add compressed output (#33)
This commit is contained in:
parent
142bc9da10
commit
a5f3823521
@ -136,7 +136,7 @@ mod utils;
|
|||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum OutputStyle {
|
pub enum OutputStyle {
|
||||||
/// The default style, this mode writes each
|
/// The default style, this mode writes each
|
||||||
/// selector and declaration on its own line.
|
/// selector and declaration on its own line.
|
||||||
@ -313,7 +313,7 @@ pub fn from_path(p: &str, options: &Options) -> Result<String> {
|
|||||||
|
|
||||||
Css::from_stmts(stmts, false, options.allows_charset)
|
Css::from_stmts(stmts, false, options.allows_charset)
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?
|
||||||
.pretty_print(&map)
|
.pretty_print(&map, options.style)
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +359,7 @@ pub fn from_string(p: String, options: &Options) -> Result<String> {
|
|||||||
|
|
||||||
Css::from_stmts(stmts, false, options.allows_charset)
|
Css::from_stmts(stmts, false, options.allows_charset)
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))?
|
||||||
.pretty_print(&map)
|
.pretty_print(&map, options.style)
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))
|
.map_err(|e| raw_to_parse_error(&map, *e, options.unicode_error_messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,6 +396,6 @@ pub fn from_string(p: String) -> std::result::Result<String, JsValue> {
|
|||||||
|
|
||||||
Ok(Css::from_stmts(stmts, false, true)
|
Ok(Css::from_stmts(stmts, false, true)
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?
|
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?
|
||||||
.pretty_print(&map)
|
.pretty_print(&map, options.style)
|
||||||
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?)
|
.map_err(|e| raw_to_parse_error(&map, *e, true).to_string())?)
|
||||||
}
|
}
|
||||||
|
15
src/main.rs
15
src/main.rs
@ -7,8 +7,9 @@ use std::{
|
|||||||
use clap::{arg_enum, App, AppSettings, Arg};
|
use clap::{arg_enum, App, AppSettings, Arg};
|
||||||
|
|
||||||
#[cfg(not(feature = "wasm"))]
|
#[cfg(not(feature = "wasm"))]
|
||||||
use grass::{from_path, from_string, Options};
|
use grass::{from_path, from_string, Options, OutputStyle};
|
||||||
|
|
||||||
|
// TODO remove this
|
||||||
arg_enum! {
|
arg_enum! {
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum Style {
|
pub enum Style {
|
||||||
@ -58,11 +59,10 @@ fn main() -> std::io::Result<()> {
|
|||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("STYLE")
|
Arg::with_name("STYLE")
|
||||||
.short("s")
|
|
||||||
// this is required for compatibility with ruby sass
|
// this is required for compatibility with ruby sass
|
||||||
.short("t")
|
.short("t") // FIXME change this to short_alias later
|
||||||
|
.short("s")
|
||||||
.long("style")
|
.long("style")
|
||||||
.hidden(true)
|
|
||||||
.help("Minified or expanded output")
|
.help("Minified or expanded output")
|
||||||
.default_value("expanded")
|
.default_value("expanded")
|
||||||
.case_insensitive(true)
|
.case_insensitive(true)
|
||||||
@ -183,8 +183,15 @@ fn main() -> std::io::Result<()> {
|
|||||||
.values_of("LOAD_PATH")
|
.values_of("LOAD_PATH")
|
||||||
.map_or_else(Vec::new, |vals| vals.map(Path::new).collect());
|
.map_or_else(Vec::new, |vals| vals.map(Path::new).collect());
|
||||||
|
|
||||||
|
let style = match matches.value_of("STYLE").unwrap() {
|
||||||
|
"expanded" => OutputStyle::Expanded,
|
||||||
|
"compressed" => OutputStyle::Compressed,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
let options = &Options::default()
|
let options = &Options::default()
|
||||||
.load_paths(&load_paths)
|
.load_paths(&load_paths)
|
||||||
|
.style(style)
|
||||||
.quiet(matches.is_present("QUIET"))
|
.quiet(matches.is_present("QUIET"))
|
||||||
.unicode_error_messages(!matches.is_present("NO_UNICODE"))
|
.unicode_error_messages(!matches.is_present("NO_UNICODE"))
|
||||||
.allows_charset(!matches.is_present("NO_CHARSET"));
|
.allows_charset(!matches.is_present("NO_CHARSET"));
|
||||||
|
256
src/output.rs
256
src/output.rs
@ -11,8 +11,9 @@ use crate::{
|
|||||||
},
|
},
|
||||||
error::SassResult,
|
error::SassResult,
|
||||||
parse::Stmt,
|
parse::Stmt,
|
||||||
selector::Selector,
|
selector::{ComplexSelector, ComplexSelectorComponent, Selector},
|
||||||
style::Style,
|
style::Style,
|
||||||
|
OutputStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -233,35 +234,224 @@ impl Css {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pretty_print(mut self, map: &CodeMap) -> SassResult<String> {
|
pub fn pretty_print(self, map: &CodeMap, style: OutputStyle) -> SassResult<String> {
|
||||||
let mut string = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let allows_charset = self.allows_charset;
|
let allows_charset = self.allows_charset;
|
||||||
self._inner_pretty_print(&mut string, map, 0)?;
|
match style {
|
||||||
if allows_charset && string.iter().any(|s| !s.is_ascii()) {
|
OutputStyle::Compressed => {
|
||||||
return Ok(format!("@charset \"UTF-8\";\n{}", unsafe {
|
CompressedFormatter::default().write_css(&mut buf, self, map)?;
|
||||||
String::from_utf8_unchecked(string)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
Ok(unsafe { String::from_utf8_unchecked(string) })
|
OutputStyle::Expanded => ExpandedFormatter::default().write_css(&mut buf, self, map)?,
|
||||||
|
}
|
||||||
|
// TODO: check for this before writing
|
||||||
|
let show_charset = allows_charset && buf.iter().any(|s| !s.is_ascii());
|
||||||
|
let out = unsafe { String::from_utf8_unchecked(buf) };
|
||||||
|
Ok(if show_charset {
|
||||||
|
match style {
|
||||||
|
OutputStyle::Compressed => format!("@charset \"UTF-8\";{}", out),
|
||||||
|
OutputStyle::Expanded => format!("@charset \"UTF-8\";\n{}", out),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Formatter {
|
||||||
|
fn write_css(&mut self, buf: &mut Vec<u8>, css: Css, map: &CodeMap) -> SassResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct CompressedFormatter {}
|
||||||
|
|
||||||
|
impl Formatter for CompressedFormatter {
|
||||||
|
fn write_css(&mut self, buf: &mut Vec<u8>, mut css: Css, map: &CodeMap) -> SassResult<()> {
|
||||||
|
for block in mem::take(&mut css.blocks) {
|
||||||
|
match block {
|
||||||
|
Toplevel::RuleSet(selector, styles) => {
|
||||||
|
if styles.is_empty() {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _inner_pretty_print(
|
let mut complexes = selector.0.components.iter().filter(|c| !c.is_invisible());
|
||||||
&mut self,
|
if let Some(complex) = complexes.next() {
|
||||||
buf: &mut Vec<u8>,
|
self.write_complex(buf, complex)?;
|
||||||
map: &CodeMap,
|
}
|
||||||
|
for complex in complexes {
|
||||||
|
write!(buf, ",")?;
|
||||||
|
self.write_complex(buf, complex)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(buf, "{{")?;
|
||||||
|
self.write_block_entry(buf, &styles)?;
|
||||||
|
write!(buf, "}}")?;
|
||||||
|
}
|
||||||
|
Toplevel::KeyframesRuleSet(selectors, styles) => {
|
||||||
|
if styles.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut selectors = selectors.iter();
|
||||||
|
if let Some(selector) = selectors.next() {
|
||||||
|
match selector {
|
||||||
|
KeyframesSelector::To => write!(buf, "to")?,
|
||||||
|
KeyframesSelector::From => write!(buf, "from")?,
|
||||||
|
KeyframesSelector::Percent(p) => write!(buf, "{}%", p)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for selector in selectors {
|
||||||
|
match selector {
|
||||||
|
KeyframesSelector::To => write!(buf, ",to")?,
|
||||||
|
KeyframesSelector::From => write!(buf, ",from")?,
|
||||||
|
KeyframesSelector::Percent(p) => write!(buf, ",{}%", p)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(buf, "{{")?;
|
||||||
|
self.write_block_entry(buf, &styles)?;
|
||||||
|
write!(buf, "}}")?;
|
||||||
|
}
|
||||||
|
Toplevel::MultilineComment(s) => {
|
||||||
|
write!(buf, "/*{}*/", s)?;
|
||||||
|
}
|
||||||
|
Toplevel::Import(s) => {
|
||||||
|
write!(buf, "@import {};", s)?;
|
||||||
|
}
|
||||||
|
Toplevel::UnknownAtRule(u) => {
|
||||||
|
let ToplevelUnknownAtRule { params, name, body } = *u;
|
||||||
|
|
||||||
|
if params.is_empty() {
|
||||||
|
write!(buf, "@{}", name)?;
|
||||||
|
} else {
|
||||||
|
write!(buf, "@{} {}", name, params)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.is_empty() {
|
||||||
|
write!(buf, ";")?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(buf, "{{")?;
|
||||||
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
|
self.write_css(buf, css, map)?;
|
||||||
|
write!(buf, "}}")?;
|
||||||
|
}
|
||||||
|
Toplevel::Keyframes(k) => {
|
||||||
|
let Keyframes { rule, name, body } = *k;
|
||||||
|
|
||||||
|
write!(buf, "@{}", rule)?;
|
||||||
|
|
||||||
|
if !name.is_empty() {
|
||||||
|
write!(buf, " {}", name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.is_empty() {
|
||||||
|
write!(buf, "{{}}")?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(buf, "{{")?;
|
||||||
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
|
self.write_css(buf, css, map)?;
|
||||||
|
write!(buf, "}}")?;
|
||||||
|
}
|
||||||
|
Toplevel::Supports { params, body } => {
|
||||||
|
if params.is_empty() {
|
||||||
|
write!(buf, "@supports")?;
|
||||||
|
} else {
|
||||||
|
write!(buf, "@supports {}", params)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.is_empty() {
|
||||||
|
write!(buf, ";")?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(buf, "{{")?;
|
||||||
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
|
self.write_css(buf, css, map)?;
|
||||||
|
write!(buf, "}}")?;
|
||||||
|
}
|
||||||
|
Toplevel::Media { query, body } => {
|
||||||
|
if body.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(buf, "@media {}{{", query)?;
|
||||||
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
|
self.write_css(buf, css, map)?;
|
||||||
|
write!(buf, "}}")?;
|
||||||
|
}
|
||||||
|
Toplevel::Style(style) => {
|
||||||
|
let value = style.value.node.to_css_string(style.value.span)?;
|
||||||
|
write!(buf, "{}:{};", style.property, value)?;
|
||||||
|
}
|
||||||
|
Toplevel::Newline => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this could be a trait implemented on value itself
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
|
impl CompressedFormatter {
|
||||||
|
fn write_complex(&self, buf: &mut Vec<u8>, complex: &ComplexSelector) -> SassResult<()> {
|
||||||
|
let mut was_compound = false;
|
||||||
|
for component in &complex.components {
|
||||||
|
match component {
|
||||||
|
ComplexSelectorComponent::Compound(c) if was_compound => write!(buf, " {}", c)?,
|
||||||
|
ComplexSelectorComponent::Compound(c) => write!(buf, "{}", c)?,
|
||||||
|
ComplexSelectorComponent::Combinator(c) => write!(buf, "{}", c)?,
|
||||||
|
}
|
||||||
|
was_compound = matches!(component, ComplexSelectorComponent::Compound(_));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_block_entry(&self, buf: &mut Vec<u8>, styles: &[BlockEntry]) -> SassResult<()> {
|
||||||
|
let mut styles = styles.iter();
|
||||||
|
if let Some(style) = styles.next() {
|
||||||
|
match style {
|
||||||
|
BlockEntry::Style(s) => {
|
||||||
|
let value = s.value.node.to_css_string(s.value.span)?;
|
||||||
|
write!(buf, "{}:{}", s.property, value)?;
|
||||||
|
}
|
||||||
|
BlockEntry::MultilineComment(s) => write!(buf, "/*{}*/", s)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for style in styles {
|
||||||
|
match style {
|
||||||
|
BlockEntry::Style(s) => {
|
||||||
|
let value = s.value.node.to_css_string(s.value.span)?;
|
||||||
|
write!(buf, ";{}:{}", s.property, value)?;
|
||||||
|
}
|
||||||
|
BlockEntry::MultilineComment(s) => write!(buf, "/*{}*/", s)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct ExpandedFormatter {
|
||||||
nesting: usize,
|
nesting: usize,
|
||||||
) -> SassResult<()> {
|
}
|
||||||
|
|
||||||
|
impl Formatter for ExpandedFormatter {
|
||||||
|
fn write_css(&mut self, buf: &mut Vec<u8>, mut css: Css, map: &CodeMap) -> SassResult<()> {
|
||||||
let mut has_written = false;
|
let mut has_written = false;
|
||||||
let padding = vec![' '; nesting * 2].iter().collect::<String>();
|
let padding = " ".repeat(self.nesting);
|
||||||
let mut should_emit_newline = false;
|
let mut should_emit_newline = false;
|
||||||
for block in mem::take(&mut self.blocks) {
|
self.nesting += 1;
|
||||||
|
for block in mem::take(&mut css.blocks) {
|
||||||
match block {
|
match block {
|
||||||
Toplevel::RuleSet(selector, styles) => {
|
Toplevel::RuleSet(selector, styles) => {
|
||||||
if styles.is_empty() {
|
if styles.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
has_written = true;
|
has_written = true;
|
||||||
if should_emit_newline && !self.in_at_rule {
|
if should_emit_newline && !css.in_at_rule {
|
||||||
should_emit_newline = false;
|
should_emit_newline = false;
|
||||||
writeln!(buf)?;
|
writeln!(buf)?;
|
||||||
}
|
}
|
||||||
@ -319,12 +509,8 @@ impl Css {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeln!(buf, " {{")?;
|
writeln!(buf, " {{")?;
|
||||||
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
Css::from_stmts(body, true, self.allows_charset)?._inner_pretty_print(
|
self.write_css(buf, css, map)?;
|
||||||
buf,
|
|
||||||
map,
|
|
||||||
nesting + 1,
|
|
||||||
)?;
|
|
||||||
writeln!(buf, "{}}}", padding)?;
|
writeln!(buf, "{}}}", padding)?;
|
||||||
}
|
}
|
||||||
Toplevel::Keyframes(k) => {
|
Toplevel::Keyframes(k) => {
|
||||||
@ -346,12 +532,8 @@ impl Css {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeln!(buf, " {{")?;
|
writeln!(buf, " {{")?;
|
||||||
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
Css::from_stmts(body, true, self.allows_charset)?._inner_pretty_print(
|
self.write_css(buf, css, map)?;
|
||||||
buf,
|
|
||||||
map,
|
|
||||||
nesting + 1,
|
|
||||||
)?;
|
|
||||||
writeln!(buf, "{}}}", padding)?;
|
writeln!(buf, "{}}}", padding)?;
|
||||||
}
|
}
|
||||||
Toplevel::Supports { params, body } => {
|
Toplevel::Supports { params, body } => {
|
||||||
@ -372,12 +554,8 @@ impl Css {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeln!(buf, " {{")?;
|
writeln!(buf, " {{")?;
|
||||||
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
Css::from_stmts(body, true, self.allows_charset)?._inner_pretty_print(
|
self.write_css(buf, css, map)?;
|
||||||
buf,
|
|
||||||
map,
|
|
||||||
nesting + 1,
|
|
||||||
)?;
|
|
||||||
writeln!(buf, "{}}}", padding)?;
|
writeln!(buf, "{}}}", padding)?;
|
||||||
}
|
}
|
||||||
Toplevel::Media { query, body } => {
|
Toplevel::Media { query, body } => {
|
||||||
@ -386,11 +564,8 @@ impl Css {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeln!(buf, "{}@media {} {{", padding, query)?;
|
writeln!(buf, "{}@media {} {{", padding, query)?;
|
||||||
Css::from_stmts(body, true, self.allows_charset)?._inner_pretty_print(
|
let css = Css::from_stmts(body, true, css.allows_charset)?;
|
||||||
buf,
|
self.write_css(buf, css, map)?;
|
||||||
map,
|
|
||||||
nesting + 1,
|
|
||||||
)?;
|
|
||||||
writeln!(buf, "{}}}", padding)?;
|
writeln!(buf, "{}}}", padding)?;
|
||||||
}
|
}
|
||||||
Toplevel::Style(s) => {
|
Toplevel::Style(s) => {
|
||||||
@ -404,6 +579,7 @@ impl Css {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.nesting -= 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user