diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 193 | ||||
| -rw-r--r-- | src/run/builtin.rs | 0 | ||||
| -rw-r--r-- | src/run/mod.rs | 216 |
3 files changed, 253 insertions, 156 deletions
diff --git a/src/main.rs b/src/main.rs index 885e03d..08c4528 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,12 @@ pub mod linebuf; pub mod panic; pub mod parse; pub mod raw; +pub mod run; use linebuf::LineBuf; use raw::*; use crate::cursor::{Direction, move_cursor}; -use crate::parse::Ast; macro_rules! print { ($($x:tt)*) => {{ @@ -37,134 +37,6 @@ macro_rules! println { const PROMPT: &str = "> "; -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}"); -} - -fn env_path() { - let path = std::env::var("PATH").unwrap(); - for p in path.split(":") { - println!("{p}"); - } -} - fn completely_clear_screen() { print!("\x1B[2J\x1B[1;1H"); } @@ -174,9 +46,17 @@ fn clear_screen() { print!("{PROMPT}") } -fn event_loop() { - env_path(); +type BString = Vec<u8>; +#[allow(non_camel_case_types)] +type bstr = [u8]; +pub struct Session { + raw: ScopedRawMode, + line: LineBuf, + history: Vec<BString>, +} + +fn event_loop() { let stdin = io::stdin(); let stdout = io::stdout(); @@ -184,21 +64,22 @@ fn event_loop() { let raw = ScopedRawMode::on_fd(fd); raw.enable(); - let mut stdin = stdin.lock(); - let mut stdout = stdout.lock(); - - let mut buffer = [0u8; 1]; - let mut line = LineBuf::new(); - let mut history = Vec::new(); + let mut se = Session { + raw, + line: LineBuf::new(), + history: Vec::new(), + }; print!("{PROMPT}"); loop { - let Ok(_) = stdin.read_exact(&mut buffer) else { + let mut buf = [0u8; 1]; + + let Ok(_) = stdin.lock().read_exact(&mut buf) else { break; }; - match buffer[0] { + match buf[0] { // EOF 4 => { break; @@ -212,23 +93,23 @@ fn event_loop() { // Enter b'\r' => { - let line = line.dump(); + let line = se.line.dump(); if !line.is_empty() { print!("\r\n"); - history.push(line.clone()); - run_command(&raw, line); + se.history.push(line.clone()); + run::run(&se, line); } } // Backspace (127 on most systems) 127 => { - if line.is_empty() && !line.is_dirty() && !history.is_empty() { + if se.line.is_empty() && !se.line.is_dirty() && !se.history.is_empty() { // take previous command for editing - let cmd = history[history.len() - 1].clone(); + let cmd = se.history[se.history.len() - 1].clone(); io::stdout().write_all(&cmd).unwrap(); io::stdout().flush().unwrap(); - line.set_content(cmd); - } else if line.del_left().is_some() { + se.line.set_content(cmd); + } else if se.line.del_left().is_some() { print!("\x08 \x08"); } } @@ -240,7 +121,7 @@ fn event_loop() { // Escape sequence 27 => { let mut seq = [0u8; 2]; - stdin.read_exact(&mut seq).unwrap(); + stdin.lock().read_exact(&mut seq).unwrap(); if seq[0] == b'[' { match seq[1] { @@ -252,35 +133,35 @@ fn event_loop() { } b'C' => { move_cursor(Direction::Right, 1); - line.right(); + se.line.right(); } b'D' => { move_cursor(Direction::Left, 1); - line.left(); + se.line.left(); } x => todo!("escape character {x}"), } } } - b'|' if line.is_empty() && !history.is_empty() => { - let mut cmd = history[history.len() - 1].clone(); + b'|' if se.line.is_empty() && !se.history.is_empty() => { + let mut cmd = se.history[se.history.len() - 1].clone(); cmd.extend_from_slice(b" | "); io::stdout().write_all(&cmd).unwrap(); io::stdout().flush().unwrap(); - line.set_content(cmd); + se.line.set_content(cmd); } // Normal character x => { - line.add(x); - stdout.write_all(&[x]).unwrap(); - line.display_post(); + se.line.add(x); + stdout.lock().write_all(&[x]).unwrap(); + se.line.display_post(); } } } - raw.disable(); + se.raw.disable(); } fn main() { 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}"); +} |
