css-minify 0.5.2

CSS parser and minificator based on nom
Documentation
use derive_more::{Deref, DerefMut, Display as DeriveDisplay, From, Into};
use indexmap::map::IndexMap;
use std::fmt::Display;
use std::fmt::Formatter;

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct Block {
    pub selectors: Selectors,
    pub parameters: Parameters,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct Media {
    pub screen: Name,
    pub entities: CssEntities,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct Page {
    pub selectors: Option<Name>,
    pub parameters: Parameters,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct Supports {
    pub conditions: Name,
    pub entities: CssEntities,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct Keyframes {
    pub webkit_prefix: bool,
    pub name: Name,
    pub blocks: KeyframeBlocks,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct KeyframeBlock {
    pub name: Name,
    pub parameters: Parameters,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct FontFace {
    pub parameters: Parameters,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct Viewport {
    pub parameters: Parameters,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct MsViewport {
    pub parameters: Parameters,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct NamespaceAt {
    prefix: Option<Value>,
    url: Value,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct ImportAt {
    url: Value,
    media_queries: Option<Value>,
}

#[derive(Clone, Eq, PartialEq, Debug, From, Into)]
pub struct CharsetAt {
    charset: Value,
}

#[derive(Clone, Eq, PartialEq, Debug, From)]
pub enum At {
    Namespace(NamespaceAt),
    Import(ImportAt),
    Charset(CharsetAt),
}

#[derive(Clone, Eq, PartialEq, Debug, From, DeriveDisplay)]
pub enum CssEntity {
    Block(Block),
    Media(Media),
    Page(Page),
    Supports(Supports),
    FontFace(FontFace),
    Viewport(Viewport),
    MsViewport(MsViewport),
    Keyframes(Keyframes),
    At(At),
}

#[derive(Clone, Eq, PartialEq, Default, Debug, Deref, DerefMut, From, Into)]
pub struct Selectors(pub(crate) Vec<SelectorWithPseudoClasses>);

#[derive(Clone, Eq, PartialEq, Default, Debug, Deref, DerefMut, From, Into)]
pub struct Parameters(pub(crate) IndexMap<Name, Value>);

#[derive(Clone, Eq, PartialEq, Default, Debug, Deref, DerefMut, From, Into)]
pub struct Blocks(pub(crate) Vec<Block>);

#[derive(Clone, Eq, PartialEq, Default, Debug, Deref, DerefMut, From, Into)]
pub struct KeyframeBlocks(pub(crate) Vec<KeyframeBlock>);

#[derive(Clone, Eq, PartialEq, Default, Debug, Deref, DerefMut, From, Into)]
pub struct CssEntities(pub(crate) Vec<CssEntity>);

pub type Name = String;
pub type Value = String;

pub type Id = String;
pub type Class = String;
pub type Tag = String;

#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct SelectorWithPseudoClasses(pub Option<Selector>, pub Vec<PseudoClass>);
impl Display for SelectorWithPseudoClasses {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if let Some(s) = &self.0 {
            write!(f, "{}", s)?
        }
        for pc in self.1.iter() {
            write!(f, "{}", pc)?
        }

        Ok(())
    }
}

#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct PseudoClass {
    pub name: String,
    pub params: Option<String>,
    pub next: Option<String>,
    pub prefix: String,
}

impl Display for PseudoClass {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}{}", self.prefix, self.name)?;
        if let Some(params) = &self.params {
            write!(f, "({})", params)?;
        }
        if let Some(next) = &self.next {
            write!(f, " {}", next)?;
        }

        Ok(())
    }
}

#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub enum Selector {
    Id(Id),
    Class(Class),
    Tag(Tag),
}

impl Display for Selector {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Selector::Id(id) => {
                write!(f, "#{}", id)
            }
            Selector::Class(class) => {
                write!(f, ".{}", class)
            }
            Selector::Tag(tag) => {
                write!(f, "{}", tag)
            }
        }
    }
}

impl Display for Selectors {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut selectors = self
            .0
            .iter()
            .map(|selector| format!("{}", selector))
            .collect::<Vec<String>>();
        selectors.sort();
        write!(f, "{}", selectors.join(","))
    }
}

impl Display for Parameters {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let parameters = self
            .0
            .iter()
            .map(|(name, value)| format!("{}:{}", name, value))
            .collect::<Vec<String>>();
        write!(f, "{}", parameters.join(";"))
    }
}

impl Display for Block {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}{{{}}}", self.selectors, self.parameters)
    }
}

