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