diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 161 |
1 files changed, 154 insertions, 7 deletions
diff --git a/src/main.rs b/src/main.rs index 20ca930..0662c98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,155 @@ -use eyre::Result; +use eyre::{bail, Result}; +use regex::Regex; use rustyline::DefaultEditor; +use std::io; +use std::io::Write; use std::path::PathBuf; +use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; +use std::thread::spawn; +use rustyline::error::ReadlineError; + +const STDOUT_BUFFER_SIZE: usize = 1024; +const STDERR_BUFFER_SIZE: usize = 1024; fn main() -> Result<()> { color_eyre::install()?; - let mut rl = DefaultEditor::new()?; + let mut line_editor = DefaultEditor::new()?; + loop { - let line = rl.readline(">>> ")?; - println!("{line}"); + let line = match line_editor.readline(">>> ") { + Ok(line) => line, + Err(ReadlineError::Eof) => break, + Err(err) => return Err(err.into()) + }; + + let Ok(command) = Command::parse(&line) else { + eprintln!("unrecognized command: {}", line); + continue; + }; + + line_editor.add_history_entry(&line)?; + let Ok(CommandReceivers { stdout, stderr, exit_status }) = command.run() else { + eprintln!("unimplemented command: {}", line); + continue; + }; + + // print stdout, stderr interleaved until exit_status produces something + let stdout_writer = spawn(move || { + loop { + let Ok(bytes) = stdout.recv() else { + break; + }; + let mut out = io::stderr(); + out.write_all(bytes.as_slice()).expect("stdout write failed"); + out.flush().expect("stdout flush failed"); + } + }); + + let stderr_writer = spawn(move || { + loop { + let Ok(bytes) = stderr.recv() else { + break; + }; + let mut out = io::stderr(); + out.write_all(bytes.as_slice()).expect("stderr write failed"); + out.flush().expect("stdout flush failed"); + } + }); + + stdout_writer.join().expect("stdout writer thread failed"); + stderr_writer.join().expect("stderr writer thread failed"); + + let _exit_status = exit_status.recv().expect("failed to receive exit status from command"); } Ok(()) } +#[derive(Debug)] enum FileSpec { Con, Path(PathBuf), } +#[derive(Debug)] enum Command { Pipe { left: Box<Command>, right: Box<Command> }, Redirect { command: Box<Command>, target: FileSpec }, External { program: String, args: Vec<String> }, - Builtin { name: BuiltinCommand }, + Builtin(BuiltinCommand), + Empty, } +struct CommandReceivers { + stdout: Receiver<Vec<u8>>, + stderr: Receiver<Vec<u8>>, + exit_status: Receiver<u16>, +} + +struct CommandSenders { + stdout: SyncSender<Vec<u8>>, + stderr: SyncSender<Vec<u8>>, + exit_status: SyncSender<u16>, +} + +struct CommandContext { + senders: CommandSenders, + receivers: CommandReceivers, +} + +impl CommandContext { + fn new() -> Self { + let (sout, rout) = sync_channel(STDOUT_BUFFER_SIZE); + let (serr, rerr) = sync_channel(STDERR_BUFFER_SIZE); + let (sexit, rexit) = sync_channel(1); + + Self { + senders: CommandSenders { + stdout: sout, + stderr: serr, + exit_status: sexit, + }, + receivers: CommandReceivers { + stdout: rout, + stderr: rerr, + exit_status: rexit, + } + } + } + + fn split(self) -> (CommandSenders, CommandReceivers) { + (self.senders, self.receivers) + } +} + +impl Command { + pub(crate) fn run(&self) -> Result<CommandReceivers> { + use crate::Command::*; + use crate::BuiltinCommand::*; + + let (senders, receivers) = CommandContext::new().split(); + let CommandSenders { stdout, stderr, exit_status } = senders; + + match self { + Empty => { + exit_status.send(0)?; + } + + Builtin(EchoText { message }) => { + stdout.send(message.bytes().collect())?; + stdout.send(b"\n".into())?; + exit_status.send(0)?; + } + + _ => bail!("Command::run not implemented for {:?}", self), + } + + Ok(receivers) + } +} + +#[derive(Debug)] enum BuiltinCommand { // File-oriented Type { file: FileSpec }, @@ -80,7 +204,30 @@ enum BuiltinCommand { } impl Command { - fn parse(_input: &str) -> Result<Command> { - Err(eyre::eyre!("Not implemented")) + fn parse(input: &str) -> Result<Command> { + use Command::*; + use BuiltinCommand::*; + + let whitespace = Regex::new(r"\s+")?; + let mut split_input = whitespace.splitn(input, 2); + let Some(name) = split_input.next() else { + return Ok(Empty); + }; + let args = split_input.next().unwrap_or(""); + + match name.to_uppercase().as_str() { + "" => Ok(Empty), + + "ECHO" => + Ok(Builtin(match args.to_uppercase().as_str() { + "ON" => EchoOn, + "OFF" => EchoOff, + "" => EchoPlain, + _ => EchoText { message: args.to_string() }, + })), + + _ => + Err(eyre::eyre!("parse not implemented for {:?}", input)) + } } } |