impl Display for KeyframeBlock {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}{{{}}}", self.name, self.parameters)
    }
}

impl Display for Media {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@media {}{{{}}}", self.screen, self.entities)
    }
}

impl Display for Page {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@page")?;
        if let Some(selectors) = &self.selectors {
            write!(f, " {}", selectors)?
        }
        write!(f, " {{{}}}", self.parameters)?;
        Ok(())
    }
}

impl Display for Supports {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@supports {}{{{}}}", self.conditions, self.entities)
    }
}

impl Display for FontFace {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@font-face {{{}}}", self.parameters)
    }
}

impl Display for Viewport {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@viewport {{{}}}", self.parameters)
    }
}

impl Display for MsViewport {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@-ms-viewport {{{}}}", self.parameters)
    }
}

impl Display for Keyframes {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        if self.webkit_prefix {
            write!(f, "@-webkit-keyframes {}{{{}}}", self.name, self.blocks)
        } else {
            write!(f, "@keyframes {}{{{}}}", self.name, self.blocks)
        }
    }
}

impl Display for Blocks {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            self.0
                .iter()
                .map(|block| format!("{}", block))
                .collect::<Vec<String>>()
                .join("")
        )
    }
}

impl Display for KeyframeBlocks {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            self.0
                .iter()
                .map(|block| format!("{}", block))
                .collect::<Vec<String>>()
                .join("")
        )
    }
}

impl Display for NamespaceAt {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@namespace ")?;
        if let Some(prefix) = &self.prefix {
            write!(f, "{} ", prefix)?;
        }
        write!(f, "{};", self.url)?;
        Ok(())
    }
}

impl Display for CharsetAt {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@charset {};", self.charset)
    }
}

impl Display for ImportAt {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "@import {};", self.url)?;
        if let Some(media_queries) = &self.media_queries {
            write!(f, " {}", media_queries)?
        }
        Ok(())
    }
}

impl Display for At {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            At::Namespace(n) => {
                write!(f, "{}", n)
            }
            At::Import(i) => {
                write!(f, "{}", i)
            }
            At::Charset(c) => write!(f, "{}", c),
        }
    }
}

impl Display for CssEntities {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            self.0
                .iter()
                .map(|block| format!("{}", block))
                .collect::<Vec<String>>()
                .join("")
        )
    }
}

#[cfg(test)]
mod test {
    use crate::structure::{Block, Blocks, Selector, SelectorWithPseudoClasses};
    use indexmap::map::IndexMap;

    #[test]
    fn write_block() {
        let blocks: Blocks = vec![
            Block {
                selectors: vec![
                    SelectorWithPseudoClasses(Some(Selector::Id("some_id".into())), vec![]),
                    SelectorWithPseudoClasses(Some(Selector::Tag("input".into())), vec![]),
                ]
                .into(),
                parameters: {
                    let mut tmp = IndexMap::new();
                    tmp.insert("padding".into(), "5px 3px".into());
                    tmp.insert("color".into(), "white".into());
                    tmp.into()
                },
            },
            Block {
                selectors: vec![
                    SelectorWithPseudoClasses(Some(Selector::Id("some_id_2".into())), vec![]),
                    SelectorWithPseudoClasses(Some(Selector::Class("class".into())), vec![]),
                ]
                .into(),
                parameters: {
                    let mut tmp = IndexMap::new();
                    tmp.insert("padding".into(), "5px 4px".into());
                    tmp.insert("color".into(), "black".into());
                    tmp.into()
                },
            },
        ]
        .into();
        assert_eq!(format!("{}", blocks), "#some_id,input{padding:5px 3px;color:white}#some_id_2,.class{padding:5px 4px;color:black}")
    }
}