use crate::grammar::*;
use pest::iterators::Pair;
use std::collections::HashMap;
use std::fmt;
type Identifiers = HashMap<String, String>;
type Nodes = Vec<Node>;
#[derive(Debug, PartialEq)]
pub enum Node {
AtRule {
name: Option<String>,
rule: Option<String>,
children: Nodes,
},
Comment {
value: Option<String>,
},
Property {
name: Option<String>,
value: Option<String>,
},
SelectRule {
rule: Option<String>,
children: Nodes,
},
}
impl fmt::Display for Node {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match self {
Node::AtRule {
name,
rule,
children,
} => {
if let (Some(name), Some(rule)) = (name, rule) {
if children.is_empty() {
write!(formatter, "@{} {};\n", name, rule.trim())
} else {
let mut result = String::new();
for child in children {
result.push_str(&format!("{}", child));
}
write!(formatter, "@{} {} {{ {} }}\n", name, rule.trim(), result)
}
} else if let Some(name) = name {
if children.is_empty() {
write!(formatter, "@{};", name)
} else {
let mut result = String::new();
for child in children {
result.push_str(&format!("{}", child));
}
write!(formatter, "@{} {{ {} }}\n", name, result)
}
} else {
write!(formatter, "")
}
}
Node::SelectRule { rule, children } => {
if children.is_empty() {
write!(formatter, "")
} else if let Some(rule) = rule {
let mut result = String::new();
for child in children {
result.push_str(&format!("{}", child));
}
write!(formatter, "{} {{\n{}}}\n", rule, result)
} else {
write!(formatter, "")
}
}
Node::Property { name, value } => {
if let (Some(name), Some(value)) = (name, value) {
write!(formatter, "\t{}: {};\n", name, value)
} else if let Some(name) = name {
write!(formatter, "\t{}:;\n", name)
} else {
write!(formatter, "")
}
}
Node::Comment { value } => {
if let Some(value) = value {
write!(formatter, "{}", value)
} else {
write!(formatter, "")
}
}
}
}
}
#[derive(Debug, PartialEq)]
pub struct Stylesheet {
pub name: String,
pub children: Nodes,
pub identifiers: Identifiers,
}
impl<'t> Stylesheet {
pub fn new(name: &'t str, stylesheet: &'t str) -> ParserResult<'t, Self> {
let pairs = parse_stylesheet(stylesheet)?;
let mut stylesheet = Self {
name: name.to_string(),
children: Vec::new(),
identifiers: HashMap::new(),
};
for pair in pairs {
let child = match pair.as_rule() {
Rule::comment => Some(create_comment(pair)),
Rule::atrule => Some(create_atrule(&mut stylesheet, pair)?),
Rule::selectrule => Some(create_selectrule(&mut stylesheet, pair)?),
Rule::EOI => None,
_ => return Err(Error::from(pair)),
};
if let Some(child) = child {
stylesheet.children.push(child);
}
}
Ok(stylesheet)
}
pub fn id(&self, class: &str) -> Result<String, String> {
if let Some(class) = self.identifiers.get(class) {
Ok(class.to_owned())
} else {
Err(class.to_owned())
}
}
}
impl fmt::Display for Stylesheet {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let mut result = String::new();
for child in &self.children {
result.push_str(&format!("{}", child));
}
write!(formatter, "{}", result)
}
}
fn create_atrule<'t>(
mut stylesheet: &mut Stylesheet,
pair: Pair<'t, Rule>,
) -> ParserResult<'t, Node> {
let mut name: Option<String> = None;
let mut rule: Option<String> = None;
let mut children = Vec::new();
for pair in pair.into_inner() {
let child = match pair.as_rule() {
Rule::identifier => {
name = Some(pair.as_str().to_string());
None
}
Rule::atrule_rule => {
if Some("keyframes".to_string()) == name {
let animation = pair.as_str().trim().to_string();
let length = stylesheet.identifiers.len();
let replacement = stylesheet
.identifiers
.entry(animation.clone())
.or_insert(format!("{}_{}_{}", &stylesheet.name, &animation, length));
rule = Some(format!("{} ", replacement));
} else {
rule = Some(pair.as_str().to_string());
}
None
}
Rule::comment | Rule::line_comment => Some(create_comment(pair)),
Rule::property => Some(create_property(&mut stylesheet, pair)?),
Rule::atrule => Some(create_atrule(&mut stylesheet, pair)?),
Rule::selectrule => Some(create_selectrule(&mut stylesheet, pair)?),
_ => return Err(Error::from(pair)),
};
if let Some(child) = child {
children.push(child);
}
}
Ok(Node::AtRule {
name,
rule,
children,
})
}
fn create_comment<'t>(pair: Pair<'t, Rule>) -> Node {
Node::Comment {
value: Some(pair.as_str().to_string()),
}
}
fn create_property<'t>(
mut stylesheet: &mut Stylesheet,
pair: Pair<'t, Rule>,
) -> ParserResult<'t, Node> {
let mut name: Option<String> = None;
let mut value: Option<String> = None;
for pair in pair.into_inner() {
match pair.as_rule() {
Rule::identifier => {
name = Some(pair.as_str().to_string());
}
Rule::property_value => {
if Some("animation".to_string()) == name
|| Some("animation-name".to_string()) == name
{
value = replace_animations_in_property(&mut stylesheet, pair.as_str())?;
} else {
value = Some(pair.as_str().trim().to_string());
}
}
_ => return Err(Error::from(pair)),
}
}
Ok(Node::Property { name, value })
}
fn replace_animations_in_property<'t>(
stylesheet: &mut Stylesheet,
input: &'t str,
) -> ParserResult<'t, Option<String>> {
let mut result = String::new();
let pairs = parse_animation(input)?;
for pair in pairs {
match pair.as_rule() {
Rule::identifier => {
let animation = pair.as_str().trim().to_string();
let length = stylesheet.identifiers.len();
let replacement = stylesheet
.identifiers
.entry(animation.clone())
.or_insert(format!("{}_{}_{}", &stylesheet.name, &animation, length));
result.push_str(&format!("{}", replacement));
}
_ => {
result.push_str(pair.as_str());
}
}
}
result = result.trim().to_string();
if result.is_empty() {
Ok(None)
} else {
Ok(Some(result))
}
}
fn create_selectrule<'t>(
mut stylesheet: &mut Stylesheet,
pair: Pair<'t, Rule>,
) -> ParserResult<'t, Node> {
let mut rule: Option<String> = None;
let mut children = Vec::new();
for pair in pair.into_inner() {
let child = match pair.as_rule() {
Rule::selectrule_rule => {
rule = replace_classes_in_rule(&mut stylesheet, pair.as_str())?;
None
}
Rule::comment | Rule::line_comment => Some(create_comment(pair)),
Rule::property => Some(create_property(&mut stylesheet, pair)?),
Rule::atrule => Some(create_atrule(&mut stylesheet, pair)?),
Rule::selectrule => Some(create_selectrule(&mut stylesheet, pair)?),
_ => return Err(Error::from(pair)),
};
if let Some(child) = child {
children.push(child);
}
}
Ok(Node::SelectRule { rule, children })
}
fn replace_classes_in_rule<'t>(
stylesheet: &mut Stylesheet,
input: &'t str,
) -> ParserResult<'t, Option<String>> {
let mut result = String::new();
let pairs = parse_selector(input)?;
for pair in pairs {
match pair.as_rule() {
Rule::selector_class => {
let class = pair.as_str()[1..].trim().to_string();
let length = stylesheet.identifiers.len();
let replacement = stylesheet
.identifiers
.entry(class.clone())
.or_insert(format!("{}_{}_{}", &stylesheet.name, &class, length));
result.push_str(&format!(".{}", replacement));
}
_ => {
result.push_str(pair.as_str());
}
}
}
result = result.trim().to_string();
if result.is_empty() {
Ok(None)
} else {
Ok(Some(result))
}
}