diff options
| author | Jonas Maier <> | 2026-03-05 13:06:53 +0100 |
|---|---|---|
| committer | Jonas Maier <> | 2026-03-05 13:06:53 +0100 |
| commit | 04b55265cb13c260c8f93ba637e84e184ba83441 (patch) | |
| tree | 967d22c76605b825a0f277b358ffaf754c3b4702 /src/run | |
| parent | be347f26767b1a45cb77477307a1988ebdc3848c (diff) | |
| download | pish-04b55265cb13c260c8f93ba637e84e184ba83441.tar.gz | |
start of a nice architecture for builtins and path commands
Diffstat (limited to 'src/run')
| -rw-r--r-- | src/run/builtin.rs | 0 | ||||
| -rw-r--r-- | src/run/mod.rs | 216 |
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}"); +} |
