Skip to content

Instantly share code, notes, and snippets.

@jacobmischka
Last active October 19, 2018 19:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jacobmischka/cc11991b4be566923c54ba7a45be5b1b to your computer and use it in GitHub Desktop.
Save jacobmischka/cc11991b4be566923c54ba7a45be5b1b to your computer and use it in GitHub Desktop.
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