Improved error reporting and fully implemented the shopt command
This commit is contained in:
@@ -12,8 +12,9 @@ pub mod jobctl;
|
|||||||
pub mod alias;
|
pub mod alias;
|
||||||
pub mod flowctl;
|
pub mod flowctl;
|
||||||
pub mod zoltraak;
|
pub mod zoltraak;
|
||||||
|
pub mod shopt;
|
||||||
|
|
||||||
pub const BUILTINS: [&str;15] = [
|
pub const BUILTINS: [&str;16] = [
|
||||||
"echo",
|
"echo",
|
||||||
"cd",
|
"cd",
|
||||||
"export",
|
"export",
|
||||||
@@ -28,7 +29,8 @@ pub const BUILTINS: [&str;15] = [
|
|||||||
"break",
|
"break",
|
||||||
"continue",
|
"continue",
|
||||||
"exit",
|
"exit",
|
||||||
"zoltraak"
|
"zoltraak",
|
||||||
|
"shopt"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sets up a builtin command
|
/// Sets up a builtin command
|
||||||
|
|||||||
30
src/builtin/shopt.rs
Normal file
30
src/builtin/shopt.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::{jobs::JobBldr, libsh::error::{ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::write_shopts};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
|
pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
|
|
||||||
|
let mut io_frame = io_frame.unwrap();
|
||||||
|
io_frame.redirect()?;
|
||||||
|
for (arg,span) in argv {
|
||||||
|
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
|
||||||
|
let output_channel = borrow_fd(STDOUT_FILENO);
|
||||||
|
output.push('\n');
|
||||||
|
|
||||||
|
if let Err(e) = write(output_channel, output.as_bytes()) {
|
||||||
|
io_frame.restore()?;
|
||||||
|
return Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io_frame.restore()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -61,6 +61,7 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
match flag {
|
match flag {
|
||||||
'r' => flags |= ZoltFlags::RECURSIVE,
|
'r' => flags |= ZoltFlags::RECURSIVE,
|
||||||
'f' => flags |= ZoltFlags::FORCE,
|
'f' => flags |= ZoltFlags::FORCE,
|
||||||
|
'v' => flags |= ZoltFlags::VERBOSE,
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +142,7 @@ fn annihilate(path: &str, flags: ZoltFlags) -> ShResult<()> {
|
|||||||
fs::remove_file(path)?;
|
fs::remove_file(path)?;
|
||||||
if is_verbose {
|
if is_verbose {
|
||||||
let stderr = borrow_fd(STDERR_FILENO);
|
let stderr = borrow_fd(STDERR_FILENO);
|
||||||
write(stderr, format!("removed file '{path}'").as_bytes())?;
|
write(stderr, format!("shredded file '{path}'\n").as_bytes())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if path_buf.is_dir() {
|
} else if path_buf.is_dir() {
|
||||||
@@ -183,7 +184,7 @@ fn annihilate_recursive(dir: &str, flags: ZoltFlags) -> ShResult<()> {
|
|||||||
fs::remove_dir(dir)?;
|
fs::remove_dir(dir)?;
|
||||||
if is_verbose {
|
if is_verbose {
|
||||||
let stderr = borrow_fd(STDERR_FILENO);
|
let stderr = borrow_fd(STDERR_FILENO);
|
||||||
write(stderr, format!("removed directory '{dir}'").as_bytes())?;
|
write(stderr, format!("shredded directory '{dir}'\n").as_bytes())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{exec_input, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Span, Tk, TkFlags, TkRule}, Redir, RedirType}, prelude::*, procio::{IoBuf, IoFrame, IoMode}, state::{read_logic, read_vars, write_meta, LogTab}};
|
use crate::{exec_input, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Span, Tk, TkFlags, TkRule}, Redir, RedirType}, prelude::*, procio::{IoBuf, IoFrame, IoMode}, state::{read_vars, write_meta, LogTab}};
|
||||||
|
|
||||||
/// Variable substitution marker
|
/// Variable substitution marker
|
||||||
pub const VAR_SUB: char = '\u{fdd0}';
|
pub const VAR_SUB: char = '\u{fdd0}';
|
||||||
|
|||||||
@@ -77,7 +77,12 @@ pub fn exec_input(input: String) -> ShResult<()> {
|
|||||||
let log_tab = read_logic(|l| l.clone());
|
let log_tab = read_logic(|l| l.clone());
|
||||||
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
||||||
let mut parser = ParsedSrc::new(Arc::new(input));
|
let mut parser = ParsedSrc::new(Arc::new(input));
|
||||||
parser.parse_src()?;
|
if let Err(errors) = parser.parse_src() {
|
||||||
|
for error in errors {
|
||||||
|
eprintln!("{error}");
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
let mut dispatcher = Dispatcher::new(parser.extract_nodes());
|
let mut dispatcher = Dispatcher::new(parser.extract_nodes());
|
||||||
dispatcher.begin_dispatch()
|
dispatcher.begin_dispatch()
|
||||||
|
|||||||
@@ -135,8 +135,6 @@ impl ShErr {
|
|||||||
total_len += ch.len_utf8();
|
total_len += ch.len_utf8();
|
||||||
cur_line.push(ch);
|
cur_line.push(ch);
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
total_lines += 1;
|
|
||||||
|
|
||||||
if total_len > span.start {
|
if total_len > span.start {
|
||||||
let line = (
|
let line = (
|
||||||
total_lines,
|
total_lines,
|
||||||
@@ -147,6 +145,8 @@ impl ShErr {
|
|||||||
if total_len >= span.end {
|
if total_len >= span.end {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
total_lines += 1;
|
||||||
|
|
||||||
cur_line.clear();
|
cur_line.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,6 +183,23 @@ impl ShErr {
|
|||||||
}
|
}
|
||||||
(lineno,colno)
|
(lineno,colno)
|
||||||
}
|
}
|
||||||
|
pub fn get_indicator_lines(&self) -> Option<Vec<String>> {
|
||||||
|
match self {
|
||||||
|
ShErr::Simple { kind: _, msg: _, notes: _ } => None,
|
||||||
|
ShErr::Full { kind: _, msg: _, notes: _, span } => {
|
||||||
|
let text = span.as_str();
|
||||||
|
let lines = text.lines();
|
||||||
|
let mut indicator_lines = vec![];
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let indicator_line = "^".repeat(line.len()).styled(Style::Red | Style::Bold);
|
||||||
|
indicator_lines.push(indicator_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(indicator_lines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErr {
|
impl Display for ShErr {
|
||||||
@@ -204,26 +221,34 @@ impl Display for ShErr {
|
|||||||
|
|
||||||
Self::Full { msg, kind, notes, span: _ } => {
|
Self::Full { msg, kind, notes, span: _ } => {
|
||||||
let window = self.get_window();
|
let window = self.get_window();
|
||||||
|
let mut indicator_lines = self.get_indicator_lines().unwrap().into_iter();
|
||||||
let mut lineno_pad_count = 0;
|
let mut lineno_pad_count = 0;
|
||||||
for (lineno,_) in window.clone() {
|
for (lineno,_) in window.clone() {
|
||||||
if lineno.to_string().len() > lineno_pad_count {
|
if lineno.to_string().len() > lineno_pad_count {
|
||||||
lineno_pad_count = lineno.to_string().len() + 1
|
lineno_pad_count = lineno.to_string().len() + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (line,col) = self.get_line_col();
|
|
||||||
let line = line.styled(Style::Cyan | Style::Bold);
|
|
||||||
let col = col.styled(Style::Cyan | Style::Bold);
|
|
||||||
let kind = kind.styled(Style::Red | Style::Bold);
|
|
||||||
let padding = " ".repeat(lineno_pad_count);
|
let padding = " ".repeat(lineno_pad_count);
|
||||||
|
writeln!(f)?;
|
||||||
|
|
||||||
|
|
||||||
|
let (line,col) = self.get_line_col();
|
||||||
|
let line_fmt = line.styled(Style::Cyan | Style::Bold);
|
||||||
|
let col_fmt = col.styled(Style::Cyan | Style::Bold);
|
||||||
|
let kind = kind.styled(Style::Red | Style::Bold);
|
||||||
let arrow = "->".styled(Style::Cyan | Style::Bold);
|
let arrow = "->".styled(Style::Cyan | Style::Bold);
|
||||||
writeln!(f,
|
writeln!(f,
|
||||||
"{padding}{arrow} [{line};{col}] - {kind}",
|
"{kind} - {msg}",
|
||||||
|
)?;
|
||||||
|
writeln!(f,
|
||||||
|
"{padding}{arrow} [{line_fmt};{col_fmt}]",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut bar = format!("{padding}|");
|
let mut bar = format!("{padding}|");
|
||||||
bar = bar.styled(Style::Cyan | Style::Bold);
|
bar = bar.styled(Style::Cyan | Style::Bold);
|
||||||
writeln!(f,"{bar}")?;
|
writeln!(f,"{bar}")?;
|
||||||
|
|
||||||
|
let mut first_ind_ln = true;
|
||||||
for (lineno,line) in window {
|
for (lineno,line) in window {
|
||||||
let lineno = lineno.to_string();
|
let lineno = lineno.to_string();
|
||||||
let line = line.trim();
|
let line = line.trim();
|
||||||
@@ -231,18 +256,29 @@ impl Display for ShErr {
|
|||||||
prefix.replace_range(0..lineno.len(), &lineno);
|
prefix.replace_range(0..lineno.len(), &lineno);
|
||||||
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
||||||
writeln!(f,"{prefix} {line}")?;
|
writeln!(f,"{prefix} {line}")?;
|
||||||
|
|
||||||
|
if let Some(ind_ln) = indicator_lines.next() {
|
||||||
|
if first_ind_ln {
|
||||||
|
let ind_ln_padding = " ".repeat(col);
|
||||||
|
let ind_ln = format!("{ind_ln_padding}{ind_ln}");
|
||||||
|
writeln!(f, "{bar}{ind_ln}")?;
|
||||||
|
first_ind_ln = false;
|
||||||
|
} else {
|
||||||
|
writeln!(f, "{bar} {ind_ln}")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(f,"{bar}")?;
|
write!(f,"{bar}")?;
|
||||||
|
|
||||||
|
|
||||||
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
||||||
writeln!(f,
|
if !notes.is_empty() {
|
||||||
"{padding}{bar_break} {msg}",
|
writeln!(f)?;
|
||||||
)?;
|
}
|
||||||
|
|
||||||
for note in notes {
|
for note in notes {
|
||||||
|
|
||||||
writeln!(f,
|
write!(f,
|
||||||
"{padding}{bar_break} {note}"
|
"{padding}{bar_break} {note}"
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::VecDeque;
|
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, 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 crate::{builtin::{alias::alias, cd::cd, echo::echo, export::export, flowctl::flowctl, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, shopt::shopt, 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};
|
use super::{lex::{Span, Tk, TkFlags, KEYWORDS}, AssignKind, CaseNode, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParsedSrc, Redir, RedirType};
|
||||||
|
|
||||||
@@ -121,7 +121,12 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut func_parser = ParsedSrc::new(Arc::new(body));
|
let mut func_parser = ParsedSrc::new(Arc::new(body));
|
||||||
func_parser.parse_src()?; // Parse the function
|
if let Err(errors) = func_parser.parse_src() {
|
||||||
|
for error in errors {
|
||||||
|
eprintln!("{error}");
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
let func = ShFunc::new(func_parser);
|
let func = ShFunc::new(func_parser);
|
||||||
write_logic(|l| l.insert_func(name, func)); // Store the AST
|
write_logic(|l| l.insert_func(name, func)); // Store the AST
|
||||||
@@ -364,6 +369,7 @@ impl Dispatcher {
|
|||||||
"continue" => flowctl(cmd, ShErrKind::LoopContinue(0)),
|
"continue" => flowctl(cmd, ShErrKind::LoopContinue(0)),
|
||||||
"exit" => flowctl(cmd, ShErrKind::CleanExit(0)),
|
"exit" => flowctl(cmd, ShErrKind::CleanExit(0)),
|
||||||
"zoltraak" => zoltraak(cmd, io_stack_mut, curr_job_mut),
|
"zoltraak" => zoltraak(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"shopt" => shopt(cmd, io_stack_mut, curr_job_mut),
|
||||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
101
src/parse/mod.rs
101
src/parse/mod.rs
@@ -37,16 +37,34 @@ impl ParsedSrc {
|
|||||||
pub fn new(src: Arc<String>) -> Self {
|
pub fn new(src: Arc<String>) -> Self {
|
||||||
Self { src, ast: Ast::new(vec![]) }
|
Self { src, ast: Ast::new(vec![]) }
|
||||||
}
|
}
|
||||||
pub fn parse_src(&mut self) -> ShResult<()> {
|
pub fn parse_src(&mut self) -> Result<(),Vec<ShErr>> {
|
||||||
let mut tokens = vec![];
|
let mut tokens = vec![];
|
||||||
for token in LexStream::new(self.src.clone(), LexFlags::empty()) {
|
let mut errors = vec![];
|
||||||
tokens.push(token?);
|
for lex_result in LexStream::new(self.src.clone(), LexFlags::empty()) {
|
||||||
|
match lex_result {
|
||||||
|
Ok(token) => tokens.push(token),
|
||||||
|
Err(error) => errors.push(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return Err(errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for result in ParseStream::new(tokens) {
|
for parse_result in ParseStream::new(tokens) {
|
||||||
nodes.push(result?);
|
flog!(DEBUG, parse_result);
|
||||||
|
match parse_result {
|
||||||
|
Ok(node) => nodes.push(node),
|
||||||
|
Err(error) => errors.push(error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
flog!(DEBUG, errors);
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return Err(errors)
|
||||||
|
}
|
||||||
|
|
||||||
*self.ast.tree_mut() = nodes;
|
*self.ast.tree_mut() = nodes;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -311,19 +329,11 @@ pub enum NdRule {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ParseStream {
|
pub struct ParseStream {
|
||||||
pub tokens: Vec<Tk>,
|
pub tokens: Vec<Tk>,
|
||||||
pub flags: ParseFlags
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ParseFlags: u32 {
|
|
||||||
const ERROR = 0b0000001;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseStream {
|
impl ParseStream {
|
||||||
pub fn new(tokens: Vec<Tk>) -> Self {
|
pub fn new(tokens: Vec<Tk>) -> Self {
|
||||||
Self { tokens, flags: ParseFlags::empty() }
|
Self { tokens }
|
||||||
}
|
}
|
||||||
fn next_tk_class(&self) -> &TkRule {
|
fn next_tk_class(&self) -> &TkRule {
|
||||||
if let Some(tk) = self.tokens.first() {
|
if let Some(tk) = self.tokens.first() {
|
||||||
@@ -444,7 +454,7 @@ impl ParseStream {
|
|||||||
/// This tries to match on different stuff that can appear in a command position
|
/// This tries to match on different stuff that can appear in a command position
|
||||||
/// Matches shell commands like if-then-fi, pipelines, etc.
|
/// Matches shell commands like if-then-fi, pipelines, etc.
|
||||||
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
|
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
|
||||||
/// The check_pipelines parameter is used to prevent left-recursion issues in self.parse_pipeline()
|
/// The check_pipelines parameter is used to prevent left-recursion issues in self.parse_pipeln()
|
||||||
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
|
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
|
||||||
try_match!(self.parse_func_def()?);
|
try_match!(self.parse_func_def()?);
|
||||||
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
|
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
|
||||||
@@ -452,7 +462,7 @@ impl ParseStream {
|
|||||||
try_match!(self.parse_loop()?);
|
try_match!(self.parse_loop()?);
|
||||||
try_match!(self.parse_if()?);
|
try_match!(self.parse_if()?);
|
||||||
if check_pipelines {
|
if check_pipelines {
|
||||||
try_match!(self.parse_pipeline()?);
|
try_match!(self.parse_pipeln()?);
|
||||||
} else {
|
} else {
|
||||||
try_match!(self.parse_cmd()?);
|
try_match!(self.parse_cmd()?);
|
||||||
}
|
}
|
||||||
@@ -488,6 +498,14 @@ impl ParseStream {
|
|||||||
|
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
|
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
||||||
|
while let Some(tk) = self.next_tk() {
|
||||||
|
node_tks.push(tk.clone());
|
||||||
|
if tk.class == TkRule::Sep {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node>> {
|
fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node>> {
|
||||||
let mut node_tks: Vec<Tk> = vec![];
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
let mut body: Vec<Node> = vec![];
|
let mut body: Vec<Node> = vec![];
|
||||||
@@ -508,6 +526,7 @@ impl ParseStream {
|
|||||||
body.push(node);
|
body.push(node);
|
||||||
}
|
}
|
||||||
if !self.next_tk_is_some() {
|
if !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected a closing brace for this brace group",
|
"Expected a closing brace for this brace group",
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -525,7 +544,6 @@ impl ParseStream {
|
|||||||
let path_tk = self.next_tk();
|
let path_tk = self.next_tk();
|
||||||
|
|
||||||
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
self.flags |= ParseFlags::ERROR;
|
|
||||||
return Err(
|
return Err(
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
@@ -541,7 +559,7 @@ impl ParseStream {
|
|||||||
let pathbuf = PathBuf::from(path_tk.span.as_str());
|
let pathbuf = PathBuf::from(path_tk.span.as_str());
|
||||||
|
|
||||||
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
||||||
self.flags |= ParseFlags::ERROR;
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Error opening file for redirection",
|
"Error opening file for redirection",
|
||||||
&path_tk.span
|
&path_tk.span
|
||||||
@@ -579,6 +597,7 @@ impl ParseStream {
|
|||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
let Some(pat_tk) = self.next_tk() else {
|
let Some(pat_tk) = self.next_tk() else {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(
|
return Err(
|
||||||
parse_err_full(
|
parse_err_full(
|
||||||
"Expected a pattern after 'case' keyword", &node_tks.get_span().unwrap()
|
"Expected a pattern after 'case' keyword", &node_tks.get_span().unwrap()
|
||||||
@@ -596,6 +615,7 @@ impl ParseStream {
|
|||||||
node_tks.push(pattern.clone());
|
node_tks.push(pattern.clone());
|
||||||
|
|
||||||
if !self.check_keyword("in") || !self.next_tk_is_some() {
|
if !self.check_keyword("in") || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full("Expected 'in' after case variable name", &node_tks.get_span().unwrap()));
|
return Err(parse_err_full("Expected 'in' after case variable name", &node_tks.get_span().unwrap()));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
@@ -604,6 +624,7 @@ impl ParseStream {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !self.check_case_pattern() || !self.next_tk_is_some() {
|
if !self.check_case_pattern() || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full("Expected a case pattern here", &node_tks.get_span().unwrap()));
|
return Err(parse_err_full("Expected a case pattern here", &node_tks.get_span().unwrap()));
|
||||||
}
|
}
|
||||||
let case_pat_tk = self.next_tk().unwrap();
|
let case_pat_tk = self.next_tk().unwrap();
|
||||||
@@ -632,6 +653,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.next_tk_is_some() {
|
if !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full("Expected 'esac' after case block", &node_tks.get_span().unwrap()));
|
return Err(parse_err_full("Expected 'esac' after case block", &node_tks.get_span().unwrap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -666,6 +688,7 @@ impl ParseStream {
|
|||||||
"elif"
|
"elif"
|
||||||
};
|
};
|
||||||
let Some(cond) = self.parse_block(true)? else {
|
let Some(cond) = self.parse_block(true)? else {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
&format!("Expected an expression after '{prefix_keywrd}'"),
|
&format!("Expected an expression after '{prefix_keywrd}'"),
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -674,6 +697,7 @@ impl ParseStream {
|
|||||||
node_tks.extend(cond.tokens.clone());
|
node_tks.extend(cond.tokens.clone());
|
||||||
|
|
||||||
if !self.check_keyword("then") || !self.next_tk_is_some() {
|
if !self.check_keyword("then") || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
&format!("Expected 'then' after '{prefix_keywrd}' condition"),
|
&format!("Expected 'then' after '{prefix_keywrd}' condition"),
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -688,6 +712,7 @@ impl ParseStream {
|
|||||||
body_blocks.push(body_block);
|
body_blocks.push(body_block);
|
||||||
}
|
}
|
||||||
if body_blocks.is_empty() {
|
if body_blocks.is_empty() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected an expression after 'then'",
|
"Expected an expression after 'then'",
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -711,6 +736,7 @@ impl ParseStream {
|
|||||||
else_block.push(block)
|
else_block.push(block)
|
||||||
}
|
}
|
||||||
if else_block.is_empty() {
|
if else_block.is_empty() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected an expression after 'else'",
|
"Expected an expression after 'else'",
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -719,6 +745,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.check_keyword("fi") || !self.next_tk_is_some() {
|
if !self.check_keyword("fi") || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'fi' after if statement",
|
"Expected 'fi' after if statement",
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -734,7 +761,6 @@ impl ParseStream {
|
|||||||
let path_tk = self.next_tk();
|
let path_tk = self.next_tk();
|
||||||
|
|
||||||
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
self.flags |= ParseFlags::ERROR;
|
|
||||||
return Err(
|
return Err(
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
@@ -750,7 +776,7 @@ impl ParseStream {
|
|||||||
let pathbuf = PathBuf::from(path_tk.span.as_str());
|
let pathbuf = PathBuf::from(path_tk.span.as_str());
|
||||||
|
|
||||||
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
||||||
self.flags |= ParseFlags::ERROR;
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Error opening file for redirection",
|
"Error opening file for redirection",
|
||||||
&path_tk.span
|
&path_tk.span
|
||||||
@@ -792,6 +818,7 @@ impl ParseStream {
|
|||||||
self.catch_separator(&mut node_tks);
|
self.catch_separator(&mut node_tks);
|
||||||
|
|
||||||
let Some(cond) = self.parse_block(true)? else {
|
let Some(cond) = self.parse_block(true)? else {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
&format!("Expected an expression after '{loop_kind}'"), // It also implements Display
|
&format!("Expected an expression after '{loop_kind}'"), // It also implements Display
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -800,6 +827,7 @@ impl ParseStream {
|
|||||||
node_tks.extend(cond.tokens.clone());
|
node_tks.extend(cond.tokens.clone());
|
||||||
|
|
||||||
if !self.check_keyword("do") || !self.next_tk_is_some() {
|
if !self.check_keyword("do") || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'do' after loop condition",
|
"Expected 'do' after loop condition",
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -814,6 +842,7 @@ impl ParseStream {
|
|||||||
body.push(block);
|
body.push(block);
|
||||||
}
|
}
|
||||||
if body.is_empty() {
|
if body.is_empty() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected an expression after 'do'",
|
"Expected an expression after 'do'",
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -821,6 +850,7 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected 'done' after loop body",
|
"Expected 'done' after loop body",
|
||||||
&node_tks.get_span().unwrap()
|
&node_tks.get_span().unwrap()
|
||||||
@@ -838,7 +868,7 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
Ok(Some(loop_node))
|
Ok(Some(loop_node))
|
||||||
}
|
}
|
||||||
fn parse_pipeline(&mut self) -> ShResult<Option<Node>> {
|
fn parse_pipeln(&mut self) -> ShResult<Option<Node>> {
|
||||||
let mut cmds = vec![];
|
let mut cmds = vec![];
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
while let Some(cmd) = self.parse_block(false)? {
|
while let Some(cmd) = self.parse_block(false)? {
|
||||||
@@ -868,7 +898,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn parse_cmd(&mut self) -> ShResult<Option<Node>> {
|
fn parse_cmd(&mut self) -> ShResult<Option<Node>> {
|
||||||
let tk_slice = self.tokens.as_slice();
|
let tk_slice = self.tokens.clone();
|
||||||
let mut tk_iter = tk_slice.iter();
|
let mut tk_iter = tk_slice.iter();
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
let mut redirs = vec![];
|
let mut redirs = vec![];
|
||||||
@@ -876,19 +906,23 @@ impl ParseStream {
|
|||||||
let mut assignments = vec![];
|
let mut assignments = vec![];
|
||||||
|
|
||||||
while let Some(prefix_tk) = tk_iter.next() {
|
while let Some(prefix_tk) = tk_iter.next() {
|
||||||
if prefix_tk.flags.contains(TkFlags::IS_CMD) {
|
let is_cmd = prefix_tk.flags.contains(TkFlags::IS_CMD);
|
||||||
|
let is_assignment = prefix_tk.flags.contains(TkFlags::ASSIGN);
|
||||||
|
let is_keyword = prefix_tk.flags.contains(TkFlags::KEYWORD);
|
||||||
|
|
||||||
|
if is_cmd {
|
||||||
node_tks.push(prefix_tk.clone());
|
node_tks.push(prefix_tk.clone());
|
||||||
argv.push(prefix_tk.clone());
|
argv.push(prefix_tk.clone());
|
||||||
break
|
break
|
||||||
|
|
||||||
} else if prefix_tk.flags.contains(TkFlags::ASSIGN) {
|
} else if is_assignment {
|
||||||
let Some(assign) = self.parse_assignment(&prefix_tk) else {
|
let Some(assign) = self.parse_assignment(&prefix_tk) else {
|
||||||
break
|
break
|
||||||
};
|
};
|
||||||
node_tks.push(prefix_tk.clone());
|
node_tks.push(prefix_tk.clone());
|
||||||
assignments.push(assign)
|
assignments.push(assign)
|
||||||
|
|
||||||
} else if prefix_tk.flags.contains(TkFlags::KEYWORD) {
|
} else if is_keyword {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -921,7 +955,6 @@ impl ParseStream {
|
|||||||
let path_tk = tk_iter.next();
|
let path_tk = tk_iter.next();
|
||||||
|
|
||||||
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
self.flags |= ParseFlags::ERROR;
|
|
||||||
return Err(
|
return Err(
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
@@ -937,7 +970,7 @@ impl ParseStream {
|
|||||||
let pathbuf = PathBuf::from(path_tk.span.as_str());
|
let pathbuf = PathBuf::from(path_tk.span.as_str());
|
||||||
|
|
||||||
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
||||||
self.flags |= ParseFlags::ERROR;
|
self.panic_mode(&mut node_tks);
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Error opening file for redirection",
|
"Error opening file for redirection",
|
||||||
&path_tk.span
|
&path_tk.span
|
||||||
@@ -1068,13 +1101,12 @@ impl ParseStream {
|
|||||||
impl Iterator for ParseStream {
|
impl Iterator for ParseStream {
|
||||||
type Item = ShResult<Node>;
|
type Item = ShResult<Node>;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
flog!(DEBUG, "parsing");
|
||||||
|
flog!(DEBUG, self.tokens);
|
||||||
// Empty token vector or only SOI/EOI tokens, nothing to do
|
// Empty token vector or only SOI/EOI tokens, nothing to do
|
||||||
if self.tokens.is_empty() || self.tokens.len() == 2 {
|
if self.tokens.is_empty() || self.tokens.len() == 2 {
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
if self.flags.contains(ParseFlags::ERROR) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
while let Some(tk) = self.tokens.first() {
|
while let Some(tk) = self.tokens.first() {
|
||||||
if let TkRule::EOI = tk.class {
|
if let TkRule::EOI = tk.class {
|
||||||
return None
|
return None
|
||||||
@@ -1085,12 +1117,17 @@ impl Iterator for ParseStream {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match self.parse_cmd_list() {
|
let result = self.parse_cmd_list();
|
||||||
|
flog!(DEBUG, result);
|
||||||
|
flog!(DEBUG, self.tokens);
|
||||||
|
match result {
|
||||||
Ok(Some(node)) => {
|
Ok(Some(node)) => {
|
||||||
return Some(Ok(node));
|
return Some(Ok(node));
|
||||||
}
|
}
|
||||||
Ok(None) => return None,
|
Ok(None) => return None,
|
||||||
Err(e) => return Some(Err(e))
|
Err(e) => {
|
||||||
|
return Some(Err(e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,40 @@ pub mod readline;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use readline::FernReadline;
|
use readline::FernReadline;
|
||||||
use rustyline::{error::ReadlineError, history::FileHistory, Editor};
|
use rustyline::{error::ReadlineError, history::FileHistory, ColorMode, Config, Editor};
|
||||||
|
|
||||||
use crate::{expand::expand_prompt, libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
|
use crate::{expand::expand_prompt, libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, state::read_shopts};
|
||||||
|
|
||||||
|
/// Initialize the line editor
|
||||||
fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
|
fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
|
||||||
let rl = FernReadline::new();
|
let rl = FernReadline::new();
|
||||||
let mut editor = Editor::new()?;
|
|
||||||
|
let tab_stop = read_shopts(|s| s.prompt.tab_stop);
|
||||||
|
let edit_mode = read_shopts(|s| s.prompt.edit_mode).into();
|
||||||
|
let bell_style = read_shopts(|s| s.core.bell_style).into();
|
||||||
|
let ignore_dups = read_shopts(|s| s.core.hist_ignore_dupes);
|
||||||
|
let comp_limit = read_shopts(|s| s.prompt.comp_limit);
|
||||||
|
let auto_hist = read_shopts(|s| s.core.auto_hist);
|
||||||
|
let max_hist = read_shopts(|s| s.core.max_hist);
|
||||||
|
let color_mode = match read_shopts(|s| s.prompt.prompt_highlight) {
|
||||||
|
true => ColorMode::Enabled,
|
||||||
|
false => ColorMode::Disabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = Config::builder()
|
||||||
|
.tab_stop(tab_stop)
|
||||||
|
.indent_size(1)
|
||||||
|
.edit_mode(edit_mode)
|
||||||
|
.bell_style(bell_style)
|
||||||
|
.color_mode(color_mode)
|
||||||
|
.history_ignore_dups(ignore_dups).unwrap()
|
||||||
|
.completion_prompt_limit(comp_limit)
|
||||||
|
.auto_add_history(auto_hist)
|
||||||
|
.max_history_size(max_hist).unwrap()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let mut editor = Editor::with_config(config).unwrap();
|
||||||
|
|
||||||
editor.set_helper(Some(rl));
|
editor.set_helper(Some(rl));
|
||||||
editor.load_history(&Path::new("/home/pagedmov/.fernhist"))?;
|
editor.load_history(&Path::new("/home/pagedmov/.fernhist"))?;
|
||||||
Ok(editor)
|
Ok(editor)
|
||||||
|
|||||||
566
src/shopt.rs
566
src/shopt.rs
@@ -1,18 +1,18 @@
|
|||||||
use std::str::FromStr;
|
use std::{collections::HashMap, fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use rustyline::EditMode;
|
use rustyline::{config::BellStyle, EditMode};
|
||||||
|
|
||||||
use crate::{libsh::error::{ShErr, ShErrKind, ShResult}, prelude::*, state::LogTab};
|
use crate::{libsh::error::{Note, ShErr, ShErrKind, ShResult}, state::ShFunc};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum BellStyle {
|
pub enum FernBellStyle {
|
||||||
Audible,
|
Audible,
|
||||||
Visible,
|
Visible,
|
||||||
Disable,
|
Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl FromStr for BellStyle {
|
impl FromStr for FernBellStyle {
|
||||||
type Err = ShErr;
|
type Err = ShErr;
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s.to_ascii_uppercase().as_str() {
|
match s.to_ascii_uppercase().as_str() {
|
||||||
@@ -29,7 +29,28 @@ impl FromStr for BellStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
impl Into<BellStyle> for FernBellStyle {
|
||||||
|
fn into(self) -> BellStyle {
|
||||||
|
match self {
|
||||||
|
FernBellStyle::Audible => BellStyle::Audible,
|
||||||
|
FernBellStyle::Visible => BellStyle::Visible,
|
||||||
|
FernBellStyle::Disable => BellStyle::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Display for FernBellStyle {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
FernBellStyle::Audible => write!(f,"audible"),
|
||||||
|
FernBellStyle::Visible => write!(f,"visible"),
|
||||||
|
FernBellStyle::Disable => write!(f,"disable"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum FernEditMode {
|
pub enum FernEditMode {
|
||||||
Vi,
|
Vi,
|
||||||
Emacs
|
Emacs
|
||||||
@@ -60,46 +81,106 @@ impl FromStr for FernEditMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for FernEditMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
FernEditMode::Vi => write!(f,"vi"),
|
||||||
|
FernEditMode::Emacs => write!(f,"emacs"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ShOpts {
|
pub struct ShOpts {
|
||||||
core: ShOptCore,
|
pub core: ShOptCore,
|
||||||
prompt: ShOptPrompt
|
pub prompt: ShOptPrompt
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ShOpts {
|
impl Default for ShOpts {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let core = ShOptCore {
|
let core = ShOptCore::default();
|
||||||
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 {
|
let prompt = ShOptPrompt::default();
|
||||||
trunc_prompt_path: 3,
|
|
||||||
edit_mode: FernEditMode::Vi,
|
|
||||||
comp_limit: 100,
|
|
||||||
prompt_highlight: true,
|
|
||||||
tab_stop: 4,
|
|
||||||
custom: LogTab::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
Self { core, prompt }
|
Self { core, prompt }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShOpts {
|
impl ShOpts {
|
||||||
pub fn get(query: &str) -> ShResult<String> {
|
pub fn query(&mut self, query: &str) -> ShResult<Option<String>> {
|
||||||
todo!();
|
if let Some((opt,new_val)) = query.split_once('=') {
|
||||||
|
self.set(opt,new_val)?;
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
self.get(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, opt: &str, val: &str) -> ShResult<()> {
|
||||||
|
let mut query = opt.split('.');
|
||||||
|
let Some(key) = query.next() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: No option given"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let remainder = query.collect::<Vec<_>>().join(".");
|
||||||
|
|
||||||
|
match key {
|
||||||
|
"core" => self.core.set(&remainder, val)?,
|
||||||
|
"prompt" => self.prompt.set(&remainder, val)?,
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: Expected 'core' or 'prompt' in shopt key"
|
||||||
|
)
|
||||||
|
.with_note(
|
||||||
|
Note::new("'shopt' takes arguments separated by periods to denote namespaces")
|
||||||
|
.with_sub_notes(vec![
|
||||||
|
"Example: 'shopt core.autocd=true'"
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, query: &str) -> ShResult<Option<String>> {
|
||||||
// TODO: handle escapes?
|
// TODO: handle escapes?
|
||||||
let mut query = query.split('.');
|
let mut query = query.split('.');
|
||||||
//let Some(key) = query.next() else {
|
let Some(key) = query.next() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: No option given"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let remainder = query.collect::<Vec<_>>().join(".");
|
||||||
|
|
||||||
//};
|
match key {
|
||||||
|
"core" => self.core.get(&remainder),
|
||||||
|
"prompt" => self.prompt.get(&remainder),
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: Expected 'core' or 'prompt' in shopt key"
|
||||||
|
)
|
||||||
|
.with_note(
|
||||||
|
Note::new("'shopt' takes arguments separated by periods to denote namespaces")
|
||||||
|
.with_sub_notes(vec![
|
||||||
|
"Example: 'shopt core.autocd=true'"
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,12 +190,243 @@ pub struct ShOptCore {
|
|||||||
pub autocd: bool,
|
pub autocd: bool,
|
||||||
pub hist_ignore_dupes: bool,
|
pub hist_ignore_dupes: bool,
|
||||||
pub max_hist: usize,
|
pub max_hist: usize,
|
||||||
pub int_comments: bool,
|
pub interactive_comments: bool,
|
||||||
pub auto_hist: bool,
|
pub auto_hist: bool,
|
||||||
pub bell_style: BellStyle,
|
pub bell_style: FernBellStyle,
|
||||||
pub max_recurse_depth: usize,
|
pub max_recurse_depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ShOptCore {
|
||||||
|
pub fn set(&mut self, opt: &str, val: &str) -> ShResult<()> {
|
||||||
|
match opt {
|
||||||
|
"dotglob" => {
|
||||||
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'true' or 'false' for dotglob value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.dotglob = val;
|
||||||
|
}
|
||||||
|
"autocd" => {
|
||||||
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'true' or 'false' for autocd value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.autocd = val;
|
||||||
|
}
|
||||||
|
"hist_ignore_dupes" => {
|
||||||
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'true' or 'false' for hist_ignore_dupes value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.hist_ignore_dupes = val;
|
||||||
|
}
|
||||||
|
"max_hist" => {
|
||||||
|
let Ok(val) = val.parse::<usize>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected a positive integer for hist_ignore_dupes value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.max_hist = val;
|
||||||
|
}
|
||||||
|
"interactive_comments" => {
|
||||||
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'true' or 'false' for interactive_comments value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.interactive_comments = val;
|
||||||
|
}
|
||||||
|
"auto_hist" => {
|
||||||
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'true' or 'false' for auto_hist value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.auto_hist = val;
|
||||||
|
}
|
||||||
|
"bell_style" => {
|
||||||
|
let Ok(val) = val.parse::<FernBellStyle>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected a bell style for bell_style value"
|
||||||
|
)
|
||||||
|
.with_note(
|
||||||
|
Note::new("bell_style takes these options as values")
|
||||||
|
.with_sub_notes(vec![
|
||||||
|
"audible",
|
||||||
|
"visible",
|
||||||
|
"disable"
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.bell_style = val;
|
||||||
|
}
|
||||||
|
"max_recurse_depth" => {
|
||||||
|
let Ok(val) = val.parse::<usize>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected a positive integer for max_recurse_depth value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.max_recurse_depth = val;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("shopt: Unexpected 'core' option '{opt}'")
|
||||||
|
)
|
||||||
|
.with_note(Note::new("options can be accessed like 'core.option_name'"))
|
||||||
|
.with_note(
|
||||||
|
Note::new("'core' contains the following options")
|
||||||
|
.with_sub_notes(vec![
|
||||||
|
"dotglob",
|
||||||
|
"autocd",
|
||||||
|
"hist_ignore_dupes",
|
||||||
|
"max_hist",
|
||||||
|
"interactive_comments",
|
||||||
|
"auto_hist",
|
||||||
|
"bell_style",
|
||||||
|
"max_recurse_depth",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get(&self, query: &str) -> ShResult<Option<String>> {
|
||||||
|
if query.is_empty() {
|
||||||
|
return Ok(Some(format!("{self}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
match query {
|
||||||
|
"dotglob" => {
|
||||||
|
let mut output = format!("Include hidden files in glob patterns\n");
|
||||||
|
output.push_str(&format!("{}",self.dotglob));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"autocd" => {
|
||||||
|
let mut output = format!("Allow navigation to directories by passing the directory as a command directly\n");
|
||||||
|
output.push_str(&format!("{}",self.autocd));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"hist_ignore_dupes" => {
|
||||||
|
let mut output = format!("Ignore consecutive duplicate command history entries\n");
|
||||||
|
output.push_str(&format!("{}",self.hist_ignore_dupes));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"max_hist" => {
|
||||||
|
let mut output = format!("Maximum number of entries in the command history file (default '.fernhist')\n");
|
||||||
|
output.push_str(&format!("{}",self.max_hist));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"interactive_comments" => {
|
||||||
|
let mut output = format!("Whether or not to allow comments in interactive mode\n");
|
||||||
|
output.push_str(&format!("{}",self.interactive_comments));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"auto_hist" => {
|
||||||
|
let mut output = format!("Whether or not to automatically save commands to the command history file\n");
|
||||||
|
output.push_str(&format!("{}",self.auto_hist));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"bell_style" => {
|
||||||
|
let mut output = format!("What type of bell style to use for the bell character\n");
|
||||||
|
output.push_str(&format!("{}",self.bell_style));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"max_recurse_depth" => {
|
||||||
|
let mut output = format!("Maximum limit of recursive shell function calls\n");
|
||||||
|
output.push_str(&format!("{}",self.max_recurse_depth));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("shopt: Unexpected 'core' option '{query}'")
|
||||||
|
)
|
||||||
|
.with_note(Note::new("options can be accessed like 'core.option_name'"))
|
||||||
|
.with_note(
|
||||||
|
Note::new("'core' contains the following options")
|
||||||
|
.with_sub_notes(vec![
|
||||||
|
"dotglob",
|
||||||
|
"autocd",
|
||||||
|
"hist_ignore_dupes",
|
||||||
|
"max_hist",
|
||||||
|
"interactive_comments",
|
||||||
|
"auto_hist",
|
||||||
|
"bell_style",
|
||||||
|
"max_recurse_depth",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ShOptCore {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut output = vec![];
|
||||||
|
output.push(format!("dotglob = {}",self.dotglob));
|
||||||
|
output.push(format!("autocd = {}",self.autocd));
|
||||||
|
output.push(format!("hist_ignore_dupes = {}",self.hist_ignore_dupes));
|
||||||
|
output.push(format!("max_hist = {}",self.max_hist));
|
||||||
|
output.push(format!("interactive_comments = {}",self.interactive_comments));
|
||||||
|
output.push(format!("auto_hist = {}",self.auto_hist));
|
||||||
|
output.push(format!("bell_style = {}",self.bell_style));
|
||||||
|
output.push(format!("max_recurse_depth = {}",self.max_recurse_depth));
|
||||||
|
|
||||||
|
let final_output = output.join("\n");
|
||||||
|
|
||||||
|
writeln!(f,"{final_output}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ShOptCore {
|
||||||
|
fn default() -> Self {
|
||||||
|
ShOptCore {
|
||||||
|
dotglob: false,
|
||||||
|
autocd: false,
|
||||||
|
hist_ignore_dupes: true,
|
||||||
|
max_hist: 1000,
|
||||||
|
interactive_comments: true,
|
||||||
|
auto_hist: true,
|
||||||
|
bell_style: FernBellStyle::Audible,
|
||||||
|
max_recurse_depth: 1000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ShOptPrompt {
|
pub struct ShOptPrompt {
|
||||||
pub trunc_prompt_path: usize,
|
pub trunc_prompt_path: usize,
|
||||||
@@ -122,5 +434,191 @@ pub struct ShOptPrompt {
|
|||||||
pub comp_limit: usize,
|
pub comp_limit: usize,
|
||||||
pub prompt_highlight: bool,
|
pub prompt_highlight: bool,
|
||||||
pub tab_stop: usize,
|
pub tab_stop: usize,
|
||||||
pub custom: LogTab // Contains functions for prompt modules
|
pub custom: HashMap<String,ShFunc> // Contains functions for prompt modules
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShOptPrompt {
|
||||||
|
pub fn set(&mut self, opt: &str, val: &str) -> ShResult<()> {
|
||||||
|
match opt {
|
||||||
|
"trunc_prompt_path" => {
|
||||||
|
let Ok(val) = val.parse::<usize>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected a positive integer for trunc_prompt_path value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.trunc_prompt_path = val;
|
||||||
|
}
|
||||||
|
"edit_mode" => {
|
||||||
|
let Ok(val) = val.parse::<FernEditMode>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'vi' or 'emacs' for edit_mode value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.edit_mode = val;
|
||||||
|
}
|
||||||
|
"comp_limit" => {
|
||||||
|
let Ok(val) = val.parse::<usize>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected a positive integer for comp_limit value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.comp_limit = val;
|
||||||
|
}
|
||||||
|
"prompt_highlight" => {
|
||||||
|
let Ok(val) = val.parse::<bool>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected 'true' or 'false' for prompt_highlight value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.prompt_highlight = val;
|
||||||
|
}
|
||||||
|
"tab_stop" => {
|
||||||
|
let Ok(val) = val.parse::<usize>() else {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"shopt: expected a positive integer for tab_stop value"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
self.tab_stop = val;
|
||||||
|
}
|
||||||
|
"custom" => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("shopt: Unexpected 'core' option '{opt}'")
|
||||||
|
)
|
||||||
|
.with_note(Note::new("options can be accessed like 'core.option_name'"))
|
||||||
|
.with_note(
|
||||||
|
Note::new("'core' contains the following options")
|
||||||
|
.with_sub_notes(vec![
|
||||||
|
"dotglob",
|
||||||
|
"autocd",
|
||||||
|
"hist_ignore_dupes",
|
||||||
|
"max_hist",
|
||||||
|
"interactive_comments",
|
||||||
|
"auto_hist",
|
||||||
|
"bell_style",
|
||||||
|
"max_recurse_depth",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get(&self, query: &str) -> ShResult<Option<String>> {
|
||||||
|
if query.is_empty() {
|
||||||
|
return Ok(Some(format!("{self}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
match query {
|
||||||
|
"trunc_prompt_path" => {
|
||||||
|
let mut output = format!("Maximum number of path segments used in the '\\W' prompt escape sequence\n");
|
||||||
|
output.push_str(&format!("{}",self.trunc_prompt_path));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"edit_mode" => {
|
||||||
|
let mut output = format!("The style of editor shortcuts used in the line-editing of the prompt\n");
|
||||||
|
output.push_str(&format!("{}",self.edit_mode));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"comp_limit" => {
|
||||||
|
let mut output = format!("Maximum number of completion candidates displayed upon pressing tab\n");
|
||||||
|
output.push_str(&format!("{}",self.comp_limit));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"prompt_highlight" => {
|
||||||
|
let mut output = format!("Whether to enable or disable syntax highlighting on the prompt\n");
|
||||||
|
output.push_str(&format!("{}",self.prompt_highlight));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"tab_stop" => {
|
||||||
|
let mut output = format!("The number of spaces used by the tab character '\\t'\n");
|
||||||
|
output.push_str(&format!("{}",self.tab_stop));
|
||||||
|
Ok(Some(output))
|
||||||
|
}
|
||||||
|
"custom" => {
|
||||||
|
let mut output = format!("A table of custom 'modules' executed as shell functions for prompt scripting\n");
|
||||||
|
output.push_str("Current modules: \n");
|
||||||
|
for key in self.custom.keys() {
|
||||||
|
output.push_str(&format!(" - {key}\n"));
|
||||||
|
}
|
||||||
|
Ok(Some(output.trim().to_string()))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("shopt: Unexpected 'core' option '{query}'")
|
||||||
|
)
|
||||||
|
.with_note(Note::new("options can be accessed like 'core.option_name'"))
|
||||||
|
.with_note(
|
||||||
|
Note::new("'core' contains the following options")
|
||||||
|
.with_sub_notes(vec![
|
||||||
|
"dotglob",
|
||||||
|
"autocd",
|
||||||
|
"hist_ignore_dupes",
|
||||||
|
"max_hist",
|
||||||
|
"interactive_comments",
|
||||||
|
"auto_hist",
|
||||||
|
"bell_style",
|
||||||
|
"max_recurse_depth",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ShOptPrompt {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
output.push(format!("trunc_prompt_path = {}", self.trunc_prompt_path));
|
||||||
|
output.push(format!("edit_mode = {}", self.edit_mode));
|
||||||
|
output.push(format!("comp_limit = {}", self.comp_limit));
|
||||||
|
output.push(format!("prompt_highlight = {}", self.prompt_highlight));
|
||||||
|
output.push(format!("tab_stop = {}", self.tab_stop));
|
||||||
|
output.push(format!("prompt modules: "));
|
||||||
|
for key in self.custom.keys() {
|
||||||
|
output.push(format!(" - {key}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let final_output = output.join("\n");
|
||||||
|
|
||||||
|
writeln!(f,"{final_output}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ShOptPrompt {
|
||||||
|
fn default() -> Self {
|
||||||
|
ShOptPrompt {
|
||||||
|
trunc_prompt_path: 4,
|
||||||
|
edit_mode: FernEditMode::Vi,
|
||||||
|
comp_limit: 100,
|
||||||
|
prompt_highlight: true,
|
||||||
|
tab_stop: 4,
|
||||||
|
custom: HashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/state.rs
17
src/state.rs
@@ -314,6 +314,23 @@ pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T
|
|||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_shopts<T, F: FnOnce(RwLockReadGuard<ShOpts>) -> T>(f: F) -> T {
|
||||||
|
let lock = SHOPTS.read().unwrap();
|
||||||
|
f(lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_shopts<T, F: FnOnce(&mut RwLockWriteGuard<ShOpts>) -> T>(f: F) -> T {
|
||||||
|
let lock = &mut SHOPTS.write().unwrap();
|
||||||
|
f(lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is used internally and ideally never sees user input
|
||||||
|
///
|
||||||
|
/// It will panic if you give it an invalid path.
|
||||||
|
pub fn get_shopt(path: &str) -> String {
|
||||||
|
read_shopts(|s| s.get(path)).unwrap().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_status() -> i32 {
|
pub fn get_status() -> i32 {
|
||||||
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use super::*;
|
use super::*;
|
||||||
use crate::libsh::error::{
|
use crate::libsh::error::{
|
||||||
Note, ShErr, ShErrKind
|
Note, ShErr, ShErrKind
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
source: src/tests/error.rs
|
source: src/tests/error.rs
|
||||||
expression: err_fmt
|
expression: err_fmt
|
||||||
---
|
---
|
||||||
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m] - [31m[1mParse Error[0m
|
[31m[1mParse Error[0m - Expected 'esac' after case block
|
||||||
|
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m]
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m1 |[0m case foo in foo) bar;; bar) foo;;
|
[36m[1m1 |[0m case foo in foo) bar;; bar) foo;;
|
||||||
|
[36m[1m |[0m [31m[1m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[0m
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m-[0m Expected 'esac' after case block
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
source: src/tests/error.rs
|
source: src/tests/error.rs
|
||||||
expression: err_fmt
|
expression: err_fmt
|
||||||
---
|
---
|
||||||
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m] - [31m[1mParse Error[0m
|
[31m[1mParse Error[0m - Expected 'in' after case variable name
|
||||||
|
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m]
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m1 |[0m case foo foo) bar;; bar) foo;; esac
|
[36m[1m1 |[0m case foo foo) bar;; bar) foo;; esac
|
||||||
|
[36m[1m |[0m [31m[1m^^^^^^^^^^^^^^^^^^^^[0m
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m-[0m Expected 'in' after case variable name
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
source: src/tests/error.rs
|
source: src/tests/error.rs
|
||||||
expression: err_fmt
|
expression: err_fmt
|
||||||
---
|
---
|
||||||
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m] - [31m[1mCommand not found: foo[0m
|
[31m[1mCommand not found: foo[0m -
|
||||||
|
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m]
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m1 |[0m foo
|
[36m[1m1 |[0m foo
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m-[0m
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
source: src/tests/error.rs
|
source: src/tests/error.rs
|
||||||
expression: err_fmt
|
expression: err_fmt
|
||||||
---
|
---
|
||||||
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m] - [31m[1mParse Error[0m
|
[31m[1mParse Error[0m - Expected 'fi' after if statement
|
||||||
|
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m]
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m1 |[0m if foo; then bar;
|
[36m[1m1 |[0m if foo; then bar;
|
||||||
|
[36m[1m |[0m [31m[1m^^^^^^^^^^^^^^^^^[0m
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m-[0m Expected 'fi' after if statement
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
source: src/tests/error.rs
|
source: src/tests/error.rs
|
||||||
expression: err_fmt
|
expression: err_fmt
|
||||||
---
|
---
|
||||||
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m] - [31m[1mParse Error[0m
|
[31m[1mParse Error[0m - Expected 'then' after 'if' condition
|
||||||
|
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m]
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m1 |[0m if foo; bar; fi
|
[36m[1m1 |[0m if foo; bar; fi
|
||||||
|
[36m[1m |[0m [31m[1m^^^^^^^^^^^^^[0m
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m-[0m Expected 'then' after 'if' condition
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
source: src/tests/error.rs
|
source: src/tests/error.rs
|
||||||
expression: err_fmt
|
expression: err_fmt
|
||||||
---
|
---
|
||||||
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m] - [31m[1mParse Error[0m
|
[31m[1mParse Error[0m - Expected 'do' after loop condition
|
||||||
|
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m]
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m1 |[0m while true; echo foo; done
|
[36m[1m1 |[0m while true; echo foo; done
|
||||||
|
[36m[1m |[0m [31m[1m^^^^^^^^^^^^^^^^^^^^^^[0m
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m-[0m Expected 'do' after loop condition
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
source: src/tests/error.rs
|
source: src/tests/error.rs
|
||||||
expression: err_fmt
|
expression: err_fmt
|
||||||
---
|
---
|
||||||
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m] - [31m[1mParse Error[0m
|
[31m[1mParse Error[0m - Expected 'done' after loop body
|
||||||
|
[36m[1m->[0m [[36m[1m1[0m;[36m[1m1[0m]
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m1 |[0m while true; do echo foo;
|
[36m[1m1 |[0m while true; do echo foo;
|
||||||
|
[36m[1m |[0m [31m[1m^^^^^^^^^^^^^^^^^^^^^^^^[0m
|
||||||
[36m[1m |[0m
|
[36m[1m |[0m
|
||||||
[36m[1m-[0m Expected 'done' after loop body
|
|
||||||
|
|||||||
Reference in New Issue
Block a user