aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/main.rs193
-rw-r--r--src/run/builtin.rs0
-rw-r--r--src/run/mod.rs216
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}");
+}