command_vault/utils/
params.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use crate::db::models::Parameter;
use regex::Regex;
use anyhow::{Result, anyhow};
use std::io::Write;
use colored::*;
use crossterm::{
    cursor::MoveTo,
    terminal::{Clear, ClearType, disable_raw_mode, enable_raw_mode},
    event::{self, Event, KeyCode, KeyModifiers},
    ExecutableCommand, QueueableCommand,
    style::Print,
};
use std::collections::HashMap;

const HEADER_LINE: u16 = 0;
const SEPARATOR_LINE: u16 = 1;
const PARAM_LINE: u16 = 3;
const DEFAULT_LINE: u16 = 4;
const INPUT_LINE: u16 = 5;
const PREVIEW_SEPARATOR_LINE: u16 = 7;
const COMMAND_LINE: u16 = 8;
const WORKDIR_LINE: u16 = 9;

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 default_value = cap.get(2).map(|m| m.as_str().to_string());
        
        parameters.push(Parameter {
            name,
            description: None,
            default_value,
        });
    }
    
    parameters
}

pub fn substitute_parameters(command: &str, parameters: &[Parameter]) -> Result<String> {
    let mut final_command = command.to_string();
    let is_test = std::env::var("COMMAND_VAULT_TEST").is_ok();
    
    // In test mode, just use default values without interactive UI
    if is_test {
        for param in parameters {
            let value = param.default_value.clone().unwrap_or_default();
            final_command = final_command.replace(&format!("@{}", param.name), &value);
        }
        return Ok(final_command);
    }
    
    let mut stdout = std::io::stdout();
    let mut param_values: HashMap<String, String> = HashMap::new();
    
    enable_raw_mode()?;
    stdout.execute(Clear(ClearType::All))?;

    let result = (|| -> Result<String> {
        for param in parameters {
            let default_str = param.default_value.as_deref().unwrap_or("");
            let desc = param.description.as_deref().unwrap_or("");
            let mut input = default_str.to_string();
            let mut cursor_pos = input.len();

            loop {
                // Clear screen
                stdout.queue(Clear(ClearType::All))?;

                // Header
                stdout.queue(MoveTo(0, HEADER_LINE))?
                      .queue(Print("Enter values for command parameters:"))?;

                // Top separator
                stdout.queue(MoveTo(0, SEPARATOR_LINE))?
                      .queue(Print("─".repeat(45).dimmed()))?;

                // Parameter info
                stdout.queue(MoveTo(0, PARAM_LINE))?
                      .queue(Print(format!("{}: {}", "Parameter".blue().bold(), param.name.yellow())))?;
                if !desc.is_empty() {
                    stdout.queue(Print(format!(" - {}", desc.dimmed())))?;
                }

                // Default value
                stdout.queue(MoveTo(0, DEFAULT_LINE))?
                      .queue(Print(format!("{}: [{}]", 
                          "Default value".dimmed(), 
                          default_str.bright_black()
                      )))?;

                // Input field
                stdout.queue(MoveTo(0, INPUT_LINE))?
                      .queue(Print(format!("{}: {}", "Enter value".dimmed(), input)))?;

                // Preview section
                let mut preview_command = command.to_string();
                for (param_name, value) in &param_values {
                    preview_command = preview_command.replace(&format!("@{}", param_name), value);
                }
                if !input.is_empty() {
                    preview_command = preview_command.replace(&format!("@{}", param.name), &input);
                } else if let Some(default) = &param.default_value {
                    preview_command = preview_command.replace(&format!("@{}", param.name), default);
                }

                // Bottom separator
                stdout.queue(MoveTo(0, PREVIEW_SEPARATOR_LINE))?
                      .queue(Print("─".repeat(45).dimmed()))?;

                // Command preview section with softer colors
                stdout.queue(MoveTo(0, COMMAND_LINE))?
                      .queue(Print(format!("{}: {}", 
                          "Command to execute".blue().bold(), 
                          preview_command.green()
                      )))?;

                // Working directory with softer colors
                stdout.queue(MoveTo(0, WORKDIR_LINE))?
                      .queue(Print(format!("{}: {}", 
                          "Working directory".cyan().bold(), 
                          std::env::current_dir()?.to_string_lossy().white()
                      )))?;

                // Position cursor at input
                let input_prompt = "Enter value: ";
                stdout.queue(MoveTo(
                    (input_prompt.len() + cursor_pos) as u16,
                    INPUT_LINE
                ))?;
                
                stdout.flush()?;

                // Handle input
                if let Event::Key(key) = event::read()? {
                    match (key.code, key.modifiers) {
                        (KeyCode::Char('c'), KeyModifiers::CONTROL) => {
                            return Err(anyhow!("Operation cancelled by user"));
                        },
                        (KeyCode::Enter, _) => break,
                        (KeyCode::Char(c), _) => {
                            input.insert(cursor_pos, c);
                            cursor_pos += 1;
                        }
                        (KeyCode::Backspace, _) if cursor_pos > 0 => {
                            input.remove(cursor_pos - 1);
                            cursor_pos -= 1;
                        }
                        (KeyCode::Left, _) if cursor_pos > 0 => {
                            cursor_pos -= 1;
                        }
                        (KeyCode::Right, _) if cursor_pos < input.len() => {
                            cursor_pos += 1;
                        }
                        (KeyCode::Esc, _) => {
                            input.clear();
                            break;
                        }
                        _ => {}
                    }
                }
            }

            let value = if input.is_empty() {
                param.default_value.clone().unwrap_or_default()
            } else {
                input
            };
            param_values.insert(param.name.clone(), value.clone());
            final_command = final_command.replace(&format!("@{}", param.name), &value);
        }

        Ok(final_command)
    })();

    // Always cleanup terminal state, regardless of success or error
    disable_raw_mode()?;
    stdout.execute(Clear(ClearType::All))?
          .execute(MoveTo(0, 0))?;

    // Re-enable colored output
    colored::control::set_override(true);
    
    // Now handle the result
    match result {
        Ok(cmd) => {
            // Final display
            println!("\n{}: {}", "Command to execute".blue().bold(), cmd.green());
            println!("{}: {}", "Working directory".cyan().bold(), std::env::current_dir()?.to_string_lossy().white());
            Ok(cmd)
        }
        Err(e) => Err(e)
    }
}