mntn 3.2.1

A Rust-based command-line tool for dotfiles management with profiles.
Documentation
use crate::utils::display::{green, red, yellow};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
    Error,
    Warning,
    Info,
}

#[derive(Debug, Clone)]
pub struct ValidationError {
    pub severity: Severity,
    pub message: String,
    pub fix_suggestion: Option<String>,
}

impl ValidationError {
    pub fn error(message: impl Into<String>) -> Self {
        Self {
            severity: Severity::Error,
            message: message.into(),
            fix_suggestion: None,
        }
    }

    pub fn warning(message: impl Into<String>) -> Self {
        Self {
            severity: Severity::Warning,
            message: message.into(),
            fix_suggestion: None,
        }
    }

    pub fn info(message: impl Into<String>) -> Self {
        Self {
            severity: Severity::Info,
            message: message.into(),
            fix_suggestion: None,
        }
    }

    pub fn with_fix(mut self, suggestion: impl Into<String>) -> Self {
        self.fix_suggestion = Some(suggestion.into());
        self
    }
}

pub trait Validator {
    fn name(&self) -> &str;
    fn validate(&self) -> Vec<ValidationError>;
}

#[derive(Default)]
pub struct ValidationReport {
    results: Vec<(String, Vec<ValidationError>)>,
}

impl ValidationReport {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add_result(&mut self, validator_name: &str, errors: Vec<ValidationError>) {
        self.results.push((validator_name.to_string(), errors));
    }

    fn count_by_severity(&self, severity: Severity) -> usize {
        self.results
            .iter()
            .flat_map(|(_, errors)| errors.iter())
            .filter(|e| e.severity == severity)
            .count()
    }

    pub fn error_count(&self) -> usize {
        self.count_by_severity(Severity::Error)
    }

    pub fn warning_count(&self) -> usize {
        self.count_by_severity(Severity::Warning)
    }

    pub fn print(&self) {
        for (name, errors) in &self.results {
            if errors.is_empty() {
                println!(" {} OK", name);
            } else {
                println!(" {}", name);
                for error in errors {
                    let line = match error.severity {
                        Severity::Error => red(&format!(" x {}", error.message)),
                        Severity::Warning => yellow(&format!(" ! {}", error.message)),
                        Severity::Info => green(&format!(" i {}", error.message)),
                    };
                    println!("{}", line);
                    if let Some(fix) = &error.fix_suggestion {
                        println!("{}", yellow(&format!(" Fix: {}", fix)));
                    }
                }
            }
        }
    }
}