process/
pipeline.rs

1//! # Pipeline of commands
2//!
3//! Module dedicated to pipelines. It only exposes the [`Pipeline`]
4//! struct, and various implementations of transformation.
5
6use std::{
7    fmt,
8    ops::{Deref, DerefMut},
9};
10
11use tracing::{debug, info};
12
13use crate::{Command, Output, Result};
14
15/// The command pipeline structure.
16///
17/// A pipeline represents a list of [`Command`]s where the output of
18/// the previous command is piped to the input of the next one.
19#[derive(Clone, Debug, Default, Eq, PartialEq)]
20#[cfg_attr(
21    feature = "derive",
22    derive(serde::Serialize, serde::Deserialize),
23    serde(from = "Vec<String>", into = "Vec<String>")
24)]
25pub struct Pipeline(Vec<Command>);
26
27impl Pipeline {
28    /// Creates a new pipeline from the given iterator.
29    pub fn new(cmds: impl IntoIterator<Item = impl ToString>) -> Self {
30        Self(cmds.into_iter().map(Command::new).collect())
31    }
32
33    /// Wrapper around [`alloc::str::replace`].
34    ///
35    /// This function is particularly useful when you need to replace
36    /// placeholders on all inner commands.
37    pub fn replace(mut self, from: impl AsRef<str>, to: impl AsRef<str>) -> Self {
38        for cmd in self.iter_mut() {
39            *cmd = cmd.clone().replace(from.as_ref(), to.as_ref())
40        }
41
42        self
43    }
44
45    /// Runs the current pipeline without initial input.
46    ///
47    /// See [`Pipeline::run_with`] to run command with output.
48    pub async fn run(&self) -> Result<Output> {
49        self.run_with([]).await
50    }
51
52    /// Run the command pipeline with the given initial input.
53    ///
54    /// After the first command executes, the input is replaced with
55    /// its output.
56    pub async fn run_with(&self, input: impl IntoIterator<Item = u8>) -> Result<Output> {
57        info!("run pipeline of {} commands", self.len());
58
59        let mut output: Vec<u8> = input.into_iter().collect();
60
61        for (i, cmd) in self.iter().enumerate() {
62            debug!("run command {} from pipeline", i + 1);
63            output = cmd.run_with(&output).await?.into();
64        }
65
66        Ok(Output::from(output))
67    }
68}
69
70impl Deref for Pipeline {
71    type Target = Vec<Command>;
72
73    fn deref(&self) -> &Self::Target {
74        &self.0
75    }
76}
77
78impl DerefMut for Pipeline {
79    fn deref_mut(&mut self) -> &mut Self::Target {
80        &mut self.0
81    }
82}
83
84impl From<Vec<Command>> for Pipeline {
85    fn from(cmds: Vec<Command>) -> Self {
86        Self(cmds)
87    }
88}
89
90impl From<Vec<String>> for Pipeline {
91    fn from(cmds: Vec<String>) -> Self {
92        Self(cmds.into_iter().map(Command::from).collect())
93    }
94}
95
96impl From<Pipeline> for Vec<String> {
97    fn from(pipeline: Pipeline) -> Self {
98        pipeline.iter().map(ToString::to_string).collect()
99    }
100}
101
102impl fmt::Display for Pipeline {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        let mut glue = "";
105
106        for cmd in self.iter() {
107            write!(f, "{glue}{}", cmd.to_string())?;
108            glue = " | ";
109        }
110
111        Ok(())
112    }
113}