Skip to main content

conc/library/
run.rs

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