Skip to main content

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::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 ExternalSubcommand {
21    pub is_debug: bool,
22    pub pipe_output: bool,
23}
24
25impl ExternalSubcommand {
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 = if self.pipe_output {
32            std::process::Command::new(&cmd.path)
33                .args(args.as_ref())
34                .stdin(Stdio::inherit())
35                .stdout(Stdio::piped())
36                .stderr(Stdio::piped())
37                .output()
38                .map(|x| (x.status, Some(x.stdout), Some(x.stderr)))
39        } else {
40            std::process::Command::new(&cmd.path)
41                .args(args.as_ref())
42                .stdin(Stdio::inherit())
43                .stdout(Stdio::inherit())
44                .stderr(Stdio::inherit())
45                .status()
46                .map(|x| (x, None, None))
47        };
48
49        match result {
50            Err(error) => {
51                if self.is_debug {
52                    eprintln!("asimov: {}", error);
53                }
54                Err(EX_SOFTWARE)
55            },
56            Ok(result) => {
57                #[cfg(unix)]
58                {
59                    use std::os::unix::process::ExitStatusExt;
60
61                    if let Some(signal) = result.0.signal() {
62                        if self.is_debug {
63                            eprintln!("asimov: terminated by signal {}", signal);
64                        }
65
66                        return Ok(ExternalResult {
67                            code: SysexitsError::try_from((signal | 0x80) & 0xff)
68                                .unwrap_or(EX_SOFTWARE),
69                            stdout: result.1,
70                            stderr: result.2,
71                        });
72                    }
73                }
74
75                Ok(ExternalResult {
76                    // unwrap_or should never happen because we are handling signal above.
77                    code: result
78                        .0
79                        .code()
80                        .and_then(|code| SysexitsError::try_from(code).ok())
81                        .unwrap_or(EX_SOFTWARE),
82                    stdout: result.1,
83                    stderr: result.2,
84                })
85            },
86        }
87    }
88}