use std::collections::HashMap; use std::path::PathBuf; use crate::parse::Ast; use crate::*; mod builtin; pub fn run(se: &mut Session, cmd: Vec) { let parsed = parse::do_parse(&cmd); let parsed = match parsed { Ok(p) => p, Err(err) => { println!("{cmd:?}"); print!("{err:?}\r\n{}", se.prompt()); return; } }; let Ast::Pipes(pipes) = parsed else { todo!("can only handle pipes"); }; let mut children = Vec::new(); let mut threads = Vec::new(); let mut prev_reader = None; let mut spawn_error = false; se.raw.disable(); let pipelen = pipes.cmds.len(); for (i, cmd) in pipes.cmds.into_iter().enumerate() { let last = i == pipelen - 1; let (reader, writer) = if !last { let (r, w) = io::pipe().unwrap(); (Some(r), Some(w)) } else { (None, None) }; let dc = se.dispatch.get(&cmd.cmd[..]); match dc { CommandKind::Path(path) => { let mut command = Command::new(&path); for arg in cmd.args.iter() { command.arg(OsStr::from_bytes(arg)); } if let Some(r) = prev_reader.take() { command.stdin(Stdio::from(r)); } if let Some(w) = writer { command.stdout(Stdio::from(w)); } let child = match command.spawn() { Ok(c) => c, Err(e) => { let cmd = path.to_string_lossy(); let msg = match e.kind() { io::ErrorKind::NotFound => format!("{cmd} not found"), io::ErrorKind::PermissionDenied => format!("{cmd} is not executable"), io::ErrorKind::FileTooLarge => format!("{cmd} is too massive"), io::ErrorKind::ResourceBusy | io::ErrorKind::ExecutableFileBusy => { format!("{cmd} is busy") } io::ErrorKind::TooManyLinks => format!("{cmd} could not be resolved"), io::ErrorKind::InvalidFilename => { format!("{cmd} is not a valid file name") } io::ErrorKind::ArgumentListTooLong => format!("too many arguments"), io::ErrorKind::Interrupted => format!("got interrupted"), io::ErrorKind::Unsupported => format!("{cmd} is not supported"), e => format!("I am surprised you can get this error here: {e:?}"), }; println!("pish: {msg}"); spawn_error = true; break; } }; children.push(child); } CommandKind::Builtin(builtin) => { builtin.mod_session(se, &cmd.args); let mut input: Box = match prev_reader.take() { Some(r) => Box::new(r), None => Box::new(io::stdin()), }; let mut output: Box = match writer { Some(w) => Box::new(w), None => Box::new(io::stdout()), }; let handle = std::thread::spawn(move || builtin.io(&cmd.args, &mut input, &mut output)); threads.push(handle); } } prev_reader = reader; } 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 = "".into(); } else { let mut code = 0; for jh in threads { // TODO do not ignore panics let _ = jh.join(); } 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}"); } } se.raw.enable(); print!("\r{status_string}\r\n{}", se.prompt()); let _ = std::io::stdout().lock().flush(); } #[allow(unused_variables)] pub trait Builtin: Send + Sync { fn name(&self) -> &str; /// quick synchronous call, `cd` for example fn mod_session(&self, session: &mut Session, args: &[BString]) {} /// 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] = &[ &builtin::cd, &builtin::clear, #[cfg(debug_assertions)] &builtin::re, &builtin::sink("to", false), &builtin::sink("into", false), &builtin::sink("append", true), &builtin::from, &builtin::builtins, &builtin::_type, &builtin::history, ]; pub struct CommandDispatch { map: HashMap, } impl CommandDispatch { pub fn new() -> Self { let mut map = HashMap::new(); // builtins for &b in BUILTINS { map.insert(b.name().as_bytes().to_vec(), CommandKind::Builtin(b)); } Self { map } } fn get(&self, cmd: &bstr) -> CommandKind { let path_cmd = CommandKind::Path(PathBuf::from(OsStr::from_bytes(cmd))); if cmd.contains(&b'/') { path_cmd } else if let Some(cmd) = self.map.get(cmd) { cmd.clone() } else { path_cmd } } } #[derive(Clone)] pub enum CommandKind { Builtin(&'static dyn Builtin), Path(PathBuf), }