garbage_code_hunter/debt_invoice/
display.rs1use super::cost_model::Invoice;
4use crate::common::i18n_ext::t;
5use colored::Colorize;
6
7pub fn format_terminal(invoice: &Invoice, lang: &str) -> String {
9 let mut out = String::new();
10
11 out.push_str(&format!(
12 "\n{}\n",
13 t(
14 lang,
15 "\u{1f4b8} 技术债账单",
16 "\u{1f4b8} Technical Debt Invoice"
17 )
18 .bold()
19 ));
20 out.push_str(&format!("{}\n\n", "\u{2501}".repeat(40)));
21
22 out.push_str(&format!(
24 " {:<30} {:>6} {:>10} {:>12}\n",
25 t(lang, "类别", "Category").bold(),
26 t(lang, "数量", "Count").bold(),
27 t(lang, "工时", "Hours").bold(),
28 t(lang, "成本 (USD)", "Cost (USD)").bold()
29 ));
30 out.push_str(&format!(" {}\n", "\u{2500}".repeat(62)));
31
32 for item in &invoice.items {
34 let cost_str = format!("${:.0}", item.estimated_cost);
35 out.push_str(&format!(
36 " {:<30} {:>6} {:>10.1} {:>12}\n",
37 item.category, item.count, item.estimated_hours, cost_str
38 ));
39 out.push_str(&format!(" {}\n", item.pain_description.dimmed()));
40 }
41
42 out.push_str(&format!(" {}\n", "\u{2500}".repeat(62)));
43
44 out.push_str(&format!(
46 " {:<30} {:>6} {:>10.1} {:>12}\n",
47 "TOTAL".bold(),
48 invoice.items.iter().map(|i| i.count).sum::<usize>(),
49 invoice.total_hours,
50 format!("${:.0}", invoice.total_cost).bold()
51 ));
52
53 out.push('\n');
54
55 out.push_str(&format!(
57 "{}\n",
58 t(lang, "\u{1f4c8} 利息", "\u{1f4c8} Interest").bold()
59 ));
60 out.push_str(&format!(
61 " {}: {:.0}%\n",
62 t(lang, "周利率", "Weekly interest rate"),
63 invoice.weekly_interest_rate
64 ));
65 let monthly_cost = invoice.total_cost * (1.0 + invoice.weekly_interest_rate / 100.0).powf(4.0)
66 - invoice.total_cost;
67 out.push_str(&format!(
68 " {}: ${:.0}\n",
69 t(lang, "月利息", "Monthly interest"),
70 monthly_cost
71 ));
72 let yearly_cost = invoice.total_cost * (1.0 + invoice.weekly_interest_rate / 100.0).powf(52.0)
73 - invoice.total_cost;
74 out.push_str(&format!(
75 " {}: ${:.0}\n",
76 t(lang, "年利息", "Yearly interest"),
77 yearly_cost
78 ));
79
80 out.push('\n');
81
82 out.push_str(&format!(
84 "{}\n",
85 t(lang, "\u{1f52e} 项目寿命", "\u{1f52e} Project Lifespan").bold()
86 ));
87 out.push_str(&format!(
88 " {}\n",
89 t(
90 lang,
91 "代码库变得无法维护的预估时间:",
92 "Estimated time before codebase becomes unmaintainable:"
93 )
94 ));
95 let lifespan_str = if invoice.project_lifespan_months >= 24 {
96 format!(
97 "{} {}",
98 invoice.project_lifespan_months,
99 t(lang, "个月(暂时安全)", "months (you're fine, for now)")
100 )
101 .green()
102 .to_string()
103 } else if invoice.project_lifespan_months >= 12 {
104 format!(
105 "{} {}",
106 invoice.project_lifespan_months,
107 t(
108 lang,
109 "个月(该重构了)",
110 "months (better start refactoring)"
111 )
112 )
113 .yellow()
114 .to_string()
115 } else {
116 format!(
117 "{} {}",
118 invoice.project_lifespan_months,
119 t(
120 lang,
121 "个月(红色警报 \u{1f6a8})",
122 "months (CODE RED \u{1f6a8})"
123 )
124 )
125 .red()
126 .bold()
127 .to_string()
128 };
129 out.push_str(&format!(" {}\n", lifespan_str));
130
131 out
132}
133
134pub fn format_json(invoice: &Invoice) -> String {
136 serde_json::json!({
137 "items": invoice.items.iter().map(|item| {
138 serde_json::json!({
139 "category": item.category,
140 "count": item.count,
141 "estimated_hours": item.estimated_hours,
142 "estimated_cost": item.estimated_cost,
143 "pain_description": item.pain_description,
144 })
145 }).collect::<Vec<_>>(),
146 "total_hours": invoice.total_hours,
147 "total_cost": invoice.total_cost,
148 "project_lifespan_months": invoice.project_lifespan_months,
149 "weekly_interest_rate": invoice.weekly_interest_rate,
150 })
151 .to_string()
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::debt_invoice::cost_model::InvoiceItem;
158
159 fn make_invoice() -> Invoice {
160 Invoice {
161 items: vec![InvoiceItem {
162 category: "unwrap() abuse".to_string(),
163 count: 10,
164 estimated_hours: 20.0,
165 estimated_cost: 1500.0,
166 pain_description: "test".to_string(),
167 }],
168 total_hours: 20.0,
169 total_cost: 1500.0,
170 project_lifespan_months: 12,
171 weekly_interest_rate: 3.0,
172 }
173 }
174
175 #[test]
176 fn test_format_terminal() {
177 let invoice = make_invoice();
178 let out = format_terminal(&invoice, "en-US");
179 assert!(out.contains("Technical Debt Invoice"));
180 assert!(out.contains("unwrap() abuse"));
181 assert!(out.contains("$1500"));
182 }
183
184 #[test]
185 fn test_format_json() {
186 let invoice = make_invoice();
187 let json = format_json(&invoice);
188 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
189 assert_eq!(parsed["total_cost"], 1500.0);
190 }
191
192 #[test]
193 fn test_format_terminal_chinese() {
194 let invoice = make_invoice();
195 let out = format_terminal(&invoice, "zh-CN");
196 assert!(out.contains("技术债账单"));
197 assert!(out.contains("类别"));
198 assert!(out.contains("成本"));
199 assert!(out.contains("利息"));
200 assert!(out.contains("项目寿命"));
201 }
202
203 #[test]
204 fn test_format_terminal_zero_items() {
205 let invoice = Invoice {
206 items: vec![],
207 total_hours: 0.0,
208 total_cost: 0.0,
209 project_lifespan_months: 24,
210 weekly_interest_rate: 0.0,
211 };
212 let out = format_terminal(&invoice, "en-US");
213 assert!(out.contains("Technical Debt Invoice"));
214 assert!(out.contains("TOTAL"));
215 }
216
217 #[test]
218 fn test_format_terminal_critical_lifespan() {
219 let invoice = Invoice {
220 items: vec![],
221 total_hours: 0.0,
222 total_cost: 0.0,
223 project_lifespan_months: 6,
224 weekly_interest_rate: 0.0,
225 };
226 let out = format_terminal(&invoice, "en-US");
227 assert!(out.contains("CODE RED"));
228 }
229
230 #[test]
231 fn test_format_terminal_warning_lifespan() {
232 let invoice = Invoice {
233 items: vec![],
234 total_hours: 0.0,
235 total_cost: 0.0,
236 project_lifespan_months: 18,
237 weekly_interest_rate: 0.0,
238 };
239 let out = format_terminal(&invoice, "en-US");
240 assert!(out.contains("better start refactoring"));
241 }
242}