io_process/runtimes/
tokio.rs

1//! The Tokio-based, async process runtime.
2
3use std::{io, process::Output};
4
5use tokio::process::Command as TokioCommand;
6
7use crate::{command::Command, io::ProcessIo, status::SpawnStatus};
8
9/// The Tokio-based, async process runtime.
10///
11/// This handler makes use of the [`tokio::process`] module to spawn
12/// processes and wait for exit status or output.
13pub async fn handle(io: ProcessIo) -> io::Result<ProcessIo> {
14    match io {
15        ProcessIo::SpawnThenWait(io) => spawn_then_wait(io).await,
16        ProcessIo::SpawnThenWaitWithOutput(io) => spawn_then_wait_with_output(io).await,
17    }
18}
19
20/// Spawns a process then wait for its child's exit status.
21///
22/// This function builds a [`std::process::Command`] from the flow's
23/// command builder, spawns a process, collects std{in,out,err} then
24/// waits for the exit status.
25pub async fn spawn_then_wait(input: Result<SpawnStatus, Command>) -> io::Result<ProcessIo> {
26    let Err(command) = input else {
27        let kind = io::ErrorKind::InvalidInput;
28        return Err(io::Error::new(kind, "missing command"));
29    };
30
31    let mut command = TokioCommand::from(command);
32    let mut child = command.spawn()?;
33
34    #[cfg(unix)]
35    let stdin = child.stdin.take().and_then(|io| io.into_owned_fd().ok());
36    #[cfg(windows)]
37    let stdin = child
38        .stdin
39        .take()
40        .and_then(|io| io.into_owned_handle().ok());
41
42    #[cfg(unix)]
43    let stdout = child.stdout.take().and_then(|io| io.into_owned_fd().ok());
44    #[cfg(windows)]
45    let stdout = child
46        .stdout
47        .take()
48        .and_then(|io| io.into_owned_handle().ok());
49
50    #[cfg(unix)]
51    let stderr = child.stderr.take().and_then(|io| io.into_owned_fd().ok());
52    #[cfg(windows)]
53    let stderr = child
54        .stderr
55        .take()
56        .and_then(|io| io.into_owned_handle().ok());
57
58    let output = SpawnStatus {
59        status: child.wait().await?,
60        stdin: stdin.map(Into::into),
61        stdout: stdout.map(Into::into),
62        stderr: stderr.map(Into::into),
63    };
64
65    Ok(ProcessIo::SpawnThenWait(Ok(output)))
66}
67
68/// Spawns a process then wait for its child's output.
69///
70/// This function builds a [`std::process::Command`] from the flow's
71/// command builder, spawns a process, then waits for the output.
72pub async fn spawn_then_wait_with_output(input: Result<Output, Command>) -> io::Result<ProcessIo> {
73    let Err(command) = input else {
74        let kind = io::ErrorKind::InvalidInput;
75        return Err(io::Error::new(kind, "missing command"));
76    };
77
78    let mut command = TokioCommand::from(command);
79    let output = command.output().await?;
80
81    Ok(ProcessIo::SpawnThenWaitWithOutput(Ok(output)))
82}
83
84/// Converts a [`Command`] builder to a [`std::process::Command`].
85impl From<Command> for TokioCommand {
86    fn from(builder: Command) -> Self {
87        let mut command = TokioCommand::new(&*builder.get_program());
88
89        if let Some(args) = builder.get_args() {
90            for arg in args {
91                command.arg(&*arg);
92            }
93        }
94
95        if let Some(envs) = builder.envs {
96            for (key, val) in envs {
97                command.env(key, val);
98            }
99        }
100
101        if let Some(dir) = builder.current_dir {
102            command.current_dir(dir);
103        }
104
105        if let Some(cfg) = builder.stdin {
106            command.stdin(cfg);
107        }
108
109        if let Some(cfg) = builder.stdout {
110            command.stdout(cfg);
111        }
112
113        if let Some(cfg) = builder.stderr {
114            command.stderr(cfg);
115        }
116
117        command
118    }
119}