use crate::ported::params::{getaparam, getsparam, setaparam, setsparam, unsetparam};
use crate::ported::zle::compcore::get_compstate_str;
use crate::ported::zle::complete::bin_compadd;
use crate::ported::zsh_h::{options, MAX_OPS};
fn make_ops() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
struct ParsedOpts {
opts: Vec<String>,
group: Vec<String>,
expl: Vec<String>,
matcher: Vec<String>,
rest: Vec<String>,
}
fn zparse(args: &[String]) -> ParsedOpts {
let mut opts = Vec::new();
let mut group = Vec::new();
let mut expl = Vec::new();
let mut matcher = Vec::new();
let mut i = 0usize;
while i < args.len() {
let a = &args[i];
if a == "--" {
i += 1;
break;
}
if !(a.starts_with('-') && a.len() >= 2) {
break;
}
let flag = a.as_bytes()[1] as char;
let attached: Option<String> = if a.len() > 2 {
Some(a[2..].to_string())
} else {
None
};
match flag {
'J' | 'V' | 'x' | 'X' | 'M' | 'P' | 'F' | 'S' | 'r' | 'R' | 'o' => {
let dest = match flag {
'J' | 'V' => &mut group,
'x' | 'X' => &mut expl,
'M' => &mut matcher,
_ => &mut opts,
};
dest.push(format!("-{}", flag));
if let Some(v) = attached {
dest.push(v);
i += 1;
} else if i + 1 < args.len() {
dest.push(args[i + 1].clone());
i += 2;
} else {
i += 1;
}
}
'q' | '1' | '2' | 'n' => {
opts.push(format!("-{}", flag));
i += 1;
}
_ => break,
}
}
ParsedOpts {
opts,
group,
expl,
matcher,
rest: args[i..].to_vec(),
}
}
fn arrlen(name: &str) -> usize {
getaparam(name).map(|v| v.len()).unwrap_or(0)
}
fn q_quote(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
if c.is_ascii_alphanumeric()
|| matches!(c, '@' | '%' | '+' | '=' | ':' | ',' | '.' | '/' | '-' | '_')
{
out.push(c);
} else {
out.push('\\');
out.push(c);
}
}
out
}
fn resolve_array_name(arr: &str) -> String {
if arr.starts_with('(') && arr.ends_with(')') && arr.len() >= 2 {
let inner = &arr[1..arr.len() - 1];
let elems: Vec<String> = inner.split_whitespace().map(|w| w.to_string()).collect();
setaparam("tmparr", elems);
"tmparr".to_string()
} else {
arr.to_string()
}
}
pub fn _sep_parts(args: &[String]) -> i32 {
let nm: i64 = get_compstate_str("nmatches")
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let parsed = zparse(args);
let ParsedOpts {
opts,
group,
expl,
matcher,
rest,
} = parsed;
let comp_correct = getsparam("_comp_correct").unwrap_or_default();
let _opre = getsparam("PREFIX").unwrap_or_default();
let _osuf = getsparam("SUFFIX").unwrap_or_default();
let mut str = format!(
"{}{}",
getsparam("PREFIX").unwrap_or_default(),
getsparam("SUFFIX").unwrap_or_default()
);
let _ = setsparam("SUFFIX", "");
let mut prefix = String::new();
let mut pos = 0usize;
let nargs = |pos: usize| rest.len().saturating_sub(pos);
while nargs(pos) > 1 {
let arr = resolve_array_name(&rest[pos]);
let sep = rest[pos + 1].clone();
if !str.contains(&sep) {
break;
}
let seg = {
let cut = str.find(&sep).unwrap();
let head = &str[..cut];
head.strip_suffix('\\').unwrap_or(head).to_string()
};
let _ = setsparam("PREFIX", &seg);
let mut argv: Vec<String> = vec!["-O".into(), "testarr".into()];
argv.extend(matcher.iter().cloned());
argv.push("-a".into());
argv.push(arr.clone());
let _ = bin_compadd("compadd", &argv, &make_ops(), 0);
if arrlen("testarr") == 0 && !comp_correct.is_empty() {
let _ = bin_compadd("compadd", &argv, &make_ops(), 0);
}
let testarr = getaparam("testarr").unwrap_or_default();
if testarr.is_empty() {
let _ = unsetparam("testarr");
let _ = unsetparam("tmparr");
return 1;
}
if testarr.len() > 1 {
break;
}
prefix.push_str(&testarr[0]);
prefix.push_str(&sep);
str = str.splitn(2, &sep).nth(1).unwrap_or("").to_string();
pos += 2; }
let arr = if pos < rest.len() {
resolve_array_name(&rest[pos])
} else {
String::new()
};
let build = nargs(pos) <= 1 || {
let sep2 = &rest[pos + 1];
!str.contains(sep2 as &str)
};
if build {
let _ = setsparam("PREFIX", &str);
let mut argv: Vec<String> = vec!["-O".into(), "testarr".into()];
argv.extend(matcher.iter().cloned());
argv.push("-a".into());
argv.push(arr.clone());
let _ = bin_compadd("compadd", &argv, &make_ops(), 0);
if arrlen("testarr") == 0 && !comp_correct.is_empty() {
let _ = bin_compadd("compadd", &argv, &make_ops(), 0);
}
}
let testarr = getaparam("testarr").unwrap_or_default();
if testarr.is_empty() || testarr[0].is_empty() {
let _ = unsetparam("testarr");
let _ = unsetparam("tmparr");
return 1;
}
pos += 1;
let mut suffixes: Vec<String> = vec![String::new()];
let mut autosuffix: Vec<String> = Vec::new();
while nargs(pos) > 0 && str.contains(&rest[pos]) {
let sep = rest[pos].clone();
str = str.splitn(2, &sep).nth(1).unwrap_or("").to_string();
if nargs(pos) > 2 {
let sep3 = &rest[pos + 2];
let seg = str.splitn(2, sep3 as &str).next().unwrap_or("").to_string();
let _ = setsparam("PREFIX", &seg);
} else {
let _ = setsparam("PREFIX", &str);
}
let arr2 = if pos + 1 < rest.len() {
resolve_array_name(&rest[pos + 1])
} else {
String::new()
};
let mut argv: Vec<String> = vec!["-O".into(), "tmparr".into()];
argv.extend(matcher.iter().cloned());
argv.push("-a".into());
argv.push(arr2.clone());
let _ = bin_compadd("compadd", &argv, &make_ops(), 0);
if arrlen("tmparr") == 0 && !comp_correct.is_empty() {
let mut argv2: Vec<String> = vec!["-O".into(), "tmparr".into()];
argv2.extend(matcher.iter().cloned());
argv2.push("-".into());
argv2.push(arr2.clone());
let _ = bin_compadd("compadd", &argv2, &make_ops(), 0);
}
let tmparr = getaparam("tmparr").unwrap_or_default();
let qsep = q_quote(&sep);
let mut next: Vec<String> = Vec::with_capacity(suffixes.len() * tmparr.len().max(1));
for s in &suffixes {
for t in &tmparr {
next.push(format!("{}{}{}", s, qsep, t));
}
}
suffixes = next;
pos += 2; }
if nargs(pos) > 0 {
autosuffix.push("-qS".into());
autosuffix.push(q_quote(&rest[pos]));
}
let _ = setsparam("PREFIX", &getsparam("pre").unwrap_or_default());
let _ = setsparam("SUFFIX", &getsparam("suf").unwrap_or_default());
let iprefix = getsparam("IPREFIX").unwrap_or_default();
let isuffix = getsparam("ISUFFIX").unwrap_or_default();
for i in &suffixes {
let mut argv: Vec<String> = vec!["-U".into()];
argv.extend(group.iter().cloned());
argv.extend(expl.iter().cloned());
argv.extend(autosuffix.iter().cloned());
argv.extend(opts.iter().cloned());
argv.push("-i".into());
argv.push(iprefix.clone());
argv.push("-I".into());
argv.push(isuffix.clone());
argv.push("-p".into());
argv.push(prefix.clone());
argv.push("-s".into());
argv.push(i.clone());
argv.push("-a".into());
argv.push("testarr".into());
let _ = bin_compadd("compadd", &argv, &make_ops(), 0);
}
let _ = unsetparam("testarr");
let _ = unsetparam("tmparr");
let nm2: i64 = get_compstate_str("nmatches")
.and_then(|s| s.parse().ok())
.unwrap_or(0);
if nm != nm2 {
0
} else {
1
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::params::setsparam;
#[test]
fn returns_compadd_status() {
let _g = crate::test_util::global_state_lock();
let _ = setsparam("PREFIX", "");
let _ = setsparam("SUFFIX", "");
let _r = _sep_parts(&[
"(foo bar)".to_string(),
"@".to_string(),
"(host1 host2)".to_string(),
]);
assert!(getaparam("testarr").map(|v| v.is_empty()).unwrap_or(true));
assert!(getaparam("tmparr").map(|v| v.is_empty()).unwrap_or(true));
}
#[test]
fn zparse_routes_specs_to_named_arrays() {
let p = zparse(&[
"-J".into(),
"grp".into(),
"-M".into(),
"mspec".into(),
"-S".into(),
"sfx".into(),
"-q".into(),
"(foo bar)".into(),
"@".into(),
"hosts".into(),
]);
assert_eq!(p.group, vec!["-J", "grp"]);
assert_eq!(p.matcher, vec!["-M", "mspec"]);
assert_eq!(p.opts, vec!["-S", "sfx", "-q"]);
assert!(p.expl.is_empty());
assert_eq!(p.rest, vec!["(foo bar)", "@", "hosts"]);
}
}