@@ -5,14 +5,15 @@ use std::process::{Child, ChildStdin, Command, Stdio};
55use crate :: constants:: GIT_EXECUTABLE ;
66use crate :: errors:: { GitXetError , Result } ;
77
8- // This mod implements utilities to invoke Git commands through child processes from the `git` program.
8+ // This mod implements utilities to invoke commands through child processes from any program, and
9+ // exposes special helpers to invoke git commands.
910
1011/// Run a Git command as a child process by setting the current working directory to `working_dir`.
1112/// This function doesn't allow the parent process to send data to the child.
1213///
1314/// Return `Ok(())` if the Git command finishes correctly and the child's stdout and stderr are ignored;
1415/// Return the underlying I/O error if the child process spawning or waiting fails; otherwise, the captured
15- /// stdout and stderr of the child are wrapped in an `Err(GitXetError::GitCommandFailed (_))` and returned.
16+ /// stdout and stderr of the child are wrapped in an `Err(GitXetError::CommandFailed (_))` and returned.
1617pub fn run_git_captured < P , S1 , I , S2 > ( working_dir : P , git_command : S1 , args : I ) -> Result < ( ) >
1718where
1819 P : AsRef < Path > ,
2122 S2 : AsRef < OsStr > ,
2223{
2324 let mut command = Command :: new ( GIT_EXECUTABLE ) ;
24- command. current_dir ( working_dir) . arg ( git_command. as_ref ( ) ) . args ( args) ;
25+ command. current_dir ( working_dir) . arg ( git_command) . args ( args) ;
26+
27+ CapturedCommand :: new ( command) ?. wait ( )
28+ }
29+
30+ /// Run a command as a child process by setting the current working directory to `working_dir`.
31+ /// This function doesn't allow the parent process to send data to the child.
32+ ///
33+ /// Return `Ok(())` if the command finishes correctly and the child's stdout and stderr are ignored;
34+ /// Return the underlying I/O error if the child process spawning or waiting fails; otherwise, the captured
35+ /// stdout and stderr of the child are wrapped in an `Err(GitXetError::CommandFailed(_))` and returned.
36+ #[ allow( dead_code) ]
37+ pub fn run_program_captured < S1 , P , I , S2 > ( program : S1 , working_dir : P , args : I ) -> Result < ( ) >
38+ where
39+ S1 : AsRef < OsStr > ,
40+ P : AsRef < Path > ,
41+ I : IntoIterator < Item = S2 > ,
42+ S2 : AsRef < OsStr > ,
43+ {
44+ let mut command = Command :: new ( program) ;
45+ command. current_dir ( working_dir) . args ( args) ;
2546
2647 CapturedCommand :: new ( command) ?. wait ( )
2748}
6081 CapturedCommand :: new_with_piped_stdin ( command)
6182}
6283
84+ /// Run a command as a child process by setting the current working directory to `working_dir`.
85+ /// This function allows the parent process to send data to the child through the piped stdin.
86+ ///
87+ /// Return `Ok(CapturedCommand)` if the command process spawns correctly; otherwise, the underlying I/O
88+ /// error is returned.
89+ ///
90+ /// # Examples
91+ /// ```ignore
92+ /// let mut cmd = run_program_captured_with_input_and_output("tee", path, &["dump.txt"])?;
93+ ///
94+ /// {
95+ /// let mut writer = cmd.stdin()?;
96+ /// write!(writer, "some_data")?;
97+ /// }
98+ ///
99+ /// let (response, _err) = cmd.wait_with_output()?;
100+ /// ```
101+ #[ allow( dead_code) ]
102+ pub fn run_program_captured_with_input_and_output < S1 , P , I , S2 > (
103+ program : S1 ,
104+ working_dir : P ,
105+ args : I ,
106+ ) -> Result < CapturedCommand >
107+ where
108+ S1 : AsRef < OsStr > ,
109+ P : AsRef < Path > ,
110+ I : IntoIterator < Item = S2 > ,
111+ S2 : AsRef < OsStr > ,
112+ {
113+ let mut command = Command :: new ( program) ;
114+ command. current_dir ( working_dir) . args ( args) ;
115+
116+ CapturedCommand :: new_with_piped_stdin ( command)
117+ }
118+
63119// This struct wraps inside a spawned child process, whose stdout and stderr is piped
64120// to the parent process instead of pointing at the terminal.
65121pub struct CapturedCommand {
@@ -74,8 +130,11 @@ impl CapturedCommand {
74130 // From past experience, if the "git" program is not found the underlying error
75131 // only says "Not Found" and is not very helpful to identify the cause. We thus
76132 // capture this error and make the message more explicit.
77- std:: io:: ErrorKind :: NotFound => GitXetError :: git_cmd_failed ( r#"program "git" not found"# , Some ( e) ) ,
78- _ => GitXetError :: git_cmd_failed ( "internal" , Some ( e) ) ,
133+ std:: io:: ErrorKind :: NotFound => GitXetError :: cmd_failed (
134+ format ! ( r#"program "{}" not found"# , command. get_program( ) . display( ) ) ,
135+ Some ( e) ,
136+ ) ,
137+ _ => GitXetError :: cmd_failed ( "internal" , Some ( e) ) ,
79138 } ) ?,
80139 } )
81140 }
@@ -98,7 +157,7 @@ impl CapturedCommand {
98157 }
99158
100159 /// Synchronously wait for the child to exit completely, returning `Ok(())` if the child exits with status code 0;
101- /// otherwise, return the captured output wrapped in an `Err(GitXetError::GitCommandFailed (_))`.
160+ /// otherwise, return the captured output wrapped in an `Err(GitXetError::CommandFailed (_))`.
102161 pub fn wait ( self ) -> Result < ( ) > {
103162 // ignores output
104163 let _ = self . wait_with_output ( ) ?;
@@ -108,7 +167,7 @@ impl CapturedCommand {
108167
109168 /// Synchronously wait for the child to exit and collect all remaining output on the stdout/stderr handles,
110169 /// returning a tuple of captured output if the child exits with status code 0; otherwise, return the captured
111- /// output wrapped in an `Err(GitXetError::GitCommandFailed (_))`.
170+ /// output wrapped in an `Err(GitXetError::CommandFailed (_))`.
112171 pub fn wait_with_output ( self ) -> Result < ( Vec < u8 > , Vec < u8 > ) > {
113172 let ret = self . child_process . wait_with_output ( ) ?;
114173
@@ -117,11 +176,60 @@ impl CapturedCommand {
117176 _ => {
118177 let stdout = std:: str:: from_utf8 ( & ret. stdout ) . unwrap_or ( "<Binary Data>" ) . trim ( ) ;
119178 let stderr = std:: str:: from_utf8 ( & ret. stderr ) . unwrap_or ( "<Binary Data>" ) . trim ( ) ;
120- Err ( GitXetError :: git_cmd_failed (
179+ Err ( GitXetError :: cmd_failed (
121180 format ! ( "err_code = {:?}, stdout = \" {}\" , stderr = \" {}\" " , ret. status. code( ) , stdout, stderr) ,
122181 None ,
123182 ) )
124183 } ,
125184 }
126185 }
127186}
187+
188+ #[ cfg( test) ]
189+ mod tests {
190+ use std:: io:: Write ;
191+
192+ use anyhow:: Result ;
193+
194+ use super :: * ;
195+
196+ #[ test]
197+ fn test_run_program_captured ( ) -> Result < ( ) > {
198+ #[ cfg( unix) ]
199+ run_program_captured ( "sh" , std:: env:: current_dir ( ) ?, & [ "-c" , "echo hello" ] ) ?;
200+ #[ cfg( windows) ]
201+ run_program_captured ( "cmd" , std:: env:: current_dir ( ) ?, & [ "/C" , "echo hello" ] ) ?;
202+
203+ Ok ( ( ) )
204+ }
205+
206+ #[ test]
207+ fn test_program_captured_with_input_and_output ( ) -> Result < ( ) > {
208+ let mut cmd = if cfg ! ( windows) {
209+ run_program_captured_with_input_and_output ( "cmd" , std:: env:: current_dir ( ) ?, & [ "/C" , "more" ] ) ?
210+ } else {
211+ run_program_captured_with_input_and_output ( "sh" , std:: env:: current_dir ( ) ?, & [ "-c" , "cat" ] ) ?
212+ } ;
213+
214+ {
215+ let mut writer = cmd. stdin ( ) ?;
216+ write ! ( writer, "hello" ) ?;
217+ }
218+
219+ let ( response, _err) = cmd. wait_with_output ( ) ?;
220+ assert_eq ! ( response, "hello" . as_bytes( ) ) ;
221+
222+ Ok ( ( ) )
223+ }
224+
225+ #[ test]
226+ fn test_error_on_get_stdin_without_captured ( ) -> Result < ( ) > {
227+ let mut command = Command :: new ( "more" ) ;
228+ command. current_dir ( std:: env:: current_dir ( ) ?) ;
229+
230+ let mut command = CapturedCommand :: new ( command) ?;
231+ assert ! ( command. stdin( ) . is_err( ) ) ;
232+
233+ Ok ( ( ) )
234+ }
235+ }
0 commit comments