Skip to content

Commit

Permalink
repo-path-tree: make public and add an iterator API
Browse files Browse the repository at this point in the history
The internal 'set-of-dirs' structure is useful to extensions that need to do
their own tree-walking routines. It may also be useful internally in other
places.
  • Loading branch information
torquestomp committed Oct 20, 2023
1 parent eee140e commit 935399c
Showing 1 changed file with 122 additions and 6 deletions.
128 changes: 122 additions & 6 deletions lib/src/matchers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#![allow(dead_code, missing_docs)]

use std::collections::{HashMap, HashSet};
use std::collections::{hash_map, HashMap, HashSet, VecDeque};
use std::iter;

use tracing::instrument;
Expand Down Expand Up @@ -271,7 +271,7 @@ struct RepoPathTree {
}

impl RepoPathTree {
fn new() -> Self {
pub fn new() -> Self {
RepoPathTree {
entries: HashMap::new(),
is_dir: false,
Expand All @@ -290,15 +290,15 @@ impl RepoPathTree {
})
}

fn add_dir(&mut self, dir: &RepoPath) {
pub fn add_dir(&mut self, dir: &RepoPath) {
self.add(dir).is_dir = true;
}

fn add_file(&mut self, file: &RepoPath) {
pub fn add_file(&mut self, file: &RepoPath) {
self.add(file).is_file = true;
}

fn get(&self, dir: &RepoPath) -> Option<&RepoPathTree> {
pub fn get(&self, dir: &RepoPath) -> Option<&RepoPathTree> {
dir.components()
.iter()
.try_fold(self, |sub, name| sub.entries.get(name))
Expand All @@ -310,7 +310,7 @@ impl RepoPathTree {
.unwrap_or(Visit::Nothing)
}

fn walk_to<'a>(
pub fn walk_to<'a>(
&'a self,
dir: &'a RepoPath,
) -> impl Iterator<Item = (&RepoPathTree, &[RepoPathComponent])> + 'a {
Expand Down Expand Up @@ -338,6 +338,87 @@ impl RepoPathTree {
}
}

pub struct RepoPathTreeIterator<'a> {
tree: &'a RepoPathTree,
map_iter: Option<hash_map::Iter<'a, RepoPathComponent, RepoPathTree>>,
map_component: Option<&'a RepoPathComponent>,
tree_iter: Option<Box<RepoPathTreeIterator<'a>>>,
}

impl<'a> RepoPathTreeIterator<'a> {
fn new(tree: &'a RepoPathTree) -> Self {
Self {
tree,
map_iter: None,
map_component: None,
tree_iter: None,
}
}
}

pub struct RepoPathTreeElement<'a> {
path: VecDeque<&'a RepoPathComponent>,
is_dir: bool,
is_file: bool,
}

impl<'a> RepoPathTreeElement<'a> {
pub fn repo_path(&self) -> RepoPath {
RepoPath::from_components(self.path.iter().cloned().cloned().collect())
}

pub fn is_dir(&self) -> bool {
self.is_dir
}

pub fn is_file(&self) -> bool {
self.is_file
}
}

impl<'a> Iterator for RepoPathTreeIterator<'a> {
type Item = RepoPathTreeElement<'a>;

fn next(&mut self) -> Option<Self::Item> {
if let Some(map_iter) = self.map_iter.as_mut() {
if let Some(tree_iter) = self.tree_iter.as_mut() {
if let Some(mut child) = tree_iter.next() {
let map_component = *self.map_component.as_ref().unwrap();
child.path.push_front(map_component);
return Some(child);
}
}

if let Some((component, tree)) = map_iter.next() {
self.map_component = Some(component);
let mut iter = tree.into_iter();
let mut first = iter.next().unwrap();
first.path.push_front(component);
self.tree_iter = Some(Box::new(iter));
Some(first)
} else {
None
}
} else {
self.map_iter = Some(self.tree.entries.iter());
Some(Self::Item {
path: Default::default(),
is_dir: self.tree.is_dir,
is_file: self.tree.is_file,
})
}
}
}

impl<'a> IntoIterator for &'a RepoPathTree {
type Item = RepoPathTreeElement<'a>;
type IntoIter = RepoPathTreeIterator<'a>;

fn into_iter(self) -> Self::IntoIter {
Self::IntoIter::new(self)
}
}

#[cfg(test)]
mod tests {
use maplit::hashset;
Expand Down Expand Up @@ -387,6 +468,41 @@ mod tests {
);
}

#[test]
fn test_repo_path_tree_iterator() {
let mut tree = RepoPathTree::new();
tree.add_file(&RepoPath::from_internal_string("dir1/dir2/file"));
tree.add_file(&RepoPath::from_internal_string("dir1/dir3/file"));

let elements: Vec<_> = tree.into_iter().collect();
assert_eq!(elements.len(), 6);
assert_eq!(elements[0].repo_path(), RepoPath::root());
assert!(elements[0].is_dir);
assert!(!elements[0].is_file);
assert_eq!(
elements[1].repo_path(),
RepoPath::from_internal_string("dir1")
);
assert!(elements[1].is_dir);
assert!(!elements[1].is_file);
assert_eq!(
hashset! {elements[2].repo_path(), elements[4].repo_path()},
hashset! {RepoPath::from_internal_string("dir1/dir2"), RepoPath::from_internal_string("dir1/dir3")}
);
assert!(elements[2].is_dir);
assert!(!elements[2].is_file);
assert!(elements[4].is_dir);
assert!(!elements[4].is_file);
assert_eq!(
hashset! {elements[3].repo_path(), elements[5].repo_path()},
hashset! {RepoPath::from_internal_string("dir1/dir2/file"), RepoPath::from_internal_string("dir1/dir3/file")}
);
assert!(!elements[3].is_dir);
assert!(elements[3].is_file);
assert!(!elements[5].is_dir);
assert!(elements[5].is_file);
}

#[test]
fn test_nothingmatcher() {
let m = NothingMatcher;
Expand Down

0 comments on commit 935399c

Please sign in to comment.