mod ast;
mod builtins;
mod emit;
mod error;
mod eval;
mod parser;
mod scanner;
mod value;
pub use error::Error;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputStyle {
#[default]
Expanded,
Compressed,
}
pub trait Importer {
fn resolve(&self, path: &str) -> Option<String>;
}
#[derive(Default)]
pub struct Options<'a> {
pub style: OutputStyle,
pub importer: Option<&'a dyn Importer>,
}
impl<'a> Options<'a> {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_style(mut self, style: OutputStyle) -> Self {
self.style = style;
self
}
#[must_use]
pub fn with_importer(mut self, importer: &'a dyn Importer) -> Self {
self.importer = Some(importer);
self
}
}
pub fn compile(source: &str, options: &Options<'_>) -> Result<String, Error> {
let sheet = parser::parse(source)?;
let mut ev = eval::Evaluator::new(eval::EvalOptions {
style: options.style,
importer: options.importer,
});
let mut out = Vec::new();
ev.eval_sheet(&sheet, &mut out)?;
Ok(emit::emit(&out, options.style))
}
pub struct FsImporter {
load_paths: Vec<PathBuf>,
}
impl FsImporter {
pub fn new(load_paths: Vec<PathBuf>) -> Self {
FsImporter { load_paths }
}
}
impl Importer for FsImporter {
fn resolve(&self, path: &str) -> Option<String> {
for base in &self.load_paths {
for cand in candidate_paths(base, path) {
if let Ok(src) = std::fs::read_to_string(&cand) {
return Some(src);
}
}
}
None
}
}
fn candidate_paths(base: &Path, path: &str) -> Vec<PathBuf> {
let p = Path::new(path);
let stem = p.file_name().and_then(|s| s.to_str()).unwrap_or(path);
let dir = match p.parent() {
Some(par) if !par.as_os_str().is_empty() => base.join(par),
_ => base.to_path_buf(),
};
let mut out = Vec::new();
if path.ends_with(".scss") {
out.push(base.join(path));
out.push(dir.join(format!("_{stem}")));
} else {
out.push(dir.join(format!("{stem}.scss")));
out.push(dir.join(format!("_{stem}.scss")));
out.push(base.join(path).join("_index.scss"));
out.push(base.join(path).join("index.scss"));
}
out
}