systemprompt_logging/services/cli/
service.rs1use 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}