Skip to main content

command_stream/execa/
command.rs

1use super::{CancelSignal, ExecaError, ExecaOutcome, ExecaResult, Options, Subprocess};
2use std::ffi::OsString;
3use std::future::{Future, IntoFuture};
4use std::path::PathBuf;
5use std::pin::Pin;
6use std::time::Duration;
7
8/// A reusable command factory with private default options.
9#[derive(Debug, Clone, Default)]
10pub struct Execa {
11    options: Options,
12}
13
14impl Execa {
15    pub fn new(options: Options) -> Self {
16        Self { options }
17    }
18
19    pub fn options(&self) -> &Options {
20        &self.options
21    }
22
23    pub fn command<P, I, S>(&self, file: P, args: I) -> ExecaCommand
24    where
25        P: Into<OsString>,
26        I: IntoIterator<Item = S>,
27        S: Into<OsString>,
28    {
29        ExecaCommand {
30            file: file.into(),
31            args: args.into_iter().map(Into::into).collect(),
32            options: self.options.clone(),
33        }
34    }
35
36    /// Run a Node.js file. Node's JavaScript IPC helpers are not emulated.
37    pub fn node<P, I, S>(&self, file: P, args: I) -> ExecaCommand
38    where
39        P: Into<OsString>,
40        I: IntoIterator<Item = S>,
41        S: Into<OsString>,
42    {
43        let mut node_args = self.options.node_options.clone();
44        node_args.push(file.into());
45        node_args.extend(args.into_iter().map(Into::into));
46        self.command(self.options.node_exec_path.clone(), node_args)
47    }
48}
49
50/// An exact-argv command. Await it directly or call `spawn()` to stream output.
51#[derive(Debug, Clone)]
52pub struct ExecaCommand {
53    pub(crate) file: OsString,
54    pub(crate) args: Vec<OsString>,
55    pub(crate) options: Options,
56}
57
58macro_rules! setter {
59    ($name:ident, $field:ident, $ty:ty) => {
60        pub fn $name(mut self, value: $ty) -> Self {
61            self.options.$field = value;
62            self
63        }
64    };
65}
66
67impl ExecaCommand {
68    pub fn with_options(mut self, options: Options) -> Self {
69        self.options = options;
70        self
71    }
72
73    setter!(reject, reject, bool);
74    setter!(all, all, bool);
75    setter!(buffer, buffer, bool);
76    setter!(strip_final_newline, strip_final_newline, bool);
77    setter!(max_buffer, max_buffer, usize);
78
79    pub fn input(mut self, input: impl Into<Vec<u8>>) -> Self {
80        self.options.input = Some(input.into());
81        self
82    }
83
84    pub fn cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
85        self.options.cwd = Some(cwd.into());
86        self
87    }
88
89    pub fn timeout(mut self, timeout: Duration) -> Self {
90        self.options.timeout = Some(timeout);
91        self
92    }
93
94    pub fn cancel_signal(mut self, signal: CancelSignal) -> Self {
95        self.options.cancel_signal = Some(signal);
96        self
97    }
98
99    pub fn spawn(self) -> Subprocess {
100        Subprocess::new(self)
101    }
102
103    pub async fn run(self) -> ExecaOutcome {
104        self.spawn().wait().await
105    }
106
107    /// Run outside a Tokio runtime. Async callers should await the command.
108    pub fn sync(self) -> ExecaOutcome {
109        let failure = |cause: String| ExecaError {
110            result: Box::new(ExecaResult {
111                failed: true,
112                cause: Some(cause),
113                ..ExecaResult::default()
114            }),
115        };
116        if tokio::runtime::Handle::try_current().is_ok() {
117            return Err(failure(
118                "sync cannot run inside Tokio; await the command instead".into(),
119            ));
120        }
121        let runtime = tokio::runtime::Runtime::new().map_err(|error| failure(error.to_string()))?;
122        runtime.block_on(self.run())
123    }
124
125    /// Pass captured stdout as exact input to the next command. This convenience
126    /// is buffered; use the native Pipeline API for a streaming Rust pipeline.
127    pub async fn pipe(self, mut destination: ExecaCommand) -> ExecaOutcome {
128        // This buffered convenience needs source bytes even when the caller
129        // disabled retaining its ordinary result output.
130        let source = self.buffer(true).strip_final_newline(false).run().await?;
131        destination.options.input = Some(source.stdout);
132        destination.run().await
133    }
134}
135
136impl IntoFuture for ExecaCommand {
137    type Output = ExecaOutcome;
138    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send>>;
139
140    fn into_future(self) -> Self::IntoFuture {
141        Box::pin(self.run())
142    }
143}