1use crate::{StringError, bait::ResultExt, bog::BogOkExt, ebog};
4use cfg_if::cfg_if;
5use log::{debug, trace};
6use std::{
7 env,
8 ffi::{OsStr, OsString},
9 path::Path,
10 process::{Child, ChildStdout, Command, Stdio, exit},
11 sync::LazyLock,
12};
13
14#[easy_ext::ext(ChildExt)]
15impl Child {
16 pub fn wait_for_code(&mut self) -> i32 {
17 if let Some(status) = self.wait()._elog() {
18 status.code().unwrap_or(1)
19 } else {
20 1
21 }
22 }
23}
24
25#[easy_ext::ext(CommandExt)]
26impl Command {
27 pub fn from_script(script: &str) -> Self {
31 let (shell, arg) = &*SHELL;
32
33 let mut ret = Command::new(shell);
34
35 ret.arg(arg).arg(script).arg(""); ret
38 }
39
40 pub fn with_arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
41 self.arg(arg);
42 self
43 }
44
45 pub fn with_args<I, S>(mut self, args: I) -> Self
46 where
47 I: IntoIterator<Item = S>,
48 S: AsRef<OsStr>,
49 {
50 self.args(args);
51 self
52 }
53
54 pub fn display(&self) -> String {
57 std::iter::once(self.get_program())
58 .chain(self.get_args())
59 .map(|s| s.to_string_lossy())
60 .collect::<Vec<_>>()
61 .join(" ")
62 }
63
64 pub fn detach(&mut self) -> &mut Self {
67 cfg_if! {
68 if #[cfg(unix)] {
69 use std::os::unix::process::CommandExt;
70
71 unsafe {
72 self.pre_exec(|| {
73 match libc::fork() {
89 -1 => (),
90 0 => (),
91 _ => libc::_exit(0),
92 }
93
94 if libc::setsid() == -1 {
95 }
97 Ok(())
98 });
99 }
100 } else if #[cfg(windows)] {
101 use std::os::windows::process::CommandExt;
102
103 const DETACHED_PROCESS: u32 = 0x00000008;
104 const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
105
106 self.creation_flags(
107 DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
109 );
110 } else {
111 log::info!("Failed to detach: unsupported platform")
112 }
113 }
114
115 self
116 }
117
118 pub fn spawn_detached(&mut self) -> Option<Child> {
122 let ep = format!("Failed to spawn: {}", self.display());
123 debug!("Spawning detached: {self:?}");
124
125 self.stdin(Stdio::null())
126 .stdout(Stdio::null())
127 .stderr(Stdio::null())
128 .detach();
129
130 self.spawn().prefix(&ep)._ebog()
131 }
132
133 pub fn spawn_piped(&mut self) -> Result<ChildStdout, StringError> {
136 trace!("Spawning piped: {self:?}");
137
138 match self
139 .stdin(Stdio::null())
140 .stdout(Stdio::piped())
141 .stderr(Stdio::null())
142 .spawn()
143 .prefix(&format!("Failed to spawn: {}", self.display()))?
144 .stdout
145 .take()
146 {
147 Some(s) => Ok(s),
148 None => Err(format!("No stdout for {}.", self.display()).into()), }
150 }
151
152 pub fn success(&mut self) -> bool {
154 self.stdout(Stdio::null())
155 .stderr(Stdio::null())
156 .status()
157 .map(|status| status.success())
158 .unwrap_or(false)
159 }
160
161 pub fn _exec(&mut self) -> ! {
165 debug!("Becoming: {self:?}");
166
167 #[cfg(not(windows))]
168 {
169 use std::os::unix::process::CommandExt;
171 let err = self.exec();
172
173 ebog!("Could not exec {}: {err}", self.display());
174 exit(1)
175 }
176
177 #[cfg(windows)]
178 {
179 match self.status() {
180 Ok(status) => exit(
181 status
182 .code()
183 .unwrap_or(if status.success() { 0 } else { 1 }),
184 ),
185 Err(err) => {
186 ebog!("Could not exec {}: {err}", self.display());
187 exit(1)
188 }
189 }
190 }
191 }
192
193 pub fn _spawn(&mut self) -> Option<Child> {
195 trace!("Spawning: {self:?}");
196 self.spawn()
197 .prefix(&format!("Could not spawn: {}", self.display()))
198 ._elog()
199 }
200}
201
202pub fn format_sh_command(inputs: &[impl AsRef<OsStr>], double: bool) -> OsString {
206 let mut cmd = OsString::new();
207 let mut first = true;
208
209 for arg in inputs {
210 if !first {
211 cmd.push(" ");
212 }
213 first = false;
214
215 let os = arg.as_ref();
216
217 match os.to_str() {
218 Some(s) => {
219 if double {
220 let escaped = s
221 .replace("\\", "\\\\")
222 .replace('\"', "\\\"")
223 .replace("$", "\\$");
224 cmd.push("\"");
225 cmd.push(escaped);
226 cmd.push("\"");
227 } else {
228 let escaped = s.replace('\'', "'\\''");
229 cmd.push("'");
230 cmd.push(escaped);
231 cmd.push("'");
232 }
233 }
234 None => {
235 cmd.push(os);
237 }
238 }
239 }
240
241 cmd
242}
243
244pub fn display_sh_prog_and_args(prog: impl AsRef<OsStr>, args: &[impl AsRef<OsStr>]) -> String {
245 format_sh_command(
246 &{
247 let mut i = vec![prog.as_ref()];
248 i.extend(args.iter().map(|x| x.as_ref()));
249 i
250 },
251 false,
252 )
253 .to_string_lossy()
254 .to_string()
255}
256
257pub static SHELL: LazyLock<(String, String)> = LazyLock::new(|| {
259 #[cfg(windows)]
260 {
261 let path = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string());
262
263 let lower = path.to_lowercase();
264 let flag = if lower.contains("powershell") || lower.contains("pwsh") {
265 "-Command".to_string()
266 } else {
267 "/C".to_string()
268 };
269 (path, flag)
270 }
271 #[cfg(unix)]
272 {
273 let path = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
274 let flag = "-c".to_string();
275 log::debug!("SHELL: {}, {}", path, flag);
276 (path, flag)
277 }
278});
279
280pub fn current_shell() -> String {
283 #[cfg(unix)]
284 {
285 if let Ok(shell) = env::var("SHELL") {
286 if let Some(name) = Path::new(&shell).file_name().and_then(|n| n.to_str()) {
287 return name.to_ascii_lowercase();
288 }
289 }
290 }
291
292 #[cfg(windows)]
293 {
294 if let Ok(ps) = env::var("PSModulePath") {
296 if !ps.is_empty() {
297 return "pwsh".into();
298 }
299 }
300
301 if let Ok(comspec) = env::var("COMSPEC") {
302 if let Some(name) = Path::new(&comspec).file_name().and_then(|n| n.to_str()) {
303 return name.to_ascii_lowercase();
304 }
305 }
306 }
307
308 String::new()
309}
310
311pub fn tty_or_inherit() -> Stdio {
312 if let Ok(mut tty) = std::fs::File::open("/dev/tty") {
313 let _ = std::io::Write::flush(&mut tty); Stdio::from(tty)
315 } else {
316 log::error!("Failed to open /dev/tty");
317 Stdio::inherit()
318 }
319}
320
321use std::{cell::RefCell, collections::HashMap};
322thread_local! {
323 static HAS_CACHE: RefCell<HashMap<String, bool>> = RefCell::new(HashMap::new());
324}
325
326pub fn has(name: &str) -> bool {
327 HAS_CACHE.with(|cache| {
328 let mut cache = cache.borrow_mut();
329 if let Some(&found) = cache.get(name) {
330 found
331 } else {
332 let found = which::which(name).is_ok();
333 cache.insert(name.to_owned(), found);
334 found
335 }
336 })
337}
338
339pub type EnvVars = Vec<(String, String)>;
341
342#[macro_export]
343macro_rules! env_vars {
344 ($( $name:expr => $value:expr ),* $(,)?) => {
345 Vec::<(String, String)>::from([
346 $( ($name.into(), $value.into()) ),*
347 ]
348 )
349 };
350}