rustfmt'd the codebase
This commit is contained in:
@@ -18,7 +18,9 @@ pub fn alias(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the environment variables
|
||||
@@ -37,11 +39,19 @@ pub fn alias(node: Node) -> ShResult<()> {
|
||||
} else {
|
||||
for (arg, span) in argv {
|
||||
if arg == "command" || arg == "builtin" {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("alias: Cannot assign alias to reserved name '{arg}'")));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
format!("alias: Cannot assign alias to reserved name '{arg}'"),
|
||||
));
|
||||
}
|
||||
|
||||
let Some((name, body)) = arg.split_once('=') else {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "alias: Expected an assignment in alias args"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
span,
|
||||
"alias: Expected an assignment in alias args",
|
||||
));
|
||||
};
|
||||
write_logic(|l| l.insert_alias(name, body, span.clone()));
|
||||
}
|
||||
@@ -60,7 +70,9 @@ pub fn unalias(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the environment variables
|
||||
@@ -79,7 +91,11 @@ pub fn unalias(node: Node) -> ShResult<()> {
|
||||
} else {
|
||||
for (arg, span) in argv {
|
||||
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("unalias: alias '{}' not found",arg.fg(next_color()))));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
span,
|
||||
format!("unalias: alias '{}' not found", arg.fg(next_color())),
|
||||
));
|
||||
};
|
||||
write_logic(|l| l.remove_alias(&arg))
|
||||
}
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, prelude::*, procio::borrow_fd, state::{self, VarFlags, VarKind, write_vars}
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||
parse::{NdRule, Node, execute::prepare_argv},
|
||||
prelude::*,
|
||||
procio::borrow_fd,
|
||||
state::{self, VarFlags, VarKind, write_vars},
|
||||
};
|
||||
|
||||
fn arr_op_optspec() -> Vec<OptSpec> {
|
||||
vec![
|
||||
OptSpec {
|
||||
opt: Opt::Short('c'),
|
||||
takes_arg: true
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('r'),
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('v'),
|
||||
takes_arg: true
|
||||
}
|
||||
]
|
||||
vec![
|
||||
OptSpec {
|
||||
opt: Opt::Short('c'),
|
||||
takes_arg: true,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('r'),
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('v'),
|
||||
takes_arg: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub struct ArrOpOpts {
|
||||
count: usize,
|
||||
reverse: bool,
|
||||
var: Option<String>,
|
||||
count: usize,
|
||||
reverse: bool,
|
||||
var: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ArrOpOpts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
count: 1,
|
||||
reverse: false,
|
||||
var: None,
|
||||
}
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
count: 1,
|
||||
reverse: false,
|
||||
var: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum End { Front, Back }
|
||||
enum End {
|
||||
Front,
|
||||
Back,
|
||||
}
|
||||
|
||||
fn arr_pop_inner(node: Node, end: End) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
@@ -49,40 +57,42 @@ fn arr_pop_inner(node: Node, end: End) -> ShResult<()> {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
let mut status = 0;
|
||||
let mut status = 0;
|
||||
|
||||
for (arg,_) in argv {
|
||||
for _ in 0..arr_op_opts.count {
|
||||
let pop = |arr: &mut std::collections::VecDeque<String>| match end {
|
||||
End::Front => arr.pop_front(),
|
||||
End::Back => arr.pop_back(),
|
||||
};
|
||||
let Some(popped) = write_vars(|v| v.get_arr_mut(&arg).ok().and_then(pop)) else {
|
||||
status = 1;
|
||||
break;
|
||||
};
|
||||
status = 0;
|
||||
for (arg, _) in argv {
|
||||
for _ in 0..arr_op_opts.count {
|
||||
let pop = |arr: &mut std::collections::VecDeque<String>| match end {
|
||||
End::Front => arr.pop_front(),
|
||||
End::Back => arr.pop_back(),
|
||||
};
|
||||
let Some(popped) = write_vars(|v| v.get_arr_mut(&arg).ok().and_then(pop)) else {
|
||||
status = 1;
|
||||
break;
|
||||
};
|
||||
status = 0;
|
||||
|
||||
if let Some(ref var) = arr_op_opts.var {
|
||||
write_vars(|v| v.set_var(var, VarKind::Str(popped), VarFlags::NONE))?;
|
||||
} else {
|
||||
write(stdout, popped.as_bytes())?;
|
||||
write(stdout, b"\n")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ref var) = arr_op_opts.var {
|
||||
write_vars(|v| v.set_var(var, VarKind::Str(popped), VarFlags::NONE))?;
|
||||
} else {
|
||||
write(stdout, popped.as_bytes())?;
|
||||
write(stdout, b"\n")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state::set_status(status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
|
||||
let blame = node.get_span().clone();
|
||||
let blame = node.get_span().clone();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
@@ -91,49 +101,60 @@ fn arr_push_inner(node: Node, end: End) -> ShResult<()> {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||
let _arr_op_opts = get_arr_op_opts(opts)?;
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||
let _arr_op_opts = get_arr_op_opts(opts)?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let mut argv = argv.into_iter();
|
||||
let Some((name, _)) = argv.next() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, blame, "push: missing array name".to_string()));
|
||||
};
|
||||
let mut argv = argv.into_iter();
|
||||
let Some((name, _)) = argv.next() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
"push: missing array name".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
for (val, span) in argv {
|
||||
let push_val = val.clone();
|
||||
write_vars(|v| {
|
||||
if let Ok(arr) = v.get_arr_mut(&name) {
|
||||
match end {
|
||||
End::Front => arr.push_front(push_val),
|
||||
End::Back => arr.push_back(push_val),
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
v.set_var(&name, VarKind::Arr(VecDeque::from([push_val])), VarFlags::NONE)
|
||||
}
|
||||
}).blame(span)?;
|
||||
}
|
||||
for (val, span) in argv {
|
||||
let push_val = val.clone();
|
||||
write_vars(|v| {
|
||||
if let Ok(arr) = v.get_arr_mut(&name) {
|
||||
match end {
|
||||
End::Front => arr.push_front(push_val),
|
||||
End::Back => arr.push_back(push_val),
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
v.set_var(
|
||||
&name,
|
||||
VarKind::Arr(VecDeque::from([push_val])),
|
||||
VarFlags::NONE,
|
||||
)
|
||||
}
|
||||
})
|
||||
.blame(span)?;
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn arr_pop(node: Node) -> ShResult<()> {
|
||||
arr_pop_inner(node, End::Back)
|
||||
arr_pop_inner(node, End::Back)
|
||||
}
|
||||
|
||||
pub fn arr_fpop(node: Node) -> ShResult<()> {
|
||||
arr_pop_inner(node, End::Front)
|
||||
arr_pop_inner(node, End::Front)
|
||||
}
|
||||
|
||||
pub fn arr_push(node: Node) -> ShResult<()> {
|
||||
arr_push_inner(node, End::Back)
|
||||
arr_push_inner(node, End::Back)
|
||||
}
|
||||
|
||||
pub fn arr_fpush(node: Node) -> ShResult<()> {
|
||||
arr_push_inner(node, End::Front)
|
||||
arr_push_inner(node, End::Front)
|
||||
}
|
||||
|
||||
pub fn arr_rotate(node: Node) -> ShResult<()> {
|
||||
@@ -145,52 +166,63 @@ pub fn arr_rotate(node: Node) -> ShResult<()> {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &arr_op_optspec())?;
|
||||
let arr_op_opts = get_arr_op_opts(opts)?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
for (arg, _) in argv {
|
||||
write_vars(|v| -> ShResult<()> {
|
||||
let arr = v.get_arr_mut(&arg)?;
|
||||
if arr_op_opts.reverse {
|
||||
arr.rotate_right(arr_op_opts.count.min(arr.len()));
|
||||
} else {
|
||||
arr.rotate_left(arr_op_opts.count.min(arr.len()));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
for (arg, _) in argv {
|
||||
write_vars(|v| -> ShResult<()> {
|
||||
let arr = v.get_arr_mut(&arg)?;
|
||||
if arr_op_opts.reverse {
|
||||
arr.rotate_right(arr_op_opts.count.min(arr.len()));
|
||||
} else {
|
||||
arr.rotate_left(arr_op_opts.count.min(arr.len()));
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_arr_op_opts(opts: Vec<Opt>) -> ShResult<ArrOpOpts> {
|
||||
let mut arr_op_opts = ArrOpOpts::default();
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::ShortWithArg('c', count) => {
|
||||
arr_op_opts.count = count.parse::<usize>().map_err(|_| {
|
||||
ShErr::simple(ShErrKind::ParseErr, format!("invalid count: {}", count))
|
||||
})?;
|
||||
}
|
||||
Opt::Short('c') => {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "missing count for -c".to_string()));
|
||||
}
|
||||
Opt::Short('r') => {
|
||||
arr_op_opts.reverse = true;
|
||||
}
|
||||
Opt::ShortWithArg('v', var) => {
|
||||
arr_op_opts.var = Some(var);
|
||||
}
|
||||
Opt::Short('v') => {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, "missing variable name for -v".to_string()));
|
||||
}
|
||||
_ => {
|
||||
return Err(ShErr::simple(ShErrKind::ParseErr, format!("invalid option: {}", opt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(arr_op_opts)
|
||||
let mut arr_op_opts = ArrOpOpts::default();
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::ShortWithArg('c', count) => {
|
||||
arr_op_opts.count = count
|
||||
.parse::<usize>()
|
||||
.map_err(|_| ShErr::simple(ShErrKind::ParseErr, format!("invalid count: {}", count)))?;
|
||||
}
|
||||
Opt::Short('c') => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ParseErr,
|
||||
"missing count for -c".to_string(),
|
||||
));
|
||||
}
|
||||
Opt::Short('r') => {
|
||||
arr_op_opts.reverse = true;
|
||||
}
|
||||
Opt::ShortWithArg('v', var) => {
|
||||
arr_op_opts.var = Some(var);
|
||||
}
|
||||
Opt::Short('v') => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ParseErr,
|
||||
"missing variable name for -v".to_string(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ParseErr,
|
||||
format!("invalid option: {}", opt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(arr_op_opts)
|
||||
}
|
||||
|
||||
@@ -1,48 +1,56 @@
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, state::{self, AutoCmd, AutoCmdKind, write_logic}
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||
parse::{NdRule, Node, execute::prepare_argv},
|
||||
state::{self, AutoCmd, AutoCmdKind, write_logic},
|
||||
};
|
||||
|
||||
pub struct AutoCmdOpts {
|
||||
pattern: Option<Regex>,
|
||||
clear: bool
|
||||
pattern: Option<Regex>,
|
||||
clear: bool,
|
||||
}
|
||||
fn autocmd_optspec() -> [OptSpec;2] {
|
||||
[
|
||||
OptSpec {
|
||||
opt: Opt::Short('p'),
|
||||
takes_arg: true
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('c'),
|
||||
takes_arg: false
|
||||
}
|
||||
]
|
||||
fn autocmd_optspec() -> [OptSpec; 2] {
|
||||
[
|
||||
OptSpec {
|
||||
opt: Opt::Short('p'),
|
||||
takes_arg: true,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('c'),
|
||||
takes_arg: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_autocmd_opts(opts: &[Opt]) -> ShResult<AutoCmdOpts> {
|
||||
let mut autocmd_opts = AutoCmdOpts {
|
||||
pattern: None,
|
||||
clear: false
|
||||
};
|
||||
let mut autocmd_opts = AutoCmdOpts {
|
||||
pattern: None,
|
||||
clear: false,
|
||||
};
|
||||
|
||||
let mut opts = opts.iter();
|
||||
while let Some(arg) = opts.next() {
|
||||
match arg {
|
||||
Opt::ShortWithArg('p', arg) => {
|
||||
autocmd_opts.pattern = Some(Regex::new(arg).map_err(|e| ShErr::simple(ShErrKind::ExecFail, format!("invalid regex for -p: {}", e)))?);
|
||||
}
|
||||
Opt::Short('c') => {
|
||||
autocmd_opts.clear = true;
|
||||
}
|
||||
_ => {
|
||||
return Err(ShErr::simple(ShErrKind::ExecFail, format!("unexpected option: {}", arg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut opts = opts.iter();
|
||||
while let Some(arg) = opts.next() {
|
||||
match arg {
|
||||
Opt::ShortWithArg('p', arg) => {
|
||||
autocmd_opts.pattern = Some(Regex::new(arg).map_err(|e| {
|
||||
ShErr::simple(ShErrKind::ExecFail, format!("invalid regex for -p: {}", e))
|
||||
})?);
|
||||
}
|
||||
Opt::Short('c') => {
|
||||
autocmd_opts.clear = true;
|
||||
}
|
||||
_ => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("unexpected option: {}", arg),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(autocmd_opts)
|
||||
Ok(autocmd_opts)
|
||||
}
|
||||
|
||||
pub fn autocmd(node: Node) -> ShResult<()> {
|
||||
@@ -55,36 +63,50 @@ pub fn autocmd(node: Node) -> ShResult<()> {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv,opts) = get_opts_from_tokens(argv, &autocmd_optspec()).promote_err(span.clone())?;
|
||||
let autocmd_opts = get_autocmd_opts(&opts).promote_err(span.clone())?;
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &autocmd_optspec()).promote_err(span.clone())?;
|
||||
let autocmd_opts = get_autocmd_opts(&opts).promote_err(span.clone())?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
let mut args = argv.iter();
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
let mut args = argv.iter();
|
||||
|
||||
let Some(autocmd_kind) = args.next() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, "expected an autocmd kind".to_string()));
|
||||
};
|
||||
let Some(autocmd_kind) = args.next() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"expected an autocmd kind".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let Ok(autocmd_kind) = autocmd_kind.0.parse::<AutoCmdKind>() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, autocmd_kind.1.clone(), format!("invalid autocmd kind: {}", autocmd_kind.0)));
|
||||
};
|
||||
let Ok(autocmd_kind) = autocmd_kind.0.parse::<AutoCmdKind>() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
autocmd_kind.1.clone(),
|
||||
format!("invalid autocmd kind: {}", autocmd_kind.0),
|
||||
));
|
||||
};
|
||||
|
||||
if autocmd_opts.clear {
|
||||
write_logic(|l| l.clear_autocmds(autocmd_kind));
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
if autocmd_opts.clear {
|
||||
write_logic(|l| l.clear_autocmds(autocmd_kind));
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(autocmd_cmd) = args.next() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, "expected an autocmd command".to_string()));
|
||||
};
|
||||
let Some(autocmd_cmd) = args.next() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"expected an autocmd command".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let autocmd = AutoCmd {
|
||||
pattern: autocmd_opts.pattern,
|
||||
command: autocmd_cmd.0.clone(),
|
||||
};
|
||||
let autocmd = AutoCmd {
|
||||
pattern: autocmd_opts.pattern,
|
||||
command: autocmd_cmd.0.clone(),
|
||||
};
|
||||
|
||||
write_logic(|l| l.insert_autocmd(autocmd_kind, autocmd));
|
||||
write_logic(|l| l.insert_autocmd(autocmd_kind, autocmd));
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
|
||||
@@ -17,40 +17,58 @@ pub fn cd(node: Node) -> ShResult<()> {
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let cd_span = argv.first().unwrap().span.clone();
|
||||
let cd_span = argv.first().unwrap().span.clone();
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let (new_dir,arg_span) = if let Some((arg, span)) = argv.into_iter().next() {
|
||||
(PathBuf::from(arg),Some(span))
|
||||
let (new_dir, arg_span) = if let Some((arg, span)) = argv.into_iter().next() {
|
||||
(PathBuf::from(arg), Some(span))
|
||||
} else {
|
||||
(PathBuf::from(env::var("HOME").unwrap()),None)
|
||||
(PathBuf::from(env::var("HOME").unwrap()), None)
|
||||
};
|
||||
|
||||
if !new_dir.exists() {
|
||||
let mut err = ShErr::new(
|
||||
ShErrKind::ExecFail,
|
||||
span.clone(),
|
||||
).labeled(cd_span.clone(), "Failed to change directory");
|
||||
if let Some(span) = arg_span {
|
||||
err = err.labeled(span, format!("No such file or directory '{}'", new_dir.display().fg(next_color())));
|
||||
}
|
||||
return Err(err);
|
||||
let mut err = ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||
.labeled(cd_span.clone(), "Failed to change directory");
|
||||
if let Some(span) = arg_span {
|
||||
err = err.labeled(
|
||||
span,
|
||||
format!(
|
||||
"No such file or directory '{}'",
|
||||
new_dir.display().fg(next_color())
|
||||
),
|
||||
);
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if !new_dir.is_dir() {
|
||||
return Err(ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||
.labeled(cd_span.clone(), format!("cd: Not a directory '{}'", new_dir.display().fg(next_color()))));
|
||||
return Err(ShErr::new(ShErrKind::ExecFail, span.clone()).labeled(
|
||||
cd_span.clone(),
|
||||
format!(
|
||||
"cd: Not a directory '{}'",
|
||||
new_dir.display().fg(next_color())
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
if let Err(e) = state::change_dir(new_dir) {
|
||||
return Err(ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||
.labeled(cd_span.clone(), format!("cd: Failed to change directory: '{}'", e.fg(Color::Red))));
|
||||
return Err(ShErr::new(ShErrKind::ExecFail, span.clone()).labeled(
|
||||
cd_span.clone(),
|
||||
format!("cd: Failed to change directory: '{}'", e.fg(Color::Red)),
|
||||
));
|
||||
}
|
||||
let new_dir = env::current_dir().map_err(|e| {
|
||||
ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||
.labeled(cd_span.clone(), format!("cd: Failed to get current directory: '{}'", e.fg(Color::Red)))
|
||||
ShErr::new(ShErrKind::ExecFail, span.clone()).labeled(
|
||||
cd_span.clone(),
|
||||
format!(
|
||||
"cd: Failed to get current directory: '{}'",
|
||||
e.fg(Color::Red)
|
||||
),
|
||||
)
|
||||
})?;
|
||||
unsafe { env::set_var("PWD", new_dir) };
|
||||
|
||||
|
||||
@@ -167,7 +167,9 @@ pub fn complete_builtin(node: Node) -> ShResult<()> {
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?;
|
||||
let comp_opts = get_comp_opts(opts)?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
if comp_opts.flags.contains(CompFlags::PRINT) {
|
||||
if argv.is_empty() {
|
||||
@@ -204,7 +206,11 @@ pub fn complete_builtin(node: Node) -> ShResult<()> {
|
||||
|
||||
if argv.is_empty() {
|
||||
state::set_status(1);
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, blame, "complete: no command specified"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
"complete: no command specified",
|
||||
));
|
||||
}
|
||||
|
||||
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
||||
@@ -279,12 +285,16 @@ pub fn get_comp_opts(opts: Vec<Opt>) -> ShResult<CompOpts> {
|
||||
"space" => comp_opts.opt_flags |= CompOptFlags::SPACE,
|
||||
_ => {
|
||||
let span: crate::parse::lex::Span = Default::default();
|
||||
return Err(ShErr::at(ShErrKind::InvalidOpt, span, format!("complete: invalid option: {}", opt_flag)));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::InvalidOpt,
|
||||
span,
|
||||
format!("complete: invalid option: {}", opt_flag),
|
||||
));
|
||||
}
|
||||
},
|
||||
|
||||
Opt::Short('a') => comp_opts.flags |= CompFlags::ALIAS,
|
||||
Opt::Short('S') => comp_opts.flags |= CompFlags::SIGNALS,
|
||||
Opt::Short('a') => comp_opts.flags |= CompFlags::ALIAS,
|
||||
Opt::Short('S') => comp_opts.flags |= CompFlags::SIGNALS,
|
||||
Opt::Short('r') => comp_opts.flags |= CompFlags::REMOVE,
|
||||
Opt::Short('j') => comp_opts.flags |= CompFlags::JOBS,
|
||||
Opt::Short('p') => comp_opts.flags |= CompFlags::PRINT,
|
||||
|
||||
@@ -47,18 +47,26 @@ fn print_dirs() -> ShResult<()> {
|
||||
|
||||
fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
|
||||
if !target.is_dir() {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("not a directory: '{}'", target.display().fg(next_color())))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("not a directory: '{}'", target.display().fg(next_color())),
|
||||
));
|
||||
}
|
||||
|
||||
if let Err(e) = state::change_dir(target) {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to change directory: '{}'", e.fg(Color::Red)))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("Failed to change directory: '{}'", e.fg(Color::Red)),
|
||||
));
|
||||
}
|
||||
let new_dir = env::current_dir().map_err(|e| {
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to get current directory: '{}'", e.fg(Color::Red)))
|
||||
ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("Failed to get current directory: '{}'", e.fg(Color::Red)),
|
||||
)
|
||||
})?;
|
||||
unsafe { env::set_var("PWD", new_dir) };
|
||||
Ok(())
|
||||
@@ -74,24 +82,32 @@ fn parse_stack_idx(arg: &str, blame: Span, cmd: &str) -> ShResult<StackIdx> {
|
||||
};
|
||||
|
||||
if digits.is_empty() {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!(
|
||||
"{cmd}: missing index after '{}'",
|
||||
if from_top { "+" } else { "-" }
|
||||
))
|
||||
);
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
for ch in digits.chars() {
|
||||
if !ch.is_ascii_digit() {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid argument: '{}'",arg.fg(next_color())))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("{cmd}: invalid argument: '{}'", arg.fg(next_color())),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let n = digits.parse::<usize>().map_err(|e| {
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid index: '{}'",e.fg(next_color())))
|
||||
ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("{cmd}: invalid index: '{}'", e.fg(next_color())),
|
||||
)
|
||||
})?;
|
||||
|
||||
if from_top {
|
||||
@@ -112,7 +128,9 @@ pub fn pushd(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let mut dir = None;
|
||||
let mut rotate_idx = None;
|
||||
@@ -126,20 +144,29 @@ pub fn pushd(node: Node) -> ShResult<()> {
|
||||
} else if arg == "-n" {
|
||||
no_cd = true;
|
||||
} else if arg.starts_with('-') {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: invalid option: '{}'", arg.fg(next_color())))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("pushd: invalid option: '{}'", arg.fg(next_color())),
|
||||
));
|
||||
} else {
|
||||
if dir.is_some() {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, "pushd: too many arguments")
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
"pushd: too many arguments",
|
||||
));
|
||||
}
|
||||
let target = PathBuf::from(&arg);
|
||||
if !target.is_dir() {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: not a directory: '{}'", target.display().fg(next_color())))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!(
|
||||
"pushd: not a directory: '{}'",
|
||||
target.display().fg(next_color())
|
||||
),
|
||||
));
|
||||
}
|
||||
dir = Some(target);
|
||||
}
|
||||
@@ -193,7 +220,9 @@ pub fn popd(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let mut remove_idx = None;
|
||||
let mut no_cd = false;
|
||||
@@ -206,9 +235,11 @@ pub fn popd(node: Node) -> ShResult<()> {
|
||||
} else if arg == "-n" {
|
||||
no_cd = true;
|
||||
} else if arg.starts_with('-') {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("popd: invalid option: '{}'", arg.fg(next_color())))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("popd: invalid option: '{}'", arg.fg(next_color())),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,9 +252,11 @@ pub fn popd(node: Node) -> ShResult<()> {
|
||||
if let Some(dir) = dir {
|
||||
change_directory(&dir, blame.clone())?;
|
||||
} else {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty")
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
"popd: directory stack empty",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,9 +266,11 @@ pub fn popd(node: Node) -> ShResult<()> {
|
||||
let dirs = m.dirs_mut();
|
||||
let idx = n - 1;
|
||||
if idx >= dirs.len() {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: +{n}"))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame.clone(),
|
||||
format!("popd: directory index out of range: +{n}"),
|
||||
));
|
||||
}
|
||||
dirs.remove(idx);
|
||||
Ok(())
|
||||
@@ -245,7 +280,11 @@ pub fn popd(node: Node) -> ShResult<()> {
|
||||
write_meta(|m| -> ShResult<()> {
|
||||
let dirs = m.dirs_mut();
|
||||
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
|
||||
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: -{n}"))
|
||||
ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame.clone(),
|
||||
format!("popd: directory index out of range: -{n}"),
|
||||
)
|
||||
})?;
|
||||
dirs.remove(actual);
|
||||
Ok(())
|
||||
@@ -265,9 +304,11 @@ pub fn popd(node: Node) -> ShResult<()> {
|
||||
change_directory(&dir, blame.clone())?;
|
||||
print_dirs()?;
|
||||
} else {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty")
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
"popd: directory stack empty",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +326,9 @@ pub fn dirs(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let mut abbreviate_home = true;
|
||||
let mut one_per_line = false;
|
||||
@@ -306,14 +349,18 @@ pub fn dirs(node: Node) -> ShResult<()> {
|
||||
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
|
||||
}
|
||||
_ if arg.starts_with('-') => {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: invalid option: '{}'", arg.fg(next_color())))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("dirs: invalid option: '{}'", arg.fg(next_color())),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: unexpected argument: '{}'", arg.fg(next_color())))
|
||||
);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!("dirs: unexpected argument: '{}'", arg.fg(next_color())),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,15 +405,17 @@ pub fn dirs(node: Node) -> ShResult<()> {
|
||||
if let Some(dir) = target {
|
||||
dirs = vec![dir.clone()];
|
||||
} else {
|
||||
return Err(
|
||||
ShErr::at(ShErrKind::ExecFail, blame, format!(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
format!(
|
||||
"dirs: directory index out of range: {}",
|
||||
match idx {
|
||||
StackIdx::FromTop(n) => format!("+{n}"),
|
||||
StackIdx::FromBottom(n) => format!("-{n}"),
|
||||
}
|
||||
))
|
||||
);
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,9 @@ pub fn echo(node: Node) -> ShResult<()> {
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &ECHO_OPTS)?;
|
||||
let flags = get_echo_flags(opts).blame(blame)?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let output_channel = if flags.contains(EchoFlags::USE_STDERR) {
|
||||
borrow_fd(STDERR_FILENO)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::{
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node, execute::{exec_input, prepare_argv}},
|
||||
parse::{
|
||||
NdRule, Node,
|
||||
execute::{exec_input, prepare_argv},
|
||||
},
|
||||
state,
|
||||
};
|
||||
|
||||
@@ -14,7 +17,9 @@ pub fn eval(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut expanded_argv = prepare_argv(argv)?;
|
||||
if !expanded_argv.is_empty() { expanded_argv.remove(0); }
|
||||
if !expanded_argv.is_empty() {
|
||||
expanded_argv.remove(0);
|
||||
}
|
||||
|
||||
if expanded_argv.is_empty() {
|
||||
state::set_status(0);
|
||||
|
||||
@@ -2,7 +2,10 @@ use nix::{errno::Errno, unistd::execvpe};
|
||||
|
||||
use crate::{
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{NdRule, Node, execute::{ExecArgs, prepare_argv}},
|
||||
parse::{
|
||||
NdRule, Node,
|
||||
execute::{ExecArgs, prepare_argv},
|
||||
},
|
||||
state,
|
||||
};
|
||||
|
||||
@@ -16,7 +19,9 @@ pub fn exec_builtin(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut expanded_argv = prepare_argv(argv)?;
|
||||
if !expanded_argv.is_empty() { expanded_argv.remove(0); }
|
||||
if !expanded_argv.is_empty() {
|
||||
expanded_argv.remove(0);
|
||||
}
|
||||
|
||||
if expanded_argv.is_empty() {
|
||||
state::set_status(0);
|
||||
@@ -34,9 +39,9 @@ pub fn exec_builtin(node: Node) -> ShResult<()> {
|
||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||
match e {
|
||||
Errno::ENOENT => Err(
|
||||
ShErr::new(ShErrKind::NotFound, span.clone())
|
||||
.labeled(span, format!("exec: command not found: {}", cmd_str))
|
||||
),
|
||||
ShErr::new(ShErrKind::NotFound, span.clone())
|
||||
.labeled(span, format!("exec: command not found: {}", cmd_str)),
|
||||
),
|
||||
_ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,11 @@ pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
|
||||
let (arg, span) = argv.into_iter().next().unwrap();
|
||||
|
||||
let Ok(status) = arg.parse::<i32>() else {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("{cmd}: Expected a number")));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
span,
|
||||
format!("{cmd}: Expected a number"),
|
||||
));
|
||||
};
|
||||
|
||||
code = status;
|
||||
|
||||
@@ -3,229 +3,251 @@ use std::str::FromStr;
|
||||
use ariadne::Fmt;
|
||||
|
||||
use crate::{
|
||||
getopt::{Opt, OptSpec}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color}, parse::{NdRule, Node, execute::prepare_argv, lex::Span}, state::{self, VarFlags, VarKind, read_meta, read_vars, write_meta, write_vars}
|
||||
getopt::{Opt, OptSpec},
|
||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
|
||||
parse::{NdRule, Node, execute::prepare_argv, lex::Span},
|
||||
state::{self, VarFlags, VarKind, read_meta, read_vars, write_meta, write_vars},
|
||||
};
|
||||
|
||||
enum OptMatch {
|
||||
NoMatch,
|
||||
IsMatch,
|
||||
WantsArg
|
||||
NoMatch,
|
||||
IsMatch,
|
||||
WantsArg,
|
||||
}
|
||||
|
||||
struct GetOptsSpec {
|
||||
silent_err: bool,
|
||||
opt_specs: Vec<OptSpec>
|
||||
silent_err: bool,
|
||||
opt_specs: Vec<OptSpec>,
|
||||
}
|
||||
|
||||
impl GetOptsSpec {
|
||||
pub fn matches(&self, ch: char) -> OptMatch {
|
||||
for spec in &self.opt_specs {
|
||||
let OptSpec { opt, takes_arg } = spec;
|
||||
match opt {
|
||||
Opt::Short(opt_ch) if ch == *opt_ch => {
|
||||
if *takes_arg {
|
||||
return OptMatch::WantsArg
|
||||
} else {
|
||||
return OptMatch::IsMatch
|
||||
}
|
||||
}
|
||||
_ => { continue }
|
||||
}
|
||||
}
|
||||
OptMatch::NoMatch
|
||||
}
|
||||
pub fn matches(&self, ch: char) -> OptMatch {
|
||||
for spec in &self.opt_specs {
|
||||
let OptSpec { opt, takes_arg } = spec;
|
||||
match opt {
|
||||
Opt::Short(opt_ch) if ch == *opt_ch => {
|
||||
if *takes_arg {
|
||||
return OptMatch::WantsArg;
|
||||
} else {
|
||||
return OptMatch::IsMatch;
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
OptMatch::NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GetOptsSpec {
|
||||
type Err = ShErr;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut s = s;
|
||||
let mut opt_specs = vec![];
|
||||
let mut silent_err = false;
|
||||
if s.starts_with(':') {
|
||||
silent_err = true;
|
||||
s = &s[1..];
|
||||
}
|
||||
type Err = ShErr;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut s = s;
|
||||
let mut opt_specs = vec![];
|
||||
let mut silent_err = false;
|
||||
if s.starts_with(':') {
|
||||
silent_err = true;
|
||||
s = &s[1..];
|
||||
}
|
||||
|
||||
let mut chars = s.chars().peekable();
|
||||
while let Some(ch) = chars.peek() {
|
||||
match ch {
|
||||
ch if ch.is_alphanumeric() => {
|
||||
let opt = Opt::Short(*ch);
|
||||
chars.next();
|
||||
let takes_arg = chars.peek() == Some(&':');
|
||||
if takes_arg {
|
||||
chars.next();
|
||||
}
|
||||
opt_specs.push(OptSpec { opt, takes_arg })
|
||||
}
|
||||
_ => return Err(ShErr::simple(
|
||||
ShErrKind::ParseErr,
|
||||
format!("unexpected character '{}'", ch.fg(next_color()))
|
||||
)),
|
||||
}
|
||||
}
|
||||
let mut chars = s.chars().peekable();
|
||||
while let Some(ch) = chars.peek() {
|
||||
match ch {
|
||||
ch if ch.is_alphanumeric() => {
|
||||
let opt = Opt::Short(*ch);
|
||||
chars.next();
|
||||
let takes_arg = chars.peek() == Some(&':');
|
||||
if takes_arg {
|
||||
chars.next();
|
||||
}
|
||||
opt_specs.push(OptSpec { opt, takes_arg })
|
||||
}
|
||||
_ => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ParseErr,
|
||||
format!("unexpected character '{}'", ch.fg(next_color())),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GetOptsSpec { silent_err, opt_specs })
|
||||
}
|
||||
Ok(GetOptsSpec {
|
||||
silent_err,
|
||||
opt_specs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_optind(opt_index: usize, amount: usize) -> ShResult<()> {
|
||||
write_vars(|v| v.set_var("OPTIND", VarKind::Str((opt_index + amount).to_string()), VarFlags::NONE))
|
||||
write_vars(|v| {
|
||||
v.set_var(
|
||||
"OPTIND",
|
||||
VarKind::Str((opt_index + amount).to_string()),
|
||||
VarFlags::NONE,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn getopts_inner(opts_spec: &GetOptsSpec, opt_var: &str, argv: &[String], blame: Span) -> ShResult<()> {
|
||||
let opt_index = read_vars(|v| v.get_var("OPTIND").parse::<usize>().unwrap_or(1));
|
||||
// OPTIND is 1-based
|
||||
let arr_idx = opt_index.saturating_sub(1);
|
||||
fn getopts_inner(
|
||||
opts_spec: &GetOptsSpec,
|
||||
opt_var: &str,
|
||||
argv: &[String],
|
||||
blame: Span,
|
||||
) -> ShResult<()> {
|
||||
let opt_index = read_vars(|v| v.get_var("OPTIND").parse::<usize>().unwrap_or(1));
|
||||
// OPTIND is 1-based
|
||||
let arr_idx = opt_index.saturating_sub(1);
|
||||
|
||||
let Some(arg) = argv.get(arr_idx) else {
|
||||
state::set_status(1);
|
||||
return Ok(())
|
||||
};
|
||||
let Some(arg) = argv.get(arr_idx) else {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// "--" stops option processing
|
||||
if arg.as_str() == "--" {
|
||||
advance_optind(opt_index, 1)?;
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
state::set_status(1);
|
||||
return Ok(())
|
||||
}
|
||||
// "--" stops option processing
|
||||
if arg.as_str() == "--" {
|
||||
advance_optind(opt_index, 1)?;
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Not an option — done
|
||||
let Some(opt_str) = arg.strip_prefix('-') else {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
};
|
||||
// Not an option — done
|
||||
let Some(opt_str) = arg.strip_prefix('-') else {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Bare "-" is not an option
|
||||
if opt_str.is_empty() {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
// Bare "-" is not an option
|
||||
if opt_str.is_empty() {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let char_idx = read_meta(|m| m.getopts_char_offset());
|
||||
let Some(ch) = opt_str.chars().nth(char_idx) else {
|
||||
// Ran out of chars in this arg (shouldn't normally happen),
|
||||
// advance to next arg and signal done for this call
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
advance_optind(opt_index, 1)?;
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
};
|
||||
let char_idx = read_meta(|m| m.getopts_char_offset());
|
||||
let Some(ch) = opt_str.chars().nth(char_idx) else {
|
||||
// Ran out of chars in this arg (shouldn't normally happen),
|
||||
// advance to next arg and signal done for this call
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
advance_optind(opt_index, 1)?;
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let last_char_in_arg = char_idx >= opt_str.len() - 1;
|
||||
let last_char_in_arg = char_idx >= opt_str.len() - 1;
|
||||
|
||||
// Advance past this character: either move to next char in this
|
||||
// arg, or reset offset and bump OPTIND to the next arg.
|
||||
let advance_one_char = |last: bool| -> ShResult<()> {
|
||||
if last {
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
advance_optind(opt_index, 1)?;
|
||||
} else {
|
||||
write_meta(|m| m.inc_getopts_char_offset());
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
// Advance past this character: either move to next char in this
|
||||
// arg, or reset offset and bump OPTIND to the next arg.
|
||||
let advance_one_char = |last: bool| -> ShResult<()> {
|
||||
if last {
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
advance_optind(opt_index, 1)?;
|
||||
} else {
|
||||
write_meta(|m| m.inc_getopts_char_offset());
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
match opts_spec.matches(ch) {
|
||||
OptMatch::NoMatch => {
|
||||
advance_one_char(last_char_in_arg)?;
|
||||
if opts_spec.silent_err {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
} else {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||
ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame.clone(),
|
||||
format!("illegal option '-{}'", ch.fg(next_color()))
|
||||
).print_error();
|
||||
}
|
||||
state::set_status(0);
|
||||
}
|
||||
OptMatch::IsMatch => {
|
||||
advance_one_char(last_char_in_arg)?;
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
state::set_status(0);
|
||||
}
|
||||
OptMatch::WantsArg => {
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
match opts_spec.matches(ch) {
|
||||
OptMatch::NoMatch => {
|
||||
advance_one_char(last_char_in_arg)?;
|
||||
if opts_spec.silent_err {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
} else {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||
ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame.clone(),
|
||||
format!("illegal option '-{}'", ch.fg(next_color())),
|
||||
)
|
||||
.print_error();
|
||||
}
|
||||
state::set_status(0);
|
||||
}
|
||||
OptMatch::IsMatch => {
|
||||
advance_one_char(last_char_in_arg)?;
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
state::set_status(0);
|
||||
}
|
||||
OptMatch::WantsArg => {
|
||||
write_meta(|m| m.reset_getopts_char_offset());
|
||||
|
||||
if !last_char_in_arg {
|
||||
// Remaining chars in this arg are the argument: -bVALUE
|
||||
let optarg: String = opt_str.chars().skip(char_idx + 1).collect();
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(optarg), VarFlags::NONE))?;
|
||||
advance_optind(opt_index, 1)?;
|
||||
} else if let Some(next_arg) = argv.get(arr_idx + 1) {
|
||||
// Next arg is the argument
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(next_arg.clone()), VarFlags::NONE))?;
|
||||
// Skip both the option arg and its value
|
||||
advance_optind(opt_index, 2)?;
|
||||
} else {
|
||||
// Missing required argument
|
||||
if opts_spec.silent_err {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(":".into()), VarFlags::NONE))?;
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
} else {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||
ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame.clone(),
|
||||
format!("option '-{}' requires an argument", ch.fg(next_color()))
|
||||
).print_error();
|
||||
}
|
||||
advance_optind(opt_index, 1)?;
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
if !last_char_in_arg {
|
||||
// Remaining chars in this arg are the argument: -bVALUE
|
||||
let optarg: String = opt_str.chars().skip(char_idx + 1).collect();
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(optarg), VarFlags::NONE))?;
|
||||
advance_optind(opt_index, 1)?;
|
||||
} else if let Some(next_arg) = argv.get(arr_idx + 1) {
|
||||
// Next arg is the argument
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(next_arg.clone()), VarFlags::NONE))?;
|
||||
// Skip both the option arg and its value
|
||||
advance_optind(opt_index, 2)?;
|
||||
} else {
|
||||
// Missing required argument
|
||||
if opts_spec.silent_err {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(":".into()), VarFlags::NONE))?;
|
||||
write_vars(|v| v.set_var("OPTARG", VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
} else {
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str("?".into()), VarFlags::NONE))?;
|
||||
ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame.clone(),
|
||||
format!("option '-{}' requires an argument", ch.fg(next_color())),
|
||||
)
|
||||
.print_error();
|
||||
}
|
||||
advance_optind(opt_index, 1)?;
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
state::set_status(0);
|
||||
}
|
||||
}
|
||||
write_vars(|v| v.set_var(opt_var, VarKind::Str(ch.to_string()), VarFlags::NONE))?;
|
||||
state::set_status(0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn getopts(node: Node) -> ShResult<()> {
|
||||
let span = node.get_span().clone();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let span = node.get_span().clone();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
let mut args = argv.into_iter();
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
let mut args = argv.into_iter();
|
||||
|
||||
let Some(arg_string) = args.next() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"getopts: missing option spec"
|
||||
))
|
||||
};
|
||||
let Some(opt_var) = args.next() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"getopts: missing variable name"
|
||||
))
|
||||
};
|
||||
let Some(arg_string) = args.next() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"getopts: missing option spec",
|
||||
));
|
||||
};
|
||||
let Some(opt_var) = args.next() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"getopts: missing variable name",
|
||||
));
|
||||
};
|
||||
|
||||
let opts_spec = GetOptsSpec::from_str(&arg_string.0)
|
||||
.promote_err(arg_string.1.clone())?;
|
||||
let opts_spec = GetOptsSpec::from_str(&arg_string.0).promote_err(arg_string.1.clone())?;
|
||||
|
||||
let explicit_args: Vec<String> = args.map(|s| s.0).collect();
|
||||
let explicit_args: Vec<String> = args.map(|s| s.0).collect();
|
||||
|
||||
if !explicit_args.is_empty() {
|
||||
getopts_inner(&opts_spec, &opt_var.0, &explicit_args, span)
|
||||
} else {
|
||||
let pos_params: Vec<String> = read_vars(|v| v.sh_argv().iter().skip(1).cloned().collect());
|
||||
getopts_inner(&opts_spec, &opt_var.0, &pos_params, span)
|
||||
}
|
||||
if !explicit_args.is_empty() {
|
||||
getopts_inner(&opts_spec, &opt_var.0, &explicit_args, span)
|
||||
} else {
|
||||
let pos_params: Vec<String> = read_vars(|v| v.sh_argv().iter().skip(1).cloned().collect());
|
||||
getopts_inner(&opts_spec, &opt_var.0, &pos_params, span)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ use std::{env, os::unix::fs::PermissionsExt, path::Path};
|
||||
|
||||
use ariadne::{Fmt, Span};
|
||||
|
||||
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind, ShResult, next_color}, parse::{NdRule, Node, execute::prepare_argv, lex::KEYWORDS}, state::{self, ShAlias, ShFunc, read_logic}};
|
||||
use crate::{
|
||||
builtin::BUILTINS,
|
||||
libsh::error::{ShErr, ShErrKind, ShResult, next_color},
|
||||
parse::{NdRule, Node, execute::prepare_argv, lex::KEYWORDS},
|
||||
state::{self, ShAlias, ShFunc, read_logic},
|
||||
};
|
||||
|
||||
pub fn type_builtin(node: Node) -> ShResult<()> {
|
||||
let NdRule::Command {
|
||||
@@ -14,59 +19,75 @@ pub fn type_builtin(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* we have to check in the same order that the dispatcher checks this
|
||||
* 1. function
|
||||
* 2. builtin
|
||||
* 3. command
|
||||
*/
|
||||
/*
|
||||
* we have to check in the same order that the dispatcher checks this
|
||||
* 1. function
|
||||
* 2. builtin
|
||||
* 3. command
|
||||
*/
|
||||
|
||||
'outer: for (arg,span) in argv {
|
||||
if let Some(func) = read_logic(|v| v.get_func(&arg)) {
|
||||
let ShFunc { body: _, source } = func;
|
||||
let (line, col) = source.line_and_col();
|
||||
let name = source.source().name();
|
||||
println!("{arg} is a function defined at {name}:{}:{}", line + 1, col + 1);
|
||||
} else if let Some(alias) = read_logic(|v| v.get_alias(&arg)) {
|
||||
let ShAlias { body, source } = alias;
|
||||
let (line, col) = source.line_and_col();
|
||||
let name = source.source().name();
|
||||
println!("{arg} is an alias for '{body}' defined at {name}:{}:{}", line + 1, col + 1);
|
||||
} else if BUILTINS.contains(&arg.as_str()) {
|
||||
println!("{arg} is a shell builtin");
|
||||
} else if KEYWORDS.contains(&arg.as_str()) {
|
||||
println!("{arg} is a shell keyword");
|
||||
} else {
|
||||
let path = env::var("PATH").unwrap_or_default();
|
||||
let paths = path.split(':')
|
||||
.map(Path::new)
|
||||
.collect::<Vec<_>>();
|
||||
'outer: for (arg, span) in argv {
|
||||
if let Some(func) = read_logic(|v| v.get_func(&arg)) {
|
||||
let ShFunc { body: _, source } = func;
|
||||
let (line, col) = source.line_and_col();
|
||||
let name = source.source().name();
|
||||
println!(
|
||||
"{arg} is a function defined at {name}:{}:{}",
|
||||
line + 1,
|
||||
col + 1
|
||||
);
|
||||
} else if let Some(alias) = read_logic(|v| v.get_alias(&arg)) {
|
||||
let ShAlias { body, source } = alias;
|
||||
let (line, col) = source.line_and_col();
|
||||
let name = source.source().name();
|
||||
println!(
|
||||
"{arg} is an alias for '{body}' defined at {name}:{}:{}",
|
||||
line + 1,
|
||||
col + 1
|
||||
);
|
||||
} else if BUILTINS.contains(&arg.as_str()) {
|
||||
println!("{arg} is a shell builtin");
|
||||
} else if KEYWORDS.contains(&arg.as_str()) {
|
||||
println!("{arg} is a shell keyword");
|
||||
} else {
|
||||
let path = env::var("PATH").unwrap_or_default();
|
||||
let paths = path.split(':').map(Path::new).collect::<Vec<_>>();
|
||||
|
||||
for path in paths {
|
||||
if let Ok(entries) = path.read_dir() {
|
||||
for entry in entries.flatten() {
|
||||
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||
continue;
|
||||
};
|
||||
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
||||
for path in paths {
|
||||
if let Ok(entries) = path.read_dir() {
|
||||
for entry in entries.flatten() {
|
||||
let Ok(meta) = std::fs::metadata(entry.path()) else {
|
||||
continue;
|
||||
};
|
||||
let is_exec = meta.permissions().mode() & 0o111 != 0;
|
||||
|
||||
if meta.is_file()
|
||||
&& is_exec
|
||||
&& let Some(name) = entry.file_name().to_str()
|
||||
&& name == arg {
|
||||
println!("{arg} is {}", entry.path().display());
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if meta.is_file()
|
||||
&& is_exec
|
||||
&& let Some(name) = entry.file_name().to_str()
|
||||
&& name == arg
|
||||
{
|
||||
println!("{arg} is {}", entry.path().display());
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state::set_status(1);
|
||||
return Err(ShErr::at(ShErrKind::NotFound, span, format!("'{}' is not a command, function, or alias", arg.fg(next_color()))));
|
||||
}
|
||||
}
|
||||
state::set_status(1);
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::NotFound,
|
||||
span,
|
||||
format!(
|
||||
"'{}' is not a command, function, or alias",
|
||||
arg.fg(next_color())
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
|
||||
@@ -16,8 +16,8 @@ pub enum JobBehavior {
|
||||
|
||||
pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
|
||||
let blame = node.get_span().clone();
|
||||
let cmd_tk = node.get_command();
|
||||
let cmd_span = cmd_tk.unwrap().span.clone();
|
||||
let cmd_tk = node.get_command();
|
||||
let cmd_span = cmd_tk.unwrap().span.clone();
|
||||
let cmd = match behavior {
|
||||
JobBehavior::Foregound => "fg",
|
||||
JobBehavior::Background => "bg",
|
||||
@@ -31,11 +31,17 @@ pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
let mut argv = argv.into_iter();
|
||||
|
||||
if read_jobs(|j| j.get_fg().is_some()) {
|
||||
return Err(ShErr::at(ShErrKind::InternalErr, cmd_span, format!("Somehow called '{}' with an existing foreground job", cmd)));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::InternalErr,
|
||||
cmd_span,
|
||||
format!("Somehow called '{}' with an existing foreground job", cmd),
|
||||
));
|
||||
}
|
||||
|
||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||
@@ -55,7 +61,11 @@ pub fn continue_job(node: Node, behavior: JobBehavior) -> ShResult<()> {
|
||||
if query_result.is_some() {
|
||||
Ok(j.remove_job(id.clone()).unwrap())
|
||||
} else {
|
||||
Err(ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("Job id `{}' not found", tabid)))
|
||||
Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame.clone(),
|
||||
format!("Job id `{}' not found", tabid),
|
||||
))
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -82,12 +92,16 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
||||
if arg.starts_with('%') {
|
||||
let arg = arg.strip_prefix('%').unwrap();
|
||||
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
let num = arg.parse::<usize>().unwrap_or_default();
|
||||
if num == 0 {
|
||||
Err(ShErr::at(ShErrKind::SyntaxErr, blame, format!("Invalid job id: {}", arg.fg(next_color()))))
|
||||
} else {
|
||||
Ok(num.saturating_sub(1))
|
||||
}
|
||||
let num = arg.parse::<usize>().unwrap_or_default();
|
||||
if num == 0 {
|
||||
Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
blame,
|
||||
format!("Invalid job id: {}", arg.fg(next_color())),
|
||||
))
|
||||
} else {
|
||||
Ok(num.saturating_sub(1))
|
||||
}
|
||||
} else {
|
||||
let result = write_jobs(|j| {
|
||||
let query_result = j.query(JobID::Command(arg.into()));
|
||||
@@ -95,7 +109,11 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
||||
});
|
||||
match result {
|
||||
Some(id) => Ok(id),
|
||||
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")),
|
||||
None => Err(ShErr::at(
|
||||
ShErrKind::InternalErr,
|
||||
blame,
|
||||
"Found a job but no table id in parse_job_id()",
|
||||
)),
|
||||
}
|
||||
}
|
||||
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||
@@ -115,10 +133,18 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
||||
|
||||
match result {
|
||||
Some(id) => Ok(id),
|
||||
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")),
|
||||
None => Err(ShErr::at(
|
||||
ShErrKind::InternalErr,
|
||||
blame,
|
||||
"Found a job but no table id in parse_job_id()",
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(ShErr::at(ShErrKind::SyntaxErr, blame, format!("Invalid arg: {}", arg.fg(next_color()))))
|
||||
Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
blame,
|
||||
format!("Invalid arg: {}", arg.fg(next_color())),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,13 +158,19 @@ pub fn jobs(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let mut flags = JobCmdFlags::empty();
|
||||
for (arg, span) in argv {
|
||||
let mut chars = arg.chars().peekable();
|
||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
span,
|
||||
"Invalid flag in jobs call",
|
||||
));
|
||||
}
|
||||
chars.next();
|
||||
for ch in chars {
|
||||
@@ -149,7 +181,11 @@ pub fn jobs(node: Node) -> ShResult<()> {
|
||||
'r' => JobCmdFlags::RUNNING,
|
||||
's' => JobCmdFlags::STOPPED,
|
||||
_ => {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
span,
|
||||
"Invalid flag in jobs call",
|
||||
));
|
||||
}
|
||||
};
|
||||
flags |= flag
|
||||
@@ -162,41 +198,44 @@ pub fn jobs(node: Node) -> ShResult<()> {
|
||||
}
|
||||
|
||||
pub fn wait(node: Node) -> ShResult<()> {
|
||||
let blame = node.get_span().clone();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let blame = node.get_span().clone();
|
||||
let NdRule::Command {
|
||||
assignments: _,
|
||||
argv,
|
||||
} = node.class
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if read_jobs(|j| j.curr_job().is_none()) {
|
||||
state::set_status(0);
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found"));
|
||||
}
|
||||
let argv = argv.into_iter()
|
||||
.map(|arg| {
|
||||
if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) {
|
||||
Ok(JobID::Pid(Pid::from_raw(arg.0.parse::<i32>().unwrap())))
|
||||
} else {
|
||||
Ok(JobID::TableID(parse_job_id(&arg.0, arg.1)?))
|
||||
}
|
||||
})
|
||||
.collect::<ShResult<Vec<JobID>>>()?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
if read_jobs(|j| j.curr_job().is_none()) {
|
||||
state::set_status(0);
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, blame, "wait: No jobs found"));
|
||||
}
|
||||
let argv = argv
|
||||
.into_iter()
|
||||
.map(|arg| {
|
||||
if arg.0.as_str().chars().all(|ch| ch.is_ascii_digit()) {
|
||||
Ok(JobID::Pid(Pid::from_raw(arg.0.parse::<i32>().unwrap())))
|
||||
} else {
|
||||
Ok(JobID::TableID(parse_job_id(&arg.0, arg.1)?))
|
||||
}
|
||||
})
|
||||
.collect::<ShResult<Vec<JobID>>>()?;
|
||||
|
||||
if argv.is_empty() {
|
||||
write_jobs(|j| j.wait_all_bg())?;
|
||||
} else {
|
||||
for arg in argv {
|
||||
wait_bg(arg)?;
|
||||
}
|
||||
}
|
||||
if argv.is_empty() {
|
||||
write_jobs(|j| j.wait_all_bg())?;
|
||||
} else {
|
||||
for arg in argv {
|
||||
wait_bg(arg)?;
|
||||
}
|
||||
}
|
||||
|
||||
// don't set status here, the status of the waited-on job should be the status of the wait builtin
|
||||
Ok(())
|
||||
// don't set status here, the status of the waited-on job should be the status of the wait builtin
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disown(node: Node) -> ShResult<()> {
|
||||
@@ -210,13 +249,19 @@ pub fn disown(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
let mut argv = argv.into_iter();
|
||||
|
||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||
id
|
||||
} else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, blame, "disown: No jobs to disown"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
blame,
|
||||
"disown: No jobs to disown",
|
||||
));
|
||||
};
|
||||
|
||||
let mut tabid = curr_job_id;
|
||||
|
||||
@@ -1,112 +1,130 @@
|
||||
use crate::{
|
||||
expand::expand_keymap, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt}, parse::{NdRule, Node, execute::prepare_argv}, prelude::*, readline::keys::KeyEvent, state::{self, write_logic}
|
||||
expand::expand_keymap,
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||
parse::{NdRule, Node, execute::prepare_argv},
|
||||
prelude::*,
|
||||
readline::keys::KeyEvent,
|
||||
state::{self, write_logic},
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct KeyMapFlags: u32 {
|
||||
const NORMAL = 0b0000001;
|
||||
const INSERT = 0b0000010;
|
||||
const VISUAL = 0b0000100;
|
||||
const EX = 0b0001000;
|
||||
const OP_PENDING = 0b0010000;
|
||||
const REPLACE = 0b0100000;
|
||||
const VERBATIM = 0b1000000;
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct KeyMapFlags: u32 {
|
||||
const NORMAL = 0b0000001;
|
||||
const INSERT = 0b0000010;
|
||||
const VISUAL = 0b0000100;
|
||||
const EX = 0b0001000;
|
||||
const OP_PENDING = 0b0010000;
|
||||
const REPLACE = 0b0100000;
|
||||
const VERBATIM = 0b1000000;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct KeyMapOpts {
|
||||
remove: Option<String>,
|
||||
flags: KeyMapFlags,
|
||||
remove: Option<String>,
|
||||
flags: KeyMapFlags,
|
||||
}
|
||||
impl KeyMapOpts {
|
||||
pub fn from_opts(opts: &[Opt]) -> ShResult<Self> {
|
||||
let mut flags = KeyMapFlags::empty();
|
||||
let mut remove = None;
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::Short('n') => flags |= KeyMapFlags::NORMAL,
|
||||
Opt::Short('i') => flags |= KeyMapFlags::INSERT,
|
||||
Opt::Short('v') => flags |= KeyMapFlags::VISUAL,
|
||||
Opt::Short('x') => flags |= KeyMapFlags::EX,
|
||||
Opt::Short('o') => flags |= KeyMapFlags::OP_PENDING,
|
||||
Opt::Short('r') => flags |= KeyMapFlags::REPLACE,
|
||||
Opt::LongWithArg(name, arg) if name == "remove" => {
|
||||
if remove.is_some() {
|
||||
return Err(ShErr::simple(ShErrKind::ExecFail, "Duplicate --remove option for keymap".to_string()));
|
||||
}
|
||||
remove = Some(arg.clone());
|
||||
},
|
||||
_ => return Err(ShErr::simple(ShErrKind::ExecFail, format!("Invalid option for keymap: {:?}", opt))),
|
||||
}
|
||||
}
|
||||
if flags.is_empty() {
|
||||
return Err(ShErr::simple(ShErrKind::ExecFail, "At least one mode option must be specified for keymap".to_string()).with_note("Use -n for normal mode, -i for insert mode, -v for visual mode, -x for ex mode, and -o for operator-pending mode".to_string()));
|
||||
}
|
||||
Ok(Self { remove, flags })
|
||||
}
|
||||
pub fn keymap_opts() -> [OptSpec;6] {
|
||||
[
|
||||
OptSpec {
|
||||
opt: Opt::Short('n'), // normal mode
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('i'), // insert mode
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('v'), // visual mode
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('x'), // ex mode
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('o'), // operator-pending mode
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('r'), // replace mode
|
||||
takes_arg: false
|
||||
},
|
||||
]
|
||||
}
|
||||
pub fn from_opts(opts: &[Opt]) -> ShResult<Self> {
|
||||
let mut flags = KeyMapFlags::empty();
|
||||
let mut remove = None;
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::Short('n') => flags |= KeyMapFlags::NORMAL,
|
||||
Opt::Short('i') => flags |= KeyMapFlags::INSERT,
|
||||
Opt::Short('v') => flags |= KeyMapFlags::VISUAL,
|
||||
Opt::Short('x') => flags |= KeyMapFlags::EX,
|
||||
Opt::Short('o') => flags |= KeyMapFlags::OP_PENDING,
|
||||
Opt::Short('r') => flags |= KeyMapFlags::REPLACE,
|
||||
Opt::LongWithArg(name, arg) if name == "remove" => {
|
||||
if remove.is_some() {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
"Duplicate --remove option for keymap".to_string(),
|
||||
));
|
||||
}
|
||||
remove = Some(arg.clone());
|
||||
}
|
||||
_ => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("Invalid option for keymap: {:?}", opt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if flags.is_empty() {
|
||||
return Err(ShErr::simple(ShErrKind::ExecFail, "At least one mode option must be specified for keymap".to_string()).with_note("Use -n for normal mode, -i for insert mode, -v for visual mode, -x for ex mode, and -o for operator-pending mode".to_string()));
|
||||
}
|
||||
Ok(Self { remove, flags })
|
||||
}
|
||||
pub fn keymap_opts() -> [OptSpec; 6] {
|
||||
[
|
||||
OptSpec {
|
||||
opt: Opt::Short('n'), // normal mode
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('i'), // insert mode
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('v'), // visual mode
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('x'), // ex mode
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('o'), // operator-pending mode
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('r'), // replace mode
|
||||
takes_arg: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum KeyMapMatch {
|
||||
NoMatch,
|
||||
IsPrefix,
|
||||
IsExact
|
||||
NoMatch,
|
||||
IsPrefix,
|
||||
IsExact,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyMap {
|
||||
pub flags: KeyMapFlags,
|
||||
pub keys: String,
|
||||
pub action: String
|
||||
pub flags: KeyMapFlags,
|
||||
pub keys: String,
|
||||
pub action: String,
|
||||
}
|
||||
|
||||
impl KeyMap {
|
||||
pub fn keys_expanded(&self) -> Vec<KeyEvent> {
|
||||
expand_keymap(&self.keys)
|
||||
}
|
||||
pub fn action_expanded(&self) -> Vec<KeyEvent> {
|
||||
expand_keymap(&self.action)
|
||||
}
|
||||
pub fn compare(&self, other: &[KeyEvent]) -> KeyMapMatch {
|
||||
log::debug!("Comparing keymap keys {:?} with input {:?}", self.keys_expanded(), other);
|
||||
let ours = self.keys_expanded();
|
||||
if other == ours {
|
||||
KeyMapMatch::IsExact
|
||||
} else if ours.starts_with(other) {
|
||||
KeyMapMatch::IsPrefix
|
||||
} else {
|
||||
KeyMapMatch::NoMatch
|
||||
}
|
||||
}
|
||||
pub fn keys_expanded(&self) -> Vec<KeyEvent> {
|
||||
expand_keymap(&self.keys)
|
||||
}
|
||||
pub fn action_expanded(&self) -> Vec<KeyEvent> {
|
||||
expand_keymap(&self.action)
|
||||
}
|
||||
pub fn compare(&self, other: &[KeyEvent]) -> KeyMapMatch {
|
||||
log::debug!(
|
||||
"Comparing keymap keys {:?} with input {:?}",
|
||||
self.keys_expanded(),
|
||||
other
|
||||
);
|
||||
let ours = self.keys_expanded();
|
||||
if other == ours {
|
||||
KeyMapMatch::IsExact
|
||||
} else if ours.starts_with(other) {
|
||||
KeyMapMatch::IsPrefix
|
||||
} else {
|
||||
KeyMapMatch::NoMatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keymap(node: Node) -> ShResult<()> {
|
||||
@@ -119,32 +137,42 @@ pub fn keymap(node: Node) -> ShResult<()> {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &KeyMapOpts::keymap_opts())?;
|
||||
let opts = KeyMapOpts::from_opts(&opts).promote_err(span.clone())?;
|
||||
if let Some(to_rm) = opts.remove {
|
||||
write_logic(|l| l.remove_keymap(&to_rm));
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &KeyMapOpts::keymap_opts())?;
|
||||
let opts = KeyMapOpts::from_opts(&opts).promote_err(span.clone())?;
|
||||
if let Some(to_rm) = opts.remove {
|
||||
write_logic(|l| l.remove_keymap(&to_rm));
|
||||
state::set_status(0);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
let Some((keys,_)) = argv.first() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, "missing keys argument".to_string()));
|
||||
};
|
||||
let Some((keys, _)) = argv.first() else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"missing keys argument".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let Some((action,_)) = argv.get(1) else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, "missing action argument".to_string()));
|
||||
};
|
||||
let Some((action, _)) = argv.get(1) else {
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"missing action argument".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let keymap = KeyMap {
|
||||
flags: opts.flags,
|
||||
keys: keys.clone(),
|
||||
action: action.clone(),
|
||||
};
|
||||
let keymap = KeyMap {
|
||||
flags: opts.flags,
|
||||
keys: keys.clone(),
|
||||
action: action.clone(),
|
||||
};
|
||||
|
||||
write_logic(|l| l.insert_keymap(keymap));
|
||||
write_logic(|l| l.insert_keymap(keymap));
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
|
||||
@@ -5,240 +5,239 @@ use nix::{libc::STDOUT_FILENO, unistd::write};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::{
|
||||
expand::expand_cmd_sub, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::{split_tk, split_tk_at}}, procio::borrow_fd, state::{self, read_vars, write_vars}
|
||||
expand::expand_cmd_sub,
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||
libsh::error::{ShErr, ShErrKind, ShResult},
|
||||
parse::{
|
||||
NdRule, Node,
|
||||
lex::{split_tk, split_tk_at},
|
||||
},
|
||||
procio::borrow_fd,
|
||||
state::{self, read_vars, write_vars},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MapNode {
|
||||
DynamicLeaf(String), // eval'd on access
|
||||
StaticLeaf(String), // static value
|
||||
Array(Vec<MapNode>),
|
||||
Branch(HashMap<String, MapNode>),
|
||||
DynamicLeaf(String), // eval'd on access
|
||||
StaticLeaf(String), // static value
|
||||
Array(Vec<MapNode>),
|
||||
Branch(HashMap<String, MapNode>),
|
||||
}
|
||||
|
||||
impl Default for MapNode {
|
||||
fn default() -> Self {
|
||||
Self::Branch(HashMap::new())
|
||||
}
|
||||
fn default() -> Self {
|
||||
Self::Branch(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MapNode> for serde_json::Value {
|
||||
fn from(val: MapNode) -> Self {
|
||||
match val {
|
||||
MapNode::Branch(map) => {
|
||||
let val_map = map.into_iter()
|
||||
.map(|(k,v)| {
|
||||
(k,v.into())
|
||||
})
|
||||
.collect::<Map<String,Value>>();
|
||||
fn from(val: MapNode) -> Self {
|
||||
match val {
|
||||
MapNode::Branch(map) => {
|
||||
let val_map = map
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
.collect::<Map<String, Value>>();
|
||||
|
||||
Value::Object(val_map)
|
||||
}
|
||||
MapNode::Array(nodes) => {
|
||||
let arr = nodes
|
||||
.into_iter()
|
||||
.map(|node| node.into())
|
||||
.collect();
|
||||
Value::Array(arr)
|
||||
}
|
||||
MapNode::StaticLeaf(leaf) | MapNode::DynamicLeaf(leaf) => {
|
||||
Value::String(leaf)
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Object(val_map)
|
||||
}
|
||||
MapNode::Array(nodes) => {
|
||||
let arr = nodes.into_iter().map(|node| node.into()).collect();
|
||||
Value::Array(arr)
|
||||
}
|
||||
MapNode::StaticLeaf(leaf) | MapNode::DynamicLeaf(leaf) => Value::String(leaf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for MapNode {
|
||||
fn from(value: Value) -> Self {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let node_map = map.into_iter()
|
||||
.map(|(k,v)| {
|
||||
(k, v.into())
|
||||
})
|
||||
.collect::<HashMap<String, MapNode>>();
|
||||
fn from(value: Value) -> Self {
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let node_map = map
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.into()))
|
||||
.collect::<HashMap<String, MapNode>>();
|
||||
|
||||
MapNode::Branch(node_map)
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
let nodes = arr
|
||||
.into_iter()
|
||||
.map(|v| v.into())
|
||||
.collect();
|
||||
MapNode::Array(nodes)
|
||||
}
|
||||
Value::String(s) => MapNode::StaticLeaf(s),
|
||||
v => MapNode::StaticLeaf(v.to_string())
|
||||
}
|
||||
}
|
||||
MapNode::Branch(node_map)
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
let nodes = arr.into_iter().map(|v| v.into()).collect();
|
||||
MapNode::Array(nodes)
|
||||
}
|
||||
Value::String(s) => MapNode::StaticLeaf(s),
|
||||
v => MapNode::StaticLeaf(v.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapNode {
|
||||
fn get(&self, path: &[String]) -> Option<&MapNode> {
|
||||
match path {
|
||||
[] => Some(self),
|
||||
[key, rest @ ..] => match self {
|
||||
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => None,
|
||||
MapNode::Array(map_nodes) => {
|
||||
let idx: usize = key.parse().ok()?;
|
||||
map_nodes.get(idx)?.get(rest)
|
||||
}
|
||||
MapNode::Branch(map) => map.get(key)?.get(rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn get(&self, path: &[String]) -> Option<&MapNode> {
|
||||
match path {
|
||||
[] => Some(self),
|
||||
[key, rest @ ..] => match self {
|
||||
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => None,
|
||||
MapNode::Array(map_nodes) => {
|
||||
let idx: usize = key.parse().ok()?;
|
||||
map_nodes.get(idx)?.get(rest)
|
||||
}
|
||||
MapNode::Branch(map) => map.get(key)?.get(rest),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&mut self, path: &[String], value: MapNode) {
|
||||
match path {
|
||||
[] => *self = value,
|
||||
[key, rest @ ..] => {
|
||||
if matches!(self, MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_)) {
|
||||
// promote leaf to branch if we still have path left to traverse
|
||||
*self = Self::default();
|
||||
}
|
||||
match self {
|
||||
MapNode::Branch(map) => {
|
||||
let child = map
|
||||
.entry(key.to_string())
|
||||
.or_insert_with(Self::default);
|
||||
child.set(rest, value);
|
||||
}
|
||||
MapNode::Array(map_nodes) => {
|
||||
let idx: usize = key.parse().expect("expected array index");
|
||||
if idx >= map_nodes.len() {
|
||||
map_nodes.resize(idx + 1, Self::default());
|
||||
}
|
||||
map_nodes[idx].set(rest, value);
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn set(&mut self, path: &[String], value: MapNode) {
|
||||
match path {
|
||||
[] => *self = value,
|
||||
[key, rest @ ..] => {
|
||||
if matches!(self, MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_)) {
|
||||
// promote leaf to branch if we still have path left to traverse
|
||||
*self = Self::default();
|
||||
}
|
||||
match self {
|
||||
MapNode::Branch(map) => {
|
||||
let child = map.entry(key.to_string()).or_insert_with(Self::default);
|
||||
child.set(rest, value);
|
||||
}
|
||||
MapNode::Array(map_nodes) => {
|
||||
let idx: usize = key.parse().expect("expected array index");
|
||||
if idx >= map_nodes.len() {
|
||||
map_nodes.resize(idx + 1, Self::default());
|
||||
}
|
||||
map_nodes[idx].set(rest, value);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, path: &[String]) -> Option<MapNode> {
|
||||
match path {
|
||||
[] => None,
|
||||
[key] => match self {
|
||||
MapNode::Branch(map) => map.remove(key),
|
||||
MapNode::Array(nodes) => {
|
||||
let idx: usize = key.parse().ok()?;
|
||||
if idx >= nodes.len() {
|
||||
return None;
|
||||
}
|
||||
Some(nodes.remove(idx))
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
[key, rest @ ..] => match self {
|
||||
MapNode::Branch(map) => map.get_mut(key)?.remove(rest),
|
||||
MapNode::Array(nodes) => {
|
||||
let idx: usize = key.parse().ok()?;
|
||||
if idx >= nodes.len() {
|
||||
return None;
|
||||
}
|
||||
nodes[idx].remove(rest)
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
fn remove(&mut self, path: &[String]) -> Option<MapNode> {
|
||||
match path {
|
||||
[] => None,
|
||||
[key] => match self {
|
||||
MapNode::Branch(map) => map.remove(key),
|
||||
MapNode::Array(nodes) => {
|
||||
let idx: usize = key.parse().ok()?;
|
||||
if idx >= nodes.len() {
|
||||
return None;
|
||||
}
|
||||
Some(nodes.remove(idx))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
[key, rest @ ..] => match self {
|
||||
MapNode::Branch(map) => map.get_mut(key)?.remove(rest),
|
||||
MapNode::Array(nodes) => {
|
||||
let idx: usize = key.parse().ok()?;
|
||||
if idx >= nodes.len() {
|
||||
return None;
|
||||
}
|
||||
nodes[idx].remove(rest)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn keys(&self) -> Vec<String> {
|
||||
match self {
|
||||
MapNode::Branch(map) => map.keys().map(|k| k.to_string()).collect(),
|
||||
MapNode::Array(nodes) => nodes.iter().filter_map(|n| n.display(false, false).ok()).collect(),
|
||||
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => vec![],
|
||||
}
|
||||
}
|
||||
fn keys(&self) -> Vec<String> {
|
||||
match self {
|
||||
MapNode::Branch(map) => map.keys().map(|k| k.to_string()).collect(),
|
||||
MapNode::Array(nodes) => nodes
|
||||
.iter()
|
||||
.filter_map(|n| n.display(false, false).ok())
|
||||
.collect(),
|
||||
MapNode::StaticLeaf(_) | MapNode::DynamicLeaf(_) => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn display(&self, json: bool, pretty: bool) -> ShResult<String> {
|
||||
if json || matches!(self, MapNode::Branch(_)) {
|
||||
let val: Value = self.clone().into();
|
||||
if pretty {
|
||||
match serde_json::to_string_pretty(&val) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("failed to serialize map: {e}")
|
||||
))
|
||||
}
|
||||
} else {
|
||||
match serde_json::to_string(&val) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("failed to serialize map: {e}")
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self {
|
||||
MapNode::StaticLeaf(leaf) => Ok(leaf.clone()),
|
||||
MapNode::DynamicLeaf(cmd) => expand_cmd_sub(cmd),
|
||||
MapNode::Array(nodes) => {
|
||||
let mut s = String::new();
|
||||
for node in nodes {
|
||||
let display = node.display(json, pretty)?;
|
||||
if matches!(node, MapNode::Branch(_)) {
|
||||
s.push_str(&format!("'{}'", display));
|
||||
} else {
|
||||
s.push_str(&node.display(json, pretty)?);
|
||||
}
|
||||
s.push('\n');
|
||||
}
|
||||
Ok(s.trim_end_matches('\n').to_string())
|
||||
}
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
fn display(&self, json: bool, pretty: bool) -> ShResult<String> {
|
||||
if json || matches!(self, MapNode::Branch(_)) {
|
||||
let val: Value = self.clone().into();
|
||||
if pretty {
|
||||
match serde_json::to_string_pretty(&val) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("failed to serialize map: {e}"),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
match serde_json::to_string(&val) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(e) => Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("failed to serialize map: {e}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self {
|
||||
MapNode::StaticLeaf(leaf) => Ok(leaf.clone()),
|
||||
MapNode::DynamicLeaf(cmd) => expand_cmd_sub(cmd),
|
||||
MapNode::Array(nodes) => {
|
||||
let mut s = String::new();
|
||||
for node in nodes {
|
||||
let display = node.display(json, pretty)?;
|
||||
if matches!(node, MapNode::Branch(_)) {
|
||||
s.push_str(&format!("'{}'", display));
|
||||
} else {
|
||||
s.push_str(&node.display(json, pretty)?);
|
||||
}
|
||||
s.push('\n');
|
||||
}
|
||||
Ok(s.trim_end_matches('\n').to_string())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_opts_spec() -> [OptSpec; 6] {
|
||||
[
|
||||
OptSpec {
|
||||
opt: Opt::Short('r'),
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('j'),
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('k'),
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Long("pretty".into()),
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('F'),
|
||||
takes_arg: false
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('l'),
|
||||
takes_arg: false
|
||||
},
|
||||
]
|
||||
[
|
||||
OptSpec {
|
||||
opt: Opt::Short('r'),
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('j'),
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('k'),
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Long("pretty".into()),
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('F'),
|
||||
takes_arg: false,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('l'),
|
||||
takes_arg: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MapOpts {
|
||||
flags: MapFlags,
|
||||
flags: MapFlags,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct MapFlags: u32 {
|
||||
const REMOVE = 0b000001;
|
||||
const KEYS = 0b000010;
|
||||
const JSON = 0b000100;
|
||||
const LOCAL = 0b001000;
|
||||
const PRETTY = 0b010000;
|
||||
const FUNC = 0b100000;
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct MapFlags: u32 {
|
||||
const REMOVE = 0b000001;
|
||||
const KEYS = 0b000010;
|
||||
const JSON = 0b000100;
|
||||
const LOCAL = 0b001000;
|
||||
const PRETTY = 0b010000;
|
||||
const FUNC = 0b100000;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map(node: Node) -> ShResult<()> {
|
||||
@@ -250,136 +249,140 @@ pub fn map(node: Node) -> ShResult<()> {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (mut argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
||||
let map_opts = get_map_opts(opts);
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0); // remove "map" command from argv
|
||||
}
|
||||
let (mut argv, opts) = get_opts_from_tokens(argv, &map_opts_spec())?;
|
||||
let map_opts = get_map_opts(opts);
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0); // remove "map" command from argv
|
||||
}
|
||||
|
||||
for arg in argv {
|
||||
if let Some((lhs,rhs)) = split_tk_at(&arg, "=") {
|
||||
let path = split_tk(&lhs, ".")
|
||||
.into_iter()
|
||||
.map(|s| s.expand().map(|exp| exp.get_words().join(" ")))
|
||||
.collect::<ShResult<Vec<String>>>()?;
|
||||
let Some(name) = path.first() else {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("invalid map path: {}", lhs.as_str())
|
||||
));
|
||||
};
|
||||
for arg in argv {
|
||||
if let Some((lhs, rhs)) = split_tk_at(&arg, "=") {
|
||||
let path = split_tk(&lhs, ".")
|
||||
.into_iter()
|
||||
.map(|s| s.expand().map(|exp| exp.get_words().join(" ")))
|
||||
.collect::<ShResult<Vec<String>>>()?;
|
||||
let Some(name) = path.first() else {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("invalid map path: {}", lhs.as_str()),
|
||||
));
|
||||
};
|
||||
|
||||
let is_json = map_opts.flags.contains(MapFlags::JSON);
|
||||
let is_func = map_opts.flags.contains(MapFlags::FUNC);
|
||||
let make_leaf = |s: String| {
|
||||
if is_func { MapNode::DynamicLeaf(s) } else { MapNode::StaticLeaf(s) }
|
||||
};
|
||||
let expanded = rhs.expand()?.get_words().join(" ");
|
||||
let found = write_vars(|v| -> ShResult<bool> {
|
||||
if let Some(map) = v.get_map_mut(name) {
|
||||
if is_json {
|
||||
if let Ok(parsed) = serde_json::from_str::<Value>(expanded.as_str()) {
|
||||
map.set(&path[1..], parsed.into());
|
||||
} else {
|
||||
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||
}
|
||||
} else {
|
||||
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
});
|
||||
let is_json = map_opts.flags.contains(MapFlags::JSON);
|
||||
let is_func = map_opts.flags.contains(MapFlags::FUNC);
|
||||
let make_leaf = |s: String| {
|
||||
if is_func {
|
||||
MapNode::DynamicLeaf(s)
|
||||
} else {
|
||||
MapNode::StaticLeaf(s)
|
||||
}
|
||||
};
|
||||
let expanded = rhs.expand()?.get_words().join(" ");
|
||||
let found = write_vars(|v| -> ShResult<bool> {
|
||||
if let Some(map) = v.get_map_mut(name) {
|
||||
if is_json {
|
||||
if let Ok(parsed) = serde_json::from_str::<Value>(expanded.as_str()) {
|
||||
map.set(&path[1..], parsed.into());
|
||||
} else {
|
||||
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||
}
|
||||
} else {
|
||||
map.set(&path[1..], make_leaf(expanded.clone()));
|
||||
}
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
});
|
||||
|
||||
if !found? {
|
||||
let mut new = MapNode::default();
|
||||
if is_json /*&& let Ok(parsed) = serde_json::from_str::<Value>(rhs.as_str()) */{
|
||||
let parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap();
|
||||
let node: MapNode = parsed.into();
|
||||
new.set(&path[1..], node);
|
||||
} else {
|
||||
new.set(&path[1..], make_leaf(expanded));
|
||||
}
|
||||
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
||||
}
|
||||
} else {
|
||||
let expanded = arg.expand()?.get_words().join(" ");
|
||||
let path: Vec<String> = expanded.split('.').map(|s| s.to_string()).collect();
|
||||
let Some(name) = path.first() else {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("invalid map path: {}", expanded)
|
||||
));
|
||||
};
|
||||
if !found? {
|
||||
let mut new = MapNode::default();
|
||||
if is_json
|
||||
/*&& let Ok(parsed) = serde_json::from_str::<Value>(rhs.as_str()) */
|
||||
{
|
||||
let parsed = serde_json::from_str::<Value>(expanded.as_str()).unwrap();
|
||||
let node: MapNode = parsed.into();
|
||||
new.set(&path[1..], node);
|
||||
} else {
|
||||
new.set(&path[1..], make_leaf(expanded));
|
||||
}
|
||||
write_vars(|v| v.set_map(name, new, map_opts.flags.contains(MapFlags::LOCAL)));
|
||||
}
|
||||
} else {
|
||||
let expanded = arg.expand()?.get_words().join(" ");
|
||||
let path: Vec<String> = expanded.split('.').map(|s| s.to_string()).collect();
|
||||
let Some(name) = path.first() else {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::InternalErr,
|
||||
format!("invalid map path: {}", expanded),
|
||||
));
|
||||
};
|
||||
|
||||
if map_opts.flags.contains(MapFlags::REMOVE) {
|
||||
write_vars(|v| {
|
||||
if path.len() == 1 {
|
||||
v.remove_map(name);
|
||||
} else {
|
||||
let Some(map) = v.get_map_mut(name) else {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("map not found: {}", name)
|
||||
));
|
||||
};
|
||||
map.remove(&path[1..]);
|
||||
}
|
||||
if map_opts.flags.contains(MapFlags::REMOVE) {
|
||||
write_vars(|v| {
|
||||
if path.len() == 1 {
|
||||
v.remove_map(name);
|
||||
} else {
|
||||
let Some(map) = v.get_map_mut(name) else {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("map not found: {}", name),
|
||||
));
|
||||
};
|
||||
map.remove(&path[1..]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
continue;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let json = map_opts.flags.contains(MapFlags::JSON);
|
||||
let pretty = map_opts.flags.contains(MapFlags::PRETTY);
|
||||
let keys = map_opts.flags.contains(MapFlags::KEYS);
|
||||
let has_map = read_vars(|v| v.get_map(name).is_some());
|
||||
if !has_map {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("map not found: {}", name)
|
||||
));
|
||||
}
|
||||
let Some(node) = read_vars(|v| {
|
||||
v.get_map(name)
|
||||
.and_then(|map| map.get(&path[1..]).cloned())
|
||||
}) else {
|
||||
state::set_status(1);
|
||||
continue;
|
||||
};
|
||||
let output = if keys {
|
||||
node.keys().join(" ")
|
||||
} else {
|
||||
node.display(json, pretty)?
|
||||
};
|
||||
let json = map_opts.flags.contains(MapFlags::JSON);
|
||||
let pretty = map_opts.flags.contains(MapFlags::PRETTY);
|
||||
let keys = map_opts.flags.contains(MapFlags::KEYS);
|
||||
let has_map = read_vars(|v| v.get_map(name).is_some());
|
||||
if !has_map {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("map not found: {}", name),
|
||||
));
|
||||
}
|
||||
let Some(node) = read_vars(|v| v.get_map(name).and_then(|map| map.get(&path[1..]).cloned()))
|
||||
else {
|
||||
state::set_status(1);
|
||||
continue;
|
||||
};
|
||||
let output = if keys {
|
||||
node.keys().join(" ")
|
||||
} else {
|
||||
node.display(json, pretty)?
|
||||
};
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, output.as_bytes())?;
|
||||
write(stdout, b"\n")?;
|
||||
}
|
||||
}
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
write(stdout, output.as_bytes())?;
|
||||
write(stdout, b"\n")?;
|
||||
}
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_map_opts(opts: Vec<Opt>) -> MapOpts {
|
||||
let mut map_opts = MapOpts {
|
||||
flags: MapFlags::empty()
|
||||
};
|
||||
let mut map_opts = MapOpts {
|
||||
flags: MapFlags::empty(),
|
||||
};
|
||||
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::Short('r') => map_opts.flags |= MapFlags::REMOVE,
|
||||
Opt::Short('j') => map_opts.flags |= MapFlags::JSON,
|
||||
Opt::Short('k') => map_opts.flags |= MapFlags::KEYS,
|
||||
Opt::Short('l') => map_opts.flags |= MapFlags::LOCAL,
|
||||
Opt::Long(ref s) if s == "pretty" => map_opts.flags |= MapFlags::PRETTY,
|
||||
Opt::Short('F') => map_opts.flags |= MapFlags::FUNC,
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
map_opts
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::Short('r') => map_opts.flags |= MapFlags::REMOVE,
|
||||
Opt::Short('j') => map_opts.flags |= MapFlags::JSON,
|
||||
Opt::Short('k') => map_opts.flags |= MapFlags::KEYS,
|
||||
Opt::Short('l') => map_opts.flags |= MapFlags::LOCAL,
|
||||
Opt::Long(ref s) if s == "pretty" => map_opts.flags |= MapFlags::PRETTY,
|
||||
Opt::Short('F') => map_opts.flags |= MapFlags::FUNC,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
map_opts
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::{
|
||||
libsh::error::ShResult,
|
||||
state,
|
||||
};
|
||||
use crate::{libsh::error::ShResult, state};
|
||||
|
||||
pub mod alias;
|
||||
pub mod arrops;
|
||||
pub mod autocmd;
|
||||
pub mod cd;
|
||||
pub mod complete;
|
||||
pub mod dirstack;
|
||||
@@ -11,7 +10,11 @@ pub mod echo;
|
||||
pub mod eval;
|
||||
pub mod exec;
|
||||
pub mod flowctl;
|
||||
pub mod getopts;
|
||||
pub mod intro;
|
||||
pub mod jobctl;
|
||||
pub mod keymap;
|
||||
pub mod map;
|
||||
pub mod pwd;
|
||||
pub mod read;
|
||||
pub mod shift;
|
||||
@@ -21,19 +24,13 @@ pub mod test; // [[ ]] thing
|
||||
pub mod trap;
|
||||
pub mod varcmds;
|
||||
pub mod zoltraak;
|
||||
pub mod map;
|
||||
pub mod arrops;
|
||||
pub mod intro;
|
||||
pub mod getopts;
|
||||
pub mod keymap;
|
||||
pub mod autocmd;
|
||||
|
||||
pub const BUILTINS: [&str; 47] = [
|
||||
"echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown",
|
||||
"alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin",
|
||||
"command", "trap", "pushd", "popd", "dirs", "exec", "eval", "true", "false", ":", "readonly",
|
||||
"unset", "complete", "compgen", "map", "pop", "fpop", "push", "fpush", "rotate", "wait", "type",
|
||||
"getopts", "keymap", "read_key", "autocmd"
|
||||
"getopts", "keymap", "read_key", "autocmd",
|
||||
];
|
||||
|
||||
pub fn true_builtin() -> ShResult<()> {
|
||||
|
||||
@@ -6,7 +6,16 @@ use nix::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
expand::expand_keymap, getopt::{Opt, OptSpec, get_opts_from_tokens}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, sys::TTY_FILENO}, parse::{NdRule, Node, execute::prepare_argv}, procio::borrow_fd, readline::term::{KeyReader, PollReader, RawModeGuard}, state::{self, VarFlags, VarKind, read_vars, write_vars}
|
||||
expand::expand_keymap,
|
||||
getopt::{Opt, OptSpec, get_opts_from_tokens},
|
||||
libsh::{
|
||||
error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||
sys::TTY_FILENO,
|
||||
},
|
||||
parse::{NdRule, Node, execute::prepare_argv},
|
||||
procio::borrow_fd,
|
||||
readline::term::{KeyReader, PollReader, RawModeGuard},
|
||||
state::{self, VarFlags, VarKind, read_vars, write_vars},
|
||||
};
|
||||
|
||||
pub const READ_OPTS: [OptSpec; 7] = [
|
||||
@@ -40,19 +49,19 @@ pub const READ_OPTS: [OptSpec; 7] = [
|
||||
}, // read until delimiter
|
||||
];
|
||||
|
||||
pub const READ_KEY_OPTS: [OptSpec;3] = [
|
||||
OptSpec {
|
||||
opt: Opt::Short('v'), // var name
|
||||
takes_arg: true
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('w'), // char whitelist
|
||||
takes_arg: true
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('b'), // char blacklist
|
||||
takes_arg: true
|
||||
}
|
||||
pub const READ_KEY_OPTS: [OptSpec; 3] = [
|
||||
OptSpec {
|
||||
opt: Opt::Short('v'), // var name
|
||||
takes_arg: true,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('w'), // char whitelist
|
||||
takes_arg: true,
|
||||
},
|
||||
OptSpec {
|
||||
opt: Opt::Short('b'), // char blacklist
|
||||
takes_arg: true,
|
||||
},
|
||||
];
|
||||
|
||||
bitflags! {
|
||||
@@ -84,7 +93,9 @@ pub fn read_builtin(node: Node) -> ShResult<()> {
|
||||
let (argv, opts) = get_opts_from_tokens(argv, &READ_OPTS)?;
|
||||
let read_opts = get_read_flags(opts).blame(blame.clone())?;
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
if let Some(prompt) = read_opts.prompt {
|
||||
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
|
||||
@@ -257,96 +268,98 @@ pub fn get_read_flags(opts: Vec<Opt>) -> ShResult<ReadOpts> {
|
||||
}
|
||||
|
||||
pub struct ReadKeyOpts {
|
||||
var_name: Option<String>,
|
||||
char_whitelist: Option<String>,
|
||||
char_blacklist: Option<String>
|
||||
var_name: Option<String>,
|
||||
char_whitelist: Option<String>,
|
||||
char_blacklist: Option<String>,
|
||||
}
|
||||
|
||||
pub fn read_key(node: Node) -> ShResult<()> {
|
||||
let blame = node.get_span().clone();
|
||||
let NdRule::Command { argv, .. } = node.class else { unreachable!() };
|
||||
let blame = node.get_span().clone();
|
||||
let NdRule::Command { argv, .. } = node.class else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
if !isatty(*TTY_FILENO)? {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
if !isatty(*TTY_FILENO)? {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (_, opts) = get_opts_from_tokens(argv, &READ_KEY_OPTS).blame(blame.clone())?;
|
||||
let read_key_opts = get_read_key_opts(opts).blame(blame.clone())?;
|
||||
let (_, opts) = get_opts_from_tokens(argv, &READ_KEY_OPTS).blame(blame.clone())?;
|
||||
let read_key_opts = get_read_key_opts(opts).blame(blame.clone())?;
|
||||
|
||||
let key = {
|
||||
let _raw = crate::readline::term::raw_mode();
|
||||
let mut buf = [0u8; 16];
|
||||
match read(*TTY_FILENO, &mut buf) {
|
||||
Ok(0) => {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(n) => {
|
||||
let mut reader = PollReader::new();
|
||||
reader.feed_bytes(&buf[..n], false);
|
||||
let Some(key) = reader.read_key()? else {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
};
|
||||
key
|
||||
},
|
||||
Err(Errno::EINTR) => {
|
||||
state::set_status(130);
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => return Err(ShErr::simple(ShErrKind::ExecFail, format!("read_key: {e}"))),
|
||||
}
|
||||
};
|
||||
let key = {
|
||||
let _raw = crate::readline::term::raw_mode();
|
||||
let mut buf = [0u8; 16];
|
||||
match read(*TTY_FILENO, &mut buf) {
|
||||
Ok(0) => {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(n) => {
|
||||
let mut reader = PollReader::new();
|
||||
reader.feed_bytes(&buf[..n], false);
|
||||
let Some(key) = reader.read_key()? else {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
};
|
||||
key
|
||||
}
|
||||
Err(Errno::EINTR) => {
|
||||
state::set_status(130);
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => return Err(ShErr::simple(ShErrKind::ExecFail, format!("read_key: {e}"))),
|
||||
}
|
||||
};
|
||||
|
||||
let vim_seq = key.as_vim_seq()?;
|
||||
let vim_seq = key.as_vim_seq()?;
|
||||
|
||||
if let Some(wl) = read_key_opts.char_whitelist {
|
||||
let allowed = expand_keymap(&wl);
|
||||
if !allowed.contains(&key) {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(wl) = read_key_opts.char_whitelist {
|
||||
let allowed = expand_keymap(&wl);
|
||||
if !allowed.contains(&key) {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bl) = read_key_opts.char_blacklist {
|
||||
let disallowed = expand_keymap(&bl);
|
||||
if disallowed.contains(&key) {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(bl) = read_key_opts.char_blacklist {
|
||||
let disallowed = expand_keymap(&bl);
|
||||
if disallowed.contains(&key) {
|
||||
state::set_status(1);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(var) = read_key_opts.var_name {
|
||||
write_vars(|v| v.set_var(&var, VarKind::Str(vim_seq), VarFlags::NONE))?;
|
||||
} else {
|
||||
write(borrow_fd(STDOUT_FILENO), vim_seq.as_bytes())?;
|
||||
}
|
||||
if let Some(var) = read_key_opts.var_name {
|
||||
write_vars(|v| v.set_var(&var, VarKind::Str(vim_seq), VarFlags::NONE))?;
|
||||
} else {
|
||||
write(borrow_fd(STDOUT_FILENO), vim_seq.as_bytes())?;
|
||||
}
|
||||
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
state::set_status(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_read_key_opts(opts: Vec<Opt>) -> ShResult<ReadKeyOpts> {
|
||||
let mut read_key_opts = ReadKeyOpts {
|
||||
var_name: None,
|
||||
char_whitelist: None,
|
||||
char_blacklist: None
|
||||
};
|
||||
let mut read_key_opts = ReadKeyOpts {
|
||||
var_name: None,
|
||||
char_whitelist: None,
|
||||
char_blacklist: None,
|
||||
};
|
||||
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::ShortWithArg('v', var_name) => read_key_opts.var_name = Some(var_name),
|
||||
Opt::ShortWithArg('w', char_whitelist) => read_key_opts.char_whitelist = Some(char_whitelist),
|
||||
Opt::ShortWithArg('b', char_blacklist) => read_key_opts.char_blacklist = Some(char_blacklist),
|
||||
_ => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("read_key: Unexpected flag '{opt}'")
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
for opt in opts {
|
||||
match opt {
|
||||
Opt::ShortWithArg('v', var_name) => read_key_opts.var_name = Some(var_name),
|
||||
Opt::ShortWithArg('w', char_whitelist) => read_key_opts.char_whitelist = Some(char_whitelist),
|
||||
Opt::ShortWithArg('b', char_blacklist) => read_key_opts.char_blacklist = Some(char_blacklist),
|
||||
_ => {
|
||||
return Err(ShErr::simple(
|
||||
ShErrKind::ExecFail,
|
||||
format!("read_key: Unexpected flag '{opt}'"),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(read_key_opts)
|
||||
Ok(read_key_opts)
|
||||
}
|
||||
|
||||
@@ -14,12 +14,18 @@ pub fn shift(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
let mut argv = argv.into_iter();
|
||||
|
||||
if let Some((arg, span)) = argv.next() {
|
||||
let Ok(count) = arg.parse::<usize>() else {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, "Expected a number in shift args"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
"Expected a number in shift args",
|
||||
));
|
||||
};
|
||||
for _ in 0..count {
|
||||
write_vars(|v| v.cur_scope_mut().fpop_arg());
|
||||
|
||||
@@ -16,7 +16,9 @@ pub fn shopt(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
if argv.is_empty() {
|
||||
let mut output = write_shopts(|s| s.display_opts())?;
|
||||
|
||||
@@ -15,15 +15,25 @@ pub fn source(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
for (arg, span) in argv {
|
||||
let path = PathBuf::from(arg);
|
||||
if !path.exists() {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: File '{}' not found", path.display())));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
format!("source: File '{}' not found", path.display()),
|
||||
));
|
||||
}
|
||||
if !path.is_file() {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: Given path '{}' is not a file", path.display())));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
format!("source: Given path '{}' is not a file", path.display()),
|
||||
));
|
||||
}
|
||||
source_file(path)?;
|
||||
}
|
||||
|
||||
@@ -61,10 +61,7 @@ impl FromStr for UnaryOp {
|
||||
"-t" => Ok(Self::Terminal),
|
||||
"-n" => Ok(Self::NonNull),
|
||||
"-z" => Ok(Self::Null),
|
||||
_ => Err(ShErr::simple(
|
||||
ShErrKind::SyntaxErr,
|
||||
"Invalid test operator",
|
||||
)),
|
||||
_ => Err(ShErr::simple(ShErrKind::SyntaxErr, "Invalid test operator")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,10 +94,7 @@ impl FromStr for TestOp {
|
||||
"-ge" => Ok(Self::IntGe),
|
||||
"-le" => Ok(Self::IntLe),
|
||||
_ if TEST_UNARY_OPS.contains(&s) => Ok(Self::Unary(s.parse::<UnaryOp>()?)),
|
||||
_ => Err(ShErr::simple(
|
||||
ShErrKind::SyntaxErr,
|
||||
"Invalid test operator",
|
||||
)),
|
||||
_ => Err(ShErr::simple(ShErrKind::SyntaxErr, "Invalid test operator")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,7 +132,11 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
||||
let operand = operand.expand()?.get_words().join(" ");
|
||||
conjunct_op = conjunct;
|
||||
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Invalid unary operator"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
err_span,
|
||||
"Invalid unary operator",
|
||||
));
|
||||
};
|
||||
match op {
|
||||
UnaryOp::Exists => {
|
||||
@@ -241,7 +239,11 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
||||
let test_op = operator.as_str().parse::<TestOp>()?;
|
||||
match test_op {
|
||||
TestOp::Unary(_) => {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Expected a binary operator in this test call; found a unary operator"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
err_span,
|
||||
"Expected a binary operator in this test call; found a unary operator",
|
||||
));
|
||||
}
|
||||
TestOp::StringEq => {
|
||||
let pattern = crate::expand::glob_to_regex(rhs.trim(), true);
|
||||
@@ -257,7 +259,11 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
||||
| TestOp::IntGe
|
||||
| TestOp::IntLe
|
||||
| TestOp::IntEq => {
|
||||
let err = ShErr::at(ShErrKind::SyntaxErr, err_span.clone(), format!("Expected an integer with '{}' operator", operator));
|
||||
let err = ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
err_span.clone(),
|
||||
format!("Expected an integer with '{}' operator", operator),
|
||||
);
|
||||
let Ok(lhs) = lhs.trim().parse::<i32>() else {
|
||||
return Err(err);
|
||||
};
|
||||
|
||||
@@ -122,7 +122,9 @@ pub fn trap(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
if argv.is_empty() {
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
|
||||
@@ -16,7 +16,11 @@ pub fn readonly(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
// Remove "readonly" from argv
|
||||
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||
let argv = if !argv.is_empty() {
|
||||
&argv[1..]
|
||||
} else {
|
||||
&argv[..]
|
||||
};
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the local variables
|
||||
@@ -67,15 +71,25 @@ pub fn unset(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
if argv.is_empty() {
|
||||
return Err(ShErr::at(ShErrKind::SyntaxErr, blame, "unset: Expected at least one argument"));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
blame,
|
||||
"unset: Expected at least one argument",
|
||||
));
|
||||
}
|
||||
|
||||
for (arg, span) in argv {
|
||||
if !read_vars(|v| v.var_exists(&arg)) {
|
||||
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("unset: No such variable '{arg}'")));
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ExecFail,
|
||||
span,
|
||||
format!("unset: No such variable '{arg}'"),
|
||||
));
|
||||
}
|
||||
write_vars(|v| v.unset_var(&arg))?;
|
||||
}
|
||||
@@ -94,7 +108,11 @@ pub fn export(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
// Remove "export" from argv
|
||||
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||
let argv = if !argv.is_empty() {
|
||||
&argv[1..]
|
||||
} else {
|
||||
&argv[..]
|
||||
};
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the environment variables
|
||||
@@ -137,7 +155,11 @@ pub fn local(node: Node) -> ShResult<()> {
|
||||
};
|
||||
|
||||
// Remove "local" from argv
|
||||
let argv = if !argv.is_empty() { &argv[1..] } else { &argv[..] };
|
||||
let argv = if !argv.is_empty() {
|
||||
&argv[1..]
|
||||
} else {
|
||||
&argv[..]
|
||||
};
|
||||
|
||||
if argv.is_empty() {
|
||||
// Display the local variables
|
||||
|
||||
@@ -104,7 +104,9 @@ pub fn zoltraak(node: Node) -> ShResult<()> {
|
||||
}
|
||||
|
||||
let mut argv = prepare_argv(argv)?;
|
||||
if !argv.is_empty() { argv.remove(0); }
|
||||
if !argv.is_empty() {
|
||||
argv.remove(0);
|
||||
}
|
||||
|
||||
for (arg, span) in argv {
|
||||
if &arg == "/" && !flags.contains(ZoltFlags::NO_PRESERVE_ROOT) {
|
||||
@@ -113,9 +115,7 @@ pub fn zoltraak(node: Node) -> ShResult<()> {
|
||||
ShErrKind::ExecFail,
|
||||
"zoltraak: Attempted to destroy root directory '/'",
|
||||
)
|
||||
.with_note(
|
||||
"If you really want to do this, you can use the --no-preserve-root flag"
|
||||
),
|
||||
.with_note("If you really want to do this, you can use the --no-preserve-root flag"),
|
||||
);
|
||||
}
|
||||
annihilate(&arg, flags).blame(span)?
|
||||
@@ -176,9 +176,7 @@ fn annihilate(path: &str, flags: ZoltFlags) -> ShResult<()> {
|
||||
ShErrKind::ExecFail,
|
||||
format!("zoltraak: '{path}' is a directory"),
|
||||
)
|
||||
.with_note(
|
||||
"Use the '-r' flag to recursively shred directories"
|
||||
),
|
||||
.with_note("Use the '-r' flag to recursively shred directories"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user