use std::fs;
use std::path::{Path, PathBuf};
use crate::bundle;
use crate::minify;
pub async fn bundle_directory_css(
src_dir: &Path,
output_path: &Path,
) -> Result<(), CssBundlerError> {
let src_dir_canon = src_dir
.canonicalize()
.map_err(|e| CssBundlerError::ReadSource {
path: src_dir.to_path_buf(),
reason: e.to_string(),
})?;
bundle_css_sources(
std::slice::from_ref(&src_dir_canon),
std::slice::from_ref(&src_dir_canon),
output_path,
)
.await
}
pub async fn bundle_css_sources(
allowed_roots: &[PathBuf],
source_dirs: &[PathBuf],
output_path: &Path,
) -> Result<(), CssBundlerError> {
let mut css_files = Vec::new();
for src_dir in source_dirs {
let files = find_css_files(src_dir).map_err(|e| CssBundlerError::ReadSource {
path: src_dir.to_path_buf(),
reason: e.to_string(),
})?;
css_files.extend(files);
}
if css_files.is_empty() {
let first = source_dirs
.first()
.cloned()
.unwrap_or_else(|| PathBuf::from("."));
return Err(CssBundlerError::NoFilesFound(first));
}
let mut bundled_content = String::new();
for css_file in &css_files {
let (bytes, _deps) = bundle::bundle_and_minify_css(allowed_roots, css_file)
.await
.map_err(|e| CssBundlerError::Bundle(format!("{:?}", e)))?;
bundled_content.push_str(&String::from_utf8_lossy(&bytes));
}
let bundled_bytes_final =
minify::minify(bundled_content.as_bytes(), crate::reload::ChangeType::Css)
.map_err(|e| CssBundlerError::Bundle(format!("Minification failed: {:?}", e)))?;
if let Some(parent) = output_path.parent() {
fs::create_dir_all(parent).map_err(|e| CssBundlerError::WriteOutput {
path: output_path.to_path_buf(),
reason: e.to_string(),
})?;
}
fs::write(output_path, &bundled_bytes_final).map_err(|e| CssBundlerError::WriteOutput {
path: output_path.to_path_buf(),
reason: e.to_string(),
})?;
Ok(())
}
fn find_css_files(dir: &Path) -> std::io::Result<Vec<PathBuf>> {
let mut css_files = Vec::new();
let mut dirs = vec![dir.to_path_buf()];
while let Some(current_dir) = dirs.pop() {
let entries = fs::read_dir(¤t_dir)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
let file_type = entry.file_type()?;
if file_type.is_dir() {
dirs.push(path);
} else if file_type.is_file() && path.extension().and_then(|s| s.to_str()) == Some("css")
{
css_files.push(path);
}
}
}
css_files.sort();
Ok(css_files)
}
#[derive(Debug)]
pub enum CssBundlerError {
ReadSource { path: PathBuf, reason: String },
WriteOutput { path: PathBuf, reason: String },
NoFilesFound(PathBuf),
Bundle(String),
}
impl std::fmt::Display for CssBundlerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CssBundlerError::ReadSource { path, reason } => {
write!(
f,
"failed to read source dir {}: {}",
path.display(),
reason
)
}
CssBundlerError::WriteOutput { path, reason } => {
write!(
f,
"failed to write output file {}: {}",
path.display(),
reason
)
}
CssBundlerError::NoFilesFound(path) => {
write!(f, "no CSS files found in {}", path.display())
}
CssBundlerError::Bundle(msg) => {
write!(f, "CSS bundling failed: {}", msg)
}
}
}
}
impl std::error::Error for CssBundlerError {}