Skip to main content

systemprompt_logging/services/cli/
service.rs

1use std::io::Write;
2use std::time::Duration;
3
4use crate::models::LoggingError;
5pub(super) type Result<T> = std::result::Result<T, LoggingError>;
6use indicatif::{ProgressBar, ProgressStyle};
7use serde::Serialize;
8use systemprompt_traits::LogEventLevel;
9
10use super::display::{CollectionDisplay, Display, DisplayUtils};
11use super::module::{BatchModuleOperations, ModuleDisplay, ModuleInstall, ModuleUpdate};
12use super::output::publish_log;
13use super::prompts::{PromptBuilder, Prompts};
14use super::summary::{OperationResult, ProgressSummary, ValidationSummary};
15use super::theme::{EmphasisType, ItemStatus, MessageLevel, ModuleType, Theme};
16
17#[derive(Copy, Clone, Debug)]
18pub struct CliService;
19
20impl CliService {
21    pub fn success(message: &str) {
22        publish_log(LogEventLevel::Info, "cli", message);
23        DisplayUtils::message(MessageLevel::Success, message);
24    }
25
26    pub fn warning(message: &str) {
27        publish_log(LogEventLevel::Warn, "cli", message);
28        DisplayUtils::message(MessageLevel::Warning, message);
29    }
30
31    pub fn error(message: &str) {
32        publish_log(LogEventLevel::Error, "cli", message);
33        DisplayUtils::message(MessageLevel::Error, message);
34    }
35
36    pub fn info(message: &str) {
37        publish_log(LogEventLevel::Info, "cli", message);
38        DisplayUtils::message(MessageLevel::Info, message);
39    }
40
41    pub fn debug(message: &str) {
42        let debug_msg = format!("DEBUG: {message}");
43        publish_log(LogEventLevel::Debug, "cli", &debug_msg);
44        DisplayUtils::message(MessageLevel::Info, &debug_msg);
45    }
46
47    pub fn verbose(message: &str) {
48        publish_log(LogEventLevel::Debug, "cli", message);
49        DisplayUtils::message(MessageLevel::Info, message);
50    }
51
52    pub fn fatal(message: &str, exit_code: i32) -> ! {
53        let fatal_msg = format!("FATAL: {message}");
54        DisplayUtils::message(MessageLevel::Error, &fatal_msg);
55        std::process::exit(exit_code);
56    }
57
58    pub fn section(title: &str) {
59        DisplayUtils::section_header(title);
60    }
61
62    pub fn subsection(title: &str) {
63        DisplayUtils::subsection_header(title);
64    }
65
66    pub fn clear_screen() {
67        let mut stderr = std::io::stderr();
68        if let Err(e) = write!(stderr, "\x1B[2J\x1B[1;1H") {
69            tracing::debug!(error = %e, "cli clear_screen write failed");
70        }
71    }
72
73    pub fn output(content: &str) {
74        let mut stdout = std::io::stdout();
75        if let Err(e) = writeln!(stdout, "{content}") {
76            tracing::debug!(error = %e, "cli output write failed");
77        }
78    }
79
80    pub fn json<T: Serialize>(value: &T) {
81        match serde_json::to_string_pretty(value) {
82            Ok(json) => {
83                let mut stdout = std::io::stdout();
84                if let Err(e) = writeln!(stdout, "{json}") {
85                    tracing::debug!(error = %e, "cli json write failed");
86                }
87            },
88            Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
89        }
90    }
91
92    pub fn json_compact<T: Serialize>(value: &T) {
93        match serde_json::to_string(value) {
94            Ok(json) => {
95                let mut stdout = std::io::stdout();
96                if let Err(e) = writeln!(stdout, "{json}") {
97                    tracing::debug!(error = %e, "cli json_compact write failed");
98                }
99            },
100            Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
101        }
102    }
103
104    pub fn yaml<T: Serialize>(value: &T) {
105        match serde_yaml::to_string(value) {
106            Ok(yaml) => {
107                let mut stdout = std::io::stdout();
108                if let Err(e) = write!(stdout, "{yaml}") {
109                    tracing::debug!(error = %e, "cli yaml write failed");
110                }
111            },
112            Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
113        }
114    }
115
116    pub fn key_value(label: &str, value: &str) {
117        let mut stderr = std::io::stderr();
118        if let Err(e) = writeln!(
119            stderr,
120            "{}: {}",
121            Theme::color(label, EmphasisType::Bold),
122            Theme::color(value, EmphasisType::Highlight)
123        ) {
124            tracing::debug!(error = %e, "cli key_value write failed");
125        }
126    }
127
128    pub fn status_line(label: &str, value: &str, status: ItemStatus) {
129        let mut stderr = std::io::stderr();
130        if let Err(e) = writeln!(
131            stderr,
132            "{} {}: {}",
133            Theme::icon(status),
134            Theme::color(label, EmphasisType::Bold),
135            Theme::color(value, status)
136        ) {
137            tracing::debug!(error = %e, "cli status_line write failed");
138        }
139    }
140
141    pub fn spinner(message: &str) -> ProgressBar {
142        let pb = ProgressBar::new_spinner();
143        pb.set_style(
144            ProgressStyle::default_spinner()
145                .template("{spinner:.cyan} {msg}")
146                .unwrap_or_else(|_| ProgressStyle::default_spinner()),
147        );
148        pb.set_message(message.to_owned());
149        pb.enable_steady_tick(Duration::from_millis(100));
150        pb
151    }
152
153    pub fn progress_bar(total: u64) -> ProgressBar {
154        let pb = ProgressBar::new(total);
155        pb.set_style(
156            ProgressStyle::default_bar()
157                .template(
158                    "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
159                )
160                .unwrap_or_else(|_| ProgressStyle::default_bar())
161                .progress_chars("#>-"),
162        );
163        pb
164    }
165
166    pub fn timed<F, R>(label: &str, f: F) -> R
167    where
168        F: FnOnce() -> R,
169    {
170        let start = std::time::Instant::now();
171        let result = f();
172        let duration = start.elapsed();
173        let duration_secs = duration.as_secs_f64();
174        let info_msg = format!("{label} completed in {duration_secs:.2}s");
175        Self::info(&info_msg);
176        result
177    }
178
179    pub fn prompt_schemas(module_name: &str, schemas: &[(String, String)]) -> Result<bool> {
180        ModuleDisplay::prompt_apply_schemas(module_name, schemas)
181    }
182
183    pub fn prompt_seeds(module_name: &str, seeds: &[(String, String)]) -> Result<bool> {
184        ModuleDisplay::prompt_apply_seeds(module_name, seeds)
185    }
186
187    pub fn prompt_install(modules: &[String]) -> Result<bool> {
188        Prompts::confirm_install(modules)
189    }
190
191    pub fn prompt_update(updates: &[(String, String, String)]) -> Result<bool> {
192        Prompts::confirm_update(updates)
193    }
194
195    pub fn confirm(question: &str) -> Result<bool> {
196        Prompts::confirm(question, false)
197    }
198
199    pub fn confirm_default_yes(question: &str) -> Result<bool> {
200        Prompts::confirm(question, true)
201    }
202
203    pub fn display_validation_summary(summary: &ValidationSummary) {
204        summary.display();
205    }
206
207    pub fn display_result(result: &OperationResult) {
208        result.display();
209    }
210
211    pub fn display_progress(progress: &ProgressSummary) {
212        progress.display();
213    }
214
215    pub fn prompt_builder(message: &str) -> PromptBuilder {
216        PromptBuilder::new(message)
217    }
218
219    pub fn collection<T: Display>(title: &str, items: Vec<T>) -> CollectionDisplay<T> {
220        CollectionDisplay::new(title, items)
221    }
222
223    pub fn module_status(module_name: &str, message: &str) {
224        DisplayUtils::module_status(module_name, message);
225    }
226
227    pub fn relationship(from: &str, to: &str, status: ItemStatus, module_type: ModuleType) {
228        DisplayUtils::relationship(module_type, from, to, status);
229    }
230
231    pub fn item(status: ItemStatus, name: &str, detail: Option<&str>) {
232        DisplayUtils::item(status, name, detail);
233    }
234
235    pub fn batch_install(modules: &[ModuleInstall]) -> Result<bool> {
236        BatchModuleOperations::prompt_install_multiple(modules)
237    }
238
239    pub fn batch_update(updates: &[ModuleUpdate]) -> Result<bool> {
240        BatchModuleOperations::prompt_update_multiple(updates)
241    }
242
243    pub fn table(headers: &[&str], rows: &[Vec<String>]) {
244        super::table::render_table(headers, rows);
245    }
246}