Skip to main content

diskard_core/
config.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3use std::path::{Path, PathBuf};
4
5use crate::error::{Error, Result};
6use crate::finding::RiskLevel;
7
8/// Top-level configuration.
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10#[serde(default)]
11pub struct Config {
12    pub defaults: Defaults,
13    pub ignore: IgnoreConfig,
14    pub recognizers: RecognizerConfig,
15}
16
17/// Default behavior settings.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(default)]
20pub struct Defaults {
21    /// Maximum risk level to show by default.
22    pub risk_tolerance: String,
23    /// "trash" or "permanent".
24    pub delete_mode: String,
25    /// Minimum size in bytes to report.
26    pub min_size: u64,
27}
28
29/// Paths and patterns to ignore.
30#[derive(Debug, Clone, Default, Serialize, Deserialize)]
31#[serde(default)]
32pub struct IgnoreConfig {
33    /// Absolute paths to never scan or delete.
34    pub paths: Vec<PathBuf>,
35}
36
37/// Per-recognizer configuration.
38#[derive(Debug, Clone, Default, Serialize, Deserialize)]
39#[serde(default)]
40pub struct RecognizerConfig {
41    /// Recognizer IDs to disable.
42    pub disabled: HashSet<String>,
43}
44
45impl Default for Defaults {
46    fn default() -> Self {
47        Self {
48            risk_tolerance: "moderate".to_string(),
49            delete_mode: "trash".to_string(),
50            min_size: 0,
51        }
52    }
53}
54
55impl Config {
56    /// Standard config file path: `~/.config/diskard/config.toml`
57    pub fn path() -> Option<PathBuf> {
58        dirs::config_dir().map(|d| d.join("diskard").join("config.toml"))
59    }
60
61    /// Load config from the default path, falling back to defaults if not found.
62    pub fn load() -> Result<Self> {
63        match Self::path() {
64            Some(path) if path.exists() => Self::load_from(&path),
65            _ => Ok(Self::default()),
66        }
67    }
68
69    /// Load config from a specific path.
70    pub fn load_from(path: &Path) -> Result<Self> {
71        let content = std::fs::read_to_string(path).map_err(|e| Error::io(path, e))?;
72        let config: Config = toml::from_str(&content)?;
73        Ok(config)
74    }
75
76    /// Write default config to the standard path.
77    pub fn init() -> Result<PathBuf> {
78        let path = Self::path()
79            .ok_or_else(|| Error::Config("Cannot determine config directory".into()))?;
80        if let Some(parent) = path.parent() {
81            std::fs::create_dir_all(parent).map_err(|e| Error::io(parent, e))?;
82        }
83        let content =
84            toml::to_string_pretty(&Config::default()).map_err(|e| Error::Config(e.to_string()))?;
85        std::fs::write(&path, content).map_err(|e| Error::io(&path, e))?;
86        Ok(path)
87    }
88
89    /// Parse the risk tolerance string into a RiskLevel.
90    pub fn max_risk(&self) -> RiskLevel {
91        match self.defaults.risk_tolerance.to_lowercase().as_str() {
92            "safe" => RiskLevel::Safe,
93            "risky" => RiskLevel::Risky,
94            _ => RiskLevel::Moderate,
95        }
96    }
97
98    /// Check if a recognizer is enabled.
99    pub fn is_recognizer_enabled(&self, id: &str) -> bool {
100        !self.recognizers.disabled.contains(id)
101    }
102
103    /// Check if a path is ignored.
104    pub fn is_path_ignored(&self, path: &Path) -> bool {
105        self.ignore
106            .paths
107            .iter()
108            .any(|ignored| path.starts_with(ignored))
109    }
110}