use super::*;
use crate::Documentation;
use fs_err as fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct TraverseModulesIter {
queue: VecDeque<(PathBuf, usize)>,
max_depth: usize,
}
impl Default for TraverseModulesIter {
fn default() -> Self {
Self {
max_depth: usize::MAX,
queue: VecDeque::with_capacity(128),
}
}
}
impl TraverseModulesIter {
fn add_initial_path<P>(&mut self, path: P, level: usize) -> Result<()>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let path = fs::canonicalize(path)?;
let meta = fs::metadata(&path)?;
if meta.is_file() {
self.queue.push_front((path, level));
} else if meta.is_dir() {
ignore::WalkBuilder::new(path)
.git_ignore(true)
.max_depth(1.into())
.same_file_system(true)
.skip_stdout(true)
.build()
.filter_map(|entry| {
entry
.ok()
.filter(|entry| entry.file_type().map(|ft| ft.is_file()).unwrap_or(false))
.map(|x| x.path().to_owned())
})
.filter(|path: &PathBuf| {
path.to_str()
.map(|x| x.to_owned())
.filter(|path| path.ends_with(".rs"))
.is_some()
})
.try_for_each::<_, Result<()>>(|path| {
log::trace!("🌱 using path {} as seed recursion dir", path.display());
self.queue.push_front((path, level));
Ok(())
})?;
}
Ok(())
}
#[allow(unused)]
pub fn with_multi<P, J, I>(entries: I) -> Result<Self>
where
P: AsRef<Path>,
J: Iterator<Item = P>,
I: IntoIterator<Item = P, IntoIter = J>,
{
let mut me = Self::default();
for path in entries.into_iter() {
me.add_initial_path(path, 0)?;
}
Ok(me)
}
pub fn with_depth_limit<P: AsRef<Path>>(path: P, max_depth: usize) -> Result<Self> {
let mut me = Self {
max_depth,
..Default::default()
};
me.add_initial_path(path, 0)?;
Ok(me)
}
#[allow(unused)]
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::with_depth_limit(path, usize::MAX)
}
pub fn collect_modules(&mut self, path: &Path, level: usize) -> Result<()> {
if path.is_file() {
log::trace!("🥞 collecting mods declared in file {}", path.display());
self.queue.extend(
extract_modules_from_file(path)?
.into_iter()
.map(|item| (item, level)),
);
} else {
log::warn!("🥞 Only dealing with files, dropping {}", path.display());
}
Ok(())
}
}
impl Iterator for TraverseModulesIter {
type Item = PathBuf;
fn next(&mut self) -> Option<Self::Item> {
if let Some((path, level)) = self.queue.pop_front() {
if level < self.max_depth {
let _ = self.collect_modules(path.as_path(), level + 1);
}
Some(path)
} else {
None
}
}
}
pub(crate) fn traverse(
path: &Path,
doc_comments: bool,
dev_comments: bool,
) -> Result<impl Iterator<Item = Documentation>> {
traverse_with_depth_limit(path, usize::MAX, doc_comments, dev_comments)
}
pub(crate) fn traverse_with_depth_limit(
path: &Path,
max_depth: usize,
doc_comments: bool,
dev_comments: bool,
) -> Result<impl Iterator<Item = Documentation>> {
let it = TraverseModulesIter::with_depth_limit(path, max_depth)?
.filter_map(move |path: PathBuf| -> Option<Documentation> {
fs::read_to_string(&path).ok().map(|content| {
Documentation::load_from_str(
ContentOrigin::RustSourceFile(path),
content.as_str(),
doc_comments,
dev_comments,
)
})
})
.filter(|documentation| !documentation.is_empty());
Ok(it)
}
#[cfg(test)]
mod tests {
use super::*;
fn demo_dir() -> PathBuf {
manifest_dir().join("demo")
}
#[test]
fn traverse_main_rs() {
let _ = env_logger::builder()
.filter_level(log::LevelFilter::Trace)
.is_test(true)
.try_init();
let manifest_path = demo_dir().join("src/main.rs");
let expect = indexmap::indexset! {
"src/main.rs",
"src/lib.rs",
"src/nested/mod.rs",
"src/nested/justone.rs",
"src/nested/justtwo.rs",
"src/nested/again/mod.rs",
"src/nested/again/code.rs",
"src/nested/fragments.rs",
"src/nested/fragments/enumerate.rs",
"src/nested/fragments/simple.rs",
}
.into_iter()
.map(|sub| demo_dir().join(sub))
.collect::<indexmap::set::IndexSet<PathBuf>>();
let found = TraverseModulesIter::new(manifest_path.as_path())
.expect("Must succeed to traverse file tree.")
.into_iter()
.collect::<Vec<PathBuf>>();
let unexpected_files: Vec<_> = dbg!(&found)
.iter()
.filter(|found_path| !expect.contains(*found_path))
.collect();
assert_eq!(Vec::<&PathBuf>::new(), unexpected_files);
let missing_files: Vec<_> = expect
.iter()
.filter(|expected_path| !found.contains(expected_path))
.collect();
assert_eq!(Vec::<&PathBuf>::new(), missing_files);
assert_eq!(found.len(), expect.len());
}
}