Skip to main content

rust_terminal/
command.rs

1use std::{
2    ffi::OsStr,
3    io::{self, Read, Write},
4    path::{Path, PathBuf},
5    process::{Child, Command as StdCmd, ExitStatus, Output, Stdio},
6};
7
8use crate::TerminalError;
9
10pub struct Command {
11    pipe_stdout: bool,
12    current_dir: Option<PathBuf>,
13}
14
15impl Command {
16    pub fn new() -> Self {
17        Self {
18            pipe_stdout: false,
19            current_dir: None,
20        }
21    }
22
23    pub fn current_dir(&mut self, dir: &Path) -> &mut Self {
24        self.current_dir = Some(dir.to_path_buf());
25        self
26    }
27
28    pub fn piped(&mut self) -> &mut Self {
29        self.pipe_stdout = true;
30        self
31    }
32
33    pub fn run<I, S>(&self, command: &str, args: I) -> Result<Output, TerminalError>
34    where
35        I: IntoIterator<Item = S>,
36        S: AsRef<OsStr>,
37    {
38        match self.pipe_stdout {
39            true => self.capture_output(command, args),
40            false => self.wait_for_output(command, args),
41        }
42    }
43
44    /// Run command without piping output to parent process.
45    fn wait_for_output<I, S>(&self, command: &str, args: I) -> Result<Output, TerminalError>
46    where
47        I: IntoIterator<Item = S>,
48        S: AsRef<OsStr>,
49    {
50        let mut cmd = StdCmd::new(command);
51        match &self.current_dir {
52            Some(dir) => Ok(cmd.args(args).current_dir(dir).output()?),
53            None => Ok(cmd.args(args).output()?),
54        }
55    }
56
57    fn capture_output<I, S>(&self, command: &str, args: I) -> Result<Output, TerminalError>
58    where
59        I: IntoIterator<Item = S>,
60        S: AsRef<OsStr>,
61    {
62        let mut child = self.spawn(command, args)?;
63        let exit_status = Self::read_stdout_from(&mut child)?;
64        Ok(Output {
65            status: exit_status,
66            stdout: vec![],
67            stderr: vec![],
68        })
69    }
70
71    /// Spawn a command and wait for it to finish, pipes stdout to parent process.
72    fn spawn<I, S>(&self, command: &str, args: I) -> Result<Child, TerminalError>
73    where
74        I: IntoIterator<Item = S>,
75        S: AsRef<OsStr>,
76    {
77        let mut cmd = StdCmd::new(command);
78
79        let child = match &self.current_dir {
80            Some(dir) => cmd
81                .args(args)
82                .current_dir(dir)
83                .stdout(Stdio::piped())
84                .spawn()?,
85            None => cmd.args(args).stdout(Stdio::piped()).spawn()?,
86        };
87        Ok(child)
88    }
89
90    /// Takes a child process and reads its stdout while waiting for it to finish.
91    pub fn read_stdout_from(child: &mut Child) -> Result<ExitStatus, TerminalError> {
92        let mut child_stdout = child
93            .stdout
94            .take()
95            .ok_or("There was a problem acquiring stdout from child process")?;
96        let mut buffer = [0; 1024];
97        loop {
98            let bytes_read = child_stdout.read(&mut buffer)?;
99            if bytes_read == 0 {
100                break;
101            }
102            io::stdout().write_all(&buffer[..bytes_read])?;
103        }
104        Ok(child.wait()?)
105    }
106}