aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2019-06-09 17:35:29 -0400
committerJesse Luehrs <doy@tozt.net>2019-06-09 17:35:29 -0400
commit56db51ac99466df9b6fb87cdc54cc859c200065f (patch)
treecda3cc5b88c729d5193a40862ecf5ac5489c89ff
parent0eff601814d37012a86e5f02f8a89e928fcfa934 (diff)
downloadnbsh-old-56db51ac99466df9b6fb87cdc54cc859c200065f.tar.gz
nbsh-old-56db51ac99466df9b6fb87cdc54cc859c200065f.zip
provide a framework for shell builtins
-rw-r--r--Cargo.lock20
-rw-r--r--Cargo.toml1
-rw-r--r--src/builtins.rs71
-rw-r--r--src/eval.rs85
-rw-r--r--src/main.rs2
-rw-r--r--src/process.rs29
-rw-r--r--src/repl.rs27
7 files changed, 201 insertions, 34 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f14a3f8..a3c245f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 1dc7a14..7b14779 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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 })