From ecd958bca79914f387b9766e5f35edc544f0a66c Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Wed, 10 Jul 2019 22:49:36 -0400 Subject: don't need multiple files here --- src/lib.rs | 1 - src/state.rs | 231 ---------------------------------------------------------- src/tui.rs | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 228 insertions(+), 237 deletions(-) delete mode 100644 src/state.rs diff --git a/src/lib.rs b/src/lib.rs index df7b61e..4f3e3df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,6 @@ mod eval; mod parser; mod process; mod readline; -mod state; pub mod repl; pub mod tui; diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index 197ff0e..0000000 --- a/src/state.rs +++ /dev/null @@ -1,231 +0,0 @@ -use futures::future::Future as _; -use futures::stream::Stream as _; -use snafu::{OptionExt as _, ResultExt as _}; -use std::io::Write as _; - -#[derive(Debug, snafu::Snafu)] -pub enum Error { - #[snafu(display("invalid command index: {}", idx))] - InvalidCommandIndex { idx: usize }, - - #[snafu(display("error during read: {}", source))] - Read { source: crate::readline::Error }, - - #[snafu(display("error during eval: {}", source))] - Eval { source: crate::eval::Error }, - - #[snafu(display("error during print: {}", source))] - Print { source: std::io::Error }, - - #[snafu(display("eof"))] - EOF, -} - -pub type Result = std::result::Result; - -pub struct State { - idx: usize, - readline: Option, - commands: std::collections::HashMap, -} - -impl State { - pub fn new() -> Result { - Ok(Self { - idx: 0, - readline: Some(Self::read()?), - commands: std::collections::HashMap::new(), - }) - } - - fn read() -> Result { - crate::readline::readline("$ ", true).context(Read) - } - - fn eval( - &mut self, - idx: usize, - line: &str, - ) -> std::result::Result<(), Error> { - if self.commands.contains_key(&idx) { - return Err(Error::InvalidCommandIndex { idx }); - } - let eval = crate::eval::eval(line).context(Eval); - match eval { - Ok(eval) => { - self.commands.insert(idx, Command::new(eval)); - } - Err(e) => return Err(e), - } - Ok(()) - } - - fn command_start( - &mut self, - idx: usize, - cmd: &str, - args: &[String], - ) -> Result<()> { - let command = self - .commands - .get_mut(&idx) - .context(InvalidCommandIndex { idx })?; - let cmd = cmd.to_string(); - let args = args.to_vec(); - eprint!("running '{} {:?}'\r\n", cmd, args); - command.cmd = Some(cmd); - command.args = Some(args); - Ok(()) - } - - fn command_output(&mut self, idx: usize, output: &[u8]) -> Result<()> { - let command = self - .commands - .get_mut(&idx) - .context(InvalidCommandIndex { idx })?; - command.output.append(&mut output.to_vec()); - - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - stdout.write(output).context(Print)?; - stdout.flush().context(Print)?; - - Ok(()) - } - - fn command_exit( - &mut self, - idx: usize, - status: std::process::ExitStatus, - ) -> Result<()> { - let command = self - .commands - .get_mut(&idx) - .context(InvalidCommandIndex { idx })?; - command.status = Some(status); - eprint!("command exited: {}\r\n", status); - Ok(()) - } - - fn poll_with_errors(&mut self) -> futures::Poll<(), Error> { - loop { - let mut did_work = false; - - if self.readline.is_none() && self.commands.is_empty() { - self.idx += 1; - self.readline = Some(Self::read()?) - } - - if let Some(mut r) = self.readline.take() { - match r.poll() { - Ok(futures::Async::Ready(line)) => { - // overlapping RawScreen lifespans don't work properly - // - if readline creates a RawScreen, then eval - // creates a separate one, then readline drops it, the - // screen will go back to cooked even though a - // RawScreen instance is still live. - drop(r); - - match self.eval(self.idx, &line) { - Ok(()) - | Err(Error::Eval { - source: - crate::eval::Error::Parser { - source: - crate::parser::Error::CommandRequired, - .. - }, - }) => {} - Err(e) => return Err(e), - } - did_work = true; - } - Ok(futures::Async::NotReady) => { - self.readline.replace(r); - } - Err(crate::readline::Error::EOF) => { - return Err(Error::EOF) - } - Err(e) => return Err(e).context(Read), - } - } - - for idx in self.commands.keys().cloned().collect::>() { - match self - .commands - .get_mut(&idx) - .unwrap() - .future - .poll() - .context(Eval)? - { - futures::Async::Ready(Some(event)) => match event { - crate::eval::CommandEvent::CommandStart( - cmd, - args, - ) => { - self.command_start(idx, &cmd, &args)?; - did_work = true; - } - crate::eval::CommandEvent::Output(out) => { - self.command_output(idx, &out)?; - did_work = true; - } - crate::eval::CommandEvent::CommandExit(status) => { - self.command_exit(idx, status)?; - did_work = true; - } - }, - futures::Async::Ready(None) => { - self.commands - .remove(&idx) - .context(InvalidCommandIndex { idx })?; - did_work = true; - } - futures::Async::NotReady => {} - } - } - - if !did_work { - return Ok(futures::Async::NotReady); - } - } - } -} - -impl futures::future::Future for State { - type Item = (); - type Error = (); - - fn poll(&mut self) -> futures::Poll { - loop { - match self.poll_with_errors() { - Ok(a) => return Ok(a), - Err(Error::EOF) => return Ok(futures::Async::Ready(())), - Err(e) => { - eprint!("error polling state: {}\r\n", e); - } - } - } - } -} - -struct Command { - future: crate::eval::Eval, - cmd: Option, - args: Option>, - output: Vec, - status: Option, -} - -impl Command { - fn new(future: crate::eval::Eval) -> Self { - Self { - future, - cmd: None, - args: None, - output: vec![], - status: None, - } - } -} diff --git a/src/tui.rs b/src/tui.rs index 296f1bf..6a19a86 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,15 +1,238 @@ -use snafu::ResultExt as _; +use futures::future::Future as _; +use futures::stream::Stream as _; +use snafu::{OptionExt as _, ResultExt as _}; +use std::io::Write as _; #[derive(Debug, snafu::Snafu)] pub enum Error { - #[snafu(display("error from state: {}", source))] - State { source: crate::state::Error }, + #[snafu(display("invalid command index: {}", idx))] + InvalidCommandIndex { idx: usize }, + + #[snafu(display("error during read: {}", source))] + Read { source: crate::readline::Error }, + + #[snafu(display("error during eval: {}", source))] + Eval { source: crate::eval::Error }, + + #[snafu(display("error during print: {}", source))] + Print { source: std::io::Error }, + + #[snafu(display("eof"))] + EOF, } +pub type Result = std::result::Result; + pub fn tui() { - let state = crate::state::State::new().context(State); - match state { + match Tui::new() { Ok(state) => tokio::run(state), Err(e) => eprintln!("failed to create state: {}", e), } } + +pub struct Tui { + idx: usize, + readline: Option, + commands: std::collections::HashMap, +} + +impl Tui { + pub fn new() -> Result { + Ok(Self { + idx: 0, + readline: Some(Self::read()?), + commands: std::collections::HashMap::new(), + }) + } + + fn read() -> Result { + crate::readline::readline("$ ", true).context(Read) + } + + fn eval( + &mut self, + idx: usize, + line: &str, + ) -> std::result::Result<(), Error> { + if self.commands.contains_key(&idx) { + return Err(Error::InvalidCommandIndex { idx }); + } + let eval = crate::eval::eval(line).context(Eval); + match eval { + Ok(eval) => { + self.commands.insert(idx, Command::new(eval)); + } + Err(e) => return Err(e), + } + Ok(()) + } + + fn command_start( + &mut self, + idx: usize, + cmd: &str, + args: &[String], + ) -> Result<()> { + let command = self + .commands + .get_mut(&idx) + .context(InvalidCommandIndex { idx })?; + let cmd = cmd.to_string(); + let args = args.to_vec(); + eprint!("running '{} {:?}'\r\n", cmd, args); + command.cmd = Some(cmd); + command.args = Some(args); + Ok(()) + } + + fn command_output(&mut self, idx: usize, output: &[u8]) -> Result<()> { + let command = self + .commands + .get_mut(&idx) + .context(InvalidCommandIndex { idx })?; + command.output.append(&mut output.to_vec()); + + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + stdout.write(output).context(Print)?; + stdout.flush().context(Print)?; + + Ok(()) + } + + fn command_exit( + &mut self, + idx: usize, + status: std::process::ExitStatus, + ) -> Result<()> { + let command = self + .commands + .get_mut(&idx) + .context(InvalidCommandIndex { idx })?; + command.status = Some(status); + eprint!("command exited: {}\r\n", status); + Ok(()) + } + + fn poll_with_errors(&mut self) -> futures::Poll<(), Error> { + loop { + let mut did_work = false; + + if self.readline.is_none() && self.commands.is_empty() { + self.idx += 1; + self.readline = Some(Self::read()?) + } + + if let Some(mut r) = self.readline.take() { + match r.poll() { + Ok(futures::Async::Ready(line)) => { + // overlapping RawScreen lifespans don't work properly + // - if readline creates a RawScreen, then eval + // creates a separate one, then readline drops it, the + // screen will go back to cooked even though a + // RawScreen instance is still live. + drop(r); + + match self.eval(self.idx, &line) { + Ok(()) + | Err(Error::Eval { + source: + crate::eval::Error::Parser { + source: + crate::parser::Error::CommandRequired, + .. + }, + }) => {} + Err(e) => return Err(e), + } + did_work = true; + } + Ok(futures::Async::NotReady) => { + self.readline.replace(r); + } + Err(crate::readline::Error::EOF) => { + return Err(Error::EOF) + } + Err(e) => return Err(e).context(Read), + } + } + + for idx in self.commands.keys().cloned().collect::>() { + match self + .commands + .get_mut(&idx) + .unwrap() + .future + .poll() + .context(Eval)? + { + futures::Async::Ready(Some(event)) => match event { + crate::eval::CommandEvent::CommandStart( + cmd, + args, + ) => { + self.command_start(idx, &cmd, &args)?; + did_work = true; + } + crate::eval::CommandEvent::Output(out) => { + self.command_output(idx, &out)?; + did_work = true; + } + crate::eval::CommandEvent::CommandExit(status) => { + self.command_exit(idx, status)?; + did_work = true; + } + }, + futures::Async::Ready(None) => { + self.commands + .remove(&idx) + .context(InvalidCommandIndex { idx })?; + did_work = true; + } + futures::Async::NotReady => {} + } + } + + if !did_work { + return Ok(futures::Async::NotReady); + } + } + } +} + +impl futures::future::Future for Tui { + type Item = (); + type Error = (); + + fn poll(&mut self) -> futures::Poll { + loop { + match self.poll_with_errors() { + Ok(a) => return Ok(a), + Err(Error::EOF) => return Ok(futures::Async::Ready(())), + Err(e) => { + eprint!("error polling state: {}\r\n", e); + } + } + } + } +} + +struct Command { + future: crate::eval::Eval, + cmd: Option, + args: Option>, + output: Vec, + status: Option, +} + +impl Command { + fn new(future: crate::eval::Eval) -> Self { + Self { + future, + cmd: None, + args: None, + output: vec![], + status: None, + } + } +} -- cgit v1.2.3-54-g00ecf