use std::path::{Path, PathBuf};
use std::sync::Mutex;
use ignore::overrides::OverrideBuilder;
use ignore::types::TypesBuilder;
use ignore::{WalkBuilder, WalkState};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Default)]
pub struct WalkOptions {
pub hidden: bool,
pub no_ignore: bool,
pub follow_symlinks: bool,
pub types: Vec<String>,
pub types_not: Vec<String>,
pub globs: Vec<String>,
}
pub fn walk_paths<P: AsRef<Path>>(roots: &[P], opts: &WalkOptions) -> Result<Vec<PathBuf>> {
let builder = build_walker(roots, opts)?;
let collected: Mutex<Vec<PathBuf>> = Mutex::new(Vec::new());
let first_error: Mutex<Option<ignore::Error>> = Mutex::new(None);
builder.build_parallel().run(|| {
Box::new(|result| match result {
Ok(entry) => {
if matches!(entry.file_type(), Some(ft) if ft.is_file())
&& let Ok(mut sink) = collected.lock()
{
sink.push(entry.into_path());
}
WalkState::Continue
}
Err(e) => {
if let Ok(mut slot) = first_error.lock()
&& slot.is_none()
{
*slot = Some(e);
}
WalkState::Quit
}
})
});
if let Some(e) = first_error.into_inner().ok().flatten() {
return Err(Error::Walk(e));
}
let mut out = collected.into_inner().unwrap_or_default();
out.sort();
Ok(out)
}
fn build_walker<P: AsRef<Path>>(roots: &[P], opts: &WalkOptions) -> Result<WalkBuilder> {
let mut builder = if let Some(first) = roots.first() {
WalkBuilder::new(first.as_ref())
} else {
WalkBuilder::new(".")
};
for extra in roots.iter().skip(1) {
builder.add(extra.as_ref());
}
builder
.hidden(!opts.hidden)
.ignore(!opts.no_ignore)
.git_ignore(!opts.no_ignore)
.git_global(!opts.no_ignore)
.git_exclude(!opts.no_ignore)
.require_git(false)
.parents(!opts.no_ignore)
.follow_links(opts.follow_symlinks)
.threads(rayon::current_num_threads().max(1));
if !opts.types.is_empty() || !opts.types_not.is_empty() {
let mut tb = TypesBuilder::new();
tb.add_defaults();
for t in &opts.types {
tb.select(t);
}
for t in &opts.types_not {
tb.negate(t);
}
builder.types(tb.build()?);
}
if !opts.globs.is_empty() {
let glob_root = roots.first().map(|p| p.as_ref()).unwrap_or_else(|| Path::new("."));
let mut ob = OverrideBuilder::new(glob_root);
for g in &opts.globs {
ob.add(g)?;
}
builder.overrides(ob.build()?);
}
Ok(builder)
}
#[cfg(test)]
#[path = "walker_tests.rs"]
mod tests;