shrimp/
step.rs

1use std::{
2    io::{prelude::*, Error, ErrorKind, Result},
3    process::{Command, Output, Stdio},
4};
5
6use crate::redirection;
7
8mod builtins {
9    #[derive(Debug)]
10    pub struct Builtin;
11    ///Roughly analogous to process::Command
12    impl Builtin {
13        pub fn new() {}
14        //Possibly implement from String to do the parsing, similar to Step::parse_command
15    }
16}
17
18/// Step, the basic Unit of execution of a Pipeline. Can either be a Shrimp Built-in function or a Command
19/// Design wise - a "Wrapper" enum was chosen because the Std::Command is a simple struct, it has no trait that builtins could implement (CommandExt are sealed)
20#[derive(Debug)]
21pub enum Step {
22    Command(std::process::Command),
23    Builtin(builtins::Builtin),
24}
25
26/// Roughly analogous to process::Output mixed with process::ExitStatus.
27/// Since process::ExitStatus is sealed, we can't instantiate it directly. With our own struct, Builtins can use it as well
28#[derive(Debug)]
29pub struct StepOutput {
30    pub success: bool,
31    pub code: Option<i32>,
32    pub stdout: Vec<u8>,
33    pub stderr: Vec<u8>,
34}
35
36impl From<Output> for StepOutput {
37    fn from(output: Output) -> StepOutput {
38        Self {
39            stdout: output.stdout,
40            stderr: output.stderr,
41            code: output.status.code(),
42            success: output.status.success(),
43        }
44    }
45}
46
47impl Step {
48    ///Creates a new Step. It will validate if the desired command is a Built-in or an external program and
49    /// Return the enum variant accordingly
50    pub fn new(raw_step_str: &str) -> Result<Step> {
51        //TODO Improve this step creation - CHECK IF BUILT-IN
52        let c = Step::parse_command(raw_step_str)?;
53        Ok(Step::Command(c))
54    }
55
56    /// Parses a string and returns a Command ready to be Executed, or and error.
57    fn parse_command(raw_cmd_str: &str) -> Result<Command> {
58        let cmd_string = String::from(raw_cmd_str);
59        let mut words = cmd_string.split_whitespace();
60
61        //Parse program
62        let program = words.next();
63        if program.is_none() {
64            let e = Error::new(ErrorKind::InvalidInput, "Empty Program");
65            return Err(e);
66        }
67
68        let mut command = Command::new(program.unwrap());
69
70        for w in words {
71            match w {
72                //There can be no arguments after the beginning of redirection
73                _ if redirection::Redirection::is_redirection(w) => {
74                    break;
75                }
76                //Arguments
77                _ => {
78                    command.arg(&w);
79                }
80            }
81        }
82        Ok(command)
83    }
84
85    /// Runs the Step
86    pub fn run(&mut self, stdin: &[u8]) -> Result<StepOutput> {
87        match self {
88            Step::Command(c) => {
89                let mut process = c
90                    .stdin(Stdio::piped())
91                    .stdout(Stdio::piped())
92                    .stderr(Stdio::piped())
93                    .spawn()?;
94                process.stdin.as_ref().unwrap().write_all(stdin)?;
95                process.wait().unwrap();
96                Ok(StepOutput::from(process.wait_with_output()?))
97            }
98            _ => unimplemented!(),
99        }
100    }
101}