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    /// whether to redirect stderr output to stdout
31    pub stderr_to_stdout: bool,
32
33    /// which output to display
34    pub show: Show,
35}
36
37/// Runs the given commands concurrently, prints their results, and returns the highest exit code.
38///
39/// # Examples
40///
41/// ```
42/// use conc::{Executable, RunArgs, Show, run, shell_executable};
43/// use std::process::ExitCode;
44/// use std::process::Command;
45///
46/// let mut command = Command::new("echo");
47/// command.arg("one");
48/// let executable1 = Executable {
49///     name: "echo one".into(),
50///     command,
51/// };
52/// let executable2 = shell_executable("echo two");
53/// let args = RunArgs {
54///     executables: vec![executable1, executable2],
55///     error_on_output: false,
56///     stderr_to_stdout: false,
57///     show: Show::All,
58/// };
59///
60/// let exit_code = run(args);
61/// assert_eq!(exit_code, ExitCode::SUCCESS);
62/// ```
63#[must_use]
64pub fn run(args: RunArgs) -> ExitCode {
65    let (send, receive) = mpsc::channel();
66
67    // execute all commands concurrently and let them signal via the channel when they are done
68    for call in args.executables {
69        let send_clone = send.clone();
70        thread::spawn(move || {
71            let _ = send_clone.send(subshell::run(call));
72        });
73    }
74
75    // drop the original sender so the receiver knows when all senders are closed
76    drop(send);
77
78    // print results as they arrive and collect exit codes
79    let mut exit_code = 0;
80    for call_result in receive {
81        match call_result {
82            Ok(call_result) => {
83                exit_code = exit_code.max(call_result.exit_code());
84                let error_from_output = args.error_on_output && call_result.has_output();
85                if error_from_output {
86                    exit_code = exit_code.max(1);
87                }
88                let call_failed = !call_result.success() || error_from_output;
89                print_result(&call_result, call_failed, args.show, args.stderr_to_stdout);
90            }
91            Err(err) => {
92                eprintln!("{}", err.to_string().red());
93                exit_code = exit_code.max(1);
94            }
95        }
96    }
97    ExitCode::from(exit_code)
98}
99
100/// prints the result of a single command execution to stdout and stderr
101fn print_result(call_result: &CallResult, is_failed: bool, show: Show, stderr_to_stdout: bool) {
102    let mut stdout = io::stdout();
103    let mut stderr = io::stderr();
104
105    // print command name
106    if show.display_command() {
107        let mut command = call_result.name.clone();
108        if is_failed {
109            let _ = writeln!(stdout, "{}", command.bold().red());
110        } else {
111            if show.display_success() {
112                command = command.bold().to_string();
113            }
114            let _ = writeln!(stdout, "{command}");
115        }
116    }
117
118    // print command output
119    if is_failed || show.display_success() {
120        write_output(&mut stdout, &call_result.output.stdout);
121        if stderr_to_stdout {
122            write_output(&mut stdout, &call_result.output.stderr);
123        } else {
124            write_output(&mut stderr, &call_result.output.stderr);
125        }
126    }
127}
128
129fn write_output(writer: &mut dyn Write, output: &[u8]) {
130    if !output.is_empty() {
131        let _ = writer.write_all(output);
132        if !output.ends_with(b"\n") {
133            let _ = writer.write_all(b"\n");
134        }
135    }
136}