cargo_autodd/
config.rs

1use anyhow::Result;
2use serde::Deserialize;
3use std::collections::HashSet;
4use std::fs;
5use std::path::Path;
6
7/// Configuration for cargo-autodd
8#[derive(Debug, Clone, Deserialize, Default)]
9pub struct Config {
10    /// Crates to exclude from analysis
11    #[serde(default)]
12    pub exclude: HashSet<String>,
13
14    /// Additional essential dependencies (never removed)
15    #[serde(default)]
16    pub essential: HashSet<String>,
17
18    /// Crates to always treat as dev-dependencies
19    #[serde(default)]
20    pub dev_only: HashSet<String>,
21
22    /// Whether to skip tests/ directory analysis
23    #[serde(default)]
24    pub skip_tests: bool,
25}
26
27impl Config {
28    /// Load config from a file path
29    pub fn load(path: &Path) -> Result<Self> {
30        if path.exists() {
31            let content = fs::read_to_string(path)?;
32            let config: Config = toml::from_str(&content)?;
33            Ok(config)
34        } else {
35            Ok(Self::default())
36        }
37    }
38
39    /// Load config from the default path (.cargo-autodd.toml)
40    pub fn load_default(project_root: &Path) -> Result<Self> {
41        let config_path = project_root.join(".cargo-autodd.toml");
42        Self::load(&config_path)
43    }
44
45    /// Check if a crate should be excluded
46    pub fn should_exclude(&self, crate_name: &str) -> bool {
47        self.exclude.contains(crate_name)
48    }
49
50    /// Check if a crate is essential (should never be removed)
51    pub fn is_essential(&self, crate_name: &str) -> bool {
52        self.essential.contains(crate_name)
53    }
54
55    /// Check if a crate should always be a dev-dependency
56    pub fn is_dev_only(&self, crate_name: &str) -> bool {
57        self.dev_only.contains(crate_name)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use std::io::Write;
65    use tempfile::TempDir;
66
67    #[test]
68    fn test_load_default_config() -> Result<()> {
69        let temp_dir = TempDir::new()?;
70        let config = Config::load_default(temp_dir.path())?;
71        assert!(config.exclude.is_empty());
72        assert!(config.essential.is_empty());
73        assert!(config.dev_only.is_empty());
74        assert!(!config.skip_tests);
75        Ok(())
76    }
77
78    #[test]
79    fn test_load_config_file() -> Result<()> {
80        let temp_dir = TempDir::new()?;
81        let config_path = temp_dir.path().join(".cargo-autodd.toml");
82
83        let config_content = r#"
84exclude = ["internal_crate", "another_crate"]
85essential = ["custom_essential"]
86dev_only = ["proptest", "criterion"]
87skip_tests = true
88"#;
89
90        let mut file = fs::File::create(&config_path)?;
91        write!(file, "{}", config_content)?;
92
93        let config = Config::load(&config_path)?;
94        assert!(config.should_exclude("internal_crate"));
95        assert!(config.should_exclude("another_crate"));
96        assert!(!config.should_exclude("external_crate"));
97        assert!(config.is_essential("custom_essential"));
98        assert!(config.is_dev_only("proptest"));
99        assert!(config.is_dev_only("criterion"));
100        assert!(config.skip_tests);
101
102        Ok(())
103    }
104
105    #[test]
106    fn test_partial_config() -> Result<()> {
107        let temp_dir = TempDir::new()?;
108        let config_path = temp_dir.path().join(".cargo-autodd.toml");
109
110        // Only specify exclude
111        let config_content = r#"
112exclude = ["internal_crate"]
113"#;
114
115        let mut file = fs::File::create(&config_path)?;
116        write!(file, "{}", config_content)?;
117
118        let config = Config::load(&config_path)?;
119        assert!(config.should_exclude("internal_crate"));
120        assert!(config.essential.is_empty());
121        assert!(config.dev_only.is_empty());
122        assert!(!config.skip_tests);
123
124        Ok(())
125    }
126}