properly implemented read builtin
fixed bugs related to redirections and compound commands improved io routing logic
This commit is contained in:
@@ -18,7 +18,7 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -54,7 +54,6 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
write_logic(|l| l.insert_alias(name, body));
|
write_logic(|l| l.insert_alias(name, body));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io_frame.unwrap().restore()?;
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -68,7 +67,7 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -97,7 +96,6 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
|
|||||||
write_logic(|l| l.remove_alias(&arg))
|
write_logic(|l| l.remove_alias(&arg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io_frame.unwrap().restore()?;
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::setup_builtin, expand::expand_prompt, getopt::{Opt, OptSet, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state
|
builtin::setup_builtin, expand::expand_prompt, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, state
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static ECHO_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
|
pub const ECHO_OPTS: [OptSpec;4] = [
|
||||||
[
|
OptSpec { opt: Opt::Short('n'), takes_arg: false },
|
||||||
Opt::Short('n'),
|
OptSpec { opt: Opt::Short('E'), takes_arg: false },
|
||||||
Opt::Short('E'),
|
OptSpec { opt: Opt::Short('e'), takes_arg: false },
|
||||||
Opt::Short('e'),
|
OptSpec { opt: Opt::Short('p'), takes_arg: false },
|
||||||
Opt::Short('p'),
|
];
|
||||||
]
|
|
||||||
.into()
|
|
||||||
});
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct EchoFlags: u32 {
|
pub struct EchoFlags: u32 {
|
||||||
@@ -33,9 +30,9 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
assert!(!argv.is_empty());
|
assert!(!argv.is_empty());
|
||||||
let (argv, opts) = get_opts_from_tokens(argv);
|
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS);
|
||||||
let flags = get_echo_flags(opts).blame(blame)?;
|
let flags = get_echo_flags(opts).blame(blame)?;
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
||||||
borrow_fd(STDERR_FILENO)
|
borrow_fd(STDERR_FILENO)
|
||||||
@@ -57,7 +54,6 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
|
|
||||||
write(output_channel, echo_output.as_bytes())?;
|
write(output_channel, echo_output.as_bytes())?;
|
||||||
|
|
||||||
io_frame.unwrap().restore()?;
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -178,24 +174,21 @@ pub fn prepare_echo_args(argv: Vec<String>, use_escape: bool, use_prompt: bool)
|
|||||||
Ok(prepared_args)
|
Ok(prepared_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_echo_flags(mut opts: Vec<Opt>) -> ShResult<EchoFlags> {
|
pub fn get_echo_flags(opts: Vec<Opt>) -> ShResult<EchoFlags> {
|
||||||
let mut flags = EchoFlags::empty();
|
let mut flags = EchoFlags::empty();
|
||||||
|
|
||||||
while let Some(opt) = opts.pop() {
|
for opt in opts {
|
||||||
if !ECHO_OPTS.contains(&opt) {
|
match opt {
|
||||||
|
Opt::Short('n') => flags |= EchoFlags::NO_NEWLINE,
|
||||||
|
Opt::Short('r') => flags |= EchoFlags::USE_STDERR,
|
||||||
|
Opt::Short('e') => flags |= EchoFlags::USE_ESCAPE,
|
||||||
|
Opt::Short('p') => flags |= EchoFlags::USE_PROMPT,
|
||||||
|
_ => {
|
||||||
return Err(ShErr::simple(
|
return Err(ShErr::simple(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
format!("echo: Unexpected flag '{opt}'"),
|
format!("echo: Unexpected flag '{opt}'"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let Opt::Short(opt) = opt else { unreachable!() };
|
|
||||||
|
|
||||||
match opt {
|
|
||||||
'n' => flags |= EchoFlags::NO_NEWLINE,
|
|
||||||
'r' => flags |= EchoFlags::USE_STDERR,
|
|
||||||
'e' => flags |= EchoFlags::USE_ESCAPE,
|
|
||||||
'p' => flags |= EchoFlags::USE_PROMPT,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
// Display the environment variables
|
// Display the environment variables
|
||||||
@@ -42,7 +42,6 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io_frame.unwrap().restore()?;
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let mut flags = JobCmdFlags::empty();
|
let mut flags = JobCmdFlags::empty();
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
@@ -175,7 +175,6 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_jobs(|j| j.print_jobs(flags))?;
|
write_jobs(|j| j.print_jobs(flags))?;
|
||||||
io_frame.unwrap().restore()?;
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ use crate::{
|
|||||||
jobs::{ChildProc, JobBldr},
|
jobs::{ChildProc, JobBldr},
|
||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{
|
parse::{
|
||||||
execute::prepare_argv,
|
Redir, execute::prepare_argv, lex::{Span, Tk}
|
||||||
lex::{Span, Tk},
|
|
||||||
Redir,
|
|
||||||
},
|
},
|
||||||
procio::{IoFrame, IoStack},
|
procio::{IoFrame, IoStack, RedirGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod alias;
|
pub mod alias;
|
||||||
@@ -21,12 +19,15 @@ pub mod pwd;
|
|||||||
pub mod shift;
|
pub mod shift;
|
||||||
pub mod shopt;
|
pub mod shopt;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod test;
|
pub mod test; // [[ ]] thing
|
||||||
pub mod zoltraak; // [[ ]] thing
|
pub mod read;
|
||||||
|
pub mod zoltraak;
|
||||||
|
|
||||||
pub const BUILTINS: [&str; 19] = [
|
pub const BUILTINS: [&str; 20] = [
|
||||||
"echo", "cd", "export", "pwd", "source", "shift", "jobs", "fg", "bg", "alias", "unalias",
|
"echo", "cd", "read", "export", "pwd", "source",
|
||||||
"return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command",
|
"shift", "jobs", "fg", "bg", "alias", "unalias",
|
||||||
|
"return", "break", "continue", "exit", "zoltraak",
|
||||||
|
"shopt", "builtin", "command",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sets up a builtin command
|
/// Sets up a builtin command
|
||||||
@@ -56,7 +57,7 @@ pub const BUILTINS: [&str; 19] = [
|
|||||||
/// * If redirections are given, the second field of the resulting tuple will
|
/// * If redirections are given, the second field of the resulting tuple will
|
||||||
/// *always* be `Some()`
|
/// *always* be `Some()`
|
||||||
/// * If no redirections are given, the second field will *always* be `None`
|
/// * If no redirections are given, the second field will *always* be `None`
|
||||||
type SetupReturns = ShResult<(Vec<(String, Span)>, Option<IoFrame>)>;
|
type SetupReturns = ShResult<(Vec<(String, Span)>, Option<RedirGuard>)>;
|
||||||
pub fn setup_builtin(
|
pub fn setup_builtin(
|
||||||
argv: Vec<Tk>,
|
argv: Vec<Tk>,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
@@ -74,16 +75,16 @@ pub fn setup_builtin(
|
|||||||
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
||||||
job.push_child(child);
|
job.push_child(child);
|
||||||
|
|
||||||
let io_frame = if let Some((io_stack, redirs)) = io_mode {
|
let guard = if let Some((io_stack, redirs)) = io_mode {
|
||||||
io_stack.append_to_frame(redirs);
|
io_stack.append_to_frame(redirs);
|
||||||
let mut io_frame = io_stack.pop_frame();
|
let io_frame = io_stack.pop_frame();
|
||||||
io_frame.redirect()?;
|
let guard = io_frame.redirect()?;
|
||||||
Some(io_frame)
|
Some(guard)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// We return the io_frame because the caller needs to also call
|
// We return the io_frame because the caller needs to also call
|
||||||
// io_frame.restore()
|
// io_frame.restore()
|
||||||
Ok((argv, io_frame))
|
Ok((argv, guard))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (_, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@ pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()
|
|||||||
curr_dir.push('\n');
|
curr_dir.push('\n');
|
||||||
write(stdout, curr_dir.as_bytes())?;
|
write(stdout, curr_dir.as_bytes())?;
|
||||||
|
|
||||||
io_frame.unwrap().restore().unwrap();
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
187
src/builtin/read.rs
Normal file
187
src/builtin/read.rs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
use bitflags::bitflags;
|
||||||
|
use nix::{errno::Errno, libc::{STDIN_FILENO, STDOUT_FILENO}, unistd::{isatty, read, write}};
|
||||||
|
|
||||||
|
use crate::{builtin::setup_builtin, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node}, procio::{IoStack, borrow_fd}, prompt::readline::term::RawModeGuard, state::{self, VarFlags, read_vars, write_vars}};
|
||||||
|
|
||||||
|
pub const READ_OPTS: [OptSpec;7] = [
|
||||||
|
OptSpec { opt: Opt::Short('r'), takes_arg: false }, // don't allow backslash escapes
|
||||||
|
OptSpec { opt: Opt::Short('s'), takes_arg: false }, // don't echo input
|
||||||
|
OptSpec { opt: Opt::Short('a'), takes_arg: false }, // read into array
|
||||||
|
OptSpec { opt: Opt::Short('n'), takes_arg: false }, // read only N characters
|
||||||
|
OptSpec { opt: Opt::Short('t'), takes_arg: false }, // timeout
|
||||||
|
OptSpec { opt: Opt::Short('p'), takes_arg: true }, // prompt
|
||||||
|
OptSpec { opt: Opt::Short('d'), takes_arg: true }, // read until delimiter
|
||||||
|
];
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
pub struct ReadFlags: u32 {
|
||||||
|
const NO_ESCAPES = 0b000001;
|
||||||
|
const NO_ECHO = 0b000010;
|
||||||
|
const ARRAY = 0b000100;
|
||||||
|
const N_CHARS = 0b001000;
|
||||||
|
const TIMEOUT = 0b010000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ReadOpts {
|
||||||
|
prompt: Option<String>,
|
||||||
|
delim: u8, // byte representation of the delimiter character
|
||||||
|
flags: ReadFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
let blame = node.get_span().clone();
|
||||||
|
let NdRule::Command {
|
||||||
|
assignments: _,
|
||||||
|
argv
|
||||||
|
} = node.class else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS);
|
||||||
|
let read_opts = get_read_flags(opts).blame(blame.clone())?;
|
||||||
|
let (argv, _) = setup_builtin(argv, job, None).blame(blame.clone())?;
|
||||||
|
|
||||||
|
if let Some(prompt) = read_opts.prompt {
|
||||||
|
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = if isatty(STDIN_FILENO)? {
|
||||||
|
// Restore default terminal settings
|
||||||
|
RawModeGuard::with_cooked_mode(|| {
|
||||||
|
let mut input: Vec<u8> = vec![];
|
||||||
|
let mut escaped = false;
|
||||||
|
loop {
|
||||||
|
let mut buf = [0u8;1];
|
||||||
|
match read(STDIN_FILENO, &mut buf) {
|
||||||
|
Ok(0) => {
|
||||||
|
state::set_status(1);
|
||||||
|
let str_result = String::from_utf8(input.clone()).map_err(|e| ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("read: Input was not valid UTF-8: {e}"),
|
||||||
|
))?;
|
||||||
|
return Ok(str_result); // EOF
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
if buf[0] == read_opts.delim {
|
||||||
|
if read_opts.flags.contains(ReadFlags::NO_ESCAPES) && escaped {
|
||||||
|
input.push(buf[0]);
|
||||||
|
} else {
|
||||||
|
// Delimiter reached, stop reading
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if read_opts.flags.contains(ReadFlags::NO_ESCAPES)
|
||||||
|
&& buf[0] == b'\\' {
|
||||||
|
escaped = true;
|
||||||
|
} else {
|
||||||
|
input.push(buf[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Errno::EINTR) => continue,
|
||||||
|
Err(e) => return Err(ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("read: Failed to read from stdin: {e}"),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
|
let str_result = String::from_utf8(input.clone()).map_err(|e| ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("read: Input was not valid UTF-8: {e}"),
|
||||||
|
))?;
|
||||||
|
Ok(str_result)
|
||||||
|
}).blame(blame)?
|
||||||
|
} else {
|
||||||
|
let mut input: Vec<u8> = vec![];
|
||||||
|
loop {
|
||||||
|
let mut buf = [0u8;1];
|
||||||
|
match read(STDIN_FILENO, &mut buf) {
|
||||||
|
Ok(0) => {
|
||||||
|
state::set_status(1);
|
||||||
|
break; // EOF
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
if buf[0] == read_opts.delim {
|
||||||
|
break; // Delimiter reached, stop reading
|
||||||
|
}
|
||||||
|
input.push(buf[0]);
|
||||||
|
}
|
||||||
|
Err(Errno::EINTR) => continue,
|
||||||
|
Err(e) => return Err(ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("read: Failed to read from stdin: {e}"),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from_utf8(input).map_err(|e| ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("read: Input was not valid UTF-8: {e}"),
|
||||||
|
))?
|
||||||
|
};
|
||||||
|
|
||||||
|
if argv.is_empty() {
|
||||||
|
write_vars(|v| {
|
||||||
|
v.set_var("REPLY", &input, VarFlags::NONE);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// get our field separator
|
||||||
|
let mut field_sep = read_vars(|v| v.get_var("IFS"));
|
||||||
|
if field_sep.is_empty() { field_sep = " ".to_string() }
|
||||||
|
let mut remaining = input;
|
||||||
|
|
||||||
|
for (i, arg) in argv.iter().enumerate() {
|
||||||
|
if i == argv.len() - 1 {
|
||||||
|
// Last arg, stuff the rest of the input into it
|
||||||
|
write_vars(|v| v.set_var(&arg.0, &remaining, VarFlags::NONE));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim leading IFS characters
|
||||||
|
let trimmed = remaining.trim_start_matches(|c: char| field_sep.contains(c));
|
||||||
|
|
||||||
|
if let Some(idx) = trimmed.find(|c: char| field_sep.contains(c)) {
|
||||||
|
// We found a field separator, split at the char index
|
||||||
|
let (field, rest) = trimmed.split_at(idx);
|
||||||
|
write_vars(|v| v.set_var(&arg.0, field, VarFlags::NONE));
|
||||||
|
|
||||||
|
// note that this doesn't account for consecutive IFS characters, which is what that trim above is for
|
||||||
|
remaining = rest.to_string();
|
||||||
|
} else {
|
||||||
|
write_vars(|v| v.set_var(&arg.0, trimmed, VarFlags::NONE));
|
||||||
|
remaining.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_read_flags(opts: Vec<Opt>) -> ShResult<ReadOpts> {
|
||||||
|
let mut read_opts = ReadOpts {
|
||||||
|
prompt: None,
|
||||||
|
delim: b'\n',
|
||||||
|
flags: ReadFlags::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for opt in opts {
|
||||||
|
match opt {
|
||||||
|
Opt::Short('r') => read_opts.flags |= ReadFlags::NO_ESCAPES,
|
||||||
|
Opt::Short('s') => read_opts.flags |= ReadFlags::NO_ECHO,
|
||||||
|
Opt::Short('a') => read_opts.flags |= ReadFlags::ARRAY,
|
||||||
|
Opt::Short('n') => read_opts.flags |= ReadFlags::N_CHARS,
|
||||||
|
Opt::Short('t') => read_opts.flags |= ReadFlags::TIMEOUT,
|
||||||
|
Opt::ShortWithArg('p', prompt) => read_opts.prompt = Some(prompt),
|
||||||
|
Opt::ShortWithArg('d', delim) => read_opts.delim = delim.chars().map(|c| c as u8).next().unwrap_or(b'\n'),
|
||||||
|
_ => {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::ExecFail,
|
||||||
|
format!("read: Unexpected flag '{opt}'"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(read_opts)
|
||||||
|
}
|
||||||
@@ -18,10 +18,8 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let mut io_frame = io_frame.unwrap();
|
|
||||||
io_frame.redirect()?;
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
|
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
|
||||||
continue;
|
continue;
|
||||||
@@ -31,11 +29,9 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
output.push('\n');
|
output.push('\n');
|
||||||
|
|
||||||
if let Err(e) = write(output_channel, output.as_bytes()) {
|
if let Err(e) = write(output_channel, output.as_bytes()) {
|
||||||
io_frame.restore()?;
|
|
||||||
return Err(e.into());
|
return Err(e.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
io_frame.restore()?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
|
use std::{os::unix::fs::OpenOptionsExt, sync::LazyLock};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
getopt::{get_opts_from_tokens, Opt, OptSet},
|
getopt::{Opt, OptSet, OptSpec, get_opts_from_tokens},
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
|
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{borrow_fd, IoStack},
|
procio::{IoStack, borrow_fd},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub static ZOLTRAAK_OPTS: LazyLock<OptSet> = LazyLock::new(|| {
|
|
||||||
[
|
|
||||||
Opt::Long("dry-run".into()),
|
|
||||||
Opt::Long("confirm".into()),
|
|
||||||
Opt::Long("no-preserve-root".into()),
|
|
||||||
Opt::Short('r'),
|
|
||||||
Opt::Short('f'),
|
|
||||||
Opt::Short('v'),
|
|
||||||
]
|
|
||||||
.into()
|
|
||||||
});
|
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
|
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
|
||||||
struct ZoltFlags: u32 {
|
struct ZoltFlags: u32 {
|
||||||
@@ -49,37 +37,59 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
let zolt_opts = [
|
||||||
|
OptSpec { opt: Opt::Long("dry-run".into()), takes_arg: false },
|
||||||
|
OptSpec { opt: Opt::Long("confirm".into()), takes_arg: false },
|
||||||
|
OptSpec { opt: Opt::Long("no-preserve-root".into()), takes_arg: false },
|
||||||
|
OptSpec { opt: Opt::Short('r'), takes_arg: false },
|
||||||
|
OptSpec { opt: Opt::Short('f'), takes_arg: false },
|
||||||
|
OptSpec { opt: Opt::Short('v'), takes_arg: false }
|
||||||
|
];
|
||||||
let mut flags = ZoltFlags::empty();
|
let mut flags = ZoltFlags::empty();
|
||||||
|
|
||||||
let (argv, opts) = get_opts_from_tokens(argv);
|
let (argv, opts) = get_opts_from_tokens(argv, &zolt_opts);
|
||||||
|
|
||||||
for opt in opts {
|
for opt in opts {
|
||||||
if !ZOLTRAAK_OPTS.contains(&opt) {
|
|
||||||
return Err(ShErr::simple(
|
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("zoltraak: unrecognized option '{opt}'"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
match opt {
|
match opt {
|
||||||
Opt::Long(flag) => match flag.as_str() {
|
Opt::Long(flag) => match flag.as_str() {
|
||||||
"no-preserve-root" => flags |= ZoltFlags::NO_PRESERVE_ROOT,
|
"no-preserve-root" => flags |= ZoltFlags::NO_PRESERVE_ROOT,
|
||||||
"confirm" => flags |= ZoltFlags::CONFIRM,
|
"confirm" => flags |= ZoltFlags::CONFIRM,
|
||||||
"dry-run" => flags |= ZoltFlags::DRY,
|
"dry-run" => flags |= ZoltFlags::DRY,
|
||||||
_ => unreachable!(),
|
_ => {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("zoltraak: unrecognized option '{flag}'"),
|
||||||
|
));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Opt::Short(flag) => match flag {
|
Opt::Short(flag) => match flag {
|
||||||
'r' => flags |= ZoltFlags::RECURSIVE,
|
'r' => flags |= ZoltFlags::RECURSIVE,
|
||||||
'f' => flags |= ZoltFlags::FORCE,
|
'f' => flags |= ZoltFlags::FORCE,
|
||||||
'v' => flags |= ZoltFlags::VERBOSE,
|
'v' => flags |= ZoltFlags::VERBOSE,
|
||||||
_ => unreachable!(),
|
_ => {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("zoltraak: unrecognized option '{flag}'"),
|
||||||
|
));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
Opt::LongWithArg(flag, _) => {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("zoltraak: unrecognized option '{flag}'"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Opt::ShortWithArg(flag, _) => {
|
||||||
|
return Err(ShErr::simple(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("zoltraak: unrecognized option '{flag}'"),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (argv, io_frame) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
|
||||||
|
|
||||||
let mut io_frame = io_frame.unwrap();
|
|
||||||
io_frame.redirect()?;
|
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
||||||
@@ -95,12 +105,10 @@ pub fn zoltraak(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Err(e) = annihilate(&arg, flags).blame(span) {
|
if let Err(e) = annihilate(&arg, flags).blame(span) {
|
||||||
io_frame.restore()?;
|
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
io_frame.restore()?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
130
src/expand.rs
130
src/expand.rs
@@ -9,7 +9,7 @@ use crate::libsh::error::{ShErr, ShErrKind, ShResult};
|
|||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::parse::lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Tk, TkFlags, TkRule};
|
use crate::parse::lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Tk, TkFlags, TkRule};
|
||||||
use crate::parse::{Redir, RedirType};
|
use crate::parse::{Redir, RedirType};
|
||||||
use crate::prelude::*;
|
use crate::{jobs, prelude::*};
|
||||||
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
||||||
use crate::state::{LogTab, VarFlags, read_jobs, read_logic, read_vars, write_jobs, write_meta, write_vars};
|
use crate::state::{LogTab, VarFlags, read_jobs, read_logic, read_vars, write_jobs, write_meta, write_vars};
|
||||||
|
|
||||||
@@ -29,14 +29,13 @@ pub const SUBSH: char = '\u{fdd4}';
|
|||||||
pub const PROC_SUB_IN: char = '\u{fdd5}';
|
pub const PROC_SUB_IN: char = '\u{fdd5}';
|
||||||
/// Output process sub marker
|
/// Output process sub marker
|
||||||
pub const PROC_SUB_OUT: char = '\u{fdd6}';
|
pub const PROC_SUB_OUT: char = '\u{fdd6}';
|
||||||
|
/// Marker for null expansion
|
||||||
|
/// This is used for when "$@" or "$*" are used in quotes and there are no arguments
|
||||||
|
/// Without this marker, it would be handled like an empty string, which breaks some commands
|
||||||
|
pub const NULL_EXPAND: char = '\u{fdd7}';
|
||||||
|
|
||||||
impl Tk {
|
impl Tk {
|
||||||
/// Create a new expanded token
|
/// 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) -> ShResult<Self> {
|
pub fn expand(self) -> ShResult<Self> {
|
||||||
let flags = self.flags;
|
let flags = self.flags;
|
||||||
let span = self.span.clone();
|
let span = self.span.clone();
|
||||||
@@ -59,8 +58,11 @@ pub struct Expander {
|
|||||||
|
|
||||||
impl Expander {
|
impl Expander {
|
||||||
pub fn new(raw: Tk) -> ShResult<Self> {
|
pub fn new(raw: Tk) -> ShResult<Self> {
|
||||||
let mut raw = raw.span.as_str().to_string();
|
let raw = raw.span.as_str();
|
||||||
raw = expand_braces_full(&raw)?.join(" ");
|
Self::from_raw(&raw)
|
||||||
|
}
|
||||||
|
pub fn from_raw(raw: &str) -> ShResult<Self> {
|
||||||
|
let raw = expand_braces_full(raw)?.join(" ");
|
||||||
let unescaped = unescape_str(&raw);
|
let unescaped = unescape_str(&raw);
|
||||||
Ok(Self { raw: unescaped })
|
Ok(Self { raw: unescaped })
|
||||||
}
|
}
|
||||||
@@ -77,24 +79,40 @@ impl Expander {
|
|||||||
let mut words = vec![];
|
let mut words = vec![];
|
||||||
let mut chars = self.raw.chars();
|
let mut chars = self.raw.chars();
|
||||||
let mut cur_word = String::new();
|
let mut cur_word = String::new();
|
||||||
|
let mut was_quoted = false;
|
||||||
|
|
||||||
'outer: while let Some(ch) = chars.next() {
|
'outer: while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
DUB_QUOTE | SNG_QUOTE | SUBSH => {
|
DUB_QUOTE | SNG_QUOTE | SUBSH => {
|
||||||
while let Some(q_ch) = chars.next() {
|
while let Some(q_ch) = chars.next() {
|
||||||
match q_ch {
|
match q_ch {
|
||||||
_ if q_ch == ch => continue 'outer, // Isn't rust cool
|
_ if q_ch == ch => {
|
||||||
|
was_quoted = true;
|
||||||
|
continue 'outer; // Isn't rust cool
|
||||||
|
}
|
||||||
_ => cur_word.push(q_ch),
|
_ => cur_word.push(q_ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if is_field_sep(ch) => {
|
_ if is_field_sep(ch) => {
|
||||||
|
if cur_word.is_empty() && !was_quoted {
|
||||||
|
cur_word.clear();
|
||||||
|
} else {
|
||||||
words.push(mem::take(&mut cur_word));
|
words.push(mem::take(&mut cur_word));
|
||||||
}
|
}
|
||||||
|
was_quoted = false;
|
||||||
|
}
|
||||||
_ => cur_word.push(ch),
|
_ => cur_word.push(ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if words.is_empty() && (cur_word.is_empty() && !was_quoted) {
|
||||||
|
return words;
|
||||||
|
} else {
|
||||||
words.push(cur_word);
|
words.push(cur_word);
|
||||||
|
}
|
||||||
|
|
||||||
|
words.retain(|w| w != &NULL_EXPAND.to_string());
|
||||||
words
|
words
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,17 +226,16 @@ fn expand_one_brace(word: &str) -> ShResult<Vec<String>> {
|
|||||||
/// Extract prefix, inner, and suffix from a brace expression.
|
/// Extract prefix, inner, and suffix from a brace expression.
|
||||||
/// "pre{a,b}post" -> Some(("pre", "a,b", "post"))
|
/// "pre{a,b}post" -> Some(("pre", "a,b", "post"))
|
||||||
fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
||||||
let mut chars = word.chars().enumerate().peekable();
|
let mut chars = word.chars().peekable();
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
let mut cur_quote: Option<char> = None;
|
let mut cur_quote: Option<char> = None;
|
||||||
let mut brace_start = None;
|
|
||||||
|
|
||||||
// Find the opening brace
|
// Find the opening brace
|
||||||
while let Some((i, ch)) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
prefix.push(ch);
|
prefix.push(ch);
|
||||||
if let Some((_, next)) = chars.next() {
|
if let Some(next) = chars.next() {
|
||||||
prefix.push(next);
|
prefix.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,25 +246,22 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
'"' if cur_quote.is_none() => { cur_quote = Some('"'); prefix.push(ch); }
|
'"' if cur_quote.is_none() => { cur_quote = Some('"'); prefix.push(ch); }
|
||||||
'"' if cur_quote == Some('"') => { cur_quote = None; prefix.push(ch); }
|
'"' if cur_quote == Some('"') => { cur_quote = None; prefix.push(ch); }
|
||||||
'{' if cur_quote.is_none() => {
|
'{' if cur_quote.is_none() => {
|
||||||
brace_start = Some(i);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => prefix.push(ch),
|
_ => prefix.push(ch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let brace_start = brace_start?;
|
|
||||||
|
|
||||||
// Find matching closing brace
|
// Find matching closing brace
|
||||||
let mut depth = 1;
|
let mut depth = 1;
|
||||||
let mut inner = String::new();
|
let mut inner = String::new();
|
||||||
cur_quote = None;
|
cur_quote = None;
|
||||||
|
|
||||||
while let Some((_, ch)) = chars.next() {
|
while let Some(ch) = chars.next() {
|
||||||
match ch {
|
match ch {
|
||||||
'\\' => {
|
'\\' => {
|
||||||
inner.push(ch);
|
inner.push(ch);
|
||||||
if let Some((_, next)) = chars.next() {
|
if let Some(next) = chars.next() {
|
||||||
inner.push(next);
|
inner.push(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,7 +289,7 @@ fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect suffix
|
// Collect suffix
|
||||||
let suffix: String = chars.map(|(_, c)| c).collect();
|
let suffix: String = chars.collect();
|
||||||
|
|
||||||
Some((prefix, inner, suffix))
|
Some((prefix, inner, suffix))
|
||||||
}
|
}
|
||||||
@@ -492,6 +506,11 @@ pub fn expand_var(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
|
|||||||
chars.next();
|
chars.next();
|
||||||
let parameter = format!("{ch}");
|
let parameter = format!("{ch}");
|
||||||
let val = read_vars(|v| v.get_var(¶meter));
|
let val = read_vars(|v| v.get_var(¶meter));
|
||||||
|
|
||||||
|
if (ch == '@' || ch == '*') && val.is_empty() {
|
||||||
|
return Ok(NULL_EXPAND.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
flog!(DEBUG, val);
|
flog!(DEBUG, val);
|
||||||
return Ok(val);
|
return Ok(val);
|
||||||
}
|
}
|
||||||
@@ -815,7 +834,7 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Reclaim terminal foreground in case child changed it
|
// Reclaim terminal foreground in case child changed it
|
||||||
crate::jobs::take_term()?;
|
jobs::take_term()?;
|
||||||
|
|
||||||
match status {
|
match status {
|
||||||
WtStat::Exited(_, _) => {
|
WtStat::Exited(_, _) => {
|
||||||
@@ -995,6 +1014,77 @@ pub fn unescape_str(raw: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
'$' if chars.peek() == Some(&'\'') => {
|
||||||
|
chars.next();
|
||||||
|
result.push(SNG_QUOTE);
|
||||||
|
while let Some(q_ch) = chars.next() {
|
||||||
|
match q_ch {
|
||||||
|
'\'' => {
|
||||||
|
result.push(SNG_QUOTE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
'\\' => {
|
||||||
|
if let Some(esc) = chars.next() {
|
||||||
|
match esc {
|
||||||
|
'n' => result.push('\n'),
|
||||||
|
't' => result.push('\t'),
|
||||||
|
'r' => result.push('\r'),
|
||||||
|
'\'' => result.push('\''),
|
||||||
|
'\\' => result.push('\\'),
|
||||||
|
'a' => result.push('\x07'),
|
||||||
|
'b' => result.push('\x08'),
|
||||||
|
'e' | 'E' => result.push('\x1b'),
|
||||||
|
'v' => result.push('\x0b'),
|
||||||
|
'x' => {
|
||||||
|
let mut hex = String::new();
|
||||||
|
if let Some(h1) = chars.next() {
|
||||||
|
hex.push(h1);
|
||||||
|
} else {
|
||||||
|
result.push_str("\\x");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(h2) = chars.next() {
|
||||||
|
hex.push(h2);
|
||||||
|
} else {
|
||||||
|
result.push_str(&format!("\\x{hex}"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Ok(byte) = u8::from_str_radix(&hex, 16) {
|
||||||
|
result.push(byte as char);
|
||||||
|
} else {
|
||||||
|
result.push_str(&format!("\\x{hex}"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'o' => {
|
||||||
|
let mut oct = String::new();
|
||||||
|
for _ in 0..3 {
|
||||||
|
if let Some(o) = chars.peek() {
|
||||||
|
if o.is_digit(8) {
|
||||||
|
oct.push(*o);
|
||||||
|
chars.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(byte) = u8::from_str_radix(&oct, 8) {
|
||||||
|
result.push(byte as char);
|
||||||
|
} else {
|
||||||
|
result.push_str(&format!("\\o{oct}"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => result.push(esc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => result.push(q_ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
'$' => {
|
'$' => {
|
||||||
result.push(VAR_SUB);
|
result.push(VAR_SUB);
|
||||||
if chars.peek() == Some(&'$') {
|
if chars.peek() == Some(&'$') {
|
||||||
|
|||||||
@@ -9,7 +9,14 @@ pub type OptSet = Arc<[Opt]>;
|
|||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Opt {
|
pub enum Opt {
|
||||||
Long(String),
|
Long(String),
|
||||||
|
LongWithArg(String,String),
|
||||||
Short(char),
|
Short(char),
|
||||||
|
ShortWithArg(char,String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OptSpec {
|
||||||
|
pub opt: Opt,
|
||||||
|
pub takes_arg: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Opt {
|
impl Opt {
|
||||||
@@ -34,6 +41,8 @@ impl Display for Opt {
|
|||||||
match self {
|
match self {
|
||||||
Self::Long(opt) => write!(f, "--{}", opt),
|
Self::Long(opt) => write!(f, "--{}", opt),
|
||||||
Self::Short(opt) => write!(f, "-{}", opt),
|
Self::Short(opt) => write!(f, "-{}", opt),
|
||||||
|
Self::LongWithArg(opt, arg) => write!(f, "--{} {}", opt, arg),
|
||||||
|
Self::ShortWithArg(opt, arg) => write!(f, "-{} {}", opt, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +67,7 @@ pub fn get_opts(words: Vec<String>) -> (Vec<String>, Vec<Opt>) {
|
|||||||
(non_opts, opts)
|
(non_opts, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_opts_from_tokens(tokens: Vec<Tk>) -> (Vec<Tk>, Vec<Opt>) {
|
pub fn get_opts_from_tokens(tokens: Vec<Tk>, opt_specs: &[OptSpec]) -> (Vec<Tk>, Vec<Opt>) {
|
||||||
let mut tokens_iter = tokens.into_iter();
|
let mut tokens_iter = tokens.into_iter();
|
||||||
let mut opts = vec![];
|
let mut opts = vec![];
|
||||||
let mut non_opts = vec![];
|
let mut non_opts = vec![];
|
||||||
@@ -69,10 +78,37 @@ pub fn get_opts_from_tokens(tokens: Vec<Tk>) -> (Vec<Tk>, Vec<Opt>) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let parsed_opts = Opt::parse(&token.to_string());
|
let parsed_opts = Opt::parse(&token.to_string());
|
||||||
|
|
||||||
if parsed_opts.is_empty() {
|
if parsed_opts.is_empty() {
|
||||||
non_opts.push(token)
|
non_opts.push(token)
|
||||||
} else {
|
} else {
|
||||||
opts.extend(parsed_opts);
|
for opt in parsed_opts {
|
||||||
|
let mut pushed = false;
|
||||||
|
for opt_spec in opt_specs {
|
||||||
|
if opt_spec.opt == opt {
|
||||||
|
if opt_spec.takes_arg {
|
||||||
|
let arg = tokens_iter.next()
|
||||||
|
.map(|t| t.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let opt = match opt {
|
||||||
|
Opt::Long(ref opt) => Opt::LongWithArg(opt.to_string(), arg),
|
||||||
|
Opt::Short(opt) => Opt::ShortWithArg(opt, arg),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
opts.push(opt);
|
||||||
|
pushed = true;
|
||||||
|
} else {
|
||||||
|
opts.push(opt.clone());
|
||||||
|
pushed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pushed {
|
||||||
|
non_opts.push(token.clone());
|
||||||
|
log::warn!("Unexpected flag '{opt}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(non_opts, opts)
|
(non_opts, opts)
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ use state::{read_vars, write_vars};
|
|||||||
struct FernArgs {
|
struct FernArgs {
|
||||||
script: Option<String>,
|
script: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short)]
|
||||||
|
command: Option<String>,
|
||||||
|
|
||||||
#[arg(trailing_var_arg = true)]
|
#[arg(trailing_var_arg = true)]
|
||||||
script_args: Vec<String>,
|
script_args: Vec<String>,
|
||||||
|
|
||||||
@@ -74,6 +77,8 @@ fn main() -> ExitCode {
|
|||||||
|
|
||||||
if let Err(e) = if let Some(path) = args.script {
|
if let Err(e) = if let Some(path) = args.script {
|
||||||
run_script(path, args.script_args)
|
run_script(path, args.script_args)
|
||||||
|
} else if let Some(cmd) = args.command {
|
||||||
|
exec_input(cmd, None, false)
|
||||||
} else {
|
} else {
|
||||||
fern_interactive()
|
fern_interactive()
|
||||||
} {
|
} {
|
||||||
|
|||||||
@@ -2,18 +2,7 @@ use std::collections::{HashSet, VecDeque};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{
|
builtin::{
|
||||||
alias::{alias, unalias},
|
alias::{alias, unalias}, cd::cd, echo::echo, export::export, flowctl::flowctl, jobctl::{JobBehavior, continue_job, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, zoltraak::zoltraak
|
||||||
cd::cd,
|
|
||||||
echo::echo,
|
|
||||||
export::export,
|
|
||||||
flowctl::flowctl,
|
|
||||||
jobctl::{JobBehavior, continue_job, jobs},
|
|
||||||
pwd::pwd,
|
|
||||||
shift::shift,
|
|
||||||
shopt::shopt,
|
|
||||||
source::source,
|
|
||||||
test::double_bracket_test,
|
|
||||||
zoltraak::zoltraak,
|
|
||||||
},
|
},
|
||||||
expand::expand_aliases,
|
expand::expand_aliases,
|
||||||
jobs::{ChildProc, JobBldr, JobStack, dispatch_job},
|
jobs::{ChildProc, JobBldr, JobStack, dispatch_job},
|
||||||
@@ -314,12 +303,13 @@ impl Dispatcher {
|
|||||||
let NdRule::BraceGrp { body } = brc_grp.class else {
|
let NdRule::BraceGrp { body } = brc_grp.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let mut io_frame = self.io_stack.pop_frame();
|
self.io_stack.append_to_frame(brc_grp.redirs);
|
||||||
io_frame.extend(brc_grp.redirs);
|
let _guard = self.io_stack
|
||||||
|
.pop_frame()
|
||||||
|
.redirect()?;
|
||||||
|
|
||||||
for node in body {
|
for node in body {
|
||||||
let blame = node.get_span();
|
let blame = node.get_span();
|
||||||
self.io_stack.push_frame(io_frame.clone());
|
|
||||||
self.dispatch_node(node).try_blame(blame)?;
|
self.dispatch_node(node).try_blame(blame)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +325,9 @@ impl Dispatcher {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.io_stack.append_to_frame(case_stmt.redirs);
|
self.io_stack.append_to_frame(case_stmt.redirs);
|
||||||
|
let _guard = self.io_stack
|
||||||
|
.pop_frame()
|
||||||
|
.redirect()?;
|
||||||
|
|
||||||
let exp_pattern = pattern.clone().expand()?;
|
let exp_pattern = pattern.clone().expand()?;
|
||||||
let pattern_raw = exp_pattern
|
let pattern_raw = exp_pattern
|
||||||
@@ -372,16 +365,13 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let io_frame = self.io_stack.pop_frame();
|
self.io_stack.append_to_frame(loop_stmt.redirs);
|
||||||
let (mut cond_frame, mut body_frame) = io_frame.split_frame();
|
let _guard = self.io_stack
|
||||||
let (in_redirs, out_redirs) = loop_stmt.redirs.split_by_channel();
|
.pop_frame()
|
||||||
cond_frame.extend(in_redirs);
|
.redirect()?;
|
||||||
body_frame.extend(out_redirs);
|
|
||||||
|
|
||||||
let CondNode { cond, body } = cond_node;
|
let CondNode { cond, body } = cond_node;
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
self.io_stack.push(cond_frame.clone());
|
|
||||||
|
|
||||||
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
@@ -389,7 +379,6 @@ impl Dispatcher {
|
|||||||
|
|
||||||
let status = state::get_status();
|
let status = state::get_status();
|
||||||
if keep_going(kind, status) {
|
if keep_going(kind, status) {
|
||||||
self.io_stack.push(body_frame.clone());
|
|
||||||
for node in &body {
|
for node in &body {
|
||||||
if let Err(e) = self.dispatch_node(node.clone()) {
|
if let Err(e) = self.dispatch_node(node.clone()) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
@@ -401,7 +390,9 @@ impl Dispatcher {
|
|||||||
state::set_status(*code);
|
state::set_status(*code);
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
}
|
}
|
||||||
_ => return Err(e),
|
_ => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,15 +412,15 @@ impl Dispatcher {
|
|||||||
vars.iter().map(|v| v.to_string()).collect()
|
vars.iter().map(|v| v.to_string()).collect()
|
||||||
);
|
);
|
||||||
|
|
||||||
let io_frame = self.io_stack.pop_frame();
|
self.io_stack.append_to_frame(for_stmt.redirs);
|
||||||
let (_, mut body_frame) = io_frame.split_frame();
|
let _guard = self.io_stack
|
||||||
let (_, out_redirs) = for_stmt.redirs.split_by_channel();
|
.pop_frame()
|
||||||
body_frame.extend(out_redirs);
|
.redirect()?;
|
||||||
|
|
||||||
'outer: for chunk in arr.chunks(vars.len()) {
|
'outer: for chunk in arr.chunks(vars.len()) {
|
||||||
let empty = Tk::default();
|
let empty = Tk::default();
|
||||||
let chunk_iter = vars.iter().zip(
|
let chunk_iter = vars.iter().zip(
|
||||||
chunk.iter().chain(std::iter::repeat(&empty)), // Or however you define an empty token
|
chunk.iter().chain(std::iter::repeat(&empty)),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (var, val) in chunk_iter {
|
for (var, val) in chunk_iter {
|
||||||
@@ -438,7 +429,6 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for node in body.clone() {
|
for node in body.clone() {
|
||||||
self.io_stack.push(body_frame.clone());
|
|
||||||
if let Err(e) = self.dispatch_node(node) {
|
if let Err(e) = self.dispatch_node(node) {
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::LoopBreak(code) => {
|
ShErrKind::LoopBreak(code) => {
|
||||||
@@ -465,17 +455,15 @@ impl Dispatcher {
|
|||||||
else {
|
else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
// Pop the current frame and split it
|
|
||||||
let io_frame = self.io_stack.pop_frame();
|
self.io_stack.append_to_frame(if_stmt.redirs);
|
||||||
let (mut cond_frame, mut body_frame) = io_frame.split_frame();
|
let _guard = self.io_stack
|
||||||
let (in_redirs, out_redirs) = if_stmt.redirs.split_by_channel();
|
.pop_frame()
|
||||||
cond_frame.extend(in_redirs); // Condition gets input redirs
|
.redirect()?;
|
||||||
body_frame.extend(out_redirs); // Body gets output redirs
|
|
||||||
|
|
||||||
let mut matched = false;
|
let mut matched = false;
|
||||||
for node in cond_nodes {
|
for node in cond_nodes {
|
||||||
let CondNode { cond, body } = node;
|
let CondNode { cond, body } = node;
|
||||||
self.io_stack.push(cond_frame.clone());
|
|
||||||
|
|
||||||
if let Err(e) = self.dispatch_node(*cond) {
|
if let Err(e) = self.dispatch_node(*cond) {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
@@ -486,7 +474,6 @@ impl Dispatcher {
|
|||||||
0 => {
|
0 => {
|
||||||
matched = true;
|
matched = true;
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
self.io_stack.push(body_frame.clone());
|
|
||||||
self.dispatch_node(body_node)?;
|
self.dispatch_node(body_node)?;
|
||||||
}
|
}
|
||||||
break; // Don't check remaining elif conditions
|
break; // Don't check remaining elif conditions
|
||||||
@@ -497,7 +484,6 @@ impl Dispatcher {
|
|||||||
|
|
||||||
if !matched && !else_block.is_empty() {
|
if !matched && !else_block.is_empty() {
|
||||||
for node in else_block {
|
for node in else_block {
|
||||||
self.io_stack.push(body_frame.clone());
|
|
||||||
self.dispatch_node(node)?;
|
self.dispatch_node(node)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -576,6 +562,7 @@ impl Dispatcher {
|
|||||||
"exit" => flowctl(cmd, ShErrKind::CleanExit(0)),
|
"exit" => flowctl(cmd, ShErrKind::CleanExit(0)),
|
||||||
"zoltraak" => zoltraak(cmd, io_stack_mut, curr_job_mut),
|
"zoltraak" => zoltraak(cmd, io_stack_mut, curr_job_mut),
|
||||||
"shopt" => shopt(cmd, io_stack_mut, curr_job_mut),
|
"shopt" => shopt(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"read" => read_builtin(cmd, io_stack_mut, curr_job_mut),
|
||||||
_ => unimplemented!(
|
_ => unimplemented!(
|
||||||
"Have not yet added support for builtin '{}'",
|
"Have not yet added support for builtin '{}'",
|
||||||
cmd_raw.span.as_str()
|
cmd_raw.span.as_str()
|
||||||
@@ -613,9 +600,11 @@ impl Dispatcher {
|
|||||||
log::info!("expanded argv: {:?}", exec_args.argv.iter().map(|s| s.to_str().unwrap()).collect::<Vec<_>>());
|
log::info!("expanded argv: {:?}", exec_args.argv.iter().map(|s| s.to_str().unwrap()).collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
let io_frame = self.io_stack.pop_frame();
|
let _guard = self.io_stack
|
||||||
|
.pop_frame()
|
||||||
|
.redirect()?;
|
||||||
|
|
||||||
run_fork(
|
run_fork(
|
||||||
io_frame,
|
|
||||||
Some(exec_args),
|
Some(exec_args),
|
||||||
self.job_stack.curr_job_mut().unwrap(),
|
self.job_stack.curr_job_mut().unwrap(),
|
||||||
def_child_action,
|
def_child_action,
|
||||||
@@ -683,19 +672,18 @@ pub fn prepare_argv(argv: Vec<Tk>) -> ShResult<Vec<(String, Span)>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_fork<C, P>(
|
pub fn run_fork<C, P>(
|
||||||
io_frame: IoFrame,
|
|
||||||
exec_args: Option<ExecArgs>,
|
exec_args: Option<ExecArgs>,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
child_action: C,
|
child_action: C,
|
||||||
parent_action: P,
|
parent_action: P,
|
||||||
) -> ShResult<()>
|
) -> ShResult<()>
|
||||||
where
|
where
|
||||||
C: Fn(IoFrame, Option<ExecArgs>),
|
C: Fn(Option<ExecArgs>),
|
||||||
P: Fn(&mut JobBldr, Option<&str>, Pid) -> ShResult<()>,
|
P: Fn(&mut JobBldr, Option<&str>, Pid) -> ShResult<()>,
|
||||||
{
|
{
|
||||||
match unsafe { fork()? } {
|
match unsafe { fork()? } {
|
||||||
ForkResult::Child => {
|
ForkResult::Child => {
|
||||||
child_action(io_frame, exec_args);
|
child_action(exec_args);
|
||||||
exit(0); // Just in case
|
exit(0); // Just in case
|
||||||
}
|
}
|
||||||
ForkResult::Parent { child } => {
|
ForkResult::Parent { child } => {
|
||||||
@@ -710,10 +698,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The default behavior for the child process after forking
|
/// The default behavior for the child process after forking
|
||||||
pub fn def_child_action(mut io_frame: IoFrame, exec_args: Option<ExecArgs>) {
|
pub fn def_child_action(exec_args: Option<ExecArgs>) {
|
||||||
if let Err(e) = io_frame.redirect() {
|
|
||||||
eprintln!("{e}");
|
|
||||||
}
|
|
||||||
let exec_args = exec_args.unwrap();
|
let exec_args = exec_args.unwrap();
|
||||||
let cmd = &exec_args.cmd.0;
|
let cmd = &exec_args.cmd.0;
|
||||||
let span = exec_args.cmd.1;
|
let span = exec_args.cmd.1;
|
||||||
|
|||||||
@@ -1150,6 +1150,7 @@ impl ParseStream {
|
|||||||
|
|
||||||
let cond_node: CondNode;
|
let cond_node: CondNode;
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
|
let mut redirs = vec![];
|
||||||
|
|
||||||
if (!self.check_keyword("while") && !self.check_keyword("until")) || !self.next_tk_is_some() {
|
if (!self.check_keyword("while") && !self.check_keyword("until")) || !self.next_tk_is_some() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -1204,6 +1205,9 @@ impl ParseStream {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
self.parse_redir(&mut redirs, &mut node_tks)?;
|
||||||
|
|
||||||
self.assert_separator(&mut node_tks)?;
|
self.assert_separator(&mut node_tks)?;
|
||||||
|
|
||||||
cond_node = CondNode {
|
cond_node = CondNode {
|
||||||
@@ -1216,7 +1220,7 @@ impl ParseStream {
|
|||||||
cond_node,
|
cond_node,
|
||||||
},
|
},
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs: vec![],
|
redirs,
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
};
|
};
|
||||||
Ok(Some(loop_node))
|
Ok(Some(loop_node))
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::{
|
expand::Expander, libsh::{
|
||||||
error::{ShErr, ShErrKind, ShResult},
|
error::{ShErr, ShErrKind, ShResult},
|
||||||
utils::RedirVecUtils,
|
utils::RedirVecUtils,
|
||||||
},
|
}, parse::{Redir, RedirType, get_redir_file}, prelude::*
|
||||||
parse::{get_redir_file, Redir, RedirType},
|
|
||||||
prelude::*,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Credit to fish-shell for many of the implementation ideas present in this
|
// Credit to fish-shell for many of the implementation ideas present in this
|
||||||
@@ -72,7 +70,19 @@ impl IoMode {
|
|||||||
}
|
}
|
||||||
pub fn open_file(mut self) -> ShResult<Self> {
|
pub fn open_file(mut self) -> ShResult<Self> {
|
||||||
if let IoMode::File { tgt_fd, path, mode } = self {
|
if let IoMode::File { tgt_fd, path, mode } = self {
|
||||||
let file = get_redir_file(mode, path)?;
|
let path_raw = path
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let expanded_path = Expander::from_raw(&path_raw)?
|
||||||
|
.expand()?
|
||||||
|
.join(" "); // should just be one string, will have to find some way to handle a return of multiple
|
||||||
|
|
||||||
|
let expanded_pathbuf = PathBuf::from(expanded_path);
|
||||||
|
|
||||||
|
let file = get_redir_file(mode, expanded_pathbuf)?;
|
||||||
self = IoMode::OpenedFile {
|
self = IoMode::OpenedFile {
|
||||||
tgt_fd,
|
tgt_fd,
|
||||||
file: Arc::new(OwnedFd::from(file)),
|
file: Arc::new(OwnedFd::from(file)),
|
||||||
@@ -145,6 +155,13 @@ impl<R: Read> IoBuf<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RedirGuard(IoFrame);
|
||||||
|
impl Drop for RedirGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.restore().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and
|
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and
|
||||||
/// `stderr` respectively
|
/// `stderr` respectively
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -199,7 +216,7 @@ impl<'e> IoFrame {
|
|||||||
let saved_err = dup(STDERR_FILENO).unwrap();
|
let saved_err = dup(STDERR_FILENO).unwrap();
|
||||||
self.saved_io = Some(IoGroup(saved_in, saved_out, saved_err));
|
self.saved_io = Some(IoGroup(saved_in, saved_out, saved_err));
|
||||||
}
|
}
|
||||||
pub fn redirect(&mut self) -> ShResult<()> {
|
pub fn redirect(mut self) -> ShResult<RedirGuard> {
|
||||||
self.save();
|
self.save();
|
||||||
for redir in &mut self.redirs {
|
for redir in &mut self.redirs {
|
||||||
let io_mode = &mut redir.io_mode;
|
let io_mode = &mut redir.io_mode;
|
||||||
@@ -212,7 +229,7 @@ impl<'e> IoFrame {
|
|||||||
let src_fd = io_mode.src_fd();
|
let src_fd = io_mode.src_fd();
|
||||||
dup2(src_fd, tgt_fd)?;
|
dup2(src_fd, tgt_fd)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(RedirGuard(self))
|
||||||
}
|
}
|
||||||
pub fn restore(&mut self) -> ShResult<()> {
|
pub fn restore(&mut self) -> ShResult<()> {
|
||||||
if let Some(saved) = self.saved_io.take() {
|
if let Some(saved) = self.saved_io.take() {
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ use nix::{
|
|||||||
errno::Errno,
|
errno::Errno,
|
||||||
libc::{self, STDIN_FILENO},
|
libc::{self, STDIN_FILENO},
|
||||||
poll::{self, PollFlags, PollTimeout},
|
poll::{self, PollFlags, PollTimeout},
|
||||||
sys::termios,
|
sys::termios::{self, tcgetattr, tcsetattr},
|
||||||
unistd::isatty,
|
unistd::isatty,
|
||||||
};
|
};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
use vte::{Parser, Perform};
|
use vte::{Parser, Perform};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, procio::borrow_fd};
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||||
prompt::readline::keys::{KeyCode, ModKeys},
|
prompt::readline::keys::{KeyCode, ModKeys},
|
||||||
@@ -282,6 +282,18 @@ impl RawModeGuard {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_cooked_mode<F, R>(f: F) -> R
|
||||||
|
where F: FnOnce() -> R {
|
||||||
|
let raw = tcgetattr(borrow_fd(STDIN_FILENO)).expect("Failed to get terminal attributes");
|
||||||
|
let mut cooked = raw.clone();
|
||||||
|
cooked.local_flags |= termios::LocalFlags::ICANON | termios::LocalFlags::ECHO;
|
||||||
|
cooked.input_flags |= termios::InputFlags::ICRNL;
|
||||||
|
tcsetattr(borrow_fd(STDIN_FILENO), termios::SetArg::TCSANOW, &cooked).expect("Failed to set cooked mode");
|
||||||
|
let res = f();
|
||||||
|
tcsetattr(borrow_fd(STDIN_FILENO), termios::SetArg::TCSANOW, &raw).expect("Failed to restore raw mode");
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RawModeGuard {
|
impl Drop for RawModeGuard {
|
||||||
|
|||||||
Reference in New Issue
Block a user