summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs161
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))
+ }
}
}