extern crate nom;
use nom::{
number::complete::float,
error::{
convert_error,
VerboseError,
ParseError,
},
InputTakeAtPosition,
character::complete::multispace0,
AsChar,
sequence::{
delimited,
tuple,
separated_pair,
preceded,
},
multi::many0,
branch::alt,
character::{
is_alphanumeric,
complete::char,
},
bytes::complete::{
take_while,
take_while_m_n,
tag,
},
combinator::map_res,
IResult,
Err,
};
use crossbeam_channel::{
unbounded,
TryRecvError,
};
use notify::{
RecursiveMode,
RecommendedWatcher,
Watcher,
EventKind,
event::{
ModifyKind,
},
};
use std::collections::BTreeMap;
pub fn try_parse_styles(style: &str) -> Option<Vec<Rule>> {
match rules::<VerboseError<&str>>(style) {
Ok((_, s)) => Some(s),
Err(Err::Error(e)) | Err(Err::Failure(e)) => {
log::info!("Failed to load stylesheet.");
log::info!("Trace: {}", convert_error(style, e));
None
},
Err(Err::Incomplete(_)) => {
log::info!("Unexpected EOF loading the stylesheet.");
None
}
}
}
pub struct RulesCache {
pub rules: Vec<Rule>,
rx: crossbeam_channel::Receiver<std::result::Result<notify::event::Event, notify::Error>>,
_watcher: RecommendedWatcher,
}
impl RulesCache {
pub fn try_load_from_file(filename: impl Into<String>) -> Option<Self> {
let filename = filename.into();
let contents = std::fs::read_to_string(std::path::Path::new(&filename.clone()))
.expect("Something went wrong reading the file");
let (tx, rx) = unbounded();
let mut watcher: RecommendedWatcher = match Watcher::new_immediate(tx) {
Ok(watcher) => watcher,
Err(err) => {
log::info!("Failed to create a watcher for the stylesheet:");
log::info!("{}", err);
return None;
},
};
match watcher.watch(&filename, RecursiveMode::Recursive) {
Ok(_) => {},
Err(err) => {
log::info!("Failed to start watching {}:", filename);
log::info!("{}", err);
return None;
},
};
let rules = try_parse_styles(&contents)?;
Some(Self {
rules: rules,
rx,
_watcher: watcher,
})
}
pub fn get_matching_rules(&self, selector: &Selector) -> Vec<&Rule> {
self.rules.iter().filter(|rule| selector.matches(&rule.selector)).collect()
}
pub fn get_matching_rules_mut(&mut self, selector: &Selector) -> Vec<&mut Rule> {
self.rules.iter_mut().filter(|rule| selector.matches(&rule.selector)).collect()
}
pub fn add_rule(&mut self, rule: Rule) {
self.rules.push(rule);
}
pub fn try_get_rule_mut(&mut self, selector: Selector) -> Option<&mut Rule> {
self.rules.iter_mut().find(|rule| selector == rule.selector)
}
pub fn update(&mut self) -> bool {
match self.rx.try_recv() {
Ok(Ok(notify::event::Event {
kind: EventKind::Modify(ModifyKind::Data(_)),
paths,
..
})) => {
self.try_reload_from_file(&paths[0].as_path())
},
Ok(Ok(_)) => { false },
Ok(Err(err)) => {
log::info!("Something went wrong with the CSS file watcher:\r\n{:?}", err);
false
},
Err(TryRecvError::Empty) => false,
Err(err) => {
log::info!("Something went wrong with the CSS file watcher:\r\n{:?}", err);
false
},
}
}
fn try_reload_from_file(&mut self, filename: &std::path::Path) -> bool {
match std::fs::read_to_string(filename) {
Ok(contents) => {
self.rules = match try_parse_styles(&contents) {
Some(rules) => rules,
None => return false,
}
},
Err(err) => {
log::info!("Failed to read file at {:?}:", filename);
log::info!("{}", err);
return false;
},
}
true
}
}
#[derive(Debug)]
pub struct Rule {
pub selector: Selector,
pub kvs: BTreeMap<String, CSSValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, MallocSizeOf)]
pub struct Selector {
pub typ: Option<String>,
pub id: Option<String>,
pub classes: Vec<String>,
pub any: BTreeMap<String, String>,
}
impl Default for Selector {
fn default() -> Self {
Self {
typ: None,
id: None,
classes: vec![],
any: BTreeMap::new(),
}
}
}
impl std::fmt::Display for Selector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut selector = self.typ.clone().unwrap_or(String::new());
self.id.as_ref().map(|id| selector += &id);
for class in &self.classes {
selector += ".";
selector += &class;
}
for (k, v) in &self.any {
selector += "[";
selector += &k;
selector += "=";
selector += &v;
selector += "]";
}
write!(f, "({})", selector)
}
}
impl Selector {
pub fn new() -> Self {
Self {
typ: None,
id: None,
classes: vec![],
any: BTreeMap::new(),
}
}
pub fn with_type(mut self, typ: impl Into<String>) -> Self {
self.typ = Some(typ.into());
self
}
pub fn _with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn _with_class(mut self, class: impl Into<String>) -> Self {
self.classes.push(class.into());
self
}
pub fn with_any(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.any.insert(key.into(), value.into());
self
}
pub fn matches(&self, other: &Selector) -> bool {
if let Some(t1) = &other.typ {
if let Some(t2) = &self.typ {
if t1 != t2 { return false; }
} else {
return false;
}
}
if let Some(i1) = &other.id {
if let Some(i2) = &self.id {
if i1 != i2 { return false; }
} else {
return false;
}
}
for (k, v) in &other.any {
if let Some(value) = self.any.get(k) {
if value != v {
return false;
}
} else {
return false;
}
}
for c in &other.classes {
if !self.classes.contains(c) {
return false;
}
}
true
}
pub fn size(&self) -> usize {
use parity_util_mem::MallocSizeOfExt;
std::mem::size_of::<Selector>() + self.malloc_size_of()
}
}
#[derive(Debug)]
enum SelectorPart {
Class(String),
Id(String),
Any(String, String),
}
fn rules<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Vec<Rule>, E> {
many0(rule)(input)
}
fn whitespace<I, O, E, F>(f: F) -> impl Fn(I) -> IResult<I, O, E>
where
I: Clone + PartialEq + InputTakeAtPosition,
<I as InputTakeAtPosition>::Item: AsChar + Clone,
F: Fn(I) -> IResult<I, O, E>,
E: ParseError<I>,
{
delimited(multispace0, f, multispace0)
}
fn rule<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Rule, E> {
let (remaining, (selector, _, kvs, _)) = tuple((
whitespace(selector),
whitespace(char('{')),
body,
whitespace(char('}'))
))(input)?;
Ok((remaining, Rule { selector, kvs }))
}
fn selector<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Selector, E> {
let mut selector: Selector = Default::default();
let (remaining, typ) = take_while(|c| is_alphanumeric(c as u8))(input)?;
selector.typ = if typ.len() > 0 { Some(typ.into()) } else { None };
let (remaining, pairs) = many0(alt((class, id, any)))(remaining)?;
for pair in pairs {
match pair {
SelectorPart::Class(v) => selector.classes.push(v),
SelectorPart::Id(v) => selector.id = Some(v),
SelectorPart::Any(k, v) => { selector.any.insert(k, v); },
}
}
Ok((remaining, selector))
}
fn class<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, SelectorPart, E> {
preceded(char('.'), take_while(|c| is_alphanumeric(c as u8)))(input).map(|(r, v)| (r, SelectorPart::Class(v.into())))
}
fn id<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, SelectorPart, E> {
preceded(char('#'), take_while(|c| is_alphanumeric(c as u8)))(input).map(|(r, v)| (r, SelectorPart::Id(v.into())))
}
fn any<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, SelectorPart, E> {
let (remaining, _) = char('[')(input)?;
let (remaining, name) = take_while(|c| is_alphanumeric(c as u8))(remaining)?;
let (remaining, _) = char('=')(remaining)?;
let (remaining, value) = take_while(|c| is_alphanumeric(c as u8))(remaining)?;
let (remaining, _) = char(']')(remaining)?;
Ok((remaining, SelectorPart::Any(name.into(), value.into())))
}
fn body<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, std::collections::BTreeMap::<String, CSSValue>, E> {
let mut hm = std::collections::BTreeMap::new();
many0(kv)(input).map(|v| { v.1.into_iter().for_each(|v| { hm.insert(v.0.into(), v.1); }); (v.0, hm) })
}
fn kv<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, (&'a str, CSSValue), E> {
let (remaining, (kv, _)) = tuple((
separated_pair(css_name, char(':'), css_value),
char(';')
))(input)?;
Ok((remaining, kv))
}
fn css_name<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
whitespace(take_while(|c| is_alphanumeric(c as u8) || c == '-'))(input)
}
fn css_value<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
alt((
whitespace(hex_color),
whitespace(rgba_color),
whitespace(rgb_color),
whitespace(px_value),
whitespace(world_value),
whitespace(unitless_value),
whitespace(string),
))(input)
}
#[derive(Debug, Copy, Clone)]
pub enum Number {
Px(f32),
Unitless(f32),
World(f32),
}
#[derive(Debug, Clone)]
pub enum CSSValue {
String(String),
Color(Color),
Number(Number),
}
fn string<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
let (input, value) = whitespace(take_while(|c| is_alphanumeric(c as u8) || c == '-' || c == ' '))(input)?;
Ok((input, CSSValue::String(value.into())))
}
fn px_value<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
let (input, (value, _)) = tuple((float, tag("px")))(input)?;
Ok((input, CSSValue::Number(Number::Px(value))))
}
fn world_value<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
let (input, (value, _)) = tuple((float, tag("w")))(input)?;
Ok((input, CSSValue::Number(Number::World(value))))
}
fn unitless_value<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
let (input, value) = float(input)?;
Ok((input, CSSValue::Number(Number::Unitless(value))))
}
#[derive(Debug,PartialEq, Clone)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const TRANSPARENT: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0, };
pub const WHITE: Color = Color { r: 1.0, g: 1.0, b: 1.0, a: 1.0, };
pub const BLACK: Color = Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0, };
pub const RED: Color = Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0, };
pub const GREEN: Color = Color { r: 0.0, g: 1.0, b: 0.0, a: 1.0, };
pub const BLUE: Color = Color { r: 0.0, g: 0.0, b: 1.0, a: 1.0, };
}
fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
u8::from_str_radix(input, 16)
}
fn is_hex_digit(c: char) -> bool {
c.is_digit(16)
}
fn hex_primary<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, u8, E> {
map_res(
take_while_m_n(2, 2, is_hex_digit),
from_hex
)(input)
}
fn hex_color<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
let (input, _) = tag("#")(input)?;
let (input, (r, g, b)) = tuple((hex_primary, hex_primary, hex_primary))(input)?;
Ok((input, CSSValue::Color(Color { r: r as f32 / 255.0, g: g as f32 / 255.0, b: b as f32 / 255.0, a: 1.0 })))
}
fn u8<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, u8, E> {
use std::str::FromStr;
map_res(
take_while(|c: char| c.is_digit(10)),
u8::from_str
)(input)
}
fn rgba_color<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
let (input, _) = whitespace(tag("rgba("))(input)?;
let (input, (r, _, g, _, b, _, a)) = tuple((
u8,
whitespace(char(',')),
u8,
whitespace(char(',')),
u8,
whitespace(char(',')),
float,
))(input)?;
let (input, _) = tag(")")(input)?;
Ok((input, CSSValue::Color(Color {r: r as f32 / 255.0, g: g as f32 / 255.0, b: b as f32 / 255.0, a })))
}
fn rgb_color<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, CSSValue, E> {
let (input, _) = whitespace(tag("rgb("))(input)?;
let (input, (r, _, g, _, b)) = tuple((
u8,
whitespace(char(',')),
u8,
whitespace(char(',')),
u8,
))(input)?;
let (input, _) = tag(")")(input)?;
Ok((input, CSSValue::Color(Color { r: r as f32 / 255.0, g: g as f32 / 255.0, b: b as f32 / 255.0, a: 1.0 })))
}
#[test]
fn selector_size() {
let selector = super::Selector::default();
assert_eq!(selector.size(), 42);
}