summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-02-20 02:49:20 -0700
committerRory Dudley2024-02-20 02:49:20 -0700
commit5d7e3646b7a267bdcf068d5667201033b3aa9207 (patch)
treeec2b10266e723ccea4a35760237344970b6160c3
parentde2fe9dd7f2836f89490b822ae4464dab3bc769c (diff)
downloaddwarvish-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.rs497
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);
}