asimov_cli/commands/
external.rs

1// This is free and unencumbered software released into the public domain.
2
3use clientele::SysexitsError::{self, *};
4use std::process::{ExitStatus, Stdio};
5
6use crate::shared::locate_subcommand;
7use crate::Result;
8
9pub struct ExternalResult {
10    /// Return code of the executed command.
11    pub code: SysexitsError,
12
13    /// If `pipe_output` is `true`, this field contains stdout, otherwise its None.
14    pub stdout: Option<Vec<u8>>,
15
16    /// If `pipe_output` is `true`, this field contains stderr, otherwise its None.
17    pub stderr: Option<Vec<u8>>,
18}
19
20/// Executes the given subcommand.
21pub struct External {
22    pub is_debug: bool,
23    pub pipe_output: bool,
24}
25
26impl External {
27    pub fn execute(&self, cmd: &str, args: &[String]) -> Result<ExternalResult> {
28        // Locate the given subcommand:
29        let cmd = locate_subcommand(cmd)?;
30
31        // Prepare the process:
32        let result: std::io::Result<(ExitStatus, Option<Vec<u8>>, Option<Vec<u8>>)> =
33            if self.pipe_output {
34                std::process::Command::new(&cmd.path)
35                    .args(args)
36                    .stdin(Stdio::inherit())
37                    .stdout(Stdio::piped())
38                    .stderr(Stdio::piped())
39                    .output()
40                    .map(|x| (x.status, Some(x.stdout), Some(x.stderr)))
41            } else {
42                std::process::Command::new(&cmd.path)
43                    .args(args)
44                    .stdin(Stdio::inherit())
45                    .stdout(Stdio::inherit())
46                    .stderr(Stdio::inherit())
47                    .status()
48                    .map(|x| (x, None, None))
49            };
50
51        match result {
52            Err(error) => {
53                if self.is_debug {
54                    eprintln!("{}: {}", "asimov", error);
55                }
56                Err(EX_SOFTWARE)
57            }
58            Ok(result) => {
59                #[cfg(unix)]
60                {
61                    use std::os::unix::process::ExitStatusExt;
62
63                    if let Some(signal) = result.0.signal() {
64                        if self.is_debug {
65                            eprintln!("{}: terminated by signal {}", "asimov", signal);
66                        }
67
68                        return Ok(ExternalResult {
69                            code: SysexitsError::try_from((signal | 0x80) & 0xff)
70                                .unwrap_or(EX_SOFTWARE),
71                            stdout: result.1,
72                            stderr: result.2,
73                        });
74                    }
75                }
76
77                Ok(ExternalResult {
78                    // unwrap_or should never happen because we are handling signal above.
79                    code: result
80                        .0
81                        .code()
82                        .and_then(|code| SysexitsError::try_from(code).ok())
83                        .unwrap_or(EX_SOFTWARE),
84                    stdout: result.1,
85                    stderr: result.2,
86                })
87            }
88        }
89    }
90}