use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::Context;
use crate::parser::{unwrap_parse_error, ParseCss};
use crate::render::RenderCss;
#[cfg(not(target_arch = "wasm32"))]
use crate::utils::fs;
#[cfg(feature = "iotest")]
use crate::utils::IoTestFs;
use crate::{ast, transformers, utils};
pub struct BuildCss<'a> {
paths: Vec<PathBuf>,
contents: HashMap<&'a Path, String>,
trees: HashMap<&'a Path, ast::Tree<'a>>,
css: HashMap<&'a Path, ast::Css<'a>>,
rootdir: PathBuf,
}
pub struct CompiledCss<'a>(&'a BuildCss<'a>);
impl<'a> BuildCss<'a> {
pub fn new<P: Into<PathBuf>>(rootdir: P) -> Self {
Self {
paths: Default::default(),
contents: Default::default(),
trees: Default::default(),
css: Default::default(),
rootdir: rootdir.into(),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn add_file<P: ?Sized + AsRef<Path>>(&mut self, path: &'a P) {
self.paths.push(path.as_ref().into());
let inpath = self.rootdir.join(path);
let txt = fs::read_to_string(&inpath).unwrap();
self.contents.insert(path.as_ref(), txt);
}
pub fn add_content<P: ?Sized + AsRef<Path>>(&mut self, path: &'a P, scss: String) {
self.paths.push(path.as_ref().into());
self.contents.insert(path.as_ref(), scss);
}
pub fn compile(&'a mut self) -> anyhow::Result<CompiledCss<'a>> {
for (path, contents) in &self.contents {
let tree = ast::Tree::parse(contents);
let (_, tree) = tree.map_err(|err| unwrap_parse_error(contents, err))?;
self.trees.insert(path, tree);
}
for path in self.paths.iter() {
let dep_trees = self.trees.clone();
let tree = self.trees.get_mut(path.as_path()).unwrap();
transformers::apply_import(&dep_trees)(tree);
transformers::apply_mixin(tree);
transformers::apply_var(tree);
self.css.insert(path, tree.flatten_tree());
}
for (path, css) in self.css.iter_mut() {
let srcdir = utils::join_paths(&self.rootdir, path);
transformers::inline_url(&srcdir.to_string_lossy())(css);
transformers::merge_siblings(css);
transformers::remove_mixin(css);
transformers::remove_var(css);
transformers::deduplicate(css);
}
Ok(CompiledCss(self))
}
}
impl<'a> CompiledCss<'a> {
#[cfg(not(target_arch = "wasm32"))]
pub fn write<P: AsRef<Path>>(self, outdir: P) -> anyhow::Result<()> {
for (outfile, css, path) in self.iter_files().flatten() {
let outdir = utils::join_paths(outdir.as_ref(), path);
fs::create_dir_all(outdir.clone()).unwrap_or_default();
fs::write(outdir.join(outfile), css)?;
}
Ok(())
}
pub fn as_strings(&self) -> anyhow::Result<HashMap<String, String>> {
let mut results = HashMap::default();
for (outfile, css, _) in self.iter_files().flatten() {
results.insert(outfile, css).unwrap_or_default();
}
Ok(results)
}
fn iter_files(&self) -> impl Iterator<Item = anyhow::Result<(String, String, &'_ Path)>> {
self.0.css.iter().map(|(path, css)| {
let outpath = PathBuf::from(path);
let outfile = format!(
"{}.css",
outpath
.file_prefix()
.context("No Prefix")?
.to_string_lossy()
);
Ok((outfile, css.as_css_string(), *path))
})
}
}
#[cfg(all(test, feature = "iotest"))]
mod tests {
use std::cell::RefCell;
use std::path::*;
use std::rc::Rc;
use super::*;
#[test]
fn test_simple_build() {
let outputs = Rc::new(RefCell::new(vec![]));
let infiles = Rc::new(RefCell::new(vec![]));
let outfiles = Rc::new(RefCell::new(vec![]));
let ctx = fs::read_to_string_context();
let infiles2 = infiles.clone();
ctx.expect().times(1).returning_st(move |x: &Path| {
infiles2.borrow_mut().push(x.to_string_lossy().to_string());
Ok("div{.open{color:green}}".to_owned())
});
let ctx = fs::create_dir_all_context();
ctx.expect().times(1).returning(|_: PathBuf| Ok(()));
let ctx = fs::write_context();
let outputs2 = outputs.clone();
let outfiles2 = outfiles.clone();
ctx.expect().returning_st(move |x: PathBuf, y: String| {
outfiles2.borrow_mut().push(x.to_string_lossy().to_string());
outputs2.borrow_mut().push(y);
Ok(())
});
let mut build = BuildCss::new("./src".to_owned());
build.add_file("app/component.scss");
build.compile().unwrap().write("./dist").unwrap();
let outputs = outputs.borrow().clone();
assert_eq!(outputs, vec!["div .open{color:green;}".to_owned()]);
let infiles = infiles.borrow().clone();
assert_eq!(infiles, vec!["./src/app/component.scss".to_owned()]);
let outfiles = outfiles.borrow().clone();
assert_eq!(outfiles, vec!["./dist/app/component.css".to_owned()])
}
}