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