Skip to content

Commit

Permalink
Add deduplicate rule
Browse files Browse the repository at this point in the history
  • Loading branch information
texodus committed Nov 24, 2023
1 parent 56fdc73 commit 7b2e9a6
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 32 deletions.
6 changes: 3 additions & 3 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use crate::transformers;
/// A non-nested "flat" CSS representation, suitable for browser output. The
/// [`Css`] AST is typically generated via the
/// [`crate::ast::Tree::flatten_tree`] method.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Css<'a>(pub Vec<FlatRuleset<'a>>);

impl<'a> Css<'a> {
Expand All @@ -67,7 +67,7 @@ impl<'a> Css<'a> {
}

/// Iterate over the immediate children of this Tree (non-recursive).
pub fn iter(&self) -> impl Iterator<Item = &'_ FlatRuleset<'a>> {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &'_ FlatRuleset<'a>> {
self.0.iter()
}
}
Expand Down Expand Up @@ -97,7 +97,7 @@ impl<'a> RenderCss for Css<'a> {
/// [`RenderCss`] if this is needed, though this output can't be read by
/// browsers and is not identical to the input since whitespace has been
/// discarded.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Tree<'a>(pub Vec<TreeRuleset<'a>>);

impl<'a> Tree<'a> {
Expand Down
8 changes: 4 additions & 4 deletions src/ast/ruleset/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use crate::transform::TransformCss;
/// color: red;
/// }
/// ```
#[derive(Debug, Clone)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct SelectorRuleset<'a, T>(pub Selector<'a>, pub Vec<T>);

impl<'a, T: RenderCss> RenderCss for SelectorRuleset<'a, T> {
Expand Down Expand Up @@ -95,7 +95,7 @@ impl<'a> RenderCss for QualRule<'a> {
/// font-family: "My Font";
/// };
/// ```
#[derive(Debug, Clone)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct QualRuleset<'a, T>(pub QualRule<'a>, pub Vec<T>);

impl<'a, T: RenderCss> RenderCss for QualRuleset<'a, T> {
Expand Down Expand Up @@ -139,7 +139,7 @@ impl<'a, T: TransformCss<U>, U> TransformCss<U> for QualRuleset<'a, T> {
/// }
/// }
/// ```
#[derive(Debug, Clone)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct QualNestedRuleset<'a, T>(pub QualRule<'a>, pub Vec<Ruleset<'a, T>>);

impl<'a, T: RenderCss> RenderCss for QualNestedRuleset<'a, T> {
Expand Down Expand Up @@ -194,7 +194,7 @@ where
/// }
/// ```
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum Ruleset<'a, T> {
SelectorRuleset(SelectorRuleset<'a, T>),
QualRule(QualRule<'a>),
Expand Down
2 changes: 1 addition & 1 deletion src/ast/ruleset/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::render::RenderCss;
use crate::transform::TransformCss;

/// A CSS rule, of the form `xxx: yyy` (delimited by `;` in a ruleset).
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Rule<'a> {
pub property: Cow<'a, str>,
pub value: Cow<'a, str>,
Expand Down
2 changes: 1 addition & 1 deletion src/ast/tree_ruleset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::transform::TransformCss;
/// A tree node which expresses a recursive `T` over `Ruleset<T>`. Using this
/// struct in place of `Rule` allows nested CSS selectors that can be later
/// flattened.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum TreeRule<'a> {
Rule(Rule<'a>),
Ruleset(TreeRuleset<'a>),
Expand Down
9 changes: 6 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ impl<'a> BuildCss<'a> {
self.trees.insert(path, tree);
}

let dep_trees = self.trees.clone();
for (path, tree) in self.trees.iter_mut() {
for path in self.paths.iter() {
let dep_trees = self.trees.clone();
let tree = self.trees.get_mut(path.as_path()).unwrap();
transformers::apply_import(&dep_trees)(tree);
transformers::apply_mixin(tree);
transformers::apply_var(tree);
Expand All @@ -92,7 +93,9 @@ impl<'a> BuildCss<'a> {
for (path, css) in self.css.iter_mut() {
let srcdir = utils::join_paths(&self.rootdir, path);
transformers::inline_url(&srcdir.to_string_lossy())(css);
transformers::dedupe(css);
transformers::merge_siblings(css);
transformers::remove_mixin(css);
transformers::deduplicate(css);
}

Ok(CompiledCss(self))
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@
//!
//! let mut ast = procss::parse(test).unwrap();
//! transformers::apply_mixin(&mut ast);
//! let flat = ast.flatten_tree().as_css_string();
//! assert_eq!(flat, "div{color:red;}");
//! let mut flat = ast.flatten_tree();
//! transformers::remove_mixin(&mut flat);
//! let css = flat.as_css_string();
//! assert_eq!(css, "div{color:red;}");
//! ```
//!
//! For coordinating large builds on a tree of CSS files, the [`BuildCss`]
Expand Down
16 changes: 8 additions & 8 deletions src/transformers/apply_mixin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::ast::*;
/// # Example
///
/// ```
/// # use procss::{parse, transformers::apply_mixin, RenderCss};
/// # use procss::{parse, transformers::apply_mixin, transformers::remove_mixin, RenderCss};
/// let css = "
/// @mixin test {
/// opacity: 0;
Expand All @@ -32,21 +32,21 @@ use crate::ast::*;
/// ";
/// let mut tree = parse(css).unwrap();
/// apply_mixin(&mut tree);
/// let css = tree.flatten_tree().as_css_string();
/// let mut flat = tree.flatten_tree();
/// remove_mixin(&mut flat);
/// let css = flat.as_css_string();
/// assert_eq!(css, "div.open{color:red;}div.open{opacity:0;}");
/// ```
pub fn apply_mixin<'a>(tree: &mut Tree<'a>) {
let mut mixins: HashMap<&'a str, Vec<TreeRule<'a>>> = HashMap::new();
tree.transform(|ruleset| {
if let Ruleset::QualRuleset(crate::ast::QualRuleset(QualRule(name, Some(val)), props)) = ruleset {
if *name == "mixin" {
if let Ruleset::QualRuleset(crate::ast::QualRuleset(QualRule(name, Some(val)), props)) =
ruleset
{
if *name == "mixin" {
mixins.insert(val.trim(), props.clone());
}
}

if matches!(ruleset, Ruleset::QualRuleset(QualRuleset(QualRule(name, _), _)) if *name == "mixin") {
*ruleset = Ruleset::SelectorRuleset(SelectorRuleset(Selector::default(), vec![]))
}
});

let mut count = 5;
Expand Down
27 changes: 27 additions & 0 deletions src/transformers/deduplicate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘

use std::collections::HashSet;

use crate::ast::Ruleset::{self};
use crate::ast::*;

pub fn deduplicate(css: &mut Css) {
let mut seen: HashSet<&Ruleset<'_, Rule<'_>>> = HashSet::default();
let res = css
.iter()
.rev()
.filter(|x| seen.insert(*x))
.rev()
.cloned()
.collect();
*css = crate::ast::Css(res);
}
6 changes: 2 additions & 4 deletions src/transformers/filter_refs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ use crate::ast::*;

pub fn filter_refs(tree: &mut Tree) {
*tree = Tree(
tree.iter()
.cloned()
.filter(|y| match y {
tree.iter().filter(|&y| match y {
Ruleset::SelectorRuleset(_) => false,
Ruleset::QualRule(_) => true,
Ruleset::QualRuleset(_) => true,
Ruleset::QualNestedRuleset(_) => true,
})
}).cloned()
.collect(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
// │ │
// └───────────────────────────────────────────────────────────────────────────┘

use std::collections::HashSet;

use crate::ast::Ruleset::{self};
use crate::ast::*;

pub fn dedupe(css: &mut Css) {
pub fn merge_siblings(css: &mut Css) {
let mut res = vec![];
let reduced = css.iter().cloned().reduce(|x, y| match (x, y) {
(Ruleset::QualRule(x), Ruleset::QualRule(y)) if x == y => Ruleset::QualRule(x),
Expand All @@ -31,5 +33,7 @@ pub fn dedupe(css: &mut Css) {
res.push(reduced.clone());
}

let mut seen: HashSet<&Ruleset<'_, Rule<'_>>> = HashSet::default();
let res = res.iter().filter(|x| seen.insert(*x)).cloned().collect();
*css = crate::ast::Css(res)
}
8 changes: 6 additions & 2 deletions src/transformers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@
mod apply_import;
mod apply_mixin;
mod apply_var;
mod dedupe;
mod deduplicate;
mod filter_refs;
mod flat_self;
mod inline_url;
mod merge_siblings;
mod remove_mixin;

pub use self::apply_import::apply_import;
pub use self::apply_mixin::apply_mixin;
pub use self::apply_var::apply_var;
pub use self::dedupe::dedupe;
pub use self::deduplicate::deduplicate;
pub use self::filter_refs::filter_refs;
pub(crate) use self::flat_self::flat_self;
pub use self::inline_url::inline_url;
pub use self::merge_siblings::merge_siblings;
pub use self::remove_mixin::remove_mixin;
29 changes: 29 additions & 0 deletions src/transformers/remove_mixin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// ┌───────────────────────────────────────────────────────────────────────────┐
// │ │
// │ ██████╗ ██████╗ ██████╗ Copyright (C) 2022, The Prospective Company │
// │ ██╔══██╗██╔══██╗██╔═══██╗ │
// │ ██████╔╝██████╔╝██║ ██║ This file is part of the Procss library, │
// │ ██╔═══╝ ██╔══██╗██║ ██║ distributed under the terms of the │
// │ ██║ ██║ ██║╚██████╔╝ Apache License 2.0. The full license can │
// │ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ be found in the LICENSE file. │
// │ │
// └───────────────────────────────────────────────────────────────────────────┘

use crate::ast::Ruleset::{self};
use crate::ast::*;

pub fn remove_mixin(css: &mut Css) {
let reduced = css
.iter()
.filter(|&x| {
!matches!(
x,
Ruleset::QualRule(QualRule("mixin", _))
| Ruleset::QualNestedRuleset(QualNestedRuleset(QualRule("mixin", _), _))
| Ruleset::QualRuleset(QualRuleset(QualRule("mixin", _), _))
)
})
.cloned();

*css = crate::ast::Css(reduced.collect())
}
6 changes: 4 additions & 2 deletions tests/apply_mixin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#[cfg(test)]
use std::assert_matches::assert_matches;

use procss::transformers::apply_mixin;
use procss::transformers::{apply_mixin, remove_mixin};
use procss::{parse, RenderCss};

#[test]
Expand All @@ -35,7 +35,9 @@ fn test_advanced_mixin() {
)
.map(|mut x| {
apply_mixin(&mut x);
x.flatten_tree().as_css_string()
let mut flatten = x.flatten_tree();
remove_mixin(&mut flatten);
flatten.as_css_string()
})
.as_deref(),
Ok("div.open{color:red;}div.open{color:green;opacity:0;}")
Expand Down
3 changes: 2 additions & 1 deletion tests/dedupe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ fn test_advanced_mixin() {
.map(|mut tree| {
transformers::apply_mixin(&mut tree);
let mut flat = tree.flatten_tree();
transformers::dedupe(&mut flat);
transformers::merge_siblings(&mut flat);
transformers::remove_mixin(&mut flat);
flat.as_css_string()
})
.as_deref(),
Expand Down

0 comments on commit 7b2e9a6

Please sign in to comment.