use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use crate::workspace::Workspace;
use prov_graph::error::Result;
use prov_store::fs::Storage;
use prov_store::index::IndexStore;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Omission {
Unreached,
Bookkeeping,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Uncaptured {
pub path: PathBuf,
pub reason: Omission,
}
impl<FS: Storage, IdP, Ix: IndexStore> Workspace<FS, IdP, Ix> {
pub async fn uncaptured(&self, root_doc: &Path) -> Result<Vec<Uncaptured>> {
let captured: BTreeSet<PathBuf> = self
.history_capture_set(root_doc)
.await?
.into_iter()
.collect();
let parked = self.parked_dirs(root_doc).await?;
let mut known = parked.clone();
let (store_index, _) = self.history_store().store_index(root_doc).await?;
known.push(crate::history::store_dir(&store_index));
if let Some(about) = self.about_path(root_doc).await? {
known.push(about);
}
let mut found = Vec::new();
self.scan_uncaptured(PathBuf::new(), &captured, &parked, &known, &mut found)
.await?;
found.sort_by(|a, b| a.reason.cmp(&b.reason).then_with(|| a.path.cmp(&b.path)));
Ok(found)
}
fn scan_uncaptured<'a>(
&'a self,
rel_dir: PathBuf,
captured: &'a BTreeSet<PathBuf>,
parked: &'a [PathBuf],
known: &'a [PathBuf],
out: &'a mut Vec<Uncaptured>,
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
Box::pin(async move {
let Ok(entries) = self.listing(&rel_dir).await else {
return Ok(());
};
for entry in entries {
let Some(name) = entry
.file_name()
.and_then(|n| n.to_str())
.map(str::to_owned)
else {
continue;
};
if name.starts_with('.') {
continue;
}
let rel = match rel_dir.as_os_str().is_empty() {
true => PathBuf::from(&name),
false => rel_dir.join(&name),
};
if entry.file_type().is_dir() {
if parked.iter().any(|dir| rel.starts_with(dir)) {
out.push(Uncaptured {
path: rel,
reason: Omission::Bookkeeping,
});
continue;
}
if self.manifest_node_for(&rel).await?.is_some() {
continue;
}
self.scan_uncaptured(rel, captured, parked, known, out)
.await?;
} else if entry.file_type().is_file() && !captured.contains(&rel) {
let reason = match known.iter().any(|dir| rel.starts_with(dir)) {
true => Omission::Bookkeeping,
false => Omission::Unreached,
};
out.push(Uncaptured { path: rel, reason });
}
}
Ok(())
})
}
}