1use clap::Subcommand;
4use colored::Colorize;
5use std::fs;
6use std::path::PathBuf;
7
8use nika_engine::error::NikaError;
9
10#[derive(Subcommand)]
12pub enum ConfigAction {
13 List {
15 #[arg(long)]
17 json: bool,
18 },
19
20 Get {
22 key: String,
24 },
25
26 Set {
28 key: String,
30 value: String,
32 },
33
34 Edit,
36
37 Path,
39
40 Reset {
42 #[arg(short, long)]
44 force: bool,
45 },
46}
47
48pub fn handle_config_command(action: ConfigAction, quiet: bool) -> Result<(), NikaError> {
49 let nika_dir = find_nika_dir()?;
51 let config_path = nika_dir.join("config.toml");
52
53 match action {
54 ConfigAction::Path => {
55 println!("{}", config_path.display());
56 Ok(())
57 }
58
59 ConfigAction::List { json } => {
60 if !config_path.exists() {
61 if json {
62 println!("{{}}");
63 } else {
64 println!(
65 "{} No config file found at {}",
66 "ℹ".cyan(),
67 config_path.display()
68 );
69 println!(" Run 'nika init' to create one.");
70 }
71 return Ok(());
72 }
73
74 let content = fs::read_to_string(&config_path)?;
75
76 if json {
77 let value: toml::Value =
79 toml::from_str(&content).map_err(|e| NikaError::ValidationError {
80 reason: format!("Invalid TOML: {e}"),
81 })?;
82 let json = serde_json::to_string_pretty(&value).map_err(|e| {
83 NikaError::ValidationError {
84 reason: format!("JSON conversion failed: {e}"),
85 }
86 })?;
87 println!("{json}");
88 } else {
89 println!("{}", "Nika Configuration".bold());
90 println!("{}", "─".repeat(40));
91 println!();
92 println!("{content}");
93 }
94 Ok(())
95 }
96
97 ConfigAction::Get { key } => {
98 if !config_path.exists() {
99 return Err(NikaError::ValidationError {
100 reason: "No config file found. Run 'nika init' first.".to_string(),
101 });
102 }
103
104 let content = fs::read_to_string(&config_path)?;
105 let value: toml::Value =
106 toml::from_str(&content).map_err(|e| NikaError::ValidationError {
107 reason: format!("Invalid TOML: {e}"),
108 })?;
109
110 let mut current = &value;
112 for part in key.split('.') {
113 current = current
114 .get(part)
115 .ok_or_else(|| NikaError::ValidationError {
116 reason: format!("Key '{key}' not found"),
117 })?;
118 }
119
120 match current {
122 toml::Value::String(s) => println!("{s}"),
123 toml::Value::Integer(i) => println!("{i}"),
124 toml::Value::Float(f) => println!("{f}"),
125 toml::Value::Boolean(b) => println!("{b}"),
126 _ => println!("{current}"),
127 }
128 Ok(())
129 }
130
131 ConfigAction::Set { key, value } => {
132 if !config_path.exists() {
133 return Err(NikaError::ValidationError {
134 reason: "No config file found. Run 'nika init' first.".to_string(),
135 });
136 }
137
138 let content = fs::read_to_string(&config_path)?;
139 let mut doc =
140 content
141 .parse::<toml::Table>()
142 .map_err(|e| NikaError::ValidationError {
143 reason: format!("Invalid TOML: {e}"),
144 })?;
145
146 let parts: Vec<&str> = key.split('.').collect();
148 if parts.is_empty() {
149 return Err(NikaError::ValidationError {
150 reason: "Empty key".to_string(),
151 });
152 }
153
154 let mut current = &mut doc;
156 for (i, part) in parts.iter().enumerate() {
157 if i == parts.len() - 1 {
158 let toml_value = parse_config_value(&value);
160 current.insert((*part).to_string(), toml_value);
161 } else {
162 if !current.contains_key(*part) {
164 current.insert((*part).to_string(), toml::Value::Table(toml::Table::new()));
165 }
166 current = current
167 .get_mut(*part)
168 .ok_or_else(|| NikaError::ValidationError {
169 reason: format!("Config key '{part}' not found"),
170 })?
171 .as_table_mut()
172 .ok_or_else(|| NikaError::ValidationError {
173 reason: format!("'{part}' is not a table"),
174 })?;
175 }
176 }
177
178 let new_content =
180 toml::to_string_pretty(&doc).map_err(|e| NikaError::ValidationError {
181 reason: format!("TOML serialization failed: {e}"),
182 })?;
183 fs::write(&config_path, new_content)?;
184
185 if !quiet {
186 println!("{} {} = {}", "✓".green(), key, value);
187 }
188 Ok(())
189 }
190
191 ConfigAction::Edit => {
192 let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
193
194 if !config_path.exists() {
195 return Err(NikaError::ValidationError {
196 reason: format!(
197 "No config file found at {}. Run 'nika init' first.",
198 config_path.display()
199 ),
200 });
201 }
202
203 let status = std::process::Command::new(&editor)
204 .arg(&config_path)
205 .status()
206 .map_err(|e| NikaError::ValidationError {
207 reason: format!("Failed to launch editor '{editor}': {e}"),
208 })?;
209
210 if !status.success() {
211 return Err(NikaError::ValidationError {
212 reason: format!("Editor '{}' exited with code {:?}", editor, status.code()),
213 });
214 }
215 Ok(())
216 }
217
218 ConfigAction::Reset { force } => {
219 if !force {
220 println!(
221 "{} This will reset config to defaults. Use --force to confirm.",
222 "⚠".yellow()
223 );
224 return Ok(());
225 }
226
227 if config_path.exists() {
228 fs::remove_file(&config_path)?;
229 }
230
231 let default_config = include_str!("../templates/config.toml");
233 fs::write(&config_path, default_config)?;
234
235 if !quiet {
236 println!("{} Config reset to defaults", "✓".green());
237 }
238 Ok(())
239 }
240 }
241}
242
243fn parse_config_value(value: &str) -> toml::Value {
244 if value == "true" {
246 return toml::Value::Boolean(true);
247 }
248 if value == "false" {
249 return toml::Value::Boolean(false);
250 }
251
252 if let Ok(i) = value.parse::<i64>() {
254 return toml::Value::Integer(i);
255 }
256
257 if let Ok(f) = value.parse::<f64>() {
259 return toml::Value::Float(f);
260 }
261
262 toml::Value::String(value.to_string())
264}
265
266pub fn find_nika_dir() -> Result<PathBuf, NikaError> {
267 let current = std::env::current_dir()?;
268
269 let mut dir = current.as_path();
270 loop {
271 let nika_dir = dir.join(".nika");
272 if nika_dir.exists() && nika_dir.is_dir() {
273 return Ok(nika_dir);
274 }
275
276 match dir.parent() {
277 Some(parent) => dir = parent,
278 None => break,
279 }
280 }
281
282 Ok(current.join(".nika"))
284}