metis_docs_cli/commands/
config.rs1use crate::workspace;
2use anyhow::Result;
3use clap::{Args, Subcommand};
4use metis_core::{domain::configuration::FlightLevelConfig, Database};
5
6#[derive(Args)]
7pub struct ConfigCommand {
8 #[command(subcommand)]
9 pub action: ConfigAction,
10}
11
12#[derive(Subcommand)]
13pub enum ConfigAction {
14 Show,
16 Set {
18 #[arg(short, long)]
20 preset: Option<String>,
21 #[arg(long)]
23 strategies: Option<bool>,
24 #[arg(long)]
26 initiatives: Option<bool>,
27 },
28 Get {
30 key: String,
32 },
33}
34
35impl ConfigCommand {
36 pub async fn execute(&self) -> Result<()> {
37 let (workspace_exists, metis_dir) = workspace::has_metis_vault();
39 if !workspace_exists {
40 anyhow::bail!("Not in a Metis workspace. Run 'metis init' to create one.");
41 }
42 let metis_dir = metis_dir.unwrap();
43
44 let db_path = metis_dir.join("metis.db");
46 let db = Database::new(db_path.to_str().unwrap())
47 .map_err(|e| anyhow::anyhow!("Database connection failed: {}", e))?;
48 let mut config_repo = db
49 .configuration_repository()
50 .map_err(|e| anyhow::anyhow!("Failed to create configuration repository: {}", e))?;
51
52 match &self.action {
54 ConfigAction::Show => self.show_config(&mut config_repo).await,
55 ConfigAction::Set {
56 preset,
57 strategies,
58 initiatives,
59 } => {
60 self.set_config(&mut config_repo, preset, *strategies, *initiatives)
61 .await
62 }
63 ConfigAction::Get { key } => self.get_config(&mut config_repo, key).await,
64 }
65 }
66
67 async fn show_config(
68 &self,
69 config_repo: &mut metis_core::dal::database::configuration_repository::ConfigurationRepository,
70 ) -> Result<()> {
71 let flight_config = config_repo
72 .get_flight_level_config()
73 .map_err(|e| anyhow::anyhow!("Failed to get flight level configuration: {}", e))?;
74
75 println!("Current Flight Level Configuration:");
76 println!(" Preset: {}", flight_config.preset_name());
77 println!(" Strategies enabled: {}", flight_config.strategies_enabled);
78 println!(
79 " Initiatives enabled: {}",
80 flight_config.initiatives_enabled
81 );
82 println!();
83 println!("Hierarchy: {}", flight_config.hierarchy_display());
84 println!();
85 println!("Available document types:");
86 for doc_type in flight_config.enabled_document_types() {
87 println!(" - {}", doc_type);
88 }
89
90 Ok(())
91 }
92
93 async fn set_config(
94 &self,
95 config_repo: &mut metis_core::dal::database::configuration_repository::ConfigurationRepository,
96 preset: &Option<String>,
97 strategies: Option<bool>,
98 initiatives: Option<bool>,
99 ) -> Result<()> {
100 let new_config = if let Some(preset_name) = preset {
101 match preset_name.as_str() {
103 "full" => FlightLevelConfig::full(),
104 "streamlined" => FlightLevelConfig::streamlined(),
105 "direct" => FlightLevelConfig::direct(),
106 _ => {
107 anyhow::bail!(
108 "Invalid preset '{}'. Valid presets are: full, streamlined, direct",
109 preset_name
110 );
111 }
112 }
113 } else if strategies.is_some() || initiatives.is_some() {
114 let current_config = config_repo
116 .get_flight_level_config()
117 .map_err(|e| anyhow::anyhow!("Failed to get current configuration: {}", e))?;
118
119 let strategies_enabled = strategies.unwrap_or(current_config.strategies_enabled);
120 let initiatives_enabled = initiatives.unwrap_or(current_config.initiatives_enabled);
121
122 FlightLevelConfig::new(strategies_enabled, initiatives_enabled)
123 .map_err(|e| anyhow::anyhow!("Invalid configuration: {}", e))?
124 } else {
125 anyhow::bail!(
126 "Must specify either --preset or at least one of --strategies/--initiatives"
127 );
128 };
129
130 config_repo
132 .set_flight_level_config(&new_config)
133 .map_err(|e| anyhow::anyhow!("Failed to save configuration: {}", e))?;
134
135 println!("Configuration updated successfully!");
136 println!("New configuration:");
137 println!(" Preset: {}", new_config.preset_name());
138 println!(" Strategies enabled: {}", new_config.strategies_enabled);
139 println!(" Initiatives enabled: {}", new_config.initiatives_enabled);
140 println!(" Hierarchy: {}", new_config.hierarchy_display());
141
142 Ok(())
143 }
144
145 async fn get_config(
146 &self,
147 config_repo: &mut metis_core::dal::database::configuration_repository::ConfigurationRepository,
148 key: &str,
149 ) -> Result<()> {
150 let value = config_repo
151 .get(key)
152 .map_err(|e| anyhow::anyhow!("Failed to get configuration value: {}", e))?;
153
154 match value {
155 Some(v) => {
156 println!("{}", v);
157 Ok(())
158 }
159 None => {
160 anyhow::bail!("Configuration key '{}' not found", key);
161 }
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::commands::InitCommand;
170 use tempfile::tempdir;
171
172 #[tokio::test]
173 async fn test_config_show_default() {
174 let temp_dir = tempdir().unwrap();
175 let original_dir = std::env::current_dir().ok();
176
177 std::env::set_current_dir(temp_dir.path()).unwrap();
179
180 let init_cmd = InitCommand {
182 name: Some("Test Project".to_string()),
183 preset: None,
184 strategies: None,
185 initiatives: None,
186 prefix: None,
187 };
188 init_cmd.execute().await.unwrap();
189
190 let config_cmd = ConfigCommand {
192 action: ConfigAction::Show,
193 };
194
195 let result = config_cmd.execute().await;
196 assert!(result.is_ok());
197
198 if let Some(dir) = original_dir {
200 std::env::set_current_dir(dir).unwrap();
201 }
202 }
203
204 #[tokio::test]
205 async fn test_config_set_streamlined_preset() {
206 let temp_dir = tempdir().unwrap();
207 let original_dir = std::env::current_dir().ok();
208
209 std::env::set_current_dir(temp_dir.path()).unwrap();
211
212 let init_cmd = InitCommand {
214 name: Some("Test Project".to_string()),
215 preset: None,
216 strategies: None,
217 initiatives: None,
218 prefix: None,
219 };
220 init_cmd.execute().await.unwrap();
221
222 let config_cmd = ConfigCommand {
224 action: ConfigAction::Set {
225 preset: Some("streamlined".to_string()),
226 strategies: None,
227 initiatives: None,
228 },
229 };
230
231 let result = config_cmd.execute().await;
232 assert!(result.is_ok());
233
234 if let Some(dir) = original_dir {
236 std::env::set_current_dir(dir).unwrap();
237 }
238 }
239
240 #[tokio::test]
241 async fn test_config_set_invalid_preset() {
242 let temp_dir = tempdir().unwrap();
243 let original_dir = std::env::current_dir().ok();
244
245 std::env::set_current_dir(temp_dir.path()).unwrap();
247
248 let init_cmd = InitCommand {
250 name: Some("Test Project".to_string()),
251 preset: None,
252 strategies: None,
253 initiatives: None,
254 prefix: None,
255 };
256 init_cmd.execute().await.unwrap();
257
258 let config_cmd = ConfigCommand {
260 action: ConfigAction::Set {
261 preset: Some("invalid".to_string()),
262 strategies: None,
263 initiatives: None,
264 },
265 };
266
267 let result = config_cmd.execute().await;
268 assert!(result.is_err());
269 assert!(result.unwrap_err().to_string().contains("Invalid preset"));
270
271 if let Some(dir) = original_dir {
273 std::env::set_current_dir(dir).unwrap();
274 }
275 }
276
277 #[tokio::test]
278 async fn test_config_without_workspace() {
279 let temp_dir = tempdir().unwrap();
280 let original_dir = std::env::current_dir().ok();
281
282 std::env::set_current_dir(temp_dir.path()).unwrap();
284
285 let config_cmd = ConfigCommand {
287 action: ConfigAction::Show,
288 };
289
290 let result = config_cmd.execute().await;
291 assert!(result.is_err());
292 assert!(result
293 .unwrap_err()
294 .to_string()
295 .contains("Not in a Metis workspace"));
296
297 if let Some(dir) = original_dir {
299 std::env::set_current_dir(dir).unwrap();
300 }
301 }
302}