wal-css 0.1.1

A framework for creating client-side single-page apps using Rust.
Documentation
#[derive(Debug, PartialEq)]
pub enum Instruction<'a> {
    ComplexSelector(Vec<Selector<'a>>),
    SpecialInstruction {
        command: &'a str,
        parameters: &'a str,
    },
}
impl<'a> Instruction<'a> {
    pub fn gen_mapping(&self, prefix: &str) -> Vec<(String, String)> {
        if let Instruction::ComplexSelector(selectors) = self {
            return selectors
                .iter()
                .filter_map(|s| s.gen_mapping(prefix))
                .collect();
        }
        vec![]
    }
    pub fn gen_css(&self, prefix: &str) -> String {
        match self {
            Instruction::ComplexSelector(selectors) => selectors
                .iter()
                .map(|s| s.gen_css(prefix))
                .collect::<Vec<String>>()
                .join(", "),
            Instruction::SpecialInstruction {
                command,
                parameters,
            } => format!("@{} {}", command.trim(), parameters.trim()),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Selector<'a> {
    Id(&'a str),
    Class(&'a str),
    Element(&'a str),
}
impl<'a> Selector<'a> {
    pub fn gen_mapping(&self, prefix: &str) -> Option<(String, String)> {
        match self {
            Selector::Id(id) => Some((id.to_string(), format!("{}{}", prefix, id))),
            Selector::Class(class) => Some((class.to_string(), format!("{}{}", prefix, class))),
            Selector::Element(_) => None,
        }
    }
    pub fn gen_css(&self, prefix: &str) -> String {
        match self {
            Selector::Id(id) => format!("#{}{}", prefix, id),
            Selector::Class(class) => format!(".{}{}", prefix, class),
            Selector::Element(element) => element.to_string(),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Section<'a> {
    WithBody {
        instruction: Instruction<'a>,
        body: Body<'a>,
    },
    WithoutBody(Instruction<'a>),
}
impl<'a> Section<'a> {
    pub fn gen_mapping(&self, prefix: &str) -> Vec<(String, String)> {
        if let Section::WithBody { instruction, body } = self {
            let mut mapping = instruction.gen_mapping(prefix);

            if let Body::ParsedBody(stylesheet) = body {
                mapping.extend(stylesheet.gen_mapping(prefix));
            }
            return mapping;
        }
        vec![]
    }
    pub fn gen_css(&self, prefix: &str) -> String {
        match self {
            Section::WithBody { instruction, body } => format!(
                "{} {{ {} }}",
                instruction.gen_css(prefix),
                body.gen_css(prefix)
            ),
            Section::WithoutBody(instruction) => {
                format!("{};", instruction.gen_css(prefix))
            }
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Body<'a> {
    ParsedBody(Stylesheet<'a>),
    LiteralBody(&'a str),
}
impl<'a> Body<'a> {
    pub fn gen_css(&self, prefix: &str) -> String {
        match self {
            Body::LiteralBody(body_str) => body_str.trim().to_string(),
            Body::ParsedBody(stylesheet) => stylesheet.gen_css(prefix),
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct Stylesheet<'a> {
    sections: Vec<Section<'a>>,
}
impl<'a> Stylesheet<'a> {
    pub fn new(sections: Vec<Section<'a>>) -> Self {
        Stylesheet { sections }
    }
    pub fn gen_mapping(&self, prefix: &str) -> Vec<(String, String)> {
        self.sections
            .iter()
            .flat_map(|s| s.gen_mapping(prefix))
            .collect::<Vec<(String, String)>>()
    }
    pub fn gen_css(&self, prefix: &str) -> String {
        self.sections
            .iter()
            .map(|s| s.gen_css(prefix))
            .collect::<Vec<String>>()
            .join(" ")
    }
}

#[cfg(test)]
mod tests {
    use crate::parser::types::{Body, Instruction, Section, Stylesheet};

    use super::Selector;

    #[test]
    fn class_selector_gens_correct_mapping() {
        let selector = Selector::Class("class");
        let prefix = "test-";
        let expected = Some(("class".to_owned(), "test-class".to_owned()));

        assert_eq!(expected, selector.gen_mapping(prefix))
    }
    #[test]
    fn id_selector_gens_correct_mapping() {
        let selector = Selector::Id("id");
        let prefix = "test-";
        let expected = Some(("id".to_owned(), "test-id".to_owned()));

        assert_eq!(expected, selector.gen_mapping(prefix))
    }
    #[test]
    fn element_selector_does_not_gen_mapping() {
        let selector = Selector::Element("body");
        let prefix = "test-";
        let expected = None;

        assert_eq!(expected, selector.gen_mapping(prefix))
    }
    #[test]
    fn instruction_complex_selector_gens_correct_mapping() {
        let instruction = Instruction::ComplexSelector(vec![
            Selector::Class("class"),
            Selector::Element("body"),
            Selector::Id("id"),
        ]);
        let prefix = "test-";
        let expected = vec![
            ("class".to_owned(), "test-class".to_owned()),
            ("id".to_owned(), "test-id".to_owned()),
        ];

        assert_eq!(expected, instruction.gen_mapping(prefix))
    }
    #[test]
    fn instruction_special_insruction_gens_no_mapping() {
        let instruction = Instruction::SpecialInstruction {
            command: "namespace",
            parameters: " svg url('http://www.w3.org/2000/svg')",
        };
        let prefix = "test-";
        let expected: Vec<(String, String)> = vec![];

        assert_eq!(expected, instruction.gen_mapping(prefix))
    }
    #[test]
    fn section_with_literal_body_gens_correct_mapping() {
        let section = Section::WithBody {
            instruction: Instruction::ComplexSelector(vec![Selector::Class("class")]),
            body: Body::LiteralBody(" color: red; "),
        };
        let prefix = "test-";
        let expected = vec![("class".to_owned(), "test-class".to_owned())];

        assert_eq!(expected, section.gen_mapping(prefix))
    }
    #[test]
    fn section_with_parsed_body_gens_correct_mapping() {
        let section = Section::WithBody {
            instruction: Instruction::SpecialInstruction {
                command: "media",
                parameters: " (hover: hover) ",
            },
            body: Body::ParsedBody(Stylesheet::new(vec![Section::WithBody {
                instruction: Instruction::ComplexSelector(vec![Selector::Class("class")]),
                body: Body::LiteralBody(" color: green; "),
            }])),
        };
        let prefix = "test-";
        let expected = vec![("class".to_owned(), "test-class".to_owned())];

        assert_eq!(expected, section.gen_mapping(prefix))
    }
    #[test]
    fn section_without_body_gens_no_mapping() {
        let section = Section::WithoutBody(Instruction::SpecialInstruction {
            command: "namespace",
            parameters: " svg url('http://www.w3.org/2000/svg')",
        });
        let prefix = "test-";
        let expected: Vec<(String, String)> = vec![];

        assert_eq!(expected, section.gen_mapping(prefix))
    }
    #[test]
    fn stylesheet_gens_correct_mapping() {
        let stylesheet = Stylesheet::new(vec![
            Section::WithBody {
                instruction: Instruction::SpecialInstruction {
                    command: "media",
                    parameters: " (hover: hover) ",
                },
                body: Body::ParsedBody(Stylesheet::new(vec![Section::WithBody {
                    instruction: Instruction::ComplexSelector(vec![Selector::Class("class1")]),
                    body: Body::LiteralBody(" color: green; "),
                }])),
            },
            Section::WithBody {
                instruction: Instruction::ComplexSelector(vec![Selector::Class("class2")]),
                body: Body::LiteralBody(" color: red; "),
            },
            Section::WithBody {
                instruction: Instruction::ComplexSelector(vec![Selector::Id("id1")]),
                body: Body::LiteralBody(" color: green; "),
            },
        ]);
        let prefix = "test-";
        let expected = vec![
            ("class1".to_owned(), "test-class1".to_owned()),
            ("class2".to_owned(), "test-class2".to_owned()),
            ("id1".to_owned(), "test-id1".to_owned()),
        ];

        assert_eq!(expected, stylesheet.gen_mapping(prefix))
    }
    #[test]
    fn class_selector_gens_correct_css() {
        let selector = Selector::Class("class");
        let prefix = "test-";
        let expected = ".test-class".to_owned();

        assert_eq!(expected, selector.gen_css(prefix))
    }
    #[test]
    fn id_selector_gens_correct_css() {
        let selector = Selector::Id("id");
        let prefix = "test-";
        let expected = "#test-id".to_owned();

        assert_eq!(expected, selector.gen_css(prefix))
    }
    #[test]
    fn element_selector_gens_correct_css() {
        let selector = Selector::Element("body");
        let prefix = "test-";
        let expected = "body";

        assert_eq!(expected, selector.gen_css(prefix))
    }
    #[test]
    fn instruction_complex_selector_gens_correct_css() {
        let instruction = Instruction::ComplexSelector(vec![
            Selector::Class("class"),
            Selector::Element("body"),
            Selector::Id("id"),
        ]);
        let prefix = "test-";
        let expected = ".test-class, body, #test-id".to_owned();

        assert_eq!(expected, instruction.gen_css(prefix))
    }
    #[test]
    fn instruction_special_insruction_gens_correct_css() {
        let instruction = Instruction::SpecialInstruction {
            command: "namespace",
            parameters: " svg url('http://www.w3.org/2000/svg')",
        };
        let prefix = "test-";
        let expected = "@namespace svg url('http://www.w3.org/2000/svg')".to_owned();

        assert_eq!(expected, instruction.gen_css(prefix))
    }
    #[test]
    fn literal_body_gens_correct_css() {
        let body = Body::LiteralBody(" color: green; ");
        let prefix = "test-";
        let expected = "color: green;".to_owned();

        assert_eq!(expected, body.gen_css(prefix))
    }
    #[test]
    fn parsed_body_gens_correct_css() {
        let body = Body::ParsedBody(Stylesheet::new(vec![Section::WithBody {
            instruction: Instruction::ComplexSelector(vec![Selector::Class("class")]),
            body: Body::LiteralBody(" color: green; "),
        }]));
        let prefix = "test-";
        let expected = ".test-class { color: green; }".to_owned();

        assert_eq!(expected, body.gen_css(prefix))
    }
    #[test]
    fn section_without_body_gens_correct_css() {
        let section = Section::WithoutBody(Instruction::SpecialInstruction {
            command: "namespace",
            parameters: " svg url('http://www.w3.org/2000/svg')",
        });
        let prefix = "test-";
        let expected = "@namespace svg url('http://www.w3.org/2000/svg');".to_owned();

        assert_eq!(expected, section.gen_css(prefix))
    }
    #[test]
    fn section_with_body_gens_correct_css() {
        let section = Section::WithBody {
            instruction: Instruction::SpecialInstruction {
                command: "media",
                parameters: " (hover: hover) ",
            },
            body: Body::ParsedBody(Stylesheet::new(vec![Section::WithBody {
                instruction: Instruction::ComplexSelector(vec![Selector::Class("class")]),
                body: Body::LiteralBody(" color: green; "),
            }])),
        };
        let prefix = "test-";
        let expected = "@media (hover: hover) { .test-class { color: green; } }";

        assert_eq!(expected, section.gen_css(prefix))
    }
    #[test]
    fn stylesheet_gens_correct_css() {
        let stylesheet = Stylesheet::new(vec![
            Section::WithBody {
                instruction: Instruction::SpecialInstruction {
                    command: "media",
                    parameters: " (hover: hover) ",
                },
                body: Body::ParsedBody(Stylesheet::new(vec![Section::WithBody {
                    instruction: Instruction::ComplexSelector(vec![Selector::Class("class1")]),
                    body: Body::LiteralBody(" color: green; "),
                }])),
            },
            Section::WithBody {
                instruction: Instruction::ComplexSelector(vec![Selector::Class("class2")]),
                body: Body::LiteralBody(" color: red; "),
            },
            Section::WithBody {
                instruction: Instruction::ComplexSelector(vec![Selector::Id("id1")]),
                body: Body::LiteralBody(" color: green; "),
            },
        ]);
        let prefix = "test-";
        let expected = "@media (hover: hover) { .test-class1 { color: green; } } .test-class2 { color: red; } #test-id1 { color: green; }".to_owned();

        assert_eq!(expected, stylesheet.gen_css(prefix))
    }
}