use std::path::{Path, PathBuf};
use std::time::SystemTime;
use globset::GlobSet;
use crate::error::Result;
#[derive(Debug, Clone)]
pub struct ScannedFile {
pub rel_path: String,
pub abs_path: PathBuf,
pub modified: SystemTime,
}
pub fn is_supported_ext(ext: &str) -> bool {
matches!(ext, "md" | "json")
}
pub fn scan_directory(root: &Path, ignore: &GlobSet) -> Result<Vec<ScannedFile>> {
let mut files = Vec::new();
walk_dir(root, root, ignore, &mut files)?;
files.sort_by_key(|b| std::cmp::Reverse(b.modified));
Ok(files)
}
fn walk_dir(root: &Path, dir: &Path, ignore: &GlobSet, out: &mut Vec<ScannedFile>) -> Result<()> {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return Ok(()),
};
for entry in entries {
let entry = match entry {
Ok(e) => e,
Err(_) => continue, };
let path = entry.path();
if path
.file_name()
.is_some_and(|n| n.to_string_lossy().starts_with('.'))
{
continue;
}
if path.is_dir() {
let rel = path
.strip_prefix(root)
.unwrap_or(&path)
.to_string_lossy()
.to_string();
if ignore.is_match(&rel) {
continue;
}
let before = out.len();
walk_dir(root, &path, ignore, out)?;
if out.len() == before {
continue;
}
} else if path
.extension()
.and_then(|e| e.to_str())
.is_some_and(is_supported_ext)
{
let rel = path
.strip_prefix(root)
.unwrap_or(&path)
.to_string_lossy()
.to_string();
if ignore.is_match(&rel) {
continue;
}
let Ok(meta) = entry.metadata() else {
continue;
};
let modified = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
out.push(ScannedFile {
rel_path: rel,
abs_path: path,
modified,
});
}
}
Ok(())
}
pub fn build_globset(patterns: &[String]) -> GlobSet {
let mut builder = globset::GlobSetBuilder::new();
for p in patterns {
match globset::Glob::new(p) {
Ok(g) => {
builder.add(g);
}
Err(e) => {
eprintln!("Ignoring invalid glob pattern {p:?}: {e}");
}
}
}
builder.build().unwrap_or_else(|_| GlobSet::empty())
}