rustfmt'd the codebase

This commit is contained in:
2026-03-04 19:52:29 -05:00
parent ecd6eda424
commit 7be79a3803
51 changed files with 4926 additions and 4131 deletions

View File

@@ -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))
}

View File

@@ -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)
}

View File

@@ -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(())

View File

@@ -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) };

View File

@@ -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,

View File

@@ -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}"),
}
))
);
),
));
}
}

View File

@@ -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)

View File

@@ -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);

View File

@@ -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}"))),
}
}

View File

@@ -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;

View File

@@ -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)
}
}

View File

@@ -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(())

View File

@@ -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;

View File

@@ -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(())

View File

@@ -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
}

View File

@@ -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<()> {

View File

@@ -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)
}

View File

@@ -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());

View File

@@ -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())?;

View File

@@ -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)?;
}

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -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

View File

@@ -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"),
);
}
}