Skip to main content

conc/library/
run.rs

1use super::CallResult;
2use super::Show;
3use crate::library::subshell;
4use colored::Colorize;
5use std::io::{self, Write};
6use std::process::Command;
7use std::process::ExitCode;
8use std::sync::mpsc;
9use std::thread;
10
11/// all information Conc needs to execute a command
12#[derive(Debug)]
13pub struct Executable {
14    /// how the command will be displayed
15    pub name: String,
16
17    /// the command to execute
18    pub command: Command,
19}
20
21/// named arguments for the `run` function
22#[derive(Debug)]
23pub struct RunArgs {
24    /// the commands to execute concurrently
25    pub executables: Vec<Executable>,
26
27    /// whether to error if any command produces output
28    pub error_on_output: bool,
29
30    /// which output to display
31    pub show: Show,
32}
33
34/// Runs the given commands concurrently, prints their results, and returns the highest exit code.
35///
36/// # Examples
37///
38/// ```
39/// use conc::{Executable, RunArgs, Show, run, shell_executable};
40/// use std::process::ExitCode;
41/// use std::process::Command;
42///
43/// let mut command = Command::new("echo");
44/// command.arg("one");
45/// let executable1 = Executable {
46///     name: "echo one".into(),
47///     command,
48/// };
49/// let executable2 = shell_executable("echo two");
50/// let args = RunArgs {
51///     executables: vec![executable1, executable2],
52///     error_on_output: false,
53///     show: Show::All,
54/// };
55///
56/// let exit_code = run(args);
57/// assert_eq!(exit_code, ExitCode::SUCCESS);
58/// ```
59#[must_use]
60pub fn run(args: RunArgs) -> ExitCode {
61    let (send, receive) = mpsc::channel();
62
63    // execute all commands concurrently and let them signal via the channel when they are done
64    for call in args.executables {
65        let send_clone = send.clone();
66        thread::spawn(move || {
67            let _ = send_clone.send(subshell::run(call));
68        });
69    }
70
71    // drop the original sender so the receiver knows when all senders are closed
72    drop(send);
73
74    // print results as they arrive and collect exit codes
75    let mut exit_code = 0;
76    for call_result in receive {
77        match call_result {
78            Ok(call_result) => {
79                exit_code = exit_code.max(call_result.exit_code());
80                let error_from_output = args.error_on_output && call_result.has_output();
81                if error_from_output {
82                    exit_code = exit_code.max(1);
83                }
84                let call_failed = !call_result.success() || error_from_output;
85                print_result(&call_result, call_failed, args.show);
86            }
87            Err(err) => {
88                eprintln!("{}", err.to_string().red());
89                exit_code = exit_code.max(1);
90            }
91        }
92    }
93    ExitCode::from(exit_code)
94}
95
96/// prints the result of a single command execution to stdout and stderr
97fn print_result(call_result: &CallResult, is_failed: bool, show: Show) {
98    let mut stdout = io::stdout();
99    let mut stderr = io::stderr();
100
101    // print command name
102    if show.display_command() {
103        let mut command = call_result.name.clone();
104        if is_failed {
105            let _ = writeln!(stdout, "{}", command.bold().red());
106        } else {
107            if show.display_success() {
108                command = command.bold().to_string();
109            }
110            let _ = writeln!(stdout, "{command}");
111        }
112    }
113
114    // print command output
115    if is_failed || show.display_success() {
116        write_output(&mut stdout, &call_result.output.stdout);
117        write_output(&mut stderr, &call_result.output.stderr);
118    }
119}
120
121fn write_output(writer: &mut dyn Write, output: &[u8]) {
122    if !output.is_empty() {
123        let _ = writer.write_all(output);
124        if !output.ends_with(b"\n") {
125            let _ = writer.write_all(b"\n");
126        }
127    }
128}