command_vault/utils/
params.rsuse anyhow::Result;
use colored::*;
use crossterm::{
cursor::MoveTo,
event::{self, Event, KeyCode},
QueueableCommand,
style::Print,
terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
};
use regex::Regex;
use std::{
collections::HashMap,
io::{stdout, Stdout, Write},
};
use crate::db::models::Parameter;
pub fn parse_parameters(command: &str) -> Vec<Parameter> {
let re = Regex::new(r"@([a-zA-Z_][a-zA-Z0-9_]*)(?::([^@\s][^@]*))?").unwrap();
let mut parameters = Vec::new();
for cap in re.captures_iter(command) {
let name = cap[1].to_string();
let description = cap.get(2).map(|m| {
let desc = m.as_str().trim_end();
if let Some(space_pos) = desc.find(char::is_whitespace) {
&desc[..space_pos]
} else {
desc
}.to_string()
});
parameters.push(Parameter::with_description(name, description));
}
parameters
}
pub fn substitute_parameters(command: &str, parameters: &[Parameter], test_input: Option<&str>) -> Result<String> {
let is_test = test_input.is_some() || std::env::var("COMMAND_VAULT_TEST").is_ok();
if is_test {
let mut final_command = command.to_string();
let test_values: Vec<&str> = if let Some(input) = test_input {
if input.is_empty() {
parameters.iter()
.map(|p| p.description.as_deref().unwrap_or(""))
.collect()
} else {
input.split('\n').collect()
}
} else {
parameters.iter()
.map(|p| p.description.as_deref().unwrap_or(""))
.collect()
};
for (i, param) in parameters.iter().enumerate() {
let value = if i < test_values.len() {
test_values[i]
} else {
param.description.as_deref().unwrap_or("")
};
let needs_quotes = value.is_empty() ||
value.contains(' ') ||
value.contains('*') ||
value.contains(';') ||
value.contains('|') ||
value.contains('>') ||
value.contains('<') ||
command.contains('>') ||
command.contains('<') ||
command.contains('|') ||
final_command.starts_with("grep");
let quoted_value = if needs_quotes && !value.starts_with('\'') && !value.starts_with('"') {
format!("'{}'", value.replace('\'', "'\\''"))
} else {
value.to_string()
};
final_command = final_command.replace(&format!("@{}", param.name), "ed_value);
if let Some(desc) = ¶m.description {
final_command = final_command.replace(&format!(":{}", desc), "");
}
}
Ok(final_command)
} else {
prompt_parameters(command, parameters, test_input)
}
}
pub fn prompt_parameters(command: &str, parameters: &[Parameter], test_input: Option<&str>) -> Result<String> {
let is_test = test_input.is_some() || std::env::var("COMMAND_VAULT_TEST").is_ok();
let result = (|| -> Result<String> {
let mut param_values: HashMap<String, String> = HashMap::new();
let mut final_command = String::new();
for param in parameters {
let value = if is_test {
if let Some(input) = test_input {
input.to_string()
} else {
param.description.clone().unwrap_or_default()
}
} else {
enable_raw_mode()?;
let mut stdout = stdout();
stdout.queue(Clear(ClearType::All))?;
let update_preview = |stdout: &mut Stdout, current_value: &str| -> Result<()> {
let mut preview_command = command.to_string();
for (name, value) in ¶m_values {
let needs_quotes = value.is_empty() ||
value.contains(' ') ||
value.contains('*') ||
value.contains(';') ||
value.contains('|') ||
value.contains('>') ||
value.contains('<') ||
preview_command.starts_with("grep");
let quoted_value = if needs_quotes && !value.starts_with('\'') && !value.starts_with('"') {
format!("'{}'", value.replace('\'', "'\\''"))
} else {
value.clone()
};
preview_command = preview_command.replace(&format!("@{}", name), "ed_value);
}
let needs_quotes = current_value.is_empty() ||
current_value.contains(' ') ||
current_value.contains('*') ||
current_value.contains(';') ||
current_value.contains('|') ||
current_value.contains('>') ||
current_value.contains('<') ||
preview_command.starts_with("grep");
let quoted_value = if needs_quotes && !current_value.starts_with('\'') && !current_value.starts_with('"') {
format!("'{}'", current_value.replace('\'', "'\\''"))
} else {
current_value.to_string()
};
preview_command = preview_command.replace(&format!("@{}", param.name), "ed_value);
stdout.queue(MoveTo(0, 0))?
.queue(Print("─".repeat(45).dimmed()))?;
stdout.queue(MoveTo(0, 1))?
.queue(Clear(ClearType::CurrentLine))?
.queue(Print(format!("{}: {}",
"Command to execute".blue().bold(),
preview_command.green()
)))?;
stdout.queue(MoveTo(0, 2))?
.queue(Print(format!("{}: {}",
"Working directory".cyan().bold(),
std::env::current_dir()?.to_string_lossy().white()
)))?;
Ok(())
};
update_preview(&mut stdout, "")?;
stdout.queue(MoveTo(0, 4))?
.queue(Print("─".repeat(45).dimmed()))?;
stdout.queue(MoveTo(0, 5))?
.queue(Print(format!("{}: {}",
"Parameter".blue().bold(),
param.name.green()
)))?;
if let Some(desc) = ¶m.description {
stdout.queue(MoveTo(0, 6))?
.queue(Print(format!("{}: {}",
"Description".cyan().bold(),
desc.white()
)))?;
}
stdout.queue(MoveTo(0, 7))?
.queue(Print(format!("{}: ", "Enter value".yellow().bold())))?;
stdout.flush()?;
let mut value = String::new();
let mut cursor_pos = 0;
loop {
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Enter => break,
KeyCode::Char('c') if key.modifiers.contains(event::KeyModifiers::CONTROL) => {
disable_raw_mode()?;
stdout.queue(Clear(ClearType::All))?;
stdout.queue(MoveTo(0, 0))?;
stdout.flush()?;
return Err(anyhow::anyhow!("Operation cancelled by user"));
}
KeyCode::Char(c) => {
value.insert(cursor_pos, c);
cursor_pos += 1;
}
KeyCode::Backspace if cursor_pos > 0 => {
value.remove(cursor_pos - 1);
cursor_pos -= 1;
}
KeyCode::Left if cursor_pos > 0 => {
cursor_pos -= 1;
}
KeyCode::Right if cursor_pos < value.len() => {
cursor_pos += 1;
}
_ => {}
}
update_preview(&mut stdout, &value)?;
stdout.queue(MoveTo(0, 7))?
.queue(Clear(ClearType::CurrentLine))?
.queue(Print(format!("{}: {}",
"Enter value".yellow().bold(),
value
)))?;
stdout.queue(MoveTo((cursor_pos + 13) as u16, 7))?;
stdout.flush()?;
}
}
disable_raw_mode()?;
value
};
param_values.insert(param.name.clone(), value);
}
final_command = command.to_string();
for (name, value) in ¶m_values {
let needs_quotes = value.is_empty() ||
value.contains(' ') ||
value.contains('*') ||
value.contains(';') ||
value.contains('|') ||
value.contains('>') ||
value.contains('<') ||
command.contains('>') ||
command.contains('<') ||
command.contains('|') ||
final_command.starts_with("grep");
let quoted_value = if needs_quotes && !value.starts_with('\'') && !value.starts_with('"') {
format!("'{}'", value.replace('\'', "'\\''"))
} else {
value.clone()
};
final_command = final_command.replace(&format!("@{}", name), "ed_value);
if let Some(desc) = ¶meters.iter().find(|p| p.name == *name).unwrap().description {
final_command = final_command.replace(&format!(":{}", desc), "");
}
}
if !is_test {
let mut stdout = stdout();
stdout.queue(Clear(ClearType::All))?;
stdout.queue(MoveTo(0, 0))?;
stdout.flush()?;
}
Ok(final_command)
})();
if !is_test {
disable_raw_mode()?;
}
result
}