xscript/
lib.rs

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/// Module for sealing traits.
22#[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
37/// Lossy string conversion.
38pub trait ToStringLossy: sealed::Sealed {
39    /// Convert to string, potentially skipping invalid characters.
40    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
55/// A string type that can be used to construct commands.
56pub 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    /// Unsized equivalent for references.
64    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/// Shared inner data of a command.
92#[derive(Debug, Clone)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94struct CmdData<S: CmdString> {
95    /// The program to run.
96    prog: S,
97    /// The arguments to run the program with.
98    args: Vec<S>,
99    /// The directory in which to run the command.
100    cwd: Option<S>,
101    /// The environment variables to run the command with.
102    vars: Option<Vars<S>>,
103    /// The `stdin` input to provide to the command.
104    stdin: Option<In>,
105    /// Indicates what to do with the `stdout` output of the command.
106    stdout: Option<Out>,
107    /// Indicates what to do with the `stderr` output of the command.
108    stderr: Option<Out>,
109    /// Indicates whether the command may fail.
110    may_fail: bool,
111    /// Hints that the command may contain secret information.
112    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/// A command.
132#[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    /// Creates a new command for the given program.
139    pub fn new<P: AsRef<S::Str>>(prog: P) -> Self {
140        Cmd(Arc::new(CmdData::new(prog.as_ref().to_owned())))
141    }
142
143    /// The program to run.
144    pub fn prog(&self) -> &S::Str {
145        self.0.prog.as_ref()
146    }
147
148    /// The arguments to run the program with.
149    pub fn args(&self) -> impl Iterator<Item = &S::Str> {
150        self.0.args.iter().map(AsRef::as_ref)
151    }
152
153    /// The directory in which to run the command, if any.
154    pub fn cwd(&self) -> Option<&S::Str> {
155        self.0.cwd.as_deref()
156    }
157
158    /// The environment variables to run the command with.
159    pub fn vars(&self) -> Option<&Vars<S>> {
160        self.0.vars.as_ref()
161    }
162
163    /// The `stdin` input to provide to the command.
164    pub fn stdin(&self) -> Option<&In> {
165        self.0.stdin.as_ref()
166    }
167
168    /// Indicates what to do with the `stdout` output of the command.
169    pub fn stdout(&self) -> Option<&Out> {
170        self.0.stdout.as_ref()
171    }
172
173    /// Indicates what to do with the `stderr` output of the command.
174    pub fn stderr(&self) -> Option<&Out> {
175        self.0.stderr.as_ref()
176    }
177
178    /// Indicates whether the command may fail.
179    pub fn may_fail(&self) -> bool {
180        self.0.may_fail
181    }
182
183    /// Hints that the command may contain secret information.
184    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    /// Adds an argument to the command.
193    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    /// Extends the arguments of the command.
199    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    /// Sets the directory in which to run the command.
210    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    /// Sets the environment variables to run the command with.
216    pub fn with_vars(mut self, vars: Vars<S>) -> Self {
217        self.data_mut().vars = Some(vars);
218        self
219    }
220
221    /// Sets an environment variable.
222    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    /// Sets the `stdin` input to provide to the command.
231    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    /// Sets what to do with the `stdout` output of the command.
237    pub fn with_stdout(mut self, stdout: Out) -> Self {
238        self.data_mut().stdout = Some(stdout);
239        self
240    }
241
242    /// Sets what to do with the `stderr` output of the command.
243    pub fn with_stderr(mut self, stderr: Out) -> Self {
244        self.data_mut().stderr = Some(stderr);
245        self
246    }
247
248    /// Do not return an error when the command fails.
249    pub fn allow_failures(mut self) -> Self {
250        self.data_mut().may_fail = true;
251        self
252    }
253
254    /// Mark the command as secret.
255    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/// Private auxiliary macro. **Not part of the public API!**
301#[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/// Constructs a command.
323///
324/// See [crate] documentation for details and examples.
325#[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/// Constructs a command using [`OsString`] as string type.
342#[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/// Constructs a command using [`String`] as string type.
351#[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/// Indicates what to do with the output of a command.
360#[derive(Debug, Clone)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
362pub enum Out {
363    /// Discard the output.
364    Discard,
365    /// Inherit the output stream from the parent process.
366    Inherit,
367    /// Capture the output.
368    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/// An input provided to a command.
382#[derive(Debug, Clone)]
383#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
384pub enum In {
385    /// Do not provide any input, i.e., `/dev/null`.
386    Null,
387    /// Inherit the input stream from the parent process.
388    Inherit,
389    /// Provide the given bytes as input.
390    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    /// Indicates that all other environment variables shall be discarded.
431    is_clean: bool,
432    /// The values of the variables.
433    values: HashMap<S, Option<S>>,
434}
435
436/// A set of environment variables.
437#[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    /// Constructs an empty set of environment variables.
443    pub fn new() -> Self {
444        Self(Default::default())
445    }
446
447    /// Indicates that all other environment variables shall be discarded.
448    pub fn is_clean(&self) -> bool {
449        self.0.is_clean
450    }
451
452    /// The values of the environment variables.
453    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    /// Sets the value of an environment variable.
465    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    /// Discards the value of an environment variable.
473    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    /// Inherits the environment variable from the parent process.
481    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    /// Resets a variable.
499    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/// Private auxiliary macro. **Not part of the public API!**
506#[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/// Convenience macro for constructing sets of variables.
529///
530/// ```rust
531/// # use xscript::{vars_os as vars};
532/// vars! {
533///     RUSTDOCFLAGS = "--cfg docsrs --cfg xscript_unstable",
534///     RUSTFLAGS = "--cfg xscript_unstable",
535/// };
536/// ```
537#[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/// Constructs environment variables using [`OsString`] as string type.
548#[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/// Constructs environment variables using [`String`] as string type.
557#[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/// Output produced when running a command.
566#[derive(Debug, Clone, Default)]
567#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
568#[non_exhaustive]
569pub struct RunOutput {
570    /// The exit code, if any.
571    pub code: Option<i32>,
572    /// The `stdout` output, if captured.
573    pub stdout: Option<Vec<u8>>,
574    /// The `stderr` output, if captured.
575    pub stderr: Option<Vec<u8>>,
576}
577
578impl RunOutput {
579    /// Constructs a new [`RunOutput`].
580    pub fn new() -> Self {
581        Self::default()
582    }
583
584    /// Sets the exit code of the command.
585    pub fn with_code(mut self, code: i32) -> Self {
586        self.code = Some(code);
587        self
588    }
589
590    /// Sets the `stdout` output of the command.
591    pub fn with_stdout(mut self, stdout: Vec<u8>) -> Self {
592        self.stdout = Some(stdout);
593        self
594    }
595
596    /// Sets the `stderr` output of the command.
597    pub fn with_stderr(mut self, stderr: Vec<u8>) -> Self {
598        self.stderr = Some(stderr);
599        self
600    }
601
602    /// Tries to transform the `stdout` output to a string.
603    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/// Error running a command.
619#[derive(Debug)]
620pub struct RunError<S: CmdString> {
621    /// The command that failed.
622    cmd: Cmd<S>,
623    /// The kind of error.
624    kind: RunErrorKind,
625}
626
627impl<S: CmdString> RunError<S> {
628    /// Creates a new [`RunError`].
629    pub fn new(cmd: Cmd<S>, kind: RunErrorKind) -> Self {
630        Self { cmd, kind }
631    }
632
633    /// Transforms a [`RunErrorKind`] of a closure to [`RunError`].
634    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    /// Transforms a [`RunErrorKind`] of a closure to [`RunError`].
642    #[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
698/// The result of running a command.
699pub type RunResult<T, S> = Result<T, RunError<S>>;
700
701/// Error while running a command.
702#[derive(Debug)]
703pub enum RunErrorKind {
704    /// The command failed with a non-zero exit code.
705    Failed(RunOutput),
706    /// There was an [`io::Error`].
707    Io(io::Error),
708    /// A custom error message.
709    Custom(String),
710    /// The was some other error.
711    Other(Box<dyn 'static + Sync + Send + std::error::Error>),
712}
713
714impl RunErrorKind {
715    /// Constructs a [`RunErrorKind`] from some error.
716    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/// Runs a command in a given environment (see [`Run::run`]).
746#[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/// Runs a command in a given environment reading `stdout` as a string (see
757/// [`Run::read_str`]).
758#[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/// Runs a command in a given environment reading `stdout` as bytes (see
769/// [`Run::read_bytes`])).
770#[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/// Shared inner data of an execution environment.
781#[derive(Debug, Clone)]
782struct EnvInner {
783    /// The working directory of the environment.
784    cwd: PathBuf,
785    /// The environment variables of the environment, if any.
786    vars: Vars<OsString>,
787    /// The default input provided to commands, if any.
788    default_stdin: In,
789    /// Indicates what to do with the `stdout` output by default.
790    default_stdout: Out,
791    /// Indicates what to do with the `stderr` output by default.
792    default_stderr: Out,
793    /// Replay any captured `stdout` output.
794    replay_stdout: bool,
795    /// Replay any captured `stderr` output.
796    replay_stderr: bool,
797    /// Echo commands before they are executed.
798    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
816/// Execution environment of the parent process.
817pub struct ParentEnv;
818
819impl Run<OsString> for ParentEnv {
820    fn run(&self, cmd: Cmd<OsString>) -> Result<RunOutput, RunError<OsString>> {
821        // TODO: This is inefficient, we should factor out the actual launch code.
822        let env = RunError::catch(&cmd, || LocalEnv::current_dir().map_err(RunErrorKind::from))?;
823        Run::run(&env, cmd)
824    }
825}
826
827/// A local execution environment.
828#[derive(Debug, Clone)]
829pub struct LocalEnv(Arc<EnvInner>);
830
831impl LocalEnv {
832    /// Creates an execution environment with the given working directory.
833    pub fn new<P: AsRef<Path>>(cwd: P) -> Self {
834        Self(Arc::new(EnvInner::new(cwd.as_ref().to_path_buf())))
835    }
836
837    /// Creates an execution environment with the current working directory.
838    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    /// The working directory of the environment.
847    pub fn cwd(&self) -> &Path {
848        &self.0.cwd
849    }
850
851    /// Sets the working directory of the environment.
852    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    /// Sets the working directory of the environment.
858    pub fn with_cwd<P: AsRef<Path>>(mut self, cwd: P) -> Self {
859        self.set_cwd(cwd);
860        self
861    }
862
863    /// The environment variables of the environment.
864    pub fn vars(&self) -> &Vars {
865        &self.0.vars
866    }
867
868    /// Sets the environment variables of the environment.
869    pub fn set_vars(&mut self, vars: Vars) -> &mut Self {
870        self.inner_mut().vars = vars;
871        self
872    }
873
874    /// Sets the environment variables of the environment.
875    pub fn with_vars(mut self, vars: Vars) -> Self {
876        self.set_vars(vars);
877        self
878    }
879
880    /// Sets an environment variable.
881    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    /// Sets an environment variable.
887    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    /// The default `stdin` input to provide to commands.
893    pub fn default_stdin(&self) -> &In {
894        &self.0.default_stdin
895    }
896
897    /// Sets the default `stdin` input to provide to commands.
898    pub fn with_default_stdin(mut self, stdin: In) -> Self {
899        self.inner_mut().default_stdin = stdin;
900        self
901    }
902
903    /// Indicates what to do with the `stdout` output of commands by default.
904    pub fn default_stdout(&self) -> &Out {
905        &self.0.default_stdout
906    }
907
908    /// Sets what to do with the `stdout` output of commands by default.
909    pub fn with_default_stdout(mut self, stdout: Out) -> Self {
910        self.inner_mut().default_stdout = stdout;
911        self
912    }
913
914    /// Indicates what to do with the `stderr` output of commands by default.
915    pub fn default_stderr(&self) -> &Out {
916        &self.0.default_stderr
917    }
918
919    // Sets what to do with the `stderr` output of commands by default.
920    pub fn with_default_stderr(mut self, stderr: Out) -> Self {
921        self.inner_mut().default_stderr = stderr;
922        self
923    }
924
925    /// Enables the echoing of commands.
926    pub fn with_echo(mut self) -> Self {
927        self.inner_mut().echo_commands = true;
928        self
929    }
930
931    /// Disables the echoing of commands.
932    pub fn without_echo(mut self) -> Self {
933        self.inner_mut().echo_commands = false;
934        self
935    }
936
937    /// Changes the working directory of the environment.
938    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    /// Resolves a path relative to the working directory of the environment.
943    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        // Populate the environment variables.
970        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        // Configure IO.
978        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
1003/// Trait for running commands in an execution environment.
1004pub trait Run<S: CmdString> {
1005    /// Runs a command returning its output.
1006    fn run(&self, cmd: Cmd<S>) -> Result<RunOutput, RunError<S>>;
1007
1008    /// Runs a command returning its `stdout` output as a string.
1009    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    /// Runs a command returning its `stderr` output as a string.
1016    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/// Trait for running commands asynchronously in an execution environment.
1074#[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            // Force capture the output.
1086            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}