Progress
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||
let argv = argv.drop_first();
|
||||
let mut argv_iter = argv.into_iter();
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
let arg_raw = shenv.input_slice(arg.span()).to_string();
|
||||
if let Some((alias,body)) = arg_raw.split_once('=') {
|
||||
let clean_body = clean_string(&body);
|
||||
shenv.logic_mut().set_alias(alias, &clean_body);
|
||||
} else {
|
||||
return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", shenv.get_input(), arg.span().clone()))
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn cd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||
let mut argv_iter = argv.into_iter();
|
||||
argv_iter.next(); // Ignore 'cd'
|
||||
let dir_raw = argv_iter.next().map(|arg| shenv.input_slice(arg.span()).into()).unwrap_or(std::env::var("HOME")?);
|
||||
let dir = PathBuf::from(&dir_raw);
|
||||
std::env::set_current_dir(dir)?;
|
||||
let new_dir = std::env::current_dir()?;
|
||||
shenv.vars_mut().export("PWD",new_dir.to_str().unwrap());
|
||||
shenv.set_code(0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn sh_flow(node: Node, shenv: &mut ShEnv, kind: ShErrKind) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
let mut code: i32 = 0;
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let mut argv_iter = argv.into_iter();
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
if let Ok(code_arg) = shenv.input_slice(arg.span()).parse() {
|
||||
code = code_arg
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
shenv.set_code(code);
|
||||
// Our control flow keywords are used as ShErrKinds
|
||||
// This design will halt the execution flow and start heading straight back upward
|
||||
// Function returns and loop breaks/continues will be caught in the proper context to allow
|
||||
// Execution to continue at the proper return point.
|
||||
Err(ShErr::simple(kind, ""))
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
use shellenv::jobs::{ChildProc, JobBldr};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub struct EchoFlags: u32 {
|
||||
const USE_ESCAPE = 0b0001;
|
||||
const NO_ESCAPE = 0b0010;
|
||||
const STDERR = 0b0100;
|
||||
const NO_NEWLINE = 0b1000;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let mut argv_iter = argv.into_iter().skip(1).peekable();
|
||||
let mut echo_flags = EchoFlags::empty();
|
||||
while let Some(arg) = argv_iter.peek() {
|
||||
let blame = arg.span();
|
||||
let raw = arg.as_raw(shenv);
|
||||
if raw.starts_with('-') {
|
||||
let _ = argv_iter.next();
|
||||
let mut options = raw.strip_prefix('-').unwrap().chars();
|
||||
while let Some(opt) = options.next() {
|
||||
match opt {
|
||||
'r' => echo_flags |= EchoFlags::STDERR,
|
||||
'n' => echo_flags |= EchoFlags::NO_NEWLINE,
|
||||
'e' => {
|
||||
if echo_flags.contains(EchoFlags::NO_ESCAPE) {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
"the 'e' and 'E' flags are mutually exclusive",
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
echo_flags |= EchoFlags::USE_ESCAPE;
|
||||
}
|
||||
'E' => {
|
||||
if echo_flags.contains(EchoFlags::USE_ESCAPE) {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
"the 'e' and 'E' flags are mutually exclusive",
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
echo_flags |= EchoFlags::NO_ESCAPE;
|
||||
}
|
||||
_ => return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
format!("Unrecognized echo option"),
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
let mut argv = argv_iter.collect::<Vec<_>>().as_strings(shenv);
|
||||
argv.retain(|arg| arg != "\n");
|
||||
log!(DEBUG,argv);
|
||||
let mut formatted = argv.join(" ");
|
||||
if !echo_flags.contains(EchoFlags::NO_NEWLINE) {
|
||||
formatted.push('\n');
|
||||
}
|
||||
|
||||
shenv.collect_redirs(redirs);
|
||||
log!(DEBUG,"{:?}",shenv.ctx().redirs());
|
||||
shenv.ctx_mut().activate_rdrs()?;
|
||||
write_out(formatted)?;
|
||||
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn export(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||
let mut argv_iter = argv.into_iter();
|
||||
argv_iter.next(); // Ignore 'export'
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
let arg_raw = arg.as_raw(shenv);
|
||||
if let Some((var,val)) = arg_raw.split_once('=') {
|
||||
shenv.vars_mut().export(var, &clean_string(val));
|
||||
} else {
|
||||
eprintln!("Expected an assignment in export args, found this: {}", arg_raw)
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
use shellenv::jobs::JobCmdFlags;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> {
|
||||
let blame = node.span();
|
||||
let cmd = if fg { "fg" } else { "bg" };
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let mut argv_s = argv.drop_first().as_strings(shenv).into_iter();
|
||||
|
||||
if read_jobs(|j| j.get_fg().is_some()) {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::InternalErr,
|
||||
format!("Somehow called {} with an existing foreground job",cmd),
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||
id
|
||||
} else {
|
||||
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", shenv.get_input(), blame))
|
||||
};
|
||||
|
||||
let tabid = match argv_s.next() {
|
||||
Some(arg) => parse_job_id(&arg, blame.clone(),shenv)?,
|
||||
None => curr_job_id
|
||||
};
|
||||
|
||||
let mut job = write_jobs(|j| {
|
||||
let id = JobID::TableID(tabid);
|
||||
let query_result = j.query(id.clone());
|
||||
if query_result.is_some() {
|
||||
Ok(j.remove_job(id.clone()).unwrap())
|
||||
} else {
|
||||
Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
format!("Job id `{}' not found", tabid),
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
})?;
|
||||
|
||||
job.killpg(Signal::SIGCONT)?;
|
||||
|
||||
if fg {
|
||||
write_jobs(|j| j.new_fg(job))?;
|
||||
} else {
|
||||
let job_order = read_jobs(|j| j.order().to_vec());
|
||||
write(borrow_fd(1), job.display(&job_order, JobCmdFlags::PIDS).as_bytes())?;
|
||||
write_jobs(|j| j.insert_job(job, true))?;
|
||||
}
|
||||
shenv.set_code(0);
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_job_id(arg: &str, blame: Rc<RefCell<Span>>, shenv: &mut ShEnv) -> ShResult<usize> {
|
||||
if arg.starts_with('%') {
|
||||
let arg = arg.strip_prefix('%').unwrap();
|
||||
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
Ok(arg.parse::<usize>().unwrap())
|
||||
} else {
|
||||
let result = write_jobs(|j| {
|
||||
let query_result = j.query(JobID::Command(arg.into()));
|
||||
query_result.map(|job| job.tabid().unwrap())
|
||||
});
|
||||
match result {
|
||||
Some(id) => Ok(id),
|
||||
None => Err(
|
||||
ShErr::full(
|
||||
ShErrKind::InternalErr,
|
||||
"Found a job but no table id in parse_job_id()",
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
let result = write_jobs(|j| {
|
||||
let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::<i32>().unwrap())));
|
||||
if let Some(job) = pgid_query_result {
|
||||
return Some(job.tabid().unwrap())
|
||||
}
|
||||
|
||||
if arg.parse::<i32>().unwrap() > 0 {
|
||||
let table_id_query_result = j.query(JobID::TableID(arg.parse::<usize>().unwrap()));
|
||||
return table_id_query_result.map(|job| job.tabid().unwrap());
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
match result {
|
||||
Some(id) => Ok(id),
|
||||
None => Err(
|
||||
ShErr::full(
|
||||
ShErrKind::InternalErr,
|
||||
"Found a job but no table id in parse_job_id()",
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Err(
|
||||
ShErr::full(
|
||||
ShErrKind::SyntaxErr,
|
||||
format!("Invalid fd arg: {}", arg),
|
||||
shenv.get_input(),
|
||||
blame
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let mut argv = argv.drop_first().into_iter();
|
||||
|
||||
let mut flags = JobCmdFlags::empty();
|
||||
while let Some(arg) = argv.next() {
|
||||
let arg_s = shenv.input_slice(arg.span());
|
||||
let mut chars = arg_s.chars().peekable();
|
||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::SyntaxErr,
|
||||
"Invalid flag in jobs call",
|
||||
shenv.get_input(),
|
||||
arg.span()
|
||||
)
|
||||
)
|
||||
}
|
||||
chars.next();
|
||||
while let Some(ch) = chars.next() {
|
||||
let flag = match ch {
|
||||
'l' => JobCmdFlags::LONG,
|
||||
'p' => JobCmdFlags::PIDS,
|
||||
'n' => JobCmdFlags::NEW_ONLY,
|
||||
'r' => JobCmdFlags::RUNNING,
|
||||
's' => JobCmdFlags::STOPPED,
|
||||
_ => return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::SyntaxErr,
|
||||
"Invalid flag in jobs call",
|
||||
shenv.get_input(),
|
||||
arg.span()
|
||||
)
|
||||
)
|
||||
|
||||
};
|
||||
flags |= flag
|
||||
}
|
||||
}
|
||||
write_jobs(|j| j.print_jobs(flags))?;
|
||||
shenv.set_code(0);
|
||||
} else { unreachable!() }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
pub mod echo;
|
||||
pub mod cd;
|
||||
pub mod pwd;
|
||||
pub mod export;
|
||||
pub mod jobctl;
|
||||
pub mod read;
|
||||
pub mod alias;
|
||||
pub mod control_flow;
|
||||
pub mod source;
|
||||
|
||||
pub const BUILTINS: [&str;14] = [
|
||||
"echo",
|
||||
"cd",
|
||||
"pwd",
|
||||
"export",
|
||||
"fg",
|
||||
"bg",
|
||||
"jobs",
|
||||
"read",
|
||||
"alias",
|
||||
"exit",
|
||||
"continue",
|
||||
"return",
|
||||
"break",
|
||||
"source",
|
||||
];
|
||||
@@ -1,17 +0,0 @@
|
||||
use shellenv::jobs::{ChildProc, JobBldr};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn pwd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv: _, redirs } = rule {
|
||||
let mut pwd = shenv.vars().get_var("PWD").to_string();
|
||||
pwd.push('\n');
|
||||
|
||||
shenv.collect_redirs(redirs);
|
||||
shenv.ctx_mut().activate_rdrs()?;
|
||||
write_out(pwd)?;
|
||||
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn read_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||
let argv = argv.drop_first();
|
||||
let mut argv_iter = argv.iter();
|
||||
// TODO: properly implement redirections
|
||||
// using activate_redirs() was causing issues, may require manual handling
|
||||
|
||||
let mut buf = vec![0u8; 1024];
|
||||
let bytes_read = read(0, &mut buf)?;
|
||||
buf.truncate(bytes_read);
|
||||
|
||||
let read_input = String::from_utf8_lossy(&buf).trim_end().to_string();
|
||||
|
||||
if let Some(var) = argv_iter.next() {
|
||||
/*
|
||||
let words: Vec<&str> = read_input.split_whitespace().collect();
|
||||
|
||||
for (var, value) in argv_iter.zip(words.iter().chain(std::iter::repeat(&""))) {
|
||||
shenv.vars_mut().set_var(&var.to_string(), value);
|
||||
}
|
||||
|
||||
// Assign the rest of the string to the first variable if there's only one
|
||||
if argv.len() == 1 {
|
||||
shenv.vars_mut().set_var(&first_var.to_string(), &read_input);
|
||||
}
|
||||
*/
|
||||
let var_name = shenv.input_slice(var.span()).to_string();
|
||||
shenv.vars_mut().set_var(&var_name, &read_input);
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
log!(TRACE, "leaving read");
|
||||
shenv.set_code(0);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn source(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
shenv.collect_redirs(redirs);
|
||||
let mut argv_iter = argv.into_iter().skip(1);
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
let arg_raw = arg.as_raw(shenv);
|
||||
let arg_path = PathBuf::from(arg_raw);
|
||||
shenv.source_file(arg_path)?;
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,514 +0,0 @@
|
||||
use crate::{expand::{arithmetic::expand_arith_string, tilde::expand_tilde_string, vars::expand_string}, prelude::*};
|
||||
use shellenv::jobs::{ChildProc, JobBldr};
|
||||
|
||||
pub mod shellcmd;
|
||||
|
||||
|
||||
pub fn exec_input<S: Into<String>>(input: S, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let input = input.into();
|
||||
shenv.new_input(&input);
|
||||
let total_time = std::time::Instant::now();
|
||||
|
||||
let token_time = std::time::Instant::now();
|
||||
let token_stream = Lexer::new(input,shenv).lex();
|
||||
|
||||
let token_stream = expand_aliases(token_stream, shenv);
|
||||
for token in &token_stream {
|
||||
log!(TRACE, token);
|
||||
log!(TRACE, "{}",token.as_raw(shenv));
|
||||
}
|
||||
log!(INFO, "Tokenizing done in {:?}", token_time.elapsed());
|
||||
|
||||
let parse_time = std::time::Instant::now();
|
||||
let syn_tree = Parser::new(token_stream,shenv).parse()?;
|
||||
log!(TRACE,syn_tree);
|
||||
log!(INFO, "Parsing done in {:?}", parse_time.elapsed());
|
||||
if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) {
|
||||
shenv.save_io()?;
|
||||
}
|
||||
|
||||
let exec_time = std::time::Instant::now();
|
||||
if let Err(e) = Executor::new(syn_tree, shenv).walk() {
|
||||
if let ShErrKind::CleanExit = e.kind() {
|
||||
let code = shenv.get_code();
|
||||
sh_quit(code);
|
||||
} else {
|
||||
if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) {
|
||||
shenv.reset_io()?;
|
||||
}
|
||||
return Err(e.into())
|
||||
}
|
||||
}
|
||||
log!(INFO, "Executing done in {:?}", exec_time.elapsed());
|
||||
log!(INFO, "Total time spent: {:?}", total_time.elapsed());
|
||||
if !shenv.ctx().flags().contains(ExecFlags::IN_FUNC) {
|
||||
shenv.reset_io()?;
|
||||
}
|
||||
log!(INFO, "Io reset");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Executor<'a> {
|
||||
ast: SynTree,
|
||||
shenv: &'a mut ShEnv
|
||||
}
|
||||
|
||||
impl<'a> Executor<'a> {
|
||||
pub fn new(ast: SynTree, shenv: &'a mut ShEnv) -> Self {
|
||||
Self { ast, shenv }
|
||||
}
|
||||
pub fn walk(&mut self) -> ShResult<()> {
|
||||
self.shenv.inputman_mut().push_state();
|
||||
log!(TRACE, "Starting walk");
|
||||
while let Some(node) = self.ast.next_node() {
|
||||
if let NdRule::CmdList { cmds } = node.clone().into_rule() {
|
||||
log!(TRACE, "{:?}", cmds);
|
||||
exec_list(cmds, self.shenv).try_blame(node.as_raw(self.shenv),node.span())?
|
||||
} else { unreachable!() }
|
||||
}
|
||||
self.shenv.inputman_mut().pop_state();
|
||||
log!(TRACE, "passed");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Executing list");
|
||||
let mut list = VecDeque::from(list);
|
||||
while let Some(cmd_info) = list.fpop() {
|
||||
let guard = cmd_info.0;
|
||||
let cmd = cmd_info.1;
|
||||
|
||||
if let Some(guard) = guard {
|
||||
let code = shenv.get_code();
|
||||
match guard {
|
||||
CmdGuard::And => {
|
||||
if code != 0 { break; }
|
||||
}
|
||||
CmdGuard::Or => {
|
||||
if code == 0 { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch_node(cmd, shenv)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_node(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let node_raw = node.as_raw(shenv);
|
||||
let span = node.span();
|
||||
match *node.rule() {
|
||||
NdRule::Command {..} |
|
||||
NdRule::Subshell {..} |
|
||||
NdRule::Assignment {..} => dispatch_command(node, shenv).try_blame(node_raw, span)?,
|
||||
NdRule::IfThen {..} => shellcmd::exec_if(node, shenv).try_blame(node_raw, span)?,
|
||||
NdRule::Loop {..} => shellcmd::exec_loop(node, shenv).try_blame(node_raw, span)?,
|
||||
NdRule::ForLoop {..} => shellcmd::exec_for(node, shenv).try_blame(node_raw, span)?,
|
||||
NdRule::Case {..} => shellcmd::exec_case(node, shenv).try_blame(node_raw, span)?,
|
||||
NdRule::FuncDef {..} => exec_funcdef(node,shenv).try_blame(node_raw, span)?,
|
||||
NdRule::Pipeline {..} => exec_pipeline(node, shenv).try_blame(node_raw, span)?,
|
||||
_ => unimplemented!("No support for NdRule::{:?} yet", node.rule())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let mut is_builtin = false;
|
||||
let mut is_func = false;
|
||||
let mut is_subsh = false;
|
||||
let mut is_assign = false;
|
||||
if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() {
|
||||
if !shenv.ctx().flags().contains(ExecFlags::NO_EXPAND) {
|
||||
*argv = expand_argv(argv.to_vec(), shenv)?;
|
||||
}
|
||||
let cmd = argv.first().unwrap().as_raw(shenv);
|
||||
if shenv.logic().get_function(&cmd).is_some() {
|
||||
is_func = true;
|
||||
} else if node.flags().contains(NdFlag::BUILTIN) {
|
||||
is_builtin = true;
|
||||
}
|
||||
} else if let NdRule::Subshell { body: _, ref mut argv, redirs: _ } = node.rule_mut() {
|
||||
if !shenv.ctx().flags().contains(ExecFlags::NO_EXPAND) {
|
||||
*argv = expand_argv(argv.to_vec(), shenv)?;
|
||||
}
|
||||
is_subsh = true;
|
||||
} else if let NdRule::Assignment { assignments: _, cmd: _ } = node.rule() {
|
||||
is_assign = true;
|
||||
} else { unreachable!() }
|
||||
|
||||
if is_builtin {
|
||||
exec_builtin(node, shenv)?;
|
||||
} else if is_func {
|
||||
exec_func(node, shenv)?;
|
||||
} else if is_subsh {
|
||||
exec_subshell(node, shenv)?;
|
||||
} else if is_assign {
|
||||
exec_assignment(node, shenv)?;
|
||||
} else {
|
||||
exec_cmd(node, shenv)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_func(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let mut argv_iter = argv.into_iter();
|
||||
let func_name = argv_iter.next().unwrap().as_raw(shenv);
|
||||
let body = shenv.logic().get_function(&func_name).unwrap().to_string();
|
||||
let snapshot = shenv.clone();
|
||||
shenv.vars_mut().reset_params();
|
||||
shenv.ctx_mut().set_flag(ExecFlags::IN_FUNC);
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
let arg_raw = shenv.input_slice(arg.span()).to_string();
|
||||
shenv.vars_mut().bpush_arg(&arg_raw);
|
||||
}
|
||||
shenv.collect_redirs(redirs);
|
||||
|
||||
match exec_input(body, shenv) {
|
||||
Ok(()) => {
|
||||
*shenv = snapshot;
|
||||
return Ok(())
|
||||
}
|
||||
Err(e) if e.kind() == ShErrKind::FuncReturn => {
|
||||
let code = shenv.get_code();
|
||||
*shenv = snapshot;
|
||||
shenv.set_code(code);
|
||||
return Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
*shenv = snapshot;
|
||||
return Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_funcdef(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::FuncDef { name, body } = rule {
|
||||
let name_raw = name.as_raw(shenv);
|
||||
let name = name_raw.trim_end_matches("()");
|
||||
let body_raw = body.as_raw(shenv);
|
||||
let body = body_raw[1..body_raw.len() - 1].trim();
|
||||
|
||||
shenv.logic_mut().set_function(name, body);
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let snapshot = shenv.clone();
|
||||
shenv.vars_mut().reset_params();
|
||||
let is_bg = node.flags().contains(NdFlag::BACKGROUND);
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Subshell { body, argv, redirs } = rule {
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK); // Allow sub-forks in this case
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
for arg in argv {
|
||||
let arg_raw = &arg.as_raw(shenv);
|
||||
shenv.vars_mut().bpush_arg(arg_raw);
|
||||
}
|
||||
let body_raw = body.as_raw(shenv);
|
||||
|
||||
match exec_input(body_raw, shenv) {
|
||||
Ok(()) => exit(0),
|
||||
Err(e) => {
|
||||
eprintln!("{}",e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
write_err(e)?;
|
||||
exit(1);
|
||||
}
|
||||
for arg in argv {
|
||||
let arg_raw = &arg.as_raw(shenv);
|
||||
shenv.vars_mut().bpush_arg(arg_raw);
|
||||
}
|
||||
let body_raw = body.as_raw(shenv);
|
||||
match exec_input(body_raw, shenv) {
|
||||
Ok(()) => exit(0),
|
||||
Err(e) => {
|
||||
eprintln!("{}",e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Parent { child } => {
|
||||
*shenv = snapshot;
|
||||
let children = vec![
|
||||
ChildProc::new(child, Some("anonymous subshell"), Some(child))?
|
||||
];
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(child)
|
||||
.build();
|
||||
dispatch_job(job, is_bg, shenv)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Executing builtin");
|
||||
let command = if let NdRule::Command { argv, redirs: _ } = node.rule() {
|
||||
argv.first().unwrap().as_raw(shenv)
|
||||
} else { unreachable!() };
|
||||
|
||||
log!(TRACE, "{}", command.as_str());
|
||||
match command.as_str() {
|
||||
"echo" => echo(node, shenv)?,
|
||||
"cd" => cd(node,shenv)?,
|
||||
"pwd" => pwd(node, shenv)?,
|
||||
"export" => export(node, shenv)?,
|
||||
"jobs" => jobs(node, shenv)?,
|
||||
"fg" => continue_job(node, shenv, true)?,
|
||||
"bg" => continue_job(node, shenv, false)?,
|
||||
"read" => read_builtin(node, shenv)?,
|
||||
"alias" => alias(node, shenv)?,
|
||||
"exit" => sh_flow(node, shenv, ShErrKind::CleanExit)?,
|
||||
"return" => sh_flow(node, shenv, ShErrKind::FuncReturn)?,
|
||||
"break" => sh_flow(node, shenv, ShErrKind::LoopBreak)?,
|
||||
"continue" => sh_flow(node, shenv, ShErrKind::LoopContinue)?,
|
||||
"source" => source(node, shenv)?,
|
||||
_ => unimplemented!("Have not yet implemented support for builtin `{}'",command)
|
||||
}
|
||||
log!(TRACE, "done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Executing assignment");
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Assignment { assignments, cmd } = rule {
|
||||
log!(TRACE, "Assignments: {:?}", assignments);
|
||||
log!(TRACE, "Command: {:?}", cmd);
|
||||
let mut assigns = assignments.into_iter();
|
||||
if let Some(cmd) = cmd {
|
||||
let saved_env = shenv.vars().env().clone();
|
||||
while let Some(token) = assigns.next() {
|
||||
let raw = token.as_raw(shenv);
|
||||
if let Some((var,val)) = raw.split_once('=') {
|
||||
let val_rule = Lexer::get_rule(&val);
|
||||
if EXPANSIONS.contains(&val_rule) {
|
||||
let exp = match val_rule {
|
||||
TkRule::ArithSub => expand_arith_string(val,shenv)?,
|
||||
TkRule::DQuote => expand_string(val, shenv)?,
|
||||
TkRule::TildeSub => expand_tilde_string(val),
|
||||
TkRule::VarSub => {
|
||||
let val = shenv.vars().get_var(var);
|
||||
val.to_string()
|
||||
}
|
||||
_ => unimplemented!()
|
||||
};
|
||||
shenv.vars_mut().export(var, &exp);
|
||||
} else {
|
||||
shenv.vars_mut().export(var, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch_command(*cmd, shenv)?;
|
||||
*shenv.vars_mut().env_mut() = saved_env;
|
||||
} else {
|
||||
while let Some(token) = assigns.next() {
|
||||
let raw = token.as_raw(shenv);
|
||||
if let Some((var,val)) = raw.split_once('=') {
|
||||
let val_rule = Lexer::get_rule(&val);
|
||||
if EXPANSIONS.contains(&val_rule) {
|
||||
let exp = match val_rule {
|
||||
TkRule::ArithSub => expand_arith_string(val,shenv)?,
|
||||
TkRule::DQuote => expand_string(val, shenv)?,
|
||||
TkRule::TildeSub => expand_tilde_string(val),
|
||||
TkRule::VarSub => {
|
||||
let val = shenv.vars().get_var(var);
|
||||
val.to_string()
|
||||
}
|
||||
_ => unimplemented!()
|
||||
};
|
||||
shenv.vars_mut().set_var(var, &exp);
|
||||
} else {
|
||||
shenv.vars_mut().set_var(var, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Executing pipeline");
|
||||
let is_bg = node.flags().contains(NdFlag::BACKGROUND);
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::Pipeline { cmds } = rule {
|
||||
let mut prev_rpipe: Option<i32> = None;
|
||||
let mut cmds = VecDeque::from(cmds);
|
||||
let mut pgid = None;
|
||||
let mut cmd_names = vec![];
|
||||
let mut pids = vec![];
|
||||
|
||||
while let Some(cmd) = cmds.pop_front() {
|
||||
let (mut r_pipe, mut w_pipe) = if cmds.is_empty() {
|
||||
// If we are on the last command, don't make new pipes
|
||||
(None,None)
|
||||
} else {
|
||||
let (r_pipe, w_pipe) = c_pipe()?;
|
||||
(Some(r_pipe),Some(w_pipe))
|
||||
};
|
||||
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
||||
let cmd_name = argv.first().unwrap().as_raw(shenv);
|
||||
cmd_names.push(cmd_name);
|
||||
} else if let NdRule::Subshell {..} = cmd.rule() {
|
||||
cmd_names.push("subshell".to_string());
|
||||
} else {
|
||||
cmd_names.push("shell cmd".to_string());
|
||||
}
|
||||
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
// Set NO_FORK since we are already in a fork, to prevent unnecessarily forking again
|
||||
shenv.ctx_mut().set_flag(ExecFlags::NO_FORK);
|
||||
// We close this r_pipe since it's the one the next command will use, so not useful here
|
||||
if let Some(r_pipe) = r_pipe.take() {
|
||||
close(r_pipe)?;
|
||||
}
|
||||
|
||||
// Create some redirections
|
||||
if let Some(w_pipe) = w_pipe.take() {
|
||||
if !cmds.is_empty() {
|
||||
let wpipe_redir = Redir::output(1, w_pipe);
|
||||
shenv.ctx_mut().push_rdr(wpipe_redir);
|
||||
}
|
||||
}
|
||||
// Use the r_pipe created in the last iteration
|
||||
if let Some(prev_rpipe) = prev_rpipe.take() {
|
||||
let rpipe_redir = Redir::input(0, prev_rpipe);
|
||||
shenv.ctx_mut().push_rdr(rpipe_redir);
|
||||
}
|
||||
|
||||
if let Err(e) = dispatch_node(cmd, shenv) {
|
||||
eprintln!("{}",e);
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
Parent { child } => {
|
||||
// Close the write pipe out here to signal EOF
|
||||
if let Some(w_pipe) = w_pipe.take() {
|
||||
close(w_pipe)?;
|
||||
}
|
||||
if pgid.is_none() {
|
||||
pgid = Some(child);
|
||||
}
|
||||
pids.push(child);
|
||||
if let Some(pipe) = prev_rpipe {
|
||||
close(pipe)?;
|
||||
}
|
||||
prev_rpipe = r_pipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut children = vec![];
|
||||
for (i,pid) in pids.iter().enumerate() {
|
||||
let command = cmd_names.get(i).unwrap();
|
||||
let child = ChildProc::new(*pid, Some(&command), pgid)?;
|
||||
children.push(child);
|
||||
}
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(pgid.unwrap())
|
||||
.build();
|
||||
dispatch_job(job, is_bg, shenv)?;
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Executing command");
|
||||
let blame = node.span();
|
||||
let is_bg = node.flags().contains(NdFlag::BACKGROUND);
|
||||
let rule = node.into_rule();
|
||||
|
||||
if let NdRule::Command { argv, redirs } = rule {
|
||||
let (argv,envp) = prep_execve(argv, shenv);
|
||||
let command = argv.first().unwrap().to_string();
|
||||
if get_bin_path(&command, shenv).is_some() {
|
||||
|
||||
log!(TRACE, "{:?}",shenv.ctx().flags());
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
log!(TRACE, "Not forking");
|
||||
shenv.collect_redirs(redirs);
|
||||
log!(TRACE, "{:?}",shenv.ctx().redirs());
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
if let Err(errno) = execvpe(command, argv, envp) {
|
||||
if errno != Errno::EFAULT {
|
||||
exit(errno as i32);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log!(TRACE, "Forking");
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
log!(TRACE, redirs);
|
||||
shenv.collect_redirs(redirs);
|
||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
||||
eprintln!("{:?}",e);
|
||||
exit(1);
|
||||
}
|
||||
execvpe(command, argv, envp)?;
|
||||
exit(1);
|
||||
}
|
||||
Parent { child } => {
|
||||
let children = vec![
|
||||
ChildProc::new(child, Some(&command), Some(child))?
|
||||
];
|
||||
let job = JobBldr::new()
|
||||
.with_children(children)
|
||||
.with_pgid(child)
|
||||
.build();
|
||||
log!(TRACE, "New job: {:?}", job);
|
||||
dispatch_job(job, is_bg, shenv)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(ShErr::full(ShErrKind::CmdNotFound, format!("{}", command), shenv.get_input(), blame))
|
||||
}
|
||||
} else { unreachable!("Found this rule in exec_cmd: {:?}", rule) }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prep_execve(argv: Vec<Token>, shenv: &mut ShEnv) -> (Vec<String>, Vec<String>) {
|
||||
log!(TRACE, "Preparing execvpe args");
|
||||
let argv_s = argv.as_strings(shenv);
|
||||
log!(TRACE, argv_s);
|
||||
|
||||
let mut envp = vec![];
|
||||
let mut env_vars = shenv.vars().env().iter();
|
||||
while let Some(entry) = env_vars.next() {
|
||||
let key = entry.0;
|
||||
let val = entry.1;
|
||||
let formatted = format!("{}={}",key,val);
|
||||
envp.push(formatted);
|
||||
}
|
||||
log!(TRACE, argv_s);
|
||||
log!(DEBUG, argv_s);
|
||||
(argv_s, envp)
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn exec_if(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
if let NdRule::IfThen { cond_blocks, else_block, redirs } = rule {
|
||||
shenv.collect_redirs(redirs);
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
|
||||
}
|
||||
let mut cond_blocks = cond_blocks.into_iter();
|
||||
|
||||
while let Some(block) = cond_blocks.next() {
|
||||
let cond = block.0;
|
||||
let body = block.1;
|
||||
let ret = shenv.exec_as_cond(cond)?;
|
||||
if ret == 0 {
|
||||
shenv.exec_as_body(body)?;
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(block) = else_block {
|
||||
shenv.exec_as_body(block)?;
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_loop(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
|
||||
if let NdRule::Loop { kind, cond, body, redirs } = rule {
|
||||
shenv.collect_redirs(redirs);
|
||||
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
|
||||
}
|
||||
|
||||
loop {
|
||||
let ret = shenv.exec_as_cond(cond.clone())?;
|
||||
match kind {
|
||||
LoopKind::While => {
|
||||
if ret == 0 {
|
||||
match shenv.exec_as_body(body.clone()) {
|
||||
Ok(_) => continue,
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
ShErrKind::LoopContinue => continue,
|
||||
ShErrKind::LoopBreak => break,
|
||||
_ => return Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { break }
|
||||
}
|
||||
LoopKind::Until => {
|
||||
if ret != 0 {
|
||||
match shenv.exec_as_body(body.clone()) {
|
||||
Ok(_) => continue,
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
ShErrKind::LoopContinue => continue,
|
||||
ShErrKind::LoopBreak => break,
|
||||
_ => return Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_for(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
|
||||
if let NdRule::ForLoop { vars, arr, body, redirs } = rule {
|
||||
shenv.collect_redirs(redirs);
|
||||
let saved_vars = shenv.vars().clone();
|
||||
|
||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||
shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK);
|
||||
}
|
||||
log!(DEBUG, vars);
|
||||
log!(DEBUG, arr);
|
||||
|
||||
for chunk in arr.chunks(vars.len()) {
|
||||
log!(DEBUG, "input: {}", shenv.get_input());
|
||||
for (var,value) in vars.iter().zip(chunk.iter()) {
|
||||
let var = var.as_raw(shenv);
|
||||
let val = value.as_raw(shenv);
|
||||
log!(DEBUG,var);
|
||||
log!(DEBUG,val);
|
||||
shenv.vars_mut().set_var(&var, &val);
|
||||
}
|
||||
|
||||
if chunk.len() < vars.len() {
|
||||
for var in &vars[chunk.len()..] { // If 'vars' is longer than the chunk, then unset the orphaned vars
|
||||
let var = var.as_raw(shenv);
|
||||
log!(DEBUG, "unsetting");
|
||||
log!(DEBUG, var);
|
||||
shenv.vars_mut().unset_var(&var);
|
||||
}
|
||||
}
|
||||
|
||||
shenv.exec_as_body(body.clone())?;
|
||||
}
|
||||
*shenv.vars_mut() = saved_vars;
|
||||
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exec_case(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
let rule = node.into_rule();
|
||||
|
||||
if let NdRule::Case { pat, blocks, redirs } = rule {
|
||||
shenv.collect_redirs(redirs);
|
||||
let mut blocks_iter = blocks.into_iter();
|
||||
let pat_raw = expand_token(pat, shenv)?
|
||||
.iter()
|
||||
.map(|tk| tk.as_raw(shenv))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
while let Some((block_pat, block)) = blocks_iter.next() {
|
||||
let block_pat_raw = block_pat.as_raw(shenv);
|
||||
let block_pat_raw = block_pat_raw.trim_end_matches(')');
|
||||
if block_pat_raw == "*" {
|
||||
let _ret = shenv.exec_as_body(block)?;
|
||||
return Ok(())
|
||||
} else if block_pat_raw.contains('|') {
|
||||
let pats = block_pat_raw.split('|');
|
||||
for pat in pats {
|
||||
if pat_raw.trim() == pat.trim() {
|
||||
let _ret = shenv.exec_as_body(block)?;
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
} else if pat_raw.trim() == block_pat_raw.trim() {
|
||||
let _ret = shenv.exec_as_body(block)?;
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
} else { unreachable!() }
|
||||
Ok(())
|
||||
}
|
||||
102
src/expand.rs
Normal file
102
src/expand.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use crate::{libsh::error::{ShErr, ShErrKind}, parse::lex::{is_hard_sep, LexFlags, LexStream, Tk, Span, TkErr, TkFlags, TkState, TkRule}, prelude::*, state::read_vars};
|
||||
|
||||
/// Variable substitution marker
|
||||
pub const VAR_SUB: char = '\u{fdd0}';
|
||||
|
||||
impl<'t> Tk<'t> {
|
||||
/// Create a new expanded token
|
||||
///
|
||||
/// params
|
||||
/// tokens: A vector of raw tokens lexed from the expansion result
|
||||
/// span: The span of the original token that is being expanded
|
||||
/// flags: some TkFlags
|
||||
pub fn expand(self, span: Span<'t>, flags: TkFlags) -> Self {
|
||||
let exp = Expander::new(self).expand();
|
||||
let class = TkRule::Expanded { exp };
|
||||
Self { class, span, err_span: None, flags, err: TkErr::Null }
|
||||
}
|
||||
pub fn get_words(&self) -> Vec<String> {
|
||||
match &self.class {
|
||||
TkRule::Expanded { exp } => exp.clone(),
|
||||
_ => vec![self.to_string()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Expander {
|
||||
raw: String,
|
||||
}
|
||||
|
||||
impl<'t> Expander {
|
||||
pub fn new(raw: Tk<'t>) -> Self {
|
||||
let unescaped = unescape_str(raw.span.as_str());
|
||||
Self { raw: unescaped }
|
||||
}
|
||||
pub fn expand(&'t mut self) -> Vec<String> {
|
||||
self.raw = self.expand_raw();
|
||||
let tokens: Vec<_> = LexStream::new(&self.raw, LexFlags::RAW)
|
||||
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
||||
.map(|tk| tk.to_string())
|
||||
.collect();
|
||||
tokens
|
||||
}
|
||||
pub fn expand_raw(&self) -> String {
|
||||
let mut chars = self.raw.chars();
|
||||
let mut result = String::new();
|
||||
let mut var_name = String::new();
|
||||
let mut in_brace = false;
|
||||
|
||||
// TODO: implement error handling for unclosed braces
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
VAR_SUB => {
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'{' => in_brace = true,
|
||||
'}' if in_brace => {
|
||||
let var_val = read_vars(|v| v.get_var(&var_name));
|
||||
result.push_str(&var_val);
|
||||
var_name.clear();
|
||||
break
|
||||
}
|
||||
_ if is_hard_sep(ch) => {
|
||||
let var_val = read_vars(|v| v.get_var(&var_name));
|
||||
result.push_str(&var_val);
|
||||
result.push(ch);
|
||||
var_name.clear();
|
||||
break
|
||||
}
|
||||
_ => var_name.push(ch),
|
||||
}
|
||||
}
|
||||
if !var_name.is_empty() {
|
||||
let var_val = read_vars(|v| v.get_var(&var_name));
|
||||
result.push_str(&var_val);
|
||||
var_name.clear();
|
||||
}
|
||||
}
|
||||
_ => result.push(ch)
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Clean up a single layer of escape characters, and then replace control characters like '$' with a non-character unicode representation that is unmistakable by the rest of the code
|
||||
pub fn unescape_str(raw: &str) -> String {
|
||||
let mut chars = raw.chars();
|
||||
let mut result = String::new();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' => {
|
||||
if let Some(next_ch) = chars.next() {
|
||||
result.push(next_ch)
|
||||
}
|
||||
}
|
||||
'$' => result.push(VAR_SUB),
|
||||
_ => result.push(ch)
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
use crate::{parse::lex::SEPARATORS, prelude::*};
|
||||
|
||||
pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
||||
let mut tokens = vec![];
|
||||
let mut work_stack = VecDeque::new();
|
||||
let logic = shenv.logic().clone();
|
||||
let mut done = false;
|
||||
|
||||
// Start with the candidate token in the work queue
|
||||
work_stack.bpush(candidate);
|
||||
|
||||
// Process until there are no more tokens in the queue
|
||||
while !done {
|
||||
done = true;
|
||||
while let Some(token) = work_stack.fpop() {
|
||||
if token.rule() == TkRule::Ident {
|
||||
let cand_str = token.as_raw(shenv);
|
||||
if let Some(alias) = logic.get_alias(&cand_str) {
|
||||
done = false;
|
||||
if !token.span().borrow().expanded {
|
||||
let mut new_tokens = shenv.expand_input(alias, token.span());
|
||||
new_tokens.retain(|tk| tk.rule() != TkRule::Whitespace);
|
||||
for token in &new_tokens {
|
||||
tokens.push(token.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tokens.push(token);
|
||||
}
|
||||
} else {
|
||||
tokens.push(token);
|
||||
}
|
||||
}
|
||||
if !done {
|
||||
work_stack.extend(tokens.drain(..));
|
||||
}
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
pub fn expand_aliases(tokens: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
|
||||
let mut stream = tokens.iter();
|
||||
let mut processed = vec![];
|
||||
let mut is_command = true;
|
||||
while let Some(token) = stream.next() {
|
||||
match token.rule() {
|
||||
_ if SEPARATORS.contains(&token.rule()) => {
|
||||
is_command = true;
|
||||
processed.push(token.clone());
|
||||
}
|
||||
TkRule::Case | TkRule::For => {
|
||||
processed.push(token.clone());
|
||||
while let Some(token) = stream.next() {
|
||||
processed.push(token.clone());
|
||||
if token.rule() == TkRule::Sep {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
TkRule::Ident if is_command => {
|
||||
is_command = false;
|
||||
let mut alias_tokens = expand_alias(token.clone(), shenv);
|
||||
if !alias_tokens.is_empty() {
|
||||
processed.append(&mut alias_tokens);
|
||||
} else {
|
||||
processed.push(token.clone());
|
||||
}
|
||||
}
|
||||
_ => processed.push(token.clone()),
|
||||
}
|
||||
}
|
||||
processed
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::vars::expand_string;
|
||||
|
||||
#[derive(Clone,PartialEq,Debug)]
|
||||
pub enum ExprToken {
|
||||
Number(f64),
|
||||
Operator(Op),
|
||||
OpenParen,
|
||||
CloseParen
|
||||
}
|
||||
|
||||
#[derive(Clone,PartialEq,Debug)]
|
||||
pub enum Op {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
IntDiv,
|
||||
Mod,
|
||||
Pow
|
||||
}
|
||||
|
||||
impl Op {
|
||||
pub fn precedence(&self) -> u8 {
|
||||
match self {
|
||||
Op::Add | Op::Sub => 1,
|
||||
Op::Mul | Op::Div | Op::IntDiv | Op::Mod => 2,
|
||||
Op::Pow => 3
|
||||
}
|
||||
}
|
||||
pub fn is_left_associative(&self) -> bool {
|
||||
*self != Op::Pow
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize_expr(expr: &str) -> ShResult<Vec<ExprToken>> {
|
||||
let mut chars = expr.chars().peekable();
|
||||
let mut tokens = vec![];
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'+' => tokens.push(ExprToken::Operator(Op::Add)),
|
||||
'-' => tokens.push(ExprToken::Operator(Op::Sub)),
|
||||
'*' => {
|
||||
if chars.peek() == Some(&'*') {
|
||||
chars.next();
|
||||
tokens.push(ExprToken::Operator(Op::Pow));
|
||||
} else {
|
||||
tokens.push(ExprToken::Operator(Op::Mul));
|
||||
}
|
||||
}
|
||||
'/' => {
|
||||
if chars.peek() == Some(&'/') {
|
||||
chars.next();
|
||||
tokens.push(ExprToken::Operator(Op::IntDiv));
|
||||
} else {
|
||||
tokens.push(ExprToken::Operator(Op::Div));
|
||||
}
|
||||
}
|
||||
'%' => tokens.push(ExprToken::Operator(Op::Mod)),
|
||||
'(' => tokens.push(ExprToken::OpenParen),
|
||||
')' => tokens.push(ExprToken::CloseParen),
|
||||
'0'..='9' => {
|
||||
let mut number = ch.to_string();
|
||||
while let Some(next_ch) = chars.peek() {
|
||||
if next_ch.is_ascii_digit() {
|
||||
number.push(chars.next().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let value = number.parse::<f64>().unwrap();
|
||||
tokens.push(ExprToken::Number(value));
|
||||
}
|
||||
' ' | '\t' => continue, // Skip whitespace
|
||||
_ => return Err(ShErr::simple(ShErrKind::ParseErr, format!("Unexpected character in arithmetic expansion: {}",ch))), // Handle unexpected characters
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn shunting_yard(tokens: Vec<ExprToken>) -> ShResult<Vec<ExprToken>> {
|
||||
let mut sorted = vec![];
|
||||
let mut operators = vec![];
|
||||
|
||||
for token in tokens {
|
||||
match token {
|
||||
ExprToken::Number(_) => sorted.push(token.clone()),
|
||||
ExprToken::Operator(ref op) => {
|
||||
while let Some(top) = operators.last() {
|
||||
if let ExprToken::Operator(top_op) = top {
|
||||
if (op.is_left_associative() && op.precedence() <= top_op.precedence())
|
||||
|| (!op.is_left_associative() && op.precedence() < top_op.precedence())
|
||||
{
|
||||
sorted.push(operators.pop().unwrap())
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
operators.push(token.clone())
|
||||
}
|
||||
ExprToken::OpenParen => operators.push(token.clone()),
|
||||
ExprToken::CloseParen => {
|
||||
while let Some(top) = operators.pop() {
|
||||
if matches!(top, ExprToken::OpenParen) {
|
||||
break;
|
||||
}
|
||||
sorted.push(top);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(op) = operators.pop() {
|
||||
if matches!(op, ExprToken::OpenParen | ExprToken::CloseParen) {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Mismatched parenthesis in arithmetic expansion"))
|
||||
}
|
||||
sorted.push(op);
|
||||
}
|
||||
|
||||
Ok(sorted)
|
||||
}
|
||||
|
||||
pub fn eval_rpn(tokens: Vec<ExprToken>) -> ShResult<f64> {
|
||||
let mut stack = vec![];
|
||||
|
||||
for token in tokens {
|
||||
match token {
|
||||
ExprToken::Number(num) => stack.push(num),
|
||||
ExprToken::Operator(op) => {
|
||||
if stack.len() < 2 {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Not enough operands in arithmetic expansion"))
|
||||
}
|
||||
let rhs = stack.pop().unwrap();
|
||||
let lhs = stack.pop().unwrap();
|
||||
let result = match op {
|
||||
Op::Add => lhs + rhs,
|
||||
Op::Sub => lhs - rhs,
|
||||
Op::Mul => lhs * rhs,
|
||||
Op::Mod => lhs % rhs,
|
||||
Op::Pow => lhs.powf(rhs),
|
||||
Op::Div => {
|
||||
if rhs == 0.0 {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Attempt to divide by zero in arithmetic expansion"))
|
||||
}
|
||||
lhs / rhs
|
||||
}
|
||||
Op::IntDiv => {
|
||||
if rhs == 0.0 {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Attempt to divide by zero in arithmetic expansion"))
|
||||
}
|
||||
(lhs as i64 / rhs as i64) as f64
|
||||
}
|
||||
};
|
||||
stack.push(result);
|
||||
}
|
||||
ExprToken::OpenParen => todo!(),
|
||||
ExprToken::CloseParen => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(stack.pop().unwrap())
|
||||
}
|
||||
|
||||
pub fn expand_arith_token(token: Token, shenv: &mut ShEnv) -> ShResult<Token> {
|
||||
// I mean hey it works
|
||||
let token_raw = token.as_raw(shenv);
|
||||
|
||||
let arith_raw = token_raw.trim_matches('`');
|
||||
|
||||
let result = expand_arith_string(arith_raw,shenv)?;
|
||||
|
||||
let mut final_expansion = shenv.expand_input(&result, token.span());
|
||||
|
||||
Ok(final_expansion.pop().unwrap_or(token))
|
||||
}
|
||||
|
||||
pub fn expand_arith_string(s: &str,shenv: &mut ShEnv) -> ShResult<String> {
|
||||
let mut exp = expand_string(s,shenv)?;
|
||||
if exp.starts_with('`') && s.ends_with('`') {
|
||||
exp = exp[1..exp.len() - 1].to_string();
|
||||
}
|
||||
let expr_tokens = shunting_yard(tokenize_expr(&exp)?)?;
|
||||
log!(DEBUG,expr_tokens);
|
||||
let result = eval_rpn(expr_tokens)?.to_string();
|
||||
Ok(result)
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
use crate::{expand::vars::expand_string, prelude::*};
|
||||
|
||||
pub fn expand_brace_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
||||
let raw = token.as_raw(shenv);
|
||||
let raw_exp = expand_string(&raw, shenv)?;
|
||||
log!(DEBUG, raw_exp);
|
||||
let expanded = expand_brace_string(&raw_exp);
|
||||
log!(DEBUG, expanded);
|
||||
let mut new_tokens = shenv.expand_input(&expanded, token.span());
|
||||
new_tokens.retain(|tk| tk.rule() != TkRule::Whitespace);
|
||||
log!(DEBUG, new_tokens);
|
||||
Ok(new_tokens)
|
||||
}
|
||||
|
||||
pub fn expand_brace_string(raw: &str) -> String {
|
||||
let mut result = VecDeque::new();
|
||||
let mut stack = vec![];
|
||||
stack.push(raw.to_string());
|
||||
|
||||
while let Some(current) = stack.pop() {
|
||||
if let Some((prefix,braces,suffix)) = get_brace_positions(¤t) {
|
||||
let expanded = expand_brace_inner(&braces);
|
||||
for part in expanded {
|
||||
let formatted = format!("{prefix}{part}{suffix}");
|
||||
stack.push(formatted);
|
||||
}
|
||||
} else {
|
||||
result.fpush(current);
|
||||
}
|
||||
}
|
||||
|
||||
result.into_iter().collect::<Vec<_>>().join(" ")
|
||||
}
|
||||
|
||||
pub fn get_brace_positions(slice: &str) -> Option<(String, String, String)> {
|
||||
let mut chars = slice.chars().enumerate();
|
||||
let mut start = None;
|
||||
let mut brc_count = 0;
|
||||
while let Some((i,ch)) = chars.next() {
|
||||
match ch {
|
||||
'{' => {
|
||||
if brc_count == 0 {
|
||||
start = Some(i);
|
||||
}
|
||||
brc_count += 1;
|
||||
}
|
||||
'}' => {
|
||||
brc_count -= 1;
|
||||
if brc_count == 0 {
|
||||
if let Some(start) = start {
|
||||
let prefix = slice[..start].to_string();
|
||||
let braces = slice[start+1..i].to_string();
|
||||
let suffix = slice[i+1..].to_string();
|
||||
return Some((prefix,braces,suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn expand_brace_inner(inner: &str) -> Vec<String> {
|
||||
if inner.split_once("..").is_some() && !inner.contains(['{','}']) {
|
||||
expand_range(inner)
|
||||
} else {
|
||||
split_list(inner)
|
||||
}
|
||||
}
|
||||
|
||||
fn split_list(list: &str) -> Vec<String> {
|
||||
log!(DEBUG, list);
|
||||
let mut chars = list.chars();
|
||||
let mut items = vec![];
|
||||
let mut curr_item = String::new();
|
||||
let mut brc_count = 0;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
',' if brc_count == 0 => {
|
||||
if !curr_item.is_empty() {
|
||||
items.push(std::mem::take(&mut curr_item));
|
||||
}
|
||||
}
|
||||
'{' => {
|
||||
brc_count += 1;
|
||||
curr_item.push(ch);
|
||||
}
|
||||
'}' => {
|
||||
if brc_count == 0 {
|
||||
return vec![list.to_string()];
|
||||
}
|
||||
brc_count -= 1;
|
||||
curr_item.push(ch);
|
||||
}
|
||||
_ => curr_item.push(ch),
|
||||
}
|
||||
}
|
||||
if !curr_item.is_empty() {
|
||||
items.push(std::mem::take(&mut curr_item))
|
||||
}
|
||||
log!(DEBUG,items);
|
||||
items
|
||||
}
|
||||
|
||||
fn expand_range(range: &str) -> Vec<String> {
|
||||
if let Some((left,right)) = range.split_once("..") {
|
||||
// I know, I know
|
||||
// This is checking to see if the range looks like "a..b" or "A..B"
|
||||
// one character on both sides, both are letters, AND (both are uppercase OR both are lowercase)
|
||||
if (left.len() == 1 && right.len() == 1) &&
|
||||
(left.chars().all(|ch| ch.is_ascii_alphanumeric() && right.chars().all(|ch| ch.is_ascii_alphanumeric()))) &&
|
||||
(
|
||||
(left.chars().all(|ch| ch.is_uppercase()) && right.chars().all(|ch| ch.is_uppercase())) ||
|
||||
(left.chars().all(|ch| ch.is_lowercase()) && right.chars().all(|ch| ch.is_lowercase()))
|
||||
)
|
||||
{
|
||||
expand_range_alpha(left, right)
|
||||
}
|
||||
else if right.chars().all(|ch| ch.is_ascii_digit()) && left.chars().all(|ch| ch.is_ascii_digit())
|
||||
{
|
||||
expand_range_numeric(left, right)
|
||||
}
|
||||
else
|
||||
{
|
||||
vec![range.to_string()]
|
||||
}
|
||||
} else {
|
||||
vec![range.to_string()]
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_range_alpha(left: &str, right: &str) -> Vec<String> {
|
||||
let start = left.chars().next().unwrap() as u8;
|
||||
let end = right.chars().next().unwrap() as u8;
|
||||
|
||||
if start > end {
|
||||
(end..=start).rev().map(|c| (c as char).to_string()).collect()
|
||||
} else {
|
||||
(start..=end).map(|c| (c as char).to_string()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_range_numeric(left: &str, right: &str) -> Vec<String> {
|
||||
let start = left.parse::<i32>().unwrap();
|
||||
let end = right.parse::<i32>().unwrap();
|
||||
(start..=end).map(|i| i.to_string()).collect()
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn expand_cmdsub_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
||||
let cmdsub_raw = token.as_raw(shenv);
|
||||
let output = expand_cmdsub_string(&cmdsub_raw, shenv)?;
|
||||
let new_tokens = shenv.expand_input(&output, token.span());
|
||||
|
||||
Ok(new_tokens)
|
||||
}
|
||||
|
||||
pub fn expand_cmdsub_string(mut s: &str, shenv: &mut ShEnv) -> ShResult<String> {
|
||||
if s.starts_with("$(") && s.ends_with(')') {
|
||||
s = &s[2..s.len() - 1]; // From '$(this)' to 'this'
|
||||
}
|
||||
|
||||
let (r_pipe,w_pipe) = c_pipe()?;
|
||||
let pipe_redir = Redir::output(1, w_pipe);
|
||||
let mut sub_shenv = shenv.clone();
|
||||
sub_shenv.ctx_mut().set_flag(ExecFlags::NO_FORK);
|
||||
sub_shenv.collect_redirs(vec![pipe_redir]);
|
||||
|
||||
match unsafe { fork()? } {
|
||||
Child => {
|
||||
close(r_pipe).ok();
|
||||
exec_input(s, &mut sub_shenv).abort_if_err();
|
||||
exit(0);
|
||||
}
|
||||
Parent { child: _ } => {
|
||||
close(w_pipe).ok();
|
||||
}
|
||||
}
|
||||
let result = read_to_string(r_pipe);
|
||||
close(r_pipe)?;
|
||||
Ok(result?)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
pub mod vars;
|
||||
pub mod tilde;
|
||||
pub mod alias;
|
||||
pub mod cmdsub;
|
||||
pub mod arithmetic;
|
||||
pub mod prompt;
|
||||
pub mod brace;
|
||||
|
||||
use arithmetic::expand_arith_token;
|
||||
use brace::expand_brace_token;
|
||||
use cmdsub::expand_cmdsub_token;
|
||||
use vars::{expand_string, expand_var};
|
||||
use tilde::expand_tilde_token;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
||||
let mut processed = vec![];
|
||||
for arg in argv {
|
||||
log!(TRACE, "{}",arg.as_raw(shenv));
|
||||
log!(TRACE, processed);
|
||||
let mut expanded = expand_token(arg, shenv)?;
|
||||
processed.append(&mut expanded);
|
||||
}
|
||||
Ok(processed)
|
||||
}
|
||||
|
||||
pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
||||
let mut processed = vec![];
|
||||
match token.rule() {
|
||||
TkRule::DQuote => {
|
||||
let dquote_exp = expand_string(&token.as_raw(shenv), shenv)?;
|
||||
let mut expanded = shenv.expand_input(&dquote_exp, token.span());
|
||||
processed.append(&mut expanded);
|
||||
}
|
||||
TkRule::VarSub => {
|
||||
let mut varsub_exp = expand_var(token.clone(), shenv);
|
||||
processed.append(&mut varsub_exp);
|
||||
}
|
||||
TkRule::TildeSub => {
|
||||
let tilde_exp = expand_tilde_token(token.clone(), shenv);
|
||||
processed.push(tilde_exp);
|
||||
}
|
||||
TkRule::ArithSub => {
|
||||
let arith_exp = expand_arith_token(token.clone(), shenv)?;
|
||||
processed.push(arith_exp);
|
||||
}
|
||||
TkRule::BraceExp => {
|
||||
let mut brace_exp = expand_brace_token(token, shenv)?;
|
||||
processed.append(&mut brace_exp);
|
||||
}
|
||||
TkRule::CmdSub => {
|
||||
let mut cmdsub_exp = expand_cmdsub_token(token.clone(), shenv)?;
|
||||
processed.append(&mut cmdsub_exp);
|
||||
}
|
||||
_ => {
|
||||
if token.rule() != TkRule::Ident {
|
||||
log!(WARN, "found this in expand_token: {:?}", token.rule());
|
||||
}
|
||||
processed.push(token.clone())
|
||||
}
|
||||
}
|
||||
Ok(processed)
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PromptTk {
|
||||
AsciiOct(i32),
|
||||
Text(String),
|
||||
AnsiSeq(String),
|
||||
VisGrp,
|
||||
UserSeq,
|
||||
Runtime,
|
||||
Weekday,
|
||||
Dquote,
|
||||
Squote,
|
||||
Return,
|
||||
Newline,
|
||||
Pwd,
|
||||
PwdShort,
|
||||
Hostname,
|
||||
HostnameShort,
|
||||
ShellName,
|
||||
Username,
|
||||
PromptSymbol,
|
||||
ExitCode,
|
||||
SuccessSymbol,
|
||||
FailureSymbol,
|
||||
JobCount
|
||||
}
|
||||
|
||||
pub fn format_cmd_runtime(dur: std::time::Duration) -> String {
|
||||
const ETERNITY: u128 = f32::INFINITY as u128;
|
||||
let mut micros = dur.as_micros();
|
||||
let mut millis = 0;
|
||||
let mut seconds = 0;
|
||||
let mut minutes = 0;
|
||||
let mut hours = 0;
|
||||
let mut days = 0;
|
||||
let mut weeks = 0;
|
||||
let mut months = 0;
|
||||
let mut years = 0;
|
||||
let mut decades = 0;
|
||||
let mut centuries = 0;
|
||||
let mut millennia = 0;
|
||||
let mut epochs = 0;
|
||||
let mut aeons = 0;
|
||||
let mut eternities = 0;
|
||||
|
||||
if micros >= 1000 {
|
||||
millis = micros / 1000;
|
||||
micros %= 1000;
|
||||
}
|
||||
if millis >= 1000 {
|
||||
seconds = millis / 1000;
|
||||
millis %= 1000;
|
||||
}
|
||||
if seconds >= 60 {
|
||||
minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
}
|
||||
if minutes >= 60 {
|
||||
hours = minutes / 60;
|
||||
minutes %= 60;
|
||||
}
|
||||
if hours >= 24 {
|
||||
days = hours / 24;
|
||||
hours %= 24;
|
||||
}
|
||||
if days >= 7 {
|
||||
weeks = days / 7;
|
||||
days %= 7;
|
||||
}
|
||||
if weeks >= 4 {
|
||||
months = weeks / 4;
|
||||
weeks %= 4;
|
||||
}
|
||||
if months >= 12 {
|
||||
years = months / 12;
|
||||
weeks %= 12;
|
||||
}
|
||||
if years >= 10 {
|
||||
decades = years / 10;
|
||||
years %= 10;
|
||||
}
|
||||
if decades >= 10 {
|
||||
centuries = decades / 10;
|
||||
decades %= 10;
|
||||
}
|
||||
if centuries >= 10 {
|
||||
millennia = centuries / 10;
|
||||
centuries %= 10;
|
||||
}
|
||||
if millennia >= 1000 {
|
||||
epochs = millennia / 1000;
|
||||
millennia %= 1000;
|
||||
}
|
||||
if epochs >= 1000 {
|
||||
aeons = epochs / 1000;
|
||||
epochs %= aeons;
|
||||
}
|
||||
if aeons == ETERNITY {
|
||||
eternities = aeons / ETERNITY;
|
||||
aeons %= ETERNITY;
|
||||
}
|
||||
|
||||
// Format the result
|
||||
let mut result = Vec::new();
|
||||
if eternities > 0 {
|
||||
let mut string = format!("{} eternit", eternities);
|
||||
if eternities > 1 {
|
||||
string.push_str("ies");
|
||||
} else {
|
||||
string.push('y');
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if aeons > 0 {
|
||||
let mut string = format!("{} aeon", aeons);
|
||||
if aeons > 1 {
|
||||
string.push('s')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if epochs > 0 {
|
||||
let mut string = format!("{} epoch", epochs);
|
||||
if epochs > 1 {
|
||||
string.push('s')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if millennia > 0 {
|
||||
let mut string = format!("{} millenni", millennia);
|
||||
if millennia > 1 {
|
||||
string.push_str("um")
|
||||
} else {
|
||||
string.push('a')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if centuries > 0 {
|
||||
let mut string = format!("{} centur", centuries);
|
||||
if centuries > 1 {
|
||||
string.push_str("ies")
|
||||
} else {
|
||||
string.push('y')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if decades > 0 {
|
||||
let mut string = format!("{} decade", decades);
|
||||
if decades > 1 {
|
||||
string.push('s')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if years > 0 {
|
||||
let mut string = format!("{} year", years);
|
||||
if years > 1 {
|
||||
string.push('s')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if months > 0 {
|
||||
let mut string = format!("{} month", months);
|
||||
if months > 1 {
|
||||
string.push('s')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if weeks > 0 {
|
||||
let mut string = format!("{} week", weeks);
|
||||
if weeks > 1 {
|
||||
string.push('s')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if days > 0 {
|
||||
let mut string = format!("{} day", days);
|
||||
if days > 1 {
|
||||
string.push('s')
|
||||
}
|
||||
result.push(string)
|
||||
}
|
||||
if hours > 0 {
|
||||
let string = format!("{}h", hours);
|
||||
result.push(string);
|
||||
}
|
||||
if minutes > 0 {
|
||||
let string = format!("{}m", minutes);
|
||||
result.push(string);
|
||||
}
|
||||
if seconds > 0 {
|
||||
let string = format!("{}s", seconds);
|
||||
result.push(string);
|
||||
}
|
||||
if millis > 0 {
|
||||
let string = format!("{}ms",millis);
|
||||
result.push(string);
|
||||
}
|
||||
if result.is_empty() && micros > 0 {
|
||||
let string = format!("{}µs",micros);
|
||||
result.push(string);
|
||||
}
|
||||
|
||||
result.join(" ")
|
||||
}
|
||||
|
||||
fn tokenize_prompt(raw: &str) -> Vec<PromptTk> {
|
||||
let mut chars = raw.chars().peekable();
|
||||
let mut tk_text = String::new();
|
||||
let mut tokens = vec![];
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' => {
|
||||
// Push any accumulated text as a token
|
||||
if !tk_text.is_empty() {
|
||||
tokens.push(PromptTk::Text(std::mem::take(&mut tk_text)));
|
||||
}
|
||||
|
||||
// Handle the escape sequence
|
||||
if let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'w' => tokens.push(PromptTk::Pwd),
|
||||
'W' => tokens.push(PromptTk::PwdShort),
|
||||
'h' => tokens.push(PromptTk::Hostname),
|
||||
'H' => tokens.push(PromptTk::HostnameShort),
|
||||
's' => tokens.push(PromptTk::ShellName),
|
||||
'u' => tokens.push(PromptTk::Username),
|
||||
'$' => tokens.push(PromptTk::PromptSymbol),
|
||||
'n' => tokens.push(PromptTk::Text("\n".into())),
|
||||
'r' => tokens.push(PromptTk::Text("\r".into())),
|
||||
'T' => tokens.push(PromptTk::Runtime),
|
||||
'\\' => tokens.push(PromptTk::Text("\\".into())),
|
||||
'"' => tokens.push(PromptTk::Text("\"".into())),
|
||||
'\'' => tokens.push(PromptTk::Text("'".into())),
|
||||
'e' => {
|
||||
if chars.next() == Some('[') {
|
||||
let mut params = String::new();
|
||||
|
||||
// Collect parameters and final character
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'0'..='9' | ';' | '?' | ':' => params.push(ch), // Valid parameter characters
|
||||
'A'..='Z' | 'a'..='z' => { // Final character (letter)
|
||||
params.push(ch);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
// Invalid character in ANSI sequence
|
||||
tokens.push(PromptTk::Text(format!("\x1b[{params}")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push(PromptTk::AnsiSeq(format!("\x1b[{params}")));
|
||||
} else {
|
||||
// Handle case where 'e' is not followed by '['
|
||||
tokens.push(PromptTk::Text("\\e".into()));
|
||||
}
|
||||
}
|
||||
'0'..='7' => {
|
||||
// Handle octal escape
|
||||
let mut octal_str = String::new();
|
||||
octal_str.push(ch);
|
||||
|
||||
// Collect up to 2 more octal digits
|
||||
for _ in 0..2 {
|
||||
if let Some(&next_ch) = chars.peek() {
|
||||
if next_ch >= '0' && next_ch <= '7' {
|
||||
octal_str.push(chars.next().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the octal string into an integer
|
||||
if let Ok(octal) = i32::from_str_radix(&octal_str, 8) {
|
||||
tokens.push(PromptTk::AsciiOct(octal));
|
||||
} else {
|
||||
// Fallback: treat as raw text
|
||||
tokens.push(PromptTk::Text(format!("\\{octal_str}")));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Unknown escape sequence: treat as raw text
|
||||
tokens.push(PromptTk::Text(format!("\\{ch}")));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle trailing backslash
|
||||
tokens.push(PromptTk::Text("\\".into()));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Accumulate non-escape characters
|
||||
tk_text.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push any remaining text as a token
|
||||
if !tk_text.is_empty() {
|
||||
tokens.push(PromptTk::Text(tk_text));
|
||||
}
|
||||
|
||||
tokens
|
||||
}
|
||||
|
||||
pub fn expand_prompt(raw: &str, shenv: &mut ShEnv) -> ShResult<String> {
|
||||
let mut tokens = tokenize_prompt(raw).into_iter();
|
||||
let mut result = String::new();
|
||||
|
||||
while let Some(token) = tokens.next() {
|
||||
match token {
|
||||
PromptTk::AsciiOct(_) => todo!(),
|
||||
PromptTk::Text(txt) => result.push_str(&txt),
|
||||
PromptTk::AnsiSeq(params) => result.push_str(¶ms),
|
||||
PromptTk::Runtime => {
|
||||
log!(INFO, "getting runtime");
|
||||
if let Some(runtime) = shenv.meta().get_runtime() {
|
||||
log!(DEBUG, runtime);
|
||||
let runtime_fmt = format_cmd_runtime(runtime);
|
||||
result.push_str(&runtime_fmt);
|
||||
}
|
||||
}
|
||||
PromptTk::Pwd => {
|
||||
let mut pwd = std::env::var("PWD")?;
|
||||
let home = std::env::var("HOME")?;
|
||||
if pwd.starts_with(&home) {
|
||||
pwd = pwd.replacen(&home, "~", 1);
|
||||
}
|
||||
result.push_str(&pwd);
|
||||
}
|
||||
PromptTk::PwdShort => {
|
||||
let mut path = std::env::var("PWD")?;
|
||||
let home = std::env::var("HOME")?;
|
||||
if path.starts_with(&home) {
|
||||
path = path.replacen(&home, "~", 1);
|
||||
}
|
||||
let pathbuf = PathBuf::from(&path);
|
||||
let mut segments = pathbuf.iter().count();
|
||||
let mut path_iter = pathbuf.into_iter();
|
||||
while segments > 4 {
|
||||
path_iter.next();
|
||||
segments -= 1;
|
||||
}
|
||||
let path_rebuilt: PathBuf = path_iter.collect();
|
||||
let mut path_rebuilt = path_rebuilt.to_str().unwrap().to_string();
|
||||
if path_rebuilt.starts_with(&home) {
|
||||
path_rebuilt = path_rebuilt.replacen(&home, "~", 1);
|
||||
}
|
||||
result.push_str(&path_rebuilt);
|
||||
}
|
||||
PromptTk::Hostname => {
|
||||
let hostname = std::env::var("HOSTNAME")?;
|
||||
result.push_str(&hostname);
|
||||
}
|
||||
PromptTk::HostnameShort => todo!(),
|
||||
PromptTk::ShellName => result.push_str("fern"),
|
||||
PromptTk::Username => {
|
||||
let username = std::env::var("USER")?;
|
||||
result.push_str(&username);
|
||||
}
|
||||
PromptTk::PromptSymbol => {
|
||||
let uid = std::env::var("UID")?;
|
||||
let symbol = if &uid == "0" {
|
||||
'#'
|
||||
} else {
|
||||
'$'
|
||||
};
|
||||
result.push(symbol);
|
||||
}
|
||||
PromptTk::ExitCode => todo!(),
|
||||
PromptTk::SuccessSymbol => todo!(),
|
||||
PromptTk::FailureSymbol => todo!(),
|
||||
PromptTk::JobCount => todo!(),
|
||||
_ => unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn expand_tilde_token(tilde_sub: Token, shenv: &mut ShEnv) -> Token {
|
||||
let tilde_sub_raw = tilde_sub.as_raw(shenv);
|
||||
let result = expand_tilde_string(&tilde_sub_raw);
|
||||
if result == tilde_sub_raw {
|
||||
return tilde_sub
|
||||
}
|
||||
shenv.expand_input(&result, tilde_sub.span()).pop().unwrap_or(tilde_sub)
|
||||
}
|
||||
|
||||
pub fn expand_tilde_string(s: &str) -> String {
|
||||
if s.starts_with('~') {
|
||||
let home = std::env::var("HOME").unwrap_or_default();
|
||||
s.replacen('~', &home, 1)
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
use crate::{parse::lex::Token, prelude::*};
|
||||
|
||||
use super::cmdsub::expand_cmdsub_string;
|
||||
|
||||
pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
||||
let var_name = var_sub.as_raw(shenv);
|
||||
let var_name = var_name.trim_start_matches('$').trim_matches(['{','}']);
|
||||
let value = shenv.vars().get_var(var_name).to_string();
|
||||
|
||||
shenv.expand_input(&value, var_sub.span())
|
||||
}
|
||||
|
||||
pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult<String> {
|
||||
log!(DEBUG, s);
|
||||
let mut result = String::new();
|
||||
let mut var_name = String::new();
|
||||
let mut chars = s.chars().peekable();
|
||||
let mut in_brace = false;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' => {
|
||||
result.push(ch);
|
||||
if let Some(next_ch) = chars.next() {
|
||||
result.push(next_ch)
|
||||
}
|
||||
}
|
||||
'$' => {
|
||||
let mut expanded = false;
|
||||
while let Some(ch) = chars.peek() {
|
||||
if *ch == '"' || *ch == '`' {
|
||||
break
|
||||
}
|
||||
let ch = chars.next().unwrap();
|
||||
log!(DEBUG,var_name);
|
||||
match ch {
|
||||
'{' if var_name.is_empty() => {
|
||||
in_brace = true;
|
||||
}
|
||||
'}' if in_brace => {
|
||||
let value = shenv.vars().get_var(&var_name);
|
||||
result.push_str(value);
|
||||
expanded = true;
|
||||
break
|
||||
}
|
||||
'(' if var_name.is_empty() => {
|
||||
let mut paren_count = 1;
|
||||
var_name.push_str("$(");
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'(' => {
|
||||
paren_count += 1;
|
||||
var_name.push(ch);
|
||||
}
|
||||
')' => {
|
||||
paren_count -= 1;
|
||||
var_name.push(ch);
|
||||
if paren_count == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
_ => var_name.push(ch)
|
||||
}
|
||||
}
|
||||
let value = expand_cmdsub_string(&var_name, shenv)?;
|
||||
result.push_str(&value);
|
||||
expanded = true;
|
||||
break
|
||||
}
|
||||
_ if ch.is_ascii_digit() && var_name.is_empty() && !in_brace => {
|
||||
var_name.push(ch);
|
||||
let value = shenv.vars().get_var(&var_name);
|
||||
result.push_str(value);
|
||||
expanded = true;
|
||||
break
|
||||
}
|
||||
'@' | '#' | '*' | '-' | '?' | '!' | '$' if var_name.is_empty() => {
|
||||
var_name.push(ch);
|
||||
let value = shenv.vars().get_var(&var_name);
|
||||
result.push_str(value);
|
||||
expanded = true;
|
||||
break
|
||||
}
|
||||
' ' | '\t' | '\n' | ';' | ',' | '{' => {
|
||||
let value = shenv.vars().get_var(&var_name);
|
||||
result.push_str(value);
|
||||
result.push(ch);
|
||||
expanded = true;
|
||||
break
|
||||
}
|
||||
_ => var_name.push(ch)
|
||||
}
|
||||
}
|
||||
if !expanded {
|
||||
let value = shenv.vars().get_var(&var_name);
|
||||
result.push_str(value);
|
||||
}
|
||||
var_name.clear();
|
||||
}
|
||||
_ => result.push(ch)
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
43
src/fern.rs
Normal file
43
src/fern.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
pub mod prelude;
|
||||
pub mod libsh;
|
||||
pub mod prompt;
|
||||
pub mod procio;
|
||||
pub mod parse;
|
||||
pub mod expand;
|
||||
pub mod state;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use parse::{execute::{get_pipe_stack, Dispatcher}, lex::{LexFlags, LexStream}, ParseResult, ParseStream};
|
||||
use state::write_vars;
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
let input = prompt::read_line().unwrap();
|
||||
if input == "quit" { break };
|
||||
write_vars(|v| v.new_var("foo", "bar"));
|
||||
|
||||
let mut tokens = vec![];
|
||||
for token in LexStream::new(&input, LexFlags::empty()) {
|
||||
if token.is_err() {
|
||||
let error = format!("{:?}: {}",token.err,token.err_span.unwrap().as_str());
|
||||
panic!("{error}");
|
||||
}
|
||||
tokens.push(token);
|
||||
}
|
||||
|
||||
let mut nodes = vec![];
|
||||
for result in ParseStream::new(tokens) {
|
||||
match result {
|
||||
ParseResult::Error(e) => panic!("{}",e),
|
||||
ParseResult::Match(node) => nodes.push(node),
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
let mut dispatcher = Dispatcher::new(nodes);
|
||||
dispatcher.begin_dispatch().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
foobar
|
||||
@@ -1 +0,0 @@
|
||||
foo
|
||||
@@ -1,36 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub trait VecDequeAliases<T> {
|
||||
fn fpop(&mut self) -> Option<T>;
|
||||
fn fpush(&mut self, value: T);
|
||||
fn bpop(&mut self) -> Option<T>;
|
||||
fn bpush(&mut self, value: T);
|
||||
fn to_vec(self) -> Vec<T>;
|
||||
}
|
||||
|
||||
impl<T> VecDequeAliases<T> for VecDeque<T> {
|
||||
/// Alias for pop_front()
|
||||
fn fpop(&mut self) -> Option<T> {
|
||||
self.pop_front()
|
||||
}
|
||||
/// Alias for push_front()
|
||||
fn fpush(&mut self, value: T) {
|
||||
self.push_front(value);
|
||||
}
|
||||
/// Alias for pop_back()
|
||||
fn bpop(&mut self) -> Option<T> {
|
||||
self.pop_back()
|
||||
}
|
||||
/// Alias for push_back()
|
||||
fn bpush(&mut self, value: T) {
|
||||
self.push_back(value);
|
||||
}
|
||||
/// Just turns the deque into a vector
|
||||
fn to_vec(mut self) -> Vec<T> {
|
||||
let mut vec = vec![];
|
||||
while let Some(item) = self.fpop() {
|
||||
vec.push(item)
|
||||
}
|
||||
vec
|
||||
}
|
||||
}
|
||||
@@ -1,119 +1,81 @@
|
||||
use std::fmt::Display;
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use crate::parse::lex::Span;
|
||||
use crate::prelude::*;
|
||||
use crate::{parse::lex::Span, prelude::*};
|
||||
|
||||
pub type ShResult<T> = Result<T,ShErr>;
|
||||
pub type ShResult<'s,T> = Result<T,ShErr<'s>>;
|
||||
|
||||
pub trait ResultExt {
|
||||
fn eprint(self) -> Self;
|
||||
fn abort_if_err(&self);
|
||||
#[derive(Debug)]
|
||||
pub enum ShErr<'s> {
|
||||
Simple { kind: ShErrKind, msg: String },
|
||||
Full { kind: ShErrKind, msg: String, span: Span<'s> }
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct BlamePair {
|
||||
input: String,
|
||||
span: Rc<RefCell<Span>>
|
||||
}
|
||||
|
||||
impl BlamePair {
|
||||
pub fn new(input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
Self { input, span }
|
||||
impl<'s> ShErr<'s> {
|
||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||
let msg = msg.into();
|
||||
Self::Simple { kind, msg }
|
||||
}
|
||||
pub fn start(&self) -> usize {
|
||||
self.span.borrow().start()
|
||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span<'s>) -> Self {
|
||||
let msg = msg.into();
|
||||
Self::Full { kind, msg, span }
|
||||
}
|
||||
pub fn end(&self) -> usize {
|
||||
self.span.borrow().end()
|
||||
pub fn unpack(self) -> (ShErrKind,String,Option<Span<'s>>) {
|
||||
match self {
|
||||
ShErr::Simple { kind, msg } => (kind,msg,None),
|
||||
ShErr::Full { kind, msg, span } => (kind,msg,Some(span))
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.input.len()
|
||||
}
|
||||
pub fn with_span(sherr: ShErr, span: Span<'s>) -> Self {
|
||||
let (kind,msg,_) = sherr.unpack();
|
||||
Self::Full { kind, msg, span }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<String> for BlamePair {
|
||||
fn into(self) -> String {
|
||||
self.input
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: Display> ResultExt for Result<T, E> {
|
||||
fn eprint(self) -> Self {
|
||||
if let Err(err) = &self {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
self
|
||||
}
|
||||
fn abort_if_err(&self) {
|
||||
if let Err(err) = &self {
|
||||
eprintln!("{}", err);
|
||||
sh_quit(1)
|
||||
impl<'s> Display for ShErr<'s> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg),
|
||||
Self::Full { msg, kind: _, span: _ } => writeln!(f, "{}", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Blame {
|
||||
/// Blame a span for a propagated error. This will convert a ShErr::Simple into a ShErr::Full
|
||||
/// This will also set the span on a ShErr::Builder
|
||||
fn blame(self, input: String, span: Rc<RefCell<Span>>) -> Self;
|
||||
|
||||
/// If an error is propagated to this point, then attempt to blame a span.
|
||||
/// If the error in question has already blamed a span, don't overwrite it.
|
||||
/// Used as a last resort in higher level contexts in case an error somehow goes unblamed
|
||||
fn try_blame(self, input: String, span: Rc<RefCell<Span>>) -> Self;
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ShErr {
|
||||
impl<'s> From<std::io::Error> for ShErr<'s> {
|
||||
fn from(_: std::io::Error) -> Self {
|
||||
ShErr::io()
|
||||
let msg = std::io::Error::last_os_error();
|
||||
ShErr::simple(ShErrKind::IoErr, msg.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::env::VarError> for ShErr {
|
||||
impl<'s> From<std::env::VarError> for ShErr<'s> {
|
||||
fn from(value: std::env::VarError) -> Self {
|
||||
ShErr::simple(ShErrKind::InternalErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rustyline::error::ReadlineError> for ShErr {
|
||||
impl<'s> From<rustyline::error::ReadlineError> for ShErr<'s> {
|
||||
fn from(value: rustyline::error::ReadlineError) -> Self {
|
||||
ShErr::simple(ShErrKind::ParseErr, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Errno> for ShErr {
|
||||
impl<'s> From<Errno> for ShErr<'s> {
|
||||
fn from(value: Errno) -> Self {
|
||||
ShErr::simple(ShErrKind::Errno, &value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Blame for Result<T,ShErr> {
|
||||
fn blame(self, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
if let Err(mut e) = self {
|
||||
e.blame(input,span);
|
||||
Err(e)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
fn try_blame(self, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
if let Err(mut e) = self {
|
||||
e.try_blame(input,span);
|
||||
Err(e)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Copy,Clone,PartialEq,Eq)]
|
||||
#[derive(Debug)]
|
||||
pub enum ShErrKind {
|
||||
IoErr,
|
||||
SyntaxErr,
|
||||
ParseErr,
|
||||
InternalErr,
|
||||
ExecFail,
|
||||
ResourceLimitExceeded,
|
||||
BadPermission,
|
||||
Errno,
|
||||
FileNotFound,
|
||||
CmdNotFound,
|
||||
CleanExit,
|
||||
FuncReturn,
|
||||
@@ -121,156 +83,3 @@ pub enum ShErrKind {
|
||||
LoopBreak,
|
||||
Null
|
||||
}
|
||||
|
||||
impl Default for ShErrKind {
|
||||
fn default() -> Self {
|
||||
Self::Null
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum ShErr {
|
||||
Simple { kind: ShErrKind, message: String },
|
||||
Full { kind: ShErrKind, message: String, blame: BlamePair },
|
||||
}
|
||||
|
||||
impl ShErr {
|
||||
pub fn simple<S: Into<String>>(kind: ShErrKind, message: S) -> Self {
|
||||
Self::Simple { kind, message: message.into() }
|
||||
}
|
||||
pub fn io() -> Self {
|
||||
io::Error::last_os_error().into()
|
||||
}
|
||||
pub fn full<S: Into<String>>(kind: ShErrKind, message: S, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||
let blame = BlamePair::new(input.to_string(), span);
|
||||
Self::Full { kind, message: message.into(), blame }
|
||||
}
|
||||
pub fn try_blame(&mut self, input: String, span: Rc<RefCell<Span>>) {
|
||||
let blame_pair = BlamePair::new(input, span);
|
||||
match self {
|
||||
Self::Full {..} => {
|
||||
/* Do not overwrite */
|
||||
}
|
||||
Self::Simple { kind, message } => {
|
||||
*self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair }
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn blame(&mut self, input: String, span: Rc<RefCell<Span>>) {
|
||||
let blame_pair = BlamePair::new(input, span);
|
||||
match self {
|
||||
Self::Full { kind: _, message: _, blame } => {
|
||||
*blame = blame_pair;
|
||||
}
|
||||
Self::Simple { kind, message } => {
|
||||
*self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair }
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn with_msg(&mut self, new_message: String) {
|
||||
match self {
|
||||
Self::Full { kind: _, message, blame: _ } => {
|
||||
*message = new_message
|
||||
}
|
||||
Self::Simple { kind: _, message } => {
|
||||
*message = new_message
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn kind(&self) -> ShErrKind {
|
||||
match self {
|
||||
ShErr::Simple { kind, message: _ } => {
|
||||
*kind
|
||||
}
|
||||
ShErr::Full { kind, message: _, blame: _ } => {
|
||||
*kind
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn with_kind(&mut self, new_kind: ShErrKind) {
|
||||
match self {
|
||||
Self::Full { kind, message: _, blame: _ } => {
|
||||
*kind = new_kind
|
||||
}
|
||||
Self::Simple { kind, message: _ } => {
|
||||
*kind = new_kind
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn display_kind(&self) -> String {
|
||||
match self {
|
||||
ShErr::Simple { kind, message: _ } |
|
||||
ShErr::Full { kind, message: _, blame: _ } => {
|
||||
match kind {
|
||||
ShErrKind::IoErr => "I/O Error: ".into(),
|
||||
ShErrKind::SyntaxErr => "Syntax Error: ".into(),
|
||||
ShErrKind::ParseErr => "Parse Error: ".into(),
|
||||
ShErrKind::InternalErr => "Internal Error: ".into(),
|
||||
ShErrKind::ExecFail => "Execution Failed: ".into(),
|
||||
ShErrKind::Errno => "ERRNO: ".into(),
|
||||
ShErrKind::CmdNotFound => "Command not found: ".into(),
|
||||
ShErrKind::CleanExit |
|
||||
ShErrKind::FuncReturn |
|
||||
ShErrKind::LoopContinue |
|
||||
ShErrKind::LoopBreak |
|
||||
ShErrKind::Null => "".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_line(&self) -> (usize,usize,String) {
|
||||
if let ShErr::Full { kind: _, message: _, blame } = self {
|
||||
unsafe {
|
||||
let mut dist = 0;
|
||||
let mut line_no = 0;
|
||||
let window = self.get_window();
|
||||
let mut lines = window.lines();
|
||||
while let Some(line) = lines.next() {
|
||||
line_no += 1;
|
||||
dist += line.len();
|
||||
if dist > blame.start() {
|
||||
dist -= line.len();
|
||||
let offset = blame.start() - dist;
|
||||
return (offset,line_no,line.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
(0,0,String::new())
|
||||
} else {
|
||||
(0,0,String::new())
|
||||
}
|
||||
}
|
||||
pub fn get_window(&self) -> String {
|
||||
if let ShErr::Full { kind: _, message: _, blame } = self.clone() {
|
||||
let window: String = blame.into();
|
||||
window.split_once('\n').unwrap_or((&window,"")).0.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ShErr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let error_display = match self {
|
||||
ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message),
|
||||
ShErr::Full { kind: _, message, blame } => {
|
||||
let (offset,line_no,line_text) = self.get_line();
|
||||
let dist = blame.end().saturating_sub(blame.start());
|
||||
let padding = " ".repeat(offset);
|
||||
let line_inner = "~".repeat(dist.saturating_sub(2));
|
||||
let err_kind = &self.display_kind().styled(Style::Red | Style::Bold);
|
||||
let stat_line = format!("[{}:{}] - {}{}",line_no,offset,err_kind,message);
|
||||
let indicator_line = if dist == 1 {
|
||||
format!("{}^",padding)
|
||||
} else {
|
||||
format!("{}^{}^",padding,line_inner)
|
||||
};
|
||||
let error_full = format!("\n{}\n{}\n{}\n",stat_line,line_text,indicator_line);
|
||||
|
||||
error_full
|
||||
}
|
||||
};
|
||||
write!(f,"{}",error_display)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,2 @@
|
||||
pub mod sys;
|
||||
#[macro_use]
|
||||
pub mod utils;
|
||||
pub mod collections;
|
||||
pub mod error;
|
||||
pub mod term;
|
||||
|
||||
118
src/libsh/sys.rs
118
src/libsh/sys.rs
@@ -1,118 +0,0 @@
|
||||
use std::{fmt::Display, os::{fd::AsRawFd, unix::fs::PermissionsExt}};
|
||||
|
||||
use nix::sys::termios;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||
|
||||
pub fn get_path_cmds() -> ShResult<Vec<String>> {
|
||||
let mut cmds = vec![];
|
||||
let path_var = std::env::var("PATH")?;
|
||||
let paths = path_var.split(':');
|
||||
|
||||
for path in paths {
|
||||
let path = PathBuf::from(&path);
|
||||
if path.is_dir() {
|
||||
let path_files = std::fs::read_dir(&path)?;
|
||||
for file in path_files {
|
||||
let file_path = file?.path();
|
||||
if file_path.is_file() {
|
||||
if let Ok(meta) = std::fs::metadata(&file_path) {
|
||||
let perms = meta.permissions();
|
||||
if perms.mode() & 0o111 != 0 {
|
||||
let file_name = file_path.file_name().unwrap();
|
||||
cmds.push(file_name.to_str().unwrap().to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cmds)
|
||||
}
|
||||
|
||||
pub fn get_bin_path(command: &str, shenv: &ShEnv) -> Option<PathBuf> {
|
||||
let env = shenv.vars().env();
|
||||
let path_var = env.get("PATH")?;
|
||||
let mut paths = path_var.split(':');
|
||||
|
||||
let script_check = PathBuf::from(command);
|
||||
if script_check.is_file() {
|
||||
return Some(script_check)
|
||||
}
|
||||
while let Some(raw_path) = paths.next() {
|
||||
let mut path = PathBuf::from(raw_path);
|
||||
path.push(command);
|
||||
//TODO: handle this unwrap
|
||||
if path.exists() {
|
||||
return Some(path)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn write_out(text: impl Display) -> ShResult<()> {
|
||||
write(borrow_fd(1), text.to_string().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_err(text: impl Display) -> ShResult<()> {
|
||||
write(borrow_fd(2), text.to_string().as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return is `readpipe`, `writepipe`
|
||||
/// Contains all of the necessary boilerplate for grabbing two pipe fds using libc::pipe()
|
||||
pub fn c_pipe() -> Result<(RawFd,RawFd),Errno> {
|
||||
let mut pipes: [i32;2] = [0;2];
|
||||
let ret = unsafe { libc::pipe(pipes.as_mut_ptr()) };
|
||||
if ret < 0 {
|
||||
return Err(Errno::from_raw(ret))
|
||||
}
|
||||
Ok((pipes[0],pipes[1]))
|
||||
}
|
||||
|
||||
pub fn sh_quit(code: i32) -> ! {
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
if let Some(termios) = crate::get_saved_termios() {
|
||||
termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap();
|
||||
}
|
||||
if code == 0 {
|
||||
write_err("exit\n").ok();
|
||||
} else {
|
||||
write_err(format!("exit {code}\n")).ok();
|
||||
}
|
||||
exit(code);
|
||||
}
|
||||
|
||||
pub fn read_to_string(fd: i32) -> ShResult<String> {
|
||||
let mut buf = Vec::with_capacity(4096);
|
||||
let mut temp_buf = [0u8;1024];
|
||||
|
||||
loop {
|
||||
match read(fd, &mut temp_buf) {
|
||||
Ok(0) => break, // EOF
|
||||
Ok(n) => buf.extend_from_slice(&temp_buf[..n]),
|
||||
Err(Errno::EINTR) => continue, // Retry on EINTR
|
||||
Err(e) => return Err(e.into()), // Return other errors
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&buf).to_string())
|
||||
}
|
||||
|
||||
pub fn execvpe(cmd: String, argv: Vec<String>, envp: Vec<String>) -> Result<(),Errno> {
|
||||
let cmd_raw = CString::new(cmd).unwrap();
|
||||
|
||||
let argv = argv.into_iter().map(|arg| CString::new(arg).unwrap()).collect::<Vec<CString>>();
|
||||
let envp = envp.into_iter().map(|var| CString::new(var).unwrap()).collect::<Vec<CString>>();
|
||||
|
||||
nix::unistd::execvpe(&cmd_raw, &argv, &envp).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,9 +1,21 @@
|
||||
use std::{fmt::Display, ops::BitOr};
|
||||
|
||||
pub trait Styled: Sized + Display {
|
||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
||||
let styles: StyleSet = style.into();
|
||||
let reset = Style::Reset;
|
||||
format!("{styles}{self}{reset}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> Styled for T {}
|
||||
|
||||
/// Enum representing a single ANSI style
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Style {
|
||||
// Undoes all styles
|
||||
Reset,
|
||||
// Foreground Colors
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
@@ -20,36 +32,86 @@ pub enum Style {
|
||||
BrightMagenta,
|
||||
BrightCyan,
|
||||
BrightWhite,
|
||||
RGB(u8, u8, u8), // Custom foreground color
|
||||
|
||||
// Background Colors
|
||||
BgBlack,
|
||||
BgRed,
|
||||
BgGreen,
|
||||
BgYellow,
|
||||
BgBlue,
|
||||
BgMagenta,
|
||||
BgCyan,
|
||||
BgWhite,
|
||||
BgBrightBlack,
|
||||
BgBrightRed,
|
||||
BgBrightGreen,
|
||||
BgBrightYellow,
|
||||
BgBrightBlue,
|
||||
BgBrightMagenta,
|
||||
BgBrightCyan,
|
||||
BgBrightWhite,
|
||||
BgRGB(u8, u8, u8), // Custom background color
|
||||
|
||||
// Text Attributes
|
||||
Bold,
|
||||
Dim,
|
||||
Italic,
|
||||
Underline,
|
||||
Strikethrough,
|
||||
Reversed,
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
impl Display for Style {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Style::Reset => "\x1b[0m",
|
||||
Style::Black => "\x1b[30m",
|
||||
Style::Red => "\x1b[31m",
|
||||
Style::Green => "\x1b[32m",
|
||||
Style::Yellow => "\x1b[33m",
|
||||
Style::Blue => "\x1b[34m",
|
||||
Style::Magenta => "\x1b[35m",
|
||||
Style::Cyan => "\x1b[36m",
|
||||
Style::White => "\x1b[37m",
|
||||
Style::BrightBlack => "\x1b[90m",
|
||||
Style::BrightRed => "\x1b[91m",
|
||||
Style::BrightGreen => "\x1b[92m",
|
||||
Style::BrightYellow => "\x1b[93m",
|
||||
Style::BrightBlue => "\x1b[94m",
|
||||
Style::BrightMagenta => "\x1b[95m",
|
||||
Style::BrightCyan => "\x1b[96m",
|
||||
Style::BrightWhite => "\x1b[97m",
|
||||
Style::Bold => "\x1b[1m",
|
||||
Style::Italic => "\x1b[3m",
|
||||
Style::Underline => "\x1b[4m",
|
||||
Style::Reversed => "\x1b[7m",
|
||||
Style::Reset => write!(f, "\x1b[0m"),
|
||||
|
||||
// Foreground colors
|
||||
Style::Black => write!(f, "\x1b[30m"),
|
||||
Style::Red => write!(f, "\x1b[31m"),
|
||||
Style::Green => write!(f, "\x1b[32m"),
|
||||
Style::Yellow => write!(f, "\x1b[33m"),
|
||||
Style::Blue => write!(f, "\x1b[34m"),
|
||||
Style::Magenta => write!(f, "\x1b[35m"),
|
||||
Style::Cyan => write!(f, "\x1b[36m"),
|
||||
Style::White => write!(f, "\x1b[37m"),
|
||||
Style::BrightBlack => write!(f, "\x1b[90m"),
|
||||
Style::BrightRed => write!(f, "\x1b[91m"),
|
||||
Style::BrightGreen => write!(f, "\x1b[92m"),
|
||||
Style::BrightYellow => write!(f, "\x1b[93m"),
|
||||
Style::BrightBlue => write!(f, "\x1b[94m"),
|
||||
Style::BrightMagenta => write!(f, "\x1b[95m"),
|
||||
Style::BrightCyan => write!(f, "\x1b[96m"),
|
||||
Style::BrightWhite => write!(f, "\x1b[97m"),
|
||||
Style::RGB(r, g, b) => write!(f, "\x1b[38;2;{r};{g};{b}m"),
|
||||
|
||||
// Background colors
|
||||
Style::BgBlack => write!(f, "\x1b[40m"),
|
||||
Style::BgRed => write!(f, "\x1b[41m"),
|
||||
Style::BgGreen => write!(f, "\x1b[42m"),
|
||||
Style::BgYellow => write!(f, "\x1b[43m"),
|
||||
Style::BgBlue => write!(f, "\x1b[44m"),
|
||||
Style::BgMagenta => write!(f, "\x1b[45m"),
|
||||
Style::BgCyan => write!(f, "\x1b[46m"),
|
||||
Style::BgWhite => write!(f, "\x1b[47m"),
|
||||
Style::BgBrightBlack => write!(f, "\x1b[100m"),
|
||||
Style::BgBrightRed => write!(f, "\x1b[101m"),
|
||||
Style::BgBrightGreen => write!(f, "\x1b[102m"),
|
||||
Style::BgBrightYellow => write!(f, "\x1b[103m"),
|
||||
Style::BgBrightBlue => write!(f, "\x1b[104m"),
|
||||
Style::BgBrightMagenta => write!(f, "\x1b[105m"),
|
||||
Style::BgBrightCyan => write!(f, "\x1b[106m"),
|
||||
Style::BgBrightWhite => write!(f, "\x1b[107m"),
|
||||
Style::BgRGB(r, g, b) => write!(f, "\x1b[48;2;{r};{g};{b}m"),
|
||||
|
||||
// Text attributes
|
||||
Style::Bold => write!(f, "\x1b[1m"),
|
||||
Style::Dim => write!(f, "\x1b[2m"), // New
|
||||
Style::Italic => write!(f, "\x1b[3m"),
|
||||
Style::Underline => write!(f, "\x1b[4m"),
|
||||
Style::Strikethrough => write!(f, "\x1b[9m"), // New
|
||||
Style::Reversed => write!(f, "\x1b[7m"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +124,7 @@ pub struct StyleSet {
|
||||
|
||||
impl StyleSet {
|
||||
pub fn new() -> Self {
|
||||
Self { styles: Vec::new() }
|
||||
Self { styles: vec![] }
|
||||
}
|
||||
|
||||
pub fn add(mut self, style: Style) -> Self {
|
||||
@@ -71,9 +133,14 @@ impl StyleSet {
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> String {
|
||||
self.styles.iter().map(|s| s.as_str()).collect::<String>()
|
||||
impl Display for StyleSet {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for style in &self.styles {
|
||||
style.fmt(f)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +148,7 @@ impl StyleSet {
|
||||
impl BitOr for Style {
|
||||
type Output = StyleSet;
|
||||
|
||||
fn bitor(self, rhs: Self) -> StyleSet {
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
StyleSet::new().add(self).add(rhs)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +157,7 @@ impl BitOr for Style {
|
||||
impl BitOr<Style> for StyleSet {
|
||||
type Output = StyleSet;
|
||||
|
||||
fn bitor(self, rhs: Style) -> StyleSet {
|
||||
fn bitor(self, rhs: Style) -> Self::Output {
|
||||
self.add(rhs)
|
||||
}
|
||||
}
|
||||
@@ -100,9 +167,3 @@ impl From<Style> for StyleSet {
|
||||
StyleSet::new().add(style)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply styles to a string
|
||||
pub fn style_text<Str: Display, Sty: Into<StyleSet>>(text: Str, styles: Sty) -> String {
|
||||
let styles = styles.into();
|
||||
format!("{}{}{}", styles.as_str(), text, Style::Reset.as_str())
|
||||
}
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
use core::fmt::{Debug, Display, Write};
|
||||
use std::{os::fd::{AsRawFd, BorrowedFd}, str::FromStr};
|
||||
|
||||
|
||||
use crate::{parse::lex::EXPANSIONS, prelude::*};
|
||||
|
||||
use super::term::StyleSet;
|
||||
|
||||
pub trait RedirTargetType {
|
||||
fn as_tgt(self) -> RedirTarget;
|
||||
}
|
||||
|
||||
impl RedirTargetType for PathBuf {
|
||||
fn as_tgt(self) -> RedirTarget {
|
||||
RedirTarget::File(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RedirTargetType for i32 {
|
||||
fn as_tgt(self) -> RedirTarget {
|
||||
RedirTarget::Fd(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StrOps {
|
||||
/// This function operates on anything that implements `AsRef<str>` and `Display`, which is mainly strings.
|
||||
/// It takes a 'Style' which can be passed as a single Style object like `Style::Cyan` or a Bit OR of many styles,
|
||||
/// For instance: `Style::Red | Style::Bold | Style::Italic`
|
||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Display> StrOps for T {
|
||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
||||
style_text(&self, style)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArgVec {
|
||||
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String>;
|
||||
fn drop_first(self) -> Vec<Token>;
|
||||
}
|
||||
|
||||
impl ArgVec for Vec<Token> {
|
||||
/// Converts the contained tokens into strings.
|
||||
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String> {
|
||||
let mut argv_iter = self.into_iter();
|
||||
let mut argv_processed = vec![];
|
||||
while let Some(arg) = argv_iter.next() {
|
||||
let cleaned = clean_string(&arg.as_raw(shenv)).trim_matches(' ').to_string();
|
||||
argv_processed.push(cleaned);
|
||||
}
|
||||
argv_processed
|
||||
}
|
||||
/// This is used to ignore the first argument
|
||||
/// Most commonly used in builtins where execvpe is not used
|
||||
fn drop_first(self) -> Vec<Token> {
|
||||
self[1..].to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test {
|
||||
($test:block) => {
|
||||
$test
|
||||
exit(1)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq , Debug)]
|
||||
#[repr(i32)]
|
||||
pub enum LogLevel {
|
||||
ERROR = 1,
|
||||
WARN = 2,
|
||||
INFO = 3,
|
||||
DEBUG = 4,
|
||||
TRACE = 5,
|
||||
NULL = 0
|
||||
}
|
||||
|
||||
impl Display for LogLevel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ERROR => write!(f,"{}","ERROR".styled(Style::Red | Style::Bold)),
|
||||
WARN => write!(f,"{}","WARN".styled(Style::Yellow | Style::Bold)),
|
||||
INFO => write!(f,"{}","INFO".styled(Style::Green | Style::Bold)),
|
||||
DEBUG => write!(f,"{}","DEBUG".styled(Style::Magenta | Style::Bold)),
|
||||
TRACE => write!(f,"{}","TRACE".styled(Style::Blue | Style::Bold)),
|
||||
NULL => write!(f,"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:expr, $($var:ident),+) => {{
|
||||
$(
|
||||
let var_name = stringify!($var);
|
||||
if $level <= log_level() {
|
||||
let file = file!();
|
||||
let file_styled = file.styled(Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = line.to_string().styled(Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {} = {:#?}",$level, file_styled,line_styled,var_name, &$var);
|
||||
|
||||
write(borrow_fd(2),format!("{}\n",logged).as_bytes()).unwrap();
|
||||
}
|
||||
)+
|
||||
}};
|
||||
|
||||
($level:expr, $lit:literal) => {{
|
||||
if $level <= log_level() {
|
||||
let file = file!();
|
||||
let file_styled = file.styled(Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = line.to_string().styled(Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, $lit);
|
||||
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
|
||||
}
|
||||
}};
|
||||
|
||||
($level:expr, $($arg:tt)*) => {{
|
||||
if $level <= log_level() {
|
||||
let formatted = format!($($arg)*);
|
||||
let file = file!();
|
||||
let file_styled = file.styled(Style::Cyan);
|
||||
let line = line!();
|
||||
let line_styled = line.to_string().styled(Style::Cyan);
|
||||
let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, formatted);
|
||||
write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap();
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bp {
|
||||
($var:expr) => {
|
||||
log!($var);
|
||||
let mut buf = String::new();
|
||||
readln!("Press enter to continue", buf);
|
||||
};
|
||||
($($arg:tt)*) => {
|
||||
log!($(arg)*);
|
||||
let mut buf = String::new();
|
||||
readln!("Press enter to continue", buf);
|
||||
};
|
||||
}
|
||||
|
||||
pub fn borrow_fd<'a>(fd: i32) -> BorrowedFd<'a> {
|
||||
unsafe { BorrowedFd::borrow_raw(fd) }
|
||||
}
|
||||
|
||||
// TODO: add more of these
|
||||
#[derive(Debug,Clone,PartialEq,Copy)]
|
||||
pub enum RedirType {
|
||||
Input,
|
||||
Output,
|
||||
Append,
|
||||
HereDoc,
|
||||
HereString
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum RedirTarget {
|
||||
Fd(i32),
|
||||
File(PathBuf),
|
||||
HereDoc(String),
|
||||
HereString(String),
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct RedirBldr {
|
||||
src: Option<i32>,
|
||||
op: Option<RedirType>,
|
||||
tgt: Option<RedirTarget>,
|
||||
}
|
||||
|
||||
impl RedirBldr {
|
||||
pub fn new() -> Self {
|
||||
Self { src: None, op: None, tgt: None }
|
||||
}
|
||||
pub fn with_src(self, src: i32) -> Self {
|
||||
Self { src: Some(src), op: self.op, tgt: self.tgt }
|
||||
}
|
||||
pub fn with_op(self, op: RedirType) -> Self {
|
||||
Self { src: self.src, op: Some(op), tgt: self.tgt }
|
||||
}
|
||||
pub fn with_tgt(self, tgt: RedirTarget) -> Self {
|
||||
Self { src: self.src, op: self.op, tgt: Some(tgt) }
|
||||
}
|
||||
pub fn src(&self) -> Option<i32> {
|
||||
self.src
|
||||
}
|
||||
pub fn op(&self) -> Option<RedirType> {
|
||||
self.op
|
||||
}
|
||||
pub fn tgt(&self) -> Option<&RedirTarget> {
|
||||
self.tgt.as_ref()
|
||||
}
|
||||
pub fn build(self) -> Redir {
|
||||
Redir::new(self.src.unwrap(), self.op.unwrap(), self.tgt.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RedirBldr {
|
||||
type Err = ShErr;
|
||||
fn from_str(raw: &str) -> ShResult<Self> {
|
||||
let mut redir_bldr = RedirBldr::new().with_src(1);
|
||||
let mut chars = raw.chars().peekable();
|
||||
|
||||
let mut raw_src = String::new();
|
||||
while chars.peek().is_some_and(|ch| ch.is_ascii_digit()) {
|
||||
raw_src.push(chars.next().unwrap())
|
||||
}
|
||||
if !raw_src.is_empty() {
|
||||
let src = raw_src.parse::<i32>().unwrap();
|
||||
redir_bldr = redir_bldr.with_src(src);
|
||||
}
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'<' => {
|
||||
redir_bldr = redir_bldr.with_src(0);
|
||||
if chars.peek() == Some(&'<') {
|
||||
chars.next();
|
||||
if chars.peek() == Some(&'<') {
|
||||
chars.next();
|
||||
redir_bldr = redir_bldr.with_op(RedirType::HereString);
|
||||
break
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::HereDoc);
|
||||
let body = extract_heredoc_body(raw)?;
|
||||
redir_bldr = redir_bldr.with_tgt(RedirTarget::HereDoc(body));
|
||||
break
|
||||
}
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Input);
|
||||
}
|
||||
}
|
||||
'>' => {
|
||||
if chars.peek() == Some(&'>') {
|
||||
chars.next();
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Append);
|
||||
break
|
||||
} else {
|
||||
redir_bldr = redir_bldr.with_op(RedirType::Output);
|
||||
break
|
||||
}
|
||||
}
|
||||
'&' => {
|
||||
let mut raw_tgt = String::new();
|
||||
while chars.peek().is_some_and(|ch| ch.is_ascii_digit()) {
|
||||
raw_tgt.push(chars.next().unwrap())
|
||||
}
|
||||
let redir_target = RedirTarget::Fd(raw_tgt.parse::<i32>().unwrap());
|
||||
redir_bldr = redir_bldr.with_tgt(redir_target);
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
Ok(redir_bldr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Redir {
|
||||
pub src: i32,
|
||||
pub op: RedirType,
|
||||
pub tgt: RedirTarget
|
||||
}
|
||||
|
||||
impl Redir {
|
||||
pub fn new(src: i32, op: RedirType, tgt: RedirTarget) -> Self {
|
||||
Self { src, op, tgt }
|
||||
}
|
||||
pub fn output(src: i32, tgt: impl RedirTargetType) -> Self {
|
||||
Self::new(src, RedirType::Output, tgt.as_tgt())
|
||||
}
|
||||
pub fn input(src: i32, tgt: impl RedirTargetType) -> Self {
|
||||
Self::new(src, RedirType::Input, tgt.as_tgt())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct CmdRedirs {
|
||||
open: Vec<RawFd>,
|
||||
targets_fd: Vec<Redir>,
|
||||
targets_file: Vec<Redir>,
|
||||
targets_text: Vec<Redir>,
|
||||
}
|
||||
|
||||
impl CmdRedirs {
|
||||
pub fn new(mut redirs: Vec<Redir>) -> Self {
|
||||
let mut targets_fd = vec![];
|
||||
let mut targets_file = vec![];
|
||||
let mut targets_text = vec![];
|
||||
while let Some(redir) = redirs.pop() {
|
||||
let Redir { src: _, op: _, tgt } = &redir;
|
||||
match tgt {
|
||||
RedirTarget::Fd(_) => targets_fd.push(redir),
|
||||
RedirTarget::File(_) => targets_file.push(redir),
|
||||
_ => targets_text.push(redir)
|
||||
}
|
||||
}
|
||||
Self { open: vec![], targets_fd, targets_file, targets_text }
|
||||
}
|
||||
pub fn activate(&mut self) -> ShResult<()> {
|
||||
self.open_file_tgts()?;
|
||||
self.open_fd_tgts()?;
|
||||
self.open_text_tgts()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_text_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_text.pop() {
|
||||
let Redir { src, op: _, tgt } = redir;
|
||||
let (rpipe, wpipe) = c_pipe()?;
|
||||
let src = borrow_fd(src);
|
||||
let wpipe_fd = borrow_fd(wpipe);
|
||||
match tgt {
|
||||
RedirTarget::HereDoc(body) |
|
||||
RedirTarget::HereString(body) => {
|
||||
write(wpipe_fd, body.as_bytes())?;
|
||||
close(wpipe).ok();
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
dup2(rpipe, src.as_raw_fd())?;
|
||||
close(rpipe).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_file_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_file.pop() {
|
||||
let Redir { src, op, tgt } = redir;
|
||||
let src = borrow_fd(src);
|
||||
|
||||
let file_fd = if let RedirTarget::File(path) = tgt {
|
||||
let flags = match op {
|
||||
RedirType::Input => OFlag::O_RDONLY,
|
||||
RedirType::Output => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC,
|
||||
RedirType::Append => OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_APPEND,
|
||||
_ => unimplemented!()
|
||||
};
|
||||
let mode = Mode::from_bits(0o644).unwrap();
|
||||
open(&path,flags,mode)?
|
||||
} else { unreachable!() };
|
||||
|
||||
dup2(file_fd.as_raw_fd(),src.as_raw_fd())?;
|
||||
close(file_fd.as_raw_fd())?;
|
||||
self.open.push(src.as_raw_fd());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn open_fd_tgts(&mut self) -> ShResult<()> {
|
||||
while let Some(redir) = self.targets_fd.pop() {
|
||||
let Redir { src, op: _, tgt } = redir;
|
||||
let tgt = if let RedirTarget::Fd(fd) = tgt {
|
||||
borrow_fd(fd)
|
||||
} else { unreachable!() };
|
||||
let src = borrow_fd(src);
|
||||
dup2(tgt.as_raw_fd(), src.as_raw_fd())?;
|
||||
close(tgt.as_raw_fd())?;
|
||||
self.open.push(src.as_raw_fd());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_heredoc_body(body: &str) -> ShResult<String> {
|
||||
log!(DEBUG,body);
|
||||
if let Some(cleaned) = body.strip_prefix("<<") {
|
||||
if let Some((delim,body)) = cleaned.split_once('\n') {
|
||||
if let Some(body) = body.trim().strip_suffix(&delim) {
|
||||
Ok(body.to_string())
|
||||
} else {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Malformed closing delimiter in heredoc"))
|
||||
}
|
||||
} else {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Invalid heredoc delimiter"))
|
||||
}
|
||||
} else {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "Invalid heredoc operator"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_expansion(s: &str) -> Option<TkRule> {
|
||||
let rule = Lexer::get_rule(s);
|
||||
if EXPANSIONS.contains(&rule) {
|
||||
Some(rule)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean_string(s: impl ToString) -> String {
|
||||
let s = s.to_string();
|
||||
if (s.starts_with('"') && s.ends_with('"')) ||
|
||||
(s.starts_with('\'') && s.ends_with('\'')) ||
|
||||
(s.starts_with('`') && s.ends_with('`'))
|
||||
{
|
||||
if s.len() > 1 {
|
||||
s[1..s.len() - 1].to_string()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
} else if s.starts_with("$(") && s.ends_with(')') {
|
||||
s[2..s.len() - 1].to_string()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
126
src/main.rs
126
src/main.rs
@@ -1,126 +0,0 @@
|
||||
#![allow(static_mut_refs,unused_unsafe)]
|
||||
|
||||
pub mod libsh;
|
||||
pub mod shellenv;
|
||||
pub mod parse;
|
||||
pub mod prelude;
|
||||
pub mod execute;
|
||||
pub mod signal;
|
||||
pub mod prompt;
|
||||
pub mod builtin;
|
||||
pub mod expand;
|
||||
pub mod tests;
|
||||
|
||||
use std::os::fd::AsRawFd;
|
||||
|
||||
use nix::sys::termios::{self, LocalFlags, Termios};
|
||||
use signal::sig_setup;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
||||
|
||||
bitflags! {
|
||||
pub struct FernFlags: u32 {
|
||||
const NO_RC = 0b000001;
|
||||
const NO_HIST = 0b000010;
|
||||
const INTERACTIVE = 0b000100;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_termios() {
|
||||
unsafe {
|
||||
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
termios.local_flags &= !LocalFlags::ECHOCTL;
|
||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
||||
Some(termios)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
}
|
||||
}
|
||||
pub fn get_saved_termios() -> Option<Termios> {
|
||||
unsafe {
|
||||
// This is only used when the shell exits so it's fine
|
||||
// SAVED_TERMIOS is only mutated once at the start as well
|
||||
SAVED_TERMIOS.clone().flatten()
|
||||
}
|
||||
}
|
||||
fn set_termios() {
|
||||
if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||
let mut termios = termios::tcgetattr(std::io::stdin()).unwrap();
|
||||
termios.local_flags &= !LocalFlags::ECHOCTL;
|
||||
termios::tcsetattr(std::io::stdin(), nix::sys::termios::SetArg::TCSANOW, &termios).unwrap();
|
||||
}
|
||||
}
|
||||
fn parse_args(shenv: &mut ShEnv) {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let mut script_path: Option<PathBuf> = None;
|
||||
let mut command: Option<String> = None;
|
||||
let mut flags = FernFlags::empty();
|
||||
|
||||
log!(DEBUG, args);
|
||||
while let Some(mut arg) = args.next() {
|
||||
log!(DEBUG, arg);
|
||||
if arg.starts_with("--") {
|
||||
arg = arg.strip_prefix("--").unwrap().to_string();
|
||||
match arg.as_str() {
|
||||
"no-rc" => flags |= FernFlags::NO_RC,
|
||||
"no-hist" => flags |= FernFlags::NO_HIST,
|
||||
_ => eprintln!("Warning - Unrecognized option: {arg}")
|
||||
}
|
||||
} else if arg.starts_with('-') {
|
||||
arg = arg.strip_prefix('-').unwrap().to_string();
|
||||
match arg.as_str() {
|
||||
"c" => command = args.next(),
|
||||
_ => eprintln!("Warning - Unrecognized option: {arg}")
|
||||
}
|
||||
} else {
|
||||
let path_check = PathBuf::from(&arg);
|
||||
if path_check.is_file() {
|
||||
script_path = Some(path_check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !flags.contains(FernFlags::NO_RC) {
|
||||
let _ = shenv.source_rc().eprint();
|
||||
}
|
||||
|
||||
if let Some(cmd) = command {
|
||||
let input = clean_string(cmd);
|
||||
let _ = exec_input(input, shenv).eprint();
|
||||
|
||||
} else if let Some(script) = script_path {
|
||||
let _ = shenv.source_file(script).eprint();
|
||||
|
||||
} else {
|
||||
interactive(shenv);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() {
|
||||
sig_setup();
|
||||
save_termios();
|
||||
set_termios();
|
||||
let mut shenv = ShEnv::new();
|
||||
|
||||
parse_args(&mut shenv);
|
||||
}
|
||||
|
||||
fn interactive(shenv: &mut ShEnv) {
|
||||
loop {
|
||||
log!(TRACE, "Entered loop");
|
||||
match prompt::read_line(shenv) {
|
||||
Ok(line) => {
|
||||
shenv.meta_mut().start_timer();
|
||||
let _ = exec_input(line, shenv).eprint();
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{}",e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
193
src/parse/execute.rs
Normal file
193
src/parse/execute.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use nix::sys::wait::WaitPidFlag;
|
||||
|
||||
use crate::{libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state};
|
||||
|
||||
use super::{lex::Tk, ConjunctNode, ConjunctOp, NdRule, Node, Redir, RedirType};
|
||||
|
||||
/// Arguments to the execvpe function
|
||||
pub struct ExecArgs {
|
||||
pub cmd: CString,
|
||||
pub argv: Vec<CString>,
|
||||
pub envp: Vec<CString>
|
||||
}
|
||||
|
||||
impl ExecArgs {
|
||||
pub fn new(argv: Vec<String>) -> Self {
|
||||
assert!(!argv.is_empty());
|
||||
let cmd = Self::get_cmd(&argv);
|
||||
let argv = Self::get_argv(argv);
|
||||
let envp = Self::get_envp();
|
||||
Self { cmd, argv, envp }
|
||||
}
|
||||
pub fn get_cmd(argv: &[String]) -> CString {
|
||||
CString::new(argv[0].as_str()).unwrap()
|
||||
}
|
||||
pub fn get_argv(argv: Vec<String>) -> Vec<CString> {
|
||||
argv.into_iter().map(|s| CString::new(s).unwrap()).collect()
|
||||
}
|
||||
pub fn get_envp() -> Vec<CString> {
|
||||
std::env::vars().map(|v| CString::new(format!("{}={}",v.0,v.1)).unwrap()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dispatcher<'t> {
|
||||
nodes: VecDeque<Node<'t>>,
|
||||
pub io_stack: IoStack
|
||||
}
|
||||
|
||||
impl<'t> Dispatcher<'t> {
|
||||
pub fn new(nodes: Vec<Node<'t>>) -> Self {
|
||||
let nodes = VecDeque::from(nodes);
|
||||
Self { nodes, io_stack: IoStack::new() }
|
||||
}
|
||||
pub fn begin_dispatch(&mut self) -> ShResult<'t,()> {
|
||||
while let Some(list) = self.nodes.pop_front() {
|
||||
self.dispatch_node(list)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<'t,()> {
|
||||
match node.class {
|
||||
NdRule::CmdList {..} => self.exec_conjunction(node)?,
|
||||
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
||||
NdRule::Command {..} => self.exec_cmd(node)?,
|
||||
_ => unreachable!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<'t,()> {
|
||||
let NdRule::CmdList { elements } = conjunction.class else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut elem_iter = elements.into_iter();
|
||||
while let Some(element) = elem_iter.next() {
|
||||
let ConjunctNode { cmd, operator } = element;
|
||||
self.dispatch_node(*cmd)?;
|
||||
|
||||
let status = state::get_status();
|
||||
match operator {
|
||||
ConjunctOp::And => if status != 0 { break },
|
||||
ConjunctOp::Or => if status == 0 { break },
|
||||
ConjunctOp::Null => break
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<'t,()> {
|
||||
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
|
||||
unreachable!()
|
||||
};
|
||||
// Zip the commands and their respective pipes into an iterator
|
||||
let pipes_and_cmds = get_pipe_stack(cmds.len())
|
||||
.into_iter()
|
||||
.zip(cmds);
|
||||
|
||||
for ((rpipe,wpipe), cmd) in pipes_and_cmds {
|
||||
if let Some(pipe) = rpipe {
|
||||
self.io_stack.push_to_frame(pipe);
|
||||
}
|
||||
if let Some(pipe) = wpipe {
|
||||
self.io_stack.push_to_frame(pipe);
|
||||
}
|
||||
self.dispatch_node(cmd)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<'t,()> {
|
||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||
unreachable!()
|
||||
};
|
||||
for redir in cmd.redirs {
|
||||
self.io_stack.push_to_frame(redir);
|
||||
}
|
||||
|
||||
let exec_args = ExecArgs::new(prepare_argv(argv));
|
||||
let io_frame = self.io_stack.pop_frame();
|
||||
run_fork(
|
||||
io_frame,
|
||||
exec_args,
|
||||
def_child_action,
|
||||
def_parent_action
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_argv(argv: Vec<Tk>) -> Vec<String> {
|
||||
let mut args = vec![];
|
||||
|
||||
for arg in argv {
|
||||
let flags = arg.flags;
|
||||
let span = arg.span.clone();
|
||||
let expanded = arg.expand(span, flags);
|
||||
args.extend(expanded.get_words());
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
pub fn run_fork<'t,C,P>(
|
||||
io_frame: IoFrame,
|
||||
exec_args: ExecArgs,
|
||||
child_action: C,
|
||||
parent_action: P,
|
||||
) -> ShResult<'t,()>
|
||||
where
|
||||
C: Fn(IoFrame,ExecArgs),
|
||||
P: Fn(IoFrame,Pid) -> ShResult<'t,()>
|
||||
{
|
||||
match unsafe { fork()? } {
|
||||
ForkResult::Child => {
|
||||
child_action(io_frame,exec_args);
|
||||
exit(1);
|
||||
}
|
||||
ForkResult::Parent { child } => {
|
||||
parent_action(io_frame,child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The default behavior for the child process after forking
|
||||
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: ExecArgs) {
|
||||
io_frame.redirect().unwrap();
|
||||
execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp).unwrap();
|
||||
}
|
||||
|
||||
/// The default behavior for the parent process after forking
|
||||
pub fn def_parent_action<'t>(io_frame: IoFrame, child: Pid) -> ShResult<'t,()> {
|
||||
let status = waitpid(child, Some(WaitPidFlag::WSTOPPED))?;
|
||||
match status {
|
||||
WaitStatus::Exited(_, status) => state::set_status(status),
|
||||
_ => unimplemented!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/// Initialize the pipes for a pipeline
|
||||
/// The first command gets `(None, WPipe)`
|
||||
/// The last command gets `(RPipe, None)`
|
||||
/// Commands inbetween get `(RPipe, WPipe)`
|
||||
/// If there is only one command, it gets `(None, None)`
|
||||
pub fn get_pipe_stack(num_cmds: usize) -> Vec<(Option<Redir>,Option<Redir>)> {
|
||||
let mut stack = Vec::with_capacity(num_cmds);
|
||||
let mut prev_read: Option<Redir> = None;
|
||||
|
||||
for i in 0..num_cmds {
|
||||
if i == num_cmds - 1 {
|
||||
stack.push((prev_read.take(), None));
|
||||
} else {
|
||||
let (rpipe,wpipe) = IoPipe::get_pipes();
|
||||
let r_redir = Redir::new(Box::new(rpipe), RedirType::Input);
|
||||
let w_redir = Redir::new(Box::new(wpipe), RedirType::Output);
|
||||
|
||||
// Push (prev_read, Some(w_redir)) and set prev_read to r_redir
|
||||
stack.push((prev_read.take(), Some(w_redir)));
|
||||
prev_read = Some(r_redir);
|
||||
}
|
||||
}
|
||||
stack
|
||||
}
|
||||
1878
src/parse/lex.rs
1878
src/parse/lex.rs
File diff suppressed because it is too large
Load Diff
1616
src/parse/mod.rs
1616
src/parse/mod.rs
File diff suppressed because it is too large
Load Diff
199
src/prelude.rs
199
src/prelude.rs
@@ -1,180 +1,35 @@
|
||||
pub use std::{
|
||||
io::{
|
||||
// Standard Library Common IO and FS Abstractions
|
||||
pub use std::io::{
|
||||
self,
|
||||
BufRead,
|
||||
BufReader,
|
||||
BufWriter,
|
||||
Error,
|
||||
ErrorKind,
|
||||
Read,
|
||||
Write
|
||||
},
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
os::fd::{
|
||||
OwnedFd,
|
||||
BorrowedFd,
|
||||
RawFd,
|
||||
FromRawFd
|
||||
},
|
||||
collections::{
|
||||
VecDeque,
|
||||
HashMap,
|
||||
},
|
||||
ffi::{
|
||||
CStr,
|
||||
CString
|
||||
},
|
||||
path::{
|
||||
Path,
|
||||
PathBuf,
|
||||
},
|
||||
process::{
|
||||
exit
|
||||
},
|
||||
Seek,
|
||||
SeekFrom,
|
||||
Write,
|
||||
};
|
||||
pub use bitflags::bitflags;
|
||||
pub use std::fs::{ self, File, OpenOptions };
|
||||
pub use std::path::{ Path, PathBuf };
|
||||
pub use std::ffi::{ CStr, CString };
|
||||
pub use std::process::exit;
|
||||
|
||||
// Unix-specific IO abstractions
|
||||
pub use std::os::unix::io::{ AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, };
|
||||
|
||||
// Nix crate for POSIX APIs
|
||||
pub use nix::{
|
||||
fcntl::{
|
||||
open,
|
||||
OFlag,
|
||||
},
|
||||
sys::{
|
||||
signal::{
|
||||
killpg,
|
||||
kill,
|
||||
signal,
|
||||
pthread_sigmask,
|
||||
SigmaskHow,
|
||||
SigSet,
|
||||
SigHandler,
|
||||
Signal
|
||||
},
|
||||
wait::{
|
||||
waitpid,
|
||||
WaitStatus as WtStat,
|
||||
WaitPidFlag as WtFlag
|
||||
},
|
||||
stat::Mode,
|
||||
memfd::memfd_create,
|
||||
},
|
||||
errno::Errno,
|
||||
unistd::{
|
||||
Pid,
|
||||
ForkResult::*,
|
||||
fork,
|
||||
getppid,
|
||||
getpid,
|
||||
getpgid,
|
||||
getpgrp,
|
||||
geteuid,
|
||||
read,
|
||||
write,
|
||||
isatty,
|
||||
tcgetpgrp,
|
||||
tcsetpgrp,
|
||||
dup,
|
||||
dup2,
|
||||
close,
|
||||
},
|
||||
libc,
|
||||
};
|
||||
pub use crate::{
|
||||
libsh::{
|
||||
term::{
|
||||
Style,
|
||||
style_text
|
||||
},
|
||||
utils::{
|
||||
LogLevel::*,
|
||||
ArgVec,
|
||||
Redir,
|
||||
RedirType,
|
||||
RedirBldr,
|
||||
StrOps,
|
||||
RedirTarget,
|
||||
CmdRedirs,
|
||||
borrow_fd,
|
||||
check_expansion,
|
||||
clean_string
|
||||
},
|
||||
collections::{
|
||||
VecDequeAliases
|
||||
},
|
||||
fcntl::{ open, OFlag },
|
||||
sys::{
|
||||
self,
|
||||
get_path_cmds,
|
||||
get_bin_path,
|
||||
sh_quit,
|
||||
read_to_string,
|
||||
write_err,
|
||||
write_out,
|
||||
c_pipe,
|
||||
execvpe
|
||||
signal::{ self, kill, SigHandler, Signal },
|
||||
stat::Mode,
|
||||
wait::{ waitpid, WaitStatus },
|
||||
},
|
||||
error::{
|
||||
ResultExt,
|
||||
Blame,
|
||||
ShErrKind,
|
||||
ShErr,
|
||||
ShResult
|
||||
},
|
||||
},
|
||||
builtin::{
|
||||
echo::echo,
|
||||
cd::cd,
|
||||
pwd::pwd,
|
||||
read::read_builtin,
|
||||
alias::alias,
|
||||
control_flow::sh_flow,
|
||||
export::export,
|
||||
source::source,
|
||||
jobctl::{
|
||||
continue_job,
|
||||
jobs
|
||||
},
|
||||
BUILTINS
|
||||
},
|
||||
expand::{
|
||||
expand_argv,
|
||||
expand_token,
|
||||
prompt::expand_prompt,
|
||||
alias::expand_aliases
|
||||
},
|
||||
shellenv::{
|
||||
self,
|
||||
dispatch_job,
|
||||
log_level,
|
||||
attach_tty,
|
||||
term_ctlr,
|
||||
take_term,
|
||||
jobs::{
|
||||
JobTab,
|
||||
JobID,
|
||||
write_jobs,
|
||||
read_jobs
|
||||
},
|
||||
exec_ctx::ExecFlags,
|
||||
shenv::ShEnv
|
||||
},
|
||||
execute::{
|
||||
exec_input,
|
||||
Executor,
|
||||
},
|
||||
parse::{
|
||||
SynTree,
|
||||
LoopKind,
|
||||
Node,
|
||||
CmdGuard,
|
||||
NdFlag,
|
||||
NdRule,
|
||||
Parser,
|
||||
ParseRule,
|
||||
lex::{
|
||||
EXPANSIONS,
|
||||
Span,
|
||||
Token,
|
||||
TkRule,
|
||||
Lexer,
|
||||
LexRule
|
||||
},
|
||||
},
|
||||
log,
|
||||
test,
|
||||
bp,
|
||||
libc::{ STDIN_FILENO, STDERR_FILENO, STDOUT_FILENO },
|
||||
unistd::{ dup, read, write, close, dup2, execvpe, fork, pipe, Pid, ForkResult },
|
||||
};
|
||||
|
||||
// Additional utilities, if needed, can be added here
|
||||
|
||||
252
src/procio.rs
Normal file
252
src/procio.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use std::{fmt::Debug, ops::{Deref, DerefMut}};
|
||||
|
||||
use crate::{libsh::error::ShResult, parse::Redir, prelude::*};
|
||||
|
||||
// Credit to fish-shell for many of the implementation ideas present in this module
|
||||
// https://fishshell.com/
|
||||
|
||||
pub enum IoMode {
|
||||
Fd,
|
||||
File,
|
||||
Pipe,
|
||||
}
|
||||
|
||||
pub trait IoInfo: Read {
|
||||
fn mode(&self) -> IoMode;
|
||||
/// The fildesc that is replaced by src_fd in dup2()
|
||||
/// e.g. `dup2(src_fd, tgt_fd)`
|
||||
fn tgt_fd(&self) -> RawFd;
|
||||
/// The fildesc that replaces tgt_fd in dup2()
|
||||
/// e.g. `dup2(src_fd, tgt_fd)`
|
||||
fn src_fd(&self) -> RawFd;
|
||||
fn print(&self) -> String;
|
||||
fn close(&mut self) -> ShResult<()>;
|
||||
}
|
||||
|
||||
macro_rules! read_impl {
|
||||
($type:path) => {
|
||||
impl Read for $type {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let src_fd = self.src_fd();
|
||||
|
||||
Ok(read(src_fd, buf)?)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
read_impl!(IoPipe);
|
||||
read_impl!(IoFile);
|
||||
read_impl!(IoFd);
|
||||
|
||||
|
||||
// TODO: implement this
|
||||
impl Debug for Box<dyn IoInfo> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f,"{}",self.print())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoFd {
|
||||
tgt_fd: RawFd,
|
||||
src_fd: RawFd
|
||||
}
|
||||
|
||||
impl IoFd {
|
||||
pub fn new(tgt_fd: RawFd, src_fd: RawFd) -> Self {
|
||||
Self { tgt_fd, src_fd }
|
||||
}
|
||||
}
|
||||
|
||||
impl IoInfo for IoFd {
|
||||
fn mode(&self) -> IoMode {
|
||||
IoMode::Fd
|
||||
}
|
||||
fn tgt_fd(&self) -> RawFd {
|
||||
self.tgt_fd
|
||||
}
|
||||
fn src_fd(&self) -> RawFd {
|
||||
self.src_fd
|
||||
}
|
||||
fn close(&mut self) -> ShResult<()> {
|
||||
if self.src_fd == -1 {
|
||||
return Ok(())
|
||||
}
|
||||
close(self.src_fd)?;
|
||||
self.src_fd = -1;
|
||||
Ok(())
|
||||
}
|
||||
fn print(&self) -> String {
|
||||
format!("{:?}",self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoFile {
|
||||
tgt_fd: RawFd,
|
||||
file: File
|
||||
}
|
||||
|
||||
impl IoFile {
|
||||
pub fn new(tgt_fd: RawFd, file: File) -> Self {
|
||||
Self { tgt_fd, file }
|
||||
}
|
||||
}
|
||||
|
||||
impl IoInfo for IoFile {
|
||||
fn mode(&self) -> IoMode {
|
||||
IoMode::File
|
||||
}
|
||||
fn tgt_fd(&self) -> RawFd {
|
||||
self.tgt_fd
|
||||
}
|
||||
fn src_fd(&self) -> RawFd {
|
||||
self.file.as_raw_fd()
|
||||
}
|
||||
fn close(&mut self) -> ShResult<()> {
|
||||
// Closes on it's own when it's dropped
|
||||
Ok(())
|
||||
}
|
||||
fn print(&self) -> String {
|
||||
format!("{:?}",self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoPipe {
|
||||
tgt_fd: RawFd,
|
||||
pipe_fd: OwnedFd
|
||||
}
|
||||
|
||||
impl IoPipe {
|
||||
pub fn new(tgt_fd: RawFd, pipe_fd: OwnedFd) -> Self {
|
||||
Self { tgt_fd, pipe_fd }
|
||||
}
|
||||
pub fn get_pipes() -> (Self, Self) {
|
||||
let (rpipe,wpipe) = pipe().unwrap();
|
||||
let r_iopipe = Self::new(STDIN_FILENO, rpipe);
|
||||
let w_iopipe = Self::new(STDOUT_FILENO, wpipe);
|
||||
|
||||
(r_iopipe,w_iopipe)
|
||||
}
|
||||
}
|
||||
|
||||
impl IoInfo for IoPipe {
|
||||
fn mode(&self) -> IoMode {
|
||||
IoMode::Pipe
|
||||
}
|
||||
fn tgt_fd(&self) -> RawFd {
|
||||
self.tgt_fd
|
||||
}
|
||||
fn src_fd(&self) -> RawFd {
|
||||
self.pipe_fd.as_raw_fd()
|
||||
}
|
||||
fn close(&mut self) -> ShResult<()> {
|
||||
// Closes on it's own
|
||||
Ok(())
|
||||
}
|
||||
fn print(&self) -> String {
|
||||
format!("{:?}",self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IoGroup(OwnedFd,OwnedFd,OwnedFd);
|
||||
|
||||
#[derive(Default,Debug)]
|
||||
pub struct IoFrame {
|
||||
redirs: Vec<Redir>,
|
||||
saved_io: Option<IoGroup>,
|
||||
}
|
||||
|
||||
impl<'e> IoFrame {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
pub fn save(&'e mut self) {
|
||||
unsafe {
|
||||
let saved_in = OwnedFd::from_raw_fd(dup(STDIN_FILENO).unwrap());
|
||||
let saved_out = OwnedFd::from_raw_fd(dup(STDOUT_FILENO).unwrap());
|
||||
let saved_err = OwnedFd::from_raw_fd(dup(STDERR_FILENO).unwrap());
|
||||
self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err));
|
||||
}
|
||||
}
|
||||
pub fn redirect(&'e mut self) -> ShResult<'e,()> {
|
||||
self.save();
|
||||
for redir in &mut self.redirs {
|
||||
let io_info = &mut redir.io_info;
|
||||
let tgt_fd = io_info.tgt_fd();
|
||||
let src_fd = io_info.src_fd();
|
||||
dup2(src_fd, tgt_fd)?;
|
||||
io_info.close()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn restore(&'e mut self) -> ShResult<'e,()> {
|
||||
while let Some(mut redir) = self.pop() {
|
||||
redir.io_info.close().ok();
|
||||
}
|
||||
if let Some(saved) = self.saved_io.take() {
|
||||
dbg!(&saved);
|
||||
dup2(saved.0.as_raw_fd(), STDIN_FILENO)?;
|
||||
dup2(saved.1.as_raw_fd(), STDOUT_FILENO)?;
|
||||
dup2(saved.2.as_raw_fd(), STDERR_FILENO)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IoFrame {
|
||||
type Target = Vec<Redir>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.redirs
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for IoFrame {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.redirs
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct IoStack {
|
||||
stack: Vec<IoFrame>,
|
||||
}
|
||||
|
||||
impl<'e> IoStack {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stack: vec![IoFrame::new()],
|
||||
}
|
||||
}
|
||||
pub fn curr_frame(&self) -> &IoFrame {
|
||||
self.stack.last().unwrap()
|
||||
}
|
||||
pub fn curr_frame_mut(&mut self) -> &mut IoFrame {
|
||||
self.stack.last_mut().unwrap()
|
||||
}
|
||||
pub fn push_to_frame(&mut self, redir: Redir) {
|
||||
self.curr_frame_mut().push(redir)
|
||||
}
|
||||
pub fn pop_frame(&mut self) -> IoFrame {
|
||||
if self.stack.len() > 1 {
|
||||
self.stack.pop().unwrap()
|
||||
} else {
|
||||
std::mem::take(self.curr_frame_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IoStack {
|
||||
type Target = Vec<IoFrame>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.stack
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for IoStack {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.stack
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
use rustyline::completion::{Candidate, Completer};
|
||||
|
||||
use crate::{expand::cmdsub::expand_cmdsub_string, parse::lex::KEYWORDS, prelude::*};
|
||||
|
||||
use super::readline::SynHelper;
|
||||
|
||||
impl<'a> Completer for SynHelper<'a> {
|
||||
type Candidate = String;
|
||||
fn complete( &self, line: &str, pos: usize, ctx: &rustyline::Context<'_>,) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
|
||||
let mut shenv = self.shenv.clone();
|
||||
let mut comps = vec![];
|
||||
shenv.new_input(line);
|
||||
let mut token_stream = Lexer::new(line.to_string(), &mut shenv).lex();
|
||||
if let Some(comp_token) = token_stream.pop() {
|
||||
let raw = comp_token.as_raw(&mut shenv);
|
||||
let is_cmd = if let Some(token) = token_stream.pop() {
|
||||
match token.rule() {
|
||||
TkRule::Sep => true,
|
||||
_ if KEYWORDS.contains(&token.rule()) => true,
|
||||
_ => false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
if let TkRule::Ident | TkRule::Whitespace = comp_token.rule() {
|
||||
if is_cmd {
|
||||
let cmds = shenv.meta().path_cmds();
|
||||
comps.extend(cmds.iter().map(|cmd| cmd.to_string()));
|
||||
comps.retain(|cmd| cmd.starts_with(&raw));
|
||||
if !comps.is_empty() && comps.len() > 1 {
|
||||
if get_bin_path("fzf", &self.shenv).is_some() {
|
||||
if let Some(mut selection) = fzf_comp(&comps, &mut shenv) {
|
||||
while selection.starts_with(&raw) {
|
||||
selection = selection.strip_prefix(&raw).unwrap().to_string();
|
||||
}
|
||||
comps = vec![selection];
|
||||
}
|
||||
}
|
||||
} else if let Some(mut comp) = comps.pop() {
|
||||
while comp.starts_with(&raw) {
|
||||
comp = comp.strip_prefix(&raw).unwrap().to_string();
|
||||
}
|
||||
comps = vec![comp];
|
||||
}
|
||||
return Ok((pos,comps))
|
||||
} else {
|
||||
let (start, matches) = self.file_comp.complete(line, pos, ctx)?;
|
||||
comps.extend(matches.iter().map(|c| c.display().to_string()));
|
||||
|
||||
if !comps.is_empty() && comps.len() > 1 {
|
||||
if get_bin_path("fzf", &self.shenv).is_some() {
|
||||
if let Some(selection) = fzf_comp(&comps, &mut shenv) {
|
||||
return Ok((start, vec![selection]))
|
||||
} else {
|
||||
return Ok((start, vec![]))
|
||||
}
|
||||
} else {
|
||||
return Ok((start, comps))
|
||||
}
|
||||
} else if let Some(comp) = comps.pop() {
|
||||
// Slice off the already typed bit
|
||||
return Ok((start, vec![comp]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((pos,comps))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fzf_comp(comps: &[String], shenv: &mut ShEnv) -> Option<String> {
|
||||
// All of the fzf wrapper libraries suck
|
||||
// So we gotta do this now
|
||||
let echo_args = comps.join("\n");
|
||||
let echo = format!("echo \"{echo_args}\"");
|
||||
let fzf = "fzf --height=~30% --layout=reverse --border --border-label=completion";
|
||||
let command = format!("{echo} | {fzf}");
|
||||
|
||||
shenv.ctx_mut().set_flag(ExecFlags::NO_EXPAND); // Prevent any pesky shell injections with filenames like '$(rm -rf /)'
|
||||
let selection = expand_cmdsub_string(&command, shenv).ok()?;
|
||||
if selection.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(selection.trim().to_string())
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
use rustyline::highlight::Highlighter;
|
||||
use sys::get_bin_path;
|
||||
|
||||
use crate::{parse::lex::KEYWORDS, prelude::*};
|
||||
|
||||
use super::readline::SynHelper;
|
||||
|
||||
impl<'a> Highlighter for SynHelper<'a> {
|
||||
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
|
||||
let mut shenv_clone = self.shenv.clone();
|
||||
shenv_clone.new_input(line);
|
||||
|
||||
let mut result = String::new();
|
||||
let mut tokens = Lexer::new(line.to_string(),&mut shenv_clone).lex().into_iter();
|
||||
let mut is_command = true;
|
||||
let mut in_array = false;
|
||||
let mut in_case = false;
|
||||
|
||||
while let Some(token) = tokens.next() {
|
||||
let raw = token.as_raw(&mut shenv_clone);
|
||||
match token.rule() {
|
||||
TkRule::Comment => {
|
||||
let styled = &raw.styled(Style::BrightBlack);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
TkRule::ErrPipeOp |
|
||||
TkRule::OrOp |
|
||||
TkRule::AndOp |
|
||||
TkRule::PipeOp |
|
||||
TkRule::RedirOp |
|
||||
TkRule::BgOp => {
|
||||
is_command = true;
|
||||
let styled = &raw.styled(Style::Cyan);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
TkRule::CasePat => {
|
||||
let pat = raw.trim_end_matches(')');
|
||||
let len_delta = raw.len().saturating_sub(pat.len());
|
||||
let parens = ")".repeat(len_delta);
|
||||
let styled = pat.styled(Style::Magenta);
|
||||
let rebuilt = format!("{styled}{parens}");
|
||||
result.push_str(&rebuilt);
|
||||
}
|
||||
TkRule::FuncName => {
|
||||
let name = raw.strip_suffix("()").unwrap_or(&raw);
|
||||
let styled = name.styled(Style::Cyan);
|
||||
let rebuilt = format!("{styled}()");
|
||||
result.push_str(&rebuilt);
|
||||
}
|
||||
TkRule::DQuote | TkRule::SQuote => {
|
||||
let styled = raw.styled(Style::BrightYellow);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
_ if KEYWORDS.contains(&token.rule()) => {
|
||||
if in_array || in_case {
|
||||
if &raw == "in" {
|
||||
let styled = &raw.styled(Style::Yellow);
|
||||
result.push_str(&styled);
|
||||
if in_case { in_case = false };
|
||||
} else {
|
||||
let styled = &raw.styled(Style::Magenta);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
} else {
|
||||
if &raw == "for" {
|
||||
in_array = true;
|
||||
}
|
||||
if &raw == "case" {
|
||||
in_case = true;
|
||||
}
|
||||
let styled = &raw.styled(Style::Yellow);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
}
|
||||
TkRule::BraceGrp => {
|
||||
let body = &raw[1..raw.len() - 1];
|
||||
let highlighted = self.highlight(body, 0).to_string();
|
||||
let styled_o_brace = "{".styled(Style::BrightBlue);
|
||||
let styled_c_brace = "}".styled(Style::BrightBlue);
|
||||
let rebuilt = format!("{styled_o_brace}{highlighted}{styled_c_brace}");
|
||||
|
||||
is_command = false;
|
||||
result.push_str(&rebuilt);
|
||||
}
|
||||
TkRule::CmdSub => {
|
||||
let body = &raw[2..raw.len() - 1];
|
||||
let highlighted = self.highlight(body, 0).to_string();
|
||||
let styled_o_paren = "$(".styled(Style::BrightBlue);
|
||||
let styled_c_paren = ")".styled(Style::BrightBlue);
|
||||
let rebuilt = format!("{styled_o_paren}{highlighted}{styled_c_paren}");
|
||||
|
||||
is_command = false;
|
||||
result.push_str(&rebuilt);
|
||||
}
|
||||
TkRule::Subshell => {
|
||||
let body = &raw[1..raw.len() - 1];
|
||||
let highlighted = self.highlight(body, 0).to_string();
|
||||
let styled_o_paren = "(".styled(Style::BrightBlue);
|
||||
let styled_c_paren = ")".styled(Style::BrightBlue);
|
||||
let rebuilt = format!("{styled_o_paren}{highlighted}{styled_c_paren}");
|
||||
|
||||
is_command = false;
|
||||
result.push_str(&rebuilt);
|
||||
}
|
||||
TkRule::VarSub => {
|
||||
let styled = raw.styled(Style::Magenta);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
TkRule::Ident => {
|
||||
if in_array || in_case {
|
||||
if &raw == "in" {
|
||||
let styled = &raw.styled(Style::Yellow);
|
||||
result.push_str(&styled);
|
||||
if in_case { in_case = false };
|
||||
} else {
|
||||
let styled = &raw.styled(Style::Magenta);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
} else if let Some((var,val)) = raw.split_once('=') {
|
||||
let var_styled = var.styled(Style::Magenta);
|
||||
let val_styled = val.styled(Style::Cyan);
|
||||
let rebuilt = vec![var_styled,val_styled].join("=");
|
||||
result.push_str(&rebuilt);
|
||||
} else if raw.starts_with(['"','\'']) {
|
||||
let styled = &raw.styled(Style::BrightYellow);
|
||||
result.push_str(&styled);
|
||||
} else if &raw == "{" || &raw == "}" {
|
||||
result.push_str(&raw);
|
||||
|
||||
} else if is_command {
|
||||
if get_bin_path(&token.as_raw(&mut shenv_clone), self.shenv).is_some() ||
|
||||
self.shenv.logic().get_alias(&raw).is_some() ||
|
||||
self.shenv.logic().get_function(&raw).is_some() ||
|
||||
BUILTINS.contains(&raw.as_str()) {
|
||||
let styled = &raw.styled(Style::Green);
|
||||
result.push_str(&styled);
|
||||
|
||||
} else {
|
||||
let styled = &raw.styled(Style::Red | Style::Bold);
|
||||
result.push_str(&styled);
|
||||
}
|
||||
|
||||
is_command = false;
|
||||
|
||||
} else {
|
||||
result.push_str(&raw);
|
||||
}
|
||||
}
|
||||
TkRule::Sep => {
|
||||
is_command = true;
|
||||
in_array = false;
|
||||
result.push_str(&raw);
|
||||
}
|
||||
_ => {
|
||||
result.push_str(&raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::borrow::Cow::Owned(result)
|
||||
}
|
||||
|
||||
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
|
||||
&'s self,
|
||||
prompt: &'p str,
|
||||
default: bool,
|
||||
) -> std::borrow::Cow<'b, str> {
|
||||
let _ = default;
|
||||
std::borrow::Cow::Borrowed(prompt)
|
||||
}
|
||||
|
||||
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
|
||||
std::borrow::Cow::Borrowed(hint)
|
||||
}
|
||||
|
||||
fn highlight_candidate<'c>(
|
||||
&self,
|
||||
candidate: &'c str, // FIXME should be Completer::Candidate
|
||||
completion: rustyline::CompletionType,
|
||||
) -> std::borrow::Cow<'c, str> {
|
||||
let _ = completion;
|
||||
std::borrow::Cow::Borrowed(candidate)
|
||||
}
|
||||
|
||||
fn highlight_char(&self, line: &str, pos: usize, kind: rustyline::highlight::CmdKind) -> bool {
|
||||
let _ = (line, pos, kind);
|
||||
true
|
||||
}
|
||||
}
|
||||
260
src/prompt/history.rs
Normal file
260
src/prompt/history.rs
Normal file
@@ -0,0 +1,260 @@
|
||||
use std::{fs::{File, OpenOptions}, ops::{Deref, DerefMut}, path::PathBuf};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use rustyline::history::{History, SearchResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{libsh::error::{ShErr, ShErrKind, ShResult}, prelude::*};
|
||||
|
||||
#[derive(Deserialize,Serialize,Debug)]
|
||||
pub struct HistEntry {
|
||||
body: String,
|
||||
id: usize
|
||||
}
|
||||
|
||||
impl HistEntry {
|
||||
pub fn new(body: String, id: usize) -> Self {
|
||||
Self { body, id }
|
||||
}
|
||||
pub fn cmd(&self) -> &str {
|
||||
&self.body
|
||||
}
|
||||
pub fn id(&self) -> usize {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize,Serialize,Default)]
|
||||
pub struct HistEntries {
|
||||
entries: Vec<HistEntry>
|
||||
}
|
||||
|
||||
impl HistEntries {
|
||||
pub fn new() -> Self {
|
||||
Self { entries: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for HistEntries {
|
||||
type Target = Vec<HistEntry>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.entries
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for HistEntries {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.entries
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FernHist {
|
||||
file_path: Option<PathBuf>,
|
||||
entries: HistEntries,
|
||||
max_len: usize,
|
||||
pub flags: HistFlags
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct HistFlags: u32 {
|
||||
const NO_DUPES = 0b0000001;
|
||||
const IGNORE_SPACE = 0b0000010;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'e> FernHist {
|
||||
pub fn new() -> Self {
|
||||
Self { file_path: None, entries: HistEntries::new(), max_len: 1000, flags: HistFlags::empty() }
|
||||
}
|
||||
pub fn from_path(file_path: PathBuf) -> ShResult<'e,Self> {
|
||||
let mut new_hist = FernHist::new();
|
||||
new_hist.file_path = Some(file_path);
|
||||
new_hist.load_hist()?;
|
||||
Ok(new_hist)
|
||||
}
|
||||
pub fn create_entry(&self, body: &str) -> HistEntry {
|
||||
let id = self.len() + 1;
|
||||
HistEntry::new(body.to_string(), id)
|
||||
}
|
||||
pub fn init_hist_file(&mut self) -> ShResult<'e,()> {
|
||||
let Some(path) = self.file_path.clone() else {
|
||||
return Ok(());
|
||||
};
|
||||
self.save(&path)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn load_hist(&mut self) -> ShResult<'e,()> {
|
||||
let Some(file_path) = self.file_path.clone() else {
|
||||
return Err(
|
||||
ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
"History file not set"
|
||||
)
|
||||
)
|
||||
};
|
||||
if !file_path.is_file() {
|
||||
self.init_hist_file()?;
|
||||
}
|
||||
let hist_file = File::open(&file_path)?;
|
||||
self.entries = serde_yaml::from_reader(hist_file).unwrap_or_default();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Default for FernHist {
|
||||
fn default() -> Self {
|
||||
let home = std::env::var("HOME").unwrap();
|
||||
let file_path = PathBuf::from(&format!("{home}/.fernhist"));
|
||||
Self::from_path(file_path).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl History for FernHist {
|
||||
fn add(&mut self, line: &str) -> rustyline::Result<bool> {
|
||||
let new_entry = self.create_entry(line);
|
||||
if self.flags.contains(HistFlags::NO_DUPES) {
|
||||
let most_recent = self.get(self.len(), rustyline::history::SearchDirection::Reverse)?.unwrap();
|
||||
dbg!(&most_recent);
|
||||
if new_entry.body == most_recent.entry.to_string() {
|
||||
return Ok(false)
|
||||
}
|
||||
}
|
||||
self.entries.push(new_entry);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn get(&self, index: usize, dir: rustyline::history::SearchDirection) -> rustyline::Result<Option<rustyline::history::SearchResult>> {
|
||||
Ok(self.entries.iter().find(|ent| ent.id() == index).map(|ent| {
|
||||
SearchResult { entry: ent.cmd().to_string().into(), idx: index, pos: 0 }
|
||||
}))
|
||||
}
|
||||
|
||||
fn add_owned(&mut self, line: String) -> rustyline::Result<bool> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
fn set_max_len(&mut self, len: usize) -> rustyline::Result<()> {
|
||||
self.max_len = len;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ignore_dups(&mut self, yes: bool) -> rustyline::Result<()> {
|
||||
if yes {
|
||||
self.flags |= HistFlags::NO_DUPES;
|
||||
} else {
|
||||
self.flags &= !HistFlags::NO_DUPES;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ignore_space(&mut self, yes: bool) {
|
||||
if yes {
|
||||
self.flags |= HistFlags::IGNORE_SPACE;
|
||||
} else {
|
||||
self.flags &= !HistFlags::IGNORE_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
fn save(&mut self, path: &std::path::Path) -> rustyline::Result<()> {
|
||||
let hist_file = File::create(path)?;
|
||||
serde_yaml::to_writer(hist_file, &self.entries).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append(&mut self, path: &std::path::Path) -> rustyline::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn load(&mut self, path: &std::path::Path) -> rustyline::Result<()> {
|
||||
let path = path.to_path_buf();
|
||||
self.file_path = Some(path);
|
||||
self.load_hist().map_err(|_| rustyline::error::ReadlineError::Io(std::io::Error::last_os_error()))
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> rustyline::Result<()> {
|
||||
self.entries.clear();
|
||||
if self.file_path.is_some() {
|
||||
self.save(&self.file_path.clone().unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn search(
|
||||
&self,
|
||||
term: &str,
|
||||
start: usize,
|
||||
dir: rustyline::history::SearchDirection,
|
||||
) -> rustyline::Result<Option<rustyline::history::SearchResult>> {
|
||||
if term.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
let mut matches: Vec<&HistEntry> = self.entries.iter()
|
||||
.filter(|ent| is_subsequence(&ent.body, term))
|
||||
.collect();
|
||||
|
||||
matches.sort_by(|ent_a, ent_b| {
|
||||
let ent_a_rank = fuzzy_rank(term, &ent_a.body);
|
||||
let ent_b_rank = fuzzy_rank(term, &ent_b.body);
|
||||
ent_a_rank.cmp(&ent_b_rank)
|
||||
.then(ent_a.id().cmp(&ent_b.id()))
|
||||
});
|
||||
|
||||
Ok(matches.last().map(|ent| {
|
||||
SearchResult {
|
||||
entry: ent.body.clone().into(),
|
||||
idx: ent.id(),
|
||||
pos: start
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn starts_with(
|
||||
&self,
|
||||
term: &str,
|
||||
start: usize,
|
||||
dir: rustyline::history::SearchDirection,
|
||||
) -> rustyline::Result<Option<rustyline::history::SearchResult>> {
|
||||
let mut matches: Vec<&HistEntry> = self.entries.iter()
|
||||
.filter(|ent| ent.body.starts_with(term))
|
||||
.collect();
|
||||
|
||||
matches.sort_by(|ent_a, ent_b| ent_a.id().cmp(&ent_b.id()));
|
||||
dbg!(&matches);
|
||||
Ok(matches.first().map(|ent| {
|
||||
SearchResult {
|
||||
entry: ent.body.clone().into(),
|
||||
idx: ent.id(),
|
||||
pos: start
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn fuzzy_rank(search_term: &str, search_result: &str) -> u8 {
|
||||
if search_result == search_term {
|
||||
4
|
||||
} else if search_result.starts_with(search_term) {
|
||||
3
|
||||
} else if search_result.contains(search_term) {
|
||||
2
|
||||
} else if is_subsequence(search_result, search_term) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a search term is a subsequence of the body (characters in order but not necessarily adjacent)
|
||||
fn is_subsequence(search_result: &str, search_term: &str) -> bool {
|
||||
let mut result_chars = search_result.chars();
|
||||
search_term.chars().all(|ch| result_chars.any(|c| c == ch))
|
||||
}
|
||||
@@ -1,59 +1,38 @@
|
||||
use crate::prelude::*;
|
||||
use readline::SynHelper;
|
||||
use rustyline::{config::Configurer, history::{DefaultHistory, History}, ColorMode, CompletionType, Config, EditMode, Editor};
|
||||
|
||||
pub mod history;
|
||||
pub mod readline;
|
||||
pub mod highlight;
|
||||
pub mod validate;
|
||||
pub mod comp;
|
||||
|
||||
fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
|
||||
let hist_path = std::env::var("FERN_HIST").unwrap_or_default();
|
||||
let config = Config::builder()
|
||||
.max_history_size(1000).unwrap()
|
||||
.history_ignore_dups(true).unwrap()
|
||||
.completion_prompt_limit(100)
|
||||
.edit_mode(EditMode::Vi)
|
||||
.color_mode(ColorMode::Enabled)
|
||||
.tab_stop(2)
|
||||
.build();
|
||||
use std::path::Path;
|
||||
|
||||
let mut editor = Editor::with_config(config).unwrap();
|
||||
editor.set_completion_type(CompletionType::List);
|
||||
editor.set_helper(Some(SynHelper::new(shenv)));
|
||||
if !hist_path.is_empty() {
|
||||
editor.load_history(&PathBuf::from(hist_path)).unwrap();
|
||||
}
|
||||
editor
|
||||
use history::FernHist;
|
||||
use readline::FernReadline;
|
||||
use rustyline::{error::ReadlineError, history::{FileHistory, History}, Config, Editor};
|
||||
|
||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
|
||||
|
||||
fn init_rl<'s>() -> ShResult<'s,Editor<FernReadline,FernHist>> {
|
||||
let hist = FernHist::default();
|
||||
let rl = FernReadline::new();
|
||||
let config = Config::default();
|
||||
let mut editor = Editor::with_history(config,hist)?;
|
||||
editor.set_helper(Some(rl));
|
||||
Ok(editor)
|
||||
}
|
||||
|
||||
pub fn read_line(shenv: &mut ShEnv) -> ShResult<String> {
|
||||
log!(TRACE, "Entering prompt");
|
||||
shenv.meta_mut().stop_timer();
|
||||
let ps1 = std::env::var("PS1").unwrap_or("\\$ ".styled(Style::Green | Style::Bold));
|
||||
let prompt = expand_prompt(&ps1,shenv)?;
|
||||
let mut editor = init_rl(shenv);
|
||||
pub fn read_line<'s>() -> ShResult<'s,String> {
|
||||
let mut editor = init_rl()?;
|
||||
let prompt = "$ ".styled(Style::Green | Style::Bold);
|
||||
match editor.readline(&prompt) {
|
||||
Ok(line) => {
|
||||
if !line.is_empty() {
|
||||
let hist_path = std::env::var("FERN_HIST").ok();
|
||||
editor.history_mut().add(&line).unwrap();
|
||||
if let Some(path) = hist_path {
|
||||
editor.history_mut().save(&PathBuf::from(path)).unwrap();
|
||||
}
|
||||
editor.add_history_entry(&line)?;
|
||||
editor.save_history(&Path::new("/home/pagedmov/.fernhist"))?;
|
||||
}
|
||||
Ok(line)
|
||||
},
|
||||
Err(rustyline::error::ReadlineError::Eof) => {
|
||||
kill(Pid::this(), Signal::SIGQUIT)?;
|
||||
Ok(String::new())
|
||||
}
|
||||
Err(rustyline::error::ReadlineError::Interrupted) => {
|
||||
Ok(String::new())
|
||||
}
|
||||
Err(ReadlineError::Eof) => std::process::exit(0),
|
||||
Err(ReadlineError::Interrupted) => Ok(String::new()),
|
||||
Err(e) => {
|
||||
log!(ERROR, e);
|
||||
Err(e.into())
|
||||
return Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +1,66 @@
|
||||
use rustyline::{completion::{Candidate, Completer, FilenameCompleter}, hint::{Hint, Hinter}, history::{History, SearchDirection}, Helper};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::prelude::*;
|
||||
use rustyline::{completion::Completer, highlight::Highlighter, hint::{Hint, Hinter}, validate::{ValidationResult, Validator}, Helper};
|
||||
|
||||
pub struct SynHelper<'a> {
|
||||
pub file_comp: FilenameCompleter,
|
||||
pub shenv: &'a mut ShEnv,
|
||||
use crate::{libsh::term::{Style, Styled}, prelude::*};
|
||||
|
||||
pub struct FernReadline {
|
||||
}
|
||||
|
||||
impl<'a> Helper for SynHelper<'a> {}
|
||||
|
||||
impl<'a> SynHelper<'a> {
|
||||
pub fn new(shenv: &'a mut ShEnv) -> Self {
|
||||
Self {
|
||||
file_comp: FilenameCompleter::new(),
|
||||
shenv,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hist_search(&self, term: &str, hist: &dyn History) -> Option<String> {
|
||||
let limit = hist.len();
|
||||
let mut latest_match = None;
|
||||
for i in 0..limit {
|
||||
if let Some(hist_entry) = hist.get(i, SearchDirection::Forward).ok()? {
|
||||
if hist_entry.entry.starts_with(term) {
|
||||
latest_match = Some(hist_entry.entry.into_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
latest_match
|
||||
impl FernReadline {
|
||||
pub fn new() -> Self {
|
||||
Self { }
|
||||
}
|
||||
}
|
||||
|
||||
impl Helper for FernReadline {}
|
||||
|
||||
impl Completer for FernReadline {
|
||||
type Candidate = String;
|
||||
}
|
||||
|
||||
|
||||
pub struct SynHint {
|
||||
text: String,
|
||||
pub struct FernHint {
|
||||
raw: String,
|
||||
styled: String
|
||||
}
|
||||
|
||||
impl SynHint {
|
||||
pub fn new(text: String) -> Self {
|
||||
let styled = (&text).styled(Style::BrightBlack);
|
||||
Self { text, styled }
|
||||
}
|
||||
pub fn empty() -> Self {
|
||||
Self { text: String::new(), styled: String::new() }
|
||||
impl FernHint {
|
||||
pub fn new(raw: String) -> Self {
|
||||
let styled = (&raw).styled(Style::Dim | Style::BrightBlack);
|
||||
Self { raw, styled }
|
||||
}
|
||||
}
|
||||
|
||||
impl Hint for SynHint {
|
||||
impl Hint for FernHint {
|
||||
fn display(&self) -> &str {
|
||||
&self.styled
|
||||
}
|
||||
fn completion(&self) -> Option<&str> {
|
||||
if !self.text.is_empty() {
|
||||
Some(&self.text)
|
||||
if !self.raw.is_empty() {
|
||||
Some(&self.raw)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Hinter for SynHelper<'a> {
|
||||
type Hint = SynHint;
|
||||
impl Hinter for FernReadline {
|
||||
type Hint = FernHint;
|
||||
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
|
||||
if line.is_empty() {
|
||||
return None
|
||||
}
|
||||
let history = ctx.history();
|
||||
let result = self.hist_search(line, history)?;
|
||||
let window = result[line.len()..].trim_end().to_string();
|
||||
Some(SynHint::new(window))
|
||||
let ent = ctx.history().search(line, pos, rustyline::history::SearchDirection::Reverse).ok()??;
|
||||
let entry_raw = ent.entry.get(pos..)?.to_string();
|
||||
Some(FernHint::new(entry_raw))
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter for FernReadline {
|
||||
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
|
||||
Cow::Owned(line.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Validator for FernReadline {
|
||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
Ok(ValidationResult::Valid(None))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
use rustyline::validate::{ValidationResult, Validator};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::readline::SynHelper;
|
||||
|
||||
pub fn check_delims(line: &str) -> bool {
|
||||
let mut delim_stack = vec![];
|
||||
let mut chars = line.chars();
|
||||
let mut case_depth: u64 = 0;
|
||||
let mut case_check = String::new();
|
||||
let mut in_quote = None; // Tracks which quote type is open (`'` or `"`)
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
case_check.push(ch);
|
||||
if case_check.ends_with("case") {
|
||||
case_depth += 1;
|
||||
}
|
||||
if case_check.ends_with("esac") {
|
||||
case_depth = case_depth.saturating_sub(1);
|
||||
}
|
||||
match ch {
|
||||
'{' | '(' | '[' if in_quote.is_none() => delim_stack.push(ch),
|
||||
'}' if in_quote.is_none() && delim_stack.pop() != Some('{') => return false,
|
||||
')' if in_quote.is_none() && delim_stack.pop() != Some('(') => {
|
||||
if case_depth == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
']' if in_quote.is_none() && delim_stack.pop() != Some('[') => return false,
|
||||
'"' | '\'' => {
|
||||
if in_quote == Some(ch) {
|
||||
in_quote = None;
|
||||
} else if in_quote.is_none() {
|
||||
in_quote = Some(ch);
|
||||
}
|
||||
}
|
||||
'\\' => { chars.next(); } // Skip next character if escaped
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
delim_stack.is_empty() && in_quote.is_none()
|
||||
}
|
||||
|
||||
pub fn check_keywords(line: &str, shenv: &mut ShEnv) -> bool {
|
||||
shenv.new_input(line);
|
||||
let tokens = Lexer::new(line.to_string(),shenv).lex();
|
||||
Parser::new(tokens, shenv).parse().is_ok()
|
||||
}
|
||||
|
||||
impl<'a> Validator for SynHelper<'a> {
|
||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||
let input = ctx.input();
|
||||
let mut shenv_clone = self.shenv.clone();
|
||||
match check_delims(input) && check_keywords(input, &mut shenv_clone) {
|
||||
true => Ok(ValidationResult::Valid(None)),
|
||||
false => Ok(ValidationResult::Incomplete),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy,Clone,Debug,PartialEq,PartialOrd)]
|
||||
pub struct ExecFlags: u32 {
|
||||
const NO_FORK = 0b00000001;
|
||||
const IN_FUNC = 0b00000010;
|
||||
const NO_EXPAND = 0b00000100;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ExecCtx {
|
||||
redirs: Vec<Redir>,
|
||||
depth: usize,
|
||||
max_depth: usize,
|
||||
flags: ExecFlags,
|
||||
io_masks: IoMasks,
|
||||
saved_io: Option<SavedIo>
|
||||
}
|
||||
|
||||
impl ExecCtx {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
redirs: vec![],
|
||||
depth: 0,
|
||||
max_depth: 1500,
|
||||
flags: ExecFlags::empty(),
|
||||
io_masks: IoMasks::new(),
|
||||
saved_io: None
|
||||
}
|
||||
}
|
||||
pub fn as_cond(&self) -> Self {
|
||||
let mut clone = self.clone();
|
||||
let (cond_redirs,_) = self.sort_redirs();
|
||||
clone.redirs = cond_redirs;
|
||||
clone
|
||||
}
|
||||
pub fn as_body(&self) -> Self {
|
||||
let mut clone = self.clone();
|
||||
let (_,body_redirs) = self.sort_redirs();
|
||||
clone.redirs = body_redirs;
|
||||
clone
|
||||
}
|
||||
pub fn redirs(&self) -> &Vec<Redir> {
|
||||
&self.redirs
|
||||
}
|
||||
pub fn clear_redirs(&mut self) {
|
||||
self.redirs.clear()
|
||||
}
|
||||
pub fn sort_redirs(&self) -> (Vec<Redir>,Vec<Redir>) {
|
||||
let mut cond_redirs = vec![];
|
||||
let mut body_redirs = vec![];
|
||||
for redir in self.redirs.clone() {
|
||||
match redir.op {
|
||||
RedirType::Input |
|
||||
RedirType::HereString |
|
||||
RedirType::HereDoc => cond_redirs.push(redir),
|
||||
RedirType::Output |
|
||||
RedirType::Append => body_redirs.push(redir)
|
||||
}
|
||||
}
|
||||
(cond_redirs,body_redirs)
|
||||
}
|
||||
pub fn masks(&self) -> &IoMasks {
|
||||
&self.io_masks
|
||||
}
|
||||
pub fn push_rdr(&mut self, redir: Redir) {
|
||||
self.redirs.push(redir)
|
||||
}
|
||||
pub fn saved_io(&mut self) -> &mut Option<SavedIo> {
|
||||
&mut self.saved_io
|
||||
}
|
||||
pub fn activate_rdrs(&mut self) -> ShResult<()> {
|
||||
let mut redirs = CmdRedirs::new(core::mem::take(&mut self.redirs));
|
||||
self.redirs = vec![];
|
||||
redirs.activate()?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn flags(&self) -> ExecFlags {
|
||||
self.flags
|
||||
}
|
||||
pub fn set_flag(&mut self, flag: ExecFlags) {
|
||||
self.flags |= flag
|
||||
}
|
||||
pub fn unset_flag(&mut self, flag: ExecFlags) {
|
||||
self.flags &= !flag
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct SavedIo {
|
||||
pub stdin: RawFd,
|
||||
pub stdout: RawFd,
|
||||
pub stderr: RawFd
|
||||
}
|
||||
|
||||
impl SavedIo {
|
||||
pub fn save(stdin: RawFd, stdout: RawFd, stderr: RawFd) -> Self {
|
||||
Self { stdin, stdout, stderr }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct IoMask {
|
||||
default: RawFd,
|
||||
mask: Option<RawFd>
|
||||
}
|
||||
|
||||
impl IoMask {
|
||||
pub fn new(default: RawFd) -> Self {
|
||||
Self { default, mask: None }
|
||||
}
|
||||
pub fn new_mask(&mut self, mask: RawFd) {
|
||||
self.mask = Some(mask)
|
||||
}
|
||||
pub fn unmask(&mut self) {
|
||||
self.mask = None
|
||||
}
|
||||
pub fn get_fd(&self) -> RawFd {
|
||||
if let Some(fd) = self.mask {
|
||||
fd
|
||||
} else {
|
||||
self.default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
/// Necessary for when process file descriptors are permanently redirected using `exec`
|
||||
pub struct IoMasks {
|
||||
stdin: IoMask,
|
||||
stdout: IoMask,
|
||||
stderr: IoMask
|
||||
}
|
||||
|
||||
impl IoMasks {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stdin: IoMask::new(0),
|
||||
stdout: IoMask::new(1),
|
||||
stderr: IoMask::new(2),
|
||||
}
|
||||
}
|
||||
pub fn stdin(&self) -> &IoMask {
|
||||
&self.stdin
|
||||
}
|
||||
pub fn stdout(&self) -> &IoMask {
|
||||
&self.stdout
|
||||
}
|
||||
pub fn stderr(&self) -> &IoMask {
|
||||
&self.stderr
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub struct SavedSpan {
|
||||
pointer: Rc<RefCell<Span>>,
|
||||
start: usize,
|
||||
end: usize,
|
||||
expanded: bool
|
||||
}
|
||||
|
||||
impl SavedSpan {
|
||||
pub fn from_span(pointer: Rc<RefCell<Span>>) -> Self {
|
||||
let expanded = pointer.borrow().expanded;
|
||||
let start = pointer.borrow().start();
|
||||
let end = pointer.borrow().end();
|
||||
Self { pointer, start, end, expanded }
|
||||
}
|
||||
pub fn restore(&self) {
|
||||
let mut deref = self.pointer.borrow_mut();
|
||||
deref.set_start(self.start);
|
||||
deref.set_end(self.end);
|
||||
deref.expanded = self.expanded
|
||||
}
|
||||
pub fn into_span(self) -> Rc<RefCell<Span>> {
|
||||
self.pointer
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub struct InputMan {
|
||||
input: Option<String>,
|
||||
spans: Vec<Rc<RefCell<Span>>>,
|
||||
saved_states: Vec<(String,Vec<SavedSpan>)>,
|
||||
}
|
||||
|
||||
impl InputMan {
|
||||
pub fn new() -> Self {
|
||||
Self { input: None, spans: vec![], saved_states: vec![] }
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
pub fn new_input(&mut self, input: &str) {
|
||||
self.input = Some(input.to_string())
|
||||
}
|
||||
pub fn get_input(&self) -> Option<&String> {
|
||||
self.input.as_ref()
|
||||
}
|
||||
pub fn get_input_mut(&mut self) -> Option<&mut String> {
|
||||
self.input.as_mut()
|
||||
}
|
||||
pub fn push_state(&mut self) {
|
||||
if let Some(input) = &self.input {
|
||||
let saved_input = input.clone();
|
||||
let mut saved_spans = vec![];
|
||||
for span in &self.spans {
|
||||
let saved_span = SavedSpan::from_span(span.clone());
|
||||
saved_spans.push(saved_span);
|
||||
}
|
||||
self.saved_states.push((saved_input,saved_spans));
|
||||
}
|
||||
}
|
||||
pub fn pop_state(&mut self) {
|
||||
if let Some((saved_input, saved_spans)) = self.saved_states.pop() {
|
||||
self.input = Some(saved_input);
|
||||
let mut restored_spans = vec![];
|
||||
for saved_span in saved_spans.into_iter() {
|
||||
saved_span.restore();
|
||||
let span = saved_span.into_span();
|
||||
restored_spans.push(span);
|
||||
}
|
||||
self.spans = restored_spans;
|
||||
}
|
||||
}
|
||||
pub fn new_span(&mut self, start: usize, end: usize) -> Rc<RefCell<Span>> {
|
||||
if let Some(_input) = &self.input {
|
||||
let span = Rc::new(RefCell::new(Span::new(start, end)));
|
||||
self.spans.push(span.clone());
|
||||
span
|
||||
} else {
|
||||
Rc::new(RefCell::new(Span::new(0,0)))
|
||||
}
|
||||
}
|
||||
pub fn remove_span(&mut self, span: Rc<RefCell<Span>>) {
|
||||
if let Some(idx) = self.spans.iter().position(|iter_span| *iter_span == span) {
|
||||
self.spans.remove(idx);
|
||||
}
|
||||
}
|
||||
pub fn spans_mut(&mut self) -> &mut Vec<Rc<RefCell<Span>>> {
|
||||
&mut self.spans
|
||||
}
|
||||
pub fn clamp(&self, span: Rc<RefCell<Span>>) {
|
||||
let mut span = span.borrow_mut();
|
||||
if let Some(input) = &self.input {
|
||||
span.clamp_start(input.len());
|
||||
span.clamp_end(input.len());
|
||||
}
|
||||
}
|
||||
pub fn clamp_all(&self) {
|
||||
for span in &self.spans {
|
||||
self.clamp(span.clone());
|
||||
}
|
||||
}
|
||||
pub fn get_slice(&self, span: Rc<RefCell<Span>>) -> Option<&str> {
|
||||
let span = span.borrow();
|
||||
let mut start = span.start();
|
||||
let end = span.end();
|
||||
if start > end {
|
||||
start = end;
|
||||
}
|
||||
|
||||
self.input.as_ref().map(|s| &s[start..end])
|
||||
}
|
||||
}
|
||||
@@ -1,576 +0,0 @@
|
||||
use std::{fmt, sync::{Arc, LazyLock, RwLock}};
|
||||
|
||||
use nix::unistd::setpgid;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct JobCmdFlags: u8 {
|
||||
const LONG = 0b0000_0001; // 0x01
|
||||
const PIDS = 0b0000_0010; // 0x02
|
||||
const NEW_ONLY = 0b0000_0100; // 0x04
|
||||
const RUNNING = 0b0000_1000; // 0x08
|
||||
const STOPPED = 0b0001_0000; // 0x10
|
||||
const INIT = 0b0010_0000; // 0x20
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DisplayWaitStatus(pub WtStat);
|
||||
|
||||
impl fmt::Display for DisplayWaitStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => write!(f, "done"),
|
||||
_ => write!(f, "failed: {}", code),
|
||||
}
|
||||
}
|
||||
WtStat::Signaled(_, signal, _) => {
|
||||
write!(f, "signaled: {:?}", signal)
|
||||
}
|
||||
WtStat::Stopped(_, signal) => {
|
||||
write!(f, "stopped: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceEvent(_, signal, _) => {
|
||||
write!(f, "ptrace event: {:?}", signal)
|
||||
}
|
||||
WtStat::PtraceSyscall(_) => {
|
||||
write!(f, "ptrace syscall")
|
||||
}
|
||||
WtStat::Continued(_) => {
|
||||
write!(f, "continued")
|
||||
}
|
||||
WtStat::StillAlive => {
|
||||
write!(f, "running")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The job table
|
||||
pub static JOBS: LazyLock<Arc<RwLock<JobTab>>> = LazyLock::new(|| {
|
||||
Arc::new(
|
||||
RwLock::new(
|
||||
JobTab::new()
|
||||
)
|
||||
)
|
||||
});
|
||||
|
||||
pub fn write_jobs<'a,T,F: FnOnce(&mut JobTab) -> T>(operation: F) -> T {
|
||||
unsafe {
|
||||
let mut jobs = JOBS.write().unwrap();
|
||||
operation(&mut jobs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_jobs<'a,T,F: FnOnce(&JobTab) -> T>(operation: F) -> T {
|
||||
unsafe {
|
||||
let jobs = JOBS.read().unwrap();
|
||||
operation(&jobs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub enum JobID {
|
||||
Pgid(Pid),
|
||||
Pid(Pid),
|
||||
TableID(usize),
|
||||
Command(String)
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct ChildProc {
|
||||
pgid: Pid,
|
||||
pid: Pid,
|
||||
command: Option<String>,
|
||||
stat: WtStat
|
||||
}
|
||||
|
||||
impl<'a> ChildProc {
|
||||
pub fn new(pid: Pid, command: Option<&str>, pgid: Option<Pid>) -> ShResult<Self> {
|
||||
let command = command.map(|str| str.to_string());
|
||||
let stat = if kill(pid,None).is_ok() {
|
||||
WtStat::StillAlive
|
||||
} else {
|
||||
WtStat::Exited(pid, 0)
|
||||
};
|
||||
let mut child = Self { pgid: pid, pid, command, stat };
|
||||
if let Some(pgid) = pgid {
|
||||
child.set_pgid(pgid).ok();
|
||||
}
|
||||
Ok(child)
|
||||
}
|
||||
pub fn pid(&self) -> Pid {
|
||||
self.pid
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn cmd(&self) -> Option<&str> {
|
||||
self.command.as_ref().map(|cmd| cmd.as_str())
|
||||
}
|
||||
pub fn stat(&self) -> WtStat {
|
||||
self.stat
|
||||
}
|
||||
pub fn wait(&mut self, flags: Option<WtFlag>) -> Result<WtStat,Errno> {
|
||||
let result = waitpid(self.pid, flags);
|
||||
if let Ok(stat) = result {
|
||||
self.stat = stat
|
||||
}
|
||||
result
|
||||
}
|
||||
pub fn kill<T: Into<Option<Signal>>>(&self, sig: T) -> ShResult<()> {
|
||||
Ok(kill(self.pid, sig)?)
|
||||
}
|
||||
pub fn set_pgid(&mut self, pgid: Pid) -> ShResult<()> {
|
||||
unsafe { setpgid(self.pid, pgid)? };
|
||||
self.pgid = pgid;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_stat(&mut self, stat: WtStat) {
|
||||
self.stat = stat
|
||||
}
|
||||
pub fn is_alive(&self) -> bool {
|
||||
self.stat == WtStat::StillAlive
|
||||
}
|
||||
pub fn is_stopped(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Stopped(..))
|
||||
}
|
||||
pub fn exited(&self) -> bool {
|
||||
matches!(self.stat,WtStat::Exited(..))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobBldr {
|
||||
table_id: Option<usize>,
|
||||
pgid: Option<Pid>,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Default for JobBldr {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl JobBldr {
|
||||
pub fn new() -> Self {
|
||||
Self { table_id: None, pgid: None, children: vec![] }
|
||||
}
|
||||
pub fn with_id(self, id: usize) -> Self {
|
||||
Self {
|
||||
table_id: Some(id),
|
||||
pgid: self.pgid,
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_pgid(self, pgid: Pid) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: Some(pgid),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
|
||||
Self {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid,
|
||||
children
|
||||
}
|
||||
}
|
||||
pub fn build(self) -> Job {
|
||||
Job {
|
||||
table_id: self.table_id,
|
||||
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
|
||||
children: self.children
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Job {
|
||||
table_id: Option<usize>,
|
||||
pgid: Pid,
|
||||
children: Vec<ChildProc>
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn set_tabid(&mut self, id: usize) {
|
||||
self.table_id = Some(id)
|
||||
}
|
||||
pub fn running(&self) -> bool {
|
||||
!self.children.iter().all(|chld| chld.exited())
|
||||
}
|
||||
pub fn tabid(&self) -> Option<usize> {
|
||||
self.table_id
|
||||
}
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.pgid
|
||||
}
|
||||
pub fn get_cmds(&self) -> Vec<&str> {
|
||||
let mut cmds = vec![];
|
||||
for child in &self.children {
|
||||
cmds.push(child.cmd().unwrap_or_default())
|
||||
}
|
||||
cmds
|
||||
}
|
||||
pub fn set_stats(&mut self, stat: WtStat) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
pub fn get_stats(&self) -> Vec<WtStat> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.stat())
|
||||
.collect::<Vec<WtStat>>()
|
||||
}
|
||||
pub fn get_pids(&self) -> Vec<Pid> {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|chld| chld.pid())
|
||||
.collect::<Vec<Pid>>()
|
||||
}
|
||||
pub fn children(&self) -> &[ChildProc] {
|
||||
&self.children
|
||||
}
|
||||
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
|
||||
&mut self.children
|
||||
}
|
||||
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
|
||||
let stat = match sig {
|
||||
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
|
||||
Signal::SIGCONT => WtStat::Continued(self.pgid),
|
||||
Signal::SIGTERM => WtStat::Signaled(self.pgid, Signal::SIGTERM, false),
|
||||
_ => unimplemented!("{}",sig)
|
||||
};
|
||||
self.set_stats(stat);
|
||||
Ok(killpg(self.pgid, sig)?)
|
||||
}
|
||||
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
|
||||
let mut stats = vec![];
|
||||
for child in self.children.iter_mut() {
|
||||
let result = child.wait(Some(WtFlag::WUNTRACED));
|
||||
match result {
|
||||
Ok(stat) => {
|
||||
stats.push(stat);
|
||||
}
|
||||
Err(Errno::ECHILD) => break,
|
||||
Err(e) => return Err(e.into())
|
||||
}
|
||||
}
|
||||
Ok(stats)
|
||||
}
|
||||
pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> {
|
||||
match id {
|
||||
JobID::Pid(pid) => {
|
||||
let query_result = self.children.iter_mut().find(|chld| chld.pid == pid);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::Command(cmd) => {
|
||||
let query_result = self.children
|
||||
.iter_mut()
|
||||
.find(|chld| chld
|
||||
.cmd()
|
||||
.is_some_and(|chld_cmd| chld_cmd.contains(&cmd))
|
||||
);
|
||||
if let Some(child) = query_result {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
JobID::TableID(tid) => {
|
||||
if self.table_id.is_some_and(|tblid| tblid == tid) {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
JobID::Pgid(pgid) => {
|
||||
if pgid == self.pgid {
|
||||
for child in self.children.iter_mut() {
|
||||
child.set_stat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn display(&self, job_order: &[usize], flags: JobCmdFlags) -> String {
|
||||
let long = flags.contains(JobCmdFlags::LONG);
|
||||
let init = flags.contains(JobCmdFlags::INIT);
|
||||
let pids = flags.contains(JobCmdFlags::PIDS);
|
||||
|
||||
let current = job_order.last();
|
||||
let prev = if job_order.len() > 2 {
|
||||
job_order.get(job_order.len() - 2)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let id = self.table_id.unwrap();
|
||||
let symbol = if current == self.table_id.as_ref() {
|
||||
"+"
|
||||
} else if prev == self.table_id.as_ref() {
|
||||
"-"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let padding_count = symbol.len() + id.to_string().len() + 3;
|
||||
let padding = " ".repeat(padding_count);
|
||||
|
||||
let mut output = format!("[{}]{}\t", id + 1, symbol);
|
||||
for (i, cmd) in self.get_cmds().iter().enumerate() {
|
||||
let pid = if pids || init {
|
||||
let mut pid = self.get_pids().get(i).unwrap().to_string();
|
||||
pid.push(' ');
|
||||
pid
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
let job_stat = *self.get_stats().get(i).unwrap();
|
||||
let fmt_stat = DisplayWaitStatus(job_stat).to_string();
|
||||
|
||||
let mut stat_line = if init {
|
||||
"".to_string()
|
||||
} else {
|
||||
fmt_stat.clone()
|
||||
};
|
||||
stat_line = format!("{}{} ",pid,stat_line);
|
||||
stat_line = format!("{} {}", stat_line, cmd);
|
||||
stat_line = match job_stat {
|
||||
WtStat::Stopped(..) | WtStat::Signaled(..) => stat_line.styled(Style::Magenta),
|
||||
WtStat::Exited(_, code) => {
|
||||
match code {
|
||||
0 => stat_line.styled(Style::Green),
|
||||
_ => stat_line.styled(Style::Red),
|
||||
}
|
||||
}
|
||||
_ => stat_line.styled(Style::Cyan)
|
||||
};
|
||||
if i != self.get_cmds().len() - 1 {
|
||||
stat_line = format!("{} |",stat_line);
|
||||
}
|
||||
|
||||
let stat_final = if long {
|
||||
format!(
|
||||
"{}{} {}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
self.get_pids().get(i).unwrap(),
|
||||
stat_line
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
if i != 0 { &padding } else { "" },
|
||||
stat_line
|
||||
)
|
||||
};
|
||||
output.push_str(&stat_final);
|
||||
output.push('\n');
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobTab {
|
||||
fg: Option<Job>,
|
||||
order: Vec<usize>,
|
||||
new_updates: Vec<usize>,
|
||||
jobs: Vec<Option<Job>>
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
pub fn new() -> Self {
|
||||
Self { fg: None, order: vec![], new_updates: vec![], jobs: vec![] }
|
||||
}
|
||||
pub fn take_fg(&mut self) -> Option<Job> {
|
||||
self.fg.take()
|
||||
}
|
||||
fn next_open_pos(&self) -> usize {
|
||||
if let Some(position) = self.jobs.iter().position(|slot| slot.is_none()) {
|
||||
position
|
||||
} else {
|
||||
self.jobs.len()
|
||||
}
|
||||
}
|
||||
pub fn jobs(&self) -> &Vec<Option<Job>> {
|
||||
&self.jobs
|
||||
}
|
||||
pub fn jobs_mut(&mut self) -> &mut Vec<Option<Job>> {
|
||||
&mut self.jobs
|
||||
}
|
||||
pub fn curr_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
pub fn prev_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
fn prune_jobs(&mut self) {
|
||||
while let Some(job) = self.jobs.last() {
|
||||
if job.is_none() {
|
||||
self.jobs.pop();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn insert_job(&mut self, mut job: Job, silent: bool) -> ShResult<usize> {
|
||||
self.prune_jobs();
|
||||
let tab_pos = if let Some(id) = job.tabid() { id } else { self.next_open_pos() };
|
||||
job.set_tabid(tab_pos);
|
||||
self.order.push(tab_pos);
|
||||
if !silent {
|
||||
write(borrow_fd(1),format!("{}", job.display(&self.order, JobCmdFlags::INIT)).as_bytes())?;
|
||||
}
|
||||
if tab_pos == self.jobs.len() {
|
||||
self.jobs.push(Some(job))
|
||||
} else {
|
||||
self.jobs[tab_pos] = Some(job);
|
||||
}
|
||||
Ok(tab_pos)
|
||||
}
|
||||
pub fn order(&self) -> &[usize] {
|
||||
&self.order
|
||||
}
|
||||
pub fn query(&self, identifier: JobID) -> Option<&Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.pgid == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.children.iter().any(|child| child.pid == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get(id).and_then(|job| job.as_ref())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| {
|
||||
j.children.iter().any(|child| {
|
||||
child.command.as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.pgid == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.children.iter().any(|child| child.pid == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get_mut(id).and_then(|job| job.as_mut())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| {
|
||||
j.children.iter().any(|child| {
|
||||
child.command.as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_fg(&self) -> Option<&Job> {
|
||||
self.fg.as_ref()
|
||||
}
|
||||
pub fn get_fg_mut(&mut self) -> Option<&mut Job> {
|
||||
self.fg.as_mut()
|
||||
}
|
||||
pub fn new_fg<'a>(&mut self, job: Job) -> ShResult<Vec<WtStat>> {
|
||||
let pgid = job.pgid();
|
||||
self.fg = Some(job);
|
||||
attach_tty(pgid)?;
|
||||
let statuses = self.fg.as_mut().unwrap().wait_pgrp()?;
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(statuses)
|
||||
}
|
||||
pub fn fg_to_bg(&mut self, stat: WtStat) -> ShResult<()> {
|
||||
if self.fg.is_none() {
|
||||
return Ok(())
|
||||
}
|
||||
take_term()?;
|
||||
let fg = std::mem::take(&mut self.fg);
|
||||
if let Some(mut job) = fg {
|
||||
job.set_stats(stat);
|
||||
self.insert_job(job, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn bg_to_fg(&mut self, shenv: &mut ShEnv, id: JobID) -> ShResult<()> {
|
||||
let job = self.remove_job(id);
|
||||
if let Some(job) = job {
|
||||
super::wait_fg(job, shenv)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
||||
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
||||
if let Some(tabid) = tabid {
|
||||
self.jobs.get_mut(tabid).and_then(Option::take)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn print_jobs(&mut self, flags: JobCmdFlags) -> ShResult<()> {
|
||||
let jobs = if flags.contains(JobCmdFlags::NEW_ONLY) {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.filter(|job| job.as_ref().is_some_and(|job| self.new_updates.contains(&job.tabid().unwrap())))
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
} else {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
};
|
||||
let mut jobs_to_remove = vec![];
|
||||
for job in jobs.iter().flatten() {
|
||||
// Skip foreground job
|
||||
let id = job.tabid().unwrap();
|
||||
// Filter jobs based on flags
|
||||
if flags.contains(JobCmdFlags::RUNNING) && !matches!(job.get_stats().get(id).unwrap(), WtStat::StillAlive | WtStat::Continued(_)) {
|
||||
continue;
|
||||
}
|
||||
if flags.contains(JobCmdFlags::STOPPED) && !matches!(job.get_stats().get(id).unwrap(), WtStat::Stopped(_,_)) {
|
||||
continue;
|
||||
}
|
||||
// Print the job in the selected format
|
||||
write(borrow_fd(1), format!("{}\n",job.display(&self.order,flags)).as_bytes())?;
|
||||
if job.get_stats().iter().all(|stat| matches!(stat,WtStat::Exited(_, _))) {
|
||||
jobs_to_remove.push(JobID::TableID(id));
|
||||
}
|
||||
}
|
||||
for id in jobs_to_remove {
|
||||
self.remove_job(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct LogTab {
|
||||
aliases: HashMap<String,String>,
|
||||
functions: HashMap<String,String>
|
||||
}
|
||||
|
||||
impl LogTab {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
aliases: HashMap::new(),
|
||||
functions: HashMap::new()
|
||||
}
|
||||
}
|
||||
pub fn get_alias(&self,name: &str) -> Option<&str> {
|
||||
self.aliases.get(name).map(|a| a.as_str())
|
||||
}
|
||||
pub fn set_alias(&mut self, name: &str, body: &str) {
|
||||
self.aliases.insert(name.to_string(),body.trim().to_string());
|
||||
}
|
||||
pub fn get_function(&self,name: &str) -> Option<&str> {
|
||||
self.functions.get(name).map(|a| a.as_str())
|
||||
}
|
||||
pub fn set_function(&mut self, name: &str, body: &str) {
|
||||
self.functions.insert(name.to_string(),body.trim().to_string());
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
use std::time::{Duration, Instant};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct MetaTab {
|
||||
timer_start: Instant,
|
||||
last_runtime: Option<Duration>,
|
||||
path_cmds: Vec<String> // Used for command completion
|
||||
}
|
||||
|
||||
impl MetaTab {
|
||||
pub fn new() -> Self {
|
||||
let path_cmds = get_path_cmds().unwrap_or_default();
|
||||
Self {
|
||||
timer_start: Instant::now(),
|
||||
last_runtime: None,
|
||||
path_cmds
|
||||
}
|
||||
}
|
||||
pub fn start_timer(&mut self) {
|
||||
self.timer_start = Instant::now();
|
||||
}
|
||||
pub fn stop_timer(&mut self) {
|
||||
self.last_runtime = Some(self.timer_start.elapsed());
|
||||
}
|
||||
pub fn get_runtime(&self) -> Option<Duration> {
|
||||
self.last_runtime
|
||||
}
|
||||
pub fn path_cmds(&self) -> &[String] {
|
||||
&self.path_cmds
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
use jobs::Job;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub mod jobs;
|
||||
pub mod logic;
|
||||
pub mod exec_ctx;
|
||||
pub mod meta;
|
||||
pub mod shenv;
|
||||
pub mod vars;
|
||||
pub mod input;
|
||||
pub mod shopt;
|
||||
|
||||
/// Calls attach_tty() on the shell's process group to retake control of the terminal
|
||||
pub fn take_term() -> ShResult<()> {
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_reaping() -> ShResult<()> {
|
||||
log!(TRACE, "Disabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::ignore_sigchld)) }?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits on the current foreground job and updates the shell's last status code
|
||||
pub fn wait_fg(job: Job, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
log!(TRACE, "Waiting on foreground job");
|
||||
let mut code = 0;
|
||||
attach_tty(job.pgid())?;
|
||||
disable_reaping()?;
|
||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||
for status in statuses {
|
||||
match status {
|
||||
WtStat::Exited(_, exit_code) => {
|
||||
code = exit_code;
|
||||
}
|
||||
WtStat::Stopped(pid, sig) => {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
code = sys::SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
WtStat::Signaled(pid, sig, _) => {
|
||||
if sig == Signal::SIGTSTP {
|
||||
write_jobs(|j| j.fg_to_bg(status))?;
|
||||
}
|
||||
code = sys::SIG_EXIT_OFFSET + sig as i32;
|
||||
},
|
||||
_ => { /* Do nothing */ }
|
||||
}
|
||||
}
|
||||
take_term()?;
|
||||
shenv.set_code(code);
|
||||
log!(TRACE, "exit code: {}", code);
|
||||
enable_reaping()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dispatch_job(job: Job, is_bg: bool, shenv: &mut ShEnv) -> ShResult<()> {
|
||||
if is_bg {
|
||||
write_jobs(|j| {
|
||||
j.insert_job(job, false)
|
||||
})?;
|
||||
} else {
|
||||
wait_fg(job, shenv)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log_level() -> crate::libsh::utils::LogLevel {
|
||||
let level = env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
||||
match level.to_lowercase().as_str() {
|
||||
"error" => ERROR,
|
||||
"warn" => WARN,
|
||||
"info" => INFO,
|
||||
"debug" => DEBUG,
|
||||
"trace" => TRACE,
|
||||
_ => NULL
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_reaping() -> ShResult<()> {
|
||||
log!(TRACE, "Enabling reaping");
|
||||
unsafe { signal(Signal::SIGCHLD, SigHandler::Handler(crate::signal::handle_sigchld)) }.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
||||
// If we aren't attached to a terminal, the pgid already controls it, or the process group does not exist
|
||||
// Then return ok
|
||||
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() {
|
||||
return Ok(())
|
||||
}
|
||||
log!(TRACE, "Attaching tty to pgid: {}",pgid);
|
||||
|
||||
if pgid == getpgrp() && term_ctlr() != getpgrp() {
|
||||
kill(term_ctlr(), Signal::SIGTTOU).ok();
|
||||
}
|
||||
|
||||
let mut new_mask = SigSet::empty();
|
||||
let mut mask_bkup = SigSet::empty();
|
||||
|
||||
new_mask.add(Signal::SIGTSTP);
|
||||
new_mask.add(Signal::SIGTTIN);
|
||||
new_mask.add(Signal::SIGTTOU);
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&mut new_mask), Some(&mut mask_bkup))?;
|
||||
|
||||
let result = unsafe { tcsetpgrp(borrow_fd(0), pgid) };
|
||||
|
||||
pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(&mut mask_bkup), Some(&mut new_mask))?;
|
||||
|
||||
match result {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => {
|
||||
log!(ERROR, "error while switching term control: {}",e);
|
||||
unsafe { tcsetpgrp(borrow_fd(0), getpgrp())? };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn term_ctlr() -> Pid {
|
||||
unsafe { tcgetpgrp(borrow_fd(0)).unwrap_or(getpgrp()) }
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ShEnv {
|
||||
vars: shellenv::vars::VarTab,
|
||||
logic: shellenv::logic::LogTab,
|
||||
meta: shellenv::meta::MetaTab,
|
||||
input_man: shellenv::input::InputMan,
|
||||
ctx: shellenv::exec_ctx::ExecCtx
|
||||
}
|
||||
|
||||
impl ShEnv {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vars: shellenv::vars::VarTab::new(),
|
||||
logic: shellenv::logic::LogTab::new(),
|
||||
meta: shellenv::meta::MetaTab::new(),
|
||||
input_man: shellenv::input::InputMan::new(),
|
||||
ctx: shellenv::exec_ctx::ExecCtx::new(),
|
||||
}
|
||||
}
|
||||
pub fn vars(&self) -> &shellenv::vars::VarTab {
|
||||
&self.vars
|
||||
}
|
||||
pub fn vars_mut(&mut self) -> &mut shellenv::vars::VarTab {
|
||||
&mut self.vars
|
||||
}
|
||||
pub fn meta(&self) -> &shellenv::meta::MetaTab {
|
||||
&self.meta
|
||||
}
|
||||
pub fn input_slice(&self, span: Rc<RefCell<Span>>) -> &str {
|
||||
&self.input_man.get_slice(span).unwrap_or_default()
|
||||
}
|
||||
pub fn source_file(&mut self, path: PathBuf) -> ShResult<()> {
|
||||
if path.is_file() {
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
|
||||
exec_input(buf, self)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn source_rc(&mut self) -> ShResult<()> {
|
||||
let path_raw = std::env::var("FERN_RC")?;
|
||||
let path = PathBuf::from(path_raw);
|
||||
self.source_file(path)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn expand_input(&mut self, new: &str, repl_span: Rc<RefCell<Span>>) -> Vec<Token> {
|
||||
if repl_span.borrow().expanded {
|
||||
return vec![];
|
||||
}
|
||||
repl_span.borrow_mut().expanded = true;
|
||||
let saved_spans = self.input_man.spans_mut().clone();
|
||||
let mut new_tokens = Lexer::new(new.to_string(), self).lex();
|
||||
*self.input_man.spans_mut() = saved_spans;
|
||||
|
||||
let offset = repl_span.borrow().start();
|
||||
for token in new_tokens.iter_mut() {
|
||||
token.span().borrow_mut().shift(offset as isize);
|
||||
}
|
||||
|
||||
let repl_start = repl_span.borrow().start();
|
||||
let repl_end = repl_span.borrow().end();
|
||||
let range = repl_start..repl_end;
|
||||
|
||||
if let Some(input) = self.input_man.get_input_mut() {
|
||||
let old = &input[range.clone()];
|
||||
let delta: isize = new.len() as isize - old.len() as isize;
|
||||
input.replace_range(range, new);
|
||||
|
||||
for span in self.input_man.spans_mut() {
|
||||
let mut span_mut = span.borrow_mut();
|
||||
if span_mut.start() > repl_start {
|
||||
span_mut.shift(delta);
|
||||
}
|
||||
}
|
||||
for token in &new_tokens {
|
||||
self.input_man.spans_mut().push(token.span());
|
||||
}
|
||||
}
|
||||
self.input_man.clamp_all();
|
||||
if new_tokens.is_empty() {
|
||||
let empty = Token::new(
|
||||
TkRule::Ident,
|
||||
self.inputman_mut().new_span(repl_start, repl_start)
|
||||
);
|
||||
vec![empty]
|
||||
} else {
|
||||
new_tokens
|
||||
}
|
||||
}
|
||||
/// Executes a group of command lists, and only uses redirections that operate on input
|
||||
/// For instance:
|
||||
/// `if cat; then echo foo; fi < file.txt > otherfile.txt`
|
||||
/// `cat` will be executed as a condition, meaning the input from file.txt will be the only
|
||||
/// redirection used.
|
||||
pub fn exec_as_cond(&mut self, nodes: Vec<Node>) -> ShResult<i32> {
|
||||
let saved = self.ctx().clone();
|
||||
self.ctx = self.ctx().as_cond();
|
||||
let ast = SynTree::from_vec(nodes);
|
||||
Executor::new(ast, self).walk()?;
|
||||
self.ctx = saved;
|
||||
Ok(self.get_code())
|
||||
}
|
||||
/// Executes a group of command lists, and only uses redirections that operate on output
|
||||
/// For instance:
|
||||
/// `if cat; then echo foo; fi < file.txt > otherfile.txt`
|
||||
/// `echo foo` will be executed as a body, meaning the output to otherfile.txt will be the only
|
||||
/// redirection used.
|
||||
pub fn exec_as_body(&mut self, nodes: Vec<Node>) -> ShResult<i32> {
|
||||
let saved = self.ctx().clone();
|
||||
self.ctx = self.ctx().as_body();
|
||||
let ast = SynTree::from_vec(nodes);
|
||||
Executor::new(ast, self).walk()?;
|
||||
self.ctx = saved;
|
||||
Ok(self.get_code())
|
||||
}
|
||||
pub fn new_input(&mut self, input: &str) {
|
||||
self.input_man.clear();
|
||||
self.input_man.new_input(input);
|
||||
}
|
||||
pub fn get_input(&self) -> String {
|
||||
let input = self.input_man.get_input().map(|s| s.to_string()).unwrap_or_default();
|
||||
log!(TRACE, input);
|
||||
input
|
||||
}
|
||||
pub fn inputman(&self) -> &shellenv::input::InputMan {
|
||||
&self.input_man
|
||||
}
|
||||
pub fn inputman_mut(&mut self) -> &mut shellenv::input::InputMan {
|
||||
&mut self.input_man
|
||||
}
|
||||
pub fn meta_mut(&mut self) -> &mut shellenv::meta::MetaTab {
|
||||
&mut self.meta
|
||||
}
|
||||
pub fn logic(&self) -> &shellenv::logic::LogTab {
|
||||
&self.logic
|
||||
}
|
||||
pub fn logic_mut(&mut self) -> &mut shellenv::logic::LogTab {
|
||||
&mut self.logic
|
||||
}
|
||||
pub fn save_io(&mut self) -> ShResult<()> {
|
||||
if self.ctx_mut().saved_io().is_none() {
|
||||
let ctx = self.ctx_mut();
|
||||
let stdin = ctx.masks().stdin().get_fd();
|
||||
let stdout = ctx.masks().stdout().get_fd();
|
||||
let stderr = ctx.masks().stderr().get_fd();
|
||||
|
||||
let saved_in = dup(stdin)?;
|
||||
let saved_out = dup(stdout)?;
|
||||
let saved_err = dup(stderr)?;
|
||||
|
||||
let saved_io = shellenv::exec_ctx::SavedIo::save(saved_in, saved_out, saved_err);
|
||||
*ctx.saved_io() = Some(saved_io);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn reset_io(&mut self) -> ShResult<()> {
|
||||
let ctx = self.ctx_mut();
|
||||
ctx.clear_redirs();
|
||||
if let Some(saved) = ctx.saved_io().take() {
|
||||
let saved_in = saved.stdin;
|
||||
let saved_out = saved.stdout;
|
||||
let saved_err = saved.stderr;
|
||||
dup2(saved_in,STDIN_FILENO)?;
|
||||
close(saved_in).ok();
|
||||
dup2(saved_out,STDOUT_FILENO)?;
|
||||
close(saved_out).ok();
|
||||
dup2(saved_err,STDERR_FILENO)?;
|
||||
close(saved_err).ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn collect_redirs(&mut self, mut redirs: Vec<Redir>) {
|
||||
let ctx = self.ctx_mut();
|
||||
while let Some(redir) = redirs.pop() {
|
||||
ctx.push_rdr(redir);
|
||||
}
|
||||
}
|
||||
pub fn set_code(&mut self, code: i32) {
|
||||
self.vars_mut().set_param("?", &code.to_string());
|
||||
}
|
||||
pub fn get_code(&self) -> i32 {
|
||||
self.vars().get_param("?").parse::<i32>().unwrap_or(0)
|
||||
}
|
||||
pub fn ctx(&self) -> &shellenv::exec_ctx::ExecCtx {
|
||||
&self.ctx
|
||||
}
|
||||
pub fn ctx_mut(&mut self) -> &mut shellenv::exec_ctx::ExecCtx {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
pub struct ShOpts {
|
||||
|
||||
}
|
||||
|
||||
pub struct CoreOpts {
|
||||
|
||||
}
|
||||
|
||||
pub struct PromptOpts {
|
||||
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
use nix::unistd::{gethostname, User};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct VarTab {
|
||||
env: HashMap<String,String>,
|
||||
params: HashMap<String,String>,
|
||||
pos_params: VecDeque<String>,
|
||||
vars: HashMap<String,String>
|
||||
}
|
||||
|
||||
impl VarTab {
|
||||
pub fn new() -> Self {
|
||||
let (params,pos_params) = Self::init_params();
|
||||
Self {
|
||||
env: Self::init_env(),
|
||||
params,
|
||||
pos_params,
|
||||
vars: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn init_params() -> (HashMap<String,String>, VecDeque<String>) {
|
||||
let mut args = std::env::args().collect::<Vec<String>>();
|
||||
let mut params = HashMap::new();
|
||||
let mut pos_params = VecDeque::new();
|
||||
|
||||
params.insert("@".to_string(), args.join(" "));
|
||||
params.insert("#".to_string(), args.len().to_string());
|
||||
|
||||
while let Some(arg) = args.pop() {
|
||||
pos_params.fpush(arg);
|
||||
}
|
||||
|
||||
(params,pos_params)
|
||||
}
|
||||
pub fn init_env() -> HashMap<String,String> {
|
||||
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
|
||||
// First, inherit any env vars from the parent process
|
||||
let mut env_vars = std::env::vars().collect::<HashMap<String,String>>();
|
||||
let term = {
|
||||
if isatty(1).unwrap() {
|
||||
if let Ok(term) = std::env::var("TERM") {
|
||||
term
|
||||
} else {
|
||||
"linux".to_string()
|
||||
}
|
||||
} else {
|
||||
"xterm-256color".to_string()
|
||||
}
|
||||
};
|
||||
let home;
|
||||
let username;
|
||||
let uid;
|
||||
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
|
||||
home = user.dir;
|
||||
username = user.name;
|
||||
uid = user.uid;
|
||||
} else {
|
||||
home = PathBuf::new();
|
||||
username = "unknown".into();
|
||||
uid = 0.into();
|
||||
}
|
||||
let home = pathbuf_to_string(Ok(home));
|
||||
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
|
||||
|
||||
env_vars.insert("IFS".into(), " \t\n".into());
|
||||
env::set_var("IFS", " \t\n");
|
||||
env_vars.insert("HOSTNAME".into(), hostname.clone());
|
||||
env::set_var("HOSTNAME", hostname);
|
||||
env_vars.insert("UID".into(), uid.to_string());
|
||||
env::set_var("UID", uid.to_string());
|
||||
env_vars.insert("PPID".into(), getppid().to_string());
|
||||
env::set_var("PPID", getppid().to_string());
|
||||
env_vars.insert("TMPDIR".into(), "/tmp".into());
|
||||
env::set_var("TMPDIR", "/tmp");
|
||||
env_vars.insert("TERM".into(), term.clone());
|
||||
env::set_var("TERM", term);
|
||||
env_vars.insert("LANG".into(), "en_US.UTF-8".into());
|
||||
env::set_var("LANG", "en_US.UTF-8");
|
||||
env_vars.insert("USER".into(), username.clone());
|
||||
env::set_var("USER", username.clone());
|
||||
env_vars.insert("LOGNAME".into(), username.clone());
|
||||
env::set_var("LOGNAME", username);
|
||||
env_vars.insert("PWD".into(), pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env_vars.insert("OLDPWD".into(), pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env_vars.insert("HOME".into(), home.clone());
|
||||
env::set_var("HOME", home.clone());
|
||||
env_vars.insert("SHELL".into(), pathbuf_to_string(std::env::current_exe()));
|
||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||
env_vars.insert("FERN_HIST".into(),format!("{}/.fern_hist",home));
|
||||
env::set_var("FERN_HIST",format!("{}/.fern_hist",home));
|
||||
env_vars.insert("FERN_RC".into(),format!("{}/.fernrc",home));
|
||||
env::set_var("FERN_RC",format!("{}/.fernrc",home));
|
||||
|
||||
env_vars
|
||||
}
|
||||
pub fn env(&self) -> &HashMap<String,String> {
|
||||
&self.env
|
||||
}
|
||||
pub fn env_mut(&mut self) -> &mut HashMap<String,String> {
|
||||
&mut self.env
|
||||
}
|
||||
pub fn reset_params(&mut self) {
|
||||
self.params.clear();
|
||||
}
|
||||
pub fn unset_param(&mut self, key: &str) {
|
||||
self.params.remove(key);
|
||||
}
|
||||
pub fn set_param(&mut self, key: &str, val: &str) {
|
||||
self.params.insert(key.to_string(), val.to_string());
|
||||
}
|
||||
pub fn get_param(&self, key: &str) -> &str {
|
||||
self.params.get(key).map(|s| s.as_str()).unwrap_or_default()
|
||||
}
|
||||
/// Push an arg to the back of the positional parameter deque
|
||||
pub fn bpush_arg(&mut self, arg: &str) {
|
||||
self.pos_params.bpush(arg.to_string());
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
}
|
||||
/// Pop an arg from the back of the positional parameter deque
|
||||
pub fn bpop_arg(&mut self) -> Option<String> {
|
||||
let item = self.pos_params.bpop();
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
item
|
||||
}
|
||||
/// Push an arg to the front of the positional parameter deque
|
||||
pub fn fpush_arg(&mut self, arg: &str) {
|
||||
self.pos_params.fpush(arg.to_string());
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
}
|
||||
/// Pop an arg from the front of the positional parameter deque
|
||||
pub fn fpop_arg(&mut self) -> Option<String> {
|
||||
let item = self.pos_params.fpop();
|
||||
self.set_param("@", &self.pos_params.clone().to_vec().join(" "));
|
||||
self.set_param("#", &self.pos_params.len().to_string());
|
||||
item
|
||||
}
|
||||
pub fn get_var(&self, var: &str) -> &str {
|
||||
if let Ok(idx) = var.parse::<usize>() {
|
||||
self.pos_params.get(idx).map(|p| p.as_str()).unwrap_or_default()
|
||||
} else if let Some(var) = self.env.get(var) {
|
||||
var.as_str()
|
||||
} else if let Some(param) = self.params.get(var) {
|
||||
param.as_str()
|
||||
} else {
|
||||
self.vars.get(var).map(|v| v.as_str()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
pub fn set_var(&mut self, var: &str, val: &str) {
|
||||
self.vars.insert(var.to_string(), val.to_string());
|
||||
}
|
||||
pub fn unset_var(&mut self, var: &str) {
|
||||
self.vars.remove(var);
|
||||
}
|
||||
pub fn export(&mut self, var: &str, val: &str) {
|
||||
self.env.insert(var.to_string(),val.to_string());
|
||||
std::env::set_var(var, val);
|
||||
}
|
||||
}
|
||||
159
src/signal.rs
159
src/signal.rs
@@ -1,159 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn sig_setup() {
|
||||
unsafe {
|
||||
signal(Signal::SIGCHLD, SigHandler::Handler(handle_sigchld)).unwrap();
|
||||
signal(Signal::SIGQUIT, SigHandler::Handler(handle_sigquit)).unwrap();
|
||||
signal(Signal::SIGTSTP, SigHandler::Handler(handle_sigtstp)).unwrap();
|
||||
signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup)).unwrap();
|
||||
signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)).unwrap();
|
||||
signal(Signal::SIGTTIN, SigHandler::SigIgn).unwrap();
|
||||
signal(Signal::SIGTTOU, SigHandler::SigIgn).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern "C" fn handle_sighup(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGTSTP).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigint(_: libc::c_int) {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGINT).ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub extern "C" fn ignore_sigchld(_: libc::c_int) {
|
||||
/*
|
||||
Do nothing
|
||||
|
||||
This function exists because using SIGIGN to ignore SIGCHLD
|
||||
will cause the kernel to automatically reap the child process, which is not what we want.
|
||||
This handler will leave the signaling process as a zombie, allowing us
|
||||
to handle it somewhere else.
|
||||
|
||||
This handler is used when we want to handle SIGCHLD explicitly,
|
||||
like in the case of handling foreground jobs
|
||||
*/
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigquit(_: libc::c_int) {
|
||||
sh_quit(0)
|
||||
}
|
||||
|
||||
pub extern "C" fn handle_sigchld(_: libc::c_int) {
|
||||
let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED;
|
||||
while let Ok(status) = waitpid(None, Some(flags)) {
|
||||
if let Err(e) = match status {
|
||||
WtStat::Exited(pid, _) => child_exited(pid, status),
|
||||
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal),
|
||||
WtStat::Stopped(pid, signal) => child_stopped(pid, signal),
|
||||
WtStat::Continued(pid) => child_continued(pid),
|
||||
WtStat::StillAlive => break,
|
||||
_ => unimplemented!()
|
||||
} {
|
||||
eprintln!("{}",e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
||||
let stat = WtStat::Signaled(pid, sig, false);
|
||||
child.set_stat(stat);
|
||||
}
|
||||
});
|
||||
if sig == Signal::SIGINT {
|
||||
take_term().unwrap()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_stopped(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
let child = job.children_mut().iter_mut().find(|chld| pid == chld.pid()).unwrap();
|
||||
let status = WtStat::Stopped(pid, sig);
|
||||
child.set_stat(status);
|
||||
} else if j.get_fg_mut().is_some_and(|fg| fg.pgid() == pgid) {
|
||||
j.fg_to_bg(WtStat::Stopped(pid, sig)).unwrap();
|
||||
}
|
||||
});
|
||||
take_term()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_continued(pid: Pid) -> ShResult<()> {
|
||||
let pgid = getpgid(Some(pid)).unwrap_or(pid);
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.query_mut(JobID::Pgid(pgid)) {
|
||||
job.killpg(Signal::SIGCONT).ok();
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
||||
/*
|
||||
* Here we are going to get metadata on the exited process by querying the job table with the pid.
|
||||
* Then if the discovered job is the fg task, return terminal control to rsh
|
||||
* If it is not the fg task, print the display info for the job in the job table
|
||||
* We can reasonably assume that if it is not a foreground job, then it exists in the job table
|
||||
* If this assumption is incorrect, the code has gone wrong somewhere.
|
||||
*/
|
||||
if let Some((
|
||||
pgid,
|
||||
is_fg,
|
||||
is_finished
|
||||
)) = write_jobs(|j| {
|
||||
let fg_pgid = j.get_fg().map(|job| job.pgid());
|
||||
if let Some(job) = j.query_mut(JobID::Pid(pid)) {
|
||||
let pgid = job.pgid();
|
||||
let is_fg = fg_pgid.is_some_and(|fg| fg == pgid);
|
||||
job.update_by_id(JobID::Pid(pid), status).unwrap();
|
||||
let is_finished = !job.running();
|
||||
|
||||
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
|
||||
child.set_stat(status);
|
||||
}
|
||||
|
||||
Some((pgid, is_fg, is_finished))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
|
||||
if is_finished {
|
||||
if is_fg {
|
||||
take_term()?;
|
||||
} else {
|
||||
println!();
|
||||
let job_order = read_jobs(|j| j.order().to_vec());
|
||||
let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned());
|
||||
if let Some(job) = result {
|
||||
println!("{}",job.display(&job_order,shellenv::jobs::JobCmdFlags::PIDS))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
118
src/state.rs
Normal file
118
src/state.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use std::{collections::HashMap, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
||||
|
||||
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
||||
|
||||
pub static ENV_TABLE: LazyLock<RwLock<EnvTab>> = LazyLock::new(|| RwLock::new(EnvTab::new()));
|
||||
|
||||
pub struct JobTab {
|
||||
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VarTab {
|
||||
vars: HashMap<String,String>,
|
||||
params: HashMap<char,String>,
|
||||
}
|
||||
|
||||
impl VarTab {
|
||||
pub fn new() -> Self {
|
||||
let vars = HashMap::new();
|
||||
let params = Self::init_params();
|
||||
Self { vars, params }
|
||||
}
|
||||
fn init_params() -> HashMap<char, String> {
|
||||
let mut params = HashMap::new();
|
||||
params.insert('?', "0".into()); // Last command exit status
|
||||
params.insert('#', "0".into()); // Number of positional parameters
|
||||
params.insert('0', std::env::current_exe().unwrap().to_str().unwrap().to_string()); // Name of the shell
|
||||
params.insert('$', Pid::this().to_string()); // PID of the shell
|
||||
params.insert('!', "".into()); // PID of the last background job (if any)
|
||||
params
|
||||
}
|
||||
pub fn vars(&self) -> &HashMap<String,String> {
|
||||
&self.vars
|
||||
}
|
||||
pub fn vars_mut(&mut self) -> &mut HashMap<String,String> {
|
||||
&mut self.vars
|
||||
}
|
||||
pub fn params(&self) -> &HashMap<char,String> {
|
||||
&self.params
|
||||
}
|
||||
pub fn params_mut(&mut self) -> &mut HashMap<char,String> {
|
||||
&mut self.params
|
||||
}
|
||||
pub fn get_var(&self, var: &str) -> String {
|
||||
self.vars.get(var).map(|s| s.to_string()).unwrap_or_default()
|
||||
}
|
||||
pub fn new_var(&mut self, var: &str, val: &str) {
|
||||
self.vars.insert(var.to_string(), val.to_string());
|
||||
}
|
||||
pub fn set_param(&mut self, param: char, val: &str) {
|
||||
self.params.insert(param,val.to_string());
|
||||
}
|
||||
pub fn get_param(&self, param: char) -> String {
|
||||
self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnvTab {
|
||||
|
||||
}
|
||||
|
||||
impl EnvTab {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from the job table
|
||||
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
||||
let lock = JOB_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
/// Write to the job table
|
||||
pub fn write_jobs<T, F: FnOnce(&mut RwLockWriteGuard<JobTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut JOB_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
/// Read from the variable table
|
||||
pub fn read_vars<T, F: FnOnce(RwLockReadGuard<VarTab>) -> T>(f: F) -> T {
|
||||
let lock = VAR_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
/// Write to the variable table
|
||||
pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut VAR_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
/// Read from the environment table
|
||||
pub fn read_env<T, F: FnOnce(RwLockReadGuard<EnvTab>) -> T>(f: F) -> T {
|
||||
let lock = ENV_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
/// Write to the environment table
|
||||
pub fn write_env<T, F: FnOnce(&mut RwLockWriteGuard<EnvTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut ENV_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
}
|
||||
|
||||
pub fn get_status() -> i32 {
|
||||
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
||||
}
|
||||
pub fn set_status(code: i32) {
|
||||
write_vars(|v| v.set_param('?', &code.to_string()))
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
pub mod lex_test {
|
||||
|
||||
}
|
||||
|
||||
pub mod parse_test {
|
||||
|
||||
}
|
||||
19
src/tests/expand.rs
Normal file
19
src/tests/expand.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use parse::lex::{Tk, TkFlags, TkRule};
|
||||
use state::write_vars;
|
||||
use super::super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_expansion() {
|
||||
let varsub = "$foo";
|
||||
write_vars(|v| v.new_var("foo", "this is the value of the variable".into()));
|
||||
|
||||
let mut tokens: Vec<Tk> = LexStream::new(varsub, LexFlags::empty())
|
||||
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
||||
.collect();
|
||||
let var_tk = tokens.pop().unwrap();
|
||||
|
||||
let var_span = var_tk.span.clone();
|
||||
let exp_tk = var_tk.expand(var_span, TkFlags::empty());
|
||||
write_vars(|v| v.vars_mut().clear());
|
||||
insta::assert_debug_snapshot!(exp_tk.get_words())
|
||||
}
|
||||
36
src/tests/lexer.rs
Normal file
36
src/tests/lexer.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use super::super::*;
|
||||
#[test]
|
||||
fn lex_simple() {
|
||||
let input = "echo hello world";
|
||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(tokens)
|
||||
}
|
||||
#[test]
|
||||
fn lex_redir() {
|
||||
let input = "echo foo > bar.txt";
|
||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(tokens)
|
||||
}
|
||||
#[test]
|
||||
fn lex_redir_fds() {
|
||||
let input = "echo foo 1>&2";
|
||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(tokens)
|
||||
}
|
||||
#[test]
|
||||
fn lex_quote_str() {
|
||||
let input = "echo \"foo bar\" biz baz";
|
||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(tokens)
|
||||
}
|
||||
#[test]
|
||||
fn lex_with_keywords() {
|
||||
let input = "if true; then echo foo; fi";
|
||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(tokens)
|
||||
}
|
||||
4
src/tests/mod.rs
Normal file
4
src/tests/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod expand;
|
||||
pub mod term;
|
||||
37
src/tests/parser.rs
Normal file
37
src/tests/parser.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use super::super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_simple() {
|
||||
let input = "echo hello world";
|
||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(nodes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pipeline() {
|
||||
let input = "echo foo | sed s/foo/bar";
|
||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(nodes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_conjunction() {
|
||||
let input = "echo foo && echo bar";
|
||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(nodes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_conjunction_and_pipeline() {
|
||||
let input = "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/";
|
||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(nodes)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/tests/expand.rs
|
||||
expression: exp_tk.get_words()
|
||||
---
|
||||
[
|
||||
"this",
|
||||
"is",
|
||||
"the",
|
||||
"value",
|
||||
"of",
|
||||
"the",
|
||||
"variable",
|
||||
]
|
||||
78
src/tests/snapshots/fern__tests__lexer__lex_quote_str.snap
Normal file
78
src/tests/snapshots/fern__tests__lexer__lex_quote_str.snap
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
source: src/tests/lexer.rs
|
||||
expression: tokens
|
||||
---
|
||||
[
|
||||
Tk {
|
||||
class: SOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..0,
|
||||
source: "echo \"foo bar\" biz baz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo \"foo bar\" biz baz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..14,
|
||||
source: "echo \"foo bar\" biz baz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..18,
|
||||
source: "echo \"foo bar\" biz baz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 19..22,
|
||||
source: "echo \"foo bar\" biz baz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: EOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 22..22,
|
||||
source: "echo \"foo bar\" biz baz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
]
|
||||
78
src/tests/snapshots/fern__tests__lexer__lex_redir.snap
Normal file
78
src/tests/snapshots/fern__tests__lexer__lex_redir.snap
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
source: src/tests/lexer.rs
|
||||
expression: tokens
|
||||
---
|
||||
[
|
||||
Tk {
|
||||
class: SOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..0,
|
||||
source: "echo foo > bar.txt",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo > bar.txt",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo > bar.txt",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Redir,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..10,
|
||||
source: "echo foo > bar.txt",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..18,
|
||||
source: "echo foo > bar.txt",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: EOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 18..18,
|
||||
source: "echo foo > bar.txt",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
]
|
||||
66
src/tests/snapshots/fern__tests__lexer__lex_redir_fds.snap
Normal file
66
src/tests/snapshots/fern__tests__lexer__lex_redir_fds.snap
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
source: src/tests/lexer.rs
|
||||
expression: tokens
|
||||
---
|
||||
[
|
||||
Tk {
|
||||
class: SOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..0,
|
||||
source: "echo foo 1>&2",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo 1>&2",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo 1>&2",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Redir,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..13,
|
||||
source: "echo foo 1>&2",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: EOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 13..13,
|
||||
source: "echo foo 1>&2",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
]
|
||||
66
src/tests/snapshots/fern__tests__lexer__lex_simple.snap
Normal file
66
src/tests/snapshots/fern__tests__lexer__lex_simple.snap
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
source: src/tests/lexer.rs
|
||||
expression: tokens
|
||||
---
|
||||
[
|
||||
Tk {
|
||||
class: SOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..0,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..10,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..16,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: EOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 16..16,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
]
|
||||
126
src/tests/snapshots/fern__tests__lexer__lex_with_keywords.snap
Normal file
126
src/tests/snapshots/fern__tests__lexer__lex_with_keywords.snap
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
source: src/tests/lexer.rs
|
||||
expression: tokens
|
||||
---
|
||||
[
|
||||
Tk {
|
||||
class: SOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..0,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..2,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
KEYWORD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 3..7,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 7..9,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..13,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
KEYWORD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 14..18,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 19..22,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 22..24,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 24..26,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
KEYWORD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: EOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 26..26,
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
]
|
||||
282
src/tests/snapshots/fern__tests__parser__parse_conjunction.snap
Normal file
282
src/tests/snapshots/fern__tests__parser__parse_conjunction.snap
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: And,
|
||||
},
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..16,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..20,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..16,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..20,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..16,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..20,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Null,
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: And,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..11,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..16,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..20,
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,962 @@
|
||||
---
|
||||
source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..10,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: And,
|
||||
},
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 37..38,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Or,
|
||||
},
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 69..70,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Null,
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..10,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: And,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 25..27,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 28..32,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 33..36,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 37..38,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 43..52,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Or,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 53..55,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 56..60,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 61..64,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 65..68,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 69..70,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 71..74,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 75..80,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 81..88,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 89..93,
|
||||
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
278
src/tests/snapshots/fern__tests__parser__parse_pipeline.snap
Normal file
278
src/tests/snapshots/fern__tests__parser__parse_pipeline.snap
Normal file
@@ -0,0 +1,278 @@
|
||||
---
|
||||
source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..10,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Null,
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..8,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Pipe,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 9..10,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..14,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 15..24,
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
193
src/tests/snapshots/fern__tests__parser__parse_simple.snap
Normal file
193
src/tests/snapshots/fern__tests__parser__parse_simple.snap
Normal file
@@ -0,0 +1,193 @@
|
||||
---
|
||||
source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Match(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..10,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..16,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..10,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..16,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..10,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..16,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Null,
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..10,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..16,
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/tests/term.rs
|
||||
expression: styled
|
||||
---
|
||||
[44m[1mtext with background[0m
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/tests/term.rs
|
||||
expression: styled
|
||||
---
|
||||
[31m[1m[4mstyled text[0m
|
||||
5
src/tests/snapshots/fern__tests__term__styled_reset.snap
Normal file
5
src/tests/snapshots/fern__tests__term__styled_reset.snap
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/tests/term.rs
|
||||
expression: styled
|
||||
---
|
||||
[1m[0mreset test[0m
|
||||
5
src/tests/snapshots/fern__tests__term__styled_rgb.snap
Normal file
5
src/tests/snapshots/fern__tests__term__styled_rgb.snap
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/tests/term.rs
|
||||
expression: styled
|
||||
---
|
||||
[38;2;255;99;71mRGB styled text[0m
|
||||
5
src/tests/snapshots/fern__tests__term__styled_set.snap
Normal file
5
src/tests/snapshots/fern__tests__term__styled_set.snap
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/tests/term.rs
|
||||
expression: styled
|
||||
---
|
||||
[35m[3mmulti-style text[0m
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: src/tests/term.rs
|
||||
expression: styled
|
||||
---
|
||||
[32mhello world[0m
|
||||
41
src/tests/term.rs
Normal file
41
src/tests/term.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use libsh::term::{Style, StyleSet, Styled};
|
||||
|
||||
use super::super::*;
|
||||
#[test]
|
||||
fn styled_simple() {
|
||||
let input = "hello world";
|
||||
let styled = input.styled(Style::Green);
|
||||
|
||||
insta::assert_snapshot!(styled)
|
||||
}
|
||||
#[test]
|
||||
fn styled_multiple() {
|
||||
let input = "styled text";
|
||||
let styled = input.styled(Style::Red | Style::Bold | Style::Underline);
|
||||
insta::assert_snapshot!(styled);
|
||||
}
|
||||
#[test]
|
||||
fn styled_rgb() {
|
||||
let input = "RGB styled text";
|
||||
let styled = input.styled(Style::RGB(255, 99, 71)); // Tomato color
|
||||
insta::assert_snapshot!(styled);
|
||||
}
|
||||
#[test]
|
||||
fn styled_background() {
|
||||
let input = "text with background";
|
||||
let styled = input.styled(Style::BgBlue | Style::Bold);
|
||||
insta::assert_snapshot!(styled);
|
||||
}
|
||||
#[test]
|
||||
fn styled_set() {
|
||||
let input = "multi-style text";
|
||||
let style_set = StyleSet::new().add(Style::Magenta).add(Style::Italic);
|
||||
let styled = input.styled(style_set);
|
||||
insta::assert_snapshot!(styled);
|
||||
}
|
||||
#[test]
|
||||
fn styled_reset() {
|
||||
let input = "reset test";
|
||||
let styled = input.styled(Style::Bold | Style::Reset);
|
||||
insta::assert_snapshot!(styled);
|
||||
}
|
||||
Reference in New Issue
Block a user