Skip to main content

systemprompt_logging/services/cli/
module.rs

1use 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}