diff options
author | Rory Dudley | 2024-02-20 02:49:20 -0700 |
---|---|---|
committer | Rory Dudley | 2024-02-20 02:49:20 -0700 |
commit | 5d7e3646b7a267bdcf068d5667201033b3aa9207 (patch) | |
tree | ec2b10266e723ccea4a35760237344970b6160c3 | |
parent | de2fe9dd7f2836f89490b822ae4464dab3bc769c (diff) | |
download | dwarvish-5d7e3646b7a267bdcf068d5667201033b3aa9207.tar.gz |
Pipes, forks, and consecutive calls
This adds some preliminary support for pipes (|), forks (&), and
consecutive command calls (&&) to the shell.
Notes
Notes:
This branch is a huge WIP, and am only pushing it, cause it's late, and
want to have my changes saved. A lot of cleanup and comments will be
necessary moving forward.
-rw-r--r-- | src/main.rs | 497 |
1 files changed, 407 insertions, 90 deletions
diff --git a/src/main.rs b/src/main.rs index 014b882..09fc70a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,314 @@ use ctrlc; use std::fs; use std::io::{self, Write}; use std::path::Path; -use std::process::Command; +use std::process::{Command, Stdio}; + +// #[derive(Debug)] +// enum Meter { +// Verb, // A command (and possible some arguments) +// Pipe, // Pipe the output of this command into the next +// Daemon, // Fork the command into the background +// And, // Run the next command only if this succeeds +// } + +// impl fmt::Display for Meter { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// let meter = match self { +// Meter::Verb => "verb", +// Meter::Pipe => "pipe", +// Meter::Daemon => "daemon", +// Meter::And => "and", +// }; +// write!(f, "{}", meter) +// } +// } + +// #[derive(Debug)] +// struct Stanza { +// verb: Option<String>, +// clause: Option<Vec<String>>, +// } + +// impl Stanza { +// fn new(line: String) -> Stanza { +// let mut split = line.split(' '); +// let cmd = match split.next() { +// Some(verb) if verb.trim().is_empty() => None, +// Some(verb) => Some(String::from(verb.trim())), +// None => None, +// }; + +// let mut args: Option<Vec<String>>; +// match cmd { +// Some(_) => { +// args = Some(Vec::new()); +// loop { +// let next = split.next(); +// match next { +// Some(clause) => args.unwrap().push(clause.to_string()), +// None => break, +// } +// } +// } +// None => args = None, +// } + +// Stanza { +// verb: cmd, +// clause: args, +// } +// } +// } + +// #[derive(Debug)] +// struct Verse { +// meter: Meter, +// stanza: Option<Stanza>, +// } + +// #[derive(Debug)] +// struct Poem { +// verses: Vec<Verse>, +// } + +// impl Poem { +// fn new() -> Poem { +// Poem { verses: Vec::new() } +// } + +// fn recite(&self) { +// unimplemented!(); +// } +// } + +#[derive(Debug)] +enum Meter { + None, // No meter + Pipe, // Pipe the output of this command into the next + Daemon, // Fork the command into the background + And, // Run the next command only if this succeeds +} + +#[derive(Debug)] +struct Stanza { + verb: String, + clause: Vec<String>, +} + +impl Stanza { + fn new(stanza: Vec<String>) -> Stanza { + Stanza { + verb: stanza[0].clone(), + clause: stanza[1..].to_vec(), + } + } + + fn spellcheck(&self, bins: &Vec<String>) -> bool { + if self.verb.is_empty() { + return false; + } + + if !Path::new(self.verb.as_str()).exists() { + match bins + .iter() + .find(|bin| bin.split('/').last().unwrap() == self.verb) + { + Some(_) => return true, + None => return false, + } + } + + true + } +} + +#[derive(Debug)] +struct Verse { + stanza: Stanza, + meter: Meter, + stdin: bool, +} + +impl Verse { + fn new(stanza: Stanza, meter: Meter, stdin: bool) -> Verse { + Verse { + stanza, + meter, + stdin, + } + } + + fn spellcheck(&self, bins: &Vec<String>) -> bool { + self.stanza.spellcheck(bins) + } + + fn verb(&self) -> String { + self.stanza.verb.clone() + } + + fn clause(&self) -> Vec<String> { + self.stanza.clause.clone() + } +} + +#[derive(Debug)] +struct Poem { + verses: Vec<Verse>, +} + +impl Poem { + fn new(verses: Vec<Verse>) -> Poem { + Poem { verses } + } + + fn recite(&self, paths: &Vec<&Path>, bins: &mut Vec<String>) -> bool { + // println!("{:#?}", self); + let mut out: String = String::new(); + + for verse in self.verses.iter() { + // let verse = match self.verses.iter().next() { + // Some(verse) => verse, + // None => break, + // }; + + if !verse.spellcheck(bins) { + *bins = prefresh(paths); + if !verse.spellcheck(bins) { + println!("dwvsh: {}: command not found", verse.verb()); + continue; + } + } + + if verse.stdin { + match verse.meter { + Meter::Pipe => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("dwvsh: error 0"); + + let mut stdin = child.stdin.as_mut().expect("dwvsh: error 6"); + stdin.write_all(&out.as_bytes()).expect("dwvsh: error 7"); + out.clear(); + + let output = child.wait_with_output().unwrap(); + out = String::from_utf8_lossy(&output.stdout).to_string(); + } + Meter::Daemon => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .stdin(Stdio::piped()) + .spawn() + .expect("dwvsh: error 1"); + + let mut stdin = child.stdin.as_mut().expect("dwvsh: error 8"); + stdin.write_all(&out.as_bytes()).expect("dwvsh: error 9"); + out.clear(); + + child.wait().unwrap(); + } + Meter::And | Meter::None => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .stdin(Stdio::piped()) + .spawn() + .expect("dwvsh: error 2"); + + let mut stdin = child.stdin.as_mut().expect("dwvsh: error 10"); + stdin.write_all(&out.as_bytes()).expect("dwvsh: error 11"); + out.clear(); + + child.wait().unwrap(); + } + }; + } else { + match verse.meter { + Meter::Pipe => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .stdout(Stdio::piped()) + .spawn() + .expect("dwvsh: error 3"); + + let output = child.wait_with_output().unwrap(); + out = String::from_utf8_lossy(&output.stdout).to_string(); + } + Meter::Daemon => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .spawn() + .expect("dwvsh: error 4"); + + child.wait().unwrap(); + } + Meter::And | Meter::None => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .spawn() + .expect("dwvsh: error 5"); + + child.wait().unwrap(); + } + }; + } + } + + true + } +} + +fn read(poetry: String) -> Poem { + let mut words = poetry.split(' '); + let mut verses: Vec<Verse> = Vec::new(); + let mut stanza: Vec<String> = Vec::new(); + + let mut prev: Option<&Verse> = None; + loop { + let word = words.next(); + let stdin = match prev { + Some(prev) => match prev.meter { + Meter::Pipe => true, + Meter::Daemon | Meter::And | Meter::None => false, + }, + None => false, + }; + + match word { + Some(verb) if verb == "|" => { + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Pipe, stdin)); + stanza = Vec::new(); + } + Some(verb) if verb == "&" => { + verses.push(Verse::new( + Stanza::new(stanza.clone()), + Meter::Daemon, + stdin, + )); + stanza = Vec::new(); + } + Some(verb) if verb == "&&" => { + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::And, stdin)); + stanza = Vec::new(); + } + Some(verb) => { + stanza.push(verb.trim().to_string()); + } + None => { + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::None, stdin)); + break; + } + } + + prev = match verses.last() { + Some(verse) => Some(verse), + None => None, + }; + } + + Poem::new(verses) +} /// Refresh the shell's $PATH /// @@ -72,9 +379,9 @@ fn repl(paths: &Vec<&Path>, prompt: &str) { io::stdout().flush().unwrap(); // Wait for user input - let mut input = String::new(); + let mut poetry = String::new(); let bytes = io::stdin() - .read_line(&mut input) + .read_line(&mut poetry) .expect("dwvsh: error: unable to evaluate the input string"); // Check if we've reached EOF (i.e. <C-d>) @@ -84,108 +391,112 @@ fn repl(paths: &Vec<&Path>, prompt: &str) { } // Trim the input - let input = input.trim(); + let poetry = String::from(poetry.trim()); // Check if user wants to exit the shell - if input == "exit" || input == "quit" { + if poetry == "exit" || poetry == "quit" { break; } - // Parse command - let mut split = input.split(' '); - let mut cmd = match split.next() { - Some(str) if str.trim().is_empty() => continue, - Some(str) => String::from(str.trim()), - None => continue, - }; + // Parse a poem + let poem = read(poetry); + poem.recite(paths, &mut bins); - // Parse arguments - let mut args = vec![]; - loop { - let next = split.next(); - match next { - Some(str) => args.push(str), - None => break, - } - } + // // Parse command + // let mut split = poetry.split(' '); + // let mut cmd = match split.next() { + // Some(str) if str.trim().is_empty() => continue, + // Some(str) => String::from(str.trim()), + // None => continue, + // }; - // Check if user wants to change directories - if cmd == "cd" { - let path = match args.first() { - Some(str) => str, - None => env!("HOME"), - }; - - match std::env::set_current_dir(path) { - Ok(_) => continue, - Err(_) => { - println!("cd: unable to change into {}", path); - continue; - } - } - } + // // Parse arguments + // let mut args = vec![]; + // loop { + // let next = split.next(); + // match next { + // Some(str) => args.push(str), + // None => break, + // } + // } + + // // Check if user wants to change directories + // if cmd == "cd" { + // let path = match args.first() { + // Some(str) => str, + // None => env!("HOME"), + // }; + + // match std::env::set_current_dir(path) { + // Ok(_) => continue, + // Err(_) => { + // println!("cd: unable to change into {}", path); + // continue; + // } + // } + // } // Check if the file exists, if given a pull or relative path // TODO: Check if file at the path is executable (i.e. +x) - if !Path::new(cmd.as_str()).exists() { - let b = bins.clone(); - // Check if the command exists in $PATH if a full or relative path - // was not given, or if the path does not exist - // - // If the command is not found the first time, try refreshing the - // path first, and only print an error if if it's not found after - // the path refresh - cmd = match b - .clone() - .iter() - .find(|b| b.split("/").last().unwrap() == cmd) - { - Some(cmd) => cmd.clone(), - None => { - bins = prefresh(&paths); - match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { - Some(cmd) => cmd.clone(), - None => { - println!("dwvsh: {}: command not found", cmd); - continue; - } - } - } - } - } + // if !Path::new(cmd.as_str()).exists() { + // let b = bins.clone(); + // // Check if the command exists in $PATH if a full or relative path + // // was not given, or if the path does not exist + // // + // // If the command is not found the first time, try refreshing the + // // path first, and only print an error if if it's not found after + // // the path refresh + // cmd = match b + // .clone() + // .iter() + // .find(|b| b.split("/").last().unwrap() == cmd) + // { + // Some(cmd) => cmd.clone(), + // None => { + // bins = prefresh(&paths); + // match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { + // Some(cmd) => cmd.clone(), + // None => { + // println!("dwvsh: {}: command not found", cmd); + // continue; + // } + // } + // } + // } + // } // Run the command (and wait for it to finish) - let mut child = match Command::new(&cmd).args(args).spawn() { - Ok(ch) => ch, - Err(err) => { - match err.kind() { - // Occurs when the user doesn't have read access, or if the +x bit is not set - std::io::ErrorKind::PermissionDenied => { - println!( - "dwvsh: permission denied: trying to fork to {}", - cmd.split("/").last().unwrap() - ) - } + // let mut child = match Command::new(&cmd).args(args).spawn() { + // Ok(ch) => ch, + // Err(err) => { + // match err.kind() { + // // Occurs when the user doesn't have read access, or if the +x bit is not set + // std::io::ErrorKind::PermissionDenied => { + // println!( + // "dwvsh: permission denied: trying to fork to {}", + // cmd.split("/").last().unwrap() + // ) + // } - // Occurs if a command was removed from the path - // If this is the case, refresh the path - std::io::ErrorKind::NotFound => { - bins = prefresh(&paths); - println!( - "dwvsh: {}: command not found", - cmd.split("/").last().unwrap() - ); - } + // // Occurs if a command was removed from the path + // // If this is the case, refresh the path + // std::io::ErrorKind::NotFound => { + // bins = prefresh(&paths); + // println!( + // "dwvsh: {}: command not found", + // cmd.split("/").last().unwrap() + // ); + // } - // Otherwise print the OS error - _ => println!("dwvsh: fork: {}", err), - } + // // Otherwise print the OS error + // _ => println!("dwvsh: fork: {}", err), + // } - // Restart the loop - continue; - } - }; - child.wait().unwrap(); + // // Restart the loop + // continue; + // } + // }; + // child.wait().unwrap(); } } @@ -215,6 +526,12 @@ fn main() { }) .expect("dwvsh: signals: unable to set sigint handler"); + // let poem = read("eza -la".to_string()); + // for line in poem.verses.iter().zip(poem.meters) { + // let (verse, meter) = line; + // println!("{}: {}", meter, verse); + // } + // Begin evaluating commands repl(&paths, prompt); } |