use std::path::{Path, PathBuf};
use walkdir::WalkDir;
#[derive(Debug, Clone)]
pub struct DiscoveredFile {
pub rel_path: String,
pub abs_path: PathBuf,
}
const RESERVED: &[&str] = &["index.md", "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<Vec<DiscoveredFile>, 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 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") || RESERVED.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("/");
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(out)
}