11use std:: {
22 fmt:: { self } ,
3+ io:: { Read , Write } ,
34 path:: PathBuf ,
5+ process:: Stdio ,
6+ thread,
47} ;
58
69use yansi:: { Condition , Paint } ;
@@ -116,7 +119,44 @@ impl Command {
116119 self . execution_mode = ExecutionMode :: PrintOutput { live : true } ;
117120 self
118121 }
122+
119123 // maybe Result<CommandResults, std::io::Error> instead? ie Error::new(ErrorKind::Other, "something went wrong");
124+ // pub fn try_call(&self) -> CommandResults {
125+ // println!("{self:#}");
126+
127+ // let mut cmd = std::process::Command::new(self.command.clone());
128+ // cmd.args(self.args.clone());
129+ // if let Some(dir) = &self.cd {
130+ // cmd.current_dir(dir);
131+ // }
132+
133+ // if let ExecutionMode::PrintOutput { live: true } = self.execution_mode {
134+ // let s = cmd
135+ // .spawn()
136+ // .unwrap_or_else(|_| panic!("command: {} failed to start", self.command.red()))
137+ // .wait()
138+ // .expect("failed to wait on child");
139+ // CommandResults {
140+ // stdout: "".into(),
141+ // stderr: "".into(),
142+ // success: s.success(),
143+ // }
144+ // } else {
145+ // let o = cmd.output().expect("failed to execute process");
146+ // let r = CommandResults {
147+ // stdout: String::from_utf8_lossy(&o.stdout).into(),
148+ // stderr: String::from_utf8_lossy(&o.stderr).into(),
149+ // success: o.status.success(),
150+ // };
151+ // if let ExecutionMode::PrintOutput { live: false } = self.execution_mode {
152+ // println!("{}", r.stdout);
153+ // eprintln!("{}", r.stderr);
154+ // }
155+ // r
156+ // }
157+ // }
158+
159+ /// Execute the command and capture both stdout and stderr while simultaneously printing them live if live is true
120160 pub fn try_call ( & self ) -> CommandResults {
121161 println ! ( "{self:#}" ) ;
122162
@@ -127,15 +167,81 @@ impl Command {
127167 }
128168
129169 if let ExecutionMode :: PrintOutput { live : true } = self . execution_mode {
130- let s = cmd
170+ cmd. stdout ( Stdio :: piped ( ) ) ;
171+ cmd. stderr ( Stdio :: piped ( ) ) ;
172+
173+ let mut child = cmd
131174 . spawn ( )
132- . unwrap_or_else ( |_| panic ! ( "command: {} failed to start" , self . command. red( ) ) )
133- . wait ( )
134- . expect ( "failed to wait on child" ) ;
175+ . unwrap_or_else ( |_| panic ! ( "command: {} failed to start" , self . command. red( ) ) ) ;
176+
177+ let stdout = child. stdout . take ( ) . expect ( "Failed to capture stdout" ) ;
178+ let stderr = child. stderr . take ( ) . expect ( "Failed to capture stderr" ) ;
179+
180+ // Create channels to collect output
181+ let ( stdout_tx, stdout_rx) = std:: sync:: mpsc:: channel ( ) ;
182+ let ( stderr_tx, stderr_rx) = std:: sync:: mpsc:: channel ( ) ;
183+
184+ fn ssd ( ) { }
185+ // Spawn thread to read and print stdout
186+ let stdout_thread = thread:: spawn ( move || {
187+ let mut reader = stdout;
188+ let mut output = Vec :: new ( ) ;
189+ let mut buf = [ 0u8 ; 8192 ] ;
190+
191+ let mut out = std:: io:: stdout ( ) . lock ( ) ;
192+
193+ loop {
194+ let n = match reader. read ( & mut buf) {
195+ Ok ( 0 ) => break ,
196+ Ok ( n) => n,
197+ Err ( _) => break ,
198+ } ;
199+
200+ output. extend_from_slice ( & buf[ ..n] ) ;
201+ let _ = out. write_all ( & buf[ ..n] ) ; // <-- bytes, not chars
202+ let _ = out. flush ( ) ;
203+ }
204+
205+ let _ = stdout_tx. send ( output) ;
206+ } ) ;
207+
208+ // Spawn thread to read and print stderr
209+ let stderr_thread = thread:: spawn ( move || {
210+ let mut reader = stderr;
211+ let mut output = Vec :: new ( ) ;
212+ let mut buf = [ 0u8 ; 8192 ] ;
213+
214+ let mut err = std:: io:: stderr ( ) . lock ( ) ;
215+
216+ loop {
217+ let n = match reader. read ( & mut buf) {
218+ Ok ( 0 ) => break ,
219+ Ok ( n) => n,
220+ Err ( _) => break ,
221+ } ;
222+
223+ output. extend_from_slice ( & buf[ ..n] ) ;
224+ let _ = err. write_all ( & buf[ ..n] ) ;
225+ let _ = err. flush ( ) ;
226+ }
227+
228+ let _ = stderr_tx. send ( output) ;
229+ } ) ;
230+
231+ // Wait for the child process to complete
232+ let status = child. wait ( ) . expect ( "Failed to wait on child process" ) ;
233+
234+ // Wait for threads to finish and collect output
235+ stdout_thread. join ( ) . expect ( "Failed to join stdout thread" ) ;
236+ stderr_thread. join ( ) . expect ( "Failed to join stderr thread" ) ;
237+
238+ let stdout_bytes = stdout_rx. recv ( ) . unwrap_or_default ( ) ;
239+ let stderr_bytes = stderr_rx. recv ( ) . unwrap_or_default ( ) ;
240+
135241 CommandResults {
136- stdout : "" . into ( ) ,
137- stderr : "" . into ( ) ,
138- success : s . success ( ) ,
242+ stdout : String :: from_utf8_lossy ( & stdout_bytes ) . into ( ) ,
243+ stderr : String :: from_utf8_lossy ( & stderr_bytes ) . into ( ) ,
244+ success : status . success ( ) ,
139245 }
140246 } else {
141247 let o = cmd. output ( ) . expect ( "failed to execute process" ) ;
@@ -275,4 +381,39 @@ mod tests {
275381 assert ! ( alt_output. contains( "echo" ) ) ;
276382 assert ! ( alt_output. contains( "hello world" ) ) ;
277383 }
384+
385+ #[ test]
386+ fn test_call_live ( ) {
387+ let result = Command :: shell ( "echo stdout_test && echo stderr_test >&2" )
388+ . live ( )
389+ . try_call ( ) ;
390+
391+ assert ! ( result. success) ;
392+ assert_eq ! ( result. stdout. trim( ) , "stdout_test" ) ;
393+ assert_eq ! ( result. stderr. trim( ) , "stderr_test" ) ;
394+ }
395+
396+ #[ test]
397+ fn test_call_live_multiline ( ) {
398+ let result = Command :: shell ( "echo line1 && echo line2 && echo line3" )
399+ . live ( )
400+ . try_call ( ) ;
401+ assert ! ( result. success) ;
402+ assert ! ( result. stdout. contains( "line1" ) ) ;
403+ assert ! ( result. stdout. contains( "line2" ) ) ;
404+ assert ! ( result. stdout. contains( "line3" ) ) ;
405+ }
406+
407+ #[ test]
408+ fn test_call_live_utf8 ( ) {
409+ // Test with various UTF-8 multi-byte characters
410+ let result = Command :: shell ( "echo こんにちは世界 🌸 おちゃ café" )
411+ . live ( )
412+ . try_call ( ) ;
413+
414+ assert ! ( result. success) ;
415+ assert ! ( result. stdout. contains( "こんにちは世界" ) ) ;
416+ assert ! ( result. stdout. contains( "🌸" ) ) ;
417+ assert ! ( result. stdout. contains( "café" ) ) ;
418+ }
278419}
0 commit comments