Implemented a new builtin and improved error handling
This commit is contained in:
@@ -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<OptSet> = LazyLock::new(|| {[
|
||||
Opt::Short('n'),
|
||||
Opt::Short('E'),
|
||||
Opt::Short('e'),
|
||||
Opt::Short('r'),
|
||||
].into()});
|
||||
|
||||
bitflags! {
|
||||
pub struct EchoFlags: u32 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
115
src/builtin/zoltraak.rs
Normal file
115
src/builtin/zoltraak.rs
Normal file
@@ -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<OptSet> = 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(())
|
||||
}
|
||||
55
src/fern.rs
55
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<Option<Termios>> = 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<Option<Termios>> = None;
|
||||
|
||||
pub fn save_termios() {
|
||||
unsafe {
|
||||
@@ -37,15 +55,15 @@ pub fn save_termios() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(static_mut_refs)]
|
||||
pub fn get_saved_termios() -> Option<Termios> {
|
||||
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<Termios> {
|
||||
// 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);
|
||||
}
|
||||
|
||||
0
src/foo/bar
Normal file
0
src/foo/bar
Normal file
@@ -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<OptSet> = 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<Self> {
|
||||
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<String>) -> (Vec<String>,Vec<Opt>) {
|
||||
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
|
||||
|
||||
@@ -20,8 +20,8 @@ impl<T> ShResultExt for Result<T,ShErr> {
|
||||
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<T> ShResultExt for Result<T,ShErr> {
|
||||
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<Note>,
|
||||
depth: usize
|
||||
}
|
||||
|
||||
impl Note {
|
||||
pub fn new(main: impl Into<String>) -> Self {
|
||||
Self { main: main.into(), sub_notes: vec![], depth: 0 }
|
||||
}
|
||||
|
||||
pub fn with_sub_notes(self, new_sub_notes: Vec<impl Into<String>>) -> 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<Note> },
|
||||
Full { kind: ShErrKind, msg: String, notes: Vec<Note>, span: Span }
|
||||
}
|
||||
|
||||
impl ShErr {
|
||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||
let msg = msg.into();
|
||||
Self::Simple { kind, msg }
|
||||
Self::Simple { kind, msg, notes: vec![] }
|
||||
}
|
||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, 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<Span>) {
|
||||
pub fn unpack(self) -> (ShErrKind,String,Vec<Note>,Option<Span>) {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -66,6 +66,7 @@ impl Highlighter for FernReadline {
|
||||
|
||||
impl Validator for FernReadline {
|
||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
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 {
|
||||
|
||||
126
src/shopt.rs
Normal file
126
src/shopt.rs
Normal file
@@ -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<Self, Self::Err> {
|
||||
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<EditMode> 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<Self, Self::Err> {
|
||||
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<String> {
|
||||
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
|
||||
}
|
||||
@@ -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<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
||||
|
||||
@@ -12,6 +12,8 @@ pub static META_TABLE: LazyLock<RwLock<MetaTab>> = LazyLock::new(|| RwLock::new(
|
||||
|
||||
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
|
||||
|
||||
pub static SHOPTS: LazyLock<RwLock<ShOpts>> = 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<String,ShFunc>,
|
||||
aliases: HashMap<String,String>
|
||||
|
||||
Reference in New Issue
Block a user