stream_download/process/
command_builder.rs

1use std::io;
2use std::process::Stdio;
3
4use super::{Command, SpawnCommand, SpawnedCommand, WrapIoResult, stdio_to_tmp_file};
5
6/// A builder object that can pipe multiple commands together and automatically configure the child
7/// process' handles correctly.
8#[derive(Debug)]
9pub struct CommandBuilder {
10    commands: Vec<Command>,
11}
12
13impl CommandBuilder {
14    /// Creates a new [`CommandBuilder`].
15    pub fn new<C>(command: C) -> Self
16    where
17        C: Into<Command>,
18    {
19        Self {
20            commands: vec![command.into()],
21        }
22    }
23
24    /// Adds a new [`Command`] to the pipeline. The previous command's `stdout` stream will be piped
25    /// into this command's `stdin`. This is equivalent to doing `cmd1 | cmd2`.
26    #[must_use]
27    pub fn pipe<C>(mut self, command: C) -> Self
28    where
29        C: Into<Command>,
30    {
31        self.commands.push(command.into());
32        self
33    }
34}
35
36impl SpawnCommand for CommandBuilder {
37    fn spawn(mut self) -> io::Result<SpawnedCommand> {
38        let last = self.commands.pop().expect("prevented by constructor");
39        let mut prev_stdout = None;
40        let mut stderr_files = Vec::new();
41
42        for command in self.commands {
43            let mut std_command = std::process::Command::new(command.program);
44            std_command
45                .args(command.args.clone())
46                .stdout(Stdio::piped());
47            if let Some(handle) = command.stderr_handle {
48                std_command.stderr(handle);
49            } else {
50                let (stdio, stderr_file) = stdio_to_tmp_file()?;
51                std_command.stderr(stdio);
52                stderr_files.push(stderr_file);
53            }
54
55            #[cfg(target_os = "windows")]
56            {
57                // CREATE_NO_WINDOW
58                use std::os::windows::process::CommandExt;
59                std_command.creation_flags(0x08000000);
60            }
61
62            if let Some(prev_stdout) = prev_stdout.take() {
63                std_command.stdin(prev_stdout);
64            }
65            let mut command_out = std_command.spawn().wrap_err("error spawning process")?;
66            prev_stdout = command_out.stdout.take();
67        }
68        SpawnedCommand::new(last, prev_stdout, stderr_files)
69    }
70}