Skip to main content

conc/library/
subshell.rs

1use crate::library::run::Executable;
2use std::io;
3use std::process::Command;
4
5/// executes the given command
6pub fn run(Executable { mut command, name }: Executable) -> Result<CallResult, RunError> {
7    match command.output() {
8        Ok(output) => Ok(CallResult { name, output }),
9        Err(error) => Err(RunError { name, error }),
10    }
11}
12
13/// `CallResult` represents the result of a single command execution.
14pub struct CallResult {
15    pub name: String,
16    pub output: std::process::Output,
17}
18
19impl CallResult {
20    pub(crate) fn exit_code(&self) -> u8 {
21        if self.output.status.success() {
22            0
23        } else {
24            to_exitcode_u8(self.output.status.code().unwrap_or(1))
25        }
26    }
27
28    /// indicates whether this call produced any output to STDOUT or STDERR
29    pub(crate) fn has_output(&self) -> bool {
30        !self.output.stdout.is_empty() || !self.output.stderr.is_empty()
31    }
32
33    /// indicates whether this call exited with a success code
34    pub(crate) fn success(&self) -> bool {
35        self.output.status.success()
36    }
37}
38
39/// Creates an Executable that runs the given command
40/// in a shell environment so that shell features can be used.
41///
42/// In Unix-like environments, this uses the `sh` shell.
43/// In Windows, it uses `cmd.exe`.
44#[must_use]
45pub fn shell_executable<IS: Into<String>>(command: IS) -> Executable {
46    let name = command.into();
47    let command = shell_command(&name);
48    Executable { name, command }
49}
50
51/// Provides a Command instance that executes the given command
52/// in a shell environment so that shell features can be used.
53///
54/// In Unix-like environments, this uses the `sh` shell.
55/// In Windows, it uses `cmd.exe`.
56#[cfg(unix)]
57#[must_use]
58pub fn shell_command(command: &str) -> Command {
59    let mut cmd = Command::new("sh");
60    cmd.arg("-c").arg(command);
61    cmd
62}
63
64/// Provides a Command instance that executes the given command
65/// in a shell environment so that shell features can be used.
66///
67/// In Unix-like environments, this uses the `sh` shell.
68/// In Windows, it uses `cmd.exe`.
69#[cfg(windows)]
70#[must_use]
71pub fn shell_command(command: &str) -> Command {
72    let mut cmd = Command::new("cmd.exe");
73    cmd.arg("/C").arg(command);
74    cmd
75}
76
77fn to_exitcode_u8(value: i32) -> u8 {
78    if value == i32::MIN {
79        return 255;
80    }
81    u8::try_from(value.abs()).unwrap_or(255)
82}
83
84pub struct RunError {
85    /// display version of the command that failed to execute
86    pub name: String,
87    /// the error that occurred while executing the command
88    pub error: io::Error,
89}
90
91impl std::fmt::Display for RunError {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        write!(f, "Cannot execute '{}': {}", self.name, self.error)
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_safe_convert_to_u8() {
103        assert_eq!(to_exitcode_u8(0), 0);
104        assert_eq!(to_exitcode_u8(1), 1);
105        assert_eq!(to_exitcode_u8(-1), 1);
106        assert_eq!(to_exitcode_u8(255), 255);
107        assert_eq!(to_exitcode_u8(-255), 255);
108        assert_eq!(to_exitcode_u8(256), 255);
109        assert_eq!(to_exitcode_u8(-256), 255);
110        assert_eq!(to_exitcode_u8(i32::MAX), 255);
111        assert_eq!(to_exitcode_u8(i32::MIN), 255);
112    }
113}