diff options
author | Jesse Luehrs <doy@tozt.net> | 2022-01-02 23:10:26 -0500 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2022-01-02 23:10:26 -0500 |
commit | 0115a566afa763c9732e03765d50a2ace4008d18 (patch) | |
tree | 9a2a01940b0e40da502c99f60859b56f8e8177ac /src/builtins.rs | |
parent | 1b146445943d5aaeea3261a40bb5962a422b7c48 (diff) | |
download | nbsh-0115a566afa763c9732e03765d50a2ace4008d18.tar.gz nbsh-0115a566afa763c9732e03765d50a2ace4008d18.zip |
more refactoring to lay the groundwork for reintroducing builtins
Diffstat (limited to 'src/builtins.rs')
-rw-r--r-- | src/builtins.rs | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..e55f6ec --- /dev/null +++ b/src/builtins.rs @@ -0,0 +1,216 @@ +use async_std::io::WriteExt as _; +use std::os::unix::process::ExitStatusExt as _; + +type BuiltinFunc = &'static (dyn for<'a> Fn( + &'a crate::parse::Exe, + &'a crate::command::Env, +) -> std::pin::Pin< + Box< + dyn std::future::Future<Output = std::process::ExitStatus> + + Sync + + Send + + 'a, + >, +> + Sync + + Send); + +static BUILTINS: once_cell::sync::Lazy< + std::collections::HashMap<&'static str, BuiltinFunc>, +> = once_cell::sync::Lazy::new(|| { + // all this does is convince the type system to do the right thing, i + // don't think there's any way to just do it directly through annotations + // or casts or whatever + fn coerce_builtin<F>(f: &'static F) -> BuiltinFunc + where + F: for<'a> Fn( + &'a crate::parse::Exe, + &'a crate::command::Env, + ) -> std::pin::Pin< + Box< + dyn std::future::Future<Output = std::process::ExitStatus> + + Sync + + Send + + 'a, + >, + > + Sync + + Send + + 'static, + { + f + } + + let mut builtins = std::collections::HashMap::new(); + builtins.insert("cd", coerce_builtin(&|exe, env| Box::pin(cd(exe, env)))); + builtins + .insert("and", coerce_builtin(&|exe, env| Box::pin(and(exe, env)))); + builtins.insert("or", coerce_builtin(&|exe, env| Box::pin(or(exe, env)))); + builtins.insert( + "command", + coerce_builtin(&|exe, env| Box::pin(command(exe, env))), + ); + builtins.insert( + "builtin", + coerce_builtin(&|exe, env| Box::pin(builtin(exe, env))), + ); + builtins +}); + +pub struct Builtin { + f: BuiltinFunc, + stdin: Box<dyn async_std::io::Read>, + stdout: Box<dyn async_std::io::Write>, + stderr: Box<dyn async_std::io::Write>, +} + +impl Builtin { + pub fn new(exe: &crate::parse::Exe) -> Option<Self> { + if let Some(f) = BUILTINS.get(exe.exe()) { + Some(Self { + f, + stdin: Box::new(async_std::io::stdin()), + stdout: Box::new(async_std::io::stdout()), + stderr: Box::new(async_std::io::stderr()), + }) + } else { + None + } + } + + pub fn stdin(&mut self, fh: std::fs::File) { + self.stdin = Box::new(async_std::fs::File::from(fh)); + } + + pub fn stdout(&mut self, fh: std::fs::File) { + self.stdout = Box::new(async_std::fs::File::from(fh)); + } + + pub fn stderr(&mut self, fh: std::fs::File) { + self.stderr = Box::new(async_std::fs::File::from(fh)); + } +} + +pub fn run( + exe: &crate::parse::Exe, + env: &crate::command::Env, +) -> Option< + std::pin::Pin< + Box< + dyn std::future::Future<Output = async_std::process::ExitStatus> + + Send + + Sync, + >, + >, +> { + // the closure form doesn't work without explicit type annotations + #[allow(clippy::option_if_let_else)] + if let Some(f) = BUILTINS.get(exe.exe()) { + let exe = exe.clone(); + let env = env.clone(); + Some(Box::pin(async move { f(&exe, &env).await })) + } else { + None + } +} + +async fn cd( + exe: &crate::parse::Exe, + env: &crate::command::Env, +) -> async_std::process::ExitStatus { + let dir = exe + .args() + .into_iter() + .map(std::convert::AsRef::as_ref) + .next() + .unwrap_or(""); + + let dir = if dir.is_empty() { + home() + } else if dir.starts_with('~') { + let path: std::path::PathBuf = dir.into(); + if let std::path::Component::Normal(prefix) = + path.components().next().unwrap() + { + if prefix.to_str() == Some("~") { + home().join(path.strip_prefix(prefix).unwrap()) + } else { + // TODO + async_std::io::stderr() + .write(b"unimplemented\n") + .await + .unwrap(); + return async_std::process::ExitStatus::from_raw(1 << 8); + } + } else { + unreachable!() + } + } else { + dir.into() + }; + let code = match std::env::set_current_dir(&dir) { + Ok(()) => 0, + Err(e) => { + async_std::io::stderr() + .write( + format!( + "{}: {}: {}\n", + exe.exe(), + crate::format::io_error(&e), + dir.display() + ) + .as_bytes(), + ) + .await + .unwrap(); + 1 + } + }; + async_std::process::ExitStatus::from_raw(code << 8) +} + +async fn and( + exe: &crate::parse::Exe, + env: &crate::command::Env, +) -> async_std::process::ExitStatus { + let exe = exe.shift(); + if env.latest_status().success() { + todo!() + // super::run_exe(&exe, env).await + } else { + *env.latest_status() + } +} + +async fn or( + exe: &crate::parse::Exe, + env: &crate::command::Env, +) -> async_std::process::ExitStatus { + let exe = exe.shift(); + if env.latest_status().success() { + *env.latest_status() + } else { + todo!() + // super::run_exe(&exe, env).await + } +} + +async fn command( + exe: &crate::parse::Exe, + env: &crate::command::Env, +) -> async_std::process::ExitStatus { + let exe = exe.shift(); + // super::run_binary(&exe, env).await; + *env.latest_status() +} + +async fn builtin( + exe: &crate::parse::Exe, + env: &crate::command::Env, +) -> async_std::process::ExitStatus { + let exe = exe.shift(); + run(&exe, env).unwrap().await; + *env.latest_status() +} + +fn home() -> std::path::PathBuf { + std::env::var_os("HOME").unwrap().into() +} |