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