1use std::env;
5
6use regex::Regex;
7
8use std::fmt;
9
10#[derive(Debug)]
12pub enum EnvExpansionError {
13 MissingVar(String),
14}
15
16impl fmt::Display for EnvExpansionError {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 EnvExpansionError::MissingVar(var) => {
20 write!(f, "Missing environment variable: {}", var)
21 }
22 }
23 }
24}
25
26impl std::error::Error for EnvExpansionError {}
27
28pub fn expand_env_vars(input: &str) -> Result<String, EnvExpansionError> {
39 #[cfg(unix)]
40 {
41 let unix_re = Regex::new(r"\$(\w+)|\$\{(\w+)\}").unwrap();
42 let result = unix_re.replace_all(input, |caps: ®ex::Captures| {
43 let var_name = caps
44 .get(1)
45 .or_else(|| caps.get(2))
46 .map(|m| m.as_str())
47 .unwrap_or("");
48 env::var(var_name).unwrap_or_default()
49 });
50 Ok(result.into_owned())
51 }
52
53 #[cfg(windows)]
54 {
55 let windows_re = Regex::new(r"%(\w+)%").unwrap();
56 let result = windows_re.replace_all(input, |caps: ®ex::Captures| {
57 let var_name = caps.get(1).map(|m| m.as_str()).unwrap_or("");
58 env::var(var_name).unwrap_or_default()
59 });
60 result.into_owned()
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn test_single_var_unix() {
70 unsafe {
71 std::env::set_var("USER", "alice");
72 }
73 let input = "Hello $USER!";
74 let output = expand_env_vars(input).unwrap();
75 assert_eq!(output, "Hello alice!");
76 }
77
78 #[test]
79 fn test_braced_var_unix() {
80 unsafe {
81 std::env::set_var("HOME", "/home/alice");
82 }
83 let input = "Path: ${HOME}/code";
84 let output = expand_env_vars(input).unwrap();
85 assert_eq!(output, "Path: /home/alice/code");
86 }
87
88 #[test]
89 fn test_multiple_vars_unix() {
90 unsafe {
91 std::env::set_var("USER", "bob");
92 std::env::set_var("SHELL", "/bin/bash");
93 }
94 let input = "$USER uses $SHELL";
95 let output = expand_env_vars(input).unwrap();
96 assert_eq!(output, "bob uses /bin/bash");
97 }
98
99 #[test]
100 fn test_missing_var_unix() {
101 unsafe {
102 std::env::remove_var("DOES_NOT_EXIST");
103 }
104 let input = "This is $DOES_NOT_EXIST";
105 let output = expand_env_vars(input).unwrap();
106 assert_eq!(output, "This is ");
107 }
108
109 #[cfg(windows)]
110 #[test]
111 fn test_single_var_windows() {
112 unsafe {
113 std::env::set_var("USERNAME", "charlie");
114 }
115 let input = "User: %USERNAME%";
116 let output = expand_env_vars(input).unwrap();
117 assert_eq!(output, "User: charlie");
118 }
119
120 #[cfg(windows)]
121 #[test]
122 fn test_multiple_vars_windows() {
123 unsafe {
124 std::env::set_var("USERNAME", "charlie");
125 std::env::set_var("APPDATA", "C:\\Users\\charlie\\AppData");
126 }
127 let input = "%USERNAME%'s config: %APPDATA%";
128 let output = expand_env_vars(input).unwrap();
129 assert_eq!(output, "charlie's config: C:\\Users\\charlie\\AppData");
130 }
131
132 #[cfg(windows)]
133 #[test]
134 fn test_missing_var_windows() {
135 unsafe {
136 std::env::remove_var("DOES_NOT_EXIST");
137 }
138 let input = "Value: %DOES_NOT_EXIST%";
139 let output = expand_env_vars(input).unwrap();
140 assert_eq!(output, "Value: ");
141 }
142}