use std::{collections::BTreeMap, fmt};
use super::extensions::ExtensionFilter;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CandidatePath(String);
impl CandidatePath {
#[must_use]
pub fn new(path: String) -> Self { Self(path) }
#[must_use]
pub fn as_str(&self) -> &str { &self.0 }
}
impl fmt::Display for CandidatePath {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FileIdentity(String);
impl FileIdentity {
#[must_use]
pub fn from_canonical_name(name: String) -> Self { Self(name) }
#[cfg(test)]
#[must_use]
pub fn as_str(&self) -> &str { &self.0 }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PathKind {
RegularFile(FileIdentity),
Missing,
Symlink,
Other,
OutsideRoot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProbeFailureKind {
Unreadable,
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("reading `{path}` while selecting files: {detail}")]
pub struct ProbeFailure {
pub path: CandidatePath,
pub kind: ProbeFailureKind,
pub detail: String,
}
impl ProbeFailure {
#[must_use]
pub fn unreadable(path: CandidatePath, detail: String) -> Self {
Self {
path,
kind: ProbeFailureKind::Unreadable,
detail,
}
}
}
pub trait PathProbe {
fn probe(&self, candidate: &CandidatePath) -> Result<PathKind, ProbeFailure>;
}
pub fn select_files<P>(
candidates: &[CandidatePath],
extensions: &ExtensionFilter,
probe: &P,
) -> Result<Vec<CandidatePath>, ProbeFailure>
where
P: PathProbe + ?Sized,
{
let mut matching: Vec<&CandidatePath> = candidates
.iter()
.filter(|candidate| extensions.matches(candidate.as_str()))
.collect();
matching.sort_unstable();
matching.dedup();
let mut by_identity: BTreeMap<FileIdentity, &CandidatePath> = BTreeMap::new();
for candidate in matching {
if let PathKind::RegularFile(identity) = probe.probe(candidate)? {
by_identity.entry(identity).or_insert(candidate);
}
}
let mut selected: Vec<CandidatePath> = by_identity.into_values().cloned().collect();
selected.sort_unstable();
Ok(selected)
}
#[cfg(test)]
#[path = "policy_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "policy_property_tests.rs"]
mod property_tests;