use anyhow::Result;
use std::path::Path;
#[allow(dead_code)]
pub trait ProjectDetector {
fn has_entry_files(&self, project_path: &str) -> bool {
let path = Path::new(project_path);
if !path.exists() || !path.is_dir() {
return false;
}
for entry_file in self.get_entry_files() {
let entry_path = path.join(entry_file);
if entry_path.exists() {
return true;
}
}
false
}
fn get_entry_files(&self) -> &[&str];
fn has_supported_extension(&self, file_path: &str) -> bool {
let path = Path::new(file_path);
if let Some(extension) = path.extension() {
if let Some(ext_str) = extension.to_str() {
return self.get_supported_extensions().contains(&ext_str);
}
}
false
}
fn get_supported_extensions(&self) -> &[&str];
}
pub trait ProjectBundler {
fn read_project_files(
&self,
project_path: &str,
) -> Result<std::collections::HashMap<String, Vec<u8>>> {
use std::collections::HashMap;
let mut files = HashMap::new();
let path = Path::new(project_path);
if !path.exists() {
return Err(anyhow::anyhow!(
"Project path does not exist: {project_path}"
));
}
self.read_directory_recursive(path, &mut files, "")?;
Ok(files)
}
fn read_directory_recursive(
&self,
dir: &Path,
files: &mut std::collections::HashMap<String, Vec<u8>>,
prefix: &str,
) -> Result<()> {
use std::fs;
if !dir.is_dir() {
return Ok(());
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let file_name = match path.file_name() {
Some(name) => name.to_string_lossy(),
None => continue,
};
let relative_path = if prefix.is_empty() {
file_name.to_string()
} else {
format!("{prefix}/{file_name}")
};
if file_name.starts_with('.') {
continue;
}
if self.should_skip_directory(&file_name) {
continue;
}
if path.is_file() {
if self.should_include_file(&relative_path) {
let content = fs::read(&path)?;
files.insert(relative_path, content);
}
} else if path.is_dir() {
self.read_directory_recursive(&path, files, &relative_path)?;
}
}
Ok(())
}
fn should_skip_directory(&self, dir_name: &str) -> bool {
matches!(
dir_name,
"node_modules"
| "target"
| "build"
| "dist"
| "__pycache__"
| ".git"
| ".vscode"
| ".idea"
| "vendor"
| "bin"
| "obj"
)
}
fn should_include_file(&self, file_path: &str) -> bool;
fn extract_dependencies(&self, project_path: &str) -> Result<Vec<String>>;
}
pub struct DefaultProjectOps;
impl ProjectDetector for DefaultProjectOps {
fn get_entry_files(&self) -> &[&str] {
&[]
}
fn get_supported_extensions(&self) -> &[&str] {
&[]
}
}
impl ProjectBundler for DefaultProjectOps {
fn should_include_file(&self, _file_path: &str) -> bool {
true
}
fn extract_dependencies(&self, _project_path: &str) -> Result<Vec<String>> {
Ok(vec![])
}
}