summaryrefslogtreecommitdiffstats
path: root/src/builtins.rs
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2022-01-02 23:10:26 -0500
committerJesse Luehrs <doy@tozt.net>2022-01-02 23:10:26 -0500
commit0115a566afa763c9732e03765d50a2ace4008d18 (patch)
tree9a2a01940b0e40da502c99f60859b56f8e8177ac /src/builtins.rs
parent1b146445943d5aaeea3261a40bb5962a422b7c48 (diff)
downloadnbsh-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.rs216
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()
+}