use std::{
ffi::OsString,
path::{Component, PathBuf},
};
use crate::{
backend::node::{Metadata, Node, NodeType},
blob::tree::comp_to_osstr,
};
pub(crate) struct TreeIterator<T, I> {
iter: I,
path: PathBuf,
item: Option<T>,
}
impl<T, I> TreeIterator<T, I>
where
I: Iterator<Item = T>,
{
pub(crate) fn new(mut iter: I) -> Self {
let item = iter.next();
Self {
iter,
path: PathBuf::new(),
item,
}
}
fn pop(&mut self) -> bool {
let mut comps = self.path.components();
loop {
let comp = comps.next_back();
match comp {
Some(Component::Prefix(_) | Component::Normal(_)) => {
self.path = comps.collect();
return true;
}
Some(Component::RootDir | Component::ParentDir | Component::CurDir) => {}
None => return false,
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum TreeType<T, U> {
NewTree((PathBuf, Node, U)),
EndTree,
Other((PathBuf, Node, T)),
}
impl<I, O> Iterator for TreeIterator<(PathBuf, Node, O), I>
where
I: Iterator<Item = (PathBuf, Node, O)>,
{
type Item = TreeType<O, OsString>;
fn next(&mut self) -> Option<Self::Item> {
match &self.item {
None => self.pop().then_some(TreeType::EndTree),
Some((path, node, _)) => {
match path.strip_prefix(&self.path) {
Err(_) => {
_ = self.pop();
Some(TreeType::EndTree)
}
Ok(missing_dirs) => {
for comp in missing_dirs.components() {
self.path.push(comp);
if let Some(p) = comp_to_osstr(comp).ok().flatten() {
if node.is_dir() && path == &self.path {
let (path, node, _) = self.item.take().unwrap();
self.item = self.iter.next();
let name = node.name().into_owned();
return Some(TreeType::NewTree((path, node, name)));
}
let meta = Metadata {
mode: Some(0o755),
..Default::default()
};
let node = Node::new_node(&p, NodeType::Dir, meta);
return Some(TreeType::NewTree((
self.path.clone(),
node,
p.into_owned(),
)));
}
}
let item = self.item.take().unwrap();
self.item = self.iter.next();
Some(TreeType::Other(item))
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot;
use rstest::rstest;
use super::*;
use std::path::Path;
fn test_tree_iter(case: &str, paths: Vec<&str>) {
let paths = paths.into_iter().map(|p| {
(
Path::new(&p).to_path_buf(),
Node::new_node(
Path::new(&p).file_name().unwrap(),
NodeType::Dir,
Metadata::default(),
),
(),
)
});
let result: Vec<_> = TreeIterator::new(paths).collect();
assert_debug_snapshot!(format!("tree_iter#{case}"), result);
}
#[cfg(not(windows))]
#[rstest]
#[case("simple", ["a", "a/b", "a/b/c"].to_vec())]
#[case("simple_root", ["/a", "/a/b", "/a/b/c"].to_vec())]
#[case("simple_relative", ["./a", "./a/b", "./a/b/c"].to_vec())]
#[case("complex", ["a/b", "a/b/c", "a/b/d", "f"].to_vec())]
#[case("complex_root", ["/a/b", "/a/b/c", "/a/b/d", "/f"].to_vec())]
#[case("complex_relative", ["./a/b", "./a/b/c", "./a/b/d", "./f"].to_vec())]
fn test_tree_iter_nix(#[case] case: &str, #[case] paths: Vec<&str>) {
test_tree_iter(case, paths);
}
#[cfg(windows)]
#[rstest]
#[case("windows", [r"C:\a", r"C:\a\b", r"D:\a"].to_vec())]
fn test_tree_iter_windows(#[case] case: &str, #[case] paths: Vec<&str>) {
test_tree_iter(case, paths);
}
}