jabba_lib/jprocess.rs
1//! # process
2//!
3//! Working with external commands.
4//!
5//! - call an external command and get its exit code, stdout, and stderr
6//! - call an external command (and see its output on the stdout)
7
8use shlex;
9
10use std::process;
11
12/// Stores process information: exit code, stdout, stderr.
13#[allow(dead_code)]
14#[derive(Debug)]
15pub struct ProcStat {
16 pub exit_code: i32,
17 pub stdout: String,
18 pub stderr: String,
19}
20
21impl ProcStat {
22 /// Returns a copy of the output.
23 ///
24 /// Use it if you want to avoid value moving.
25 pub fn output(&self) -> String {
26 self.stdout.clone()
27 }
28
29 /// Trims the trailing whitespaces from the output.
30 pub fn trimmed_output(&self) -> String {
31 self.stdout.trim_end().to_string()
32 }
33}
34
35/// Executes an external command and gets its exit code, stdout and stderr.
36///
37/// It waits for the command to complete.
38///
39/// The three values are returned in a `ProcStat` structure.
40///
41/// The command must be a simple command with some optional arguments.
42/// Pipes, redirections are not allowed.
43///
44/// # Examples
45///
46/// ```
47/// let commands = vec![
48/// r#"python -c "print('Hello Rust!')""#,
49/// "python --version",
50/// ];
51///
52/// for cmd in commands.iter() {
53/// let stat = jabba_lib::jprocess::get_exitcode_stdout_stderr(cmd).unwrap();
54/// println!("{:?}", stat);
55/// }
56///
57/// let answer = jabba_lib::jprocess::get_exitcode_stdout_stderr("rustc --version")
58/// .unwrap()
59/// .trimmed_output(); // no trailing whitespaces
60/// println!("{:?}", answer);
61/// ```
62///
63/// # Sample Output
64///
65/// ```text
66/// ProcStat { exit_code: 0, stdout: "Hello Rust!\n", stderr: "" }
67/// ProcStat { exit_code: 0, stdout: "Python 3.10.5\n", stderr: "" }
68/// "rustc 1.62.1 (e092d0b6b 2022-07-16)"
69/// ```
70pub fn get_exitcode_stdout_stderr(cmd: &str) -> Option<ProcStat> {
71 let parts = shlex::split(cmd).unwrap_or_else(|| panic!("cannot parse command {:?}", cmd));
72 let head = &parts[0];
73 let tail = &parts[1..];
74
75 let mut p = process::Command::new(head);
76 p.args(tail);
77 let p = p
78 .output()
79 .unwrap_or_else(|_| panic!("failed to execute {:?}", cmd));
80
81 let result = ProcStat {
82 exit_code: p.status.code()?,
83 stdout: String::from_utf8_lossy(&p.stdout).to_string(),
84 stderr: String::from_utf8_lossy(&p.stderr).to_string(),
85 };
86
87 Some(result)
88}
89
90/// Executes an external command and waits for it to complete.
91///
92/// The command's output goes to stdout (i.e., not captured).
93/// Similar to Python's `os.system("something")`.
94///
95/// The command must be a simple command with some optional arguments.
96/// Pipes, redirections are not allowed.
97///
98/// # Examples
99///
100/// ```
101/// let cmd = "rustc --version";
102/// jabba_lib::jprocess::exec_cmd(cmd);
103/// ```
104///
105/// # Sample Output
106///
107/// ```text
108/// rustc 1.62.1 (e092d0b6b 2022-07-16)
109/// ```
110pub fn exec_cmd(cmd: &str) {
111 let parts = shlex::split(cmd).unwrap();
112 let head = &parts[0];
113 let tail = &parts[1..];
114
115 let mut p = process::Command::new(head);
116 p.args(tail);
117 let mut child = p
118 .spawn()
119 .unwrap_or_else(|_| panic!("command {:?} failed to start", cmd));
120 child.wait().expect("command wasn't running");
121}
122
123/// Executes an external command in the background (i.e., it doesn't wait for it to complete).
124///
125/// The command's output goes to stdout (i.e., not captured).
126/// Similar to Python's `os.system("something &")`.
127///
128/// The command must be a simple command with some optional arguments.
129/// Pipes, redirections are not allowed.
130///
131/// # Examples
132///
133/// ```
134/// let cmd = "rustc --version";
135/// jabba_lib::jprocess::exec_cmd_in_bg(cmd);
136/// ```
137///
138/// # Sample Output
139///
140/// ```text
141/// rustc 1.62.1 (e092d0b6b 2022-07-16)
142/// ```
143pub fn exec_cmd_in_bg(cmd: &str) {
144 let parts = shlex::split(cmd).unwrap();
145 let head = &parts[0];
146 let tail = &parts[1..];
147
148 process::Command::new(head)
149 .args(tail)
150 .spawn()
151 .unwrap_or_else(|_| panic!("command {:?} failed to start", cmd));
152}
153
154// ==========================================================================
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn get_exitcode_stdout_stderr_exit_code_test() {
162 use which::which;
163
164 let cmd = "rustc";
165 let result = which(cmd);
166 if result.is_err() {
167 return;
168 }
169 // else, if "rustc" is available (should be...)
170 let cmd = "rustc --version";
171 let stat = get_exitcode_stdout_stderr(cmd).unwrap();
172 assert_eq!(stat.exit_code, 0);
173 }
174
175 #[test]
176 fn get_exitcode_stdout_stderr_stdout_test() {
177 use which::which;
178
179 let cmd = "rustc";
180 let result = which(cmd);
181 if result.is_err() {
182 return;
183 }
184 // else, if "rustc" is available (should be...)
185 let cmd = "rustc --version";
186 let stat = get_exitcode_stdout_stderr(cmd).unwrap();
187 assert!(stat.stdout.starts_with("rustc"));
188 }
189
190 #[test]
191 fn get_exitcode_stdout_stderr_stderr_test() {
192 use which::which;
193
194 let cmd = "rustc";
195 let result = which(cmd);
196 if result.is_err() {
197 return;
198 }
199 // else, if "rustc" is available (should be...)
200 let cmd = "rustc --nothing20220731"; // this option doesn't exist
201 let stat = get_exitcode_stdout_stderr(cmd).unwrap();
202 assert!(stat.exit_code != 0);
203 assert!(stat.stderr.len() > 0);
204 }
205
206 #[test]
207 fn trimmed_output_test() {
208 use which::which;
209
210 let cmd = "rustc";
211 let result = which(cmd);
212 if result.is_err() {
213 return;
214 }
215 // else, if "rustc" is available (should be...)
216 let cmd = "rustc --version";
217 let stat = get_exitcode_stdout_stderr(cmd).unwrap();
218 assert!(stat.output() == stat.stdout.clone());
219 assert!(stat.trimmed_output().len() < stat.output().len());
220 }
221
222 #[test]
223 fn exec_cmd_test() {
224 let cmd = "rustc --version";
225 exec_cmd(cmd);
226 }
227
228 #[test]
229 fn exec_cmd_in_bg_test() {
230 let cmd = "rustc --version";
231 exec_cmd_in_bg(cmd);
232 }
233}