ori-core 0.1.0-alpha.1

Core library for Ori, a declarative UI framework for Rust.
Documentation
use std::str::FromStr;

use ori_graphics::Color;
use pest::{error::Error, iterators::Pair, Parser};
use pest_derive::Parser;

use crate::{
    StyleAttribute, StyleAttributeKey, StyleAttributeValue, StyleClasses, StyleElement, StyleRule,
    StyleSelector, StyleSelectors, StyleStates, StyleTransition, Stylesheet, Unit,
};

#[derive(Parser)]
#[grammar = "style/grammar.pest"]
pub struct StyleParser;

pub type StyleParseError = Error<Rule>;
pub type SelectorParseError = Error<Rule>;

fn parse_number(pair: Pair<'_, Rule>) -> f32 {
    pair.as_str().parse().unwrap()
}

fn parse_unit(pair: Pair<'_, Rule>) -> Unit {
    let mut pairs = pair.into_inner();

    let number_pair = pairs.next().unwrap();
    let number = parse_number(number_pair);

    match pairs.next().as_ref().map(Pair::as_rule) {
        Some(Rule::Px) | None => Unit::Px(number),
        Some(Rule::Pt) => Unit::Pt(number),
        Some(Rule::Pc) => Unit::Pc(number),
        Some(Rule::Vw) => Unit::Vw(number),
        Some(Rule::Vh) => Unit::Vh(number),
        Some(Rule::Em) => Unit::Em(number),
        _ => unreachable!(),
    }
}

fn parse_color(pair: Pair<'_, Rule>) -> Color {
    let pair = pair.into_inner().next().unwrap();

    match pair.as_rule() {
        Rule::HexColor => Color::hex(pair.as_str()),
        Rule::RgbColor => {
            let mut iter = pair.into_inner();

            let r = iter.next().unwrap().as_str().parse().unwrap();
            let g = iter.next().unwrap().as_str().parse().unwrap();
            let b = iter.next().unwrap().as_str().parse().unwrap();

            Color::rgb(r, g, b)
        }
        Rule::RgbaColor => {
            let mut iter = pair.into_inner();

            let r = iter.next().unwrap().as_str().parse().unwrap();
            let g = iter.next().unwrap().as_str().parse().unwrap();
            let b = iter.next().unwrap().as_str().parse().unwrap();
            let a = iter.next().unwrap().as_str().parse().unwrap();

            Color::rgba(r, g, b, a)
        }
        _ => unreachable!(),
    }
}

fn parse_transition(pair: Option<Pair<'_, Rule>>) -> Option<StyleTransition> {
    Some(StyleTransition::new(parse_number(pair?)))
}

fn parse_value(pair: Pair<'_, Rule>) -> (StyleAttributeValue, Option<StyleTransition>) {
    let mut pairs = pair.into_inner();

    let value = pairs.next().unwrap();
    let transition = parse_transition(pairs.next());
    let value = match value.as_rule() {
        Rule::String => {
            let value = &value.as_str()[1..value.as_str().len() - 1];
            StyleAttributeValue::String(value.to_string())
        }
        Rule::Enum => StyleAttributeValue::Enum(value.as_str().to_string()),
        Rule::Unit => StyleAttributeValue::Unit(parse_unit(value)),
        Rule::Color => StyleAttributeValue::Color(parse_color(value)),
        _ => unreachable!(),
    };

    (value, transition)
}

fn parse_class(pair: Pair<'_, Rule>) -> String {
    pair.into_inner().as_str().to_string()
}

fn parse_state(pair: Pair<'_, Rule>) -> String {
    pair.into_inner().as_str().to_string()
}

fn parse_selector(pair: Pair<'_, Rule>) -> StyleSelector {
    let mut pairs = pair.into_inner();

    let mut element_pairs = pairs.next().unwrap().into_inner();
    let element_pair = element_pairs.next().unwrap();
    let element = match element_pair.as_rule() {
        Rule::Identifier => Some(StyleElement::new(element_pair.as_str())),
        Rule::Wildcard => None,
        _ => unreachable!(),
    };

    let mut selector = StyleSelector {
        element,
        classes: StyleClasses::new(),
        states: StyleStates::new(),
    };

    for pair in element_pairs {
        match pair.as_rule() {
            Rule::State => {
                selector.states.push(parse_state(pair));
            }
            _ => unreachable!(),
        }
    }

    for pair in pairs {
        match pair.as_rule() {
            Rule::Class => {
                selector.classes.push(parse_class(pair));
            }
            _ => unreachable!(),
        }
    }

    selector
}

fn parse_selectors(pair: Pair<'_, Rule>) -> StyleSelectors {
    let mut selectors = StyleSelectors::new();

    for pair in pair.into_inner() {
        match pair.as_rule() {
            Rule::Selector => {
                selectors.push(parse_selector(pair));
            }
            _ => unreachable!(),
        }
    }

    selectors
}

fn parse_attribute(pair: Pair<'_, Rule>) -> StyleAttribute {
    let mut iter = pair.into_inner();

    let key = iter.next().unwrap().as_str();
    let (value, transition) = parse_value(iter.next().unwrap());

    StyleAttribute {
        key: StyleAttributeKey::new(key),
        value,
        transition,
    }
}

fn parse_style_rule(pair: Pair<'_, Rule>) -> StyleRule {
    let mut iter = pair.into_inner();

    let selector = parse_selectors(iter.next().unwrap());
    let mut rule = StyleRule::new(selector);

    for pair in iter {
        match pair.as_rule() {
            Rule::Attribute => {
                rule.attributes.add(parse_attribute(pair));
            }
            _ => unreachable!(),
        }
    }

    rule
}

fn parse_style(input: &str) -> Result<Stylesheet, Error<Rule>> {
    let pairs = StyleParser::parse(Rule::Style, input)?.next().unwrap();
    let mut style = Stylesheet::new();

    for pair in pairs.into_inner() {
        match pair.as_rule() {
            Rule::StyleRule => {
                style.add_rule(parse_style_rule(pair));
            }
            Rule::EOI => break,
            _ => unreachable!(),
        }
    }

    Ok(style)
}

impl FromStr for Stylesheet {
    type Err = StyleParseError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        parse_style(input)
    }
}

impl FromStr for StyleSelectors {
    type Err = SelectorParseError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        let pair = StyleParser::parse(Rule::Selector, input)?.next().unwrap();
        Ok(parse_selectors(pair))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        parse_style(include_str!("test.css")).unwrap();
    }
}