extern crate backtrace;
extern crate lazy_static;
extern crate regex;
use std::collections::HashMap;
pub struct CssModuleBuilder(CssModule);
impl Default for CssModuleBuilder {
fn default() -> Self {
CssModuleBuilder(CssModule::default())
}
}
impl CssModuleBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn finish(self) -> CssModule {
use lazy_static::lazy_static;
use regex::{Captures, Regex};
lazy_static! {
static ref CLASSES: Regex = Regex::new(r"\.[\w\d\-_]+").unwrap();
}
let name = self.0.name;
let stylesheet = self.0.stylesheet;
let mut classes = self.0.classes;
let stylesheet = CLASSES
.replace_all(&stylesheet, |caps: &Captures| {
let original = &caps[0][1..].to_owned();
let original = original.to_string();
let replacement = format!("{}_{}_{}", &name, &original, classes.len());
let result = format!(".{}", replacement);
classes.entry(original).or_insert(replacement);
result
})
.to_string();
CssModule {
name,
stylesheet,
classes,
}
}
pub fn with_name(self, name: &str) -> Self {
CssModuleBuilder(CssModule {
name: name.to_owned(),
..self.0
})
}
pub fn with_stylesheet(self, stylesheet: &str) -> Self {
let stylesheet = stylesheet.to_string();
CssModuleBuilder(CssModule {
stylesheet,
..self.0
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CssModule {
pub name: String,
pub stylesheet: String,
pub classes: HashMap<String, String>,
}
impl Default for CssModule {
fn default() -> Self {
CssModule {
name: "unknown_module".to_string(),
stylesheet: String::new(),
classes: HashMap::new(),
}
}
}
impl CssModule {
pub fn new() -> CssModuleBuilder {
CssModuleBuilder::new()
}
pub fn contains(&self, class: &str) -> bool {
self.classes.contains_key(&class.to_owned())
}
pub fn get(&self, class: &str) -> String {
if let Some(class) = self.classes.get(class) {
format!(".{}", class)
} else {
format!(".{}", class)
}
}
}
#[macro_export]
macro_rules! css_module_name {
() => {{
use backtrace::{Backtrace, BacktraceFrame, BacktraceSymbol};
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref NAME_FORMATTER: Regex = Regex::new(r"[^\w]+").unwrap();
}
fn previous_symbol(level: u32) -> Option<BacktraceSymbol> {
let (trace, curr_file, curr_line) = (Backtrace::new(), file!(), line!());
let frames = trace.frames();
frames
.iter()
.flat_map(BacktraceFrame::symbols)
.skip_while(|s| {
s.filename()
.map(|p| !p.ends_with(curr_file))
.unwrap_or(true)
|| s.lineno() != Some(curr_line)
})
.nth(1 + level as usize)
.cloned()
}
let sym = previous_symbol(0);
let name = sym
.as_ref()
.and_then(BacktraceSymbol::name)
.expect("A valid backtrace.");
NAME_FORMATTER
.replace_all(&format!("{:?}", name), "_")
.to_string()
}};
}
#[macro_export]
macro_rules! include_css_module {
($file:expr) => {{
CssModule::new()
.with_name(&css_module_name!())
.with_stylesheet(include_str!($file))
.finish()
}};
($name:expr, $file:expr) => {{
CssModule::new()
.with_name($name)
.with_stylesheet(include_str!($file))
.finish()
}};
}