Add comments and ctrl+c not exiting

This commit is contained in:
Arthur Beck 2025-05-02 15:56:49 -05:00
parent ea6c94a584
commit 41c3ffcf54
Signed by: ArthurB
GPG key ID: CA200B389F0F6BC9
3 changed files with 131 additions and 60 deletions

View file

@ -5,6 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
clap = { version = "4.5.37", features = ["derive", "env"] } clap = { version = "4.5.37", features = ["derive", "env"] }
ctrlc = "3.4.6"
hostname = "0.4.1" hostname = "0.4.1"
users = "0.11.0" users = "0.11.0"

View file

@ -2,10 +2,14 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
/// List of builtins /// List of builtins
pub const BUILTINS: [(&str, fn (args: Vec<String>, state: &mut super::State) -> i32); 2] = [("cd", cd), ("exit", exit)]; pub const BUILTINS: [(&str, fn (args: Vec<String>, unsplit_args: String, state: &mut super::State) -> i32); 3] = [("cd", cd), ("exit", exit), ("echo", echo)];
/// Change the directory /// Change the directory
pub fn cd(args: Vec<String>, state: &mut super::State) -> i32 { pub fn cd(args: Vec<String>, unsplit_args: String, state: &mut super::State) -> i32 {
if args.len() == 1 {
state.working_dir = std::env::home_dir().unwrap();
return 0;
}
if args[1] == ".." { if args[1] == ".." {
state.working_dir.pop(); state.working_dir.pop();
return 0; return 0;
@ -15,6 +19,22 @@ pub fn cd(args: Vec<String>, state: &mut super::State) -> i32 {
} }
/// Exit the shell /// Exit the shell
pub fn exit(_: Vec<String>, _: &mut super::State) -> i32 { pub fn exit(_: Vec<String>, _: String, _: &mut super::State) -> i32 {
std::process::exit(0); std::process::exit(0);
} }
/// Echo a string
pub fn echo(args: Vec<String>, mut unsplit_args: String, _: &mut super::State) -> i32 {
unsplit_args = unsplit_args[5..].to_string();
if args.len() != 1 && args[1] == "-e" {
unsplit_args = unsplit_args[3..].to_string();
let escaped = crate::escapes::interpret_escaped_string(&unsplit_args);
if escaped.is_err() {
println!("sesh: echo: invalid escape: {}", escaped.unwrap_err());
return 1;
}
unsplit_args = escaped.unwrap();
}
println!("{}", unsplit_args);
0
}

View file

@ -5,10 +5,9 @@
use std::{ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
io::Write, io::{Read, Write},
path::PathBuf, path::PathBuf,
rc::Rc, sync::{Arc, Mutex, RwLock},
sync::Mutex,
}; };
use clap::Parser; use clap::Parser;
@ -53,7 +52,7 @@ enum Variable {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct State { struct State {
/// Environment variables /// Environment variables
env: Rc<Mutex<std::env::VarsOs>>, env: Arc<Mutex<std::env::VarsOs>>,
/// Shell-local variables only accessible via builtins. /// Shell-local variables only accessible via builtins.
shell_env: ShellVars, shell_env: ShellVars,
/// The focused variable. /// The focused variable.
@ -64,6 +63,10 @@ struct State {
working_dir: PathBuf, working_dir: PathBuf,
} }
unsafe impl Sync for State {}
unsafe impl Send for State {}
/// Split a statement.
fn split_statement(statement: &str) -> Vec<String> { fn split_statement(statement: &str) -> Vec<String> {
let mut out = vec![String::new()]; let mut out = vec![String::new()];
let mut i: usize = 0; let mut i: usize = 0;
@ -91,32 +94,50 @@ fn split_statement(statement: &str) -> Vec<String> {
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }
/// Removes comments from a statement
fn remove_comments(statement: &str) -> String {
let mut out = String::new();
let mut in_comment = false;
for ch in statement.chars() {
if in_comment {
if ch == '\n' {
out.push(ch);
in_comment = false
}
continue;
}
if ch == '#' {
in_comment = true;
continue;
}
out.push(ch);
}
out
}
#[allow(clippy::arc_with_non_send_sync)]
/// Evaluate a statement. May include multiple. /// Evaluate a statement. May include multiple.
fn eval(statement: &str, state: &mut State) { fn eval(statement: &str, state: &mut State) {
let statement = escapes::interpret_escaped_string(statement); let statement = remove_comments(statement);
if statement.is_err() {
println!("sesh: invalid escape: {}", statement.unwrap_err());
return;
}
let statements = statement let statements = statement
.unwrap()
.split("\n") .split("\n")
.map(|val| val.split(";").collect::<Vec<&str>>()) .map(|val| val.split(";").collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>() .collect::<Vec<Vec<&str>>>()
.iter() .iter()
.map(|val| val.iter().map(|val| val.trim()).collect::<Vec<&str>>()) .map(|val| val.iter().map(|val| val.trim()).collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>() .collect::<Vec<Vec<&str>>>()
.concat() .concat();
.iter()
.map(|val| split_statement(val))
.collect::<Vec<Vec<String>>>();
for statement in statements { for statement in statements {
if statement.is_empty() || statement[0].is_empty() { let statement_split = split_statement(statement);
if statement.is_empty() || statement_split[0].is_empty() {
continue; continue;
} }
if let Some(builtin) = builtins::BUILTINS.iter().find(|v| v.0 == statement[0]) { if let Some(builtin) = builtins::BUILTINS
let status = builtin.1(statement, state); .iter()
.find(|v| v.0 == statement_split[0])
{
let status = builtin.1(statement_split, statement.to_string(), state);
for (i, var) in state.shell_env.clone().into_iter().enumerate() { for (i, var) in state.shell_env.clone().into_iter().enumerate() {
if var.name == "STATUS" { if var.name == "STATUS" {
state.shell_env.swap_remove(i); state.shell_env.swap_remove(i);
@ -129,8 +150,8 @@ fn eval(statement: &str, state: &mut State) {
}); });
continue; continue;
} }
match std::process::Command::new(statement[0].clone()) match std::process::Command::new(statement_split[0].clone())
.args(&statement[1..]) .args(&statement_split[1..])
.current_dir(state.working_dir.clone()) .current_dir(state.working_dir.clone())
.spawn() .spawn()
{ {
@ -154,25 +175,13 @@ fn eval(statement: &str, state: &mut State) {
} }
} }
state.env = Rc::new(Mutex::new(std::env::vars_os())); state.env = Arc::new(Mutex::new(std::env::vars_os()));
state.history.push(state.clone()); let s = state.clone();
state.history.push(s);
} }
fn main() -> Result<(), Box<dyn std::error::Error>> { /// Write the prompt to the screen.
let interactive = true; fn write_prompt(state: State) -> Result<(), Box<dyn std::error::Error>> {
let mut state = State {
env: Rc::new(Mutex::new(std::env::vars_os())),
shell_env: Vec::new(),
focus: Variable::Local(String::new()),
history: Vec::new(),
working_dir: std::env::current_dir()
.unwrap_or(std::env::home_dir().unwrap_or(PathBuf::from("/"))),
};
state.shell_env.push(ShellVar {
name: "PROMPT".to_string(),
value: "$u@$h $P> ".to_string(),
});
loop {
let mut prompt = state let mut prompt = state
.shell_env .shell_env
.iter() .iter()
@ -206,9 +215,50 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
print!("{}", prompt); print!("{}", prompt);
std::io::stdout().flush()?; std::io::stdout().flush()?;
Ok(())
}
#[allow(clippy::arc_with_non_send_sync)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
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(),
working_dir: std::env::current_dir()
.unwrap_or(std::env::home_dir().unwrap_or(PathBuf::from("/"))),
};
state.shell_env.push(ShellVar {
name: "PROMPT".to_string(),
value: "\x1b[32m$u@$h\x1b[39m \x1b[34m$P\x1b[39m> ".to_string(),
});
let ctrlc_cont = Arc::new(RwLock::new(false));
let cc2 = ctrlc_cont.clone();
ctrlc::set_handler(move || {
(*cc2.write().unwrap()) = true;
})
.expect("Error setting Ctrl-C handler");
'mainloop: loop {
write_prompt(state.clone())?;
let mut input = String::new(); let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
let mut i0 = [0u8];
while i0[0] != b'\n' {
if ctrlc_cont.read().unwrap().to_owned() {
input.clear();
(*ctrlc_cont.write().unwrap()) = false;
println!();
continue 'mainloop;
}
let amount = std::io::stdin().read(&mut i0).unwrap();
if amount == 0 {
continue;
}
input.push(char::from_u32(i0[0] as u32).unwrap());
}
eval(&input, &mut state); eval(&input, &mut state);
} }