implemented for loops
This commit is contained in:
@@ -30,7 +30,7 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
"alias: Expected an assignment in alias args",
|
"alias: Expected an assignment in alias args",
|
||||||
span.into()
|
span
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -41,3 +41,45 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unalias(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)))?;
|
||||||
|
|
||||||
|
|
||||||
|
if argv.is_empty() {
|
||||||
|
// Display the environment variables
|
||||||
|
let mut alias_output = read_logic(|l| {
|
||||||
|
l.aliases()
|
||||||
|
.iter()
|
||||||
|
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
alias_output.sort(); // Sort them alphabetically
|
||||||
|
let mut alias_output = alias_output.join("\n"); // Join them with newlines
|
||||||
|
alias_output.push('\n'); // Push a final newline
|
||||||
|
|
||||||
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
write(stdout, alias_output.as_bytes())?; // Write it
|
||||||
|
} else {
|
||||||
|
for (arg,span) in argv {
|
||||||
|
flog!(DEBUG, arg);
|
||||||
|
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
||||||
|
return Err(
|
||||||
|
ShErr::full(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("unalias: alias '{arg}' not found"),
|
||||||
|
span
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
write_logic(|l| l.remove_alias(&arg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io_frame.unwrap().restore()?;
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
"Found a job but no table id in parse_job_id()",
|
"Found a job but no table id in parse_job_id()",
|
||||||
blame.into()
|
blame
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
"Found a job but no table id in parse_job_id()",
|
"Found a job but no table id in parse_job_id()",
|
||||||
blame.into()
|
blame
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("Invalid fd arg: {}", arg),
|
format!("Invalid fd arg: {}", arg),
|
||||||
blame.into()
|
blame
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -151,12 +151,12 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
"Invalid flag in jobs call",
|
"Invalid flag in jobs call",
|
||||||
span.into()
|
span
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
chars.next();
|
chars.next();
|
||||||
while let Some(ch) = chars.next() {
|
for ch in chars {
|
||||||
let flag = match ch {
|
let flag = match ch {
|
||||||
'l' => JobCmdFlags::LONG,
|
'l' => JobCmdFlags::LONG,
|
||||||
'p' => JobCmdFlags::PIDS,
|
'p' => JobCmdFlags::PIDS,
|
||||||
@@ -167,7 +167,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
"Invalid flag in jobs call",
|
"Invalid flag in jobs call",
|
||||||
span.into()
|
span
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub mod flowctl;
|
|||||||
pub mod zoltraak;
|
pub mod zoltraak;
|
||||||
pub mod shopt;
|
pub mod shopt;
|
||||||
|
|
||||||
pub const BUILTINS: [&str;16] = [
|
pub const BUILTINS: [&str;17] = [
|
||||||
"echo",
|
"echo",
|
||||||
"cd",
|
"cd",
|
||||||
"export",
|
"export",
|
||||||
@@ -25,6 +25,7 @@ pub const BUILTINS: [&str;16] = [
|
|||||||
"fg",
|
"fg",
|
||||||
"bg",
|
"bg",
|
||||||
"alias",
|
"alias",
|
||||||
|
"unalias",
|
||||||
"return",
|
"return",
|
||||||
"break",
|
"break",
|
||||||
"continue",
|
"continue",
|
||||||
@@ -55,11 +56,12 @@ pub const BUILTINS: [&str;16] = [
|
|||||||
/// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame`
|
/// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame`
|
||||||
/// * If redirections are given, the second field of the resulting tuple will *always* be `Some()`
|
/// * If redirections are given, the second field of the resulting tuple will *always* be `Some()`
|
||||||
/// * If no redirections are given, the second field will *always* be `None`
|
/// * If no redirections are given, the second field will *always* be `None`
|
||||||
|
type SetupReturns = ShResult<(Vec<(String,Span)>, Option<IoFrame>)>;
|
||||||
pub fn setup_builtin(
|
pub fn setup_builtin(
|
||||||
argv: Vec<Tk>,
|
argv: Vec<Tk>,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
io_mode: Option<(&mut IoStack,Vec<Redir>)>,
|
io_mode: Option<(&mut IoStack,Vec<Redir>)>,
|
||||||
) -> ShResult<(Vec<(String,Span)>, Option<IoFrame>)> {
|
) -> SetupReturns {
|
||||||
let mut argv: Vec<(String,Span)> = prepare_argv(argv)?;
|
let mut argv: Vec<(String,Span)> = prepare_argv(argv)?;
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
"Expected a number in shift args",
|
"Expected a number in shift args",
|
||||||
span.into()
|
span
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
"source: File not found",
|
"source: File not found",
|
||||||
span.into()
|
span
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
"source: Given path is not a file",
|
"source: Given path is not a file",
|
||||||
span.into()
|
span
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::{getopt::{get_opts_from_tokens, Opt, OptSet}, jobs::JobBldr, libsh::e
|
|||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub const ZOLTRAAK_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
|
pub static ZOLTRAAK_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
|
||||||
[
|
[
|
||||||
Opt::Long("dry-run".into()),
|
Opt::Long("dry-run".into()),
|
||||||
Opt::Long("confirm".into()),
|
Opt::Long("confirm".into()),
|
||||||
@@ -90,7 +90,7 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
}
|
}
|
||||||
if let Err(e) = annihilate(&arg, flags).blame(span) {
|
if let Err(e) = annihilate(&arg, flags).blame(span) {
|
||||||
io_frame.restore()?;
|
io_frame.restore()?;
|
||||||
return Err(e.into());
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
|||||||
flog!(DEBUG, "done");
|
flog!(DEBUG, "done");
|
||||||
Ok(io_buf.as_str()?.trim().to_string())
|
Ok(io_buf.as_str()?.trim().to_string())
|
||||||
}
|
}
|
||||||
_ => return Err(ShErr::simple(ShErrKind::InternalErr, "Command sub failed"))
|
_ => Err(ShErr::simple(ShErrKind::InternalErr, "Command sub failed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,7 +557,7 @@ fn tokenize_prompt(raw: &str) -> Vec<PromptTk> {
|
|||||||
// Collect up to 2 more octal digits
|
// Collect up to 2 more octal digits
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
if let Some(&next_ch) = chars.peek() {
|
if let Some(&next_ch) = chars.peek() {
|
||||||
if next_ch >= '0' && next_ch <= '7' {
|
if ('0'..='7').contains(&next_ch) {
|
||||||
octal_str.push(chars.next().unwrap());
|
octal_str.push(chars.next().unwrap());
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
@@ -631,7 +631,7 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
let pathbuf = PathBuf::from(&path);
|
let pathbuf = PathBuf::from(&path);
|
||||||
let mut segments = pathbuf.iter().count();
|
let mut segments = pathbuf.iter().count();
|
||||||
let mut path_iter = pathbuf.into_iter();
|
let mut path_iter = pathbuf.iter();
|
||||||
while segments > 4 {
|
while segments > 4 {
|
||||||
path_iter.next();
|
path_iter.next();
|
||||||
segments -= 1;
|
segments -= 1;
|
||||||
@@ -698,9 +698,9 @@ pub fn expand_aliases(input: String, mut already_expanded: HashSet<String>, log_
|
|||||||
}
|
}
|
||||||
|
|
||||||
if expanded_this_iter.is_empty() {
|
if expanded_this_iter.is_empty() {
|
||||||
return result
|
result
|
||||||
} else {
|
} else {
|
||||||
already_expanded.extend(expanded_this_iter.into_iter());
|
already_expanded.extend(expanded_this_iter);
|
||||||
return expand_aliases(result, already_expanded, log_tab)
|
expand_aliases(result, already_expanded, log_tab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
#![allow(
|
||||||
|
clippy::derivable_impls,
|
||||||
|
clippy::tabs_in_doc_comments,
|
||||||
|
clippy::while_let_on_iterator
|
||||||
|
)]
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod libsh;
|
pub mod libsh;
|
||||||
pub mod prompt;
|
pub mod prompt;
|
||||||
|
|||||||
25
src/jobs.rs
25
src/jobs.rs
@@ -64,7 +64,7 @@ pub struct ChildProc {
|
|||||||
stat: WtStat
|
stat: WtStat
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ChildProc {
|
impl ChildProc {
|
||||||
pub fn new(pid: Pid, command: Option<&str>, pgid: Option<Pid>) -> ShResult<Self> {
|
pub fn new(pid: Pid, command: Option<&str>, pgid: Option<Pid>) -> ShResult<Self> {
|
||||||
let command = command.map(|str| str.to_string());
|
let command = command.map(|str| str.to_string());
|
||||||
let stat = if kill(pid,None).is_ok() {
|
let stat = if kill(pid,None).is_ok() {
|
||||||
@@ -86,7 +86,7 @@ impl<'a> ChildProc {
|
|||||||
self.pgid
|
self.pgid
|
||||||
}
|
}
|
||||||
pub fn cmd(&self) -> Option<&str> {
|
pub fn cmd(&self) -> Option<&str> {
|
||||||
self.command.as_ref().map(|cmd| cmd.as_str())
|
self.command.as_deref()
|
||||||
}
|
}
|
||||||
pub fn stat(&self) -> WtStat {
|
pub fn stat(&self) -> WtStat {
|
||||||
self.stat
|
self.stat
|
||||||
@@ -120,6 +120,7 @@ impl<'a> ChildProc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default,Debug)]
|
||||||
pub struct JobTab {
|
pub struct JobTab {
|
||||||
fg: Option<Job>,
|
fg: Option<Job>,
|
||||||
order: Vec<usize>,
|
order: Vec<usize>,
|
||||||
@@ -129,7 +130,7 @@ pub struct JobTab {
|
|||||||
|
|
||||||
impl JobTab {
|
impl JobTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { fg: None, order: vec![], new_updates: vec![], jobs: vec![] }
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn take_fg(&mut self) -> Option<Job> {
|
pub fn take_fg(&mut self) -> Option<Job> {
|
||||||
self.fg.take()
|
self.fg.take()
|
||||||
@@ -168,7 +169,7 @@ impl JobTab {
|
|||||||
job.set_tabid(tab_pos);
|
job.set_tabid(tab_pos);
|
||||||
self.order.push(tab_pos);
|
self.order.push(tab_pos);
|
||||||
if !silent {
|
if !silent {
|
||||||
write(borrow_fd(1),format!("{}", job.display(&self.order, JobCmdFlags::INIT)).as_bytes())?;
|
write(borrow_fd(1),job.display(&self.order, JobCmdFlags::INIT).as_bytes())?;
|
||||||
}
|
}
|
||||||
if tab_pos == self.jobs.len() {
|
if tab_pos == self.jobs.len() {
|
||||||
self.jobs.push(Some(job))
|
self.jobs.push(Some(job))
|
||||||
@@ -246,7 +247,7 @@ impl JobTab {
|
|||||||
pub fn get_fg_mut(&mut self) -> Option<&mut Job> {
|
pub fn get_fg_mut(&mut self) -> Option<&mut Job> {
|
||||||
self.fg.as_mut()
|
self.fg.as_mut()
|
||||||
}
|
}
|
||||||
pub fn new_fg<'a>(&mut self, job: Job) -> ShResult<Vec<WtStat>> {
|
pub fn new_fg(&mut self, job: Job) -> ShResult<Vec<WtStat>> {
|
||||||
let pgid = job.pgid();
|
let pgid = job.pgid();
|
||||||
self.fg = Some(job);
|
self.fg = Some(job);
|
||||||
attach_tty(pgid)?;
|
attach_tty(pgid)?;
|
||||||
@@ -375,11 +376,12 @@ impl JobBldr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper around Vec<JobBldr> with some job-specific methods
|
/// A wrapper around Vec<JobBldr> with some job-specific methods
|
||||||
|
#[derive(Default,Debug)]
|
||||||
pub struct JobStack(Vec<JobBldr>);
|
pub struct JobStack(Vec<JobBldr>);
|
||||||
|
|
||||||
impl JobStack {
|
impl JobStack {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(vec![])
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn new_job(&mut self) {
|
pub fn new_job(&mut self) {
|
||||||
self.0.push(JobBldr::new())
|
self.0.push(JobBldr::new())
|
||||||
@@ -388,8 +390,7 @@ impl JobStack {
|
|||||||
self.0.last_mut()
|
self.0.last_mut()
|
||||||
}
|
}
|
||||||
pub fn finalize_job(&mut self) -> Option<Job> {
|
pub fn finalize_job(&mut self) -> Option<Job> {
|
||||||
let job = self.0.pop().map(|bldr| bldr.build());
|
self.0.pop().map(|bldr| bldr.build())
|
||||||
job
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +454,7 @@ impl Job {
|
|||||||
self.set_stats(stat);
|
self.set_stats(stat);
|
||||||
Ok(killpg(self.pgid, sig)?)
|
Ok(killpg(self.pgid, sig)?)
|
||||||
}
|
}
|
||||||
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
|
pub fn wait_pgrp(&mut self) -> ShResult<Vec<WtStat>> {
|
||||||
let mut stats = vec![];
|
let mut stats = vec![];
|
||||||
flog!(TRACE, "waiting on children");
|
flog!(TRACE, "waiting on children");
|
||||||
flog!(TRACE, self.children);
|
flog!(TRACE, self.children);
|
||||||
@@ -675,14 +676,14 @@ pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
|||||||
new_mask.add(Signal::SIGTTIN);
|
new_mask.add(Signal::SIGTTIN);
|
||||||
new_mask.add(Signal::SIGTTOU);
|
new_mask.add(Signal::SIGTTOU);
|
||||||
|
|
||||||
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&mut new_mask), Some(&mut mask_bkup))?;
|
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&new_mask), Some(&mut mask_bkup))?;
|
||||||
|
|
||||||
let result = tcsetpgrp(borrow_fd(0), pgid);
|
let result = tcsetpgrp(borrow_fd(0), pgid);
|
||||||
|
|
||||||
pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(&mut mask_bkup), Some(&mut new_mask))?;
|
pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(&mask_bkup), Some(&mut new_mask))?;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
flog!(ERROR, "error while switching term control: {}",e);
|
flog!(ERROR, "error while switching term control: {}",e);
|
||||||
tcsetpgrp(borrow_fd(0), getpgrp())?;
|
tcsetpgrp(borrow_fd(0), getpgrp())?;
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ impl ShErr {
|
|||||||
}
|
}
|
||||||
pub fn with_span(sherr: ShErr, span: Span) -> Self {
|
pub fn with_span(sherr: ShErr, span: Span) -> Self {
|
||||||
let (kind,msg,notes,_) = sherr.unpack();
|
let (kind,msg,notes,_) = sherr.unpack();
|
||||||
let span = span.into();
|
|
||||||
Self::Full { kind, msg, notes, span }
|
Self::Full { kind, msg, notes, span }
|
||||||
}
|
}
|
||||||
pub fn kind(&self) -> &ShErrKind {
|
pub fn kind(&self) -> &ShErrKind {
|
||||||
@@ -300,19 +299,19 @@ impl From<std::io::Error> for ShErr {
|
|||||||
|
|
||||||
impl From<std::env::VarError> for ShErr {
|
impl From<std::env::VarError> for ShErr {
|
||||||
fn from(value: std::env::VarError) -> Self {
|
fn from(value: std::env::VarError) -> Self {
|
||||||
ShErr::simple(ShErrKind::InternalErr, &value.to_string())
|
ShErr::simple(ShErrKind::InternalErr, value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<rustyline::error::ReadlineError> for ShErr {
|
impl From<rustyline::error::ReadlineError> for ShErr {
|
||||||
fn from(value: rustyline::error::ReadlineError) -> Self {
|
fn from(value: rustyline::error::ReadlineError) -> Self {
|
||||||
ShErr::simple(ShErrKind::ParseErr, &value.to_string())
|
ShErr::simple(ShErrKind::ParseErr, value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Errno> for ShErr {
|
impl From<Errno> for ShErr {
|
||||||
fn from(value: Errno) -> Self {
|
fn from(value: Errno) -> Self {
|
||||||
ShErr::simple(ShErrKind::Errno, &value.to_string())
|
ShErr::simple(ShErrKind::Errno, value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ pub fn save_termios() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(static_mut_refs)]
|
#[allow(static_mut_refs)]
|
||||||
|
///Access the saved termios
|
||||||
|
///
|
||||||
|
///# Safety
|
||||||
|
///This function is unsafe because it accesses a public mutable static value. This function should only ever be called after save_termios() has already been called.
|
||||||
pub unsafe fn get_saved_termios() -> Option<Termios> {
|
pub unsafe fn get_saved_termios() -> Option<Termios> {
|
||||||
// SAVED_TERMIOS should *only ever* be set once and accessed once
|
// 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.
|
// Set at the start of the program, and accessed during the exit of the program to reset the termios.
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ impl StyleSet {
|
|||||||
Self { styles: vec![] }
|
Self { styles: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(mut self, style: Style) -> Self {
|
pub fn add_style(mut self, style: Style) -> Self {
|
||||||
if !self.styles.contains(&style) {
|
if !self.styles.contains(&style) {
|
||||||
self.styles.push(style);
|
self.styles.push(style);
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ impl BitOr for Style {
|
|||||||
type Output = StyleSet;
|
type Output = StyleSet;
|
||||||
|
|
||||||
fn bitor(self, rhs: Self) -> Self::Output {
|
fn bitor(self, rhs: Self) -> Self::Output {
|
||||||
StyleSet::new().add(self).add(rhs)
|
StyleSet::new().add_style(self).add_style(rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,12 +158,12 @@ impl BitOr<Style> for StyleSet {
|
|||||||
type Output = StyleSet;
|
type Output = StyleSet;
|
||||||
|
|
||||||
fn bitor(self, rhs: Style) -> Self::Output {
|
fn bitor(self, rhs: Style) -> Self::Output {
|
||||||
self.add(rhs)
|
self.add_style(rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Style> for StyleSet {
|
impl From<Style> for StyleSet {
|
||||||
fn from(style: Style) -> Self {
|
fn from(style: Style) -> Self {
|
||||||
StyleSet::new().add(style)
|
StyleSet::new().add_style(style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,16 +71,12 @@ impl CharDequeUtils for VecDeque<char> {
|
|||||||
impl TkVecUtils<Tk> for Vec<Tk> {
|
impl TkVecUtils<Tk> for Vec<Tk> {
|
||||||
fn get_span(&self) -> Option<Span> {
|
fn get_span(&self) -> Option<Span> {
|
||||||
if let Some(first_tk) = self.first() {
|
if let Some(first_tk) = self.first() {
|
||||||
if let Some(last_tk) = self.last() {
|
self.last().map(|last_tk| {
|
||||||
Some(
|
Span::new(
|
||||||
Span::new(
|
first_tk.span.start..last_tk.span.end,
|
||||||
first_tk.span.start..last_tk.span.end,
|
first_tk.source()
|
||||||
first_tk.source()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
} else {
|
})
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::{HashSet, VecDeque};
|
use std::collections::{HashSet, VecDeque};
|
||||||
|
|
||||||
|
|
||||||
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}, expand::expand_aliases, jobs::{dispatch_job, ChildProc, JobBldr, JobStack}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, get_snapshots, read_logic, read_vars, restore_snapshot, write_logic, write_meta, write_vars, ShFunc, VarTab, LOGIC_TABLE}};
|
use crate::{builtin::{alias::{alias, unalias}, cd::cd, echo::echo, export::export, flowctl::flowctl, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, shopt::shopt, source::source, zoltraak::zoltraak}, expand::expand_aliases, jobs::{dispatch_job, ChildProc, JobBldr, JobStack}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, get_snapshots, read_logic, restore_snapshot, write_logic, write_meta, write_vars, ShFunc, VarTab, LOGIC_TABLE}};
|
||||||
|
|
||||||
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};
|
||||||
|
|
||||||
@@ -76,11 +76,13 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn dispatch_node(&mut self, node: Node) -> ShResult<()> {
|
pub fn dispatch_node(&mut self, node: Node) -> ShResult<()> {
|
||||||
|
flog!(DEBUG, node.class);
|
||||||
match node.class {
|
match node.class {
|
||||||
NdRule::Conjunction {..} => self.exec_conjunction(node)?,
|
NdRule::Conjunction {..} => self.exec_conjunction(node)?,
|
||||||
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
||||||
NdRule::IfNode {..} => self.exec_if(node)?,
|
NdRule::IfNode {..} => self.exec_if(node)?,
|
||||||
NdRule::LoopNode {..} => self.exec_loop(node)?,
|
NdRule::LoopNode {..} => self.exec_loop(node)?,
|
||||||
|
NdRule::ForNode {..} => self.exec_for(node)?,
|
||||||
NdRule::CaseNode {..} => self.exec_case(node)?,
|
NdRule::CaseNode {..} => self.exec_case(node)?,
|
||||||
NdRule::BraceGrp {..} => self.exec_brc_grp(node)?,
|
NdRule::BraceGrp {..} => self.exec_brc_grp(node)?,
|
||||||
NdRule::FuncDef {..} => self.exec_func_def(node)?,
|
NdRule::FuncDef {..} => self.exec_func_def(node)?,
|
||||||
@@ -169,7 +171,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
if let Err(e) = exec_input(subsh_body) {
|
if let Err(e) = exec_input(subsh_body) {
|
||||||
restore_snapshot(snapshot);
|
restore_snapshot(snapshot);
|
||||||
return Err(e.into())
|
return Err(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_snapshot(snapshot);
|
restore_snapshot(snapshot);
|
||||||
@@ -205,7 +207,7 @@ impl Dispatcher {
|
|||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
_ => return {
|
_ => return {
|
||||||
Err(e.into())
|
Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +297,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(e.into());
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = state::get_status();
|
let status = state::get_status();
|
||||||
@@ -312,7 +314,7 @@ impl Dispatcher {
|
|||||||
state::set_status(*code);
|
state::set_status(*code);
|
||||||
continue 'outer
|
continue 'outer
|
||||||
}
|
}
|
||||||
_ => return Err(e.into())
|
_ => return Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,6 +325,48 @@ impl Dispatcher {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
|
||||||
|
let NdRule::ForNode { vars, arr, body } = for_stmt.class else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
let io_frame = self.io_stack.pop_frame();
|
||||||
|
let (_, mut body_frame) = io_frame.split_frame();
|
||||||
|
let (_, out_redirs) = for_stmt.redirs.split_by_channel();
|
||||||
|
body_frame.extend(out_redirs);
|
||||||
|
|
||||||
|
'outer: for chunk in arr.chunks(vars.len()) {
|
||||||
|
let empty = Tk::default();
|
||||||
|
let chunk_iter = vars.iter().zip(
|
||||||
|
chunk.iter().chain(std::iter::repeat(&empty)) // Or however you define an empty token
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var, val) in chunk_iter {
|
||||||
|
write_vars(|v| v.set_var(&var.to_string(), &val.to_string(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.io_stack.push(body_frame.clone());
|
||||||
|
|
||||||
|
for node in body.clone() {
|
||||||
|
if let Err(e) = self.dispatch_node(node) {
|
||||||
|
match e.kind() {
|
||||||
|
ShErrKind::LoopBreak(code) => {
|
||||||
|
state::set_status(*code);
|
||||||
|
break 'outer
|
||||||
|
}
|
||||||
|
ShErrKind::LoopContinue(code) => {
|
||||||
|
state::set_status(*code);
|
||||||
|
continue 'outer
|
||||||
|
}
|
||||||
|
_ => return Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
|
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
|
||||||
let NdRule::IfNode { cond_nodes, else_block } = if_stmt.class else {
|
let NdRule::IfNode { cond_nodes, else_block } = if_stmt.class else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
@@ -340,7 +384,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
if let Err(e) = self.dispatch_node(*cond) {
|
if let Err(e) = self.dispatch_node(*cond) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(e.into());
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
match state::get_status() {
|
match state::get_status() {
|
||||||
@@ -409,6 +453,7 @@ impl Dispatcher {
|
|||||||
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
||||||
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
||||||
"alias" => alias(cmd, io_stack_mut, curr_job_mut),
|
"alias" => alias(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"unalias" => unalias(cmd, io_stack_mut, curr_job_mut),
|
||||||
"return" => flowctl(cmd, ShErrKind::FuncReturn(0)),
|
"return" => flowctl(cmd, ShErrKind::FuncReturn(0)),
|
||||||
"break" => flowctl(cmd, ShErrKind::LoopBreak(0)),
|
"break" => flowctl(cmd, ShErrKind::LoopBreak(0)),
|
||||||
"continue" => flowctl(cmd, ShErrKind::LoopContinue(0)),
|
"continue" => flowctl(cmd, ShErrKind::LoopContinue(0)),
|
||||||
@@ -424,7 +469,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(e.into())
|
return Err(e)
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -518,7 +563,7 @@ pub fn prepare_argv(argv: Vec<Tk>) -> ShResult<Vec<(String,Span)>> {
|
|||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_fork<'t,C,P>(
|
pub fn run_fork<C,P>(
|
||||||
io_frame: IoFrame,
|
io_frame: IoFrame,
|
||||||
exec_args: Option<ExecArgs>,
|
exec_args: Option<ExecArgs>,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
@@ -554,7 +599,7 @@ pub fn def_child_action(mut io_frame: IoFrame, exec_args: Option<ExecArgs>) {
|
|||||||
let cmd = &exec_args.cmd.0;
|
let cmd = &exec_args.cmd.0;
|
||||||
let span = exec_args.cmd.1;
|
let span = exec_args.cmd.1;
|
||||||
|
|
||||||
let Err(e) = execvpe(&cmd, &exec_args.argv, &exec_args.envp);
|
let Err(e) = execvpe(cmd, &exec_args.argv, &exec_args.envp);
|
||||||
|
|
||||||
let cmd = cmd.to_str().unwrap().to_string();
|
let cmd = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use bitflags::bitflags;
|
|||||||
|
|
||||||
use crate::{builtin::BUILTINS, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::CharDequeUtils}, prelude::*};
|
use crate::{builtin::BUILTINS, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::CharDequeUtils}, prelude::*};
|
||||||
|
|
||||||
pub const KEYWORDS: [&'static str;14] = [
|
pub const KEYWORDS: [&str;14] = [
|
||||||
"if",
|
"if",
|
||||||
"then",
|
"then",
|
||||||
"elif",
|
"elif",
|
||||||
@@ -21,7 +21,7 @@ pub const KEYWORDS: [&'static str;14] = [
|
|||||||
"esac",
|
"esac",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const OPENERS: [&'static str;6] = [
|
pub const OPENERS: [&str;6] = [
|
||||||
"if",
|
"if",
|
||||||
"while",
|
"while",
|
||||||
"until",
|
"until",
|
||||||
@@ -103,12 +103,6 @@ impl Tk {
|
|||||||
pub fn new(class: TkRule, span: Span) -> Self {
|
pub fn new(class: TkRule, span: Span) -> Self {
|
||||||
Self { class, span, flags: TkFlags::empty() }
|
Self { class, span, flags: TkFlags::empty() }
|
||||||
}
|
}
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
match &self.class {
|
|
||||||
TkRule::Expanded { exp } => exp.join(" "),
|
|
||||||
_ => self.span.as_str().to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.span.as_str()
|
self.span.as_str()
|
||||||
}
|
}
|
||||||
@@ -264,7 +258,7 @@ impl LexStream {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Invalid redirection",
|
"Invalid redirection",
|
||||||
Span::new(self.cursor..pos, self.source.clone()).into()
|
Span::new(self.cursor..pos, self.source.clone())
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
@@ -471,13 +465,13 @@ impl LexStream {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unterminated quote",
|
"Unterminated quote",
|
||||||
new_tk.span.into(),
|
new_tk.span,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO: clean up this mess
|
// TODO: clean up this mess
|
||||||
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
|
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
|
||||||
if is_keyword(&new_tk.span.as_str()) {
|
if is_keyword(new_tk.span.as_str()) {
|
||||||
if matches!(new_tk.span.as_str(), "case" | "select" | "for") {
|
if matches!(new_tk.span.as_str(), "case" | "select" | "for") {
|
||||||
self.flags |= LexFlags::EXPECTING_IN;
|
self.flags |= LexFlags::EXPECTING_IN;
|
||||||
new_tk.flags |= TkFlags::KEYWORD;
|
new_tk.flags |= TkFlags::KEYWORD;
|
||||||
@@ -485,7 +479,7 @@ impl LexStream {
|
|||||||
} else {
|
} else {
|
||||||
new_tk.flags |= TkFlags::KEYWORD;
|
new_tk.flags |= TkFlags::KEYWORD;
|
||||||
}
|
}
|
||||||
} else if is_assignment(&new_tk.span.as_str()) {
|
} else if is_assignment(new_tk.span.as_str()) {
|
||||||
new_tk.flags |= TkFlags::ASSIGN;
|
new_tk.flags |= TkFlags::ASSIGN;
|
||||||
} else {
|
} else {
|
||||||
if self.flags.contains(LexFlags::EXPECTING_IN) {
|
if self.flags.contains(LexFlags::EXPECTING_IN) {
|
||||||
@@ -503,11 +497,9 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
self.set_next_is_cmd(false);
|
self.set_next_is_cmd(false);
|
||||||
}
|
}
|
||||||
} else if self.flags.contains(LexFlags::EXPECTING_IN) {
|
} else if self.flags.contains(LexFlags::EXPECTING_IN) && new_tk.span.as_str() == "in" {
|
||||||
if new_tk.span.as_str() == "in" {
|
new_tk.flags |= TkFlags::KEYWORD;
|
||||||
new_tk.flags |= TkFlags::KEYWORD;
|
self.flags &= !LexFlags::EXPECTING_IN;
|
||||||
self.flags &= !LexFlags::EXPECTING_IN;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
Ok(new_tk)
|
Ok(new_tk)
|
||||||
|
|||||||
185
src/parse/mod.rs
185
src/parse/mod.rs
@@ -49,7 +49,6 @@ impl ParsedSrc {
|
|||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
for parse_result in ParseStream::new(tokens) {
|
for parse_result in ParseStream::new(tokens) {
|
||||||
flog!(DEBUG, parse_result);
|
|
||||||
match parse_result {
|
match parse_result {
|
||||||
Ok(node) => nodes.push(node),
|
Ok(node) => nodes.push(node),
|
||||||
Err(error) => errors.push(error)
|
Err(error) => errors.push(error)
|
||||||
@@ -412,6 +411,24 @@ impl ParseStream {
|
|||||||
assert!(num_consumed <= self.tokens.len());
|
assert!(num_consumed <= self.tokens.len());
|
||||||
self.tokens = self.tokens[num_consumed..].to_vec();
|
self.tokens = self.tokens[num_consumed..].to_vec();
|
||||||
}
|
}
|
||||||
|
/// This tries to match on different stuff that can appear in a command position
|
||||||
|
/// Matches shell commands like if-then-fi, pipelines, etc.
|
||||||
|
/// 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_pipeln()
|
||||||
|
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
|
||||||
|
try_match!(self.parse_func_def()?);
|
||||||
|
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
|
||||||
|
try_match!(self.parse_case()?);
|
||||||
|
try_match!(self.parse_loop()?);
|
||||||
|
try_match!(self.parse_for()?);
|
||||||
|
try_match!(self.parse_if()?);
|
||||||
|
if check_pipelines {
|
||||||
|
try_match!(self.parse_pipeln()?);
|
||||||
|
} else {
|
||||||
|
try_match!(self.parse_cmd()?);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
fn parse_cmd_list(&mut self) -> ShResult<Option<Node>> {
|
fn parse_cmd_list(&mut self) -> ShResult<Option<Node>> {
|
||||||
let mut elements = vec![];
|
let mut elements = vec![];
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
@@ -446,26 +463,8 @@ impl ParseStream {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// This tries to match on different stuff that can appear in a command position
|
|
||||||
/// Matches shell commands like if-then-fi, pipelines, etc.
|
|
||||||
/// 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_pipeln()
|
|
||||||
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
|
|
||||||
try_match!(self.parse_func_def()?);
|
|
||||||
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
|
|
||||||
try_match!(self.parse_case()?);
|
|
||||||
try_match!(self.parse_loop()?);
|
|
||||||
try_match!(self.parse_if()?);
|
|
||||||
if check_pipelines {
|
|
||||||
try_match!(self.parse_pipeln()?);
|
|
||||||
} else {
|
|
||||||
try_match!(self.parse_cmd()?);
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn parse_func_def(&mut self) -> ShResult<Option<Node>> {
|
fn parse_func_def(&mut self) -> ShResult<Option<Node>> {
|
||||||
let mut node_tks: Vec<Tk> = vec![];
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
let name;
|
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
if !is_func_name(self.peek_tk()) {
|
if !is_func_name(self.peek_tk()) {
|
||||||
@@ -473,7 +472,7 @@ impl ParseStream {
|
|||||||
}
|
}
|
||||||
let name_tk = self.next_tk().unwrap();
|
let name_tk = self.next_tk().unwrap();
|
||||||
node_tks.push(name_tk.clone());
|
node_tks.push(name_tk.clone());
|
||||||
name = name_tk;
|
let name = name_tk;
|
||||||
|
|
||||||
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
@@ -543,7 +542,7 @@ impl ParseStream {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Expected a filename after this redirection",
|
"Expected a filename after this redirection",
|
||||||
tk.span.clone().into()
|
tk.span.clone()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -582,7 +581,7 @@ impl ParseStream {
|
|||||||
// Needs a pattern token
|
// Needs a pattern token
|
||||||
// Followed by any number of CaseNodes
|
// Followed by any number of CaseNodes
|
||||||
let mut node_tks: Vec<Tk> = vec![];
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
let pattern: Tk;
|
|
||||||
let mut case_blocks: Vec<CaseNode> = vec![];
|
let mut case_blocks: Vec<CaseNode> = vec![];
|
||||||
let redirs: Vec<Redir> = vec![];
|
let redirs: Vec<Redir> = vec![];
|
||||||
|
|
||||||
@@ -610,7 +609,8 @@ impl ParseStream {
|
|||||||
return Err(pat_err)
|
return Err(pat_err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pattern = pat_tk;
|
let pattern: Tk = pat_tk;
|
||||||
|
|
||||||
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() {
|
||||||
@@ -764,7 +764,7 @@ impl ParseStream {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Expected a filename after this redirection",
|
"Expected a filename after this redirection",
|
||||||
tk.span.clone().into()
|
tk.span.clone()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -799,9 +799,109 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
|
fn parse_for(&mut self) -> ShResult<Option<Node>> {
|
||||||
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
|
let mut vars: Vec<Tk> = vec![];
|
||||||
|
let mut arr: Vec<Tk> = vec![];
|
||||||
|
let mut body: Vec<Node> = vec![];
|
||||||
|
let mut redirs: Vec<Redir> = vec![];
|
||||||
|
|
||||||
|
if !self.check_keyword("for") || !self.next_tk_is_some() {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
while let Some(tk) = self.next_tk() {
|
||||||
|
node_tks.push(tk.clone());
|
||||||
|
if tk.as_str() == "in" {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
vars.push(tk.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(tk) = self.next_tk() {
|
||||||
|
node_tks.push(tk.clone());
|
||||||
|
if tk.class == TkRule::Sep {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
arr.push(tk.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars.is_empty() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
|
return Err(parse_err_full("This for loop is missing a variable", &node_tks.get_span().unwrap()))
|
||||||
|
}
|
||||||
|
if arr.is_empty() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
|
return Err(parse_err_full("This for loop is missing an array", &node_tks.get_span().unwrap()))
|
||||||
|
}
|
||||||
|
if !self.check_keyword("do") || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
|
return Err(parse_err_full("Missing a 'do' for this for loop", &node_tks.get_span().unwrap()))
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
self.catch_separator(&mut node_tks);
|
||||||
|
|
||||||
|
while let Some(node) = self.parse_block(true)? {
|
||||||
|
body.push(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
|
return Err(parse_err_full("Missing a 'done' after this for loop", &node_tks.get_span().unwrap()))
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
while self.check_redir() {
|
||||||
|
let tk = self.next_tk().unwrap();
|
||||||
|
node_tks.push(tk.clone());
|
||||||
|
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
|
||||||
|
if redir_bldr.io_mode.is_none() {
|
||||||
|
let path_tk = self.next_tk();
|
||||||
|
|
||||||
|
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
|
return Err(
|
||||||
|
ShErr::full(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
"Expected a filename after this redirection",
|
||||||
|
tk.span.clone()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_tk = path_tk.unwrap();
|
||||||
|
node_tks.push(path_tk.clone());
|
||||||
|
let redir_class = redir_bldr.class.unwrap();
|
||||||
|
let pathbuf = PathBuf::from(path_tk.span.as_str());
|
||||||
|
|
||||||
|
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
||||||
|
self.panic_mode(&mut node_tks);
|
||||||
|
return Err(parse_err_full(
|
||||||
|
"Error opening file for redirection",
|
||||||
|
&path_tk.span
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file);
|
||||||
|
let redir_bldr = redir_bldr.with_io_mode(io_mode);
|
||||||
|
let redir = redir_bldr.build();
|
||||||
|
redirs.push(redir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = Node {
|
||||||
|
class: NdRule::ForNode { vars, arr, body },
|
||||||
|
flags: NdFlags::empty(),
|
||||||
|
redirs,
|
||||||
|
tokens: node_tks
|
||||||
|
};
|
||||||
|
Ok(Some(node))
|
||||||
|
}
|
||||||
fn parse_loop(&mut self) -> ShResult<Option<Node>> {
|
fn parse_loop(&mut self) -> ShResult<Option<Node>> {
|
||||||
// Requires a single CondNode and a LoopKind
|
// Requires a single CondNode and a LoopKind
|
||||||
let loop_kind: LoopKind;
|
|
||||||
let cond_node: CondNode;
|
let cond_node: CondNode;
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
|
|
||||||
@@ -809,7 +909,7 @@ impl ParseStream {
|
|||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
let loop_tk = self.next_tk().unwrap();
|
let loop_tk = self.next_tk().unwrap();
|
||||||
loop_kind = loop_tk.span
|
let loop_kind: LoopKind = loop_tk.span
|
||||||
.as_str()
|
.as_str()
|
||||||
.parse() // LoopKind implements FromStr
|
.parse() // LoopKind implements FromStr
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -876,12 +976,10 @@ impl ParseStream {
|
|||||||
cmds.push(cmd);
|
cmds.push(cmd);
|
||||||
if *self.next_tk_class() != TkRule::Pipe || is_punctuated {
|
if *self.next_tk_class() != TkRule::Pipe || is_punctuated {
|
||||||
break
|
break
|
||||||
|
} else if let Some(pipe) = self.next_tk() {
|
||||||
|
node_tks.push(pipe)
|
||||||
} else {
|
} else {
|
||||||
if let Some(pipe) = self.next_tk() {
|
break
|
||||||
node_tks.push(pipe)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cmds.is_empty() {
|
if cmds.is_empty() {
|
||||||
@@ -915,7 +1013,7 @@ impl ParseStream {
|
|||||||
break
|
break
|
||||||
|
|
||||||
} else if is_assignment {
|
} 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());
|
||||||
@@ -958,7 +1056,7 @@ impl ParseStream {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Expected a filename after this redirection",
|
"Expected a filename after this redirection",
|
||||||
tk.span.clone().into()
|
tk.span.clone()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
@@ -1004,7 +1102,7 @@ impl ParseStream {
|
|||||||
let mut pos = token.span.start;
|
let mut pos = token.span.start;
|
||||||
|
|
||||||
while let Some(ch) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
if !assign_kind.is_none() {
|
if assign_kind.is_some() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
pos += ch.len_utf8();
|
pos += ch.len_utf8();
|
||||||
@@ -1100,8 +1198,6 @@ 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() == 1 {
|
if self.tokens.is_empty() || self.tokens.len() == 1 {
|
||||||
return None
|
return None
|
||||||
@@ -1117,21 +1213,19 @@ impl Iterator for ParseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let result = self.parse_cmd_list();
|
let result = self.parse_cmd_list();
|
||||||
flog!(DEBUG, result);
|
|
||||||
flog!(DEBUG, self.tokens);
|
|
||||||
match result {
|
match result {
|
||||||
Ok(Some(node)) => {
|
Ok(Some(node)) => {
|
||||||
return Some(Ok(node));
|
Some(Ok(node))
|
||||||
}
|
}
|
||||||
Ok(None) => return None,
|
Ok(None) => None,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Some(Err(e))
|
Some(Err(e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_is_punctuated(tokens: &Vec<Tk>) -> bool {
|
fn node_is_punctuated(tokens: &[Tk]) -> bool {
|
||||||
tokens.last().is_some_and(|tk| {
|
tokens.last().is_some_and(|tk| {
|
||||||
matches!(tk.class, TkRule::Sep)
|
matches!(tk.class, TkRule::Sep)
|
||||||
})
|
})
|
||||||
@@ -1153,7 +1247,6 @@ fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult<File> {
|
|||||||
}
|
}
|
||||||
RedirType::Append => {
|
RedirType::Append => {
|
||||||
OpenOptions::new()
|
OpenOptions::new()
|
||||||
.write(true)
|
|
||||||
.create(true)
|
.create(true)
|
||||||
.append(true)
|
.append(true)
|
||||||
.open(Path::new(&path))
|
.open(Path::new(&path))
|
||||||
@@ -1167,7 +1260,7 @@ fn parse_err_full(reason: &str, blame: &Span) -> ShErr {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
reason,
|
reason,
|
||||||
blame.clone().into()
|
blame.clone()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1192,7 +1285,7 @@ pub fn node_operation<F1,F2>(node: &mut Node, filter: &F1, operation: &mut F2)
|
|||||||
F2: FnMut(&mut Node)
|
F2: FnMut(&mut Node)
|
||||||
{
|
{
|
||||||
let check_node = |node: &mut Node, filter: &F1, operation: &mut F2| {
|
let check_node = |node: &mut Node, filter: &F1, operation: &mut F2| {
|
||||||
if filter(&node) {
|
if filter(node) {
|
||||||
operation(node);
|
operation(node);
|
||||||
} else {
|
} else {
|
||||||
node_operation::<F1,F2>(node, filter, operation);
|
node_operation::<F1,F2>(node, filter, operation);
|
||||||
@@ -1254,7 +1347,7 @@ pub fn node_operation<F1,F2>(node: &mut Node, filter: &F1, operation: &mut F2)
|
|||||||
check_node(cmd,filter,operation);
|
check_node(cmd,filter,operation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NdRule::Assignment { kind: _, var: _, val: _ } => return, // No nodes to check
|
NdRule::Assignment { kind: _, var: _, val: _ } => (), // No nodes to check
|
||||||
NdRule::BraceGrp { ref mut body } => {
|
NdRule::BraceGrp { ref mut body } => {
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
check_node(body_node,filter,operation);
|
check_node(body_node,filter,operation);
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ pub struct IoStack {
|
|||||||
stack: Vec<IoFrame>,
|
stack: Vec<IoFrame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'e> IoStack {
|
impl IoStack {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: vec![IoFrame::new()],
|
stack: vec![IoFrame::new()],
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ pub fn read_line() -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
Err(ReadlineError::Interrupted) => Ok(String::new()),
|
Err(ReadlineError::Interrupted) => Ok(String::new()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(e.into())
|
Err(e.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ use rustyline::{completion::Completer, highlight::Highlighter, hint::{Hint, Hint
|
|||||||
use crate::{libsh::term::{Style, Styled}, parse::{lex::{LexFlags, LexStream}, ParseStream}};
|
use crate::{libsh::term::{Style, Styled}, parse::{lex::{LexFlags, LexStream}, ParseStream}};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Default,Debug)]
|
||||||
pub struct FernReadline {
|
pub struct FernReadline {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FernReadline {
|
impl FernReadline {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { }
|
Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ impl Hinter for FernReadline {
|
|||||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
|
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
|
||||||
let ent = ctx.history().search(
|
let ent = ctx.history().search(
|
||||||
line,
|
line,
|
||||||
ctx.history().len() - 1,
|
ctx.history().len().saturating_sub(1),
|
||||||
rustyline::history::SearchDirection::Reverse
|
rustyline::history::SearchDirection::Reverse
|
||||||
).ok()??;
|
).ok()??;
|
||||||
let entry_raw = ent.entry.get(pos..)?.to_string();
|
let entry_raw = ent.entry.get(pos..)?.to_string();
|
||||||
|
|||||||
58
src/shopt.rs
58
src/shopt.rs
@@ -15,11 +15,11 @@ pub enum FernBellStyle {
|
|||||||
impl FromStr for FernBellStyle {
|
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_lowercase().as_str() {
|
||||||
"audible" => Ok(Self::Audible),
|
"audible" => Ok(Self::Audible),
|
||||||
"visible" => Ok(Self::Visible),
|
"visible" => Ok(Self::Visible),
|
||||||
"disable" => Ok(Self::Disable),
|
"disable" => Ok(Self::Disable),
|
||||||
_ => return Err(
|
_ => Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("Invalid bell style '{s}'")
|
format!("Invalid bell style '{s}'")
|
||||||
@@ -29,9 +29,9 @@ impl FromStr for FernBellStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<BellStyle> for FernBellStyle {
|
impl From<FernBellStyle> for BellStyle {
|
||||||
fn into(self) -> BellStyle {
|
fn from(val: FernBellStyle) -> Self {
|
||||||
match self {
|
match val {
|
||||||
FernBellStyle::Audible => BellStyle::Audible,
|
FernBellStyle::Audible => BellStyle::Audible,
|
||||||
FernBellStyle::Visible => BellStyle::Visible,
|
FernBellStyle::Visible => BellStyle::Visible,
|
||||||
FernBellStyle::Disable => BellStyle::None
|
FernBellStyle::Disable => BellStyle::None
|
||||||
@@ -56,11 +56,11 @@ pub enum FernEditMode {
|
|||||||
Emacs
|
Emacs
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<EditMode> for FernEditMode {
|
impl From<FernEditMode> for EditMode {
|
||||||
fn into(self) -> EditMode {
|
fn from(val: FernEditMode) -> Self {
|
||||||
match self {
|
match val {
|
||||||
Self::Vi => EditMode::Vi,
|
FernEditMode::Vi => EditMode::Vi,
|
||||||
Self::Emacs => EditMode::Emacs
|
FernEditMode::Emacs => EditMode::Emacs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ impl FromStr for FernEditMode {
|
|||||||
match s.to_ascii_lowercase().as_str() {
|
match s.to_ascii_lowercase().as_str() {
|
||||||
"vi" => Ok(Self::Vi),
|
"vi" => Ok(Self::Vi),
|
||||||
"emacs" => Ok(Self::Emacs),
|
"emacs" => Ok(Self::Emacs),
|
||||||
_ => return Err(
|
_ => Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("Invalid edit mode '{s}'")
|
format!("Invalid edit mode '{s}'")
|
||||||
@@ -167,7 +167,7 @@ impl ShOpts {
|
|||||||
"core" => self.core.get(&remainder),
|
"core" => self.core.get(&remainder),
|
||||||
"prompt" => self.prompt.get(&remainder),
|
"prompt" => self.prompt.get(&remainder),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(
|
Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
"shopt: Expected 'core' or 'prompt' in shopt key"
|
"shopt: Expected 'core' or 'prompt' in shopt key"
|
||||||
@@ -328,47 +328,47 @@ impl ShOptCore {
|
|||||||
|
|
||||||
match query {
|
match query {
|
||||||
"dotglob" => {
|
"dotglob" => {
|
||||||
let mut output = format!("Include hidden files in glob patterns\n");
|
let mut output = String::from("Include hidden files in glob patterns\n");
|
||||||
output.push_str(&format!("{}",self.dotglob));
|
output.push_str(&format!("{}",self.dotglob));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"autocd" => {
|
"autocd" => {
|
||||||
let mut output = format!("Allow navigation to directories by passing the directory as a command directly\n");
|
let mut output = String::from("Allow navigation to directories by passing the directory as a command directly\n");
|
||||||
output.push_str(&format!("{}",self.autocd));
|
output.push_str(&format!("{}",self.autocd));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"hist_ignore_dupes" => {
|
"hist_ignore_dupes" => {
|
||||||
let mut output = format!("Ignore consecutive duplicate command history entries\n");
|
let mut output = String::from("Ignore consecutive duplicate command history entries\n");
|
||||||
output.push_str(&format!("{}",self.hist_ignore_dupes));
|
output.push_str(&format!("{}",self.hist_ignore_dupes));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"max_hist" => {
|
"max_hist" => {
|
||||||
let mut output = format!("Maximum number of entries in the command history file (default '.fernhist')\n");
|
let mut output = String::from("Maximum number of entries in the command history file (default '.fernhist')\n");
|
||||||
output.push_str(&format!("{}",self.max_hist));
|
output.push_str(&format!("{}",self.max_hist));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"interactive_comments" => {
|
"interactive_comments" => {
|
||||||
let mut output = format!("Whether or not to allow comments in interactive mode\n");
|
let mut output = String::from("Whether or not to allow comments in interactive mode\n");
|
||||||
output.push_str(&format!("{}",self.interactive_comments));
|
output.push_str(&format!("{}",self.interactive_comments));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"auto_hist" => {
|
"auto_hist" => {
|
||||||
let mut output = format!("Whether or not to automatically save commands to the command history file\n");
|
let mut output = String::from("Whether or not to automatically save commands to the command history file\n");
|
||||||
output.push_str(&format!("{}",self.auto_hist));
|
output.push_str(&format!("{}",self.auto_hist));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"bell_style" => {
|
"bell_style" => {
|
||||||
let mut output = format!("What type of bell style to use for the bell character\n");
|
let mut output = String::from("What type of bell style to use for the bell character\n");
|
||||||
output.push_str(&format!("{}",self.bell_style));
|
output.push_str(&format!("{}",self.bell_style));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"max_recurse_depth" => {
|
"max_recurse_depth" => {
|
||||||
let mut output = format!("Maximum limit of recursive shell function calls\n");
|
let mut output = String::from("Maximum limit of recursive shell function calls\n");
|
||||||
output.push_str(&format!("{}",self.max_recurse_depth));
|
output.push_str(&format!("{}",self.max_recurse_depth));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(
|
Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("shopt: Unexpected 'core' option '{query}'")
|
format!("shopt: Unexpected 'core' option '{query}'")
|
||||||
@@ -531,32 +531,32 @@ impl ShOptPrompt {
|
|||||||
|
|
||||||
match query {
|
match query {
|
||||||
"trunc_prompt_path" => {
|
"trunc_prompt_path" => {
|
||||||
let mut output = format!("Maximum number of path segments used in the '\\W' prompt escape sequence\n");
|
let mut output = String::from("Maximum number of path segments used in the '\\W' prompt escape sequence\n");
|
||||||
output.push_str(&format!("{}",self.trunc_prompt_path));
|
output.push_str(&format!("{}",self.trunc_prompt_path));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"edit_mode" => {
|
"edit_mode" => {
|
||||||
let mut output = format!("The style of editor shortcuts used in the line-editing of the prompt\n");
|
let mut output = String::from("The style of editor shortcuts used in the line-editing of the prompt\n");
|
||||||
output.push_str(&format!("{}",self.edit_mode));
|
output.push_str(&format!("{}",self.edit_mode));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"comp_limit" => {
|
"comp_limit" => {
|
||||||
let mut output = format!("Maximum number of completion candidates displayed upon pressing tab\n");
|
let mut output = String::from("Maximum number of completion candidates displayed upon pressing tab\n");
|
||||||
output.push_str(&format!("{}",self.comp_limit));
|
output.push_str(&format!("{}",self.comp_limit));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"prompt_highlight" => {
|
"prompt_highlight" => {
|
||||||
let mut output = format!("Whether to enable or disable syntax highlighting on the prompt\n");
|
let mut output = String::from("Whether to enable or disable syntax highlighting on the prompt\n");
|
||||||
output.push_str(&format!("{}",self.prompt_highlight));
|
output.push_str(&format!("{}",self.prompt_highlight));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"tab_stop" => {
|
"tab_stop" => {
|
||||||
let mut output = format!("The number of spaces used by the tab character '\\t'\n");
|
let mut output = String::from("The number of spaces used by the tab character '\\t'\n");
|
||||||
output.push_str(&format!("{}",self.tab_stop));
|
output.push_str(&format!("{}",self.tab_stop));
|
||||||
Ok(Some(output))
|
Ok(Some(output))
|
||||||
}
|
}
|
||||||
"custom" => {
|
"custom" => {
|
||||||
let mut output = format!("A table of custom 'modules' executed as shell functions for prompt scripting\n");
|
let mut output = String::from("A table of custom 'modules' executed as shell functions for prompt scripting\n");
|
||||||
output.push_str("Current modules: \n");
|
output.push_str("Current modules: \n");
|
||||||
for key in self.custom.keys() {
|
for key in self.custom.keys() {
|
||||||
output.push_str(&format!(" - {key}\n"));
|
output.push_str(&format!(" - {key}\n"));
|
||||||
@@ -564,7 +564,7 @@ impl ShOptPrompt {
|
|||||||
Ok(Some(output.trim().to_string()))
|
Ok(Some(output.trim().to_string()))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(
|
Err(
|
||||||
ShErr::simple(
|
ShErr::simple(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("shopt: Unexpected 'core' option '{query}'")
|
format!("shopt: Unexpected 'core' option '{query}'")
|
||||||
@@ -599,7 +599,7 @@ impl Display for ShOptPrompt {
|
|||||||
output.push(format!("comp_limit = {}", self.comp_limit));
|
output.push(format!("comp_limit = {}", self.comp_limit));
|
||||||
output.push(format!("prompt_highlight = {}", self.prompt_highlight));
|
output.push(format!("prompt_highlight = {}", self.prompt_highlight));
|
||||||
output.push(format!("tab_stop = {}", self.tab_stop));
|
output.push(format!("tab_stop = {}", self.tab_stop));
|
||||||
output.push(format!("prompt modules: "));
|
output.push(String::from("prompt modules: "));
|
||||||
for key in self.custom.keys() {
|
for key in self.custom.keys() {
|
||||||
output.push(format!(" - {key}"));
|
output.push(format!(" - {key}"));
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/state.rs
15
src/state.rs
@@ -51,7 +51,7 @@ impl Deref for ShFunc {
|
|||||||
/// The logic table for the shell
|
/// The logic table for the shell
|
||||||
///
|
///
|
||||||
/// Contains aliases and functions
|
/// Contains aliases and functions
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Default,Clone,Debug)]
|
||||||
pub struct LogTab {
|
pub struct LogTab {
|
||||||
functions: HashMap<String,ShFunc>,
|
functions: HashMap<String,ShFunc>,
|
||||||
aliases: HashMap<String,String>
|
aliases: HashMap<String,String>
|
||||||
@@ -59,7 +59,7 @@ pub struct LogTab {
|
|||||||
|
|
||||||
impl LogTab {
|
impl LogTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { functions: HashMap::new(), aliases: HashMap::new() }
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
||||||
self.functions.insert(name.into(), src);
|
self.functions.insert(name.into(), src);
|
||||||
@@ -79,6 +79,12 @@ impl LogTab {
|
|||||||
pub fn get_alias(&self, name: &str) -> Option<String> {
|
pub fn get_alias(&self, name: &str) -> Option<String> {
|
||||||
self.aliases.get(name).cloned()
|
self.aliases.get(name).cloned()
|
||||||
}
|
}
|
||||||
|
pub fn remove_alias(&mut self, name: &str) {
|
||||||
|
flog!(DEBUG, self.aliases);
|
||||||
|
flog!(DEBUG, name);
|
||||||
|
self.aliases.remove(name);
|
||||||
|
flog!(DEBUG, self.aliases);
|
||||||
|
}
|
||||||
pub fn clear_aliases(&mut self) {
|
pub fn clear_aliases(&mut self) {
|
||||||
self.aliases.clear()
|
self.aliases.clear()
|
||||||
}
|
}
|
||||||
@@ -109,7 +115,7 @@ impl Deref for Var {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Default,Clone,Debug)]
|
||||||
pub struct VarTab {
|
pub struct VarTab {
|
||||||
vars: HashMap<String,Var>,
|
vars: HashMap<String,Var>,
|
||||||
params: HashMap<char,String>,
|
params: HashMap<char,String>,
|
||||||
@@ -299,13 +305,14 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A table of metadata for the shell
|
/// A table of metadata for the shell
|
||||||
|
#[derive(Default,Debug)]
|
||||||
pub struct MetaTab {
|
pub struct MetaTab {
|
||||||
runtime_start: Option<Instant>
|
runtime_start: Option<Instant>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaTab {
|
impl MetaTab {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { runtime_start: None }
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn start_timer(&mut self) {
|
pub fn start_timer(&mut self) {
|
||||||
self.runtime_start = Some(Instant::now());
|
self.runtime_start = Some(Instant::now());
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ fn cmd_not_found() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unclosed_subsh() {
|
fn unclosed_subsh() {
|
||||||
let input = "(foo";
|
let input = "(foo";
|
||||||
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).skip(1).next().unwrap();
|
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).nth(1).unwrap();
|
||||||
let Err(err) = token else {
|
let Err(err) = token else {
|
||||||
panic!("{:?}",token);
|
panic!("{:?}",token);
|
||||||
};
|
};
|
||||||
@@ -25,7 +25,7 @@ fn unclosed_subsh() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unclosed_dquote() {
|
fn unclosed_dquote() {
|
||||||
let input = "\"foo bar";
|
let input = "\"foo bar";
|
||||||
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).skip(1).next().unwrap();
|
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).nth(1).unwrap();
|
||||||
let Err(err) = token else {
|
let Err(err) = token else {
|
||||||
panic!();
|
panic!();
|
||||||
};
|
};
|
||||||
@@ -37,7 +37,7 @@ fn unclosed_dquote() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unclosed_squote() {
|
fn unclosed_squote() {
|
||||||
let input = "'foo bar";
|
let input = "'foo bar";
|
||||||
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).skip(1).next().unwrap();
|
let token = LexStream::new(Arc::new(input.into()), LexFlags::empty()).nth(1).unwrap();
|
||||||
let Err(err) = token else {
|
let Err(err) = token else {
|
||||||
panic!();
|
panic!();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use super::*;
|
|||||||
#[test]
|
#[test]
|
||||||
fn simple_expansion() {
|
fn simple_expansion() {
|
||||||
let varsub = "$foo";
|
let varsub = "$foo";
|
||||||
write_vars(|v| v.set_var("foo", "this is the value of the variable".into(), false));
|
write_vars(|v| v.set_var("foo", "this is the value of the variable", false));
|
||||||
|
|
||||||
let mut tokens: Vec<Tk> = LexStream::new(Arc::new(varsub.to_string()), LexFlags::empty())
|
let mut tokens: Vec<Tk> = LexStream::new(Arc::new(varsub.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
@@ -13,7 +13,6 @@ fn simple_expansion() {
|
|||||||
.collect();
|
.collect();
|
||||||
let var_tk = tokens.pop().unwrap();
|
let var_tk = tokens.pop().unwrap();
|
||||||
|
|
||||||
let var_span = var_tk.span.clone();
|
|
||||||
let exp_tk = var_tk.expand().unwrap();
|
let exp_tk = var_tk.expand().unwrap();
|
||||||
write_vars(|v| v.vars_mut().clear());
|
write_vars(|v| v.vars_mut().clear());
|
||||||
insta::assert_debug_snapshot!(exp_tk.get_words())
|
insta::assert_debug_snapshot!(exp_tk.get_words())
|
||||||
@@ -33,7 +32,7 @@ fn expand_alias_simple() {
|
|||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
let input = String::from("foo");
|
let input = String::from("foo");
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), &l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"echo foo");
|
assert_eq!(result.as_str(),"echo foo");
|
||||||
l.clear_aliases();
|
l.clear_aliases();
|
||||||
});
|
});
|
||||||
@@ -45,7 +44,7 @@ fn expand_alias_in_if() {
|
|||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
let input = String::from("if foo; then echo bar; fi");
|
let input = String::from("if foo; then echo bar; fi");
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), &l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"if echo foo; then echo bar; fi");
|
assert_eq!(result.as_str(),"if echo foo; then echo bar; fi");
|
||||||
l.clear_aliases();
|
l.clear_aliases();
|
||||||
});
|
});
|
||||||
@@ -69,7 +68,7 @@ fn expand_alias_multiline() {
|
|||||||
fi
|
fi
|
||||||
");
|
");
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), &l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result,expected)
|
assert_eq!(result,expected)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -82,7 +81,7 @@ fn expand_multiple_aliases() {
|
|||||||
l.insert_alias("biz", "echo biz");
|
l.insert_alias("biz", "echo biz");
|
||||||
let input = String::from("foo; bar; biz");
|
let input = String::from("foo; bar; biz");
|
||||||
|
|
||||||
let result = expand_aliases(input, HashSet::new(), &l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"echo foo; echo bar; echo biz");
|
assert_eq!(result.as_str(),"echo foo; echo bar; echo biz");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -93,7 +92,7 @@ fn alias_in_arg_position() {
|
|||||||
l.insert_alias("foo", "echo foo");
|
l.insert_alias("foo", "echo foo");
|
||||||
let input = String::from("echo foo");
|
let input = String::from("echo foo");
|
||||||
|
|
||||||
let result = expand_aliases(input.clone(), HashSet::new(), &l);
|
let result = expand_aliases(input.clone(), HashSet::new(), l);
|
||||||
assert_eq!(input,result);
|
assert_eq!(input,result);
|
||||||
l.clear_aliases();
|
l.clear_aliases();
|
||||||
});
|
});
|
||||||
@@ -106,7 +105,7 @@ fn expand_recursive_alias() {
|
|||||||
l.insert_alias("bar", "foo bar");
|
l.insert_alias("bar", "foo bar");
|
||||||
|
|
||||||
let input = String::from("bar");
|
let input = String::from("bar");
|
||||||
let result = expand_aliases(input, HashSet::new(), &l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"echo foo bar");
|
assert_eq!(result.as_str(),"echo foo bar");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -117,7 +116,7 @@ fn test_infinite_recursive_alias() {
|
|||||||
l.insert_alias("foo", "foo bar");
|
l.insert_alias("foo", "foo bar");
|
||||||
|
|
||||||
let input = String::from("foo");
|
let input = String::from("foo");
|
||||||
let result = expand_aliases(input, HashSet::new(), &l);
|
let result = expand_aliases(input, HashSet::new(), l);
|
||||||
assert_eq!(result.as_str(),"foo bar");
|
assert_eq!(result.as_str(),"foo bar");
|
||||||
l.clear_aliases();
|
l.clear_aliases();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ fn getopt_from_argv() {
|
|||||||
let node = get_nodes("echo -n -e foo", |node| matches!(node.class, NdRule::Command {..}))
|
let node = get_nodes("echo -n -e foo", |node| matches!(node.class, NdRule::Command {..}))
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let NdRule::Command { assignments, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::libsh::error::{
|
|||||||
use crate::parse::{
|
use crate::parse::{
|
||||||
node_operation, Node, NdRule, ParseStream,
|
node_operation, Node, NdRule, ParseStream,
|
||||||
lex::{
|
lex::{
|
||||||
Tk, TkFlags, TkRule, LexFlags, LexStream
|
Tk, TkRule, LexFlags, LexStream
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
use crate::expand::{
|
use crate::expand::{
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ fn parse_cursed() {
|
|||||||
fn test_node_operation() {
|
fn test_node_operation() {
|
||||||
let input = String::from("echo hello world; echo foo bar");
|
let input = String::from("echo hello world; echo foo bar");
|
||||||
let mut check_nodes = vec![];
|
let mut check_nodes = vec![];
|
||||||
let mut tokens: Vec<Tk> = LexStream::new(input.into(), LexFlags::empty())
|
let tokens: Vec<Tk> = LexStream::new(input.into(), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ fn styled_background() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn styled_set() {
|
fn styled_set() {
|
||||||
let input = "multi-style text";
|
let input = "multi-style text";
|
||||||
let style_set = StyleSet::new().add(Style::Magenta).add(Style::Italic);
|
let style_set = StyleSet::new().add_style(Style::Magenta).add_style(Style::Italic);
|
||||||
let styled = input.styled(style_set);
|
let styled = input.styled(style_set);
|
||||||
insta::assert_snapshot!(styled);
|
insta::assert_snapshot!(styled);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user