Last active
October 19, 2018 19:36
-
-
Save jacobmischka/cc11991b4be566923c54ba7a45be5b1b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::io::{self, Write}; | |
use std::process::{Command, Output, ExitStatus}; | |
fn main() -> io::Result<()> { | |
let mut shell = Shell::new(); | |
shell.spawn() | |
} | |
struct Shell { | |
input_history: Vec<String>, | |
last_exit_status: Option<ExitStatus> | |
} | |
#[derive(Debug)] | |
enum ShellCommand { | |
Command(String), | |
OrCommand(String), | |
AndCommand(String) | |
} | |
impl Shell { | |
fn new() -> Shell { | |
Shell{ | |
input_history: Vec::new(), | |
last_exit_status: None | |
} | |
} | |
fn spawn(&mut self) -> io::Result<()> { | |
loop { | |
let input = self.read_line()?; | |
if input.is_empty() { | |
return Ok(()); | |
} | |
if input.trim().is_empty() { | |
continue; | |
} | |
self.input_history.push(input.clone()); | |
let commands = self.get_commands(&input)?; | |
for command in commands { | |
let result = match command { | |
ShellCommand::Command(input) => { | |
self.exec(&input) | |
} | |
ShellCommand::OrCommand(input) => { | |
if let Some(last_status) = self.last_exit_status { | |
if !last_status.success() { | |
self.exec(&input) | |
} else { | |
continue; | |
} | |
} else { | |
continue; | |
} | |
} | |
ShellCommand::AndCommand(input) => { | |
if let Some(last_status) = self.last_exit_status { | |
if last_status.success() { | |
self.exec(&input) | |
} else { | |
continue; | |
} | |
} else { | |
continue; | |
} | |
} | |
}; | |
match result { | |
Ok(output) => { | |
println!("{}", String::from_utf8_lossy(&output.stdout)); | |
self.last_exit_status = Some(output.status); | |
} | |
Err(x) => { | |
eprintln!("Err! {:?}", x); | |
} | |
} | |
} | |
} | |
} | |
fn get_commands(&self, input: &str) -> io::Result<Vec<ShellCommand>> { | |
let mut commands: Vec<ShellCommand> = Vec::new(); | |
let patterns = [";", "||", "&&"]; | |
let mut matches: Vec<_> = patterns.into_iter().flat_map(|pattern| { | |
input.match_indices(pattern) | |
}).collect(); | |
matches.push((0 as usize, ";")); | |
matches.sort_unstable_by(|a, b| { | |
a.0.cmp(&b.0) | |
}); | |
matches.push((input.len() as usize, ";")); | |
for i in 0..(matches.len() - 1) { | |
let start = matches[i].0; | |
let end = matches[i + 1].0; | |
let subcommand = &input[start..end].replace(matches[i].1, ""); | |
let subcommand = subcommand.trim(); | |
commands.push(match matches[i].1 { | |
";" => ShellCommand::Command(subcommand.to_string()), | |
"||" => ShellCommand::OrCommand(subcommand.to_string()), | |
"&&" => ShellCommand::AndCommand(subcommand.to_string()), | |
_ => panic!("Unrecognized separator") | |
}); | |
}; | |
Ok(commands) | |
} | |
fn read_line(&self) -> io::Result<String> { | |
print!("$ "); | |
io::stdout().flush()?; | |
let mut input = String::new(); | |
io::stdin().read_line(&mut input)?; | |
Ok(input) | |
} | |
fn exec(&self, input: &str) -> io::Result<Output> { | |
let mut pieces = input.split_whitespace(); | |
let cmd = pieces.next().unwrap(); | |
let args: Vec<&str> = pieces.collect(); | |
Command::new(&cmd).args(&args).output() | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn echo_works() { | |
let output = Shell::new().exec("echo hey").unwrap(); | |
assert_eq!(&String::from_utf8_lossy(&output.stdout), "hey\n"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment