Added -j flag to 'complete' for completing job names/pids

This commit is contained in:
2026-02-27 11:03:56 -05:00
parent e141e39c7e
commit c508180228
44 changed files with 3259 additions and 2853 deletions

View File

@@ -3,7 +3,7 @@ use crate::{
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
procio::{IoStack, borrow_fd},
state::{self, read_logic, write_logic},
};

View File

@@ -1,120 +1,138 @@
use bitflags::bitflags;
use nix::{libc::STDOUT_FILENO, unistd::write};
use crate::{builtin::setup_builtin, getopt::{Opt, OptSpec, get_opts_from_tokens}, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, procio::{IoStack, borrow_fd}, readline::complete::{BashCompSpec, CompContext, CompSpec}, state::{self, read_meta, write_meta}};
use crate::{
builtin::setup_builtin,
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
procio::{IoStack, borrow_fd},
readline::complete::{BashCompSpec, CompContext, CompSpec},
state::{self, read_meta, write_meta},
};
pub const COMPGEN_OPTS: [OptSpec;8] = [
OptSpec {
opt: Opt::Short('F'),
takes_arg: true
},
OptSpec {
opt: Opt::Short('W'),
takes_arg: true
},
OptSpec {
opt: Opt::Short('f'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('d'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('c'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('u'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('v'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('o'),
takes_arg: true
}
pub const COMPGEN_OPTS: [OptSpec; 9] = [
OptSpec {
opt: Opt::Short('F'),
takes_arg: true,
},
OptSpec {
opt: Opt::Short('W'),
takes_arg: true,
},
OptSpec {
opt: Opt::Short('j'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('f'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('d'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('c'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('u'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('v'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('o'),
takes_arg: true,
},
];
pub const COMP_OPTS: [OptSpec;11] = [
OptSpec {
opt: Opt::Short('F'),
takes_arg: true
},
OptSpec {
opt: Opt::Short('W'),
takes_arg: true
},
OptSpec {
opt: Opt::Short('A'),
takes_arg: true
},
OptSpec {
opt: Opt::Short('p'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('r'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('f'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('d'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('c'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('u'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('v'),
takes_arg: false
},
OptSpec {
opt: Opt::Short('o'),
takes_arg: true
}
pub const COMP_OPTS: [OptSpec; 12] = [
OptSpec {
opt: Opt::Short('F'),
takes_arg: true,
},
OptSpec {
opt: Opt::Short('W'),
takes_arg: true,
},
OptSpec {
opt: Opt::Short('A'),
takes_arg: true,
},
OptSpec {
opt: Opt::Short('j'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('p'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('r'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('f'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('d'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('c'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('u'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('v'),
takes_arg: false,
},
OptSpec {
opt: Opt::Short('o'),
takes_arg: true,
},
];
bitflags! {
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompFlags: u32 {
const FILES = 0b0000000001;
const DIRS = 0b0000000010;
const CMDS = 0b0000000100;
const USERS = 0b0000001000;
const VARS = 0b0000010000;
const PRINT = 0b0000100000;
const REMOVE = 0b0001000000;
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompOptFlags: u32 {
const DEFAULT = 0b0000000001;
const DIRNAMES = 0b0000000010;
const NOSPACE = 0b0000000100;
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompFlags: u32 {
const FILES = 0b0000000001;
const DIRS = 0b0000000010;
const CMDS = 0b0000000100;
const USERS = 0b0000001000;
const VARS = 0b0000010000;
const JOBS = 0b0000100000;
const PRINT = 0b0001000000;
const REMOVE = 0b0010000000;
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompOptFlags: u32 {
const DEFAULT = 0b0000000001;
const DIRNAMES = 0b0000000010;
const NOSPACE = 0b0000000100;
}
}
#[derive(Default, Debug, Clone)]
pub struct CompOpts {
pub func: Option<String>,
pub wordlist: Option<Vec<String>>,
pub action: Option<String>,
pub flags: CompFlags,
pub opt_flags: CompOptFlags,
pub func: Option<String>,
pub wordlist: Option<Vec<String>>,
pub action: Option<String>,
pub flags: CompFlags,
pub opt_flags: CompOptFlags,
}
pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
@@ -123,152 +141,150 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
unreachable!()
};
assert!(!argv.is_empty());
let src = argv.clone()
.into_iter()
.map(|tk| tk.expand().map(|tk| tk.get_words().join(" ")))
.collect::<ShResult<Vec<String>>>()?
.join(" ");
let src = argv
.clone()
.into_iter()
.map(|tk| tk.expand().map(|tk| tk.get_words().join(" ")))
.collect::<ShResult<Vec<String>>>()?
.join(" ");
let (argv, opts) = get_opts_from_tokens(argv, &COMP_OPTS)?;
let comp_opts = get_comp_opts(opts)?;
let (argv, _) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let comp_opts = get_comp_opts(opts)?;
let (argv, _) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if comp_opts.flags.contains(CompFlags::PRINT) {
if argv.is_empty() {
read_meta(|m| {
let specs = m.comp_specs().values();
for spec in specs {
println!("{}", spec.source());
}
})
} else {
read_meta(|m| {
for (cmd,_) in &argv {
if let Some(spec) = m.comp_specs().get(cmd) {
println!("{}", spec.source());
}
}
})
}
if comp_opts.flags.contains(CompFlags::PRINT) {
if argv.is_empty() {
read_meta(|m| {
let specs = m.comp_specs().values();
for spec in specs {
println!("{}", spec.source());
}
})
} else {
read_meta(|m| {
for (cmd, _) in &argv {
if let Some(spec) = m.comp_specs().get(cmd) {
println!("{}", spec.source());
}
}
})
}
state::set_status(0);
return Ok(());
}
state::set_status(0);
return Ok(());
}
if comp_opts.flags.contains(CompFlags::REMOVE) {
write_meta(|m| {
for (cmd,_) in &argv {
m.remove_comp_spec(cmd);
}
});
if comp_opts.flags.contains(CompFlags::REMOVE) {
write_meta(|m| {
for (cmd, _) in &argv {
m.remove_comp_spec(cmd);
}
});
state::set_status(0);
return Ok(());
}
state::set_status(0);
return Ok(());
}
if argv.is_empty() {
state::set_status(1);
return Err(ShErr::full(ShErrKind::ExecFail, "complete: no command specified", blame));
}
if argv.is_empty() {
state::set_status(1);
return Err(ShErr::full(
ShErrKind::ExecFail,
"complete: no command specified",
blame,
));
}
let comp_spec = BashCompSpec::from_comp_opts(comp_opts)
.with_source(src);
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
for (cmd,_) in argv {
write_meta(|m| m.set_comp_spec(cmd, Box::new(comp_spec.clone())));
}
for (cmd, _) in argv {
write_meta(|m| m.set_comp_spec(cmd, Box::new(comp_spec.clone())));
}
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}
pub fn compgen_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
assert!(!argv.is_empty());
let src = argv.clone()
.into_iter()
.map(|tk| tk.expand().map(|tk| tk.get_words().join(" ")))
.collect::<ShResult<Vec<String>>>()?
.join(" ");
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
assert!(!argv.is_empty());
let src = argv
.clone()
.into_iter()
.map(|tk| tk.expand().map(|tk| tk.get_words().join(" ")))
.collect::<ShResult<Vec<String>>>()?
.join(" ");
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
let prefix = argv
.clone()
.into_iter()
.nth(1)
.unwrap_or_default();
let comp_opts = get_comp_opts(opts)?;
let (_, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let (argv, opts) = get_opts_from_tokens(argv, &COMPGEN_OPTS)?;
let prefix = argv.clone().into_iter().nth(1).unwrap_or_default();
let comp_opts = get_comp_opts(opts)?;
let (_, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
let comp_spec = BashCompSpec::from_comp_opts(comp_opts)
.with_source(src);
let dummy_ctx = CompContext {
words: vec![prefix.clone()],
cword: 0,
line: prefix.to_string(),
cursor_pos: prefix.as_str().len(),
};
let dummy_ctx = CompContext {
words: vec![prefix.clone()],
cword: 0,
line: prefix.to_string(),
cursor_pos: prefix.as_str().len()
};
let results = comp_spec.complete(&dummy_ctx)?;
let results = comp_spec.complete(&dummy_ctx)?;
let stdout = borrow_fd(STDOUT_FILENO);
for result in &results {
write(stdout, result.as_bytes())?;
write(stdout, b"\n")?;
}
let stdout = borrow_fd(STDOUT_FILENO);
for result in &results {
write(stdout, result.as_bytes())?;
write(stdout, b"\n")?;
}
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}
pub fn get_comp_opts(opts: Vec<Opt>) -> ShResult<CompOpts> {
let mut comp_opts = CompOpts::default();
let mut comp_opts = CompOpts::default();
for opt in opts {
match opt {
Opt::ShortWithArg('F',func) => {
comp_opts.func = Some(func);
},
Opt::ShortWithArg('W',wordlist) => {
comp_opts.wordlist = Some(wordlist.split_whitespace().map(|s| s.to_string()).collect());
},
Opt::ShortWithArg('A',action) => {
comp_opts.action = Some(action);
}
Opt::ShortWithArg('o', opt_flag) => {
match opt_flag.as_str() {
"default" => comp_opts.opt_flags |= CompOptFlags::DEFAULT,
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
"nospace" => comp_opts.opt_flags |= CompOptFlags::NOSPACE,
_ => {
return Err(ShErr::full(
ShErrKind::InvalidOpt,
format!("complete: invalid option: {}", opt_flag),
Default::default()
));
}
}
}
for opt in opts {
match opt {
Opt::ShortWithArg('F', func) => {
comp_opts.func = Some(func);
}
Opt::ShortWithArg('W', wordlist) => {
comp_opts.wordlist = Some(wordlist.split_whitespace().map(|s| s.to_string()).collect());
}
Opt::ShortWithArg('A', action) => {
comp_opts.action = Some(action);
}
Opt::ShortWithArg('o', opt_flag) => match opt_flag.as_str() {
"default" => comp_opts.opt_flags |= CompOptFlags::DEFAULT,
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
"nospace" => comp_opts.opt_flags |= CompOptFlags::NOSPACE,
_ => {
return Err(ShErr::full(
ShErrKind::InvalidOpt,
format!("complete: invalid option: {}", opt_flag),
Default::default(),
));
}
},
Opt::Short('r') => comp_opts.flags |= CompFlags::REMOVE,
Opt::Short('p') => comp_opts.flags |= CompFlags::PRINT,
Opt::Short('f') => comp_opts.flags |= CompFlags::FILES,
Opt::Short('d') => comp_opts.flags |= CompFlags::DIRS,
Opt::Short('c') => comp_opts.flags |= CompFlags::CMDS,
Opt::Short('u') => comp_opts.flags |= CompFlags::USERS,
Opt::Short('v') => comp_opts.flags |= CompFlags::VARS,
_ => unreachable!()
}
}
Opt::Short('r') => comp_opts.flags |= CompFlags::REMOVE,
Opt::Short('j') => comp_opts.flags |= CompFlags::JOBS,
Opt::Short('p') => comp_opts.flags |= CompFlags::PRINT,
Opt::Short('f') => comp_opts.flags |= CompFlags::FILES,
Opt::Short('d') => comp_opts.flags |= CompFlags::DIRS,
Opt::Short('c') => comp_opts.flags |= CompFlags::CMDS,
Opt::Short('u') => comp_opts.flags |= CompFlags::USERS,
Opt::Short('v') => comp_opts.flags |= CompFlags::VARS,
_ => unreachable!(),
}
}
Ok(comp_opts)
Ok(comp_opts)
}

View File

@@ -2,396 +2,427 @@ use std::{env, path::PathBuf};
use nix::{libc::STDOUT_FILENO, unistd::write};
use crate::{builtin::setup_builtin, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node, lex::Span}, procio::{IoStack, borrow_fd}, state::{self, read_meta, write_meta}};
use crate::{
builtin::setup_builtin,
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node, lex::Span},
procio::{IoStack, borrow_fd},
state::{self, read_meta, write_meta},
};
enum StackIdx {
FromTop(usize),
FromBottom(usize),
FromTop(usize),
FromBottom(usize),
}
fn print_dirs() -> ShResult<()> {
let current_dir = env::current_dir()?;
let dirs_iter = read_meta(|m| {
m.dirs()
.clone()
.into_iter()
});
let all_dirs = [current_dir].into_iter().chain(dirs_iter)
.map(|d| d.to_string_lossy().to_string())
.map(|d| {
let Ok(home) = env::var("HOME") else {
return d;
};
let current_dir = env::current_dir()?;
let dirs_iter = read_meta(|m| m.dirs().clone().into_iter());
let all_dirs = [current_dir]
.into_iter()
.chain(dirs_iter)
.map(|d| d.to_string_lossy().to_string())
.map(|d| {
let Ok(home) = env::var("HOME") else {
return d;
};
if d.starts_with(&home) {
let new = d.strip_prefix(&home).unwrap();
format!("~{new}")
} else {
d
}
}).collect::<Vec<_>>()
.join(" ");
if d.starts_with(&home) {
let new = d.strip_prefix(&home).unwrap();
format!("~{new}")
} else {
d
}
})
.collect::<Vec<_>>()
.join(" ");
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, all_dirs.as_bytes())?;
write(stdout, b"\n")?;
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, all_dirs.as_bytes())?;
write(stdout, b"\n")?;
Ok(())
Ok(())
}
fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
if !target.is_dir() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("not a directory: {}", target.display()),
blame,
));
}
if !target.is_dir() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("not a directory: {}", target.display()),
blame,
));
}
if let Err(e) = env::set_current_dir(target) {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("Failed to change directory: {}", e),
blame,
));
}
let new_dir = env::current_dir().map_err(|e| {
ShErr::full(
ShErrKind::ExecFail,
format!("Failed to get current directory: {}", e),
blame,
)
})?;
unsafe { env::set_var("PWD", new_dir) };
Ok(())
if let Err(e) = env::set_current_dir(target) {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("Failed to change directory: {}", e),
blame,
));
}
let new_dir = env::current_dir().map_err(|e| {
ShErr::full(
ShErrKind::ExecFail,
format!("Failed to get current directory: {}", e),
blame,
)
})?;
unsafe { env::set_var("PWD", new_dir) };
Ok(())
}
fn parse_stack_idx(arg: &str, blame: Span, cmd: &str) -> ShResult<StackIdx> {
let (from_top, digits) = if let Some(rest) = arg.strip_prefix('+') {
(true, rest)
} else if let Some(rest) = arg.strip_prefix('-') {
(false, rest)
} else {
unreachable!()
};
let (from_top, digits) = if let Some(rest) = arg.strip_prefix('+') {
(true, rest)
} else if let Some(rest) = arg.strip_prefix('-') {
(false, rest)
} else {
unreachable!()
};
if digits.is_empty() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("{cmd}: missing index after '{}'", if from_top { "+" } else { "-" }),
blame,
));
}
if digits.is_empty() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!(
"{cmd}: missing index after '{}'",
if from_top { "+" } else { "-" }
),
blame,
));
}
for ch in digits.chars() {
if !ch.is_ascii_digit() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("{cmd}: invalid argument: {arg}"),
blame,
));
}
}
for ch in digits.chars() {
if !ch.is_ascii_digit() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("{cmd}: invalid argument: {arg}"),
blame,
));
}
}
let n = digits.parse::<usize>().map_err(|e| {
ShErr::full(
ShErrKind::ExecFail,
format!("{cmd}: invalid index: {e}"),
blame,
)
})?;
let n = digits.parse::<usize>().map_err(|e| {
ShErr::full(
ShErrKind::ExecFail,
format!("{cmd}: invalid index: {e}"),
blame,
)
})?;
if from_top {
Ok(StackIdx::FromTop(n))
} else {
Ok(StackIdx::FromBottom(n))
}
if from_top {
Ok(StackIdx::FromTop(n))
} else {
Ok(StackIdx::FromBottom(n))
}
}
pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv
} = node.class else { unreachable!() };
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let mut dir = None;
let mut rotate_idx = None;
let mut no_cd = false;
let mut dir = None;
let mut rotate_idx = None;
let mut no_cd = false;
for (arg, _) in argv {
if arg.starts_with('+') || (arg.starts_with('-') && arg.len() > 1 && arg.as_bytes()[1].is_ascii_digit()) {
rotate_idx = Some(parse_stack_idx(&arg, blame.clone(), "pushd")?);
} else if arg == "-n" {
no_cd = true;
} else if arg.starts_with('-') {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("pushd: invalid option: {arg}"),
blame.clone(),
));
} else {
if dir.is_some() {
return Err(ShErr::full(
ShErrKind::ExecFail,
"pushd: too many arguments".to_string(),
blame.clone(),
));
}
let target = PathBuf::from(&arg);
if !target.is_dir() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("pushd: not a directory: {arg}"),
blame.clone(),
));
}
dir = Some(target);
}
}
for (arg, _) in argv {
if arg.starts_with('+')
|| (arg.starts_with('-') && arg.len() > 1 && arg.as_bytes()[1].is_ascii_digit())
{
rotate_idx = Some(parse_stack_idx(&arg, blame.clone(), "pushd")?);
} else if arg == "-n" {
no_cd = true;
} else if arg.starts_with('-') {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("pushd: invalid option: {arg}"),
blame.clone(),
));
} else {
if dir.is_some() {
return Err(ShErr::full(
ShErrKind::ExecFail,
"pushd: too many arguments".to_string(),
blame.clone(),
));
}
let target = PathBuf::from(&arg);
if !target.is_dir() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("pushd: not a directory: {arg}"),
blame.clone(),
));
}
dir = Some(target);
}
}
if let Some(idx) = rotate_idx {
let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
let new_cwd = write_meta(|m| {
let dirs = m.dirs_mut();
dirs.push_front(cwd);
match idx {
StackIdx::FromTop(n) => dirs.rotate_left(n),
StackIdx::FromBottom(n) => dirs.rotate_right(n + 1),
}
dirs.pop_front()
});
if let Some(idx) = rotate_idx {
let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
let new_cwd = write_meta(|m| {
let dirs = m.dirs_mut();
dirs.push_front(cwd);
match idx {
StackIdx::FromTop(n) => dirs.rotate_left(n),
StackIdx::FromBottom(n) => dirs.rotate_right(n + 1),
}
dirs.pop_front()
});
if let Some(dir) = new_cwd
&& !no_cd {
change_directory(&dir, blame)?;
print_dirs()?;
}
} else if let Some(dir) = dir {
let old_dir = env::current_dir()?;
if old_dir != dir {
write_meta(|m| m.push_dir(old_dir));
}
if let Some(dir) = new_cwd
&& !no_cd
{
change_directory(&dir, blame)?;
print_dirs()?;
}
} else if let Some(dir) = dir {
let old_dir = env::current_dir()?;
if old_dir != dir {
write_meta(|m| m.push_dir(old_dir));
}
if no_cd {
state::set_status(0);
return Ok(());
}
if no_cd {
state::set_status(0);
return Ok(());
}
change_directory(&dir, blame)?;
print_dirs()?;
}
change_directory(&dir, blame)?;
print_dirs()?;
}
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}
pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv
} = node.class else { unreachable!() };
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let mut remove_idx = None;
let mut no_cd = false;
let mut remove_idx = None;
let mut no_cd = false;
for (arg, _) in argv {
if arg.starts_with('+') || (arg.starts_with('-') && arg.len() > 1 && arg.as_bytes()[1].is_ascii_digit()) {
remove_idx = Some(parse_stack_idx(&arg, blame.clone(), "popd")?);
} else if arg == "-n" {
no_cd = true;
} else if arg.starts_with('-') {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("popd: invalid option: {arg}"),
blame.clone(),
));
}
}
for (arg, _) in argv {
if arg.starts_with('+')
|| (arg.starts_with('-') && arg.len() > 1 && arg.as_bytes()[1].is_ascii_digit())
{
remove_idx = Some(parse_stack_idx(&arg, blame.clone(), "popd")?);
} else if arg == "-n" {
no_cd = true;
} else if arg.starts_with('-') {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("popd: invalid option: {arg}"),
blame.clone(),
));
}
}
if let Some(idx) = remove_idx {
match idx {
StackIdx::FromTop(0) => {
// +0 is same as plain popd: pop top, cd to it
let dir = write_meta(|m| m.pop_dir());
if !no_cd {
if let Some(dir) = dir {
change_directory(&dir, blame.clone())?;
} else {
return Err(ShErr::full(
ShErrKind::ExecFail,
"popd: directory stack empty".to_string(),
blame.clone(),
));
}
}
}
StackIdx::FromTop(n) => {
// +N (N>0): remove (N-1)th stored entry, no cd
write_meta(|m| {
let dirs = m.dirs_mut();
let idx = n - 1;
if idx >= dirs.len() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("popd: directory index out of range: +{n}"),
blame.clone(),
));
}
dirs.remove(idx);
Ok(())
})?;
}
StackIdx::FromBottom(n) => {
write_meta(|m| -> ShResult<()> {
let dirs = m.dirs_mut();
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
ShErr::full(
ShErrKind::ExecFail,
format!("popd: directory index out of range: -{n}"),
blame.clone(),
)
})?;
dirs.remove(actual);
Ok(())
})?;
}
}
print_dirs()?;
} else {
let dir = write_meta(|m| m.pop_dir());
if let Some(idx) = remove_idx {
match idx {
StackIdx::FromTop(0) => {
// +0 is same as plain popd: pop top, cd to it
let dir = write_meta(|m| m.pop_dir());
if !no_cd {
if let Some(dir) = dir {
change_directory(&dir, blame.clone())?;
} else {
return Err(ShErr::full(
ShErrKind::ExecFail,
"popd: directory stack empty".to_string(),
blame.clone(),
));
}
}
}
StackIdx::FromTop(n) => {
// +N (N>0): remove (N-1)th stored entry, no cd
write_meta(|m| {
let dirs = m.dirs_mut();
let idx = n - 1;
if idx >= dirs.len() {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("popd: directory index out of range: +{n}"),
blame.clone(),
));
}
dirs.remove(idx);
Ok(())
})?;
}
StackIdx::FromBottom(n) => {
write_meta(|m| -> ShResult<()> {
let dirs = m.dirs_mut();
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
ShErr::full(
ShErrKind::ExecFail,
format!("popd: directory index out of range: -{n}"),
blame.clone(),
)
})?;
dirs.remove(actual);
Ok(())
})?;
}
}
print_dirs()?;
} else {
let dir = write_meta(|m| m.pop_dir());
if no_cd {
state::set_status(0);
return Ok(());
}
if no_cd {
state::set_status(0);
return Ok(());
}
if let Some(dir) = dir {
change_directory(&dir, blame.clone())?;
print_dirs()?;
} else {
return Err(ShErr::full(
ShErrKind::ExecFail,
"popd: directory stack empty".to_string(),
blame.clone(),
));
}
}
if let Some(dir) = dir {
change_directory(&dir, blame.clone())?;
print_dirs()?;
} else {
return Err(ShErr::full(
ShErrKind::ExecFail,
"popd: directory stack empty".to_string(),
blame.clone(),
));
}
}
Ok(())
Ok(())
}
pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv
} = node.class else { unreachable!() };
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
} = node.class
else {
unreachable!()
};
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
let mut abbreviate_home = true;
let mut one_per_line = false;
let mut one_per_line_indexed = false;
let mut clear_stack = false;
let mut target_idx: Option<StackIdx> = None;
let mut abbreviate_home = true;
let mut one_per_line = false;
let mut one_per_line_indexed = false;
let mut clear_stack = false;
let mut target_idx: Option<StackIdx> = None;
for (arg,_) in argv {
match arg.as_str() {
"-p" => one_per_line = true,
"-v" => one_per_line_indexed = true,
"-c" => clear_stack = true,
"-l" => abbreviate_home = false,
_ if (arg.starts_with('+') || arg.starts_with('-')) && arg.len() > 1 && arg.as_bytes()[1].is_ascii_digit() => {
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
}
_ if arg.starts_with('-') => {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("dirs: invalid option: {arg}"),
blame.clone(),
));
}
_ => {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("dirs: unexpected argument: {arg}"),
blame.clone(),
));
}
}
}
for (arg, _) in argv {
match arg.as_str() {
"-p" => one_per_line = true,
"-v" => one_per_line_indexed = true,
"-c" => clear_stack = true,
"-l" => abbreviate_home = false,
_ if (arg.starts_with('+') || arg.starts_with('-'))
&& arg.len() > 1
&& arg.as_bytes()[1].is_ascii_digit() =>
{
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
}
_ if arg.starts_with('-') => {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("dirs: invalid option: {arg}"),
blame.clone(),
));
}
_ => {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("dirs: unexpected argument: {arg}"),
blame.clone(),
));
}
}
}
if clear_stack {
write_meta(|m| m.dirs_mut().clear());
return Ok(())
}
if clear_stack {
write_meta(|m| m.dirs_mut().clear());
return Ok(());
}
let mut dirs: Vec<String> = read_meta(|m| {
let current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
let stack = [current_dir]
.into_iter()
.chain(m.dirs().clone())
.map(|d| d.to_string_lossy().to_string());
let mut dirs: Vec<String> = read_meta(|m| {
let current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
let stack = [current_dir].into_iter()
.chain(m.dirs().clone())
.map(|d| d.to_string_lossy().to_string());
if abbreviate_home {
let Ok(home) = env::var("HOME") else {
return stack.collect();
};
stack
.map(|d| {
if d.starts_with(&home) {
let new = d.strip_prefix(&home).unwrap();
format!("~{new}")
} else {
d
}
})
.collect()
} else {
stack.collect()
}
});
if abbreviate_home {
let Ok(home) = env::var("HOME") else {
return stack.collect();
};
stack.map(|d| {
if d.starts_with(&home) {
let new = d.strip_prefix(&home).unwrap();
format!("~{new}")
} else {
d
}
}).collect()
} else {
stack.collect()
}
});
if let Some(idx) = target_idx {
let target = match idx {
StackIdx::FromTop(n) => dirs.get(n),
StackIdx::FromBottom(n) => dirs.get(dirs.len().saturating_sub(n + 1)),
};
if let Some(idx) = target_idx {
let target = match idx {
StackIdx::FromTop(n) => dirs.get(n),
StackIdx::FromBottom(n) => dirs.get(dirs.len().saturating_sub(n + 1)),
};
if let Some(dir) = target {
dirs = vec![dir.clone()];
} else {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!(
"dirs: directory index out of range: {}",
match idx {
StackIdx::FromTop(n) => format!("+{n}"),
StackIdx::FromBottom(n) => format!("-{n}"),
}
),
blame.clone(),
));
}
}
if let Some(dir) = target {
dirs = vec![dir.clone()];
} else {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("dirs: directory index out of range: {}", match idx {
StackIdx::FromTop(n) => format!("+{n}"),
StackIdx::FromBottom(n) => format!("-{n}"),
}),
blame.clone(),
));
}
}
let mut output = String::new();
let mut output = String::new();
if one_per_line {
output = dirs.join("\n");
} else if one_per_line_indexed {
for (i, dir) in dirs.iter_mut().enumerate() {
*dir = format!("{i}\t{dir}");
}
output = dirs.join("\n");
output.push('\n');
} else {
print_dirs()?;
}
if one_per_line {
output = dirs.join("\n");
} else if one_per_line_indexed {
for (i, dir) in dirs.iter_mut().enumerate() {
*dir = format!("{i}\t{dir}");
}
output = dirs.join("\n");
output.push('\n');
} else {
print_dirs()?;
}
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, output.as_bytes())?;
let stdout = borrow_fd(STDOUT_FILENO);
write(stdout, output.as_bytes())?;
Ok(())
Ok(())
}

