Skip to main content

systemprompt_logging/services/cli/
summary.rs

1#![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}