command_vault/utils/
params.rs1use 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 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), "ed_value);
83
84 if let Some(desc) = ¶m.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 let update_preview = |stdout: &mut Stdout, current_value: &str| -> Result<()> {
115 let mut preview_command = command.to_string();
116
117 for (name, value) in ¶m_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), "ed_value);
135 }
136
137 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), "ed_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 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) = ¶m.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 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_preview(&mut stdout, &value)?;
226
227 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 final_command = command.to_string();
248 for (name, value) in ¶m_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), "ed_value);
268
269 if let Some(desc) = ¶meters.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}