evobench_tools/
run_with_pre_exec.rs

1//! Run an external command after first executing some Bash code in-process
2
3use std::{borrow::Cow, ffi::OsStr, fmt::Display, path::Path, process::Command};
4
5use crate::io_utils::bash::bash_string_from_program_path_and_args;
6
7#[derive(Debug, Clone, Copy)]
8pub enum BashSettingsLevel {
9    None,
10    SetMEU,
11    SetMEUPipefail,
12}
13
14impl BashSettingsLevel {
15    pub fn str(self) -> &'static str {
16        match self {
17            BashSettingsLevel::None => "",
18            BashSettingsLevel::SetMEU => "set -meu",
19            BashSettingsLevel::SetMEUPipefail => "set -meuo pipefail",
20        }
21    }
22}
23
24pub struct BashSettings {
25    pub level: BashSettingsLevel,
26    pub set_ifs: bool,
27}
28
29impl Display for BashSettings {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        write!(
32            f,
33            "{}\n{}",
34            self.level.str(),
35            if self.set_ifs { "IFS=\n" } else { "" }
36        )
37    }
38}
39
40pub struct RunWithPreExec<'t> {
41    pub pre_exec_bash_code: Cow<'t, str>,
42    pub bash_settings: BashSettings,
43    pub bash_path: Option<Cow<'t, Path>>,
44}
45
46impl<'t> RunWithPreExec<'t> {
47    /// Return a command that executes the given command and args
48    /// directly if `pre_exec_bash_code` is empty (after trim), or
49    /// executes "bash" or the given `bash_path` with the given
50    /// pre-exec code plus `exec` to the given command and args.  The
51    /// `command_path` has to be a str since it must be possible to
52    /// represent it as unicode to be made part of bash code.
53    pub fn command<S: AsRef<OsStr> + AsRef<str>>(
54        &self,
55        command_path: &str,
56        args: impl AsRef<[S]>,
57    ) -> Command {
58        let Self {
59            pre_exec_bash_code,
60            bash_settings,
61            bash_path,
62        } = self;
63
64        if pre_exec_bash_code.trim().is_empty() {
65            let mut command = Command::new(command_path);
66            command.args(args.as_ref());
67            command
68        } else {
69            let mut command = {
70                let bash: &Path = "bash".as_ref();
71                let bash = bash_path.as_ref().map(AsRef::as_ref).unwrap_or(bash);
72                Command::new(bash)
73            };
74            command.arg("-c");
75            {
76                let mut code = bash_settings.to_string();
77                code.push_str(&pre_exec_bash_code);
78                code.push_str("\n\nexec ");
79                code.push_str(&bash_string_from_program_path_and_args(
80                    command_path,
81                    args.as_ref(),
82                ));
83                command.arg(code);
84            }
85            command
86        }
87    }
88}
89
90pub fn join_pre_exec_bash_code(a: &str, b: &str) -> String {
91    let mut s = String::new();
92    if !a.trim().is_empty() {
93        s.push_str(a);
94        s.push_str("\n\n");
95    }
96    if !b.trim().is_empty() {
97        s.push_str(b);
98        s.push_str("\n\n");
99    }
100    s
101}