Implemented heredocs and herestrings
This commit is contained in:
@@ -75,6 +75,7 @@ pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shenv.collect_redirs(redirs);
|
shenv.collect_redirs(redirs);
|
||||||
|
log!(DEBUG,"{:?}",shenv.ctx().redirs());
|
||||||
shenv.ctx_mut().activate_rdrs()?;
|
shenv.ctx_mut().activate_rdrs()?;
|
||||||
write_out(formatted)?;
|
write_out(formatted)?;
|
||||||
|
|
||||||
|
|||||||
@@ -8,43 +8,10 @@ pub fn pwd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
let mut pwd = shenv.vars().get_var("PWD").to_string();
|
let mut pwd = shenv.vars().get_var("PWD").to_string();
|
||||||
pwd.push('\n');
|
pwd.push('\n');
|
||||||
|
|
||||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
|
||||||
shenv.collect_redirs(redirs);
|
shenv.collect_redirs(redirs);
|
||||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
shenv.ctx_mut().activate_rdrs()?;
|
||||||
eprintln!("{:?}",e);
|
write_out(pwd)?;
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if let Err(e) = write_out(pwd) {
|
|
||||||
eprintln!("{:?}",e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
} else {
|
|
||||||
match unsafe { fork()? } {
|
|
||||||
Child => {
|
|
||||||
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
|
|
||||||
eprintln!("{:?}",e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
if let Err(e) = write_out(pwd) {
|
|
||||||
eprintln!("{:?}",e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
Parent { child } => {
|
|
||||||
shenv.reset_io()?;
|
|
||||||
let children = vec![
|
|
||||||
ChildProc::new(child, Some("echo"), Some(child))?
|
|
||||||
];
|
|
||||||
let job = JobBldr::new()
|
|
||||||
.with_children(children)
|
|
||||||
.with_pgid(child)
|
|
||||||
.build();
|
|
||||||
wait_fg(job, shenv)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { unreachable!() }
|
} else { unreachable!() }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,24 @@ pub mod shellcmd;
|
|||||||
pub fn exec_input<S: Into<String>>(input: S, shenv: &mut ShEnv) -> ShResult<()> {
|
pub fn exec_input<S: Into<String>>(input: S, shenv: &mut ShEnv) -> ShResult<()> {
|
||||||
let input = input.into();
|
let input = input.into();
|
||||||
shenv.new_input(&input);
|
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 = Lexer::new(input,shenv).lex();
|
||||||
log!(INFO, token_stream);
|
|
||||||
|
|
||||||
let token_stream = expand_aliases(token_stream, shenv);
|
let token_stream = expand_aliases(token_stream, shenv);
|
||||||
for token in &token_stream {
|
for token in &token_stream {
|
||||||
log!(TRACE, token);
|
log!(TRACE, token);
|
||||||
log!(TRACE, "{}",token.as_raw(shenv));
|
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()?;
|
let syn_tree = Parser::new(token_stream,shenv).parse()?;
|
||||||
let exec_start = std::time::Instant::now();
|
log!(INFO, "Parsing done in {:?}", parse_time.elapsed());
|
||||||
shenv.save_io()?;
|
shenv.save_io()?;
|
||||||
|
|
||||||
|
let exec_time = std::time::Instant::now();
|
||||||
if let Err(e) = Executor::new(syn_tree, shenv).walk() {
|
if let Err(e) = Executor::new(syn_tree, shenv).walk() {
|
||||||
if let ShErrKind::CleanExit = e.kind() {
|
if let ShErrKind::CleanExit = e.kind() {
|
||||||
let code = shenv.get_code();
|
let code = shenv.get_code();
|
||||||
@@ -31,7 +36,8 @@ pub fn exec_input<S: Into<String>>(input: S, shenv: &mut ShEnv) -> ShResult<()>
|
|||||||
return Err(e.into())
|
return Err(e.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log!(INFO, "Executing done in {:?}", exec_start.elapsed());
|
log!(INFO, "Executing done in {:?}", exec_time.elapsed());
|
||||||
|
log!(INFO, "Total time spent: {:?}", total_time.elapsed());
|
||||||
shenv.reset_io()?;
|
shenv.reset_io()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -187,6 +193,7 @@ fn exec_funcdef(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||||
let snapshot = shenv.clone();
|
let snapshot = shenv.clone();
|
||||||
shenv.vars_mut().reset_params();
|
shenv.vars_mut().reset_params();
|
||||||
|
let is_bg = node.flags().contains(NdFlag::BACKGROUND);
|
||||||
let rule = node.into_rule();
|
let rule = node.into_rule();
|
||||||
if let NdRule::Subshell { body, argv, redirs } = rule {
|
if let NdRule::Subshell { body, argv, redirs } = rule {
|
||||||
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
|
||||||
@@ -239,7 +246,7 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
.with_children(children)
|
.with_children(children)
|
||||||
.with_pgid(child)
|
.with_pgid(child)
|
||||||
.build();
|
.build();
|
||||||
wait_fg(job, shenv)?;
|
dispatch_job(job, is_bg, shenv)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,9 +306,9 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
_ => unimplemented!()
|
_ => unimplemented!()
|
||||||
};
|
};
|
||||||
shenv.vars_mut().set_var(var, &exp);
|
shenv.vars_mut().export(var, &exp);
|
||||||
} else {
|
} else {
|
||||||
shenv.vars_mut().set_var(var, val);
|
shenv.vars_mut().export(var, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,6 +343,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
|
|
||||||
fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||||
log!(TRACE, "Executing pipeline");
|
log!(TRACE, "Executing pipeline");
|
||||||
|
let is_bg = node.flags().contains(NdFlag::BACKGROUND);
|
||||||
let rule = node.into_rule();
|
let rule = node.into_rule();
|
||||||
if let NdRule::Pipeline { cmds } = rule {
|
if let NdRule::Pipeline { cmds } = rule {
|
||||||
let mut prev_rpipe: Option<i32> = None;
|
let mut prev_rpipe: Option<i32> = None;
|
||||||
@@ -415,7 +423,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
.with_children(children)
|
.with_children(children)
|
||||||
.with_pgid(pgid.unwrap())
|
.with_pgid(pgid.unwrap())
|
||||||
.build();
|
.build();
|
||||||
wait_fg(job, shenv)?;
|
dispatch_job(job, is_bg, shenv)?;
|
||||||
} else { unreachable!() }
|
} else { unreachable!() }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -423,6 +431,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||||
log!(TRACE, "Executing command");
|
log!(TRACE, "Executing command");
|
||||||
let blame = node.span();
|
let blame = node.span();
|
||||||
|
let is_bg = node.flags().contains(NdFlag::BACKGROUND);
|
||||||
let rule = node.into_rule();
|
let rule = node.into_rule();
|
||||||
|
|
||||||
if let NdRule::Command { argv, redirs } = rule {
|
if let NdRule::Command { argv, redirs } = rule {
|
||||||
@@ -466,7 +475,7 @@ fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
.with_pgid(child)
|
.with_pgid(child)
|
||||||
.build();
|
.build();
|
||||||
log!(TRACE, "New job: {:?}", job);
|
log!(TRACE, "New job: {:?}", job);
|
||||||
wait_fg(job, shenv)?;
|
dispatch_job(job, is_bg, shenv)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub enum Op {
|
|||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
|
IntDiv,
|
||||||
Mod,
|
Mod,
|
||||||
Pow
|
Pow
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,7 @@ impl Op {
|
|||||||
pub fn precedence(&self) -> u8 {
|
pub fn precedence(&self) -> u8 {
|
||||||
match self {
|
match self {
|
||||||
Op::Add | Op::Sub => 1,
|
Op::Add | Op::Sub => 1,
|
||||||
Op::Mul | Op::Div | Op::Mod => 2,
|
Op::Mul | Op::Div | Op::IntDiv | Op::Mod => 2,
|
||||||
Op::Pow => 3
|
Op::Pow => 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +50,14 @@ fn tokenize_expr(expr: &str) -> ShResult<Vec<ExprToken>> {
|
|||||||
tokens.push(ExprToken::Operator(Op::Mul));
|
tokens.push(ExprToken::Operator(Op::Mul));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'/' => tokens.push(ExprToken::Operator(Op::Div)),
|
'/' => {
|
||||||
|
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::Operator(Op::Mod)),
|
||||||
'(' => tokens.push(ExprToken::OpenParen),
|
'(' => tokens.push(ExprToken::OpenParen),
|
||||||
')' => tokens.push(ExprToken::CloseParen),
|
')' => tokens.push(ExprToken::CloseParen),
|
||||||
@@ -142,6 +150,12 @@ pub fn eval_rpn(tokens: Vec<ExprToken>) -> ShResult<f64> {
|
|||||||
}
|
}
|
||||||
lhs / rhs
|
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);
|
stack.push(result);
|
||||||
}
|
}
|
||||||
@@ -154,10 +168,8 @@ pub fn eval_rpn(tokens: Vec<ExprToken>) -> ShResult<f64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_arith_token(token: Token, shenv: &mut ShEnv) -> ShResult<Token> {
|
pub fn expand_arith_token(token: Token, shenv: &mut ShEnv) -> ShResult<Token> {
|
||||||
log!(INFO, "{}", token.as_raw(shenv));
|
|
||||||
// I mean hey it works
|
// I mean hey it works
|
||||||
let token_raw = token.as_raw(shenv);
|
let token_raw = token.as_raw(shenv);
|
||||||
log!(INFO, token_raw);
|
|
||||||
|
|
||||||
let arith_raw = token_raw.trim_matches('`');
|
let arith_raw = token_raw.trim_matches('`');
|
||||||
|
|
||||||
@@ -173,7 +185,6 @@ pub fn expand_arith_string(s: &str,shenv: &mut ShEnv) -> ShResult<String> {
|
|||||||
if exp.starts_with('`') && s.ends_with('`') {
|
if exp.starts_with('`') && s.ends_with('`') {
|
||||||
exp = exp[1..exp.len() - 1].to_string();
|
exp = exp[1..exp.len() - 1].to_string();
|
||||||
}
|
}
|
||||||
log!(INFO,exp);
|
|
||||||
let expr_tokens = shunting_yard(tokenize_expr(&exp)?)?;
|
let expr_tokens = shunting_yard(tokenize_expr(&exp)?)?;
|
||||||
log!(DEBUG,expr_tokens);
|
log!(DEBUG,expr_tokens);
|
||||||
let result = eval_rpn(expr_tokens)?.to_string();
|
let result = eval_rpn(expr_tokens)?.to_string();
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ use crate::prelude::*;
|
|||||||
pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
||||||
let mut processed = vec![];
|
let mut processed = vec![];
|
||||||
for arg in argv {
|
for arg in argv {
|
||||||
log!(DEBUG, "{}",arg.as_raw(shenv));
|
log!(TRACE, "{}",arg.as_raw(shenv));
|
||||||
log!(DEBUG, processed);
|
log!(TRACE, processed);
|
||||||
let mut expanded = expand_token(arg, shenv)?;
|
let mut expanded = expand_token(arg, shenv)?;
|
||||||
processed.append(&mut expanded);
|
processed.append(&mut expanded);
|
||||||
}
|
}
|
||||||
@@ -24,8 +24,6 @@ pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> ShResult<Vec<Token>>
|
|||||||
|
|
||||||
pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
||||||
let mut processed = vec![];
|
let mut processed = vec![];
|
||||||
log!(INFO, "expanding argv");
|
|
||||||
log!(INFO, "rule: {:?}", token.rule());
|
|
||||||
match token.rule() {
|
match token.rule() {
|
||||||
TkRule::DQuote => {
|
TkRule::DQuote => {
|
||||||
let dquote_exp = expand_string(&token.as_raw(shenv), shenv)?;
|
let dquote_exp = expand_string(&token.as_raw(shenv), shenv)?;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult<String> {
|
pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult<String> {
|
||||||
|
log!(DEBUG, s);
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
let mut var_name = String::new();
|
let mut var_name = String::new();
|
||||||
let mut chars = s.chars().peekable();
|
let mut chars = s.chars().peekable();
|
||||||
@@ -78,7 +79,7 @@ pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult<String> {
|
|||||||
expanded = true;
|
expanded = true;
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
' ' | '\t' => {
|
' ' | '\t' | '\n' | ';' => {
|
||||||
let value = shenv.vars().get_var(&var_name);
|
let value = shenv.vars().get_var(&var_name);
|
||||||
result.push_str(value);
|
result.push_str(value);
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
@@ -89,7 +90,6 @@ pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !expanded {
|
if !expanded {
|
||||||
log!(INFO, var_name);
|
|
||||||
let value = shenv.vars().get_var(&var_name);
|
let value = shenv.vars().get_var(&var_name);
|
||||||
result.push_str(value);
|
result.push_str(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ pub fn borrow_fd<'a>(fd: i32) -> BorrowedFd<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add more of these
|
// TODO: add more of these
|
||||||
#[derive(Debug,Clone,Copy)]
|
#[derive(Debug,Clone,PartialEq,Copy)]
|
||||||
pub enum RedirType {
|
pub enum RedirType {
|
||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
@@ -163,8 +163,11 @@ pub enum RedirType {
|
|||||||
pub enum RedirTarget {
|
pub enum RedirTarget {
|
||||||
Fd(i32),
|
Fd(i32),
|
||||||
File(PathBuf),
|
File(PathBuf),
|
||||||
|
HereDoc(String),
|
||||||
|
HereString(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
pub struct RedirBldr {
|
pub struct RedirBldr {
|
||||||
src: Option<i32>,
|
src: Option<i32>,
|
||||||
op: Option<RedirType>,
|
op: Option<RedirType>,
|
||||||
@@ -222,8 +225,12 @@ impl FromStr for RedirBldr {
|
|||||||
if chars.peek() == Some(&'<') {
|
if chars.peek() == Some(&'<') {
|
||||||
chars.next();
|
chars.next();
|
||||||
redir_bldr = redir_bldr.with_op(RedirType::HereString);
|
redir_bldr = redir_bldr.with_op(RedirType::HereString);
|
||||||
|
break
|
||||||
} else {
|
} else {
|
||||||
redir_bldr = redir_bldr.with_op(RedirType::HereDoc);
|
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 {
|
} else {
|
||||||
redir_bldr = redir_bldr.with_op(RedirType::Input);
|
redir_bldr = redir_bldr.with_op(RedirType::Input);
|
||||||
@@ -233,8 +240,10 @@ impl FromStr for RedirBldr {
|
|||||||
if chars.peek() == Some(&'>') {
|
if chars.peek() == Some(&'>') {
|
||||||
chars.next();
|
chars.next();
|
||||||
redir_bldr = redir_bldr.with_op(RedirType::Append);
|
redir_bldr = redir_bldr.with_op(RedirType::Append);
|
||||||
|
break
|
||||||
} else {
|
} else {
|
||||||
redir_bldr = redir_bldr.with_op(RedirType::Output);
|
redir_bldr = redir_bldr.with_op(RedirType::Output);
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'&' => {
|
'&' => {
|
||||||
@@ -275,21 +284,24 @@ impl Redir {
|
|||||||
pub struct CmdRedirs {
|
pub struct CmdRedirs {
|
||||||
open: Vec<RawFd>,
|
open: Vec<RawFd>,
|
||||||
targets_fd: Vec<Redir>,
|
targets_fd: Vec<Redir>,
|
||||||
targets_file: Vec<Redir>
|
targets_file: Vec<Redir>,
|
||||||
|
targets_text: Vec<Redir>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdRedirs {
|
impl CmdRedirs {
|
||||||
pub fn new(mut redirs: Vec<Redir>) -> Self {
|
pub fn new(mut redirs: Vec<Redir>) -> Self {
|
||||||
let mut targets_fd = vec![];
|
let mut targets_fd = vec![];
|
||||||
let mut targets_file = vec![];
|
let mut targets_file = vec![];
|
||||||
|
let mut targets_text = vec![];
|
||||||
while let Some(redir) = redirs.pop() {
|
while let Some(redir) = redirs.pop() {
|
||||||
let Redir { src: _, op: _, tgt } = &redir;
|
let Redir { src: _, op: _, tgt } = &redir;
|
||||||
match tgt {
|
match tgt {
|
||||||
RedirTarget::Fd(_) => targets_fd.push(redir),
|
RedirTarget::Fd(_) => targets_fd.push(redir),
|
||||||
RedirTarget::File(_) => targets_file.push(redir)
|
RedirTarget::File(_) => targets_file.push(redir),
|
||||||
|
_ => targets_text.push(redir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self { open: vec![], targets_fd, targets_file }
|
Self { open: vec![], targets_fd, targets_file, targets_text }
|
||||||
}
|
}
|
||||||
pub fn close_all(&mut self) -> ShResult<()> {
|
pub fn close_all(&mut self) -> ShResult<()> {
|
||||||
while let Some(fd) = self.open.pop() {
|
while let Some(fd) = self.open.pop() {
|
||||||
@@ -303,6 +315,26 @@ impl CmdRedirs {
|
|||||||
pub fn activate(&mut self) -> ShResult<()> {
|
pub fn activate(&mut self) -> ShResult<()> {
|
||||||
self.open_file_tgts()?;
|
self.open_file_tgts()?;
|
||||||
self.open_fd_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)?;
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
dup2(rpipe, src.as_raw_fd())?;
|
||||||
|
close(rpipe)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn open_file_tgts(&mut self) -> ShResult<()> {
|
pub fn open_file_tgts(&mut self) -> ShResult<()> {
|
||||||
@@ -342,6 +374,23 @@ impl CmdRedirs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
pub fn check_expansion(s: &str) -> Option<TkRule> {
|
||||||
let rule = Lexer::get_rule(s);
|
let rule = Lexer::get_rule(s);
|
||||||
if EXPANSIONS.contains(&rule) {
|
if EXPANSIONS.contains(&rule) {
|
||||||
|
|||||||
@@ -989,7 +989,7 @@ tkrule_def!(RedirOp, |input: &str| {
|
|||||||
try_match_inner!(RedirSimpleOut,input); // >
|
try_match_inner!(RedirSimpleOut,input); // >
|
||||||
try_match_inner!(RedirInOut,input); // <>
|
try_match_inner!(RedirInOut,input); // <>
|
||||||
try_match_inner!(RedirSimpleHerestring,input); // <<<
|
try_match_inner!(RedirSimpleHerestring,input); // <<<
|
||||||
try_match_inner!(RedirSimpleHeredoc,input); // <<
|
try_match_inner!(RedirHeredoc,input); // <<
|
||||||
try_match_inner!(RedirSimpleIn,input); // <
|
try_match_inner!(RedirSimpleIn,input); // <
|
||||||
try_match_inner!(RedirFdOutFd,input); // Ex: 2>&1
|
try_match_inner!(RedirFdOutFd,input); // Ex: 2>&1
|
||||||
try_match_inner!(RedirFdInFd,input); // Ex: 2<&1
|
try_match_inner!(RedirFdInFd,input); // Ex: 2<&1
|
||||||
@@ -1018,9 +1018,41 @@ tkrule_def!(RedirInOut, |input: &str| {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tkrule_def!(RedirSimpleHeredoc, |input: &str| {
|
tkrule_def!(RedirHeredoc, |mut input: &str| {
|
||||||
if input.starts_with("<<") {
|
if input.starts_with("<<") {
|
||||||
Some(2)
|
let mut len = 2;
|
||||||
|
input = &input[2..];
|
||||||
|
let mut chars = input.chars();
|
||||||
|
let mut delim = "";
|
||||||
|
let mut delim_len = 0;
|
||||||
|
let mut body = "";
|
||||||
|
let mut body_len = 1;
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
len += 1;
|
||||||
|
match ch {
|
||||||
|
' ' | '\t' | '\n' | ';' if delim.is_empty() => return None,
|
||||||
|
_ if delim.is_empty() => {
|
||||||
|
delim_len += 1;
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
len += 1;
|
||||||
|
match ch {
|
||||||
|
'\n' => {
|
||||||
|
delim = &input[..delim_len];
|
||||||
|
input = &input[delim_len..];
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ => delim_len += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
body_len += 1;
|
||||||
|
body = &input[..body_len];
|
||||||
|
if body.ends_with(&delim) { break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(len)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ pub mod lex;
|
|||||||
|
|
||||||
use std::{iter::Peekable, str::FromStr};
|
use std::{iter::Peekable, str::FromStr};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{expand::vars::expand_string, prelude::*};
|
||||||
|
|
||||||
use lex::{Span, TkRule, Token, KEYWORDS, OPERATORS, SEPARATORS};
|
use lex::{Span, TkRule, Token, KEYWORDS, OPERATORS, SEPARATORS};
|
||||||
|
|
||||||
@@ -208,9 +208,31 @@ fn get_redir(token: Token, token_slice: &[Token], shenv: &mut ShEnv) -> ShResult
|
|||||||
let mut tokens_eaten = 0;
|
let mut tokens_eaten = 0;
|
||||||
let mut tokens_iter = token_slice.into_iter();
|
let mut tokens_iter = token_slice.into_iter();
|
||||||
let redir_raw = shenv.input_slice(token.span());
|
let redir_raw = shenv.input_slice(token.span());
|
||||||
let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap();
|
let mut redir_bldr = RedirBldr::from_str(&redir_raw)?;
|
||||||
// If there isn't an FD target, get the next token and use it as the filename
|
// If there isn't an FD target, get the next token and use it as the filename
|
||||||
if redir_bldr.tgt().is_none() {
|
if redir_bldr.tgt().is_none() || redir_bldr.op() == Some(RedirType::HereDoc) {
|
||||||
|
if let Some(RedirType::HereString) = redir_bldr.op() {
|
||||||
|
if let Some(herestring) = tokens_iter.next() {
|
||||||
|
if !matches!(herestring.rule(), TkRule::SQuote | TkRule::DQuote) {
|
||||||
|
let mut err = ShErr::simple(ShErrKind::ParseErr, "Expected a string after herestring operator");
|
||||||
|
let input = shenv.input_slice(token.span()).to_string();
|
||||||
|
err.blame(input, token.span());
|
||||||
|
return Err(err)
|
||||||
|
}
|
||||||
|
tokens_eaten += 1;
|
||||||
|
let raw = clean_string(herestring.as_raw(shenv));
|
||||||
|
let exp = expand_string(&raw, shenv)?;
|
||||||
|
let tgt = RedirTarget::HereString(exp);
|
||||||
|
redir_bldr = redir_bldr.with_tgt(tgt);
|
||||||
|
}
|
||||||
|
} else if let Some(RedirType::HereDoc) = redir_bldr.op() {
|
||||||
|
if let Some(RedirTarget::HereDoc(body)) = redir_bldr.tgt() {
|
||||||
|
let body_exp = expand_string(body, shenv)?;
|
||||||
|
log!(DEBUG, body_exp);
|
||||||
|
let exp_tgt = RedirTarget::HereDoc(body_exp);
|
||||||
|
redir_bldr = redir_bldr.with_tgt(exp_tgt);
|
||||||
|
} else { unreachable!() }
|
||||||
|
} else {
|
||||||
if let Some(filename) = tokens_iter.next() {
|
if let Some(filename) = tokens_iter.next() {
|
||||||
// Make sure it's a word and not an operator or something
|
// Make sure it's a word and not an operator or something
|
||||||
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) {
|
if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) {
|
||||||
@@ -221,7 +243,7 @@ fn get_redir(token: Token, token_slice: &[Token], shenv: &mut ShEnv) -> ShResult
|
|||||||
}
|
}
|
||||||
tokens_eaten += 1;
|
tokens_eaten += 1;
|
||||||
// Construct the Path object
|
// Construct the Path object
|
||||||
let filename_raw = shenv.input_slice(filename.span()).to_string();
|
let filename_raw = filename.as_raw(shenv);
|
||||||
let filename_path = PathBuf::from(filename_raw);
|
let filename_path = PathBuf::from(filename_raw);
|
||||||
let tgt = RedirTarget::File(filename_path);
|
let tgt = RedirTarget::File(filename_path);
|
||||||
// Update the builder
|
// Update the builder
|
||||||
@@ -233,6 +255,8 @@ fn get_redir(token: Token, token_slice: &[Token], shenv: &mut ShEnv) -> ShResult
|
|||||||
return Err(err)
|
return Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Ok((tokens_eaten,redir_bldr.build()))
|
Ok((tokens_eaten,redir_bldr.build()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -983,10 +1007,11 @@ ndrule_def!(FuncDef, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
let mut tokens_iter = tokens.into_iter();
|
let mut tokens_iter = tokens.into_iter().peekable();
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
let mut argv = vec![];
|
let mut argv = vec![];
|
||||||
let mut redirs = vec![];
|
let mut redirs = vec![];
|
||||||
|
let mut flags = NdFlag::empty();
|
||||||
if let Some(token) = tokens_iter.next() {
|
if let Some(token) = tokens_iter.next() {
|
||||||
if let TkRule::Subshell = token.rule() {
|
if let TkRule::Subshell = token.rule() {
|
||||||
node_toks.push(token.clone());
|
node_toks.push(token.clone());
|
||||||
@@ -1011,6 +1036,16 @@ ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
node_toks.push(token.clone());
|
node_toks.push(token.clone());
|
||||||
argv.push(token.clone());
|
argv.push(token.clone());
|
||||||
}
|
}
|
||||||
|
TkRule::BgOp => {
|
||||||
|
flags |= NdFlag::BACKGROUND;
|
||||||
|
while let Some(token) = tokens_iter.peek() {
|
||||||
|
if token.rule() == TkRule::Sep {
|
||||||
|
let token = tokens_iter.next().unwrap();
|
||||||
|
node_toks.push(token.clone());
|
||||||
|
} else { break }
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
TkRule::RedirOp => {
|
TkRule::RedirOp => {
|
||||||
node_toks.push(token.clone());
|
node_toks.push(token.clone());
|
||||||
let slice = &tokens_iter.clone().map(|tk| tk.clone()).collect::<Vec<_>>();
|
let slice = &tokens_iter.clone().map(|tk| tk.clone()).collect::<Vec<_>>();
|
||||||
@@ -1030,7 +1065,7 @@ ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
node_rule: NdRule::Subshell { body, argv, redirs },
|
node_rule: NdRule::Subshell { body, argv, redirs },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
span,
|
span,
|
||||||
flags: NdFlag::empty()
|
flags
|
||||||
};
|
};
|
||||||
return Ok(Some(node))
|
return Ok(Some(node))
|
||||||
} else {
|
} else {
|
||||||
@@ -1045,6 +1080,7 @@ ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
let mut tokens_iter = tokens.iter().peekable();
|
let mut tokens_iter = tokens.iter().peekable();
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
let mut cmds = vec![];
|
let mut cmds = vec![];
|
||||||
|
let mut flags = NdFlag::empty();
|
||||||
|
|
||||||
while let Some(token) = tokens_iter.peek() {
|
while let Some(token) = tokens_iter.peek() {
|
||||||
match token.rule() {
|
match token.rule() {
|
||||||
@@ -1070,6 +1106,9 @@ ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
for _ in 0..cmd.len() {
|
for _ in 0..cmd.len() {
|
||||||
tokens_iter.next();
|
tokens_iter.next();
|
||||||
}
|
}
|
||||||
|
if cmd.flags().contains(NdFlag::BACKGROUND) {
|
||||||
|
flags |= NdFlag::BACKGROUND;
|
||||||
|
}
|
||||||
|
|
||||||
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
||||||
if let Some(arg) = argv.first() {
|
if let Some(arg) = argv.first() {
|
||||||
@@ -1116,7 +1155,7 @@ ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
node_rule: NdRule::Pipeline { cmds },
|
node_rule: NdRule::Pipeline { cmds },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
span,
|
span,
|
||||||
flags: NdFlag::empty()
|
flags
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
});
|
});
|
||||||
@@ -1127,6 +1166,7 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
let mut argv = vec![];
|
let mut argv = vec![];
|
||||||
let mut redirs = vec![];
|
let mut redirs = vec![];
|
||||||
|
let mut flags = NdFlag::empty();
|
||||||
|
|
||||||
while let Some(token) = tokens_iter.peek() {
|
while let Some(token) = tokens_iter.peek() {
|
||||||
match token.rule() {
|
match token.rule() {
|
||||||
@@ -1144,6 +1184,7 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
TkRule::TildeSub |
|
TkRule::TildeSub |
|
||||||
TkRule::ArithSub |
|
TkRule::ArithSub |
|
||||||
TkRule::CmdSub |
|
TkRule::CmdSub |
|
||||||
|
TkRule::BraceGrp |
|
||||||
TkRule::VarSub => {
|
TkRule::VarSub => {
|
||||||
argv.push(token.clone());
|
argv.push(token.clone());
|
||||||
}
|
}
|
||||||
@@ -1152,11 +1193,22 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
let (used,redir) = get_redir(token.clone(), slice, shenv)?;
|
let (used,redir) = get_redir(token.clone(), slice, shenv)?;
|
||||||
for _ in 0..used {
|
for _ in 0..used {
|
||||||
if let Some(token) = tokens_iter.next() {
|
if let Some(token) = tokens_iter.next() {
|
||||||
|
// FIXME: this pushes literally anything, it should only push stuff that looks like a filename
|
||||||
node_toks.push(token.clone());
|
node_toks.push(token.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
redirs.push(redir);
|
redirs.push(redir);
|
||||||
}
|
}
|
||||||
|
TkRule::BgOp => {
|
||||||
|
flags |= NdFlag::BACKGROUND;
|
||||||
|
while let Some(token) = tokens_iter.peek() {
|
||||||
|
if token.rule() == TkRule::Sep {
|
||||||
|
let token = tokens_iter.next().unwrap();
|
||||||
|
node_toks.push(token.clone());
|
||||||
|
} else { break }
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
TkRule::Sep => break,
|
TkRule::Sep => break,
|
||||||
_ => return Err(
|
_ => return Err(
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
@@ -1177,7 +1229,7 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
node_rule: NdRule::Command { argv, redirs },
|
node_rule: NdRule::Command { argv, redirs },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
span,
|
span,
|
||||||
flags: NdFlag::empty()
|
flags
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
} else {
|
} else {
|
||||||
@@ -1193,7 +1245,6 @@ ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
|||||||
while let Some(token) = tokens.peek() {
|
while let Some(token) = tokens.peek() {
|
||||||
if matches!(token.rule(), TkRule::Ident | TkRule::ArithSub | TkRule::CmdSub | TkRule::DQuote) {
|
if matches!(token.rule(), TkRule::Ident | TkRule::ArithSub | TkRule::CmdSub | TkRule::DQuote) {
|
||||||
let raw = token.as_raw(shenv);
|
let raw = token.as_raw(shenv);
|
||||||
log!(INFO, raw);
|
|
||||||
// We are going to deconstruct this Ident into two separate tokens
|
// We are going to deconstruct this Ident into two separate tokens
|
||||||
// This makes expanding it easier later
|
// This makes expanding it easier later
|
||||||
if raw.split_once('=').is_some() {
|
if raw.split_once('=').is_some() {
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ pub use crate::{
|
|||||||
},
|
},
|
||||||
shellenv::{
|
shellenv::{
|
||||||
self,
|
self,
|
||||||
wait_fg,
|
dispatch_job,
|
||||||
log_level,
|
log_level,
|
||||||
attach_tty,
|
attach_tty,
|
||||||
term_ctlr,
|
term_ctlr,
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ impl ExecCtx {
|
|||||||
pub fn redirs(&self) -> &Vec<Redir> {
|
pub fn redirs(&self) -> &Vec<Redir> {
|
||||||
&self.redirs
|
&self.redirs
|
||||||
}
|
}
|
||||||
|
pub fn clear_redirs(&mut self) {
|
||||||
|
self.redirs.clear()
|
||||||
|
}
|
||||||
pub fn sort_redirs(&self) -> (Vec<Redir>,Vec<Redir>) {
|
pub fn sort_redirs(&self) -> (Vec<Redir>,Vec<Redir>) {
|
||||||
let mut cond_redirs = vec![];
|
let mut cond_redirs = vec![];
|
||||||
let mut body_redirs = vec![];
|
let mut body_redirs = vec![];
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub mod meta;
|
|||||||
pub mod shenv;
|
pub mod shenv;
|
||||||
pub mod vars;
|
pub mod vars;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod shopt;
|
||||||
|
|
||||||
/// Calls attach_tty() on the shell's process group to retake control of the terminal
|
/// Calls attach_tty() on the shell's process group to retake control of the terminal
|
||||||
pub fn take_term() -> ShResult<()> {
|
pub fn take_term() -> ShResult<()> {
|
||||||
@@ -56,6 +57,17 @@ pub fn wait_fg(job: Job, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
Ok(())
|
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 {
|
pub fn log_level() -> crate::libsh::utils::LogLevel {
|
||||||
let level = env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
let level = env::var("FERN_LOG_LEVEL").unwrap_or_default();
|
||||||
match level.to_lowercase().as_str() {
|
match level.to_lowercase().as_str() {
|
||||||
@@ -80,7 +92,7 @@ pub fn attach_tty(pgid: Pid) -> ShResult<()> {
|
|||||||
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() {
|
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
log!(DEBUG, "Attaching tty to pgid: {}",pgid);
|
log!(TRACE, "Attaching tty to pgid: {}",pgid);
|
||||||
|
|
||||||
if pgid == getpgrp() && term_ctlr() != getpgrp() {
|
if pgid == getpgrp() && term_ctlr() != getpgrp() {
|
||||||
kill(term_ctlr(), Signal::SIGTTOU).ok();
|
kill(term_ctlr(), Signal::SIGTTOU).ok();
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ impl ShEnv {
|
|||||||
let old = &input[range.clone()];
|
let old = &input[range.clone()];
|
||||||
let delta: isize = new.len() as isize - old.len() as isize;
|
let delta: isize = new.len() as isize - old.len() as isize;
|
||||||
input.replace_range(range, new);
|
input.replace_range(range, new);
|
||||||
log!(INFO,input);
|
|
||||||
|
|
||||||
for span in self.input_man.spans_mut() {
|
for span in self.input_man.spans_mut() {
|
||||||
let mut span_mut = span.borrow_mut();
|
let mut span_mut = span.borrow_mut();
|
||||||
@@ -162,6 +161,7 @@ impl ShEnv {
|
|||||||
}
|
}
|
||||||
pub fn reset_io(&mut self) -> ShResult<()> {
|
pub fn reset_io(&mut self) -> ShResult<()> {
|
||||||
let ctx = self.ctx_mut();
|
let ctx = self.ctx_mut();
|
||||||
|
ctx.clear_redirs();
|
||||||
if let Some(saved) = ctx.saved_io().take() {
|
if let Some(saved) = ctx.saved_io().take() {
|
||||||
let saved_in = saved.stdin;
|
let saved_in = saved.stdin;
|
||||||
let saved_out = saved.stdout;
|
let saved_out = saved.stdout;
|
||||||
|
|||||||
11
src/shellenv/shopt.rs
Normal file
11
src/shellenv/shopt.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pub struct ShOpts {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CoreOpts {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PromptOpts {
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user