cargo_autodd/
lib.rs

1pub mod config;
2pub mod dependency_manager;
3pub mod models;
4pub mod utils;
5
6use std::path::PathBuf;
7
8use anyhow::Result;
9pub use config::Config;
10
11pub struct CargoAutodd {
12    #[allow(dead_code)]
13    project_root: PathBuf,
14    analyzer: dependency_manager::DependencyAnalyzer,
15    updater: dependency_manager::DependencyUpdater,
16    reporter: dependency_manager::DependencyReporter,
17    config: Config,
18    debug: bool,
19    dry_run: bool,
20}
21
22impl CargoAutodd {
23    pub fn new(project_root: PathBuf) -> Self {
24        let config = Config::load_default(&project_root).unwrap_or_default();
25        Self {
26            project_root: project_root.clone(),
27            analyzer: dependency_manager::DependencyAnalyzer::new(project_root.clone()),
28            updater: dependency_manager::DependencyUpdater::new(project_root.clone()),
29            reporter: dependency_manager::DependencyReporter::new(project_root),
30            config,
31            debug: false,
32            dry_run: false,
33        }
34    }
35
36    pub fn with_debug(project_root: PathBuf, debug: bool) -> Self {
37        let config = Config::load_default(&project_root).unwrap_or_default();
38        Self {
39            project_root: project_root.clone(),
40            analyzer: dependency_manager::DependencyAnalyzer::with_debug(
41                project_root.clone(),
42                debug,
43            ),
44            updater: dependency_manager::DependencyUpdater::new(project_root.clone()),
45            reporter: dependency_manager::DependencyReporter::new(project_root),
46            config,
47            debug,
48            dry_run: false,
49        }
50    }
51
52    pub fn with_options(project_root: PathBuf, debug: bool, dry_run: bool, config: Config) -> Self {
53        Self {
54            project_root: project_root.clone(),
55            analyzer: dependency_manager::DependencyAnalyzer::with_debug(
56                project_root.clone(),
57                debug,
58            ),
59            updater: dependency_manager::DependencyUpdater::new(project_root.clone()),
60            reporter: dependency_manager::DependencyReporter::new(project_root),
61            config,
62            debug,
63            dry_run,
64        }
65    }
66
67    pub fn analyze_and_update(&self) -> Result<()> {
68        if self.debug {
69            println!("šŸ” Starting dependency analysis in debug mode...");
70        }
71        if self.dry_run {
72            println!("šŸ” Running in dry-run mode (no changes will be made)...");
73        }
74
75        println!("šŸ” Analyzing project dependencies...");
76        let mut crate_refs = self.analyzer.analyze_dependencies()?;
77
78        // Apply config exclusions
79        crate_refs.retain(|name, _| !self.config.should_exclude(name));
80
81        if self.dry_run {
82            self.print_dry_run_summary(&crate_refs)?;
83            return Ok(());
84        }
85
86        if self.debug {
87            println!("\nšŸ“ Updating Cargo.toml with found dependencies...");
88        }
89        println!("šŸ“ Updating Cargo.toml...");
90        self.updater.update_cargo_toml(&crate_refs)?;
91
92        println!("āœ… Dependencies updated successfully!");
93        Ok(())
94    }
95
96    fn print_dry_run_summary(
97        &self,
98        crate_refs: &std::collections::HashMap<String, models::CrateReference>,
99    ) -> Result<()> {
100        println!("\nšŸ“‹ Dry-run summary:");
101        println!("==================");
102
103        let (regular, dev): (Vec<_>, Vec<_>) = crate_refs
104            .iter()
105            .partition(|(_, crate_ref)| !crate_ref.is_dev_dependency);
106
107        if !regular.is_empty() {
108            println!("\n[dependencies] would add:");
109            for (name, crate_ref) in regular {
110                if crate_ref.is_path_dependency {
111                    println!(
112                        "  {} = {{ path = \"{}\" }}",
113                        name,
114                        crate_ref.path.as_ref().unwrap_or(&"?".to_string())
115                    );
116                } else {
117                    println!("  {} = \"<latest>\"", name);
118                }
119            }
120        }
121
122        if !dev.is_empty() {
123            println!("\n[dev-dependencies] would add:");
124            for (name, crate_ref) in dev {
125                if crate_ref.is_path_dependency {
126                    println!(
127                        "  {} = {{ path = \"{}\" }}",
128                        name,
129                        crate_ref.path.as_ref().unwrap_or(&"?".to_string())
130                    );
131                } else {
132                    println!("  {} = \"<latest>\"", name);
133                }
134            }
135        }
136
137        // Show config exclusions
138        if !self.config.exclude.is_empty() {
139            println!("\nExcluded by config:");
140            for name in &self.config.exclude {
141                println!("  - {}", name);
142            }
143        }
144
145        println!("\nāœ… No changes were made (dry-run mode)");
146        Ok(())
147    }
148
149    pub fn update_dependencies(&self) -> Result<()> {
150        println!("šŸ” Checking for dependency updates...");
151        let crate_refs = self.analyzer.analyze_dependencies()?;
152        self.updater.update_cargo_toml(&crate_refs)?;
153        println!("\nšŸ” Verifying dependencies...");
154        self.updater.verify_dependencies()?;
155        println!("āœ… Dependencies updated successfully!");
156        Ok(())
157    }
158
159    pub fn generate_report(&self) -> Result<()> {
160        println!("šŸ“Š Analyzing dependency usage...");
161        let crate_refs = self.analyzer.analyze_dependencies()?;
162        self.reporter.generate_dependency_report(&crate_refs)
163    }
164
165    pub fn check_security(&self) -> Result<()> {
166        println!("šŸ”’ Running security check...");
167        self.reporter.generate_security_report()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use std::fs::File;
175    use std::io::Write;
176    use tempfile::TempDir;
177
178    fn create_test_environment() -> Result<TempDir> {
179        let temp_dir = TempDir::new()?;
180
181        // Create Cargo.toml
182        let cargo_toml = temp_dir.path().join("Cargo.toml");
183        let content = r#"
184[package]
185name = "test-package"
186version = "0.1.0"
187edition = "2021"
188
189[dependencies]
190serde = "1.0"
191"#;
192        let mut file = File::create(&cargo_toml)?;
193        writeln!(file, "{}", content)?;
194
195        // Create src directory and main.rs
196        std::fs::create_dir(temp_dir.path().join("src"))?;
197        let main_rs = temp_dir.path().join("src/main.rs");
198        let content = r#"
199use serde;
200use tokio;
201"#;
202        let mut file = File::create(main_rs)?;
203        writeln!(file, "{}", content)?;
204
205        Ok(temp_dir)
206    }
207
208    #[test]
209    fn test_analyze_and_update() -> Result<()> {
210        let temp_dir = create_test_environment()?;
211        let autodd = CargoAutodd::new(temp_dir.path().to_path_buf());
212        autodd.analyze_and_update()?;
213        Ok(())
214    }
215
216    #[test]
217    fn test_generate_report() -> Result<()> {
218        let temp_dir = create_test_environment()?;
219        let autodd = CargoAutodd::new(temp_dir.path().to_path_buf());
220        autodd.generate_report()?;
221        Ok(())
222    }
223
224    #[test]
225    fn test_check_security() -> Result<()> {
226        let temp_dir = create_test_environment()?;
227        let autodd = CargoAutodd::new(temp_dir.path().to_path_buf());
228        autodd.check_security()?;
229        Ok(())
230    }
231}