aboutsummaryrefslogtreecommitdiffstats
path: root/src/run
diff options
context:
space:
mode:
authorJonas Maier <>2026-03-05 13:06:53 +0100
committerJonas Maier <>2026-03-05 13:06:53 +0100
commit04b55265cb13c260c8f93ba637e84e184ba83441 (patch)
tree967d22c76605b825a0f277b358ffaf754c3b4702 /src/run
parentbe347f26767b1a45cb77477307a1988ebdc3848c (diff)
downloadpish-04b55265cb13c260c8f93ba637e84e184ba83441.tar.gz
start of a nice architecture for builtins and path commands
Diffstat (limited to 'src/run')
-rw-r--r--src/run/builtin.rs0
-rw-r--r--src/run/mod.rs216
2 files changed, 216 insertions, 0 deletions
diff --git a/src/run/builtin.rs b/src/run/builtin.rs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/run/builtin.rs
diff --git a/src/run/mod.rs b/src/run/mod.rs
new file mode 100644
index 0000000..82409e9
--- /dev/null
+++ b/src/run/mod.rs
@@ -0,0 +1,216 @@
+use std::collections::HashMap;
+use std::fs;
+use std::os::unix::fs::PermissionsExt;
+use std::path::PathBuf;
+
+use crate::parse::Ast;
+use crate::*;
+
+pub fn run(se: &Session, cmd: Vec<u8>) {
+ run_command(&se.raw, cmd);
+}
+
+pub trait Builtin : Send + Sync {
+ fn name(&self) -> & str;
+
+ /// quick synchronous call, `cd` for example
+ fn mod_session(&self, session: &mut Session) {}
+
+ /// potentially long, pipelineable thread, builtin `cat` for example
+ fn io(
+ &self,
+ args: &[BString],
+ stdin: &mut dyn Read,
+ stdout: &mut dyn Write,
+ ) -> std::io::Result<()> {
+ Ok(())
+ }
+}
+
+const BUILTINS: &[&'static dyn Builtin] = &[];
+
+pub struct CommandDispatch {
+ map: HashMap<BString, CommandKind>,
+}
+
+impl CommandDispatch {
+ fn new() -> Self {
+ let mut map = HashMap::new();
+
+ // all the commands from PATH
+ let path = std::env::var_os("PATH").unwrap();
+ for p in path.as_bytes().split(|x| *x == b':').rev() {
+ let p = PathBuf::from(OsStr::from_bytes(p));
+ let Ok(entries) = fs::read_dir(p) else {
+ continue;
+ };
+
+ for entry in entries {
+ let Ok(entry) = entry else { continue };
+ let Ok(meta) = entry.metadata() else {
+ continue;
+ };
+
+ if !meta.is_file() {
+ continue;
+ }
+
+ let perms = meta.permissions();
+ let mode = perms.mode();
+
+ // Check if any execute bit is set (owner/group/other)
+ if mode & 0o111 == 0 {
+ continue;
+ }
+
+ // insert into our command map, mind the .rev() on the iterator above s.t. correct precedence is had
+ map.insert(
+ entry.file_name().as_bytes().to_vec(),
+ CommandKind::Path(entry.path()),
+ );
+ }
+ }
+
+ // builtins
+ for &b in BUILTINS {
+ map.insert(b.name().as_bytes().to_vec(), CommandKind::Builtin(b));
+ }
+
+ todo!()
+ }
+
+ fn get(&self, cmd: &bstr) -> Option<CommandKind> {
+ if cmd.starts_with(b"/") || cmd.starts_with(b"./") {
+ Some(CommandKind::Path(PathBuf::from(OsStr::from_bytes(cmd))))
+ } else {
+ self.map.get(cmd).cloned()
+ }
+ }
+}
+
+#[derive(Clone)]
+pub enum CommandKind {
+ Builtin(&'static dyn Builtin),
+ Path(PathBuf),
+}
+
+fn run_command(raw: &ScopedRawMode, line: Vec<u8>) {
+ let parsed = parse::do_parse(&line);
+
+ let parsed = match parsed {
+ Ok(p) => p,
+ Err(err) => {
+ println!("{line:?}");
+ print!("{err:?}\r\n{PROMPT}");
+ return;
+ }
+ };
+
+ let Ast::Pipes(pipes) = parsed else {
+ todo!("can only handle pipes");
+ };
+
+ // simple command that can probably be builtin
+ // TODO: handle builtins uniformly instead of big if case
+ if pipes.cmds.len() == 1 {
+ let c = &pipes.cmds[0];
+ match &c.cmd[..] {
+ b"cd" => {
+ let target: &Path = match c.args.get(0).map(|v| &v[..]) {
+ Some(b"-") => todo!("prev"),
+ Some(path) => OsStr::from_bytes(path).as_ref(),
+ None => todo!("homedir"),
+ };
+
+ if let Err(_) = std::env::set_current_dir(target) {
+ print!("ERR {PROMPT}");
+ } else {
+ print!("{PROMPT}");
+ }
+
+ return;
+ }
+ b"clear" => clear_screen(),
+ #[cfg(debug_assertions)]
+ b"re" => {
+ // restart shell
+ raw.disable();
+ let _ = Command::new("cargo").arg("run").status();
+ raw.disable();
+ std::process::exit(0);
+ }
+ b"from" => todo!("read from file"),
+ b"to" | b"into" => todo!("write into file"),
+ b"append" => todo!("append to file"),
+ _ => (),
+ }
+ }
+
+ let mut children: Vec<Child> = Vec::new();
+ let mut prev_stdout = None;
+ let mut spawn_error = false;
+
+ raw.disable();
+
+ for (i, cmd) in pipes.cmds.iter().enumerate() {
+ let mut command = Command::new(OsStr::from_bytes(&cmd.cmd));
+ for arg in cmd.args.iter() {
+ command.arg(OsStr::from_bytes(arg));
+ }
+
+ if let Some(stdout) = prev_stdout.take() {
+ command.stdin(Stdio::from(stdout));
+ }
+
+ if i < pipes.cmds.len() - 1 {
+ command.stdout(Stdio::piped());
+ }
+
+ let mut child = match command.spawn() {
+ Ok(c) => c,
+ Err(e) => {
+ println!("failed to spawn {:?} - {e:?}", OsStr::from_bytes(&cmd.cmd));
+ spawn_error = true;
+ break;
+ }
+ };
+
+ prev_stdout = child.stdout.take();
+
+ children.push(child);
+ }
+
+ let status_string;
+
+ if spawn_error {
+ for child in children.iter_mut() {
+ if let Err(e) = child.kill() {
+ println!("failed to kill child - {e:?}");
+ }
+ }
+ status_string = "ERR".into();
+ } else {
+ let mut code = 0;
+ for mut child in children {
+ match child.wait() {
+ Ok(ec) => {
+ if let Some(c) = ec.code() {
+ code = c;
+ }
+ }
+ Err(e) => {
+ println!("failed to wait for child - {e:?}")
+ }
+ }
+ }
+ if code == 0 {
+ status_string = String::new();
+ } else {
+ status_string = format!("{code}");
+ }
+ }
+
+ raw.enable();
+
+ print!("\r{status_string}{PROMPT}");
+}