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    ffi::OsStr,
39    path::Path,
40    process::{Command, Stdio},
41};
42
43/// Builder-style methods for [`Command`] that take `self` and return `Self`.
44pub trait CommandExtra: Sized {
45    /// Sets the working directory.
46    ///
47    /// Corresponds to [`Command::current_dir`].
48    fn with_current_dir(self, dir: impl AsRef<Path>) -> Self;
49
50    /// Sets an environment variable.
51    ///
52    /// Corresponds to [`Command::env`].
53    fn with_env(self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self;
54
55    /// Removes an environment variable.
56    ///
57    /// Corresponds to [`Command::env_remove`].
58    fn without_env(self, key: impl AsRef<OsStr>) -> Self;
59
60    /// Clears all environment variables.
61    ///
62    /// Corresponds to [`Command::env_clear`].
63    fn with_no_env(self) -> Self;
64
65    /// Adds one argument.
66    ///
67    /// Corresponds to [`Command::arg`].
68    fn with_arg(self, arg: impl AsRef<OsStr>) -> Self;
69
70    /// Configures stdin.
71    ///
72    /// Corresponds to [`Command::stdin`].
73    fn with_stdin(self, stdio: Stdio) -> Self;
74
75    /// Configures stdout.
76    ///
77    /// Corresponds to [`Command::stdout`].
78    fn with_stdout(self, stdio: Stdio) -> Self;
79
80    /// Configures stderr.
81    ///
82    /// Corresponds to [`Command::stderr`].
83    fn with_stderr(self, stdio: Stdio) -> Self;
84
85    /// Adds multiple arguments.
86    ///
87    /// Corresponds to [`Command::args`].
88    fn with_args<Args>(self, args: Args) -> Self
89    where
90        Args: IntoIterator,
91        Args::Item: AsRef<OsStr>,
92    {
93        args.into_iter().fold(self, Self::with_arg)
94    }
95
96    /// Sets multiple environment variables.
97    ///
98    /// Corresponds to [`Command::envs`].
99    fn with_envs<Envs, Key, Value>(self, envs: Envs) -> Self
100    where
101        Envs: IntoIterator<Item = (Key, Value)>,
102        Key: AsRef<OsStr>,
103        Value: AsRef<OsStr>,
104    {
105        envs.into_iter()
106            .fold(self, |cmd, (key, value)| cmd.with_env(key, value))
107    }
108
109    /// Removes multiple environment variables.
110    ///
111    /// Equivalent to repeated [`Command::env_remove`]; no direct inherent method.
112    fn without_envs<Keys>(self, keys: Keys) -> Self
113    where
114        Keys: IntoIterator,
115        Keys::Item: AsRef<OsStr>,
116    {
117        keys.into_iter().fold(self, Self::without_env)
118    }
119}
120
121impl CommandExtra for Command {
122    fn with_current_dir(mut self, dir: impl AsRef<Path>) -> Self {
123        self.current_dir(dir);
124        self
125    }
126
127    fn with_env(mut self, key: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> Self {
128        self.env(key, value);
129        self
130    }
131
132    fn without_env(mut self, key: impl AsRef<OsStr>) -> Self {
133        self.env_remove(key);
134        self
135    }
136
137    fn with_no_env(mut self) -> Self {
138        self.env_clear();
139        self
140    }
141
142    fn with_arg(mut self, arg: impl AsRef<OsStr>) -> Self {
143        self.arg(arg);
144        self
145    }
146
147    fn with_stdin(mut self, stdio: Stdio) -> Self {
148        self.stdin(stdio);
149        self
150    }
151
152    fn with_stdout(mut self, stdio: Stdio) -> Self {
153        self.stdout(stdio);
154        self
155    }
156
157    fn with_stderr(mut self, stdio: Stdio) -> Self {
158        self.stderr(stdio);
159        self
160    }
161}