1#![doc = include_str!("../docs/lib.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4use std::borrow::{Borrow, Cow};
5use std::collections::HashMap;
6use std::ffi::{OsStr, OsString};
7use std::fmt::{Debug, Display, Write};
8use std::hash::Hash;
9use std::io;
10use std::ops::Deref;
11use std::path::{Path, PathBuf};
12use std::process::Command;
13use std::sync::Arc;
14
15use sealed::Sealed;
16
17#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
18#[cfg(feature = "tokio")]
19pub mod tokio;
20
21#[doc(hidden)]
23mod sealed {
24 use std::ffi::{OsStr, OsString};
25
26 pub trait Sealed {}
27
28 impl Sealed for str {}
29
30 impl Sealed for OsStr {}
31
32 impl Sealed for String {}
33
34 impl Sealed for OsString {}
35}
36
37pub trait ToStringLossy: sealed::Sealed {
39 fn to_string_lossy(&self) -> Cow<str>;
41}
42
43impl ToStringLossy for str {
44 fn to_string_lossy(&self) -> Cow<str> {
45 Cow::Borrowed(self)
46 }
47}
48
49impl ToStringLossy for OsStr {
50 fn to_string_lossy(&self) -> Cow<str> {
51 OsStr::to_string_lossy(&self)
52 }
53}
54
55pub trait CmdString: 'static + Debug + Clone + Default + Eq + Hash + Sealed
57where
58 Self: AsRef<Self::Str>,
59 Self: AsRef<OsStr>,
60 Self: Deref<Target = Self::Str>,
61 Self: Borrow<Self::Str>,
62{
63 type Str: ?Sized
65 + ToOwned<Owned = Self>
66 + AsRef<OsStr>
67 + AsRef<Self::Str>
68 + Eq
69 + Hash
70 + ToStringLossy;
71
72 fn from_str(string: &str) -> &Self::Str;
73}
74
75impl CmdString for String {
76 type Str = str;
77
78 fn from_str(string: &str) -> &Self::Str {
79 string
80 }
81}
82
83impl CmdString for OsString {
84 type Str = OsStr;
85
86 fn from_str(string: &str) -> &Self::Str {
87 string.as_ref()
88 }
89}
90
91#[derive(Debug, Clone)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94struct CmdData<S: CmdString> {
95 prog: S,
97 args: Vec<S>,
99 cwd: Option<S>,
101 vars: Option<Vars<S>>,
103 stdin: Option<In>,
105 stdout: Option<Out>,
107 stderr: Option<Out>,
109 may_fail: bool,
111 is_secret: bool,
113}
114
115impl<S: CmdString> CmdData<S> {
116 fn new(prog: S) -> Self {
117 Self {
118 prog,
119 args: Vec::new(),
120 cwd: None,
121 vars: None,
122 stdin: None,
123 stdout: None,
124 stderr: None,
125 may_fail: false,
126 is_secret: false,
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134#[must_use]
135pub struct Cmd<S: CmdString = OsString>(Arc<CmdData<S>>);
136
137impl<S: CmdString> Cmd<S> {
138 pub fn new<P: AsRef<S::Str>>(prog: P) -> Self {
140 Cmd(Arc::new(CmdData::new(prog.as_ref().to_owned())))
141 }
142
143 pub fn prog(&self) -> &S::Str {
145 self.0.prog.as_ref()
146 }
147
148 pub fn args(&self) -> impl Iterator<Item = &S::Str> {
150 self.0.args.iter().map(AsRef::as_ref)
151 }
152
153 pub fn cwd(&self) -> Option<&S::Str> {
155 self.0.cwd.as_deref()
156 }
157
158 pub fn vars(&self) -> Option<&Vars<S>> {
160 self.0.vars.as_ref()
161 }
162
163 pub fn stdin(&self) -> Option<&In> {
165 self.0.stdin.as_ref()
166 }
167
168 pub fn stdout(&self) -> Option<&Out> {
170 self.0.stdout.as_ref()
171 }
172
173 pub fn stderr(&self) -> Option<&Out> {
175 self.0.stderr.as_ref()
176 }
177
178 pub fn may_fail(&self) -> bool {
180 self.0.may_fail
181 }
182
183 pub fn is_secret(&self) -> bool {
185 self.0.is_secret
186 }
187
188 fn data_mut(&mut self) -> &mut CmdData<S> {
189 Arc::make_mut(&mut self.0)
190 }
191
192 pub fn add_arg<A: AsRef<S::Str>>(&mut self, arg: A) -> &mut Self {
194 self.data_mut().args.push(arg.as_ref().to_owned());
195 self
196 }
197
198 pub fn extend_args<A: AsRef<S::Str>, I: IntoIterator<Item = A>>(
200 &mut self,
201 args: I,
202 ) -> &mut Self {
203 self.data_mut()
204 .args
205 .extend(args.into_iter().map(|arg| arg.as_ref().to_owned()));
206 self
207 }
208
209 pub fn with_cwd<P: AsRef<S::Str>>(mut self, cwd: P) -> Self {
211 self.data_mut().cwd = Some(cwd.as_ref().to_owned());
212 self
213 }
214
215 pub fn with_vars(mut self, vars: Vars<S>) -> Self {
217 self.data_mut().vars = Some(vars);
218 self
219 }
220
221 pub fn with_var<N: AsRef<S::Str>, V: AsRef<S::Str>>(mut self, name: N, value: V) -> Self {
223 self.data_mut()
224 .vars
225 .get_or_insert_with(Vars::new)
226 .set(name, value);
227 self
228 }
229
230 pub fn with_stdin<T: Into<In>>(mut self, stdin: T) -> Self {
232 self.data_mut().stdin = Some(stdin.into());
233 self
234 }
235
236 pub fn with_stdout(mut self, stdout: Out) -> Self {
238 self.data_mut().stdout = Some(stdout);
239 self
240 }
241
242 pub fn with_stderr(mut self, stderr: Out) -> Self {
244 self.data_mut().stderr = Some(stderr);
245 self
246 }
247
248 pub fn allow_failures(mut self) -> Self {
250 self.data_mut().may_fail = true;
251 self
252 }
253
254 pub fn make_secret(mut self) -> Self {
256 self.data_mut().is_secret = true;
257 self
258 }
259}
260
261impl<S: CmdString> AsRef<Cmd<S>> for Cmd<S> {
262 fn as_ref(&self) -> &Cmd<S> {
263 self
264 }
265}
266
267impl<S: CmdString> std::fmt::Display for Cmd<S> {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 if self.0.is_secret {
270 f.write_str("<secret command redacted>")?
271 } else {
272 write_escaped(f, &self.0.prog.to_string_lossy())?;
273 for arg in &self.0.args {
274 f.write_char(' ')?;
275 write_escaped(f, &AsRef::<S::Str>::as_ref(arg).to_string_lossy())?;
276 }
277 }
278 Ok(())
279 }
280}
281
282fn write_escaped(f: &mut dyn std::fmt::Write, string: &str) -> std::fmt::Result {
283 let quote = string.contains(char::is_whitespace);
284 if quote {
285 f.write_char('"')?;
286 }
287 for char in string.chars() {
288 match char {
289 '\\' => f.write_str("\\\\")?,
290 '"' => f.write_str("\\\"")?,
291 _ => f.write_char(char)?,
292 }
293 }
294 if quote {
295 f.write_char('"')?;
296 }
297 Ok(())
298}
299
300#[macro_export]
302#[doc(hidden)]
303macro_rules! __private_extend_args {
304 ($cmd:ident, ) => {};
305 ($cmd:ident, $(, $($args:tt)*)?) => {
306 $crate::__private_extend_args!($cmd, $($($args)*)*);
307 };
308 ($cmd:ident, ...$arg:expr $(, $($args:tt)*)?) => {
309 $cmd.extend_args($arg);
310 $crate::__private_extend_args!($cmd, $($($args)*)*);
311 };
312 ($cmd:ident, $value:literal $(, $($args:tt)*)?) => {
313 $cmd.add_arg(format!($value));
314 $crate::__private_extend_args!($cmd, $($($args)*)*);
315 };
316 ($cmd:ident, $arg:expr $(, $($args:tt)*)?) => {
317 $cmd.add_arg($arg);
318 $crate::__private_extend_args!($cmd, $($($args)*)*);
319 }
320}
321
322#[macro_export]
326macro_rules! cmd {
327 ($prog:literal $(, $($args:tt)*)?) => {{
328 #[allow(unused_mut)]
329 let mut cmd = $crate::Cmd::new(format!($prog));
330 $crate::__private_extend_args!(cmd, $($($args)*)*);
331 cmd
332 }};
333 ($prog:expr $(, $($args:tt)*)?) => {{
334 #[allow(unused_mut)]
335 let mut cmd = $crate::Cmd::new($prog);
336 $crate::__private_extend_args!(cmd, $($($args)*)*);
337 cmd
338 }};
339}
340
341#[macro_export]
343macro_rules! cmd_os {
344 ($($cmd:tt)*) => {{
345 let cmd: $crate::Cmd::<::std::ffi::OsString> = $crate::cmd!($($cmd)*);
346 cmd
347 }};
348}
349
350#[macro_export]
352macro_rules! cmd_str {
353 ($($cmd:tt)*) => {{
354 let cmd: $crate::Cmd::<::std::string::String> = $crate::cmd!($($cmd)*);
355 cmd
356 }};
357}
358
359#[derive(Debug, Clone)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
362pub enum Out {
363 Discard,
365 Inherit,
367 Capture,
369}
370
371impl Out {
372 fn stdio(&self) -> std::process::Stdio {
373 match self {
374 Out::Discard => std::process::Stdio::null(),
375 Out::Inherit => std::process::Stdio::inherit(),
376 Out::Capture => std::process::Stdio::piped(),
377 }
378 }
379}
380
381#[derive(Debug, Clone)]
383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384pub enum In {
385 Null,
387 Inherit,
389 Bytes(Vec<u8>),
391}
392
393impl In {
394 fn stdio(&self) -> std::process::Stdio {
395 match self {
396 In::Null => std::process::Stdio::null(),
397 In::Inherit => std::process::Stdio::inherit(),
398 In::Bytes(_) => std::process::Stdio::piped(),
399 }
400 }
401}
402
403impl From<&[u8]> for In {
404 fn from(value: &[u8]) -> Self {
405 Self::Bytes(value.to_vec())
406 }
407}
408
409impl From<&str> for In {
410 fn from(value: &str) -> Self {
411 value.as_bytes().into()
412 }
413}
414
415impl From<&String> for In {
416 fn from(value: &String) -> Self {
417 value.as_bytes().into()
418 }
419}
420
421impl From<String> for In {
422 fn from(value: String) -> Self {
423 Self::Bytes(value.into())
424 }
425}
426
427#[derive(Debug, Clone, Default)]
428#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
429struct VarsData<S: CmdString> {
430 is_clean: bool,
432 values: HashMap<S, Option<S>>,
434}
435
436#[derive(Debug, Clone, Default)]
438#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
439pub struct Vars<S: CmdString = OsString>(Arc<VarsData<S>>);
440
441impl<S: CmdString> Vars<S> {
442 pub fn new() -> Self {
444 Self(Default::default())
445 }
446
447 pub fn is_clean(&self) -> bool {
449 self.0.is_clean
450 }
451
452 pub fn values(&self) -> impl Iterator<Item = (&S::Str, Option<&S::Str>)> {
454 self.0
455 .values
456 .iter()
457 .map(|(k, v)| (k.as_ref(), v.as_ref().map(AsRef::as_ref)))
458 }
459
460 fn data_mut(&mut self) -> &mut VarsData<S> {
461 Arc::make_mut(&mut self.0)
462 }
463
464 pub fn set<N: AsRef<S::Str>, V: AsRef<S::Str>>(&mut self, name: N, value: V) -> &mut Self {
466 self.data_mut()
467 .values
468 .insert(name.as_ref().to_owned(), Some(value.as_ref().to_owned()));
469 self
470 }
471
472 pub fn unset<N: AsRef<S::Str>>(&mut self, name: N) -> &mut Self {
474 self.data_mut()
475 .values
476 .insert(name.as_ref().to_owned(), None);
477 self
478 }
479
480 pub fn inherit<N: AsRef<S::Str>>(&mut self, name: N) -> Result<&mut Self, std::env::VarError> {
482 let name = name.as_ref();
483 let os_name = AsRef::<OsStr>::as_ref(name);
484 match std::env::var(os_name) {
485 Ok(value) => {
486 self.set(name, S::from_str(value.as_str()));
487 }
488 Err(std::env::VarError::NotPresent) => {
489 self.unset(name);
490 }
491 Err(error) => {
492 return Err(error);
493 }
494 }
495 Ok(self)
496 }
497
498 pub fn reset<N: AsRef<S::Str>>(&mut self, name: N) -> &mut Self {
500 self.data_mut().values.remove(name.as_ref());
501 self
502 }
503}
504
505#[macro_export]
507#[doc(hidden)]
508macro_rules! __private_populate_vars {
509 ($env_vars:ident,) => {};
510 ($env_vars:ident, $name:ident = $value:literal $(, $($vars:tt)*)?) => {
511 $env_vars.set(stringify!($name), format!($value));
512 $crate::__private_populate_vars!($env_vars, $($($vars)*)*);
513 };
514 ($env_vars:ident, $name:ident = $value:expr $(, $($vars:tt)*)?) => {
515 $env_vars.set(stringify!($name), $value);
516 $crate::__private_populate_vars!($env_vars, $($($vars)*)*);
517 };
518 ($env_vars:ident, $name:literal = $value:literal $(, $($vars:tt)*)?) => {
519 $env_vars.set(format!($name), format!($value));
520 $crate::__private_populate_vars!($env_vars, $($($vars)*)*);
521 };
522 ($env_vars:ident, $name:literal = $value:expr $(, $($vars:tt)*)?) => {
523 $env_vars.set(format!($name), $value);
524 $crate::__private_populate_vars!($env_vars, $($($vars)*)*);
525 };
526}
527
528#[macro_export]
538macro_rules! vars {
539 ($($vars:tt)*) => {{
540 #[allow(unused_mut)]
541 let mut env_vars = $crate::Vars::new();
542 $crate::__private_populate_vars!(env_vars, $($vars)*);
543 env_vars
544 }};
545}
546
547#[macro_export]
549macro_rules! vars_os {
550 ($($vars:tt)*) => {{
551 let vars: $crate::Vars<::std::ffi::OsString> = $crate::vars!($($vars)*);
552 vars
553 }};
554}
555
556#[macro_export]
558macro_rules! vars_str {
559 ($($vars:tt)*) => {{
560 let vars: $crate::Vars<::std::string::String> = $crate::vars!($($vars)*);
561 vars
562 }};
563}
564
565#[derive(Debug, Clone, Default)]
567#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
568#[non_exhaustive]
569pub struct RunOutput {
570 pub code: Option<i32>,
572 pub stdout: Option<Vec<u8>>,
574 pub stderr: Option<Vec<u8>>,
576}
577
578impl RunOutput {
579 pub fn new() -> Self {
581 Self::default()
582 }
583
584 pub fn with_code(mut self, code: i32) -> Self {
586 self.code = Some(code);
587 self
588 }
589
590 pub fn with_stdout(mut self, stdout: Vec<u8>) -> Self {
592 self.stdout = Some(stdout);
593 self
594 }
595
596 pub fn with_stderr(mut self, stderr: Vec<u8>) -> Self {
598 self.stderr = Some(stderr);
599 self
600 }
601
602 fn try_into_stdout_str(self) -> Result<String, RunErrorKind> {
604 self.stdout
605 .ok_or_else(|| "no `stdout` output found".into())
606 .and_then(|stdout| {
607 String::from_utf8(stdout).map_err(|_| "`stdout` output is not valid UTF-8".into())
608 })
609 .map(|mut stdout| {
610 while stdout.ends_with(|c: char| c.is_whitespace()) {
611 stdout.pop();
612 }
613 stdout
614 })
615 }
616}
617
618#[derive(Debug)]
620pub struct RunError<S: CmdString> {
621 cmd: Cmd<S>,
623 kind: RunErrorKind,
625}
626
627impl<S: CmdString> RunError<S> {
628 pub fn new(cmd: Cmd<S>, kind: RunErrorKind) -> Self {
630 Self { cmd, kind }
631 }
632
633 pub fn catch<F, U>(cmd: &Cmd<S>, func: F) -> RunResult<U, S>
635 where
636 F: FnOnce() -> Result<U, RunErrorKind>,
637 {
638 func().map_err(|kind| RunError::new(cmd.clone(), kind))
639 }
640
641 #[cfg(feature = "async")]
643 #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
644 pub async fn catch_async<F, U>(cmd: &Cmd<S>, future: F) -> RunResult<U, S>
645 where
646 F: std::future::Future<Output = Result<U, RunErrorKind>>,
647 {
648 future
649 .await
650 .map_err(|kind| RunError::new(cmd.clone(), kind))
651 }
652}
653
654impl<S: CmdString> std::error::Error for RunError<S> {
655 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
656 match &self.kind {
657 RunErrorKind::Failed { .. } => None,
658 RunErrorKind::Io(error) => Some(error),
659 RunErrorKind::Other(error) => Some(error.as_ref()),
660 RunErrorKind::Custom(_) => None,
661 }
662 }
663}
664
665impl<S: CmdString> Display for RunError<S> {
666 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667 f.write_fmt(format_args!("error running command `{}`: ", self.cmd))?;
668 match &self.kind {
669 RunErrorKind::Failed(output) => {
670 f.write_str("command failed with non-zero exit code")?;
671 if let Some(code) = output.code {
672 f.write_char(' ')?;
673 Display::fmt(&code, f)?;
674 }
675 if let Some(stderr) = &output.stderr {
676 f.write_str("\n=== STDERR ===\n")?;
677 if let Ok(stderr) = std::str::from_utf8(stderr) {
678 f.write_str(stderr.trim())?;
679 } else {
680 f.write_str("<invalid utf-8>")?;
681 }
682 }
683 }
684 RunErrorKind::Other(error) => {
685 Display::fmt(&error, f)?;
686 }
687 RunErrorKind::Io(error) => {
688 Display::fmt(&error, f)?;
689 }
690 RunErrorKind::Custom(message) => {
691 Display::fmt(&message, f)?;
692 }
693 }
694 Ok(())
695 }
696}
697
698pub type RunResult<T, S> = Result<T, RunError<S>>;
700
701#[derive(Debug)]
703pub enum RunErrorKind {
704 Failed(RunOutput),
706 Io(io::Error),
708 Custom(String),
710 Other(Box<dyn 'static + Sync + Send + std::error::Error>),
712}
713
714impl RunErrorKind {
715 pub fn other<E: 'static + Sync + Send + std::error::Error>(error: E) -> Self {
717 Self::Other(Box::new(error))
718 }
719}
720
721impl From<RunOutput> for RunErrorKind {
722 fn from(value: RunOutput) -> Self {
723 Self::Failed(value)
724 }
725}
726
727impl From<io::Error> for RunErrorKind {
728 fn from(value: std::io::Error) -> Self {
729 Self::Io(value)
730 }
731}
732
733impl From<&str> for RunErrorKind {
734 fn from(value: &str) -> Self {
735 Self::Custom(value.to_owned())
736 }
737}
738
739impl From<String> for RunErrorKind {
740 fn from(value: String) -> Self {
741 Self::Custom(value)
742 }
743}
744
745#[macro_export]
747macro_rules! run {
748 ($env:expr, [$($cmd_args:tt)*] $($cmd_methods:tt)*) => {
749 $env.run($crate::cmd!($($cmd_args)*)$($cmd_methods)*)
750 };
751 ([$($cmd_args:tt)*] $($cmd_methods:tt)*) => {
752 $crate::ParentEnv.run($crate::cmd!($($cmd_args)*)$($cmd_methods)*)
753 };
754}
755
756#[macro_export]
759macro_rules! read_str {
760 ($env:expr, [$($cmd_args:tt)*] $($cmd_methods:tt)*) => {
761 $env.read_str($crate::cmd!($($cmd_args)*)$($cmd_methods)*)
762 };
763 ([$($cmd_args:tt)*] $($cmd_methods:tt)*) => {
764 $crate::ParentEnv.read_str($crate::cmd!($($cmd_args)*)$($cmd_methods)*)
765 };
766}
767
768#[macro_export]
771macro_rules! read_bytes {
772 ($env:expr, [$($cmd_args:tt)*] $($cmd_methods:tt)*) => {
773 $env.read_bytes($crate::cmd!($($cmd_args)*)$($cmd_methods)*)
774 };
775 ([$($cmd_args:tt)*] $($cmd_methods:tt)*) => {
776 $crate::ParentEnv.read_bytes($crate::cmd!($($cmd_args)*)$($cmd_methods)*)
777 };
778}
779
780#[derive(Debug, Clone)]
782struct EnvInner {
783 cwd: PathBuf,
785 vars: Vars<OsString>,
787 default_stdin: In,
789 default_stdout: Out,
791 default_stderr: Out,
793 replay_stdout: bool,
795 replay_stderr: bool,
797 echo_commands: bool,
799}
800
801impl EnvInner {
802 fn new(cwd: PathBuf) -> Self {
803 Self {
804 cwd,
805 vars: Vars::new(),
806 default_stdin: In::Null,
807 default_stdout: Out::Capture,
808 default_stderr: Out::Capture,
809 replay_stdout: false,
810 replay_stderr: false,
811 echo_commands: false,
812 }
813 }
814}
815
816pub struct ParentEnv;
818
819impl Run<OsString> for ParentEnv {
820 fn run(&self, cmd: Cmd<OsString>) -> Result<RunOutput, RunError<OsString>> {
821 let env = RunError::catch(&cmd, || LocalEnv::current_dir().map_err(RunErrorKind::from))?;
823 Run::run(&env, cmd)
824 }
825}
826
827#[derive(Debug, Clone)]
829pub struct LocalEnv(Arc<EnvInner>);
830
831impl LocalEnv {
832 pub fn new<P: AsRef<Path>>(cwd: P) -> Self {
834 Self(Arc::new(EnvInner::new(cwd.as_ref().to_path_buf())))
835 }
836
837 pub fn current_dir() -> Result<Self, io::Error> {
839 Ok(Self::new(std::env::current_dir()?))
840 }
841
842 fn inner_mut(&mut self) -> &mut EnvInner {
843 Arc::make_mut(&mut self.0)
844 }
845
846 pub fn cwd(&self) -> &Path {
848 &self.0.cwd
849 }
850
851 pub fn set_cwd<P: AsRef<Path>>(&mut self, cwd: P) -> &mut Self {
853 self.inner_mut().cwd = cwd.as_ref().to_path_buf();
854 self
855 }
856
857 pub fn with_cwd<P: AsRef<Path>>(mut self, cwd: P) -> Self {
859 self.set_cwd(cwd);
860 self
861 }
862
863 pub fn vars(&self) -> &Vars {
865 &self.0.vars
866 }
867
868 pub fn set_vars(&mut self, vars: Vars) -> &mut Self {
870 self.inner_mut().vars = vars;
871 self
872 }
873
874 pub fn with_vars(mut self, vars: Vars) -> Self {
876 self.set_vars(vars);
877 self
878 }
879
880 pub fn set_var<N: AsRef<OsStr>, V: AsRef<OsStr>>(&mut self, name: N, value: V) -> &mut Self {
882 self.inner_mut().vars.set(name, value);
883 self
884 }
885
886 pub fn with_var<N: AsRef<OsStr>, V: AsRef<OsStr>>(mut self, name: N, value: V) -> Self {
888 self.set_var(name, value);
889 self
890 }
891
892 pub fn default_stdin(&self) -> &In {
894 &self.0.default_stdin
895 }
896
897 pub fn with_default_stdin(mut self, stdin: In) -> Self {
899 self.inner_mut().default_stdin = stdin;
900 self
901 }
902
903 pub fn default_stdout(&self) -> &Out {
905 &self.0.default_stdout
906 }
907
908 pub fn with_default_stdout(mut self, stdout: Out) -> Self {
910 self.inner_mut().default_stdout = stdout;
911 self
912 }
913
914 pub fn default_stderr(&self) -> &Out {
916 &self.0.default_stderr
917 }
918
919 pub fn with_default_stderr(mut self, stderr: Out) -> Self {
921 self.inner_mut().default_stderr = stderr;
922 self
923 }
924
925 pub fn with_echo(mut self) -> Self {
927 self.inner_mut().echo_commands = true;
928 self
929 }
930
931 pub fn without_echo(mut self) -> Self {
933 self.inner_mut().echo_commands = false;
934 self
935 }
936
937 pub fn change_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Self, io::Error> {
939 Ok(self.set_cwd(self.resolve_path(path).canonicalize()?))
940 }
941
942 pub fn resolve_path<P: AsRef<Path>>(&self, path: P) -> PathBuf {
944 self.0.cwd.join(path.as_ref())
945 }
946
947 fn resolve_prog<'p>(&self, prog: &'p OsStr) -> Cow<'p, Path> {
948 if prog.to_string_lossy().contains(std::path::is_separator) {
949 Cow::Owned(self.resolve_path(prog))
950 } else {
951 Cow::Borrowed(Path::new(prog))
952 }
953 }
954
955 fn echo_cmd(&self, cmd: &Cmd<OsString>) {
956 if self.0.echo_commands {
957 eprintln!("+ {cmd}");
958 }
959 }
960
961 fn command(&self, cmd: &Cmd<OsString>) -> Command {
962 let mut command = Command::new(&*self.resolve_prog(cmd.prog()));
963 command.args(cmd.args());
964 if let Some(cwd) = cmd.cwd() {
965 command.current_dir(self.resolve_path(cwd));
966 } else {
967 command.current_dir(&self.0.cwd);
968 }
969 if self.vars().is_clean() || cmd.vars().map(|vars| vars.is_clean()).unwrap_or(false) {
971 command.env_clear();
972 }
973 update_vars(&mut command, self.vars());
974 if let Some(vars) = cmd.vars() {
975 update_vars(&mut command, vars);
976 }
977 command.stdin(cmd.stdin().unwrap_or_else(|| self.default_stdin()).stdio());
979 command.stdout(
980 cmd.stdout()
981 .unwrap_or_else(|| self.default_stdout())
982 .stdio(),
983 );
984 command.stderr(
985 cmd.stderr()
986 .unwrap_or_else(|| self.default_stderr())
987 .stdio(),
988 );
989 command
990 }
991}
992
993fn update_vars(command: &mut Command, vars: &Vars) {
994 for (name, value) in vars.values() {
995 if let Some(value) = value {
996 command.env(name, value);
997 } else {
998 command.env_remove(name);
999 }
1000 }
1001}
1002
1003pub trait Run<S: CmdString> {
1005 fn run(&self, cmd: Cmd<S>) -> Result<RunOutput, RunError<S>>;
1007
1008 fn read_str(&self, cmd: Cmd<S>) -> Result<String, RunError<S>> {
1010 let cmd = cmd.with_stdout(Out::Capture);
1011 self.run(cmd.clone())
1012 .and_then(|output| RunError::catch(&cmd, || output.try_into_stdout_str()))
1013 }
1014
1015 fn read_bytes(&self, cmd: Cmd<S>) -> Result<Vec<u8>, RunError<S>> {
1017 let cmd = cmd.with_stdout(Out::Capture);
1018 self.run(cmd).map(|output| output.stdout.unwrap())
1019 }
1020}
1021
1022impl Run<OsString> for LocalEnv {
1023 fn run(&self, cmd: Cmd<OsString>) -> Result<RunOutput, RunError<OsString>> {
1024 RunError::catch(&cmd, || {
1025 use io::Write;
1026
1027 let cmd = &cmd;
1028
1029 let mut command = self.command(cmd);
1030 self.echo_cmd(cmd);
1031 let mut child = command.spawn()?;
1032 let capture_stdout = child.stdout.is_some();
1033 let capture_stderr = child.stderr.is_some();
1034 let child_output = std::thread::scope(|scope| {
1035 if let Some(mut child_stdin) = child.stdin.take() {
1036 scope.spawn(move || {
1037 if let Some(In::Bytes(stdin)) = cmd.stdin() {
1038 let _ = child_stdin.write_all(stdin);
1039 let _ = child_stdin.flush();
1040 }
1041 });
1042 }
1043 child.wait_with_output()
1044 })?;
1045 if self.0.replay_stdout {
1046 let _ = io::stdout().write_all(&child_output.stdout);
1047 }
1048 if self.0.replay_stderr {
1049 let _ = io::stderr().write_all(&child_output.stderr);
1050 }
1051 let output = RunOutput {
1052 code: child_output.status.code(),
1053 stdout: if capture_stdout {
1054 Some(child_output.stdout)
1055 } else {
1056 None
1057 },
1058 stderr: if capture_stderr {
1059 Some(child_output.stderr)
1060 } else {
1061 None
1062 },
1063 };
1064 if child_output.status.success() || cmd.may_fail() {
1065 Ok(output)
1066 } else {
1067 Err(RunErrorKind::Failed(output))
1068 }
1069 })
1070 }
1071}
1072
1073#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
1075#[cfg(feature = "async")]
1076pub trait RunAsync<S: CmdString + Send + Sync>: Sync {
1077 fn run(&self, cmd: Cmd<S>)
1078 -> impl Send + std::future::Future<Output = RunResult<RunOutput, S>>;
1079
1080 fn read_str(
1081 &self,
1082 cmd: Cmd<S>,
1083 ) -> impl Send + std::future::Future<Output = RunResult<String, S>> {
1084 async move {
1085 let cmd = cmd.with_stdout(Out::Capture);
1087 self.run(cmd.clone())
1088 .await
1089 .and_then(|output| RunError::catch(&cmd, || output.try_into_stdout_str()))
1090 }
1091 }
1092
1093 fn read_bytes(
1094 &self,
1095 cmd: Cmd<S>,
1096 ) -> impl Send + std::future::Future<Output = RunResult<Vec<u8>, S>> {
1097 async move {
1098 let cmd = cmd.with_stdout(Out::Capture);
1099 self.run(cmd).await.map(|output| output.stdout.unwrap())
1100 }
1101 }
1102}
1103
1104#[cfg(test)]
1105mod tests {
1106 use std::error::Error;
1107
1108 use crate::{write_escaped, Run};
1109
1110 #[test]
1111 fn test_write_escaped() {
1112 fn escape(string: &str) -> String {
1113 let mut buf = String::new();
1114 write_escaped(&mut buf, string).unwrap();
1115 buf
1116 }
1117 assert_eq!(escape("xyz"), "xyz");
1118 assert_eq!(escape("xyz abc"), "\"xyz abc\"");
1119 assert_eq!(escape("x\"yz\""), "x\\\"yz\\\"");
1120 assert_eq!(escape("\\x"), "\\\\x");
1121 }
1122
1123 #[test]
1124 #[cfg(target_family = "unix")]
1125 fn test_io() -> Result<(), Box<dyn Error>> {
1126 use crate::LocalEnv;
1127
1128 let env = LocalEnv::current_dir()?;
1129 assert!(read_str!(env, ["cat"])?.is_empty());
1130 assert_eq!(
1131 read_str!(env, ["cat"].with_stdin("Hello World!"))?,
1132 "Hello World!"
1133 );
1134 Ok(())
1135 }
1136}