use std::collections::VecDeque;
use anyhow::{Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use cap_std::{
ambient_authority,
fs_utf8::{Dir, DirEntry},
};
use super::SOURCE_EXTENSION;
pub(super) enum Found {
Directory(Dir, Utf8PathBuf),
Source(Utf8PathBuf, String),
Ignored,
}
pub(super) fn is_walkable(name: &str) -> bool { !name.starts_with('.') && name != "target" }
fn shown(prefix: &Utf8Path) -> &str {
if prefix.as_str().is_empty() {
"the repository root"
} else {
prefix.as_str()
}
}
fn classify_entry(current: &Dir, prefix: &Utf8Path, entry: &DirEntry) -> Result<Found> {
let name = entry
.file_name()
.with_context(|| format!("read a file name in {}", shown(prefix)))?;
let path = prefix.join(&name);
if entry
.file_type()
.with_context(|| format!("stat {path}"))?
.is_dir()
{
if !is_walkable(&name) {
return Ok(Found::Ignored);
}
let child = current
.open_dir(&name)
.with_context(|| format!("open {path}"))?;
return Ok(Found::Directory(child, path));
}
if path.extension() != Some(SOURCE_EXTENSION) {
return Ok(Found::Ignored);
}
let contents = current
.read_to_string(&name)
.with_context(|| format!("read {path}"))?;
Ok(Found::Source(path, contents))
}
pub fn rust_sources(root: &Utf8Path, relative: &str) -> Result<Vec<(Utf8PathBuf, String)>> {
let directory = Dir::open_ambient_dir(root.join(relative), ambient_authority())
.with_context(|| format!("open {relative}"))?;
let base = if relative == "." {
Utf8PathBuf::new()
} else {
Utf8PathBuf::from(relative)
};
let mut pending = VecDeque::from([(directory, base)]);
let mut sources = Vec::new();
while let Some((current, prefix)) = pending.pop_front() {
let entries = current
.entries()
.with_context(|| format!("read {}", shown(&prefix)))?;
for candidate in entries {
let entry =
candidate.with_context(|| format!("read an entry of {}", shown(&prefix)))?;
match classify_entry(¤t, &prefix, &entry)? {
Found::Directory(child, path) => pending.push_back((child, path)),
Found::Source(path, contents) => sources.push((path, contents)),
Found::Ignored => {}
}
}
}
Ok(sources)
}