matchmaker/proc/
mod.rs

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