clonable_command/
lib.rs

1#![warn(clippy::pedantic, missing_docs, clippy::cargo)]
2#![allow(clippy::missing_errors_doc)]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4//! Anoyingly, [`std::process::Command`] does not implement [`Clone`].
5//!
6//! This is due to [`std::process::Command`] containing fields (like file handles for stdin/stdout)
7//! that cannot easily be cloned (see [rust-lang/rust#22119][22119]).
8//!
9//! [22119]: https://github.com/rust-lang/rust/pull/22119#discussion_r24515717
10
11use 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/// Enum version of [std's Stdio](StdStdio), allowing easy copying and
18/// serialization.
19#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub enum Stdio {
22    /// A new pipe should be arranged to connect the parent and child processes.
23    /// [more](StdStdio::piped)
24    Piped,
25    /// The child inherits from the corresponding parent descriptor.
26    /// [more](StdStdio::inherit)
27    Inherit,
28    /// This stream will be ignored. This is the equivalent of attaching the
29    /// stream to `/dev/null`. [more](StdStdio::null)
30    Null,
31}
32
33impl Stdio {
34    /// Convert this value into a [`std::process::Stdio`].
35    #[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// TODO platform specific extentions
62
63/// A process builder, providing fine-grained control over how a new process
64/// should be spawned. Equivalent to [std's Command](StdCommand) but allowing
65/// field access cloning and serialization.
66#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
67#[derive(Clone, Debug, PartialEq, Eq)]
68pub struct Command {
69    /// Name of the program invoked.
70    pub name: OsString,
71    /// Arguments passed to the child process.
72    pub arguments: Vec<OsString>,
73    /// Controlls whether the child process will inherit the parent process'
74    /// environment.
75    pub inherit_environment: bool,
76    /// Environment for the child process, `None` represents variables that will
77    /// not be inherited from the parent, even when `inherit_environment ==
78    /// true`.
79    pub environment: HashMap<OsString, Option<OsString>>,
80    /// Working directory for the child process.
81    pub current_dir: Option<PathBuf>,
82    /// Child process' standard input (stdin) handle, [`None` will use default
83    /// for invocation type](StdCommand::stdin).
84    pub stdin: Option<Stdio>,
85    /// Child process' standard output (stdout) handle, [`None` will use default
86    /// for invocation type](StdCommand::stdout).
87    pub stdout: Option<Stdio>,
88    /// Child process' standard error (stderr) handle, [`None` will use default
89    /// for invocation type](StdCommand::stderr).
90    pub stderr: Option<Stdio>,
91}
92
93/// Builder
94impl Command {
95    /// Constructs a new `Command`. [more](StdCommand::new)
96    #[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    /// Adds an argument to pass to the program. [more](StdCommand::arg)
111    #[must_use]
112    pub fn arg(mut self, arg: impl AsRef<OsStr>) -> Self {
113        self.add_arg(arg);
114        self
115    }
116
117    /// Adds multiple arguments to pass to the program. [more](StdCommand::args)
118    #[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    /// Inserts or updates an environment variable. [more](StdCommand::env)
125    #[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    /// Inserts or updates multiple environment variables.
132    /// [more](StdCommand::envs)
133    #[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    /// Removes an explicitly set environment variable and prevents inheriting
143    /// it from a parent process. [more](StdCommand::env_remove)
144    #[must_use]
145    pub fn env_remove(mut self, key: impl AsRef<OsStr>) -> Self {
146        self.remove_env(key);
147        self
148    }
149
150    /// Clears all explicitly set environment variables and prevents inheriting
151    /// any parent process environment variables. [more](StdCommand::env_clear)
152    #[must_use]
153    pub fn env_clear(mut self) -> Self {
154        self.environment.clear();
155        self.inherit_environment = false;
156        self
157    }
158
159    /// Prevents inheriting any parent process environment variables (like
160    /// [`env_clear`](Self::env_clear) without clearing set envs).
161    #[must_use]
162    pub fn env_no_inherit(mut self) -> Self {
163        self.inherit_environment = false;
164        self
165    }
166
167    /// Sets the working directory for the child process.
168    /// [more](StdCommand::current_dir)
169    #[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    /// Configuration for the child process’s standard input (stdin) handle.
176    /// [more](StdCommand::stdin)
177    #[must_use]
178    pub fn stdin(mut self, stdin: Stdio) -> Self {
179        self.stdin = Some(stdin);
180        self
181    }
182
183    /// Configuration for the child process’s standard output (stdout) handle.
184    /// [more](StdCommand::stdout)
185    #[must_use]
186    pub fn stdout(mut self, stdout: Stdio) -> Self {
187        self.stdout = Some(stdout);
188        self
189    }
190
191    /// Configuration for the child process’s standard error (stderr) handle.
192    /// [more](StdCommand::stderr)
193    #[must_use]
194    pub fn stderr(mut self, stderr: Stdio) -> Self {
195        self.stderr = Some(stderr);
196        self
197    }
198}
199/// Setters
200impl Command {
201    /// Adds an argument to pass to the program. [more](StdCommand::arg)
202    pub fn add_arg(&mut self, arg: impl AsRef<OsStr>) {
203        self.arguments.push(arg.as_ref().to_owned());
204    }
205
206    /// Adds multiple arguments to pass to the program. [more](StdCommand::args)
207    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    /// Inserts or updates an environment variable. [more](StdCommand::env)
213    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    /// Inserts or updates multiple environment variables.
219    /// [more](StdCommand::envs)
220    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    /// Removes an explicitly set environment variable and prevents inheriting
231    /// it from a parent process. [more](StdCommand::env_remove)
232    pub fn remove_env(&mut self, key: impl AsRef<OsStr>) {
233        self.environment.remove(key.as_ref());
234    }
235
236    /// Sets the working directory for the child process.
237    /// [more](StdCommand::current_dir)
238    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            // Only need to remove inherited vars if not cleared
259            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
279/// Execution
280impl Command {
281    /// Behaves identical to std's [`Command::spawn`](StdCommand::spawn).
282    pub fn spawn(&self) -> Result<Child> {
283        StdCommand::from(self).spawn()
284    }
285
286    /// Behaves identical to std's [`Command::output`](StdCommand::output).
287    pub fn output(&self) -> Result<Output> {
288        StdCommand::from(self).output()
289    }
290
291    /// Behaves identical to std's [`Command::status`](StdCommand::status).
292    pub fn status(&self) -> Result<ExitStatus> {
293        StdCommand::from(self).status()
294    }
295
296    /// Convert this command to a [`std::process::Command`].
297    #[must_use]
298    pub fn to_std(&self) -> StdCommand {
299        self.into()
300    }
301}