From 70d114254db0b274fce2c53aa3efed3d81804339 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Mon, 24 Mar 2025 17:53:32 -0400 Subject: [PATCH] Implemented a new builtin and improved error handling --- src/builtin/echo.rs | 11 +++- src/builtin/export.rs | 5 +- src/builtin/mod.rs | 6 +- src/builtin/zoltraak.rs | 115 ++++++++++++++++++++++++++++++++++++ src/fern.rs | 55 ++++++++++++++---- src/foo/bar | 0 src/getopt.rs | 13 +---- src/libsh/error.rs | 112 ++++++++++++++++++++++++++++------- src/libsh/sys.rs | 2 +- src/parse/execute.rs | 3 +- src/parse/mod.rs | 21 +++++-- src/prompt/readline.rs | 1 + src/shopt.rs | 126 ++++++++++++++++++++++++++++++++++++++++ src/state.rs | 5 +- 14 files changed, 420 insertions(+), 55 deletions(-) create mode 100644 src/builtin/zoltraak.rs create mode 100644 src/foo/bar create mode 100644 src/shopt.rs diff --git a/src/builtin/echo.rs b/src/builtin/echo.rs index f3e7dc3..8a392dd 100644 --- a/src/builtin/echo.rs +++ b/src/builtin/echo.rs @@ -1,4 +1,13 @@ -use crate::{builtin::setup_builtin, getopt::{get_opts_from_tokens, Opt, ECHO_OPTS}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state}; +use std::sync::LazyLock; + +use crate::{builtin::setup_builtin, getopt::{get_opts_from_tokens, Opt, OptSet}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state}; + +pub static ECHO_OPTS: LazyLock = LazyLock::new(|| {[ + Opt::Short('n'), + Opt::Short('E'), + Opt::Short('e'), + Opt::Short('r'), +].into()}); bitflags! { pub struct EchoFlags: u32 { diff --git a/src/builtin/export.rs b/src/builtin/export.rs index ab6c9cd..6804996 100644 --- a/src/builtin/export.rs +++ b/src/builtin/export.rs @@ -1,4 +1,4 @@ -use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state}; +use crate::{jobs::JobBldr, libsh::error::{Note, ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state}; use super::setup_builtin; @@ -29,6 +29,9 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult "export: Expected an assignment in export args", span.into() ) + .with_note( + Note::new("Arguments for export should be formatted like 'foo=bar'") + ) ) }; env::set_var(var, val); diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 274acd9..27269dd 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -11,8 +11,9 @@ pub mod shift; pub mod jobctl; pub mod alias; pub mod flowctl; +pub mod zoltraak; -pub const BUILTINS: [&str;14] = [ +pub const BUILTINS: [&str;15] = [ "echo", "cd", "export", @@ -26,7 +27,8 @@ pub const BUILTINS: [&str;14] = [ "return", "break", "continue", - "exit" + "exit", + "zoltraak" ]; /// Sets up a builtin command diff --git a/src/builtin/zoltraak.rs b/src/builtin/zoltraak.rs new file mode 100644 index 0000000..b969bbb --- /dev/null +++ b/src/builtin/zoltraak.rs @@ -0,0 +1,115 @@ +use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock}; + +use crate::{getopt::{get_opts_from_tokens, Opt, OptSet}, jobs::JobBldr, libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::IoStack}; + +use super::setup_builtin; + +pub const ZOLTRAAK_OPTS: LazyLock = LazyLock::new(|| { + [ + Opt::Long("dry-run".into()), + Opt::Long("confirm".into()), + Opt::Long("no-preserve-root".into()), + Opt::Short('r'), + Opt::Short('f'), + Opt::Short('v') + ].into() +}); + +/// Annihilate a file +/// +/// This command works similarly to 'rm', but behaves more destructively. +/// The file given as an argument is completely destroyed. The command works by shredding all of the data contained in the file, before truncating the length of the file to 0 to ensure that not even any metadata remains. +pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { + let NdRule::Command { assignments, argv } = node.class else { + unreachable!() + }; + + let (argv,opts) = get_opts_from_tokens(argv); + + let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?; + + for (arg,span) in argv { + annihilate(&arg, false).blame(span)?; + } + + Ok(()) +} + +fn annihilate(path: &str, allow_dirs: bool) -> ShResult<()> { + let path_buf = PathBuf::from(path); + + const BLOCK_SIZE: u64 = 4096; + + if !path_buf.exists() { + return Err( + ShErr::simple( + ShErrKind::ExecFail, + format!("zoltraak: File '{path}' not found") + ) + ) + } + + if path_buf.is_file() { + let mut file = OpenOptions::new() + .write(true) + .custom_flags(libc::O_DIRECT) + .open(path_buf)?; + + let meta = file.metadata()?; + let file_size = meta.len(); + let full_blocks = file_size / BLOCK_SIZE; + let byte_remainder = file_size % BLOCK_SIZE; + + let full_buf = vec![0; BLOCK_SIZE as usize]; + let remainder_buf = vec![0; byte_remainder as usize]; + + for _ in 0..full_blocks { + file.write_all(&full_buf)?; + } + + if byte_remainder > 0 { + file.write_all(&remainder_buf)?; + } + + file.set_len(0)?; + mem::drop(file); + fs::remove_file(path)?; + + } else if path_buf.is_dir() { + if allow_dirs { + annihilate_recursive(path)?; // scary + } else { + return Err( + ShErr::simple( + ShErrKind::ExecFail, + format!("zoltraak: '{path}' is a directory") + ) + .with_note( + Note::new("Use the '-r' flag to recursively shred directories") + .with_sub_notes(vec![ + "Example: 'zoltraak -r directory'" + ]) + ) + ) + } + } + + Ok(()) +} + +fn annihilate_recursive(dir: &str) -> ShResult<()> { + let dir_path = PathBuf::from(dir); + + for dir_entry in fs::read_dir(&dir_path)? { + let entry = dir_entry?.path(); + let file = entry.to_str().unwrap(); + + if entry.is_file() { + annihilate(file, true)?; + } else if entry.is_dir() { + annihilate_recursive(file)?; + } + } + fs::remove_dir(dir)?; + Ok(()) +} diff --git a/src/fern.rs b/src/fern.rs index f20da81..6e1cd0c 100644 --- a/src/fern.rs +++ b/src/fern.rs @@ -11,6 +11,7 @@ pub mod signal; #[cfg(test)] pub mod tests; pub mod getopt; +pub mod shopt; use std::collections::HashSet; @@ -22,8 +23,25 @@ use state::{source_rc, write_meta}; use termios::{LocalFlags, Termios}; use crate::prelude::*; -pub static mut SAVED_TERMIOS: Option> = None; - +/// The previous state of the terminal options. +/// +/// This variable stores the terminal settings at the start of the program and restores them when the program exits. +/// It is initialized exactly once at the start of the program and accessed exactly once at the end of the program. +/// It will not be mutated or accessed under any other circumstances. +/// +/// This ended up being necessary because wrapping Termios in a thread-safe way was unreasonably tricky. +/// +/// The possible states of this variable are: +/// - `None`: The terminal options have not been set yet (before initialization). +/// - `Some(None)`: There were no terminal options to save (i.e., no terminal input detected). +/// - `Some(Some(Termios))`: The terminal options (as `Termios`) have been saved. +/// +/// **Important:** This static variable is mutable and accessed via unsafe code. It is only safe to use because: +/// - It is set once during program startup and accessed once during program exit. +/// - It is not mutated or accessed after the initial setup and final read. +/// +/// **Caution:** Future changes to this code should respect these constraints to ensure safety. Modifying or accessing this variable outside the defined lifecycle could lead to undefined behavior. +pub(crate) static mut SAVED_TERMIOS: Option> = None; pub fn save_termios() { unsafe { @@ -37,15 +55,15 @@ pub fn save_termios() { }); } } - #[allow(static_mut_refs)] -pub fn get_saved_termios() -> Option { - unsafe { - // This is only used when the shell exits so it's fine - // SAVED_TERMIOS is only mutated once at the start as well - SAVED_TERMIOS.clone().flatten() - } +pub unsafe fn get_saved_termios() -> Option { + // SAVED_TERMIOS should *only ever* be set once and accessed once + // Set at the start of the program, and accessed during the exit of the program to reset the termios. + // Do not use this variable anywhere else + SAVED_TERMIOS.clone().flatten() } + +/// Set termios to not echo control characters, like ^Z for instance fn set_termios() { if isatty(std::io::stdin().as_raw_fd()).unwrap() { let mut termios = termios::tcgetattr(std::io::stdin()).unwrap(); @@ -73,12 +91,24 @@ fn main() { eprintln!("{e}"); } - loop { + const MAX_READLINE_ERRORS: u32 = 5; + let mut readline_err_count: u32 = 0; + + loop { // Main loop let input = match prompt::read_line() { - Ok(line) => line, + Ok(line) => { + readline_err_count = 0; + line + } Err(e) => { eprintln!("{e}"); - continue + readline_err_count += 1; + if readline_err_count == MAX_READLINE_ERRORS { + eprintln!("reached maximum readline error count, exiting"); + break + } else { + continue + } } }; @@ -86,4 +116,5 @@ fn main() { eprintln!("{e}"); } } + exit(1); } diff --git a/src/foo/bar b/src/foo/bar new file mode 100644 index 0000000..e69de29 diff --git a/src/getopt.rs b/src/getopt.rs index 3060e8e..dc787a7 100644 --- a/src/getopt.rs +++ b/src/getopt.rs @@ -1,17 +1,11 @@ -use std::sync::{Arc, LazyLock}; +use std::sync::Arc; use fmt::Display; use crate::{parse::lex::Tk, prelude::*}; -type OptSet = Arc<[Opt]>; +pub type OptSet = Arc<[Opt]>; -pub static ECHO_OPTS: LazyLock = LazyLock::new(|| {[ - Opt::Short('n'), - Opt::Short('E'), - Opt::Short('e'), - Opt::Short('r'), -].into()}); #[derive(Clone,PartialEq,Eq,Debug)] pub enum Opt { @@ -21,7 +15,6 @@ pub enum Opt { impl Opt { pub fn parse(s: &str) -> Vec { - flog!(DEBUG, s); let mut opts = vec![]; if s.starts_with("--") { @@ -32,7 +25,6 @@ impl Opt { opts.push(Self::Short(ch)) } } - flog!(DEBUG,opts); opts } @@ -53,7 +45,6 @@ pub fn get_opts(words: Vec) -> (Vec,Vec) { let mut non_opts = vec![]; while let Some(word) = words_iter.next() { - flog!(DEBUG, opts,non_opts); if &word == "--" { non_opts.extend(words_iter); break diff --git a/src/libsh/error.rs b/src/libsh/error.rs index b6f3505..1e2acfa 100644 --- a/src/libsh/error.rs +++ b/src/libsh/error.rs @@ -20,8 +20,8 @@ impl ShResultExt for Result { return self }; match e { - ShErr::Simple { kind, msg } | - ShErr::Full { kind, msg, span: _ } => Err(ShErr::full(kind, msg, new_span)), + ShErr::Simple { kind, msg, notes } | + ShErr::Full { kind, msg, notes, span: _ } => Err(ShErr::Full { kind: kind.clone(), msg: msg.clone(), notes: notes.clone(), span: new_span }), } } /// Blame a span if no blame has been assigned yet @@ -30,46 +30,97 @@ impl ShResultExt for Result { return self }; match e { - ShErr::Simple { kind, msg } => Err(ShErr::full(kind.clone(), msg, new_span)), - ShErr::Full { kind: _, msg: _, span: _ } => self + ShErr::Simple { kind, msg, notes } => Err(ShErr::Full { kind: kind.clone(), msg: msg.clone(), notes: notes.clone(), span: new_span }), + ShErr::Full { kind: _, msg: _, span: _, notes: _ } => self } } } +#[derive(Clone,Debug)] +pub struct Note { + main: String, + sub_notes: Vec, + depth: usize +} + +impl Note { + pub fn new(main: impl Into) -> Self { + Self { main: main.into(), sub_notes: vec![], depth: 0 } + } + + pub fn with_sub_notes(self, new_sub_notes: Vec>) -> Self { + let Self { main, mut sub_notes, depth } = self; + for raw_note in new_sub_notes { + let mut note = Note::new(raw_note); + note.depth = self.depth + 1; + sub_notes.push(note); + } + Self { main, sub_notes, depth } + } +} + +impl Display for Note { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let note = "note".styled(Style::Green); + let main = &self.main; + if self.depth == 0 { + writeln!(f, "{note}: {main}")?; + } else { + let bar_break = "-".styled(Style::Cyan | Style::Bold); + let indent = " ".repeat(self.depth); + writeln!(f, " {indent}{bar_break} {main}")?; + } + + for sub_note in &self.sub_notes { + write!(f, "{sub_note}")?; + } + Ok(()) + } +} + #[derive(Debug)] pub enum ShErr { - Simple { kind: ShErrKind, msg: String }, - Full { kind: ShErrKind, msg: String, span: Span } + Simple { kind: ShErrKind, msg: String, notes: Vec }, + Full { kind: ShErrKind, msg: String, notes: Vec, span: Span } } impl ShErr { pub fn simple(kind: ShErrKind, msg: impl Into) -> Self { let msg = msg.into(); - Self::Simple { kind, msg } + Self::Simple { kind, msg, notes: vec![] } } pub fn full(kind: ShErrKind, msg: impl Into, span: Span) -> Self { let msg = msg.into(); - Self::Full { kind, msg, span } + Self::Full { kind, msg, span, notes: vec![] } } - pub fn unpack(self) -> (ShErrKind,String,Option) { + pub fn unpack(self) -> (ShErrKind,String,Vec,Option) { match self { - ShErr::Simple { kind, msg } => (kind,msg,None), - ShErr::Full { kind, msg, span } => (kind,msg,Some(span)) + ShErr::Simple { kind, msg, notes } => (kind,msg,notes,None), + ShErr::Full { kind, msg, notes, span } => (kind,msg,notes,Some(span)) + } + } + pub fn with_note(self, note: Note) -> Self { + let (kind,msg,mut notes,span) = self.unpack(); + notes.push(note); + if let Some(span) = span { + Self::Full { kind, msg, notes, span } + } else { + Self::Simple { kind, msg, notes } } } pub fn with_span(sherr: ShErr, span: Span) -> Self { - let (kind,msg,_) = sherr.unpack(); + let (kind,msg,notes,_) = sherr.unpack(); let span = span.into(); - Self::Full { kind, msg, span } + Self::Full { kind, msg, notes, span } } pub fn kind(&self) -> &ShErrKind { match self { - ShErr::Simple { kind, msg: _ } | - ShErr::Full { kind, msg: _, span: _ } => kind + ShErr::Simple { kind, msg: _, notes: _ } | + ShErr::Full { kind, msg: _, notes: _, span: _ } => kind } } pub fn get_window(&self) -> Vec<(usize,String)> { - let ShErr::Full { kind: _, msg: _, span } = self else { + let ShErr::Full { kind: _, msg: _, notes: _, span } = self else { unreachable!() }; let mut total_len: usize = 0; @@ -111,7 +162,7 @@ impl ShErr { lines } pub fn get_line_col(&self) -> (usize,usize) { - let ShErr::Full { kind: _, msg: _, span } = self else { + let ShErr::Full { kind: _, msg: _, notes: _, span } = self else { unreachable!() }; @@ -137,8 +188,21 @@ impl ShErr { impl Display for ShErr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg), - Self::Full { msg, kind, span: _ } => { + Self::Simple { msg, kind: _, notes } => { + let mut all_strings = vec![msg.to_string()]; + let mut notes_fmt = vec![]; + for note in notes { + let fmt = format!("{note}"); + notes_fmt.push(fmt); + } + all_strings.append(&mut notes_fmt); + let mut output = all_strings.join("\n"); + output.push('\n'); + + writeln!(f, "{}", output) + } + + Self::Full { msg, kind, notes, span: _ } => { let window = self.get_window(); let mut lineno_pad_count = 0; for (lineno,_) in window.clone() { @@ -174,7 +238,15 @@ impl Display for ShErr { let bar_break = "-".styled(Style::Cyan | Style::Bold); writeln!(f, "{padding}{bar_break} {msg}", - ) + )?; + + for note in notes { + + writeln!(f, + "{padding}{bar_break} {note}" + )?; + } + Ok(()) } } } diff --git a/src/libsh/sys.rs b/src/libsh/sys.rs index 1552bcd..ae0fe06 100644 --- a/src/libsh/sys.rs +++ b/src/libsh/sys.rs @@ -6,7 +6,7 @@ pub fn sh_quit(code: i32) -> ! { job.killpg(Signal::SIGTERM).ok(); } }); - if let Some(termios) = crate::get_saved_termios() { + if let Some(termios) = unsafe { crate::get_saved_termios() } { termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap(); } if code == 0 { diff --git a/src/parse/execute.rs b/src/parse/execute.rs index d657aa7..3cea2c2 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; -use crate::{builtin::{alias::alias, cd::cd, echo::echo, export::export, flowctl::flowctl, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, jobs::{dispatch_job, ChildProc, JobBldr, JobStack}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars, ShFunc, VarTab}}; +use crate::{builtin::{alias::alias, cd::cd, echo::echo, export::export, flowctl::flowctl, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source, zoltraak::zoltraak}, jobs::{dispatch_job, ChildProc, JobBldr, JobStack}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars, ShFunc, VarTab}}; use super::{lex::{Span, Tk, TkFlags, KEYWORDS}, AssignKind, CaseNode, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParsedSrc, Redir, RedirType}; @@ -363,6 +363,7 @@ impl Dispatcher { "break" => flowctl(cmd, ShErrKind::LoopBreak(0)), "continue" => flowctl(cmd, ShErrKind::LoopContinue(0)), "exit" => flowctl(cmd, ShErrKind::CleanExit(0)), + "zoltraak" => zoltraak(cmd, io_stack_mut, curr_job_mut), _ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str()) }; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 7670228..3e911e5 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -4,7 +4,7 @@ use bitflags::bitflags; use fmt::Display; use lex::{LexFlags, LexStream, Span, Tk, TkFlags, TkRule}; -use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::IoMode}; +use crate::{libsh::{error::{Note, ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::IoMode}; pub mod lex; pub mod execute; @@ -474,7 +474,7 @@ impl ParseStream { return Err(parse_err_full( "Expected a brace group after function name", &node_tks.get_span().unwrap() - ) + ) ) }; body = Box::new(brc_grp); @@ -511,7 +511,7 @@ impl ParseStream { return Err(parse_err_full( "Expected a closing brace for this brace group", &node_tks.get_span().unwrap() - ) + ) ) } } @@ -545,7 +545,8 @@ impl ParseStream { return Err(parse_err_full( "Error opening file for redirection", &path_tk.span - )); + ) + ); }; let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file); @@ -578,7 +579,17 @@ impl ParseStream { node_tks.push(self.next_tk().unwrap()); let Some(pat_tk) = self.next_tk() else { - return Err(parse_err_full("Expected a pattern after 'case'", &node_tks.get_span().unwrap())); + return Err( + parse_err_full( + "Expected a pattern after 'case' keyword", &node_tks.get_span().unwrap() + ) + .with_note( + Note::new("Patterns can be raw text, or anything that gets substituted with raw text") + .with_sub_notes(vec![ + "This includes variables like '$foo' or command substitutions like '$(echo foo)'" + ]) + ) + ); }; pattern = pat_tk; diff --git a/src/prompt/readline.rs b/src/prompt/readline.rs index dc72e63..b92bdbe 100644 --- a/src/prompt/readline.rs +++ b/src/prompt/readline.rs @@ -66,6 +66,7 @@ impl Highlighter for FernReadline { impl Validator for FernReadline { fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result { + return Ok(ValidationResult::Valid(None)); let mut tokens = vec![]; let tk_stream = LexStream::new(Arc::new(ctx.input().to_string()), LexFlags::empty()); for tk in tk_stream { diff --git a/src/shopt.rs b/src/shopt.rs new file mode 100644 index 0000000..10f0e68 --- /dev/null +++ b/src/shopt.rs @@ -0,0 +1,126 @@ +use std::str::FromStr; + +use rustyline::EditMode; + +use crate::{libsh::error::{ShErr, ShErrKind, ShResult}, prelude::*, state::LogTab}; + +#[derive(Clone, Debug)] +pub enum BellStyle { + Audible, + Visible, + Disable, +} + + +impl FromStr for BellStyle { + type Err = ShErr; + fn from_str(s: &str) -> Result { + match s.to_ascii_uppercase().as_str() { + "audible" => Ok(Self::Audible), + "visible" => Ok(Self::Visible), + "disable" => Ok(Self::Disable), + _ => return Err( + ShErr::simple( + ShErrKind::SyntaxErr, + format!("Invalid bell style '{s}'") + ) + ) + } + } +} + +#[derive(Clone, Debug)] +pub enum FernEditMode { + Vi, + Emacs +} + +impl Into for FernEditMode { + fn into(self) -> EditMode { + match self { + Self::Vi => EditMode::Vi, + Self::Emacs => EditMode::Emacs + } + } +} + +impl FromStr for FernEditMode { + type Err = ShErr; + fn from_str(s: &str) -> Result { + match s.to_ascii_lowercase().as_str() { + "vi" => Ok(Self::Vi), + "emacs" => Ok(Self::Emacs), + _ => return Err( + ShErr::simple( + ShErrKind::SyntaxErr, + format!("Invalid edit mode '{s}'") + ) + ) + } + } +} + +#[derive(Clone, Debug)] +pub struct ShOpts { + core: ShOptCore, + prompt: ShOptPrompt +} + +impl Default for ShOpts { + fn default() -> Self { + let core = ShOptCore { + dotglob: false, + autocd: false, + hist_ignore_dupes: true, + max_hist: 1000, + int_comments: true, + auto_hist: true, + bell_style: BellStyle::Audible, + max_recurse_depth: 1000, + }; + + let prompt = ShOptPrompt { + trunc_prompt_path: 3, + edit_mode: FernEditMode::Vi, + comp_limit: 100, + prompt_highlight: true, + tab_stop: 4, + custom: LogTab::new() + }; + + Self { core, prompt } + } +} + +impl ShOpts { + pub fn get(query: &str) -> ShResult { + todo!(); + // TODO: handle escapes? + let mut query = query.split('.'); + //let Some(key) = query.next() else { + + //}; + } +} + +#[derive(Clone, Debug)] +pub struct ShOptCore { + pub dotglob: bool, + pub autocd: bool, + pub hist_ignore_dupes: bool, + pub max_hist: usize, + pub int_comments: bool, + pub auto_hist: bool, + pub bell_style: BellStyle, + pub max_recurse_depth: usize, +} + +#[derive(Clone, Debug)] +pub struct ShOptPrompt { + pub trunc_prompt_path: usize, + pub edit_mode: FernEditMode, + pub comp_limit: usize, + pub prompt_highlight: bool, + pub tab_stop: usize, + pub custom: LogTab // Contains functions for prompt modules +} diff --git a/src/state.rs b/src/state.rs index ed34898..3b2688b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,7 +2,7 @@ use std::{collections::{HashMap, VecDeque}, ops::Deref, sync::{LazyLock, RwLock, use nix::unistd::{gethostname, getppid, User}; -use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt}, parse::{lex::get_char, ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*}; +use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt}, parse::{lex::get_char, ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*, shopt::ShOpts}; pub static JOB_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(JobTab::new())); @@ -12,6 +12,8 @@ pub static META_TABLE: LazyLock> = LazyLock::new(|| RwLock::new( pub static LOGIC_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(LogTab::new())); +pub static SHOPTS: LazyLock> = LazyLock::new(|| RwLock::new(ShOpts::default())); + /// A shell function /// /// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers to @@ -49,6 +51,7 @@ impl Deref for ShFunc { /// The logic table for the shell /// /// Contains aliases and functions +#[derive(Clone,Debug)] pub struct LogTab { functions: HashMap, aliases: HashMap