Part 2. Building a pipeline (25 points)
Now that your split_input()
function takes a string and returns a vector of
InputTokens
, the second step is to build a pipeline. To do this, you’re
going to create a SimpleCommand
struct and a Pipeline
struct.
The Pipeline
struct will contain a vector of SimpleCommands
to run.
At a high level, the Pipeline
constructor,
#![allow(unused)] fn main() { type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; enum InputToken { Word(String), Pipe } struct Pipeline; impl Pipeline { pub fn new(tokens: &[InputToken]) -> Result<Self> { todo!() } } }
takes a slice of InputToken
s and constructs a Pipeline
by splitting the
tokens on InputToken::Pipe
. Then, for each sequence of tokens between the
InputToken::Pipe
, new()
will construct a SimpleCommand
.
For example, the command cat Cargo.toml | wc -l
is split into the following tokens.
[Word("cat"), Word("Cargo.toml"), Pipe, Word("wc"), Word("-l")]
When these tokens are passed to Pipeline::new(tokens)
, it constructs the following Pipeline
.
Pipeline {
commands: [
SimpleCommand {
path: "cat",
args: [
"Cargo.toml",
],
},
SimpleCommand {
path: "wc",
args: [
"-l",
],
},
],
}
Your task
Create a new pipeline
module by creating a pipeline.rs
file and adding the
appropriate mod
line to main.rs
.
Create a SimpleCommand
struct that holds a path
(or command name) of the
program to execute and a vector of String
arguments. (In the next lab, we’ll
expand this struct.)
Next, create a public Pipeline
struct containing a vector of SimpleCommand
s.
Implement the Pipeline::new()
constructor shown above.
We can use the .split()
method on a slice to split it on InputToken::Pipe
. The result is an iterator over sub-slices.
for command_tokens in tokens.split(|token| token == &InputToken::Pipe) {
let mut words: Vec<String> = Vec::new();
for token in command_tokens {
todo!("Push each word into words");
}
if words.is_empty() {
return Err("Command missing".into());
}
todo!("Create a new SimpleCommand from the words");
}
words.remove(0)
will remove and return the first word from words
. You can use this to create the SimpleCommand
.
Update your main()
function to print the debug representation of the
Pipeline
that results from the input tokens you parsed in the previous part.
Create some unit tests to test the construction of a Pipeline
. You should write at least two additional tests, beyond what is provided here. Here’s one to
get you started using the example above.
#[cfg(test)]
mod test {
use super::*;
#[test]
fn simple_pipeline() {
let tokens = [
InputToken::Word("cat".to_string()),
InputToken::Word("Cargo.toml".to_string()),
InputToken::Pipe,
InputToken::Word("wc".to_string()),
InputToken::Word("-l".to_string()),
];
let pipeline = Pipeline::new(&tokens).expect("Failed to create a Pipeline");
assert_eq!(
pipeline,
Pipeline {
commands: vec![
SimpleCommand {
path: "cat".to_string(),
args: vec!["Cargo.toml".to_string()]
},
SimpleCommand {
path: "wc".to_string(),
args: vec!["-l".to_string()]
},
]
}
);
}
}