patholog 1.0.0

Diagnose, explain, clean, and safely repair PATH behavior.
Documentation
mod duplicates;
mod ordering;
mod shadowed;
mod state;
mod unwanted;

use std::path::Path;

use crate::model::{DoctorReport, PathVariable, PlatformMode};
use crate::path_env::parse_path;
use crate::policy::PathPolicy;
use crate::resolve::resolve_command;

#[cfg(any(test, feature = "fuzzing"))]
pub(crate) fn diagnose_path(
    path_value: &str,
    platform_mode: PlatformMode,
    pathext: Option<&str>,
) -> DoctorReport {
    diagnose_path_with_policy(
        path_value,
        platform_mode,
        pathext,
        PathVariable::Path,
        &PathPolicy::default(),
    )
}

pub(crate) fn diagnose_path_with_policy(
    path_value: &str,
    platform_mode: PlatformMode,
    pathext: Option<&str>,
    variable: PathVariable,
    policy: &PathPolicy,
) -> DoctorReport {
    let entries = parse_path(path_value, platform_mode, pathext);
    let mut diagnostics = Vec::new();
    diagnostics.extend(duplicates::diagnostics(&entries));
    diagnostics.extend(state::diagnostics(&entries));
    diagnostics.extend(unwanted::diagnostics(
        &entries,
        policy,
        platform_mode,
        pathext,
    ));
    if variable == PathVariable::Path {
        diagnostics.extend(ordering::diagnostics(&entries));
        diagnostics.extend(ordering::preset_diagnostics(&entries, policy));
        dedupe_diagnostics(&mut diagnostics);
    }
    DoctorReport {
        variable,
        entries,
        diagnostics,
    }
}

pub(crate) fn diagnose_command_path_with_policy(
    path_value: &str,
    command: &str,
    platform_mode: PlatformMode,
    pathext: Option<&str>,
    cwd: &Path,
    policy: &PathPolicy,
) -> DoctorReport {
    let mut report = diagnose_path_with_policy(
        path_value,
        platform_mode,
        pathext,
        PathVariable::Path,
        policy,
    );
    let resolution = resolve_command(path_value, command, platform_mode, pathext, cwd, false);
    report
        .diagnostics
        .extend(shadowed::diagnostics(command, &resolution.candidates));
    report
}

fn dedupe_diagnostics(diagnostics: &mut Vec<crate::model::Diagnostic>) {
    let mut unique = Vec::new();
    for diagnostic in diagnostics.drain(..) {
        if !unique
            .iter()
            .any(|existing| same_diagnostic(existing, &diagnostic))
        {
            unique.push(diagnostic);
        }
    }
    *diagnostics = unique;
}

fn same_diagnostic(left: &crate::model::Diagnostic, right: &crate::model::Diagnostic) -> bool {
    left.kind == right.kind
        && left.message == right.message
        && left.entry_index == right.entry_index
        && left.entry_value == right.entry_value
        && left.related_indexes == right.related_indexes
}

#[cfg(test)]
#[path = "doctor/tests.rs"]
mod tests;