systemprompt_logging/services/cli/
service.rs1use std::io::Write;
12use std::time::Duration;
13
14use indicatif::{ProgressBar, ProgressStyle};
15use serde::Serialize;
16
17use super::display;
18use super::output::OutputState;
19use super::theme::{EmphasisType, ItemStatus, MessageLevel, Theme};
20
21#[derive(Copy, Clone, Debug)]
22pub struct CliService;
23
24impl CliService {
25 pub fn success(message: &str) {
26 display::message(MessageLevel::Success, message);
27 }
28
29 pub fn warning(message: &str) {
30 display::message(MessageLevel::Warning, message);
31 }
32
33 pub fn error(message: &str) {
34 display::message(MessageLevel::Error, message);
35 }
36
37 pub fn info(message: &str) {
38 display::message(MessageLevel::Info, message);
39 }
40
41 pub fn debug(message: &str) {
42 let debug_msg = format!("DEBUG: {message}");
43 display::message(MessageLevel::Info, &debug_msg);
44 }
45
46 pub fn verbose(message: &str) {
47 display::message(MessageLevel::Info, message);
48 }
49
50 pub fn fatal(message: &str, exit_code: i32) -> ! {
51 let fatal_msg = format!("FATAL: {message}");
52 display::message(MessageLevel::Error, &fatal_msg);
53 std::process::exit(exit_code);
54 }
55
56 pub fn section(title: &str) {
57 display::section_header(title);
58 }
59
60 pub fn subsection(title: &str) {
61 display::subsection_header(title);
62 }
63
64 pub fn clear_screen() {
65 let mut stderr = std::io::stderr();
66 if let Err(e) = write!(stderr, "\x1B[2J\x1B[1;1H") {
67 tracing::debug!(error = %e, "cli clear_screen write failed");
68 }
69 }
70
71 pub fn output(content: &str) {
72 let mut stdout = std::io::stdout();
73 if let Err(e) = writeln!(stdout, "{content}") {
74 tracing::debug!(error = %e, "cli output write failed");
75 }
76 }
77
78 pub fn json_string<T: Serialize + ?Sized>(value: &T) -> Result<String, serde_json::Error> {
79 serde_json::to_string_pretty(value)
80 }
81
82 pub fn json<T: Serialize>(value: &T) {
83 OutputState::current().mark_structured_emitted();
84 match Self::json_string(value) {
85 Ok(json) => {
86 let mut stdout = std::io::stdout();
87 if let Err(e) = writeln!(stdout, "{json}") {
88 tracing::debug!(error = %e, "cli json write failed");
89 }
90 },
91 Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
92 }
93 }
94
95 pub fn json_compact<T: Serialize>(value: &T) {
96 OutputState::current().mark_structured_emitted();
97 match serde_json::to_string(value) {
98 Ok(json) => {
99 let mut stdout = std::io::stdout();
100 if let Err(e) = writeln!(stdout, "{json}") {
101 tracing::debug!(error = %e, "cli json_compact write failed");
102 }
103 },
104 Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
105 }
106 }
107
108 pub fn yaml<T: Serialize>(value: &T) {
109 OutputState::current().mark_structured_emitted();
110 match serde_yaml::to_string(value) {
111 Ok(yaml) => {
112 let mut stdout = std::io::stdout();
113 if let Err(e) = write!(stdout, "{yaml}") {
114 tracing::debug!(error = %e, "cli yaml write failed");
115 }
116 },
117 Err(e) => Self::error(&format!("Failed to format log entry: {e}")),
118 }
119 }
120
121 pub fn key_value(label: &str, value: &str) {
122 let mut stderr = std::io::stderr();
123 if let Err(e) = writeln!(
124 stderr,
125 "{}: {}",
126 Theme::color(label, EmphasisType::Bold),
127 Theme::color(value, EmphasisType::Highlight)
128 ) {
129 tracing::debug!(error = %e, "cli key_value write failed");
130 }
131 }
132
133 pub fn status_line(label: &str, value: &str, status: ItemStatus) {
134 let mut stderr = std::io::stderr();
135 if let Err(e) = writeln!(
136 stderr,
137 "{} {}: {}",
138 Theme::icon(status),
139 Theme::color(label, EmphasisType::Bold),
140 Theme::color(value, status)
141 ) {
142 tracing::debug!(error = %e, "cli status_line write failed");
143 }
144 }
145
146 pub fn spinner(message: &str) -> ProgressBar {
147 let pb = ProgressBar::new_spinner();
148 pb.set_style(
149 ProgressStyle::default_spinner()
150 .template("{spinner:.cyan} {msg}")
151 .unwrap_or_else(|_| ProgressStyle::default_spinner()),
152 );
153 pb.set_message(message.to_owned());
154 pb.enable_steady_tick(Duration::from_millis(100));
155 pb
156 }
157
158 pub fn progress_bar(total: u64) -> ProgressBar {
159 let pb = ProgressBar::new(total);
160 pb.set_style(
161 ProgressStyle::default_bar()
162 .template(
163 "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}",
164 )
165 .unwrap_or_else(|_| ProgressStyle::default_bar())
166 .progress_chars("#>-"),
167 );
168 pb
169 }
170
171 pub fn table(headers: &[&str], rows: &[Vec<String>]) {
172 super::table::render_table(headers, rows);
173 }
174}