View File

@@ -1,12 +1,12 @@
use crate::{
builtin::setup_builtin,
expand::expand_prompt,
getopt::{get_opts_from_tokens, Opt, OptSpec},
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
procio::{IoStack, borrow_fd},
state,
};
@@ -30,7 +30,7 @@ pub const ECHO_OPTS: [OptSpec; 4] = [
];
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EchoFlags: u32 {
const NO_NEWLINE = 0b000001;
const USE_STDERR = 0b000010;
@@ -59,7 +59,6 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
borrow_fd(STDOUT_FILENO)
};
let mut echo_output = prepare_echo_args(
argv
.into_iter()
@@ -197,7 +196,6 @@ pub fn prepare_echo_args(
prepared_args.push(prepared_arg);
}
Ok(prepared_args)
}

View File

@@ -2,7 +2,7 @@ use crate::{
builtin::setup_builtin,
jobs::JobBldr,
libsh::error::ShResult,
parse::{execute::exec_input, NdRule, Node},
parse::{NdRule, Node, execute::exec_input},
procio::IoStack,
state,
};

View File

@@ -4,7 +4,7 @@ use crate::{
builtin::setup_builtin,
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{execute::ExecArgs, NdRule, Node},
parse::{NdRule, Node, execute::ExecArgs},
procio::IoStack,
state,
};

