systemprompt_logging/services/cli/
summary.rs1#![allow(clippy::print_stdout)]
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 println!(
139 "\n{} {} active modules ready",
140 Theme::icon(MessageLevel::Success),
141 Theme::color(&total_active.to_string(), EmphasisType::Bold)
142 );
143 }
144 }
145}
146
147impl Default for ValidationSummary {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153#[derive(Debug, Clone)]
154pub struct OperationResult {
155 pub operation: String,
156 pub success: bool,
157 pub message: Option<String>,
158 pub details: Vec<String>,
159}
160
161impl OperationResult {
162 pub fn success(operation: impl Into<String>) -> Self {
163 Self {
164 operation: operation.into(),
165 success: true,
166 message: None,
167 details: Vec::new(),
168 }
169 }
170
171 pub fn failure(operation: impl Into<String>, message: impl Into<String>) -> Self {
172 Self {
173 operation: operation.into(),
174 success: false,
175 message: Some(message.into()),
176 details: Vec::new(),
177 }
178 }
179
180 #[must_use]
181 pub fn with_message(mut self, message: impl Into<String>) -> Self {
182 self.message = Some(message.into());
183 self
184 }
185
186 #[must_use]
187 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
188 self.details.push(detail.into());
189 self
190 }
191
192 #[must_use]
193 pub fn with_details(mut self, details: Vec<String>) -> Self {
194 self.details = details;
195 self
196 }
197}
198
199impl Display for OperationResult {
200 fn display(&self) {
201 let level = if self.success {
202 MessageLevel::Success
203 } else {
204 MessageLevel::Error
205 };
206
207 let base_message = self.message.as_ref().map_or_else(
208 || self.operation.clone(),
209 |msg| format!("{}: {msg}", self.operation),
210 );
211
212 DisplayUtils::message(level, &base_message);
213
214 for detail in &self.details {
215 let colored = Theme::color(detail, EmphasisType::Dim);
216 println!(" • {colored}");
217 }
218 }
219}
220
221#[derive(Debug)]
222pub struct ProgressSummary {
223 pub total: usize,
224 pub completed: usize,
225 pub failed: usize,
226 pub operation_name: String,
227}
228
229impl ProgressSummary {
230 pub fn new(operation_name: impl Into<String>, total: usize) -> Self {
231 Self {
232 total,
233 completed: 0,
234 failed: 0,
235 operation_name: operation_name.into(),
236 }
237 }
238
239 pub fn add_success(&mut self) {
240 self.completed += 1;
241 }
242
243 pub fn add_failure(&mut self) {
244 self.failed += 1;
245 }
246
247 pub const fn is_complete(&self) -> bool {
248 self.completed + self.failed >= self.total
249 }
250
251 pub fn success_rate(&self) -> f64 {
252 if self.total == 0 {
253 1.0
254 } else {
255 let completed = u32::try_from(self.completed).unwrap_or(u32::MAX);
256 let total = u32::try_from(self.total).unwrap_or(u32::MAX);
257 f64::from(completed) / f64::from(total)
258 }
259 }
260}
261
262impl Display for ProgressSummary {
263 fn display(&self) {
264 let status = if self.failed == 0 {
265 MessageLevel::Success
266 } else if self.completed > 0 {
267 MessageLevel::Warning
268 } else {
269 MessageLevel::Error
270 };
271
272 let message = if self.failed > 0 {
273 format!(
274 "{}: {}/{} completed, {} failed",
275 self.operation_name, self.completed, self.total, self.failed
276 )
277 } else {
278 format!(
279 "{}: {}/{} completed",
280 self.operation_name, self.completed, self.total
281 )
282 };
283
284 DisplayUtils::message(status, &message);
285 }
286}