use crate::compat::Feature;
use crate::context::{DeclarationContext, PropertyHandlerContext};
use crate::css_modules::{CssModule, CssModuleExports, CssModuleReferences};
use crate::declaration::{DeclarationBlock, DeclarationHandler};
use crate::dependencies::Dependency;
use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterError, PrinterErrorKind};
use crate::parser::TopLevelRuleParser;
use crate::printer::Printer;
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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StyleSheet<'i, 'o> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub rules: CssRuleList<'i>,
pub sources: Vec<String>,
pub source_map_url: Option<String>,
#[cfg_attr(feature = "serde", serde(skip))]
options: ParserOptions<'o, 'i>,
}
#[derive(Default)]
pub struct MinifyOptions {
pub targets: Option<Browsers>,
pub unused_symbols: HashSet<String>,
}
#[derive(Debug)]
pub struct ToCssResult {
pub code: String,
pub exports: Option<CssModuleExports>,
pub references: Option<CssModuleReferences>,
pub dependencies: Option<Vec<Dependency>>,
}
impl<'i, 'o> StyleSheet<'i, 'o> {
pub fn new(sources: Vec<String>, rules: CssRuleList<'i>, options: ParserOptions<'o, 'i>) -> StyleSheet<'i, 'o> {
StyleSheet {
sources,
source_map_url: None,
rules,
options,
}
}
pub fn parse(code: &'i str, options: ParserOptions<'o, 'i>) -> Result<Self, Error<ParserError<'i>>> {
let mut input = ParserInput::new(&code);
let mut parser = Parser::new(&mut input);
let rule_list_parser = RuleListParser::new_for_stylesheet(&mut parser, TopLevelRuleParser::new(&options));
let mut rules = vec![];
for rule in rule_list_parser {
let rule = match rule {
Ok((_, CssRule::Ignored)) => continue,
Ok((_, rule)) => rule,
Err((e, _)) => {
if options.error_recovery {
options.warn(e);
continue;
}
return Err(Error::from(e, options.filename.clone()));
}
};
rules.push(rule)
}
Ok(StyleSheet {
sources: vec![options.filename.clone()],
source_map_url: parser.current_source_map_url().map(|s| s.to_owned()),
rules: CssRuleList(rules),
options,
})
}
pub fn source_map(&self) -> Option<SourceMap> {
let source_map_url = self.source_map_url.as_ref()?;
SourceMap::from_data_url("/", source_map_url).ok()
}
pub fn minify(&mut self, options: MinifyOptions) -> Result<(), Error<MinifyErrorKind>> {
let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
let mut handler = DeclarationHandler::new(options.targets);
let mut important_handler = DeclarationHandler::new(options.targets);
let custom_media = if self.options.custom_media
&& options.targets.is_some()
&& !Feature::CustomMediaQueries.is_compatible(options.targets.unwrap())
{
let mut custom_media = HashMap::new();
for rule in &self.rules.0 {
if let CssRule::CustomMedia(rule) = rule {
custom_media.insert(rule.name.0.clone(), rule.clone());
}
}
Some(custom_media)
} else {
None
};
let mut ctx = MinifyContext {
targets: &options.targets,
handler: &mut handler,
important_handler: &mut important_handler,
handler_context: &mut context,
unused_symbols: &options.unused_symbols,
custom_media,
};
self.rules.minify(&mut ctx, false).map_err(|e| Error {
kind: e.kind,
loc: Some(ErrorLocation::new(
e.loc,
self.sources[e.loc.source_index as usize].clone(),
)),
})?;
Ok(())
}
pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, Error<PrinterErrorKind>> {
let mut dest = String::with_capacity(1);
let mut printer = Printer::new(&mut dest, options);
printer.sources = Some(&self.sources);
if let Some(config) = &self.options.css_modules {
let mut exports = HashMap::new();
let mut references = HashMap::new();
printer.css_module = Some(CssModule::new(
config,
printer.filename(),
&mut exports,
&mut references,
));
self.rules.to_css(&mut printer)?;
printer.newline()?;
Ok(ToCssResult {
dependencies: printer.dependencies,
code: dest,
exports: Some(exports),
references: Some(references),
})
} else {
self.rules.to_css(&mut printer)?;
printer.newline()?;
if let Some(sm) = printer.source_map {
if let Some(mut input_sm) = self.source_map() {
let _ = sm.extends(&mut input_sm);
}
}
Ok(ToCssResult {
dependencies: printer.dependencies,
code: dest,
exports: None,
references: None,
})
}
}
}
pub struct StyleAttribute<'i> {
pub declarations: DeclarationBlock<'i>,
}
impl<'i> StyleAttribute<'i> {
pub fn parse(
code: &'i str,
options: ParserOptions<'_, 'i>,
) -> Result<StyleAttribute<'i>, Error<ParserError<'i>>> {
let mut input = ParserInput::new(&code);
let mut parser = Parser::new(&mut input);
Ok(StyleAttribute {
declarations: DeclarationBlock::parse(&mut parser, &options).map_err(|e| Error::from(e, "".into()))?,
})
}
pub fn minify(&mut self, options: MinifyOptions) {
let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
let mut handler = DeclarationHandler::new(options.targets);
let mut important_handler = DeclarationHandler::new(options.targets);
context.context = DeclarationContext::StyleAttribute;
self.declarations.minify(&mut handler, &mut important_handler, &mut context);
}
pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, PrinterError> {
assert!(
options.source_map.is_none(),
"Source maps are not supported for style attributes"
);
let mut dest = String::with_capacity(1);
let mut printer = Printer::new(&mut dest, options);
self.declarations.to_css(&mut printer)?;
Ok(ToCssResult {
dependencies: printer.dependencies,
code: dest,
exports: None,
references: None,
})
}
}