View File

@@ -1,6 +1,6 @@
use crate::{
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{execute::prepare_argv, NdRule, Node},
parse::{NdRule, Node, execute::prepare_argv},
};
pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
@@ -31,7 +31,7 @@ pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
code = status;
}
let (kind,message) = match kind {
let (kind, message) = match kind {
LoopContinue(_) => (LoopContinue(code), "'continue' found outside of loop"),
LoopBreak(_) => (LoopBreak(code), "'break' found outside of loop"),
FuncReturn(_) => (FuncReturn(code), "'return' found outside of function"),

View File

@@ -1,9 +1,9 @@
use crate::{
jobs::{JobBldr, JobCmdFlags, JobID},
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{lex::Span, NdRule, Node},
parse::{NdRule, Node, lex::Span},
prelude::*,
procio::{borrow_fd, IoStack},
procio::{IoStack, borrow_fd},
state::{self, read_jobs, write_jobs},
};

View File

@@ -8,13 +8,17 @@ use crate::{
execute::prepare_argv,
lex::{Span, Tk},
},
procio::{IoStack, RedirGuard}, state,
procio::{IoStack, RedirGuard},
state,
};
pub mod alias;
pub mod cd;
pub mod complete;
pub mod dirstack;
pub mod echo;
pub mod varcmds;
pub mod eval;
pub mod exec;
pub mod flowctl;
pub mod jobctl;
pub mod pwd;
@@ -24,16 +28,14 @@ pub mod shopt;
pub mod source;
pub mod test; // [[ ]] thing
pub mod trap;
pub mod varcmds;
pub mod zoltraak;
pub mod dirstack;
pub mod exec;
pub mod eval;
pub mod complete;
pub const BUILTINS: [&str; 35] = [
"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"
"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",
];
/// Sets up a builtin command
@@ -96,16 +98,16 @@ pub fn setup_builtin(
}
pub fn true_builtin() -> ShResult<()> {
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}
pub fn false_builtin() -> ShResult<()> {
state::set_status(1);
Ok(())
state::set_status(1);
Ok(())
}
pub fn noop_builtin() -> ShResult<()> {
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}

View File

@@ -3,7 +3,7 @@ use crate::{
libsh::error::ShResult,
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
procio::{IoStack, borrow_fd},
state,
};

View File

@@ -7,13 +7,13 @@ use nix::{
use crate::{
builtin::setup_builtin,
getopt::{get_opts_from_tokens, Opt, OptSpec},
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
procio::{borrow_fd, IoStack},
procio::{IoStack, borrow_fd},
readline::term::RawModeGuard,
state::{self, read_vars, write_vars, VarFlags, VarKind},
state::{self, VarFlags, VarKind, read_vars, write_vars},
};
pub const READ_OPTS: [OptSpec; 7] = [
@@ -81,7 +81,10 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
write(borrow_fd(STDOUT_FILENO), prompt.as_bytes())?;
}
log::info!("read_builtin: starting read with delim={}", read_opts.delim as char);
log::info!(
"read_builtin: starting read with delim={}",
read_opts.delim as char
);
let input = if isatty(STDIN_FILENO)? {
// Restore default terminal settings
@@ -182,9 +185,7 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S
};
if argv.is_empty() {
write_vars(|v| {
v.set_var("REPLY", VarKind::Str(input.clone()), VarFlags::NONE)
})?;
write_vars(|v| v.set_var("REPLY", VarKind::Str(input.clone()), VarFlags::NONE))?;
} else {
// get our field separator
let mut field_sep = read_vars(|v| v.get_var("IFS"));

View File

@@ -20,16 +20,16 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
let mut output = write_shopts(|s| s.display_opts())?;
if argv.is_empty() {
let mut output = write_shopts(|s| s.display_opts())?;
let output_channel = borrow_fd(STDOUT_FILENO);
output.push('\n');
write(output_channel, output.as_bytes())?;
state::set_status(0);
return Ok(())
}
state::set_status(0);
return Ok(());
}
for (arg, span) in argv {
let Some(mut output) = write_shopts(|s| s.query(&arg)).blame(span)? else {
@@ -42,7 +42,6 @@ pub fn shopt(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
write(output_channel, output.as_bytes())?;
}
state::set_status(0);
state::set_status(0);
Ok(())
}

View File

@@ -8,7 +8,7 @@ use regex::Regex;
use crate::{
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{ConjunctOp, NdRule, Node, TestCase, TEST_UNARY_OPS},
parse::{ConjunctOp, NdRule, Node, TEST_UNARY_OPS, TestCase},
};
#[derive(Debug, Clone)]

View File

@@ -11,7 +11,7 @@ use crate::{
jobs::JobBldr,
libsh::error::{ShErr, ShErrKind, ShResult},
parse::{NdRule, Node},
procio::{borrow_fd, IoStack},
procio::{IoStack, borrow_fd},
state::{self, read_logic, write_logic},
};
@@ -59,12 +59,10 @@ impl FromStr for TrapTarget {
"IO" => Ok(TrapTarget::Signal(Signal::SIGIO)),
"PWR" => Ok(TrapTarget::Signal(Signal::SIGPWR)),
"SYS" => Ok(TrapTarget::Signal(Signal::SIGSYS)),
_ => {
Err(ShErr::simple(
ShErrKind::ExecFail,
format!("invalid trap target '{}'", s),
))
}
_ => Err(ShErr::simple(
ShErrKind::ExecFail,
format!("invalid trap target '{}'", s),
)),
}
}
}

View File

@@ -26,7 +26,7 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
let mut vars = v
.flatten_vars()
.into_iter()
.filter(|(_, v)| v.flags().contains(VarFlags::READONLY))
.filter(|(_, v)| v.flags().contains(VarFlags::READONLY))
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<String>>();
vars.sort();
@@ -47,12 +47,12 @@ pub fn readonly(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResu
}
}
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}
pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
let blame = node.get_span().clone();
let blame = node.get_span().clone();
let NdRule::Command {
assignments: _,
argv,
@@ -63,27 +63,27 @@ pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?;
if argv.is_empty() {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
"unset: Expected at least one argument",
blame
));
}
if argv.is_empty() {
return Err(ShErr::full(
ShErrKind::SyntaxErr,
"unset: Expected at least one argument",
blame,
));
}
for (arg,span) in argv {
if !read_vars(|v| v.var_exists(&arg)) {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("unset: No such variable '{arg}'"),
span
));
}
write_vars(|v| v.unset_var(&arg))?;
}
for (arg, span) in argv {
if !read_vars(|v| v.var_exists(&arg)) {
return Err(ShErr::full(
ShErrKind::ExecFail,
format!("unset: No such variable '{arg}'"),
span,
));
}
write_vars(|v| v.unset_var(&arg))?;
}
state::set_status(0);
Ok(())
state::set_status(0);
Ok(())
}
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
@@ -114,7 +114,7 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
write_vars(|v| v.set_var(var, VarKind::Str(val.to_string()), VarFlags::EXPORT))?;
} else {
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
// any
// any
}
}
}

View File

@@ -1,12 +1,12 @@
use std::os::unix::fs::OpenOptionsExt;
use crate::{
getopt::{get_opts_from_tokens, Opt, OptSpec},
getopt::{Opt, OptSpec, get_opts_from_tokens},
jobs::JobBldr,
libsh::error::{Note, ShErr, ShErrKind, ShResult, ShResultExt},
parse::{NdRule, Node},
prelude::*,
procio::{borrow_fd, IoStack},
procio::{IoStack, borrow_fd},
};
use super::setup_builtin;