@@ -18,8 +18,10 @@ use std::os::windows::process::CommandExt;
1818
1919#[ cfg( windows) ]
2020const CREATE_NO_WINDOW : u32 = 0x0800_0000 ;
21+ const NEWLINE_BYTE : u8 = b'\n' ;
2122
2223use crate :: async_runtime:: { block_on as block_on_task, channel, Receiver , Sender } ;
24+
2325pub use encoding_rs:: Encoding ;
2426use os_pipe:: { pipe, PipeReader , PipeWriter } ;
2527use serde:: Serialize ;
@@ -54,14 +56,13 @@ pub struct TerminatedPayload {
5456}
5557
5658/// A event sent to the command callback.
57- #[ derive( Debug , Clone , Serialize ) ]
58- #[ serde( tag = "event" , content = "payload" ) ]
59+ #[ derive( Debug , Clone ) ]
5960#[ non_exhaustive]
6061pub enum CommandEvent {
6162 /// Stderr bytes until a newline (\n) or carriage return (\r) is found.
62- Stderr ( String ) ,
63+ Stderr ( Vec < u8 > ) ,
6364 /// Stdout bytes until a newline (\n) or carriage return (\r) is found.
64- Stdout ( String ) ,
65+ Stdout ( Vec < u8 > ) ,
6566 /// An error happened waiting for the command to finish or converting the stdout/stderr bytes to an UTF-8 string.
6667 Error ( String ) ,
6768 /// Command process terminated.
@@ -76,7 +77,6 @@ pub struct Command {
7677 env_clear : bool ,
7778 env : HashMap < String , String > ,
7879 current_dir : Option < PathBuf > ,
79- encoding : Option < & ' static Encoding > ,
8080}
8181
8282/// Spawned child process.
@@ -129,9 +129,9 @@ pub struct Output {
129129 /// The status (exit code) of the process.
130130 pub status : ExitStatus ,
131131 /// The data that the process wrote to stdout.
132- pub stdout : String ,
132+ pub stdout : Vec < u8 > ,
133133 /// The data that the process wrote to stderr.
134- pub stderr : String ,
134+ pub stderr : Vec < u8 > ,
135135}
136136
137137fn relative_command_path ( command : String ) -> crate :: Result < String > {
@@ -173,7 +173,6 @@ impl Command {
173173 env_clear : false ,
174174 env : Default :: default ( ) ,
175175 current_dir : None ,
176- encoding : None ,
177176 }
178177 }
179178
@@ -219,13 +218,6 @@ impl Command {
219218 self
220219 }
221220
222- /// Sets the character encoding for stdout/stderr.
223- #[ must_use]
224- pub fn encoding ( mut self , encoding : & ' static Encoding ) -> Self {
225- self . encoding . replace ( encoding) ;
226- self
227- }
228-
229221 /// Spawns the command.
230222 ///
231223 /// # Examples
@@ -241,7 +233,7 @@ impl Command {
241233 /// let mut i = 0;
242234 /// while let Some(event) = rx.recv().await {
243235 /// if let CommandEvent::Stdout(line) = event {
244- /// println!("got: {}", line);
236+ /// println!("got: {}", String::from_utf8( line).unwrap() );
245237 /// i += 1;
246238 /// if i == 4 {
247239 /// child.write("message from Rust\n".as_bytes()).unwrap();
@@ -252,7 +244,6 @@ impl Command {
252244 /// });
253245 /// ```
254246 pub fn spawn ( self ) -> crate :: api:: Result < ( Receiver < CommandEvent > , CommandChild ) > {
255- let encoding = self . encoding ;
256247 let mut command: StdCommand = self . into ( ) ;
257248 let ( stdout_reader, stdout_writer) = pipe ( ) ?;
258249 let ( stderr_reader, stderr_writer) = pipe ( ) ?;
@@ -275,14 +266,12 @@ impl Command {
275266 guard. clone ( ) ,
276267 stdout_reader,
277268 CommandEvent :: Stdout ,
278- encoding,
279269 ) ;
280270 spawn_pipe_reader (
281271 tx. clone ( ) ,
282272 guard. clone ( ) ,
283273 stderr_reader,
284274 CommandEvent :: Stderr ,
285- encoding,
286275 ) ;
287276
288277 spawn ( move || {
@@ -350,27 +339,28 @@ impl Command {
350339 /// use tauri::api::process::Command;
351340 /// let output = Command::new("echo").args(["TAURI"]).output().unwrap();
352341 /// assert!(output.status.success());
353- /// assert_eq!(output.stdout, "TAURI");
342+ /// assert_eq!(String::from_utf8( output.stdout).unwrap() , "TAURI");
354343 /// ```
355344 pub fn output ( self ) -> crate :: api:: Result < Output > {
356345 let ( mut rx, _child) = self . spawn ( ) ?;
357346
358347 let output = crate :: async_runtime:: safe_block_on ( async move {
359348 let mut code = None ;
360- let mut stdout = String :: new ( ) ;
361- let mut stderr = String :: new ( ) ;
349+ let mut stdout = Vec :: new ( ) ;
350+ let mut stderr = Vec :: new ( ) ;
351+
362352 while let Some ( event) = rx. recv ( ) . await {
363353 match event {
364354 CommandEvent :: Terminated ( payload) => {
365355 code = payload. code ;
366356 }
367357 CommandEvent :: Stdout ( line) => {
368- stdout. push_str ( line. as_str ( ) ) ;
369- stdout. push ( '\n' ) ;
358+ stdout. extend ( line) ;
359+ stdout. push ( NEWLINE_BYTE ) ;
370360 }
371361 CommandEvent :: Stderr ( line) => {
372- stderr. push_str ( line. as_str ( ) ) ;
373- stderr. push ( '\n' ) ;
362+ stderr. extend ( line) ;
363+ stderr. push ( NEWLINE_BYTE ) ;
374364 }
375365 CommandEvent :: Error ( _) => { }
376366 }
@@ -386,36 +376,25 @@ impl Command {
386376 }
387377}
388378
389- fn spawn_pipe_reader < F : Fn ( String ) -> CommandEvent + Send + Copy + ' static > (
379+ fn spawn_pipe_reader < F : Fn ( Vec < u8 > ) -> CommandEvent + Send + Copy + ' static > (
390380 tx : Sender < CommandEvent > ,
391381 guard : Arc < RwLock < ( ) > > ,
392382 pipe_reader : PipeReader ,
393383 wrapper : F ,
394- character_encoding : Option < & ' static Encoding > ,
395384) {
396385 spawn ( move || {
397386 let _lock = guard. read ( ) . unwrap ( ) ;
398387 let mut reader = BufReader :: new ( pipe_reader) ;
399388
400- let mut buf = Vec :: new ( ) ;
401389 loop {
402- buf. clear ( ) ;
390+ let mut buf = Vec :: new ( ) ;
403391 match tauri_utils:: io:: read_line ( & mut reader, & mut buf) {
404392 Ok ( n) => {
405393 if n == 0 {
406394 break ;
407395 }
408396 let tx_ = tx. clone ( ) ;
409- let line = match character_encoding {
410- Some ( encoding) => Ok ( encoding. decode_with_bom_removal ( & buf) . 0 . into ( ) ) ,
411- None => String :: from_utf8 ( buf. clone ( ) ) ,
412- } ;
413- block_on_task ( async move {
414- let _ = match line {
415- Ok ( line) => tx_. send ( wrapper ( line) ) . await ,
416- Err ( e) => tx_. send ( CommandEvent :: Error ( e. to_string ( ) ) ) . await ,
417- } ;
418- } ) ;
397+ let _ = block_on_task ( async move { tx_. send ( wrapper ( buf) ) . await } ) ;
419398 }
420399 Err ( e) => {
421400 let tx_ = tx. clone ( ) ;
@@ -428,14 +407,34 @@ fn spawn_pipe_reader<F: Fn(String) -> CommandEvent + Send + Copy + 'static>(
428407
429408// tests for the commands functions.
430409#[ cfg( test) ]
431- mod test {
410+ mod tests {
432411 #[ cfg( not( windows) ) ]
433412 use super :: * ;
434413
435414 #[ cfg( not( windows) ) ]
436415 #[ test]
437- fn test_cmd_output ( ) {
438- // create a command to run cat.
416+ fn test_cmd_spawn_output ( ) {
417+ let cmd = Command :: new ( "cat" ) . args ( [ "test/api/test.txt" ] ) ;
418+ let ( mut rx, _) = cmd. spawn ( ) . unwrap ( ) ;
419+
420+ crate :: async_runtime:: block_on ( async move {
421+ while let Some ( event) = rx. recv ( ) . await {
422+ match event {
423+ CommandEvent :: Terminated ( payload) => {
424+ assert_eq ! ( payload. code, Some ( 0 ) ) ;
425+ }
426+ CommandEvent :: Stdout ( line) => {
427+ assert_eq ! ( String :: from_utf8( line) . unwrap( ) , "This is a test doc!" ) ;
428+ }
429+ _ => { }
430+ }
431+ }
432+ } ) ;
433+ }
434+
435+ #[ cfg( not( windows) ) ]
436+ #[ test]
437+ fn test_cmd_spawn_raw_output ( ) {
439438 let cmd = Command :: new ( "cat" ) . args ( [ "test/api/test.txt" ] ) ;
440439 let ( mut rx, _) = cmd. spawn ( ) . unwrap ( ) ;
441440
@@ -446,7 +445,7 @@ mod test {
446445 assert_eq ! ( payload. code, Some ( 0 ) ) ;
447446 }
448447 CommandEvent :: Stdout ( line) => {
449- assert_eq ! ( line, "This is a test doc!" . to_string ( ) ) ;
448+ assert_eq ! ( String :: from_utf8 ( line) . unwrap ( ) , "This is a test doc!" ) ;
450449 }
451450 _ => { }
452451 }
@@ -457,7 +456,32 @@ mod test {
457456 #[ cfg( not( windows) ) ]
458457 #[ test]
459458 // test the failure case
460- fn test_cmd_fail ( ) {
459+ fn test_cmd_spawn_fail ( ) {
460+ let cmd = Command :: new ( "cat" ) . args ( [ "test/api/" ] ) ;
461+ let ( mut rx, _) = cmd. spawn ( ) . unwrap ( ) ;
462+
463+ crate :: async_runtime:: block_on ( async move {
464+ while let Some ( event) = rx. recv ( ) . await {
465+ match event {
466+ CommandEvent :: Terminated ( payload) => {
467+ assert_eq ! ( payload. code, Some ( 1 ) ) ;
468+ }
469+ CommandEvent :: Stderr ( line) => {
470+ assert_eq ! (
471+ String :: from_utf8( line) . unwrap( ) ,
472+ "cat: test/api/: Is a directory"
473+ ) ;
474+ }
475+ _ => { }
476+ }
477+ }
478+ } ) ;
479+ }
480+
481+ #[ cfg( not( windows) ) ]
482+ #[ test]
483+ // test the failure case (raw encoding)
484+ fn test_cmd_spawn_raw_fail ( ) {
461485 let cmd = Command :: new ( "cat" ) . args ( [ "test/api/" ] ) ;
462486 let ( mut rx, _) = cmd. spawn ( ) . unwrap ( ) ;
463487
@@ -468,11 +492,40 @@ mod test {
468492 assert_eq ! ( payload. code, Some ( 1 ) ) ;
469493 }
470494 CommandEvent :: Stderr ( line) => {
471- assert_eq ! ( line, "cat: test/api/: Is a directory" . to_string( ) ) ;
495+ assert_eq ! (
496+ String :: from_utf8( line) . unwrap( ) ,
497+ "cat: test/api/: Is a directory"
498+ ) ;
472499 }
473500 _ => { }
474501 }
475502 }
476503 } ) ;
477504 }
505+
506+ #[ cfg( not( windows) ) ]
507+ #[ test]
508+ fn test_cmd_output_output ( ) {
509+ let cmd = Command :: new ( "cat" ) . args ( [ "test/api/test.txt" ] ) ;
510+ let output = cmd. output ( ) . unwrap ( ) ;
511+
512+ assert_eq ! ( String :: from_utf8( output. stderr) . unwrap( ) , "" ) ;
513+ assert_eq ! (
514+ String :: from_utf8( output. stdout) . unwrap( ) ,
515+ "This is a test doc!\n "
516+ ) ;
517+ }
518+
519+ #[ cfg( not( windows) ) ]
520+ #[ test]
521+ fn test_cmd_output_output_fail ( ) {
522+ let cmd = Command :: new ( "cat" ) . args ( [ "test/api/" ] ) ;
523+ let output = cmd. output ( ) . unwrap ( ) ;
524+
525+ assert_eq ! ( String :: from_utf8( output. stdout) . unwrap( ) , "" ) ;
526+ assert_eq ! (
527+ String :: from_utf8( output. stderr) . unwrap( ) ,
528+ "cat: test/api/: Is a directory\n "
529+ ) ;
530+ }
478531}
0 commit comments