light_fw_cli_core/
project.rs1use std::collections::BTreeSet;
2use std::path::Path;
3
4use crate::error::Result;
5use crate::manifest::{collect_dependencies, read_cargo_toml};
6use crate::registry::ComponentRegistry;
7use crate::report::{Issue, ProjectReport, Recommendation};
8use crate::resolve::needed_components;
9use crate::scan::{ScanError, scan_templates};
10
11pub fn analyze_project(project_root: &Path) -> Result<ProjectReport> {
16 let cargo_path = project_root.join("Cargo.toml");
17 let cargo_toml = read_cargo_toml(&cargo_path)?;
18 let dependencies = collect_dependencies(&cargo_toml)?;
19
20 let scan = scan_templates(project_root)?;
21 let registry = ComponentRegistry::builtin();
22 let needed = needed_components(&scan, ®istry);
23
24 let installed: Vec<_> = dependencies
26 .iter()
27 .filter(|dep| registry.by_name(&dep.name).is_some())
28 .cloned()
29 .collect();
30 let installed_names: BTreeSet<&str> = installed.iter().map(|c| c.name.as_str()).collect();
31
32 let missing: Vec<_> = needed
33 .iter()
34 .filter(|n| !installed_names.contains(n.name.as_str()))
35 .cloned()
36 .collect();
37
38 let needed_names: BTreeSet<&str> = needed.iter().map(|n| n.name.as_str()).collect();
39 let unused: Vec<_> = installed
40 .iter()
41 .filter(|c| !needed_names.contains(c.name.as_str()))
42 .cloned()
43 .collect();
44
45 let issues: Vec<Issue> = scan
46 .errors
47 .iter()
48 .map(|ScanError { file, message }| Issue {
49 message: format!("{}: {message}", file.display()),
50 })
51 .collect();
52
53 let recommendations: Vec<Recommendation> = missing
54 .iter()
55 .map(|m| {
56 let requirement = registry
57 .by_name(&m.name)
58 .map(|c| c.requirement())
59 .unwrap_or_else(|| m.name.clone());
60 Recommendation {
61 name: m.name.clone(),
62 requirement,
63 }
64 })
65 .collect();
66
67 Ok(ProjectReport {
68 scan,
69 installed,
70 missing,
71 unused,
72 issues,
73 recommendations,
74 })
75}