Skip to main content

nil_util/
process.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4use anyhow::Result;
5use std::ffi::OsStr;
6use std::process::Command;
7
8pub fn command<Args, Arg, Env, Key, Value>(program: &str, args: Args, env: Option<Env>) -> Command
9where
10  Args: IntoIterator<Item = Arg>,
11  Arg: AsRef<OsStr>,
12  Env: IntoIterator<Item = (Key, Value)>,
13  Key: AsRef<OsStr>,
14  Value: AsRef<OsStr>,
15{
16  let mut command = if cfg!(any(target_os = "windows", feature = "pwsh")) {
17    Command::new("pwsh")
18  } else {
19    Command::new(program)
20  };
21
22  if cfg!(any(target_os = "windows", feature = "pwsh")) {
23    command
24      .args(["-Command", program])
25      .args(args);
26  } else {
27    command.args(args);
28  }
29
30  if let Some(envs) = env {
31    command.envs(envs);
32  }
33
34  command
35}
36
37pub fn output<Args, Arg, Env, Key, Value>(
38  program: &str,
39  args: Args,
40  env: Option<Env>,
41) -> Result<Vec<u8>>
42where
43  Args: IntoIterator<Item = Arg>,
44  Arg: AsRef<OsStr>,
45  Env: IntoIterator<Item = (Key, Value)>,
46  Key: AsRef<OsStr>,
47  Value: AsRef<OsStr>,
48{
49  command(program, args, env)
50    .output()?
51    .exit_ok()
52    .map(|it| it.stdout)
53    .map_err(Into::into)
54}
55
56pub fn spawn<Args, Arg, Env, Key, Value>(program: &str, args: Args, env: Option<Env>) -> Result<()>
57where
58  Args: IntoIterator<Item = Arg>,
59  Arg: AsRef<OsStr>,
60  Env: IntoIterator<Item = (Key, Value)>,
61  Key: AsRef<OsStr>,
62  Value: AsRef<OsStr>,
63{
64  command(program, args, env)
65    .status()?
66    .exit_ok()
67    .map_err(Into::into)
68}
69
70#[doc(hidden)]
71#[macro_export]
72macro_rules! into_command_parts {
73  ($command:expr) => {{
74    let env: [(&str, &str); 0] = [];
75    $crate::command!($command, env)
76  }};
77  ($command:expr, $env:expr) => {{
78    let mut iter = $command
79      .trim()
80      .split_whitespace()
81      .map(|it| it.trim())
82      .filter(|it| !it.is_empty());
83
84    let Some(program) = iter.next() else {
85      anyhow::bail!("Missing program");
86    };
87
88    (program, iter)
89  }};
90}
91
92#[macro_export]
93macro_rules! output {
94  ($command:expr) => {{
95    let env: [(&str, &str); 0] = [];
96    $crate::output!($command, env)
97  }};
98  ($command:expr, $env:expr) => {{
99    let (program, args) = $crate::into_command_parts!($command, $env);
100    $crate::process::output(program, args, Some($env))
101  }};
102}
103
104#[macro_export]
105macro_rules! output_fmt {
106  ($($arg:tt)*) => {{
107    let command = format!($($arg)*);
108    $crate::output!(command)
109  }};
110}
111
112#[macro_export]
113macro_rules! spawn {
114  ($command:expr) => {{
115    let env: [(&str, &str); 0] = [];
116    $crate::spawn!($command, env)
117  }};
118  ($command:expr, $env:expr) => {{
119    let (program, args) = $crate::into_command_parts!($command, $env);
120    $crate::process::spawn(program, args, Some($env))
121  }};
122}
123
124#[macro_export]
125macro_rules! spawn_fmt {
126  ($($arg:tt)*) => {{
127    let command = format!($($arg)*);
128    $crate::spawn!(command)
129  }};
130}