pub mod ast;
pub mod parser;
use crate::ast::Module;
use crate::parser::ParserResult;
use glob::glob;
use quote::quote;
use std::collections::HashMap;
use std::env;
use std::fmt;
use std::fs::File;
use std::io::Write;
use std::ops::Index;
use std::path::{Path, PathBuf};
use std::str::FromStr;
pub use css_modules_macros::*;
#[derive(Debug)]
pub struct CssModules {
ast: ast::Stylesheet,
}
impl Default for CssModules {
fn default() -> Self {
Self {
ast: ast::Stylesheet::default(),
}
}
}
impl fmt::Display for CssModules {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
for (_, module) in &self.ast.modules {
for child in &module.children {
write!(formatter, "{}", child)?
}
}
Ok(())
}
}
impl CssModules {
pub fn modules(&self) -> Vec<&Module> {
self.ast
.modules
.iter()
.map(|(_, module)| module)
.collect::<Vec<&Module>>()
}
pub fn add_module<'m>(&mut self, path: &str) -> ParserResult<'m, &Module> {
let path = PathBuf::from_str(path).unwrap().canonicalize().unwrap();
self.ast.add_module(path)
}
pub fn add_modules<'m>(&mut self, pattern: &str) -> ParserResult<'m, ()> {
for entry in glob(pattern).expect("Failed to read glob pattern") {
self.ast.add_module(entry.unwrap())?;
}
Ok(())
}
pub fn has_module(self, path: &str) -> bool {
let path = PathBuf::from_str(path).unwrap().canonicalize().unwrap();
self.ast.has_module(path)
}
pub fn remove_module(self, path: &str) {
let path = PathBuf::from_str(path).unwrap().canonicalize().unwrap();
self.ast.remove_module(path)
}
pub fn compile(&self, stylesheet_name: &str) {
fn write(filename: &PathBuf, content: String) {
use std::fs::create_dir_all;
let dirname = filename.parent().unwrap();
create_dir_all(&dirname).expect("could not create directory.");
let mut file = File::create(&filename).expect("could not create file.");
file.write_all(content.as_bytes())
.expect("could not write to file.");
}
fn is_fresh(input: &PathBuf, output: &PathBuf) -> bool {
if !output.is_file() {
false
} else {
let input = input.metadata().unwrap();
let output = output.metadata().unwrap();
output.modified().unwrap() >= input.modified().unwrap()
}
}
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let manifest_dir = Path::new(&manifest_dir);
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let mut stylesheet = String::new();
let mut re_render = false;
for module in &self.modules() {
if !is_fresh(&module.input_path, &module.output_path) {
let module_name = module.input_path.file_name().unwrap().to_str().unwrap();
let mut identifiers = Vec::new();
for (_old, _new) in &module.identifiers {
identifiers.push(quote! {(#_old, #_new)});
}
let output = quote! {
CssModuleBuilder::new()
.with_identifiers(vec![#(#identifiers),*])
.with_module_name(#module_name)
.with_stylesheet_name(#stylesheet_name)
.finish()
};
write(&out_dir.join(&module.output_path), output.to_string());
re_render = true;
}
for child in &module.children {
stylesheet.push_str(&format!("{}", child));
}
}
if re_render {
let stylesheet_path = manifest_dir.join(&stylesheet_name);
write(&stylesheet_path, stylesheet);
}
}
}
#[derive(Debug, PartialEq)]
pub struct CssModuleBuilder<'b> {
identifiers: Option<HashMap<&'b str, &'b str>>,
module_name: Option<&'b str>,
stylesheet_name: Option<&'b str>,
}
impl<'b> CssModuleBuilder<'b> {
pub fn new() -> Self {
Self {
identifiers: None,
module_name: None,
stylesheet_name: None,
}
}
pub fn with_identifiers(self, identifiers: Vec<(&'b str, &'b str)>) -> Self {
Self {
identifiers: Some(identifiers.into_iter().collect()),
..self
}
}
pub fn with_module_name(self, module_name: &'b str) -> Self {
Self {
module_name: Some(module_name),
..self
}
}
pub fn with_stylesheet_name(self, stylesheet_name: &'b str) -> Self {
Self {
stylesheet_name: Some(stylesheet_name),
..self
}
}
pub fn finish(self) -> CssModule<'b> {
if let (Some(identifiers), Some(module_name), Some(stylesheet_name)) =
(self.identifiers, self.module_name, self.stylesheet_name)
{
CssModule {
identifiers,
module_name,
stylesheet_name,
}
} else {
panic!("Unable to initialize CssModule, not all parameters are set.");
}
}
}
#[derive(Debug, PartialEq)]
pub struct CssModule<'m> {
pub identifiers: HashMap<&'m str, &'m str>,
pub module_name: &'m str,
pub stylesheet_name: &'m str,
}
impl<'m> Index<&'m str> for CssModule<'m> {
type Output = &'m str;
fn index<'b>(&self, identifier: &'b str) -> &&'m str {
match self.identifiers.get(identifier) {
Some(new_identifier) => &new_identifier,
None => panic!(
"Class `{}` was not found in {}",
identifier, self.module_name
),
}
}
}