tauri_api/
command.rs

1use std::process::{Child, Command, Stdio};
2
3#[cfg(windows)]
4use std::os::windows::process::CommandExt;
5
6#[cfg(windows)]
7const CREATE_NO_WINDOW: u32 = 0x0800_0000;
8
9use tauri_utils::platform;
10
11/// Gets the output of the given command.
12#[cfg(not(windows))]
13pub fn get_output(cmd: String, args: Vec<String>, stdout: Stdio) -> crate::Result<String> {
14  let output = Command::new(cmd).args(args).stdout(stdout).output()?;
15
16  if output.status.success() {
17    Ok(String::from_utf8_lossy(&output.stdout).to_string())
18  } else {
19    Err(crate::Error::Command(String::from_utf8_lossy(&output.stderr).to_string()).into())
20  }
21}
22
23/// Gets the output of the given command.
24#[cfg(windows)]
25pub fn get_output(cmd: String, args: Vec<String>, stdout: Stdio) -> crate::Result<String> {
26  let output = Command::new(cmd)
27    .args(args)
28    .stdout(stdout)
29    .creation_flags(CREATE_NO_WINDOW)
30    .output()?;
31
32  if output.status.success() {
33    Ok(String::from_utf8_lossy(&output.stdout).to_string())
34  } else {
35    Err(crate::Error::Command(String::from_utf8_lossy(&output.stderr).to_string()).into())
36  }
37}
38
39/// Gets the path to command relative to the current executable path.
40#[cfg(not(windows))]
41pub fn command_path(command: String) -> crate::Result<String> {
42  match std::env::current_exe()?.parent() {
43    Some(exe_dir) => Ok(format!("{}/{}", exe_dir.display().to_string(), command)),
44    None => Err(crate::Error::Command("Could not evaluate executable dir".to_string()).into()),
45  }
46}
47
48/// Gets the path to command relative to the current executable path.
49#[cfg(windows)]
50pub fn command_path(command: String) -> crate::Result<String> {
51  match std::env::current_exe()?.parent() {
52    Some(exe_dir) => Ok(format!("{}/{}.exe", exe_dir.display().to_string(), command)),
53    None => Err(crate::Error::Command("Could not evaluate executable dir".to_string()).into()),
54  }
55}
56
57/// Spawns a process with a command string relative to the current executable path.
58/// For example, if your app bundles two executables, you don't need to worry about its path and just run `second-app`.
59#[cfg(windows)]
60pub fn spawn_relative_command(
61  command: String,
62  args: Vec<String>,
63  stdout: Stdio,
64) -> crate::Result<Child> {
65  let cmd = command_path(command)?;
66  Ok(
67    Command::new(cmd)
68      .args(args)
69      .creation_flags(CREATE_NO_WINDOW)
70      .stdout(stdout)
71      .spawn()?,
72  )
73}
74
75/// Spawns a process with a command string relative to the current executable path.
76/// For example, if your app bundles two executables, you don't need to worry about its path and just run `second-app`.
77#[cfg(not(windows))]
78pub fn spawn_relative_command(
79  command: String,
80  args: Vec<String>,
81  stdout: Stdio,
82) -> crate::Result<Child> {
83  let cmd = command_path(command)?;
84  Ok(Command::new(cmd).args(args).stdout(stdout).spawn()?)
85}
86
87/// Gets the binary command with the current target triple.
88pub fn binary_command(binary_name: String) -> crate::Result<String> {
89  Ok(format!("{}-{}", binary_name, platform::target_triple()?))
90}
91
92// tests for the commands functions.
93#[cfg(test)]
94mod test {
95  use super::*;
96  use std::io;
97
98  #[cfg(not(windows))]
99  #[test]
100  // test the get_output function with a unix cat command.
101  fn test_cmd_output() {
102    // create a string with cat in it.
103    let cmd = String::from("cat");
104
105    // call get_output with cat and the argument test/test.txt on the stdio.
106    let res = get_output(cmd, vec!["test/test.txt".to_string()], Stdio::piped());
107
108    // assert that the result is an Ok() type
109    assert!(res.is_ok());
110
111    // if the assertion passes, assert the incoming data.
112    if let Ok(s) = &res {
113      // assert that cat returns the string in the test.txt document.
114      assert_eq!(*s, "This is a test doc!".to_string());
115    }
116  }
117
118  #[cfg(not(windows))]
119  #[test]
120  // test the failure case for get_output
121  fn test_cmd_fail() {
122    use crate::Error;
123
124    // queue up a string with cat in it.
125    let cmd = String::from("cat");
126
127    // call get output with test/ as an argument on the stdio.
128    let res = get_output(cmd, vec!["test/".to_string()], Stdio::piped());
129
130    // assert that the result is an Error type.
131    assert!(res.is_err());
132
133    // destruct the Error to check the ErrorKind and test that it is a Command type.
134    if let Some(Error::Command(e)) = res.unwrap_err().downcast_ref::<Error>() {
135      // assert that the message in the error matches this string.
136      assert_eq!(*e, "cat: test/: Is a directory\n".to_string());
137    }
138  }
139
140  #[test]
141  // test the command_path function
142  fn check_command_path() {
143    // generate a string for cat
144    let cmd = String::from("cat");
145
146    // call command_path on cat
147    let res = command_path(cmd);
148
149    // assert that the result is an OK() type.
150    assert!(res.is_ok());
151  }
152
153  #[test]
154  // check the spawn_relative_command function
155  fn check_spawn_cmd() {
156    // generate a cat string
157    let cmd = String::from("cat");
158
159    // call spawn_relative_command with cat and the argument test/test.txt on the Stdio.
160    let res = spawn_relative_command(cmd, vec!["test/test.txt".to_string()], Stdio::piped());
161
162    // this fails because there is no cat binary in the relative parent folder of this current executing command.
163    assert!(res.is_err());
164
165    // after asserting that the result is an error, check that the error kind is ErrorKind::Io
166    if let Some(s) = res.unwrap_err().downcast_ref::<io::Error>() {
167      // assert that the ErrorKind inside of the ErrorKind Io is ErrorKind::NotFound
168      assert_eq!(s.kind(), std::io::ErrorKind::NotFound);
169    }
170  }
171}