use std::path::{Component, Path, PathBuf};
use crate::classifier::classify_target_relative_path;
use crate::error::{ReclaimError, ReclaimResult};
use crate::model::TargetEvidence;
use crate::planner::{PlannerCandidate, TargetContext};
use super::snapshot::snapshot_target_relative_path_from_normalized_child;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InventoryOptions {
pub follow_symlinks: bool,
pub skipped_paths: Vec<PathBuf>,
pub deep_target_scan: bool,
pub deep_directory_measurement: bool,
}
pub fn planner_candidate_from_target_relative_path(
target_root: impl AsRef<Path>,
child_path: impl AsRef<Path>,
evidence: TargetEvidence,
options: &InventoryOptions,
) -> ReclaimResult<PlannerCandidate> {
let child_path = normalize_target_relative_child(child_path.as_ref())?;
let target_root = target_root.as_ref();
let snapshot =
snapshot_target_relative_path_from_normalized_child(target_root, &child_path, options)?;
let artifact_class = classify_target_relative_path(&child_path);
Ok(
PlannerCandidate::new(snapshot, artifact_class, evidence.clone())
.with_target_context(target_context_from_evidence(target_root, &evidence)),
)
}
pub fn planner_candidate_from_target_relative_path_with_context(
target_root: impl AsRef<Path>,
child_path: impl AsRef<Path>,
evidence: TargetEvidence,
target_context: TargetContext,
options: &InventoryOptions,
) -> ReclaimResult<PlannerCandidate> {
let child_path = normalize_target_relative_child(child_path.as_ref())?;
let snapshot = snapshot_target_relative_path_from_normalized_child(
target_root.as_ref(),
&child_path,
options,
)?;
let artifact_class = classify_target_relative_path(&child_path);
Ok(PlannerCandidate::new(snapshot, artifact_class, evidence)
.with_target_context(target_context))
}
pub(super) fn normalize_target_relative_child(child_path: &Path) -> ReclaimResult<PathBuf> {
if child_path.as_os_str().is_empty() {
return Err(ReclaimError::EmptyPath);
}
if child_path.is_absolute() {
return Err(ReclaimError::AbsoluteInventoryChildPath {
path: child_path.to_path_buf(),
});
}
let mut normalized = PathBuf::new();
for component in child_path.components() {
match component {
Component::CurDir => {}
Component::Normal(component) => normalized.push(component),
Component::ParentDir | Component::RootDir | Component::Prefix(_) => {
return Err(ReclaimError::InventoryPathEscape {
path: child_path.to_path_buf(),
});
}
}
}
if normalized.as_os_str().is_empty() {
return Err(ReclaimError::EmptyPath);
}
Ok(normalized)
}
pub(super) fn target_context_from_evidence(
target_root: &Path,
evidence: &TargetEvidence,
) -> TargetContext {
let mut target_context = TargetContext::new(target_root);
if let TargetEvidence::ProjectContext { project_manifest } = evidence
&& let Some(project_root) = project_manifest.parent()
{
target_context = target_context.with_project_root(project_root);
}
target_context
}
pub(super) fn is_configured_skipped(path: &Path, options: &InventoryOptions) -> bool {
let normalized_path = lexically_normalize(path);
options.skipped_paths.iter().any(|skipped| {
let skipped = lexically_normalize(skipped);
normalized_path == skipped || normalized_path.starts_with(skipped)
})
}
pub(super) fn lexically_normalize(path: &Path) -> PathBuf {
let mut normalized = PathBuf::new();
for component in path.components() {
match component {
Component::CurDir => {}
Component::ParentDir => {
normalized.pop();
}
other => normalized.push(other.as_os_str()),
}
}
normalized
}