conch_runtime_pshaw/spawn/
sequence.rs

1use crate::env::{IsInteractiveEnvironment, LastStatusEnvironment, ReportErrorEnvironment};
2use crate::error::IsFatalError;
3use crate::spawn::swallow_non_fatal_errors;
4use crate::{ExitStatus, Spawn, EXIT_SUCCESS};
5use futures_core::future::BoxFuture;
6
7/// Spawns any iterable collection of sequential items.
8///
9/// Commands are sequentially executed regardless of the exit status of
10/// previous commands. All non-fatal errors are reported and swallowed,
11/// however, "fatal" errors are bubbled up and the sequence terminated.
12pub async fn sequence<I, E: ?Sized>(
13    iter: I,
14    env: &mut E,
15) -> Result<BoxFuture<'static, ExitStatus>, <I::Item as Spawn<E>>::Error>
16where
17    E: IsInteractiveEnvironment + LastStatusEnvironment + ReportErrorEnvironment,
18    I: IntoIterator,
19    I::Item: Spawn<E>,
20    <I::Item as Spawn<E>>::Error: IsFatalError,
21{
22    // NB: if in interactive mode, don't peek at the next command
23    // because the input may not be ready (e.g. blocking iterator)
24    // and we don't want to block this command on further, unrelated, input.
25    do_sequence(iter.into_iter().peekable(), env, |env, iter| {
26        env.is_interactive() || iter.peek().is_some()
27    })
28    .await
29}
30
31/// Spawns an exact-size iterator of sequential items.
32///
33/// Commands are sequentially executed regardless of the exit status of
34/// previous commands. All non-fatal errors are reported and swallowed,
35/// however, "fatal" errors are bubbled up and the sequence terminated.
36pub async fn sequence_exact<I, E>(
37    cmds: I,
38    env: &mut E,
39) -> Result<BoxFuture<'static, ExitStatus>, <I::Item as Spawn<E>>::Error>
40where
41    I: IntoIterator,
42    I::IntoIter: ExactSizeIterator,
43    I::Item: Spawn<E>,
44    <I::Item as Spawn<E>>::Error: IsFatalError,
45    E: ?Sized + LastStatusEnvironment + ReportErrorEnvironment,
46{
47    do_sequence(cmds.into_iter(), env, |_, iter| iter.len() != 0).await
48}
49
50/// Creates a [`Spawn`] adapter around a maybe owned slice of commands.
51///
52/// Spawn behavior is the same as [`sequence_exact`].
53pub fn sequence_slice<S>(cmds: &'_ [S]) -> SequenceSlice<'_, S> {
54    SequenceSlice { cmds }
55}
56
57/// [`Spawn`] adapter around a maybe owned slice of commands.
58///
59/// Created by the [`sequence_slice`] function.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct SequenceSlice<'a, S> {
62    cmds: &'a [S],
63}
64
65impl<'a, S, E> Spawn<E> for SequenceSlice<'a, S>
66where
67    S: Send + Sync + Spawn<E>,
68    S::Error: IsFatalError,
69    E: ?Sized + Send + LastStatusEnvironment + ReportErrorEnvironment,
70{
71    type Error = S::Error;
72
73    fn spawn<'life0, 'life1, 'async_trait>(
74        &'life0 self,
75        env: &'life1 mut E,
76    ) -> BoxFuture<'async_trait, Result<BoxFuture<'static, ExitStatus>, Self::Error>>
77    where
78        'life0: 'async_trait,
79        'life1: 'async_trait,
80        Self: 'async_trait,
81    {
82        Box::pin(sequence_exact(self.cmds, env))
83    }
84}
85
86async fn do_sequence<I, E>(
87    mut iter: I,
88    env: &mut E,
89    has_more: impl Fn(&E, &mut I) -> bool,
90) -> Result<BoxFuture<'static, ExitStatus>, <I::Item as Spawn<E>>::Error>
91where
92    E: ?Sized + LastStatusEnvironment + ReportErrorEnvironment,
93    I: Iterator,
94    I::Item: Spawn<E>,
95    <I::Item as Spawn<E>>::Error: IsFatalError,
96{
97    let mut last_status = EXIT_SUCCESS; // Init in case we don't run at all
98    while let Some(cmd) = iter.next() {
99        let cmd = swallow_non_fatal_errors(&cmd, env).await?;
100
101        if has_more(env, &mut iter) {
102            // We still expect more commands in the sequence, therefore,
103            // we should keep polling and hold on to the environment here
104            last_status = cmd.await;
105            env.set_last_status(last_status);
106        } else {
107            // The last command of our sequence which no longer needs
108            // an environment context, so we can yield it back to the caller.
109            return Ok(cmd);
110        }
111    }
112
113    Ok(Box::pin(async move { last_status }))
114}