io_process/coroutines/
spawn-then-wait.rs

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