aboutsummaryrefslogtreecommitdiffstats
path: root/src
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 /src
parent0eff601814d37012a86e5f02f8a89e928fcfa934 (diff)
downloadnbsh-old-56db51ac99466df9b6fb87cdc54cc859c200065f.tar.gz
nbsh-old-56db51ac99466df9b6fb87cdc54cc859c200065f.zip
provide a framework for shell builtins
Diffstat (limited to 'src')
-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
5 files changed, 180 insertions, 34 deletions
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 })