use std::{
cmp,
path::Path,
sync::mpsc::{self, Sender},
};
use crate::{filter::FilterType, utils, SearchBuilder};
use ignore::{WalkBuilder, WalkState};
use regex::Regex;
pub struct Search {
rx: Box<dyn Iterator<Item = String>>,
}
impl Iterator for Search {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
self.rx.next()
}
}
impl Search {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
search_location: impl AsRef<Path>,
more_locations: Option<Vec<impl AsRef<Path>>>,
search_input: Option<&str>,
file_ext: Option<&str>,
depth: Option<usize>,
limit: Option<usize>,
strict: bool,
ignore_case: bool,
with_hidden: bool,
filters: Vec<FilterType>,
) -> Self {
let regex_search_input =
utils::build_regex_search_input(search_input, file_ext, strict, ignore_case);
let mut walker = WalkBuilder::new(search_location);
walker
.hidden(!with_hidden)
.git_ignore(true)
.max_depth(depth)
.threads(cmp::min(12, num_cpus::get()));
walker.filter_entry(move |dir| filters.iter().all(|f| f.apply(dir)));
if let Some(locations) = more_locations {
for location in locations {
walker.add(location);
}
}
let (tx, rx) = mpsc::channel::<String>();
walker.build_parallel().run(|| {
let tx: Sender<String> = tx.clone();
let reg_exp: Regex = regex_search_input.clone();
let mut counter = 0;
Box::new(move |path_entry| {
if let Ok(entry) = path_entry {
let path = entry.path();
if let Some(file_name) = path.file_name() {
let file_name = file_name.to_string_lossy().to_string();
if reg_exp.is_match(&file_name) {
if tx.send(path.display().to_string()).is_ok()
&& (limit.is_none() || counter < limit.unwrap())
{
counter += 1;
return WalkState::Continue;
} else {
return WalkState::Quit;
}
}
}
}
WalkState::Continue
})
});
if let Some(limit) = limit {
Self {
rx: Box::new(rx.into_iter().take(limit)),
}
} else {
Self {
rx: Box::new(rx.into_iter()),
}
}
}
}
impl Default for Search {
fn default() -> Self {
SearchBuilder::default().build()
}
}