matchmaker/spawn/
mod.rs

1pub mod preview;
2pub mod utils;
3
4use std::{
5    env, io::Write, process::{Child, Command, Stdio}, sync::LazyLock
6};
7
8use log::error;
9
10// todo: support -i
11pub fn spawn(
12    cmd: &str,
13    vars: impl IntoIterator<Item = (String, String)>,
14    stdin: Stdio,
15    stdout: Stdio,
16    stderr: Stdio,
17) -> Option<Child> {
18    let (shell, arg) = &*SHELL;
19    
20    Command::new(shell)
21    .arg(arg)
22    .arg(cmd)
23    .envs(vars)
24    .stdin(stdin)
25    .stdout(stdout)
26    .stderr(stderr)
27    .spawn()
28    .map_err(|e| error!("Failed to spawn command {cmd}: {e}"))
29    .ok()
30}
31
32pub fn exec(cmd: &str, vars: impl IntoIterator<Item = (String, String)>) -> ! {
33    let (shell, arg) = &*SHELL;
34
35    let mut command = Command::new(shell);
36    command.arg(arg).arg(cmd).envs(vars);
37
38    #[cfg(not(windows))]
39    {
40        // replace current process
41
42        use std::os::unix::process::CommandExt;
43        #[allow(irrefutable_let_patterns)]
44        if let err = command.exec() {
45            use std::process::exit;
46
47            eprintln!("Could not exec {cmd:?}: {err}");
48            exit(1);
49        }
50    }
51
52    #[cfg(windows)]
53    {
54        match command.status() {
55            Ok(status) => {
56                exit(status.code().unwrap_or(if status.success() { 0 } else { 1 }));
57            }
58            Err(err) => {
59                eprintln!("Could not spawn {cmd:?}: {err}");
60                exit(1);
61            }
62        }
63    }
64
65    unreachable!(); // process should have exited
66}
67
68
69
70pub fn tty_or_null() -> Stdio {
71    if let Ok(mut tty) = std::fs::File::open("/dev/tty") {
72        let _ = tty.flush(); // does nothing but seems logical
73        Stdio::from(tty)
74    } else {
75        error!("Failed to open /dev/tty");
76        Stdio::inherit()
77    }
78}
79
80static SHELL: LazyLock<(String, String)> = LazyLock::new(|| {
81    #[cfg(windows)]
82    {
83        let path = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string());
84        let flag = if path.to_lowercase().contains("powershell") {
85            "-Command".to_string()
86        } else {
87            "/C".to_string()
88        };
89        (path, flag)
90    }
91    #[cfg(unix)]
92    {
93        use log::debug;
94        
95        let path = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
96        let flag = "-c".to_string();
97        debug!("SHELL: {}, {}", path, flag);
98        (path, flag)
99    }
100});
101
102
103
104pub type EnvVars = Vec<(String, String)>;
105
106#[macro_export]
107macro_rules! env_vars {
108    ($( $name:expr => $value:expr ),* $(,)?) => {
109        Vec::<(String, String)>::from([
110            $( ($name.into(), $value.into()) ),*
111            ]
112        )
113    };
114}
115
116// -------------- APPENDONLY
117use std::ops::Deref;
118use std::sync::{Arc, RwLock};
119
120#[derive(Debug, Clone)]
121pub struct AppendOnly<T>(Arc<RwLock<boxcar::Vec<T>>>);
122
123impl<T> AppendOnly<T> {
124    pub fn new() -> Self {
125        Self(Arc::new(RwLock::new(boxcar::Vec::new())))
126    }
127    
128    pub fn clear(&self) {
129        let mut guard = self.0.write().unwrap(); // acquire write lock
130        guard.clear();
131    }
132    
133    pub fn push(&self, val: T) {
134        let guard = self.0.read().unwrap();
135        guard.push(val);
136    }
137
138    pub fn map_to_vec<U, F>(&self, mut f: F) -> Vec<U>
139    where
140        F: FnMut(&T) -> U,
141    {
142        let guard = self.0.read().unwrap();
143        guard.iter().map(move |(_i, v)| f(v)).collect()
144    }
145}
146
147impl<T> Deref for AppendOnly<T> {
148    type Target = RwLock<boxcar::Vec<T>>;
149    
150    fn deref(&self) -> &Self::Target {
151        &self.0
152    }
153}