command_stream/execa/
command.rs1use 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#[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 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#[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 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 pub async fn pipe(self, mut destination: ExecaCommand) -> ExecaOutcome {
128 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}