systemprompt_logging/services/cli/
summary.rs1use std::io::Write;
2
3use crate::services::cli::display::{CollectionDisplay, Display, DisplayUtils, StatusDisplay};
4use crate::services::cli::theme::{EmphasisType, ItemStatus, MessageLevel, Theme};
5#[derive(Debug)]
6pub struct ValidationSummary {
7 pub valid: Vec<(String, String)>,
8 pub installed: Vec<String>,
9 pub updated: Vec<String>,
10 pub schemas_applied: Vec<String>,
11 pub seeds_applied: Vec<String>,
12 pub disabled: Vec<String>,
13}
14
15impl ValidationSummary {
16 pub const fn new() -> Self {
17 Self {
18 valid: Vec::new(),
19 installed: Vec::new(),
20 updated: Vec::new(),
21 schemas_applied: Vec::new(),
22 seeds_applied: Vec::new(),
23 disabled: Vec::new(),
24 }
25 }
26
27 pub fn add_valid(&mut self, name: String, version: String) {
28 self.valid.push((name, version));
29 }
30
31 pub fn add_installed(&mut self, name: String) {
32 self.installed.push(name);
33 }
34
35 pub fn add_updated(&mut self, name: String) {
36 self.updated.push(name);
37 }
38
39 pub fn add_schema_applied(&mut self, name: String) {
40 self.schemas_applied.push(name);
41 }
42
43 pub fn add_seed_applied(&mut self, name: String) {
44 self.seeds_applied.push(name);
45 }
46
47 pub fn add_disabled(&mut self, name: String) {
48 self.disabled.push(name);
49 }
50
51 pub fn total_active(&self) -> usize {
52 self.valid.len() + self.installed.len() + self.updated.len()
53 }
54
55 pub fn has_changes(&self) -> bool {
56 !self.installed.is_empty()
57 || !self.updated.is_empty()
58 || !self.schemas_applied.is_empty()
59 || !self.seeds_applied.is_empty()
60 }
61}
62
63impl Display for ValidationSummary {
64 fn display(&self) {
65 DisplayUtils::section_header("Module Validation Summary");
66
67 if !self.valid.is_empty() {
68 let displays: Vec<StatusDisplay> = self
69 .valid
70 .iter()
71 .map(|(name, version)| {
72 let detail = format!("v{version}");
73 StatusDisplay::new(ItemStatus::Valid, name).with_detail(detail)
74 })
75 .collect();
76
77 let collection = CollectionDisplay::new("Valid modules", displays);
78 collection.display();
79 }
80
81 if !self.installed.is_empty() {
82 let displays: Vec<StatusDisplay> = self
83 .installed
84 .iter()
85 .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
86 .collect();
87
88 let collection = CollectionDisplay::new("Newly installed", displays);
89 collection.display();
90 }
91
92 if !self.updated.is_empty() {
93 let displays: Vec<StatusDisplay> = self
94 .updated
95 .iter()
96 .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
97 .collect();
98
99 let collection = CollectionDisplay::new("Updated modules", displays);
100 collection.display();
101 }
102
103 if !self.schemas_applied.is_empty() {
104 let displays: Vec<StatusDisplay> = self
105 .schemas_applied
106 .iter()
107 .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
108 .collect();
109
110 let collection = CollectionDisplay::new("Schemas applied", displays);
111 collection.display();
112 }
113
114 if !self.seeds_applied.is_empty() {
115 let displays: Vec<StatusDisplay> = self
116 .seeds_applied
117 .iter()
118 .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
119 .collect();
120
121 let collection = CollectionDisplay::new("Seeds applied", displays);
122 collection.display();
123 }
124
125 if !self.disabled.is_empty() {
126 let displays: Vec<StatusDisplay> = self
127 .disabled
128 .iter()
129 .map(|name| StatusDisplay::new(ItemStatus::Disabled, name).with_detail("disabled"))
130 .collect();
131
132 let collection = CollectionDisplay::new("Disabled modules", displays);
133 collection.display();
134 }
135
136 let total_active = self.total_active();
137 if total_active > 0 {
138 let mut stdout = std::io::stdout();
139 writeln!(
140 stdout,
141 "\n{} {} active modules ready",
142 Theme::icon(MessageLevel::Success),
143 Theme::color(&total_active.to_string(), EmphasisType::Bold)
144 )
145 .ok();
146 }
147 }
148}
149
150impl Default for ValidationSummary {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156#[derive(Debug, Clone)]
157pub struct OperationResult {
158 pub operation: String,
159 pub success: bool,
160 pub message: Option<String>,
161 pub details: Vec<String>,
162}
163
164impl OperationResult {
165 pub fn success(operation: impl Into<String>) -> Self {
166 Self {
167 operation: operation.into(),
168 success: true,
169 message: None,
170 details: Vec::new(),
171 }
172 }
173
174 pub fn failure(operation: impl Into<String>, message: impl Into<String>) -> Self {
175 Self {
176 operation: operation.into(),
177 success: false,
178 message: Some(message.into()),
179 details: Vec::new(),
180 }
181 }
182
183 #[must_use]
184 pub fn with_message(mut self, message: impl Into<String>) -> Self {
185 self.message = Some(message.into());
186 self
187 }
188
189 #[must_use]
190 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
191 self.details.push(detail.into());
192 self
193 }
194
195 #[must_use]
196 pub fn with_details(mut self, details: Vec<String>) -> Self {
197 self.details = details;
198 self
199 }
200}
201
202impl Display for OperationResult {
203 fn display(&self) {
204 let level = if self.success {
205 MessageLevel::Success
206 } else {
207 MessageLevel::Error
208 };
209
210 let base_message = self.message.as_ref().map_or_else(
211 || self.operation.clone(),
212 |msg| format!("{}: {msg}", self.operation),
213 );
214
215 DisplayUtils::message(level, &base_message);
216
217 for detail in &self.details {
218 let colored = Theme::color(detail, EmphasisType::Dim);
219 let mut stdout = std::io::stdout();
220 writeln!(stdout, " \u{2022} {colored}").ok();
221 }
222 }
223}
224
225#[derive(Debug)]
226pub struct ProgressSummary {
227 pub total: usize,
228 pub completed: usize,
229 pub failed: usize,
230 pub operation_name: String,
231}
232
233impl ProgressSummary {
234 pub fn new(operation_name: impl Into<String>, total: usize) -> Self {
235 Self {
236 total,
237 completed: 0,
238 failed: 0,
239 operation_name: operation_name.into(),
240 }
241 }
242
243 pub fn add_success(&mut self) {
244 self.completed += 1;
245 }
246
247 pub fn add_failure(&mut self) {
248 self.failed += 1;
249 }
250
251 pub const fn is_complete(&self) -> bool {
252 self.completed + self.failed >= self.total
253 }
254
255 pub fn success_rate(&self) -> f64 {
256 if self.total == 0 {
257 1.0
258 } else {
259 let completed = u32::try_from(self.completed).unwrap_or(u32::MAX);
260 let total = u32::try_from(self.total).unwrap_or(u32::MAX);
261 f64::from(completed) / f64::from(total)
262 }
263 }
264}
265
266impl Display for ProgressSummary {
267 fn display(&self) {
268 let status = if self.failed == 0 {
269 MessageLevel::Success
270 } else if self.completed > 0 {
271 MessageLevel::Warning
272 } else {
273 MessageLevel::Error
274 };
275
276 let message = if self.failed > 0 {
277 format!(
278 "{}: {}/{} completed, {} failed",
279 self.operation_name, self.completed, self.total, self.failed
280 )
281 } else {
282 format!(
283 "{}: {}/{} completed",
284 self.operation_name, self.completed, self.total
285 )
286 };
287
288 DisplayUtils::message(status, &message);
289 }
290}