1use std::{ffi::OsStr, process::Command};
21use typed_builder::TypedBuilder;
22
23use crate::{wrap::HasCommand, CommandWrap};
24#[cfg(feature = "check")]
25use crate::{CommandExtCheck, CommandExtError};
26
27#[derive(TypedBuilder, Debug)]
28pub struct CommandPrint<'a> {
29 command: &'a mut Command,
30 #[builder(default, setter(into))]
31 args: bool,
33 #[builder(default, setter(into))]
34 envs: bool,
36 #[builder(default, setter(into))]
37 current_dir: bool,
39 #[builder(default, setter(into))]
40 status: bool,
42 #[builder(default, setter(into))]
43 stdout: bool,
45 #[builder(default, setter(into))]
46 stderr: bool,
48}
49
50impl<'a> CommandPrint<'a> {
51 fn print_before(&mut self) {
52 if self.args {
53 println!(
54 "args: {} {}",
55 self.command().get_program().to_string_lossy(),
56 self.command()
57 .get_args()
58 .collect::<Vec<_>>()
59 .join(OsStr::new(" "))
60 .to_string_lossy()
61 );
62 }
63
64 if self.envs {
65 self.command().get_envs().for_each(|(k, v)| {
66 println!(
67 "envs: {}={}",
68 k.to_string_lossy(),
69 v.unwrap_or_default().to_string_lossy()
70 );
71 });
72 }
73
74 if self.current_dir {
75 println!(
76 "current_dir: {}",
77 self.command()
78 .get_current_dir()
79 .map(|d| d.to_string_lossy())
80 .unwrap_or_default()
81 );
82 }
83 }
84}
85
86impl<'a> HasCommand for CommandPrint<'a> {
87 fn command(&self) -> &Command {
88 self.command
89 }
90
91 fn command_mut(&mut self) -> &mut Command {
92 self.command
93 }
94}
95
96impl<'a> CommandWrap for CommandPrint<'a> {
97 fn on_spawn(&mut self) {
98 self.print_before();
99 }
100
101 fn on_output(&mut self) {
102 self.print_before();
103 }
104
105 fn on_status(&mut self) {
106 self.print_before();
107 }
108
109 fn after_output(&mut self, output: &std::io::Result<std::process::Output>) {
110 if let Ok(output) = output {
111 if self.status {
112 println!("status: {}", output.status);
113 }
114 if self.stdout {
115 let out = String::from_utf8_lossy(&output.stdout).trim().to_string();
116 if !out.is_empty() {
117 println!("stdout: {out}",);
118 }
119 }
120 if self.stderr {
121 let err = String::from_utf8_lossy(&output.stderr).trim().to_string();
122 if !err.is_empty() {
123 println!("stderr: {err}",);
124 }
125 }
126 }
127 }
128
129 fn after_status(&mut self, status: &std::io::Result<std::process::ExitStatus>) {
130 if let Ok(status) = status {
131 if self.status {
132 println!("status: {}", status);
133 }
134 }
135 }
136}
137
138impl<'a> From<&'a mut Command> for CommandPrint<'a> {
139 fn from(value: &'a mut Command) -> Self {
140 Self::builder().command(value).build()
141 }
142}
143
144pub trait CommandExtPrint {
145 fn print_args(&mut self) -> CommandPrint;
146 fn print_envs(&mut self) -> CommandPrint;
147 fn print_current_dir(&mut self) -> CommandPrint;
148 fn print_status(&mut self) -> CommandPrint;
149 fn print_stdout(&mut self) -> CommandPrint;
150 fn print_stderr(&mut self) -> CommandPrint;
151}
152
153impl CommandExtPrint for Command {
154 fn print_args(&mut self) -> CommandPrint
155 {
156 CommandPrint::builder().command(self).args(true).build()
157 }
158
159 fn print_envs(&mut self) -> CommandPrint
160 {
161 CommandPrint::builder().command(self).envs(true).build()
162 }
163
164 fn print_current_dir(&mut self) -> CommandPrint
165 {
166 CommandPrint::builder()
167 .command(self)
168 .current_dir(true)
169 .build()
170 }
171
172 fn print_status(&mut self) -> CommandPrint
173 {
174 CommandPrint::builder().command(self).status(true).build()
175 }
176
177 fn print_stdout(&mut self) -> CommandPrint
178 {
179 CommandPrint::builder().command(self).stdout(true).build()
180 }
181
182 fn print_stderr(&mut self) -> CommandPrint
183 {
184 CommandPrint::builder().command(self).stderr(true).build()
185 }
186}
187
188impl<'a> CommandPrint<'a> {
189 pub fn print_args(&'a mut self) -> &'a mut CommandPrint
190 {
191 self.args = true;
192 self
193 }
194
195 pub fn print_envs(&'a mut self) -> &'a mut CommandPrint
196 {
197 self.envs = true;
198 self
199 }
200
201 pub fn print_current_dir(&'a mut self) -> &'a mut CommandPrint
202 {
203 self.current_dir = true;
204 self
205 }
206
207 pub fn print_status(&'a mut self) -> &'a mut CommandPrint
208 {
209 self.status = true;
210 self
211 }
212
213 pub fn print_stdout(&'a mut self) -> &'a mut CommandPrint
214 {
215 self.stdout = true;
216 self
217 }
218
219 pub fn print_stderr(&'a mut self) -> &'a mut CommandPrint
220 {
221 self.stderr = true;
222 self
223 }
224}
225
226#[cfg(feature = "check")]
227impl<'a> CommandExtCheck for CommandPrint<'a> {
228 type Error = CommandExtError;
229
230 fn check(&mut self) -> Result<std::process::Output, Self::Error> {
231 self.output().map_err(CommandExtError::from).and_then(|r| {
232 r.status
233 .success()
234 .then_some(r.clone())
235 .ok_or_else(|| CommandExtError::Check {
236 status: r.status,
237 stdout: String::from_utf8_lossy(&r.stdout).to_string(),
238 stderr: String::from_utf8_lossy(&r.stderr).to_string(),
239 })
240 })
241 }
242}
243
244#[cfg(test)]
245mod test {
246 use std::process::Command;
247 use test_log::test;
248
249 use crate::{CommandExtPrint, CommandWrap};
250
251 #[test]
252 #[cfg_attr(miri, ignore)]
253 fn test_args() -> anyhow::Result<()> {
254 Command::new("echo")
255 .arg("x")
256 .print_args()
257 .output()?;
258 Ok(())
259 }
260
261 #[test]
262 #[cfg_attr(miri, ignore)]
263 fn test_envs() -> anyhow::Result<()> {
264 Command::new("echo")
265 .env("x", "y")
266 .print_envs()
267 .output()?;
268 Ok(())
269 }
270
271 #[test]
272 #[cfg_attr(miri, ignore)]
273 fn test_current_dir() -> anyhow::Result<()> {
274 Command::new("echo")
275 .current_dir(env!("CARGO_MANIFEST_DIR"))
276 .print_current_dir()
277 .output()?;
278 Ok(())
279 }
280
281 #[test]
282 #[cfg_attr(miri, ignore)]
283 fn test_status() -> anyhow::Result<()> {
284 Command::new("echo")
285 .arg("x")
286 .print_status()
287 .output()?;
288
289 Ok(())
290 }
291
292 #[test]
293 #[cfg_attr(miri, ignore)]
294 fn test_stdout() -> anyhow::Result<()> {
295 Command::new("echo")
296 .arg("x")
297 .print_stdout()
298 .output()?;
299
300 Ok(())
301 }
302
303 #[test]
304 #[cfg_attr(miri, ignore)]
305 fn test_stderr() -> anyhow::Result<()> {
306 Command::new("bash")
307 .args(["-c", "echo y 1>&2"])
308 .print_stderr()
309 .output()?;
310
311 Ok(())
312 }
313}