envx_cli/
replace.rs

1use clap::Args;
2use color_eyre::Result;
3use comfy_table::{Table, presets::UTF8_FULL};
4use envx_core::{EnvVarManager, env::split_wildcard_pattern};
5
6#[derive(Args)]
7pub struct ReplaceArgs {
8    /// Variable name or pattern (supports wildcards with *)
9    pub pattern: String,
10
11    /// New value to set
12    pub value: String,
13
14    /// Dry run - show what would be replaced without making changes
15    #[arg(long)]
16    pub dry_run: bool,
17}
18
19#[derive(Args)]
20pub struct FindReplaceArgs {
21    /// Text to search for in values
22    pub search: String,
23
24    /// Text to replace with
25    pub replacement: String,
26
27    /// Only search in variables matching this pattern (supports wildcards)
28    #[arg(short = 'p', long)]
29    pub pattern: Option<String>,
30
31    /// Dry run - show what would be replaced without making changes
32    #[arg(long)]
33    pub dry_run: bool,
34}
35
36/// Handle replace command to replace environment variable values using patterns.
37///
38/// # Errors
39///
40/// This function will return an error if:
41/// - Environment variable operations fail (loading, replacing)
42/// - Pattern matching fails or produces invalid results
43/// - File I/O operations fail when persisting changes
44/// - Wildcard pattern parsing fails
45pub fn handle_replace(args: &ReplaceArgs) -> Result<()> {
46    let mut manager = EnvVarManager::new();
47    manager.load_all()?;
48
49    if args.dry_run {
50        // Show what would be replaced
51        let preview = preview_replace(&manager, &args.pattern)?;
52
53        if preview.is_empty() {
54            println!("No variables match the pattern '{}'", args.pattern);
55        } else {
56            println!("Would update {} variable(s):", preview.len());
57
58            let mut table = Table::new();
59            table.load_preset(UTF8_FULL);
60            table.set_header(vec!["Variable", "Current Value", "New Value"]);
61
62            for (name, current) in preview {
63                table.add_row(vec![name, current, args.value.clone()]);
64            }
65
66            println!("{table}");
67            println!("\nUse without --dry-run to apply changes");
68        }
69    } else {
70        let replaced = manager.replace(&args.pattern, &args.value)?;
71
72        if replaced.is_empty() {
73            println!("No variables match the pattern '{}'", args.pattern);
74        } else {
75            println!("āœ… Updated {} variable(s):", replaced.len());
76
77            let mut table = Table::new();
78            table.load_preset(UTF8_FULL);
79            table.set_header(vec!["Variable", "Old Value", "New Value"]);
80
81            for (name, old, new) in &replaced {
82                // Truncate long values for display
83                let display_old = if old.len() > 50 {
84                    format!("{}...", &old[..47])
85                } else {
86                    old.clone()
87                };
88                let display_new = if new.len() > 50 {
89                    format!("{}...", &new[..47])
90                } else {
91                    new.clone()
92                };
93                table.add_row(vec![name.clone(), display_old, display_new]);
94            }
95
96            println!("{table}");
97
98            #[cfg(windows)]
99            println!("\nšŸ“ Note: You may need to restart your terminal for changes to take effect");
100        }
101    }
102
103    Ok(())
104}
105
106fn preview_replace(manager: &EnvVarManager, pattern: &str) -> Result<Vec<(String, String)>> {
107    let mut preview = Vec::new();
108
109    if pattern.contains('*') {
110        let (prefix, suffix) = split_wildcard_pattern(pattern)?;
111
112        for var in manager.list() {
113            if var.name.starts_with(&prefix)
114                && var.name.ends_with(&suffix)
115                && var.name.len() >= prefix.len() + suffix.len()
116            {
117                preview.push((var.name.clone(), var.value.clone()));
118            }
119        }
120    } else if let Some(var) = manager.get(pattern) {
121        preview.push((var.name.clone(), var.value.clone()));
122    }
123
124    Ok(preview)
125}
126
127/// Handle find and replace operations within environment variable values.
128///
129/// # Errors
130///
131/// This function will return an error if:
132/// - Environment variable operations fail (loading, updating)
133/// - Pattern matching fails or produces invalid results
134/// - Find and replace operations fail
135/// - File I/O operations fail when persisting changes
136/// - Wildcard pattern parsing fails
137pub fn handle_find_replace(args: &FindReplaceArgs) -> Result<()> {
138    let mut manager = EnvVarManager::new();
139    manager.load_all()?;
140
141    if args.dry_run {
142        // Show preview
143        let preview = preview_find_replace(&manager, &args.search, &args.replacement, args.pattern.as_deref())?;
144
145        if preview.is_empty() {
146            println!("No variables contain '{}'", args.search);
147        } else {
148            println!("Would update {} variable(s):", preview.len());
149
150            let mut table = Table::new();
151            table.load_preset(UTF8_FULL);
152            table.set_header(vec!["Variable", "Current Value", "New Value"]);
153
154            for (name, old, new) in preview {
155                table.add_row(vec![name, old, new]);
156            }
157
158            println!("{table}");
159            println!("\nUse without --dry-run to apply changes");
160        }
161    } else {
162        let replaced = manager.find_replace(&args.search, &args.replacement, args.pattern.as_deref())?;
163
164        if replaced.is_empty() {
165            println!("No variables contain '{}'", args.search);
166        } else {
167            println!("āœ… Updated {} variable(s):", replaced.len());
168
169            let mut table = Table::new();
170            table.load_preset(UTF8_FULL);
171            table.set_header(vec!["Variable", "Old Value", "New Value"]);
172
173            for (name, old, new) in &replaced {
174                // Truncate long values
175                let display_old = if old.len() > 50 {
176                    format!("{}...", &old[..47])
177                } else {
178                    old.clone()
179                };
180                let display_new = if new.len() > 50 {
181                    format!("{}...", &new[..47])
182                } else {
183                    new.clone()
184                };
185                table.add_row(vec![name.clone(), display_old, display_new]);
186            }
187
188            println!("{table}");
189
190            #[cfg(windows)]
191            println!("\nšŸ“ Note: You may need to restart your terminal for changes to take effect");
192        }
193    }
194
195    Ok(())
196}
197
198fn preview_find_replace(
199    manager: &EnvVarManager,
200    search: &str,
201    replacement: &str,
202    pattern: Option<&str>,
203) -> Result<Vec<(String, String, String)>> {
204    let mut preview = Vec::new();
205
206    for var in manager.list() {
207        // Check if variable matches pattern (if specified)
208        let matches_pattern = if let Some(pat) = pattern {
209            if pat.contains('*') {
210                let (prefix, suffix) = split_wildcard_pattern(pat)?;
211                var.name.starts_with(&prefix)
212                    && var.name.ends_with(&suffix)
213                    && var.name.len() >= prefix.len() + suffix.len()
214            } else {
215                var.name == pat
216            }
217        } else {
218            true
219        };
220
221        if matches_pattern && var.value.contains(search) {
222            let new_value = var.value.replace(search, replacement);
223            preview.push((var.name.clone(), var.value.clone(), new_value));
224        }
225    }
226
227    Ok(preview)
228}