mod build;
mod filter;
mod recursion;
use crate::entries::Entry;
use crate::{error, utils};
use clap::Args;
use std::iter;
use std::path::PathBuf;
use std::rc::Rc;
pub use build::*;
pub use filter::*;
pub use recursion::*;
#[derive(Debug, Args)]
pub struct FetcherArgs {
#[arg(help_heading = None)]
dirs: Vec<PathBuf>,
#[command(flatten)]
recursion: RecursionArgs,
#[command(flatten)]
filter: FilterArgs,
}
#[derive(Debug)]
pub struct Fetcher {
dirs: Vec<Entry>,
recursion: Recursion,
filter: Filter,
}
#[derive(Debug, Copy, Clone)]
pub enum DirPolicy {
Never,
Immediately,
WithContents,
AtLimit,
}
impl Fetcher {
pub fn single(entry: &Entry, recursion: Recursion) -> Self {
Fetcher {
dirs: vec![entry.to_owned()],
recursion,
filter: Filter::default(),
}
}
pub fn fetch(self, dp: DirPolicy) -> impl Iterator<Item = Entry> {
let filter = Rc::new(self.filter);
self.dirs
.into_iter()
.flat_map(move |dir| entries(dir, self.recursion, dp, Rc::clone(&filter)))
}
pub fn find_common_dir_prefix_length(&self) -> usize {
let mut iter = self.dirs.iter().map(|e| e.to_str());
let Some(first) = iter.next() else {
return 0;
};
let common = iter.fold(first, |acc, s| {
let common_len = acc
.bytes()
.zip(s.bytes())
.take_while(|(a, b)| a == b)
.count();
&acc[..common_len]
});
common.rfind('/').unwrap_or_default()
}
}
fn entries(
dir: Entry,
recursion: Recursion,
dp: DirPolicy,
f: Rc<Filter>,
) -> Box<dyn Iterator<Item = Entry>> {
if !utils::is_running() {
return Box::new(iter::empty());
}
match std::fs::read_dir(&dir) {
Ok(rd) => Box::new(
rd.inspect(|res| {
if let Err(err) = res {
error!("dir entry: {err}");
}
})
.flatten()
.map(move |de| {
de.file_name()
.to_str()
.map(|s| dir.join(s))
.ok_or_else(|| Box::new(de))
})
.inspect(|res| {
if let Err(de) = res {
error!("no UTF-8 name: {de:?}");
}
})
.flatten()
.flat_map(move |entry| {
use DirPolicy::*;
if !entry.is_dir() {
return if f.is_in(&entry) && !entry.file_name().starts_with(".") {
Box::new(iter::once(entry)) as Box<dyn Iterator<Item = _>>
} else {
Box::new(iter::empty())
};
}
match (f.is_in(&entry), (dp, recursion.deeper())) {
(true, (WithContents | AtLimit, None) | (Immediately, _)) => {
Box::new(iter::once(entry))
}
(true, (WithContents, Some(r))) => Box::new(
iter::once(entry.clone()).chain(entries(entry, r, dp, Rc::clone(&f))),
),
(_, (_, Some(r))) if !entry.file_name().starts_with(".") => {
entries(entry, r, dp, Rc::clone(&f))
}
_ => Box::new(iter::empty()),
}
}),
),
Err(err) => {
error!("read dir {dir}: {err}");
Box::new(iter::empty())
}
}
}