css-minify 0.5.2

CSS parser and minificator based on nom
Documentation
use crate::optimizations::transformer::Transform;
use crate::structure::{CssEntities, CssEntity, Media, Parameters, Supports};
use indexmap::map::IndexMap;

#[derive(Default, Debug, Clone)]
pub struct MergeMedia;

impl Transform for MergeMedia {
    fn transform_parameters(&self, parameters: Parameters) -> Parameters {
        parameters
    }

    fn transform(&self, entity: CssEntity) -> CssEntity {
        match entity {
            CssEntity::Media(mut m) => {
                m.screen = m.screen.replace(": ", ":");
                CssEntity::Media(m)
            }
            e => e,
        }
    }

    fn transform_many(&self, entities: CssEntities) -> CssEntities {
        let mut media = IndexMap::new();
        entities
            .0
            .iter()
            .filter_map(|e| match e {
                CssEntity::Media(m) => Some(m),
                _ => None,
            })
            .for_each(|m| {
                media
                    .entry(m.screen.clone())
                    .and_modify(|media: &mut Media| {
                        media
                            .entities
                            .0
                            .append(&mut self.transform_many(m.entities.clone()).0);
                    })
                    .or_insert(m.clone());
            });
        let mut non_media_entities = CssEntities(
            entities
                .0
                .into_iter()
                .filter(|e| !matches!(e, CssEntity::Media(_)))
                .map(|e| match e {
                    CssEntity::Supports(Supports {
                        conditions,
                        entities,
                    }) => Supports {
                        conditions,
                        entities: self.transform_many(entities),
                    }
                    .into(),
                    entity => entity,
                })
                .collect(),
        );
        non_media_entities.0.append(
            &mut media
                .into_iter()
                .map(|(_, m)| m)
                .map(|m| self.transform(m.into()))
                .collect(),
        );
        non_media_entities
    }
}

#[cfg(test)]
mod test {
    use crate::optimizations::merge_media::MergeMedia;
    use crate::optimizations::transformer::Transform;
    use crate::structure::{
        Block, CssEntities, CssEntity, Media, Selector, SelectorWithPseudoClasses, Value,
    };

    #[test]
    fn test_media() {
        assert_eq!(
            MergeMedia::default().transform_many(CssEntities(vec![
                CssEntity::Media(Media {
                    screen: Value::from("only screen and (max-width: 992px)"),
                    entities: vec![CssEntity::Block(Block {
                        selectors: vec![SelectorWithPseudoClasses(
                            Some(Selector::Class("test".into())),
                            vec![]
                        )]
                        .into(),
                        parameters: Default::default()
                    })]
                    .into()
                }),
                CssEntity::Media(Media {
                    screen: Value::from("only screen and (max-width: 992px)"),
                    entities: vec![CssEntity::Block(Block {
                        selectors: vec![SelectorWithPseudoClasses(
                            Some(Selector::Class("test2".into())),
                            vec![]
                        )]
                        .into(),
                        parameters: Default::default()
                    })]
                    .into()
                })
            ])),
            CssEntities(vec![CssEntity::Media(Media {
                screen: Value::from("only screen and (max-width:992px)"),
                entities: vec![
                    CssEntity::Block(Block {
                        selectors: vec![SelectorWithPseudoClasses(
                            Some(Selector::Class("test".into())),
                            vec![]
                        )]
                        .into(),
                        parameters: Default::default()
                    }),
                    CssEntity::Block(Block {
                        selectors: vec![SelectorWithPseudoClasses(
                            Some(Selector::Class("test2".into())),
                            vec![]
                        )]
                        .into(),
                        parameters: Default::default()
                    })
                ]
                .into()
            })])
        )
    }
}