light_fw_cli_core/
report.rs1use std::collections::BTreeMap;
2
3use serde_json::json;
4
5use crate::manifest::Dependency;
6use crate::resolve::RequiredComponent;
7use crate::scan::ScanResult;
8
9#[derive(Debug, Clone)]
11pub struct Issue {
12 pub message: String,
14}
15
16#[derive(Debug, Clone)]
18pub struct Recommendation {
19 pub name: String,
21 pub requirement: String,
23}
24
25#[derive(Debug)]
27pub struct ProjectReport {
28 pub scan: ScanResult,
30 pub installed: Vec<Dependency>,
32 pub missing: Vec<RequiredComponent>,
34 pub unused: Vec<Dependency>,
36 pub issues: Vec<Issue>,
38 pub recommendations: Vec<Recommendation>,
40}
41
42impl ProjectReport {
43 pub fn has_problems(&self) -> bool {
45 !self.missing.is_empty() || !self.issues.is_empty() || !self.unused.is_empty()
46 }
47
48 pub fn to_text(&self) -> String {
50 let mut out = String::new();
51 out.push_str("Отчёт о проекте Light\n\n");
52 out.push_str(&format!("Шаблонов: {}\n", self.scan.files.len()));
53
54 out.push_str("\nТеги:\n");
55 if self.scan.tags.is_empty() {
56 out.push_str(" (нет)\n");
57 }
58 for (name, count) in &self.scan.tags {
59 out.push_str(&format!(" {name}: {count}\n"));
60 }
61
62 out.push_str("\nАтрибуты:\n");
63 if self.scan.attributes.is_empty() {
64 out.push_str(" (нет)\n");
65 }
66 for (name, count) in &self.scan.attributes {
67 out.push_str(&format!(" {name}: {count}\n"));
68 }
69
70 out.push_str("\nУстановленные компоненты:\n");
71 if self.installed.is_empty() {
72 out.push_str(" (нет)\n");
73 }
74 for c in &self.installed {
75 match &c.version {
76 Some(v) => out.push_str(&format!(" {} {}\n", c.name, v)),
77 None => out.push_str(&format!(" {}\n", c.name)),
78 }
79 }
80
81 out.push_str("\nОтсутствующие компоненты:\n");
82 if self.missing.is_empty() {
83 out.push_str(" (нет)\n");
84 }
85 for c in &self.missing {
86 out.push_str(&format!(" {}\n", c.name));
87 }
88
89 out.push_str("\nНеиспользуемые компоненты:\n");
90 if self.unused.is_empty() {
91 out.push_str(" (нет)\n");
92 }
93 for c in &self.unused {
94 out.push_str(&format!(" {}\n", c.name));
95 }
96
97 if !self.issues.is_empty() {
98 out.push_str("\nПроблемы:\n");
99 for issue in &self.issues {
100 out.push_str(&format!(" {}\n", issue.message));
101 }
102 }
103
104 if !self.recommendations.is_empty() {
105 out.push_str("\nРекомендации:\n");
106 for r in &self.recommendations {
107 out.push_str(&format!(" добавить {}\n", r.requirement));
108 }
109 }
110
111 out
112 }
113
114 pub fn to_json(&self) -> serde_json::Value {
116 let mut tags: BTreeMap<String, usize> = BTreeMap::new();
117 for (k, v) in &self.scan.tags {
118 tags.insert(k.clone(), *v);
119 }
120 let mut attrs: BTreeMap<String, usize> = BTreeMap::new();
121 for (k, v) in &self.scan.attributes {
122 attrs.insert(k.clone(), *v);
123 }
124 json!({
125 "files": self.scan.files.len(),
126 "tags": tags,
127 "attributes": attrs,
128 "installed": self.installed.iter().map(|c| {
129 json!({ "name": c.name, "version": c.version })
130 }).collect::<Vec<_>>(),
131 "missing": self.missing.iter().map(|c| c.name.clone()).collect::<Vec<_>>(),
132 "unused": self.unused.iter().map(|c| c.name.clone()).collect::<Vec<_>>(),
133 "issues": self.issues.iter().map(|i| i.message.clone()).collect::<Vec<_>>(),
134 "recommendations": self.recommendations.iter().map(|r| {
135 json!({ "name": r.name, "requirement": r.requirement })
136 }).collect::<Vec<_>>(),
137 })
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::scan::ScanResult;
145
146 fn empty_report() -> ProjectReport {
147 ProjectReport {
148 scan: ScanResult::default(),
149 installed: Vec::new(),
150 missing: Vec::new(),
151 unused: Vec::new(),
152 issues: Vec::new(),
153 recommendations: Vec::new(),
154 }
155 }
156
157 #[test]
158 fn empty_report_has_no_problems() {
159 assert!(!empty_report().has_problems());
160 }
161
162 #[test]
163 fn report_with_missing_component_has_problems() {
164 let mut report = empty_report();
165 report.missing.push(RequiredComponent {
166 name: "light-fw-element-form".into(),
167 });
168 assert!(report.has_problems());
169 }
170
171 #[test]
172 fn json_contains_expected_keys() {
173 let value = empty_report().to_json();
174 assert_eq!(value["files"], 0);
175 assert!(value["tags"].is_object());
176 assert!(value["missing"].is_array());
177 }
178}