use crate::compsys::ported::_all_labels::_all_labels;
use crate::compsys::ported::_describe::_describe;
use crate::compsys::ported::_description::_description;
use crate::compsys::ported::_message::_message;
use crate::compsys::ported::_next_label::_next_label;
use crate::compsys::ported::_tags::_tags;
use crate::ported::exec::{dispatch_function_call, execute_script};
use crate::ported::params::{
getaparam, getiparam, getsparam, setaparam, setsparam, unsetparam,
};
use crate::ported::zle::compcore::{get_compstate_str, set_compstate_str};
use crate::ported::zle::complete::bin_compadd;
use crate::ported::zle::computil::bin_compvalues;
use crate::ported::zsh_h::{options, MAX_OPS};
fn make_ops() -> options {
options {
ind: [0u8; MAX_OPS],
args: Vec::new(),
argscount: 0,
argsalloc: 0,
}
}
fn compvalues(argv: &[&str]) -> i32 {
let v: Vec<String> = argv.iter().map(|s| s.to_string()).collect();
bin_compvalues("compvalues", &v, &make_ops(), 0)
}
fn replace_last_field(ctx: &str, new: &str) -> String {
let base = match ctx.rfind(':') {
Some(i) => &ctx[..i],
None => ctx,
};
format!("{}:{}", base, new)
}
fn before_first<'a>(s: &'a str, sep: &str) -> &'a str {
match s.find(sep) {
Some(i) => &s[..i],
None => s,
}
}
fn after_first(s: &str, sep: &str) -> String {
match s.find(sep) {
Some(i) => s[i + sep.len()..].to_string(),
None => s.to_string(),
}
}
fn prefix_is_arg(prefix: &str, argsep: &str, restricted: bool, sep_char: &str) -> bool {
if argsep.is_empty() {
return false;
}
if !restricted {
return prefix.contains(argsep);
}
match prefix.rfind(argsep) {
Some(i) => {
let tail = &prefix[i + argsep.len()..];
sep_char.is_empty() || !tail.contains(sep_char)
}
None => false,
}
}
pub fn _values(args: &[String]) -> i32 {
let r = values_impl(args);
for p in [
"noargs", "args", "opts", "descr", "action", "expl", "sep", "argsep", "subc", "val_args",
"ws",
] {
unsetparam(p);
}
r
}
fn values_impl(args: &[String]) -> i32 {
let mut keep: Vec<String> = Vec::new();
let mut usecc = false;
let mut subopts: Vec<String> = Vec::new();
let mut idx = 0usize;
while idx < args.len() {
let a = &args[idx];
let b = a.as_bytes();
if b.len() != 2 || b[0] != b'-' {
break;
}
match b[1] {
b's' | b'S' if idx + 1 < args.len() => {
keep.push(a.clone());
keep.push(args[idx + 1].clone());
idx += 2;
}
b'w' => {
keep.push(a.clone());
idx += 1;
}
b'C' => {
usecc = true;
idx += 1;
}
b'O' if idx + 1 < args.len() => {
subopts = getaparam(&args[idx + 1]).unwrap_or_default();
idx += 2;
}
b'M' | b'J' | b'V' | b'o' | b'F' | b'X' if idx + 1 < args.len() => {
idx += 2;
}
b'1' | b'2' | b'n' => {
idx += 1;
}
_ => break,
}
}
let mut cvi: Vec<String> = vec!["-i".to_string()];
cvi.extend(keep.iter().cloned());
cvi.extend(args[idx..].iter().cloned());
if bin_compvalues("compvalues", &cvi, &make_ops(), 0) != 0 {
let _ = setsparam("curcontext", "");
return 1;
}
let oldcontext = getsparam("curcontext").unwrap_or_default();
compvalues(&["-S", "argsep"]);
let argsep = getsparam("argsep").unwrap_or_default();
let has_sep = compvalues(&["-s", "sep"]) == 0;
let sep_char = if has_sep {
getsparam("sep").unwrap_or_default()
} else {
String::new()
};
let test_restricted = has_sep && !sep_char.is_empty();
if compvalues(&["-D", "descr", "action"]) != 0 {
if _tags(&["values".to_string()]) != 0 {
return 1;
}
let _ = setsparam("curcontext", &replace_last_field(&oldcontext, "values"));
compvalues(&["-V", "noargs", "args", "opts"]);
let prefix = getsparam("PREFIX").unwrap_or_default();
if !argsep.is_empty() && prefix_is_arg(&prefix, &argsep, test_restricted, &sep_char) {
let name = before_first(&prefix, &argsep).to_string();
if compvalues(&["-L", name.as_str(), "descr", "action"]) == 0 {
let ipref = getsparam("IPREFIX").unwrap_or_default();
let _ = setsparam("IPREFIX", &format!("{}{}{}", ipref, name, argsep));
let _ = setsparam("PREFIX", &after_first(&prefix, &argsep));
} else {
let prefix_after = after_first(&prefix, &argsep); let suffix = getsparam("SUFFIX").unwrap_or_default(); let _ = setsparam("PREFIX", &name); let _ = setsparam("SUFFIX", ""); let mut combined = getaparam("args").unwrap_or_default();
combined.extend(getaparam("opts").unwrap_or_default());
let _ = setaparam("args", combined.clone());
let names: Vec<String> = combined
.iter()
.map(|e| before_first(e, ":").to_string())
.collect();
let mut cadd = vec![
"-M".to_string(),
"r:|[_-]=* r:|=*".to_string(),
"-D".to_string(),
"args".to_string(),
"-".to_string(),
];
cadd.extend(names);
bin_compadd("compadd", &cadd, &make_ops(), 0);
let pruned: Vec<String> = combined
.into_iter()
.filter(|e| before_first(e, ":").starts_with(&name))
.collect();
let _ = setaparam("args", pruned.clone());
if pruned.len() != 1 {
return 1;
}
let _ = setsparam("PREFIX", &prefix_after);
let _ = setsparam("SUFFIX", &suffix);
let matched = before_first(&pruned[0], ":").to_string();
let ipref = getsparam("IPREFIX").unwrap_or_default();
let _ = setsparam("IPREFIX", &format!("{}{}{}", ipref, matched, argsep));
compvalues(&["-L", matched.as_str(), "descr", "action", "subc"]);
let subc = getsparam("subc").unwrap_or_default();
let _ = setsparam("curcontext", &replace_last_field(&oldcontext, &subc));
}
} else {
compvalues(&["-d", "descr"]); let descr_v = getsparam("descr").unwrap_or_default();
let sep_group: Vec<String> = if compvalues(&["-s", "sep"]) == 0 {
vec!["-qS".to_string(), getsparam("sep").unwrap_or_default()]
} else {
Vec::new()
};
let sep2 = sep_group.get(1).cloned().unwrap_or_default();
let mut dargv: Vec<String> = vec![descr_v];
dargv.push("noargs".to_string());
dargv.extend(sep_group.iter().cloned());
dargv.extend([
"-M".to_string(),
"r:|[_-]=* r:|=*".to_string(),
"--".to_string(),
]);
dargv.extend([
"args".to_string(),
"-S".to_string(),
argsep.clone(),
"-M".to_string(),
"r:|[_-]=* r:|=*".to_string(),
"--".to_string(),
]);
dargv.extend([
"opts".to_string(),
"-qS".to_string(),
argsep.clone(),
"-r".to_string(),
format!("{}{} \\t\\n\\-", argsep, sep2),
"-M".to_string(),
"r:|[_-]=* r:|=*".to_string(),
]);
let r = _describe(&dargv);
let _ = setsparam("curcontext", &oldcontext); return r; }
} else {
compvalues(&["-C", "subc"]); let subc = getsparam("subc").unwrap_or_default();
let _ = setsparam("curcontext", &replace_last_field(&oldcontext, &subc)); }
if _tags(&["arguments".to_string()]) != 0 {
let _ = setsparam("curcontext", &oldcontext); return 1;
}
let descr = getsparam("descr").unwrap_or_default();
let _ = _description(&[
"arguments".to_string(),
"expl".to_string(),
descr.clone(),
]);
let mut sep_group: Vec<String> = Vec::new();
let cnt = getaparam("snames").map(|v| v.len()).unwrap_or(0)
+ getaparam("names").map(|v| v.len()).unwrap_or(0)
+ getaparam("onames").map(|v| v.len()).unwrap_or(0);
if cnt != 1 && compvalues(&["-s", "sep"]) == 0 {
let s = getsparam("sep").unwrap_or_default();
let flag = format!("-qS{}", s);
let mut expl = getaparam("expl").unwrap_or_default();
expl.insert(0, flag.clone());
let _ = setaparam("expl", expl);
sep_group = vec![flag];
}
let action = getsparam("action").unwrap_or_default();
if action.starts_with("->") {
compvalues(&["-v", "val_args"]); let state = action[2..].trim().to_string();
let _ = setsparam("state", &state);
let _ = setsparam("state_descr", &descr); let subc = getsparam("subc").unwrap_or_default();
if usecc {
let _ = setsparam("curcontext", &replace_last_field(&oldcontext, &subc));
} else {
let _ = setsparam("context", &subc);
}
set_compstate_str("restore", ""); return 1; }
compvalues(&["-v", "val_args"]);
if action.trim().is_empty() {
let _ = _message(&[
"-e".to_string(),
"arguments".to_string(),
descr.clone(),
]);
return 1;
} else if action.starts_with("((") && action.ends_with("))") {
let body = &action[2..action.len() - 2];
let ws: Vec<String> = body.split_whitespace().map(|s| s.to_string()).collect();
let _ = setaparam("ws", ws);
let mut d = vec![
descr.clone(),
"ws".to_string(),
"-M".to_string(),
"r:|[_-]=* r:|=*".to_string(),
];
d.extend(subopts.iter().cloned());
d.extend(sep_group.iter().cloned());
let _ = _describe(&d);
} else if action.starts_with('(') && action.ends_with(')') {
let body = &action[1..action.len() - 1];
let ws: Vec<String> = body.split_whitespace().map(|s| s.to_string()).collect();
let _ = setaparam("ws", ws);
let mut a = vec![
"arguments".to_string(),
"expl".to_string(),
descr.clone(),
"compadd".to_string(),
];
a.extend(subopts.iter().cloned());
a.extend(sep_group.iter().cloned());
a.extend(["-a".to_string(), "-".to_string(), "ws".to_string()]);
let _ = _all_labels(&a);
} else if action.starts_with('{') && action.ends_with('}') {
let body = &action[1..action.len() - 1];
loop {
if _next_label(&[
"arguments".to_string(),
"expl".to_string(),
descr.clone(),
]) != 0
{
break;
}
let _ = execute_script(body);
}
} else if action.starts_with(' ') {
let parts: Vec<String> = action.split_whitespace().map(|s| s.to_string()).collect();
loop {
if _next_label(&[
"arguments".to_string(),
"expl".to_string(),
descr.clone(),
]) != 0
{
break;
}
if let Some((cmd, rest)) = parts.split_first() {
if cmd == "compadd" {
bin_compadd("compadd", rest, &make_ops(), 0);
} else {
let _ = dispatch_function_call(cmd, rest);
}
}
}
} else {
let parts: Vec<String> = action.split_whitespace().map(|s| s.to_string()).collect();
if let Some((cmd, rest)) = parts.split_first() {
loop {
if _next_label(&[
"arguments".to_string(),
"expl".to_string(),
descr.clone(),
]) != 0
{
break;
}
let expl_now = getaparam("expl").unwrap_or_default();
let mut call = subopts.clone();
call.extend(expl_now);
call.extend(rest.iter().cloned());
if cmd == "compadd" {
bin_compadd("compadd", &call, &make_ops(), 0);
} else {
let _ = dispatch_function_call(cmd, &call);
}
}
}
}
let _ = setsparam("curcontext", &oldcontext);
let nm = getiparam("nm");
let nmatches = get_compstate_str("nmatches")
.and_then(|s| s.parse::<i64>().ok())
.unwrap_or(0);
if nm != nmatches {
0
} else {
1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(_values(&[]), 1);
}
#[test]
fn compvalues_i_failure_returns_one() {
let _g = crate::test_util::global_state_lock();
let r = _values(&[
"-s".to_string(),
",".to_string(),
"option name".to_string(),
"alpha[first]:arg:(a b)".to_string(),
"beta".to_string(),
]);
assert_eq!(r, 1);
}
#[test]
fn flag_parse_consumes_options_before_specs() {
let _g = crate::test_util::global_state_lock();
let r = _values(&[
"-C".to_string(),
"-s".to_string(),
",".to_string(),
"-S".to_string(),
"=".to_string(),
"-w".to_string(),
"-V".to_string(),
"grp".to_string(),
"descr".to_string(),
"val:msg:action".to_string(),
]);
assert_eq!(r, 1);
}
#[test]
fn helper_replace_last_field() {
assert_eq!(replace_last_field(":a:b:c", "x"), ":a:b:x");
assert_eq!(replace_last_field("noc", "x"), "noc:x");
}
#[test]
fn helper_prefix_is_arg() {
assert!(prefix_is_arg("name=val", "=", false, ""));
assert!(!prefix_is_arg("noeq", "=", false, ""));
assert!(prefix_is_arg("name=val", "=", true, ","));
assert!(!prefix_is_arg("name=a,b", "=", true, ","));
assert!(prefix_is_arg("name=", "=", true, ","));
}
#[test]
fn helper_before_after_first() {
assert_eq!(before_first("a=b=c", "="), "a");
assert_eq!(after_first("a=b=c", "="), "b=c");
assert_eq!(before_first("noeq", "="), "noeq");
assert_eq!(after_first("noeq", "="), "noeq");
}
}