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