diff --git a/src/dependencies.rs b/src/dependencies.rs index 2d3e0c05..7564ac0c 100644 --- a/src/dependencies.rs +++ b/src/dependencies.rs @@ -1,5 +1,5 @@ use crate::css_modules::hash; -use crate::printer::Printer; +use crate::printer::PrinterOptions; use crate::rules::import::ImportRule; use crate::traits::ToCss; use crate::values::url::Url; @@ -24,18 +24,14 @@ pub struct ImportDependency { impl ImportDependency { pub fn new(rule: &ImportRule, filename: &str) -> ImportDependency { let supports = if let Some(supports) = &rule.supports { - let mut s = String::new(); - let mut printer = Printer::new(&mut s, None, false, None); - supports.to_css(&mut printer).unwrap(); + let s = supports.to_css_string(PrinterOptions::default()).unwrap(); Some(s) } else { None }; let media = if !rule.media.media_queries.is_empty() { - let mut s = String::new(); - let mut printer = Printer::new(&mut s, None, false, None); - rule.media.to_css(&mut printer).unwrap(); + let s = rule.media.to_css_string(PrinterOptions::default()).unwrap(); Some(s) } else { None diff --git a/src/lib.rs b/src/lib.rs index 1b765b8e..ee940fff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,13 +10,13 @@ mod macros; pub mod media_query; mod parser; mod prefixes; -mod printer; +pub mod printer; pub mod properties; pub mod rules; mod selector; pub mod stylesheet; pub mod targets; -mod traits; +pub mod traits; pub mod values; pub mod vendor_prefix; @@ -26,10 +26,13 @@ mod tests { use crate::dependencies::Dependency; use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind, SelectorError}; use crate::properties::custom::Token; + use crate::properties::Property; use crate::rules::CssRule; use crate::rules::Location; use crate::stylesheet::*; use crate::targets::Browsers; + use crate::traits::{Parse, ToCss}; + use crate::values::color::CssColor; use indoc::indoc; use std::collections::HashMap; @@ -11840,7 +11843,7 @@ mod tests { let mut input = ParserInput::new(s); let mut parser = Parser::new(&mut input); let v = CssColor::parse(&mut parser).unwrap().to_rgb(); - format!(".foo{{color:{}}}", v.to_css_string()) + format!(".foo{{color:{}}}", v.to_css_string(PrinterOptions::default()).unwrap()) } // regex for converting web platform tests: @@ -17565,6 +17568,28 @@ mod tests { } _ => unreachable!(), } + + let color = CssColor::parse_string("#f0f").unwrap(); + assert_eq!(color.to_css_string(PrinterOptions::default()).unwrap(), "#f0f"); + + let rule = CssRule::parse_string(".foo { color: red }", ParserOptions::default()).unwrap(); + assert_eq!( + rule.to_css_string(PrinterOptions::default()).unwrap(), + indoc! {r#" + .foo { + color: red; + }"#} + ); + + let property = Property::parse_string("color", "#f0f", ParserOptions::default()).unwrap(); + assert_eq!( + property.to_css_string(false, PrinterOptions::default()).unwrap(), + "color: #f0f" + ); + assert_eq!( + property.to_css_string(true, PrinterOptions::default()).unwrap(), + "color: #f0f !important" + ); } #[test] diff --git a/src/media_query.rs b/src/media_query.rs index 7a045192..8668ad59 100644 --- a/src/media_query.rs +++ b/src/media_query.rs @@ -905,6 +905,7 @@ fn process_condition<'i>( #[cfg(test)] mod tests { use super::*; + use crate::stylesheet::PrinterOptions; fn parse(s: &str) -> MediaQuery { let mut input = ParserInput::new(&s); @@ -916,7 +917,7 @@ mod tests { let mut a = parse(a); let b = parse(b); a.and(&b).unwrap(); - a.to_css_string() + a.to_css_string(PrinterOptions::default()).unwrap() } #[test] diff --git a/src/printer.rs b/src/printer.rs index 82b83a03..5c942cc2 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -7,6 +7,15 @@ use crate::vendor_prefix::VendorPrefix; use cssparser::{serialize_identifier, SourceLocation}; use parcel_sourcemap::{OriginalLocation, SourceMap}; +#[derive(Default)] +pub struct PrinterOptions<'a> { + pub minify: bool, + pub source_map: Option<&'a mut SourceMap>, + pub targets: Option, + pub analyze_dependencies: bool, + pub pseudo_classes: Option>, +} + #[derive(Default, Debug)] pub struct PseudoClasses<'a> { pub hover: Option<&'a str>, @@ -16,47 +25,46 @@ pub struct PseudoClasses<'a> { pub focus_within: Option<&'a str>, } -pub(crate) struct Printer<'a, W> { - pub sources: Option<&'a Vec>, +pub struct Printer<'a, W> { + pub(crate) sources: Option<&'a Vec>, dest: &'a mut W, source_map: Option<&'a mut SourceMap>, - pub source_index: u32, + pub(crate) source_index: u32, indent: u8, line: u32, col: u32, - pub minify: bool, - pub targets: Option, + pub(crate) minify: bool, + pub(crate) targets: Option, /// Vendor prefix override. When non-empty, it overrides /// the vendor prefix of whatever is being printed. - pub vendor_prefix: VendorPrefix, - pub in_calc: bool, - pub css_module: Option>, - pub dependencies: Option<&'a mut Vec>, - pub pseudo_classes: Option>, + pub(crate) vendor_prefix: VendorPrefix, + pub(crate) in_calc: bool, + pub(crate) css_module: Option>, + pub(crate) dependencies: Option>, + pub(crate) pseudo_classes: Option>, } impl<'a, W: std::fmt::Write + Sized> Printer<'a, W> { - pub fn new( - dest: &'a mut W, - source_map: Option<&'a mut SourceMap>, - minify: bool, - targets: Option, - ) -> Printer<'a, W> { + pub fn new(dest: &'a mut W, options: PrinterOptions<'a>) -> Printer<'a, W> { Printer { sources: None, dest, - source_map, + source_map: options.source_map, source_index: 0, indent: 0, line: 0, col: 0, - minify, - targets, + minify: options.minify, + targets: options.targets, vendor_prefix: VendorPrefix::empty(), in_calc: false, css_module: None, - dependencies: None, - pseudo_classes: None, + dependencies: if options.analyze_dependencies { + Some(Vec::new()) + } else { + None + }, + pseudo_classes: options.pseudo_classes, } } diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 186ec02a..0346d900 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -31,7 +31,7 @@ use crate::error::{ParserError, PrinterError}; use crate::parser::starts_with_ignore_ascii_case; use crate::parser::ParserOptions; use crate::prefixes::Feature; -use crate::printer::Printer; +use crate::printer::{Printer, PrinterOptions}; use crate::targets::Browsers; use crate::traits::{Parse, ToCss}; use crate::values::string::CowArcStr; @@ -379,7 +379,13 @@ macro_rules! define_properties { } } - pub(crate) fn value_to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { + pub fn parse_string(name: &'i str, input: &'i str, options: ParserOptions) -> Result>> { + let mut input = ParserInput::new(input); + let mut parser = Parser::new(&mut input); + Self::parse(CowRcStr::from(name), &mut parser, &options) + } + + pub fn value_to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { use Property::*; match self { @@ -398,7 +404,7 @@ macro_rules! define_properties { } } - pub(crate) fn to_css(&self, dest: &mut Printer, important: bool) -> Result<(), PrinterError> where W: std::fmt::Write { + pub fn to_css(&self, dest: &mut Printer, important: bool) -> Result<(), PrinterError> where W: std::fmt::Write { use Property::*; let mut first = true; @@ -470,6 +476,13 @@ macro_rules! define_properties { write!(VendorPrefix::None); Ok(()) } + + pub fn to_css_string(&self, important: bool, options: PrinterOptions) -> Result { + let mut s = String::new(); + let mut printer = Printer::new(&mut s, options); + self.to_css(&mut printer, important)?; + Ok(s) + } } }; } diff --git a/src/properties/transform.rs b/src/properties/transform.rs index aafdb769..f0853513 100644 --- a/src/properties/transform.rs +++ b/src/properties/transform.rs @@ -5,6 +5,7 @@ use crate::error::{ParserError, PrinterError}; use crate::macros::enum_property; use crate::prefixes::Feature; use crate::printer::Printer; +use crate::stylesheet::PrinterOptions; use crate::targets::Browsers; use crate::traits::{Parse, PropertyHandler, ToCss}; use crate::values::{ @@ -53,13 +54,25 @@ impl ToCss for TransformList { if let Some(matrix) = self.to_matrix() { // Generate based on the original transforms. let mut base = String::new(); - self.to_css_base(&mut Printer::new(&mut base, None, true, None))?; + self.to_css_base(&mut Printer::new( + &mut base, + PrinterOptions { + minify: true, + ..PrinterOptions::default() + }, + ))?; // Decompose the matrix into transform functions if possible. // If the resulting length is shorter than the original, use it. if let Some(d) = matrix.decompose() { let mut decomposed = String::new(); - d.to_css_base(&mut Printer::new(&mut decomposed, None, true, None))?; + d.to_css_base(&mut Printer::new( + &mut decomposed, + PrinterOptions { + minify: true, + ..PrinterOptions::default() + }, + ))?; if decomposed.len() < base.len() { base = decomposed; } @@ -68,9 +81,21 @@ impl ToCss for TransformList { // Also generate a matrix() or matrix3d() representation and compare that. let mut mat = String::new(); if let Some(matrix) = matrix.to_matrix2d() { - Transform::Matrix(matrix).to_css(&mut Printer::new(&mut mat, None, true, None))? + Transform::Matrix(matrix).to_css(&mut Printer::new( + &mut mat, + PrinterOptions { + minify: true, + ..PrinterOptions::default() + }, + ))? } else { - Transform::Matrix3d(matrix).to_css(&mut Printer::new(&mut mat, None, true, None))? + Transform::Matrix3d(matrix).to_css(&mut Printer::new( + &mut mat, + PrinterOptions { + minify: true, + ..PrinterOptions::default() + }, + ))? } if mat.len() < base.len() { diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 0c9a1a14..9ab91f58 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -21,15 +21,18 @@ use self::property::PropertyRule; use crate::context::PropertyHandlerContext; use crate::declaration::DeclarationHandler; use crate::dependencies::{Dependency, ImportDependency}; -use crate::error::{MinifyError, PrinterError}; +use crate::error::{MinifyError, ParserError, PrinterError}; +use crate::parser::TopLevelRuleParser; use crate::prefixes::Feature; use crate::printer::Printer; use crate::selector::{downlevel_selectors, get_prefix, is_equivalent}; +use crate::stylesheet::ParserOptions; use crate::targets::Browsers; use crate::traits::ToCss; use crate::values::string::CowArcStr; use crate::vendor_prefix::VendorPrefix; use counter_style::CounterStyleRule; +use cssparser::{parse_one_rule, ParseError, Parser, ParserInput}; use custom_media::CustomMediaRule; use document::MozDocumentRule; use font_face::FontFaceRule; @@ -125,6 +128,24 @@ impl<'a, 'i> ToCssWithContext<'a, 'i> for CssRule<'i> { } } +impl<'i> CssRule<'i> { + /// Parse a single rule. + pub fn parse<'t>( + input: &mut Parser<'i, 't>, + options: &ParserOptions, + ) -> Result>> { + let (_, rule) = parse_one_rule(input, &mut TopLevelRuleParser::new(&options))?; + Ok(rule) + } + + /// Parse a single rule from a string. + pub fn parse_string(input: &'i str, options: ParserOptions) -> Result>> { + let mut input = ParserInput::new(input); + let mut parser = Parser::new(&mut input); + Self::parse(&mut parser, &options) + } +} + impl<'i> ToCss for CssRule<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where diff --git a/src/selector.rs b/src/selector.rs index 92f53e43..ea97d2bf 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -2,6 +2,7 @@ use crate::compat::Feature; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::rules::{StyleContext, ToCssWithContext}; +use crate::stylesheet::PrinterOptions; use crate::targets::Browsers; use crate::traits::{Parse, ToCss}; use crate::vendor_prefix::VendorPrefix; @@ -82,7 +83,7 @@ impl<'i> SelectorImpl<'i> for Selectors { type ExtraMatchingData = (); fn to_css(selectors: &SelectorList<'i, Self>, dest: &mut W) -> std::fmt::Result { - let mut printer = Printer::new(dest, None, false, None); + let mut printer = Printer::new(dest, PrinterOptions::default()); serialize_selector_list(selectors.0.iter(), &mut printer, None, false).map_err(|_| std::fmt::Error) } } diff --git a/src/stylesheet.rs b/src/stylesheet.rs index d74f28aa..8a495fd9 100644 --- a/src/stylesheet.rs +++ b/src/stylesheet.rs @@ -10,10 +10,10 @@ use crate::rules::{CssRule, CssRuleList, MinifyContext}; use crate::targets::Browsers; use crate::traits::ToCss; use cssparser::{Parser, ParserInput, RuleListParser}; -use parcel_sourcemap::SourceMap; use std::collections::{HashMap, HashSet}; pub use crate::parser::ParserOptions; +pub use crate::printer::PrinterOptions; pub use crate::printer::PseudoClasses; #[derive(Debug)] @@ -23,15 +23,6 @@ pub struct StyleSheet<'i> { options: ParserOptions, } -#[derive(Default)] -pub struct PrinterOptions<'a> { - pub minify: bool, - pub source_map: Option<&'a mut SourceMap>, - pub targets: Option, - pub analyze_dependencies: bool, - pub pseudo_classes: Option>, -} - #[derive(Default)] pub struct MinifyOptions { pub targets: Option, @@ -124,16 +115,8 @@ impl<'i> StyleSheet<'i> { pub fn to_css(&self, options: PrinterOptions) -> Result> { let mut dest = String::new(); - let mut printer = Printer::new(&mut dest, options.source_map, options.minify, options.targets); + let mut printer = Printer::new(&mut dest, options); - let mut dependencies = if options.analyze_dependencies { - Some(Vec::new()) - } else { - None - }; - - printer.dependencies = dependencies.as_mut(); - printer.pseudo_classes = options.pseudo_classes; printer.sources = Some(&self.sources); if self.options.css_modules { @@ -148,17 +131,17 @@ impl<'i> StyleSheet<'i> { printer.newline()?; Ok(ToCssResult { + dependencies: printer.dependencies, code: dest, exports: Some(exports), - dependencies, }) } else { self.rules.to_css(&mut printer)?; printer.newline()?; Ok(ToCssResult { + dependencies: printer.dependencies, code: dest, exports: None, - dependencies, }) } } @@ -193,15 +176,7 @@ impl<'i> StyleAttribute<'i> { ); let mut dest = String::new(); - let mut printer = Printer::new(&mut dest, None, options.minify, options.targets); - - let mut dependencies = if options.analyze_dependencies { - Some(Vec::new()) - } else { - None - }; - - printer.dependencies = dependencies.as_mut(); + let mut printer = Printer::new(&mut dest, options); let len = self.declarations.declarations.len() + self.declarations.important_declarations.len(); let mut i = 0; @@ -223,9 +198,9 @@ impl<'i> StyleAttribute<'i> { write!(self.declarations.important_declarations, true); Ok(ToCssResult { + dependencies: printer.dependencies, code: dest, exports: None, - dependencies, }) } } diff --git a/src/traits.rs b/src/traits.rs index 8a5b648f..79e7ee62 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,18 +3,27 @@ use crate::declaration::DeclarationList; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; use crate::properties::Property; +use crate::stylesheet::PrinterOptions; use crate::targets::Browsers; use cssparser::*; +/// Trait for things that can be parsed from CSS syntax. pub trait Parse<'i>: Sized { - /// Parse a value of this type. - /// - /// Returns an error on failure. + /// Parse a value of this type using an existing parser. fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>>; + + /// Parse a value from a string. + /// + /// (This is a convenience wrapper for `parse` and probably should not be overridden.) + fn parse_string(input: &'i str) -> Result>> { + let mut input = ParserInput::new(input); + let mut parser = Parser::new(&mut input); + Self::parse(&mut parser) + } } /// Trait for things the can serialize themselves in CSS syntax. -pub(crate) trait ToCss { +pub trait ToCss { /// Serialize `self` in CSS syntax, writing to `dest`. fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where @@ -24,11 +33,11 @@ pub(crate) trait ToCss { /// /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) #[inline] - fn to_css_string(&self) -> String { + fn to_css_string(&self, options: PrinterOptions) -> Result { let mut s = String::new(); - let mut printer = Printer::new(&mut s, None, false, None); - self.to_css(&mut printer).unwrap(); - s + let mut printer = Printer::new(&mut s, options); + self.to_css(&mut printer)?; + Ok(s) } } @@ -54,11 +63,13 @@ pub(crate) trait PropertyHandler<'i>: Sized { fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i>); } -pub trait TryAdd { - fn try_add(&self, other: &T) -> Option; +pub(crate) mod private { + pub trait TryAdd { + fn try_add(&self, other: &T) -> Option; + } } -pub trait FromStandard: Sized { +pub(crate) trait FromStandard: Sized { fn from_standard(val: &T) -> Option; } diff --git a/src/values/angle.rs b/src/values/angle.rs index 87c59f49..74af6171 100644 --- a/src/values/angle.rs +++ b/src/values/angle.rs @@ -3,7 +3,7 @@ use super::length::serialize_dimension; use super::percentage::DimensionPercentage; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; -use crate::traits::{Parse, ToCss, TryAdd}; +use crate::traits::{private::TryAdd, Parse, ToCss}; use cssparser::*; use std::f32::consts::PI; diff --git a/src/values/length.rs b/src/values/length.rs index 9c25b824..ff875e61 100644 --- a/src/values/length.rs +++ b/src/values/length.rs @@ -3,7 +3,7 @@ use super::number::serialize_number; use super::percentage::DimensionPercentage; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; -use crate::traits::{Parse, ToCss, TryAdd}; +use crate::traits::{private::TryAdd, Parse, ToCss}; use const_str; use cssparser::*; diff --git a/src/values/percentage.rs b/src/values/percentage.rs index 0b3355e5..45e9a542 100644 --- a/src/values/percentage.rs +++ b/src/values/percentage.rs @@ -2,7 +2,7 @@ use super::calc::Calc; use super::number::serialize_number; use crate::error::{ParserError, PrinterError}; use crate::printer::Printer; -use crate::traits::{Parse, ToCss, TryAdd}; +use crate::traits::{private::TryAdd, Parse, ToCss}; use cssparser::*; /// https://drafts.csswg.org/css-values-4/#percentages