Added -j flag to 'complete' for completing job names/pids
This commit is contained in:
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{borrow_fd, IoStack},
|
||||
procio::{IoStack, borrow_fd},
|
||||
state,
|
||||
};
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user