command_vault/utils/
params.rs

1use anyhow::Result;
2use colored::*;
3use crossterm::{
4    cursor::MoveTo,
5    event::{self, Event, KeyCode},
6    QueueableCommand,
7    style::Print,
8    terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
9};
10use regex::Regex;
11use std::{
12    collections::HashMap,
13    io::{stdout, Stdout, Write},
14};
15
16use crate::db::models::Parameter;
17
18pub fn parse_parameters(command: &str) -> Vec<Parameter> {
19    let re = Regex::new(r"@([a-zA-Z_][a-zA-Z0-9_]*)(?::([^@\s][^@]*))?").unwrap();
20    let mut parameters = Vec::new();
21    
22    for cap in re.captures_iter(command) {
23        let name = cap[1].to_string();
24        let description = cap.get(2).map(|m| {
25            let desc = m.as_str().trim_end();
26            if let Some(space_pos) = desc.find(char::is_whitespace) {
27                &desc[..space_pos]
28            } else {
29                desc
30            }.to_string()
31        });
32        parameters.push(Parameter::with_description(name, description));
33    }
34    
35    parameters
36}
37
38pub fn substitute_parameters(command: &str, parameters: &[Parameter], test_input: Option<&str>) -> Result<String> {
39    let is_test = test_input.is_some() || std::env::var("COMMAND_VAULT_TEST").is_ok();
40    if is_test {
41        let mut final_command = command.to_string();
42        let test_values: Vec<&str> = if let Some(input) = test_input {
43            if input.is_empty() {
44                parameters.iter()
45                    .map(|p| p.description.as_deref().unwrap_or(""))
46                    .collect()
47            } else {
48                input.split('\n').collect()
49            }
50        } else {
51            // When no test input is provided, use descriptions
52            parameters.iter()
53                .map(|p| p.description.as_deref().unwrap_or(""))
54                .collect()
55        };
56
57        for (i, param) in parameters.iter().enumerate() {
58            let value = if i < test_values.len() {
59                test_values[i]
60            } else {
61                param.description.as_deref().unwrap_or("")
62            };
63
64            let needs_quotes = value.is_empty() || 
65                             value.contains(' ') || 
66                             value.contains('*') || 
67                             value.contains(';') ||
68                             value.contains('|') ||
69                             value.contains('>') ||
70                             value.contains('<') ||
71                             command.contains('>') ||
72                             command.contains('<') ||
73                             command.contains('|') ||
74                             final_command.starts_with("grep");
75
76            let quoted_value = if needs_quotes && !value.starts_with('\'') && !value.starts_with('"') {
77                format!("'{}'", value.replace('\'', "'\\''"))
78            } else {
79                value.to_string()
80            };
81
82            final_command = final_command.replace(&format!("@{}", param.name), &quoted_value);
83            
84            // Remove the description part from the command
85            if let Some(desc) = &param.description {
86                final_command = final_command.replace(&format!(":{}", desc), "");
87            }
88        }
89        Ok(final_command)
90    } else {
91        prompt_parameters(command, parameters, test_input)
92    }
93}
94
95pub fn prompt_parameters(command: &str, parameters: &[Parameter], test_input: Option<&str>) -> Result<String> {
96    let is_test = test_input.is_some() || std::env::var("COMMAND_VAULT_TEST").is_ok();
97    let result = (|| -> Result<String> {
98        let mut param_values: HashMap<String, String> = HashMap::new();
99        let mut final_command = String::new();
100
101        for param in parameters {
102            let value = if is_test {
103                if let Some(input) = test_input {
104                    input.to_string()
105                } else {
106                    param.description.clone().unwrap_or_default()
107                }
108            } else {
109                enable_raw_mode()?;
110                let mut stdout = stdout();
111                stdout.queue(Clear(ClearType::All))?;
112                
113                // Function to update the preview
114                let update_preview = |stdout: &mut Stdout, current_value: &str| -> Result<()> {
115                    let mut preview_command = command.to_string();
116                    
117                    // Add all previous parameter values
118                    for (name, value) in &param_values {
119                        let needs_quotes = value.is_empty() || 
120                            value.contains(' ') || 
121                            value.contains('*') || 
122                            value.contains(';') ||
123                            value.contains('|') ||
124                            value.contains('>') ||
125                            value.contains('<') ||
126                            preview_command.starts_with("grep");
127
128                        let quoted_value = if needs_quotes && !value.starts_with('\'') && !value.starts_with('"') {
129                            format!("'{}'", value.replace('\'', "'\\''"))
130                        } else {
131                            value.clone()
132                        };
133
134                        preview_command = preview_command.replace(&format!("@{}", name), &quoted_value);
135                    }
136
137                    // Add current parameter value
138                    let needs_quotes = current_value.is_empty() || 
139                        current_value.contains(' ') || 
140                        current_value.contains('*') || 
141                        current_value.contains(';') ||
142                        current_value.contains('|') ||
143                        current_value.contains('>') ||
144                        current_value.contains('<') ||
145                        preview_command.starts_with("grep");
146
147                    let quoted_value = if needs_quotes && !current_value.starts_with('\'') && !current_value.starts_with('"') {
148                        format!("'{}'", current_value.replace('\'', "'\\''"))
149                    } else {
150                        current_value.to_string()
151                    };
152
153                    preview_command = preview_command.replace(&format!("@{}", param.name), &quoted_value);
154
155                    stdout.queue(MoveTo(0, 0))?
156                          .queue(Print("─".repeat(45).dimmed()))?;
157                    stdout.queue(MoveTo(0, 1))?
158                          .queue(Clear(ClearType::CurrentLine))?
159                          .queue(Print(format!("{}: {}", 
160                              "Command to execute".blue().bold(), 
161                              preview_command.green()
162                          )))?;
163                    stdout.queue(MoveTo(0, 2))?
164                          .queue(Print(format!("{}: {}", 
165                              "Working directory".cyan().bold(), 
166                              std::env::current_dir()?.to_string_lossy().white()
167                          )))?;
168                    Ok(())
169                };
170
171                // Initial display
172                update_preview(&mut stdout, "")?;
173
174                stdout.queue(MoveTo(0, 4))?
175                      .queue(Print("─".repeat(45).dimmed()))?;
176                stdout.queue(MoveTo(0, 5))?
177                      .queue(Print(format!("{}: {}", 
178                          "Parameter".blue().bold(), 
179                          param.name.green()
180                      )))?;
181                if let Some(desc) = &param.description {
182                    stdout.queue(MoveTo(0, 6))?
183                          .queue(Print(format!("{}: {}", 
184                              "Description".cyan().bold(), 
185                              desc.white()
186                          )))?;
187                }
188                stdout.queue(MoveTo(0, 7))?
189                      .queue(Print(format!("{}: ", "Enter value".yellow().bold())))?;
190                stdout.flush()?;
191
192                let mut value = String::new();
193                let mut cursor_pos = 0;
194
195                loop {
196                    if let Event::Key(key) = event::read()? {
197                        match key.code {
198                            KeyCode::Enter => break,
199                            KeyCode::Char('c') if key.modifiers.contains(event::KeyModifiers::CONTROL) => {
200                                // Handle Ctrl+C
201                                disable_raw_mode()?;
202                                stdout.queue(Clear(ClearType::All))?;
203                                stdout.queue(MoveTo(0, 0))?;
204                                stdout.flush()?;
205                                return Err(anyhow::anyhow!("Operation cancelled by user"));
206                            }
207                            KeyCode::Char(c) => {
208                                value.insert(cursor_pos, c);
209                                cursor_pos += 1;
210                            }
211                            KeyCode::Backspace if cursor_pos > 0 => {
212                                value.remove(cursor_pos - 1);
213                                cursor_pos -= 1;
214                            }
215                            KeyCode::Left if cursor_pos > 0 => {
216                                cursor_pos -= 1;
217                            }
218                            KeyCode::Right if cursor_pos < value.len() => {
219                                cursor_pos += 1;
220                            }
221                            _ => {}
222                        }
223
224                        // Update command preview
225                        update_preview(&mut stdout, &value)?;
226
227                        // Redraw the value line
228                        stdout.queue(MoveTo(0, 7))?
229                              .queue(Clear(ClearType::CurrentLine))?
230                              .queue(Print(format!("{}: {}", 
231                                  "Enter value".yellow().bold(), 
232                                  value
233                              )))?;
234                        stdout.queue(MoveTo((cursor_pos + 13) as u16, 7))?;
235                        stdout.flush()?;
236                    }
237                }
238
239                disable_raw_mode()?;
240                value
241            };
242
243            param_values.insert(param.name.clone(), value);
244        }
245
246        // Build final command
247        final_command = command.to_string();
248        for (name, value) in &param_values {
249            let needs_quotes = value.is_empty() || 
250                             value.contains(' ') || 
251                             value.contains('*') || 
252                             value.contains(';') ||
253                             value.contains('|') ||
254                             value.contains('>') ||
255                             value.contains('<') ||
256                             command.contains('>') ||
257                             command.contains('<') ||
258                             command.contains('|') ||
259                             final_command.starts_with("grep");
260
261            let quoted_value = if needs_quotes && !value.starts_with('\'') && !value.starts_with('"') {
262                format!("'{}'", value.replace('\'', "'\\''"))
263            } else {
264                value.clone()
265            };
266
267            final_command = final_command.replace(&format!("@{}", name), &quoted_value);
268            
269            // Remove the description part from the command
270            if let Some(desc) = &parameters.iter().find(|p| p.name == *name).unwrap().description {
271                final_command = final_command.replace(&format!(":{}", desc), "");
272            }
273        }
274
275        if !is_test {
276            let mut stdout = stdout();
277            stdout.queue(Clear(ClearType::All))?;
278            stdout.queue(MoveTo(0, 0))?;
279            stdout.flush()?;
280        }
281
282        Ok(final_command)
283    })();
284
285    if !is_test {
286        disable_raw_mode()?;
287    }
288
289    result
290}