use crate::compsys::ported::_arguments::_arguments;
use crate::compsys::ported::_files::_files;
use crate::compsys::ported::shared::glob_matches;
use crate::ported::exec::dispatch_function_call;
use crate::ported::params::{getaparam, getsparam, setaparam, setsparam};
fn build_arguments() -> Vec<String> {
[
"(--eof -e)--eof=[change the end-of-input-args string from \"--\" to eof-str]",
"(--eof -e)-e+[change the end-of-input-args string from \"--\" to eof-str]",
"(--exit -x)--exit[exit if the size (see --max-chars) is exceeded]",
"(--exit -x)-x[exit if the size (see --max-chars) is exceeded]",
"--help[print summary and exit]",
"(--interactive -p)--interactive[prompt before executing each command line]",
"(--interactive -p)-p[prompt before executing each command line]",
"(--max-args -n)--max-args=[use at most max-args arguments per command line]",
"(--max-args -n)-n+[use at most max-args arguments per command line]",
"(--max-chars -s)--max-chars=[use at most max-chars characters per command line]",
"(--max-chars -s)-s+[use at most max-chars characters per command line]",
"(--max-lines -l)--max-lines=[use at most max-lines of the input-args per command line]",
"(--max-lines -l)-l+[use at most max-lines of the input-args per command line]",
"(--max-procs -P)--max-procs=[run up to max-procs command lines in the background at once]",
"(--max-procs -P)-P+[run up to max-procs command lines in the background at once]",
"(--no-run-if-empty -r)--no-run-if-empty[do nothing if there are no input arguments before the eof-str]",
"(--no-run-if-empty -r)-r[do nothing if there are no input arguments before the eof-str]",
"(--null -0)--null[split each input-arg at null bytes, for xargs compatibility]",
"(--null -0)-0[split each input-arg at null bytes, for xargs compatibility]",
"(--replace -i)--replace=[substitute replace-str in the initial-args by each initial-arg]",
"(--replace -i)-i[substitute replace-str in the initial-args by each initial-arg]",
"(--verbose -t)--verbose[print each command line to stderr before executing it]",
"(--verbose -t)-t[print each command line to stderr before executing it]",
"--version[print the version number of zargs and exit]",
]
.iter()
.map(|s| s.to_string())
.collect()
}
fn compute_eofstr(words: &[String]) -> String {
let found = words
.iter()
.find(|w| w.starts_with("--eof=") || w.starts_with("-e"))
.cloned()
.unwrap_or_default();
let s = found.strip_prefix("--eof=").unwrap_or(&found);
let s = s.strip_prefix("-e").unwrap_or(s);
if s.is_empty() {
"--".to_string()
} else {
s.to_string()
}
}
fn find_eof_backward(words: &[String], begin: i64, eofstr: &str) -> i64 {
if begin < 1 {
return 0;
}
let n = words.len() as i64;
let mut i = begin.min(n);
while i >= 1 {
if glob_matches(eofstr, &words[(i - 1) as usize]) {
return i;
}
i -= 1;
}
0
}
fn scan_eofs(words: &[String], current: i64, eofstr: &str) -> (i64, i64) {
let mut pos = current; let mut numeofs = 0i64; let mut cmdpos = 1i64; loop {
pos = find_eof_backward(words, pos - 1, eofstr);
if numeofs == 0 {
cmdpos = pos;
}
if pos != 0 {
numeofs += 1;
}
if pos == 0 {
break;
}
}
(numeofs, cmdpos)
}
pub fn _zargs(_args: &[String]) -> i32 {
let _fn_scope = crate::compsys::ported::shared::FnScope::enter("_zargs");
let mut ret = 1;
let words = getaparam("words").unwrap_or_default();
let current: i64 = getsparam("CURRENT")
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let eofstr = compute_eofstr(&words);
let (numeofs, cmdpos) = scan_eofs(&words, current, &eofstr);
match numeofs {
0 => {
let mut call = vec!["-S".to_string(), "-s".to_string()];
call.extend(build_arguments());
if crate::compsys::ported::shared::call_compfn("_arguments", &call, || {
_arguments(&call)
}) == 0
{
ret = 0; }
}
1 => {
if crate::compsys::ported::shared::call_compfn("_files", &[], || _files(&[])) == 0 {
ret = 0;
}
}
_ => {
let drop = cmdpos.max(0) as usize;
let mut w = words;
if drop <= w.len() {
w.drain(0..drop);
} else {
w.clear();
}
setaparam("words", w);
let _ = setsparam("CURRENT", &(current - cmdpos).to_string());
let _ = dispatch_function_call("_normal", &[]);
}
}
ret }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn eofstr_defaults_to_dashdash() {
assert_eq!(compute_eofstr(&["zargs".into(), "-x".into()]), "--");
assert_eq!(compute_eofstr(&["zargs".into(), "-e".into()]), "--");
assert_eq!(compute_eofstr(&["zargs".into(), "--eof=".into()]), "--");
}
#[test]
fn eofstr_from_long_and_short_forms() {
assert_eq!(compute_eofstr(&["zargs".into(), "--eof=EOF".into()]), "EOF");
assert_eq!(compute_eofstr(&["zargs".into(), "-eEOF".into()]), "EOF");
assert_eq!(
compute_eofstr(&["zargs".into(), "-eFIRST".into(), "--eof=SECOND".into()]),
"FIRST"
);
}
#[test]
fn find_eof_backward_returns_highest_match_at_or_below_begin() {
let w: Vec<String> = ["zargs", "--", "a", "b", "--", "cmd"]
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(find_eof_backward(&w, 6, "--"), 5);
assert_eq!(find_eof_backward(&w, 4, "--"), 2);
assert_eq!(find_eof_backward(&w, 1, "--"), 0);
assert_eq!(find_eof_backward(&w, 0, "--"), 0);
}
#[test]
fn scan_eofs_classifies_option_file_and_command_positions() {
let w0: Vec<String> = ["zargs", "-"].iter().map(|s| s.to_string()).collect();
let (n0, _c0) = scan_eofs(&w0, 2, "--");
assert_eq!(n0, 0);
let w1: Vec<String> = ["zargs", "--"].iter().map(|s| s.to_string()).collect();
let (n1, c1) = scan_eofs(&w1, 3, "--");
assert_eq!(n1, 1);
assert_eq!(c1, 2);
let w2: Vec<String> = ["zargs", "--", "a", "b", "--", "cmd"]
.iter()
.map(|s| s.to_string())
.collect();
let (n2, c2) = scan_eofs(&w2, 7, "--");
assert_eq!(n2, 2);
assert_eq!(c2, 5);
}
#[test]
fn build_arguments_flattens_brace_specs() {
let a = build_arguments();
assert_eq!(a.len(), 24);
assert!(a.contains(
&"(--eof -e)--eof=[change the end-of-input-args string from \"--\" to eof-str]"
.to_string()
));
assert!(a.contains(
&"(--eof -e)-e+[change the end-of-input-args string from \"--\" to eof-str]"
.to_string()
));
assert!(a.contains(&"--help[print summary and exit]".to_string()));
assert!(a.contains(&"--version[print the version number of zargs and exit]".to_string()));
}
#[test]
fn shift_command_words_matches_precommand_shape() {
let _g = crate::test_util::global_state_lock();
crate::ported::zle::complete::INCOMPFUNC.store(0, std::sync::atomic::Ordering::Relaxed);
setaparam(
"words",
["zargs", "--", "a", "b", "--", "cmd"]
.iter()
.map(|s| s.to_string())
.collect(),
);
let _ = setsparam("CURRENT", "7");
let _ = _zargs(&[]);
assert_eq!(
getaparam("words").unwrap_or_default(),
vec!["cmd".to_string()]
);
assert_eq!(getsparam("CURRENT").unwrap_or_default(), "2");
}
}