1#![warn(clippy::pedantic, missing_docs, clippy::cargo)]
2#![allow(clippy::missing_errors_doc)]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4use std::collections::HashMap;
12use std::ffi::{OsStr, OsString};
13use std::io::Result;
14use std::path::{Path, PathBuf};
15use std::process::{Child, Command as StdCommand, ExitStatus, Output, Stdio as StdStdio};
16
17#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub enum Stdio {
22 Piped,
25 Inherit,
28 Null,
31}
32
33impl Stdio {
34 #[must_use]
36 pub fn to_std(&self) -> StdStdio {
37 self.into()
38 }
39}
40
41impl From<Stdio> for StdStdio {
42 fn from(value: Stdio) -> Self {
43 match value {
44 Stdio::Inherit => StdStdio::inherit(),
45 Stdio::Piped => StdStdio::piped(),
46 Stdio::Null => StdStdio::null(),
47 }
48 }
49}
50
51impl From<&Stdio> for StdStdio {
52 fn from(value: &Stdio) -> Self {
53 match value {
54 Stdio::Inherit => StdStdio::inherit(),
55 Stdio::Piped => StdStdio::piped(),
56 Stdio::Null => StdStdio::null(),
57 }
58 }
59}
60
61#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
67#[derive(Clone, Debug, PartialEq, Eq)]
68pub struct Command {
69 pub name: OsString,
71 pub arguments: Vec<OsString>,
73 pub inherit_environment: bool,
76 pub environment: HashMap<OsString, Option<OsString>>,
80 pub current_dir: Option<PathBuf>,
82 pub stdin: Option<Stdio>,
85 pub stdout: Option<Stdio>,
88 pub stderr: Option<Stdio>,
91}
92
93impl Command {
95 #[must_use]
97 pub fn new(name: impl AsRef<OsStr>) -> Self {
98 Self {
99 name: name.as_ref().to_owned(),
100 arguments: Vec::new(),
101 inherit_environment: true,
102 environment: HashMap::new(),
103 current_dir: None,
104 stdin: None,
105 stdout: None,
106 stderr: None,
107 }
108 }
109
110 #[must_use]
112 pub fn arg(mut self, arg: impl AsRef<OsStr>) -> Self {
113 self.add_arg(arg);
114 self
115 }
116
117 #[must_use]
119 pub fn args(mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Self {
120 self.add_args(args);
121 self
122 }
123
124 #[must_use]
126 pub fn env(mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) -> Self {
127 self.set_env(key, val);
128 self
129 }
130
131 #[must_use]
134 pub fn envs(
135 mut self,
136 vars: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
137 ) -> Self {
138 self.set_envs(vars);
139 self
140 }
141
142 #[must_use]
145 pub fn env_remove(mut self, key: impl AsRef<OsStr>) -> Self {
146 self.remove_env(key);
147 self
148 }
149
150 #[must_use]
153 pub fn env_clear(mut self) -> Self {
154 self.environment.clear();
155 self.inherit_environment = false;
156 self
157 }
158
159 #[must_use]
162 pub fn env_no_inherit(mut self) -> Self {
163 self.inherit_environment = false;
164 self
165 }
166
167 #[must_use]
170 pub fn current_dir(mut self, key: impl AsRef<Path>) -> Self {
171 self.set_current_dir(key);
172 self
173 }
174
175 #[must_use]
178 pub fn stdin(mut self, stdin: Stdio) -> Self {
179 self.stdin = Some(stdin);
180 self
181 }
182
183 #[must_use]
186 pub fn stdout(mut self, stdout: Stdio) -> Self {
187 self.stdout = Some(stdout);
188 self
189 }
190
191 #[must_use]
194 pub fn stderr(mut self, stderr: Stdio) -> Self {
195 self.stderr = Some(stderr);
196 self
197 }
198}
199impl Command {
201 pub fn add_arg(&mut self, arg: impl AsRef<OsStr>) {
203 self.arguments.push(arg.as_ref().to_owned());
204 }
205
206 pub fn add_args(&mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) {
208 self.arguments
209 .extend(args.into_iter().map(|i| i.as_ref().to_owned()));
210 }
211
212 pub fn set_env(&mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) {
214 self.environment
215 .insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned()));
216 }
217
218 pub fn set_envs(
221 &mut self,
222 vars: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
223 ) {
224 self.environment.extend(
225 vars.into_iter()
226 .map(|(k, v)| (k.as_ref().to_owned(), Some(v.as_ref().to_owned()))),
227 );
228 }
229
230 pub fn remove_env(&mut self, key: impl AsRef<OsStr>) {
233 self.environment.remove(key.as_ref());
234 }
235
236 pub fn set_current_dir(&mut self, path: impl AsRef<Path>) {
239 self.current_dir = Some(path.as_ref().to_owned());
240 }
241}
242
243impl From<&Command> for StdCommand {
244 fn from(
245 Command {
246 name,
247 arguments: args,
248 inherit_environment: inherit_env,
249 environment: env,
250 current_dir,
251 stdin,
252 stdout,
253 stderr,
254 }: &Command,
255 ) -> Self {
256 let mut command = StdCommand::new(name);
257 if *inherit_env {
258 for (removed, _) in env.iter().filter(|i| i.1.is_none()) {
260 command.env_remove(removed);
261 }
262 } else {
263 command.env_clear();
264 }
265 command
266 .args(args)
267 .envs(env.iter().filter_map(|(k, v)| v.as_ref().map(|v| (k, v))));
268 current_dir
269 .as_ref()
270 .map(|current_dir| command.current_dir(current_dir));
271 stdin.map(|stdin| command.stdin(stdin));
272 stdout.map(|stdout| command.stdout(stdout));
273 stderr.map(|stderr| command.stderr(stderr));
274
275 command
276 }
277}
278
279impl Command {
281 pub fn spawn(&self) -> Result<Child> {
283 StdCommand::from(self).spawn()
284 }
285
286 pub fn output(&self) -> Result<Output> {
288 StdCommand::from(self).output()
289 }
290
291 pub fn status(&self) -> Result<ExitStatus> {
293 StdCommand::from(self).status()
294 }
295
296 #[must_use]
298 pub fn to_std(&self) -> StdCommand {
299 self.into()
300 }
301}