use std::path::{Path, PathBuf};
use rayon::prelude::*;
use crate::Error;
use crate::config::Config;
use crate::extract::{FileFeatures, extract_file};
use crate::language::Language;
#[derive(Debug, Clone)]
pub struct SourceFile {
pub path: PathBuf,
pub relative: PathBuf,
pub language: Language,
}
pub fn slash_path(path: &Path) -> PathBuf {
if std::path::MAIN_SEPARATOR == '/' {
path.to_path_buf()
} else {
PathBuf::from(
path.to_string_lossy()
.replace(std::path::MAIN_SEPARATOR, "/"),
)
}
}
pub fn discover(root: &Path) -> Result<Vec<SourceFile>, Error> {
if root.is_file() {
return Ok(Language::from_path(root)
.map(|language| SourceFile {
path: root.to_path_buf(),
relative: slash_path(root),
language,
})
.into_iter()
.collect());
}
if !root.is_dir() {
return Err(Error::Io(
root.to_path_buf(),
std::io::Error::new(std::io::ErrorKind::NotFound, "no such file or directory"),
));
}
let mut files = Vec::new();
for entry in ignore::WalkBuilder::new(root).build() {
let entry = entry.map_err(|e| Error::Walk(e.to_string()))?;
if !entry.file_type().is_some_and(|t| t.is_file()) {
continue;
}
let path = entry.into_path();
if let Some(language) = Language::from_path(&path) {
let relative = slash_path(path.strip_prefix(root).unwrap_or(&path));
files.push(SourceFile {
path,
relative,
language,
});
}
}
files.sort_by(|a, b| a.relative.cmp(&b.relative));
Ok(files)
}
pub fn extract_all(files: &[SourceFile], config: &Config) -> Vec<Result<FileFeatures, Error>> {
files
.par_iter()
.map(|f| extract_file(&f.path, &f.relative, f.language, config))
.collect()
}