Skip to main content

command_extra/
lib.rs

1//! Builder-style methods for [`Command`] that take `self` and return `Self`.
2//!
3//! [std]'s own builder methods return `&mut Command`. They chain, but the chain
4//! cannot produce a value, so a command that needs several settings has to be
5//! built over a mutable binding and handed back separately. These methods
6//! return the command itself, which puts the whole construction in expression
7//! position: it can be returned, bound, stored in a field, or folded over.
8//!
9//! ```rust,no_run
10//! # use command_extra::CommandExtra;
11//! # use std::path::Path;
12//! # use std::process::Command;
13//! fn lister(dir: &Path) -> Command {
14//!     Command::new("ls")
15//!         .with_current_dir(dir)
16//!         .with_args(["-l", "-a"])
17//!         .with_env("LANG", "C")
18//! }
19//! ```
20//!
21//! The same function written against std cannot end in its chain, because the
22//! chain has type `&mut Command`:
23//!
24//! ```rust,no_run
25//! # use std::path::Path;
26//! # use std::process::Command;
27//! fn lister(dir: &Path) -> Command {
28//!     let mut command = Command::new("ls");
29//!     command
30//!         .current_dir(dir)
31//!         .args(["-l", "-a"])
32//!         .env("LANG", "C");
33//!     command
34//! }
35//! ```
36
37use std::{
38    borrow::Borrow,
39    ffi::OsStr,
40    path::Path,
41    process::{Command, Stdio},
42};
43
44/// Builder-style methods for [`Command`] that take `self` and return `Self`.
45pub trait CommandExtra: Sized {
46    /// Sets the working directory.
47    ///
48    /// Corresponds to [`Command::current_dir`].
49    fn with_current_dir(self, dir: impl AsRef<Path>) -> Self;
50
51    /// Sets an environment variable.
52    ///
53    /// Corresponds to [`Command::env`].
54    fn with_env(self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self;
55
56    /// Removes an environment variable.
57    ///
58    /// Corresponds to [`Command::env_remove`].
59    fn without_env(self, key: impl AsRef<OsStr>) -> Self;
60
61    /// Clears all environment variables.
62    ///
63    /// Corresponds to [`Command::env_clear`].
64    fn with_no_env(self) -> Self;
65
66    /// Adds one argument.
67    ///
68    /// Corresponds to [`Command::arg`].
69    fn with_arg(self, arg: impl AsRef<OsStr>) -> Self;
70
71    /// Configures stdin.
72    ///
73    /// Corresponds to [`Command::stdin`].
74    fn with_stdin(self, stdio: Stdio) -> Self;
75
76    /// Configures stdout.
77    ///
78    /// Corresponds to [`Command::stdout`].
79    fn with_stdout(self, stdio: Stdio) -> Self;
80
81    /// Configures stderr.
82    ///
83    /// Corresponds to [`Command::stderr`].
84    fn with_stderr(self, stdio: Stdio) -> Self;
85
86    /// Adds multiple arguments.
87    ///
88    /// Corresponds to [`Command::args`].
89    #[inline]
90    fn with_args<Args>(self, args: Args) -> Self
91    where
92        Args: IntoIterator,
93        Args::Item: AsRef<OsStr>,
94    {
95        args.into_iter().fold(self, Self::with_arg)
96    }
97
98    /// Sets multiple environment variables.
99    ///
100    /// Corresponds to [`Command::envs`].
101    #[inline]
102    fn with_envs<Envs, Key, Value>(self, envs: Envs) -> Self
103    where
104        Envs: IntoIterator,
105        Envs::Item: Borrow<(Key, Value)>,
106        Key: AsRef<OsStr>,
107        Value: AsRef<OsStr>,
108    {
109        envs.into_iter().fold(self, |cmd, pair| {
110            let (key, value) = pair.borrow();
111            cmd.with_env(key, value)
112        })
113    }
114
115    /// Removes multiple environment variables.
116    ///
117    /// Equivalent to repeated [`Command::env_remove`]; no direct inherent method.
118    #[inline]
119    fn without_envs<Keys>(self, keys: Keys) -> Self
120    where
121        Keys: IntoIterator,
122        Keys::Item: AsRef<OsStr>,
123    {
124        keys.into_iter().fold(self, Self::without_env)
125    }
126}
127
128macro_rules! impl_command_extra {
129    ($(#[$attrs:meta])* $command:ty) => {
130        $(#[$attrs])*
131        impl CommandExtra for $command {
132            #[inline]
133            fn with_current_dir(mut self, dir: impl AsRef<Path>) -> Self {
134                self.current_dir(dir);
135                self
136            }
137
138            #[inline]
139            fn with_env(mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self {
140                self.env(key, value);
141                self
142            }
143
144            #[inline]
145            fn without_env(mut self, key: impl AsRef<OsStr>) -> Self {
146                self.env_remove(key);
147                self
148            }
149
150            #[inline]
151            fn with_no_env(mut self) -> Self {
152                self.env_clear();
153                self
154            }
155
156            #[inline]
157            fn with_arg(mut self, arg: impl AsRef<OsStr>) -> Self {
158                self.arg(arg);
159                self
160            }
161
162            #[inline]
163            fn with_stdin(mut self, stdio: Stdio) -> Self {
164                self.stdin(stdio);
165                self
166            }
167
168            #[inline]
169            fn with_stdout(mut self, stdio: Stdio) -> Self {
170                self.stdout(stdio);
171                self
172            }
173
174            #[inline]
175            fn with_stderr(mut self, stdio: Stdio) -> Self {
176                self.stderr(stdio);
177                self
178            }
179        }
180    };
181}
182
183impl_command_extra!(Command);
184impl_command_extra!(Box<Command>);
185impl_command_extra!(
186    #[cfg(feature = "tokio_process")]
187    tokio::process::Command
188);
189impl_command_extra!(
190    #[cfg(feature = "tokio_process")]
191    Box<tokio::process::Command>
192);
193
194// no `&mut Command` because inherent methods already deal with it