diff options
author | Jesse Luehrs <doy@tozt.net> | 2019-06-09 17:35:29 -0400 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2019-06-09 17:35:29 -0400 |
commit | 56db51ac99466df9b6fb87cdc54cc859c200065f (patch) | |
tree | cda3cc5b88c729d5193a40862ecf5ac5489c89ff | |
parent | 0eff601814d37012a86e5f02f8a89e928fcfa934 (diff) | |
download | nbsh-old-56db51ac99466df9b6fb87cdc54cc859c200065f.tar.gz nbsh-old-56db51ac99466df9b6fb87cdc54cc859c200065f.zip |
provide a framework for shell builtins
-rw-r--r-- | Cargo.lock | 20 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/builtins.rs | 71 | ||||
-rw-r--r-- | src/eval.rs | 85 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/process.rs | 29 | ||||
-rw-r--r-- | src/repl.rs | 27 |
7 files changed, 201 insertions, 34 deletions
@@ -323,6 +323,7 @@ dependencies = [ "crossterm 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "snafu 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-pty-process 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -339,6 +340,18 @@ dependencies = [ ] [[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -807,6 +820,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -884,6 +902,7 @@ dependencies = [ "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" @@ -932,6 +951,7 @@ dependencies = [ "checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" "checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" @@ -8,6 +8,7 @@ edition = "2018" crossterm = "0.9" futures = "0.1" mio = "0.6" +nix = "0.14" snafu = "0.4" tokio = "0.1" tokio-pty-process = "0.4" diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..f4c8c44 --- /dev/null +++ b/src/builtins.rs @@ -0,0 +1,71 @@ +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("unknown builtin {}", cmd))] + UnknownBuiltin { cmd: String }, + + #[snafu(display("failed to cd to {}: {}", dir, source))] + Chdir { dir: String, source: nix::Error }, + + #[snafu(display("cd requires a directory to be given"))] + ChdirParams, +} + +pub fn exec(cmd: &str, args: &[String]) -> Result<Builtin, Error> { + Builtin::new(cmd, args) +} + +pub struct Builtin { + cmd: String, + args: Vec<String>, + done: bool, +} + +impl Builtin { + fn new(cmd: &str, args: &[String]) -> Result<Self, Error> { + match cmd { + "cd" => Ok(Builtin { + cmd: cmd.to_string(), + args: args.to_vec(), + done: false, + }), + _ => Err(Error::UnknownBuiltin { + cmd: cmd.to_string(), + }), + } + } +} + +#[must_use = "streams do nothing unless polled"] +impl futures::stream::Stream for Builtin { + type Item = crate::eval::CommandEvent; + type Error = Error; + + fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> { + if self.done { + return Ok(futures::Async::Ready(None)); + } + + self.done = true; + let res = match self.cmd.as_ref() { + "cd" => cd(&self.args), + _ => Err(Error::UnknownBuiltin { + cmd: self.cmd.clone(), + }), + }; + res.map(|_| { + futures::Async::Ready(Some( + crate::eval::CommandEvent::BuiltinExit, + )) + }) + } +} + +fn cd(args: &[String]) -> Result<(), Error> { + if let Some(dir) = args.get(0) { + nix::unistd::chdir(dir.as_str()).context(Chdir { dir: dir.clone() }) + } else { + Err(Error::ChdirParams) + } +} diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..a46045a --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,85 @@ +use futures::stream::Stream; +use snafu::{ResultExt, Snafu}; + +#[derive(Debug, Snafu)] +pub enum Error { + #[snafu(display("failed to parse command line '{}': {}", line, source))] + ParserError { + line: String, + source: crate::parser::Error, + }, + + #[snafu(display("failed to find command `{}`: {}", cmd, source))] + CommandError { + cmd: String, + source: crate::process::Error, + }, + + #[snafu(display("failed to run builtin command `{}`: {}", cmd, source))] + BuiltinExecution { + cmd: String, + source: crate::builtins::Error, + }, + + #[snafu(display("failed to run executable `{}`: {}", cmd, source))] + ProcessExecution { + cmd: String, + source: crate::process::Error, + }, +} + +pub fn eval(line: &str) -> Result<Eval, Error> { + Eval::new(line) +} + +pub enum CommandEvent { + Output(Vec<u8>), + ProcessExit(std::process::ExitStatus), + BuiltinExit, +} + +pub struct Eval { + stream: Box< + dyn futures::stream::Stream<Item = CommandEvent, Error = Error> + + Send, + >, +} + +impl Eval { + fn new(line: &str) -> Result<Self, Error> { + let (cmd, args) = + crate::parser::parse(line).context(ParserError { line })?; + let builtin_stream = crate::builtins::exec(&cmd, &args); + let stream: Box< + dyn futures::stream::Stream<Item = CommandEvent, Error = Error> + + Send, + > = if let Ok(s) = builtin_stream { + Box::new(s.map_err(move |e| Error::BuiltinExecution { + cmd: cmd.clone(), + source: e, + })) + } else { + let process_stream = crate::process::spawn(&cmd, &args); + match process_stream { + Ok(s) => { + Box::new(s.map_err(move |e| Error::ProcessExecution { + cmd: cmd.clone(), + source: e, + })) + } + Err(e) => return Err(e).context(CommandError { cmd }), + } + }; + Ok(Eval { stream }) + } +} + +#[must_use = "streams do nothing unless polled"] +impl futures::stream::Stream for Eval { + type Item = CommandEvent; + type Error = Error; + + fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> { + self.stream.poll() + } +} diff --git a/src/main.rs b/src/main.rs index d283021..3656616 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +mod builtins; +mod eval; mod parser; mod process; mod readline; diff --git a/src/process.rs b/src/process.rs index 9d6582c..d059803 100644 --- a/src/process.rs +++ b/src/process.rs @@ -12,12 +12,6 @@ pub enum Error { #[snafu(display("failed to spawn process for `{}`: {}", cmd, source))] SpawnProcess { cmd: String, source: std::io::Error }, - #[snafu(display("failed to parse command line '{}': {}", line, source))] - ParserError { - line: String, - source: crate::parser::Error, - }, - #[snafu(display("failed to write to pty: {}", source))] WriteToPty { source: std::io::Error }, @@ -40,13 +34,8 @@ pub enum Error { IntoRawMode { source: std::io::Error }, } -pub fn spawn(line: &str) -> Result<RunningProcess, Error> { - RunningProcess::new(line) -} - -pub enum ProcessEvent { - Output(Vec<u8>), - Exit(std::process::ExitStatus), +pub fn spawn(cmd: &str, args: &[String]) -> Result<RunningProcess, Error> { + RunningProcess::new(cmd, args) } pub struct RunningProcess { @@ -62,14 +51,12 @@ pub struct RunningProcess { } impl RunningProcess { - fn new(line: &str) -> Result<Self, Error> { + fn new(cmd: &str, args: &[String]) -> Result<Self, Error> { let pty = tokio_pty_process::AsyncPtyMaster::open().context(OpenPty)?; - let (cmd, args) = - crate::parser::parse(line).context(ParserError { line })?; - let process = std::process::Command::new(cmd.clone()) - .args(&args) + let process = std::process::Command::new(cmd) + .args(args) .spawn_pty_async(&pty) .context(SpawnProcess { cmd })?; @@ -92,7 +79,7 @@ impl RunningProcess { #[must_use = "streams do nothing unless polled"] impl futures::stream::Stream for RunningProcess { - type Item = ProcessEvent; + type Item = crate::eval::CommandEvent; type Error = Error; fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> { @@ -141,7 +128,7 @@ impl futures::stream::Stream for RunningProcess { acc }); return Ok(futures::Async::Ready(Some( - ProcessEvent::Output(bytes), + crate::eval::CommandEvent::Output(bytes), ))); } Ok(futures::Async::NotReady) => { @@ -162,7 +149,7 @@ impl futures::stream::Stream for RunningProcess { Ok(futures::Async::Ready(status)) => { self.exit_done = true; return Ok(futures::Async::Ready(Some( - ProcessEvent::Exit(status), + crate::eval::CommandEvent::ProcessExit(status), ))); } Ok(futures::Async::NotReady) => { diff --git a/src/repl.rs b/src/repl.rs index af87a0f..53e897c 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -9,7 +9,7 @@ enum Error { ReadError { source: crate::readline::Error }, #[snafu(display("error during eval: {}", source))] - EvalError { source: crate::process::Error }, + EvalError { source: crate::eval::Error }, #[snafu(display("error during print: {}", source))] PrintError { source: std::io::Error }, @@ -24,25 +24,26 @@ pub fn repl() { let repl = read().and_then(|line| { eprint!("running '{}'\r\n", line); eval(&line).fold(None, |acc, event| match event { - crate::process::ProcessEvent::Output(out) => { - match print(&out) { - Ok(()) => futures::future::ok(acc), - Err(e) => futures::future::err(e), - } + crate::eval::CommandEvent::Output(out) => match print(&out) { + Ok(()) => futures::future::ok(acc), + Err(e) => futures::future::err(e), + }, + crate::eval::CommandEvent::ProcessExit(status) => { + futures::future::ok(Some(format!("{}", status))) } - crate::process::ProcessEvent::Exit(status) => { - futures::future::ok(Some(status)) + crate::eval::CommandEvent::BuiltinExit => { + futures::future::ok(Some(format!("success"))) } }) }); Some(repl.then(move |res| match res { Ok(Some(status)) => { - eprint!("process exited with status {}\r\n", status); + eprint!("command exited: {}\r\n", status); return Ok((done, false)); } Ok(None) => { - eprint!("process exited weirdly?\r\n"); + eprint!("command exited weirdly?\r\n"); return Ok((done, false)); } Err(Error::ReadError { @@ -52,7 +53,7 @@ pub fn repl() { } Err(Error::EvalError { source: - crate::process::Error::ParserError { + crate::eval::Error::ParserError { source: crate::parser::Error::CommandRequired, line: _, }, @@ -81,9 +82,9 @@ fn read() -> impl futures::future::Future<Item = String, Error = Error> { fn eval( line: &str, -) -> impl futures::stream::Stream<Item = crate::process::ProcessEvent, Error = Error> +) -> impl futures::stream::Stream<Item = crate::eval::CommandEvent, Error = Error> { - crate::process::spawn(line) + crate::eval::eval(line) .into_future() .flatten_stream() .map_err(|e| Error::EvalError { source: e }) |