Skip to content

Commit 235b1c9

Browse files
committed
refactor: simplified env interpolation logic by avoiding cloning the entire object, added unit tests
1 parent ceeedbd commit 235b1c9

File tree

1 file changed

+106
-51
lines changed

1 file changed

+106
-51
lines changed

src/runtime/cmd.rs

Lines changed: 106 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,10 @@ pub struct CommandLine {
1212
pub temp_env_file: Option<tempfile::NamedTempFile>,
1313
}
1414

15-
impl Clone for CommandLine {
16-
fn clone(&self) -> Self {
17-
Self {
18-
sudo: self.sudo,
19-
app: self.app.clone(),
20-
app_in_path: self.app_in_path,
21-
args: self.args.clone(),
22-
env: self.env.clone(),
23-
temp_env_file: None, // Don't clone the temp file, create a new one if needed
24-
}
25-
}
26-
}
27-
2815
impl CommandLine {
2916
pub fn from_vec(vec: &Vec<String>) -> anyhow::Result<Self> {
3017
log::debug!("Creating CommandLine from vector: {:?}", vec);
31-
18+
3219
if vec.is_empty() {
3320
log::error!("Empty command line vector provided");
3421
return Err(anyhow::anyhow!("empty command line"));
@@ -69,8 +56,13 @@ impl CommandLine {
6956
false
7057
};
7158

72-
log::debug!("Created CommandLine: sudo={}, app={}, app_in_path={}, args={:?}",
73-
sudo, app, app_in_path, args);
59+
log::debug!(
60+
"Created CommandLine: sudo={}, app={}, app_in_path={}, args={:?}",
61+
sudo,
62+
app,
63+
app_in_path,
64+
args
65+
);
7466

7567
Ok(Self {
7668
sudo,
@@ -86,80 +78,86 @@ impl CommandLine {
8678
vec: &Vec<String>,
8779
env: BTreeMap<String, String>,
8880
) -> anyhow::Result<Self> {
89-
log::debug!("Creating CommandLine with environment variables");
90-
log::trace!("Environment variables: {:?}", env);
81+
log::debug!("creating CommandLine with environment variables");
82+
log::trace!("environment variables: {:?}", env);
9183
let mut cmd = Self::from_vec(vec)?;
9284
cmd.env = env;
9385
Ok(cmd)
9486
}
9587

96-
fn interpolate_variables(&mut self) -> anyhow::Result<()> {
97-
log::debug!("Interpolating variables from environment: {:?}", self.env);
98-
99-
self.args = self.args.iter().map(|arg| {
100-
let mut result = arg.clone();
101-
for (key, value) in &self.env {
102-
let pattern = format!("${{{}}}", key);
103-
if result.contains(&pattern) {
104-
log::debug!("Replacing {} with {}", pattern, value);
105-
result = result.replace(&pattern, value);
88+
fn get_env_interpolated_args(&self) -> Vec<String> {
89+
log::debug!("interpolating variables from environment: {:?}", self.env);
90+
91+
let args = self
92+
.args
93+
.iter()
94+
.map(|arg| {
95+
let mut result = arg.clone();
96+
for (key, value) in &self.env {
97+
let pattern = format!("${{{}}}", key);
98+
if result.contains(&pattern) {
99+
log::debug!("replacing {} with {}", pattern, value);
100+
result = result.replace(&pattern, value);
101+
}
106102
}
107-
}
108-
result
109-
}).collect();
103+
result
104+
})
105+
.collect();
110106

111-
log::debug!("After interpolation: {:?}", self.args);
112-
Ok(())
107+
log::debug!("after interpolation: {:?}", &args);
108+
109+
args
113110
}
114111

115112
pub async fn execute(&self) -> anyhow::Result<String> {
116-
log::info!("Executing command: {}", self);
117-
log::debug!("Full command details: {:?}", self);
113+
log::debug!("executing command: {}", self);
114+
log::debug!("full command details: {:?}", self);
118115

119-
// Create a mutable copy for interpolation
120-
let mut cmd = self.clone();
121-
cmd.interpolate_variables()?;
116+
let args = self.get_env_interpolated_args();
122117

123-
let mut command = tokio::process::Command::new(&cmd.app);
124-
command.args(&cmd.args);
118+
let mut command = tokio::process::Command::new(&self.app);
119+
command.args(&args);
125120

126-
// Log environment variables if present
127-
if !cmd.env.is_empty() {
128-
log::debug!("Setting environment variables: {:?}", cmd.env);
129-
command.envs(&cmd.env);
121+
// log environment variables if present
122+
if !self.env.is_empty() {
123+
log::debug!("setting environment variables: {:?}", self.env);
124+
command.envs(&self.env);
130125
}
131126

132127
let output = command.output().await?;
133-
log::debug!("Command completed with status: {:?}", output.status);
128+
log::debug!("command completed with status: {:?}", output.status);
134129

135130
let mut parts = vec![];
136131

137132
let stdout = String::from_utf8_lossy(&output.stdout);
138133
let stderr = String::from_utf8_lossy(&output.stderr);
139134

140135
if !output.status.success() {
141-
log::warn!("Command failed with exit code: {}", output.status);
136+
log::warn!("command failed with exit code: {}", output.status);
142137
parts.push(format!("EXIT CODE: {}", &output.status));
143138
}
144139

145140
if !stdout.is_empty() {
146-
log::trace!("Command stdout: {}", stdout);
141+
log::trace!("command stdout: {}", stdout);
147142
parts.push(stdout.to_string());
148143
}
149144

150145
if !stderr.is_empty() {
151146
if output.status.success() {
152-
log::debug!("Command stderr (success): {}", stderr);
147+
log::debug!("command stderr (success): {}", stderr);
153148
parts.push(stderr.to_string());
154149
} else {
155-
log::error!("Command stderr (failure): {}", stderr);
150+
log::error!("command stderr (failure): {}", stderr);
156151
parts.push(format!("ERROR: {}", stderr));
157152
}
158153
}
159154

160155
let result = parts.join("\n");
161-
log::debug!("Command execution completed, output length: {}", result.len());
162-
log::trace!("Command output: {}", result);
156+
log::debug!(
157+
"command execution completed, output length: {}",
158+
result.len()
159+
);
160+
log::trace!("command output: {}", result);
163161

164162
Ok(result)
165163
}
@@ -271,4 +269,61 @@ mod tests {
271269
let result = cmd.execute().await;
272270
assert!(result.is_err());
273271
}
272+
273+
#[test]
274+
fn test_get_env_interpolated_args_with_env_vars() {
275+
let mut env = BTreeMap::new();
276+
env.insert("TEST_VAR".to_string(), "test_value".to_string());
277+
env.insert("OTHER_VAR".to_string(), "other_value".to_string());
278+
279+
let cmd = CommandLine {
280+
sudo: false,
281+
app: "echo".to_string(),
282+
args: vec!["${TEST_VAR}".to_string(), "${OTHER_VAR}".to_string()],
283+
app_in_path: true,
284+
env,
285+
temp_env_file: None,
286+
};
287+
288+
let result = cmd.get_env_interpolated_args();
289+
assert_eq!(result, vec!["test_value", "other_value"]);
290+
}
291+
292+
#[test]
293+
fn test_get_env_interpolated_args_with_missing_vars() {
294+
let env = BTreeMap::new();
295+
let cmd = CommandLine {
296+
sudo: false,
297+
app: "echo".to_string(),
298+
args: vec!["${MISSING_VAR}".to_string()],
299+
app_in_path: true,
300+
env,
301+
temp_env_file: None,
302+
};
303+
304+
let result = cmd.get_env_interpolated_args();
305+
assert_eq!(result, vec!["${MISSING_VAR}"]);
306+
}
307+
308+
#[test]
309+
fn test_get_env_interpolated_args_with_mixed_content() {
310+
let mut env = BTreeMap::new();
311+
env.insert("VAR".to_string(), "value".to_string());
312+
313+
let cmd = CommandLine {
314+
sudo: false,
315+
app: "echo".to_string(),
316+
args: vec![
317+
"prefix_${VAR}".to_string(),
318+
"normal_arg".to_string(),
319+
"${VAR}_suffix".to_string(),
320+
],
321+
app_in_path: true,
322+
env,
323+
temp_env_file: None,
324+
};
325+
326+
let result = cmd.get_env_interpolated_args();
327+
assert_eq!(result, vec!["prefix_value", "normal_arg", "value_suffix"]);
328+
}
274329
}

0 commit comments

Comments
 (0)