@@ -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-
2815impl 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