io_process/coroutines/
spawn-then-wait-with-output.rs

1//! I/O-free coroutine to spawn a process and wait for its child's output.
2
3use log::{debug, trace};
4use std::process::Output;
5use thiserror::Error;
6
7use crate::{command::Command, io::ProcessIo};
8
9/// Errors that can occur during the coroutine progression.
10#[derive(Debug, Error)]
11pub enum SpawnThenWaitWithOutputError {
12    /// The coroutine received an invalid argument.
13    ///
14    /// Occurs when the coroutine receives an I/O response from
15    /// another coroutine, which should not happen if the runtime maps
16    /// correctly the arguments.
17    #[error("Invalid argument: expected {0}, got {1:?}")]
18    InvalidArgument(&'static str, ProcessIo),
19
20    /// The command was not initialized.
21    #[error("Command not initialized")]
22    NotInitialized,
23}
24
25/// Output emitted after a coroutine finishes its progression.
26#[derive(Debug)]
27pub enum SpawnThenWaitWithOutputResult {
28    /// The coroutine has successfully terminated its progression.
29    Ok(Output),
30
31    /// A process I/O needs to be performed to make the coroutine progress.
32    Io(ProcessIo),
33
34    /// An error occurred during the coroutine progression.
35    Err(SpawnThenWaitWithOutputError),
36}
37
38/// I/O-free coroutine for spawning a process then waiting for its
39/// child's output.
40///
41/// This coroutine should be used when you need to collect the child
42/// process' output, from stdout and stderr.
43///
44/// If you do not need to collect the output, or if you need to pipe
45/// the output to another process, see
46/// [`super::spawn_then_wait::SpawnThenWait`].
47#[derive(Debug)]
48pub struct SpawnThenWaitWithOutput {
49    cmd: Option<Command>,
50}
51
52impl SpawnThenWaitWithOutput {
53    /// Creates a new coroutine from the given command builder.
54    pub fn new(cmd: Command) -> Self {
55        trace!("prepare command to be spawned: {cmd:?}");
56        let cmd = Some(cmd);
57        Self { cmd }
58    }
59
60    /// Makes the coroutine progress.
61    pub fn resume(&mut self, arg: Option<ProcessIo>) -> SpawnThenWaitWithOutputResult {
62        let Some(arg) = arg else {
63            let Some(cmd) = self.cmd.take() else {
64                return SpawnThenWaitWithOutputResult::Err(
65                    SpawnThenWaitWithOutputError::NotInitialized,
66                );
67            };
68
69            trace!("break: need I/O to spawn command");
70            return SpawnThenWaitWithOutputResult::Io(ProcessIo::SpawnThenWaitWithOutput(Err(cmd)));
71        };
72
73        trace!("resume after spawning command");
74
75        let ProcessIo::SpawnThenWaitWithOutput(io) = arg else {
76            let err = SpawnThenWaitWithOutputError::InvalidArgument("spawn output", arg);
77            return SpawnThenWaitWithOutputResult::Err(err);
78        };
79
80        let output = match io {
81            Ok(output) => output,
82            Err(cmd) => {
83                let io = ProcessIo::SpawnThenWaitWithOutput(Err(cmd));
84                return SpawnThenWaitWithOutputResult::Io(io);
85            }
86        };
87
88        debug!("spawned command: {:?}", output.status);
89        SpawnThenWaitWithOutputResult::Ok(output)
90    }
91}