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