docker_pose/
cmd.rs

1/// Methods to perform command-line tool calls.
2/// Used by pose to make calls to the `docker` command
3/// and the `git` command.
4use crate::Verbosity;
5use colored::Colorize;
6use std::io::Write;
7use std::process::{Command, Output, Stdio};
8use std::{io, process};
9
10/// get a string that should be identical to a command-line tool
11/// call made by `std::process::Command`.
12///
13/// Used by ``cmd_call` for debugging.
14pub fn cmd_call_to_string(bin: &str, args: &[&str]) -> String {
15    format!(
16        "{} {}",
17        bin,
18        args.iter()
19            .map(|s| {
20                if s.contains(' ') {
21                    // TODO better escaping
22                    format!("\"{s}\"")
23                } else {
24                    s.to_string()
25                }
26            })
27            .collect::<Vec<_>>()
28            .join(" ")
29    )
30}
31
32pub fn cmd_call(
33    bin: &str,
34    args: &[&str],
35    output_stdout: bool,
36    output_stderr: bool,
37    verbosity: &Verbosity,
38) -> io::Result<Output> {
39    if matches!(verbosity, Verbosity::Verbose) {
40        eprintln!("{}: {}", "DEBUG".green(), cmd_call_to_string(bin, args));
41    }
42    let mut binding = Command::new(bin);
43    let mut command = binding.args(args);
44    command = command.stdout(Stdio::piped()).stderr(Stdio::piped());
45    let output = command.output()?; // an error not from the command but trying to execute it
46    if output_stdout {
47        cmd_write_stdout(bin, &output.stdout);
48    }
49    if output_stderr {
50        cmd_write_stderr(bin, &output.stderr);
51    }
52    Ok(output)
53}
54
55pub fn cmd_write_stderr(bin: &str, stderr: &[u8]) {
56    io::stderr().write_all(stderr).unwrap_or_else(|e| {
57        eprintln!("{}: writing {} stderr: {}", "ERROR".red(), bin, e);
58        process::exit(151);
59    });
60}
61
62pub fn cmd_write_stdout(bin: &str, stdout: &[u8]) {
63    io::stdout().write_all(stdout).unwrap_or_else(|e| {
64        eprintln!("{}: writing {} stdout: {}", "ERROR".red(), bin, e);
65        process::exit(151);
66    });
67}
68
69pub fn cmd_exit_code(bin: &str, output: &Output) -> i32 {
70    output.status.code().unwrap_or_else(|| {
71        eprintln!("{}: {} process terminated by signal", "ERROR".red(), bin);
72        process::exit(10)
73    })
74}
75
76/// Get the string from the `output` that was generated
77/// by a call to `bin bin_cmd`. If was not successful
78/// print the error and exit. If `quiet` is `false`
79/// also print any warning detected (stderr output).
80pub fn cmd_get_success_output_or_fail(
81    bin: &str,
82    bin_cmd: &str,
83    output: Output,
84    quiet: bool,
85) -> String {
86    match output.status.success() {
87        true => {
88            // success !
89            if !quiet && !output.stderr.is_empty() {
90                // although, there may be warnings sent to the stderr
91                eprintln!(
92                    "{}: the following are warnings from {}:",
93                    "WARN".yellow(),
94                    bin_cmd
95                );
96                cmd_write_stderr(bin, &output.stderr);
97            }
98            String::from_utf8(output.stdout).unwrap_or_else(|e| {
99                eprintln!(
100                    "{}: deserializing {} {} output: {}",
101                    "ERROR".red(),
102                    bin,
103                    bin_cmd,
104                    e,
105                );
106                process::exit(17);
107            })
108        }
109        false => {
110            eprintln!("{}: calling {}", "ERROR".red(), bin_cmd);
111            cmd_write_stderr(bin, &output.stderr);
112            process::exit(cmd_exit_code(bin, &output));
113        }
114    }
115}