use std::collections::HashMap;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
#[derive(Debug, Clone)]
pub struct DiscoveredFile {
pub rel_path: String,
pub abs_path: PathBuf,
}
#[derive(Debug, Clone, Default)]
pub struct WalkResult {
pub concepts: Vec<DiscoveredFile>,
pub index_files: HashMap<String, PathBuf>,
}
const SKIPPED: &[&str] = &["log.md"];
fn is_ignored_dir(name: &str) -> bool {
name.starts_with('.')
|| matches!(
name,
"node_modules" | "target" | "__pycache__" | "venv" | "env" | "site-packages"
)
}
pub fn discover(root: &Path) -> Result<WalkResult, String> {
if !root.exists() {
return Err(format!(
"OKF bundle path does not exist: {}",
root.display()
));
}
if !root.is_dir() {
return Err(format!(
"OKF bundle path is not a directory: {}",
root.display()
));
}
let mut out = Vec::new();
let mut index_files: HashMap<String, PathBuf> = HashMap::new();
let walker = WalkDir::new(root).into_iter().filter_entry(|e| {
if e.depth() == 0 || !e.file_type().is_dir() {
return true;
}
e.file_name()
.to_str()
.map(|n| !is_ignored_dir(n))
.unwrap_or(true)
});
for entry in walker.filter_map(Result::ok) {
if !entry.file_type().is_file() {
continue;
}
let name = match entry.file_name().to_str() {
Some(n) => n,
None => continue,
};
if !name.ends_with(".md") || SKIPPED.contains(&name) {
continue;
}
let rel = match entry.path().strip_prefix(root) {
Ok(r) => r,
Err(_) => continue,
};
let rel_path = rel
.components()
.filter_map(|c| c.as_os_str().to_str())
.collect::<Vec<_>>()
.join("/");
if name == "index.md" {
let dir = rel_path
.rfind('/')
.map(|i| rel_path[..i].to_string())
.unwrap_or_default();
index_files.insert(dir, entry.path().to_path_buf());
continue;
}
out.push(DiscoveredFile {
rel_path,
abs_path: entry.path().to_path_buf(),
});
}
out.sort_by(|a, b| a.rel_path.cmp(&b.rel_path));
Ok(WalkResult {
concepts: out,
index_files,
})
}