diff --git a/src/builtins.rs b/src/builtins.rs index 8381458..b80efb3 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -1,17 +1,20 @@ //! builtins to sesh #![allow(clippy::type_complexity)] +use std::sync::{Arc, Mutex}; + /// List of builtins pub const BUILTINS: [( &str, fn(args: Vec, unsplit_args: String, state: &mut super::State) -> i32, &str, -); 5] = [ +); 6] = [ ("cd", cd, "[dir]"), ("exit", exit, ""), ("echo", echo, "[-e] [text ...]"), ("alias", alias, "[name] [value]"), ("help", help, ""), + ("source", eval, "filename [arguments]"), ]; /// Change the directory @@ -86,8 +89,51 @@ pub fn help(_: Vec, _: String, _: &mut super::State) -> i32 { println!("Use `man sesh` to find out more about the shell in general."); println!("Use `man -k' or `info' to find out more about commands not in this list."); println!(); - for builtin in BUILTINS { + let mut builtins = BUILTINS.clone(); + builtins.sort_by(|v1, v2| v1.0.cmp(v2.0)); + + for builtin in builtins { println!("{} {}", builtin.0, builtin.2); } 0 } + +/// Run a file. +pub fn eval(args: Vec, _: String, state: &mut super::State) -> i32 { + if args.len() < 2 { + println!("sesh: {}: filename argument required", args[0]); + println!("sesh: {0}: usage: {0} filename [arguments]", args[0]); + return 1; + } + + let file = std::fs::read(args[1].clone()); + if file.is_err() { + println!( + "sesh: {}: error opening file: {}", + args[0], + file.unwrap_err() + ); + return 2; + } + let file = String::from_utf8(file.unwrap()); + if file.is_err() { + println!("sesh: {}: invalid UTF-8: {}", args[0], file.unwrap_err()); + return 3; + } + let file = file.unwrap(); + + let mut state2 = state.clone(); + + let mut i = 0usize; + for arg in &args[1..] { + state2.shell_env.push(super::ShellVar { + name: format!("{}", i), + value: arg.clone(), + }); + i += 1; + } + + super::eval(&file, &mut state2); + + 0 +} diff --git a/src/main.rs b/src/main.rs index 8116e74..1abccb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,14 +38,6 @@ struct ShellVar { /// A lot of [ShellVar]s. type ShellVars = Vec; -/// Whether a variable is local or not. -#[derive(Clone, Debug, PartialEq, Eq)] -enum VariableLocality { - /// A local variable. - Local, - /// A nonlocal variable. - Nonlocal, -} /// A reference to a variable. #[derive(Clone, Debug, PartialEq, Eq)] enum Variable { @@ -66,8 +58,6 @@ struct Alias { /// The state of the shell #[derive(Clone, Debug)] struct State { - /// Environment variables - env: Arc>, /// Shell-local variables only accessible via builtins. shell_env: ShellVars, /// The focused variable. @@ -170,11 +160,20 @@ fn split_statements(statement: &str) -> Vec { .concat() } +/// Substitute in shell variables +fn substitute_vars(statement: &str, state: State) -> String { + let mut out = statement.to_string(); + for ShellVar { name, value } in state.shell_env { + out = out.replace(&("$".to_owned()+&name), &value); + } + out +} + #[allow(clippy::arc_with_non_send_sync)] /// Evaluate a statement. May include multiple. fn eval(statement: &str, state: &mut State) { let statement = remove_comments(statement); - let statements = split_statements(&statement); + let statements = split_statements(&substitute_vars(&statement, state.clone())); for statement in statements { let mut statement_split = split_statement(&statement); @@ -236,7 +235,6 @@ fn eval(statement: &str, state: &mut State) { } } - state.env = Arc::new(Mutex::new(std::env::vars_os())); let s = state.clone(); state.history.push(s); } @@ -284,7 +282,6 @@ fn main() -> Result<(), Box> { let options = Args::parse(); let mut state = State { - env: Arc::new(Mutex::new(std::env::vars_os())), shell_env: Vec::new(), focus: Variable::Local(String::new()), history: Vec::new(), diff --git a/test.sesh b/test.sesh new file mode 100644 index 0000000..728e625 --- /dev/null +++ b/test.sesh @@ -0,0 +1 @@ +echo test $0 $1 \ No newline at end of file