use std::collections::btree_map;
use std::fs;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use crate::config::{Config, WatchConfig};
use crate::snapshots;
enum CallState {
Yield(PathBuf),
Recurse,
Done,
}
pub struct GitRepoIter<'a> {
config_iter: btree_map::Iter<'a, String, Rc<WatchConfig>>,
sub_iter: Vec<(Rc<PathBuf>, Rc<WatchConfig>, fs::ReadDir)>,
}
impl<'a> GitRepoIter<'a> {
pub fn new(config: &'a Config) -> Self {
Self {
config_iter: config.repos.iter(),
sub_iter: Vec::new(),
}
}
fn get_next(&mut self) -> CallState {
match self.sub_iter.pop() {
Some((base_path, watch_config, mut dir_iter)) => {
let mut next_next: Option<(Rc<PathBuf>, Rc<WatchConfig>, fs::ReadDir)> = None;
let mut ret_val = CallState::Recurse;
let max_depth: usize = watch_config.max_depth.into();
if let Some(Ok(entry)) = dir_iter.next() {
let child_path = entry.path();
if is_valid_directory(base_path.as_path(), child_path.as_path(), &watch_config)
{
if snapshots::is_repo(child_path.as_path()) {
ret_val = CallState::Yield(child_path);
} else if self.sub_iter.len() < max_depth {
if let Ok(child_dir_iter) = fs::read_dir(child_path.as_path()) {
next_next = Some((
Rc::clone(&base_path),
Rc::clone(&watch_config),
child_dir_iter,
))
}
}
}
self.sub_iter
.push((Rc::clone(&base_path), Rc::clone(&watch_config), dir_iter));
}
if let Some(tuple) = next_next {
self.sub_iter.push(tuple);
}
ret_val
}
None => {
match self.config_iter.next() {
Some((base_path, watch_config)) => {
let path = PathBuf::from(base_path);
let dir_iter_opt = path.parent().and_then(|p| fs::read_dir(p).ok());
if let Some(dir_iter) = dir_iter_opt {
self.sub_iter
.push((Rc::new(path), Rc::clone(watch_config), dir_iter));
}
CallState::Recurse
}
None => CallState::Done,
}
}
}
}
}
impl<'a> Iterator for GitRepoIter<'a> {
type Item = PathBuf;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.get_next() {
CallState::Yield(path) => return Some(path),
CallState::Recurse => continue,
CallState::Done => return None,
}
}
}
}
fn is_valid_directory(base_path: &Path, child_path: &Path, value: &WatchConfig) -> bool {
if !child_path.is_dir() {
return false;
}
if !child_path.starts_with(base_path) {
return false;
}
let includes = &value.include;
let excludes = &value.exclude;
let mut include = true;
if !excludes.is_empty() {
include = !excludes
.iter()
.any(|exclude| child_path.starts_with(base_path.join(exclude)));
}
if !include && !includes.is_empty() {
include = includes
.iter()
.any(|include| base_path.join(include).starts_with(child_path));
}
include
}