systemprompt_logging/services/cli/
module.rs1use std::io::Write;
9
10use crate::models::LoggingError;
11use crate::services::cli::display::{CollectionDisplay, Display, DisplayUtils, ModuleItemDisplay};
12use crate::services::cli::prompts::Prompts;
13use crate::services::cli::theme::{ItemStatus, MessageLevel, ModuleType, Theme};
14pub(crate) type Result<T> = std::result::Result<T, LoggingError>;
15
16fn stderr_writeln(args: std::fmt::Arguments<'_>) {
17 let mut out = std::io::stderr();
18 writeln!(out, "{args}").ok();
19}
20
21#[derive(Debug, Copy, Clone)]
22pub struct ModuleDisplay;
23
24impl ModuleDisplay {
25 pub fn missing_schemas(module_name: &str, schemas: &[(String, String)]) {
26 if schemas.is_empty() {
27 return;
28 }
29
30 DisplayUtils::module_status(
31 module_name,
32 &format!("{} schemas need application", schemas.len()),
33 );
34
35 DisplayUtils::count_message(MessageLevel::Warning, schemas.len(), "schemas");
36
37 let items: Vec<ModuleItemDisplay> = schemas
38 .iter()
39 .map(|(file, table)| {
40 ModuleItemDisplay::new(ModuleType::Schema, file, table, ItemStatus::Missing)
41 })
42 .collect();
43
44 for item in &items {
45 item.display();
46 }
47 }
48
49 pub fn missing_seeds(module_name: &str, seeds: &[(String, String)]) {
50 if seeds.is_empty() {
51 return;
52 }
53
54 DisplayUtils::module_status(
55 module_name,
56 &format!("{} seeds need application", seeds.len()),
57 );
58
59 DisplayUtils::count_message(MessageLevel::Warning, seeds.len(), "seeds");
60
61 let items: Vec<ModuleItemDisplay> = seeds
62 .iter()
63 .map(|(file, table)| {
64 ModuleItemDisplay::new(ModuleType::Seed, file, table, ItemStatus::Missing)
65 })
66 .collect();
67
68 for item in &items {
69 item.display();
70 }
71 }
72
73 pub fn prompt_apply_schemas(module_name: &str, schemas: &[(String, String)]) -> Result<bool> {
74 if schemas.is_empty() {
75 return Ok(false);
76 }
77
78 Self::missing_schemas(module_name, schemas);
79 stderr_writeln(format_args!(""));
80 Prompts::confirm_schemas()
81 }
82
83 pub fn prompt_apply_seeds(module_name: &str, seeds: &[(String, String)]) -> Result<bool> {
84 if seeds.is_empty() {
85 return Ok(false);
86 }
87
88 Self::missing_seeds(module_name, seeds);
89 stderr_writeln(format_args!(""));
90 Prompts::confirm_seeds()
91 }
92}
93
94#[derive(Debug, Clone)]
95pub struct ModuleUpdate {
96 pub name: String,
97 pub old_version: String,
98 pub new_version: String,
99 pub changes: Vec<String>,
100}
101
102impl ModuleUpdate {
103 pub fn new(
104 name: impl Into<String>,
105 old_version: impl Into<String>,
106 new_version: impl Into<String>,
107 ) -> Self {
108 Self {
109 name: name.into(),
110 old_version: old_version.into(),
111 new_version: new_version.into(),
112 changes: Vec::new(),
113 }
114 }
115
116 #[must_use]
117 pub fn with_change(mut self, change: impl Into<String>) -> Self {
118 self.changes.push(change.into());
119 self
120 }
121
122 #[must_use]
123 pub fn with_changes(mut self, changes: Vec<String>) -> Self {
124 self.changes = changes;
125 self
126 }
127}
128
129impl Display for ModuleUpdate {
130 fn display(&self) {
131 stderr_writeln(format_args!(
132 " {} {} {}",
133 Theme::icon(crate::services::cli::theme::ActionType::Update),
134 Theme::color(&self.name, crate::services::cli::theme::EmphasisType::Bold),
135 Theme::color(
136 &format!("{} \u{2192} {}", self.old_version, self.new_version),
137 crate::services::cli::theme::EmphasisType::Dim
138 )
139 ));
140
141 for change in &self.changes {
142 stderr_writeln(format_args!(
143 " \u{2022} {}",
144 Theme::color(change, crate::services::cli::theme::EmphasisType::Dim)
145 ));
146 }
147 }
148}
149
150#[derive(Debug, Clone)]
151pub struct ModuleInstall {
152 pub name: String,
153 pub version: String,
154 pub description: Option<String>,
155}
156
157impl ModuleInstall {
158 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
159 Self {
160 name: name.into(),
161 version: version.into(),
162 description: None,
163 }
164 }
165
166 #[must_use]
167 pub fn with_description(mut self, description: impl Into<String>) -> Self {
168 self.description = Some(description.into());
169 self
170 }
171}
172
173impl Display for ModuleInstall {
174 fn display(&self) {
175 let detail = self.description.as_ref().map_or_else(
176 || format!("v{}", self.version),
177 |desc| format!("v{} - {}", self.version, desc),
178 );
179
180 stderr_writeln(format_args!(
181 " {} {} {}",
182 Theme::icon(crate::services::cli::theme::ActionType::Install),
183 Theme::color(&self.name, crate::services::cli::theme::EmphasisType::Bold),
184 Theme::color(&detail, crate::services::cli::theme::EmphasisType::Dim)
185 ));
186 }
187}
188
189#[derive(Debug, Copy, Clone)]
190pub struct BatchModuleOperations;
191
192impl BatchModuleOperations {
193 pub fn prompt_install_multiple(modules: &[ModuleInstall]) -> Result<bool> {
194 if modules.is_empty() {
195 return Ok(false);
196 }
197
198 let collection =
199 CollectionDisplay::new("New modules available for installation", modules.to_vec());
200 collection.display();
201
202 Prompts::confirm("Install all these modules?", false)
203 }
204
205 pub fn prompt_update_multiple(updates: &[ModuleUpdate]) -> Result<bool> {
206 if updates.is_empty() {
207 return Ok(false);
208 }
209
210 let collection = CollectionDisplay::new("Module updates available", updates.to_vec());
211 collection.display();
212
213 Prompts::confirm("Update all these modules?", false)
214 }
215}