use crate::ported::utils::zwarnnam;
use indexmap::IndexMap;
use regex::Regex;
use std::collections::HashMap;
use crate::ported::zsh_h::OPT_ISSET;
use std::io::Write;
use crate::ported::zsh_h::{Param, hashnode, param, PM_ARRAY};
pub const ZOF_ARG: i32 = 1; pub const ZOF_OPT: i32 = 2; pub const ZOF_MULT: i32 = 4; pub const ZOF_SAME: i32 = 8; pub const ZOF_MAP: i32 = 16; pub const ZOF_CYC: i32 = 32; pub const ZOF_GNUS: i32 = 64; pub const ZOF_GNUL: i32 = 128;
pub struct MatchData {
pub r#match: Option<Vec<String>>,
pub mbegin: Option<Vec<String>>,
pub mend: Option<Vec<String>>,
}
#[allow(non_camel_case_types)]
#[derive(Default)]
pub struct style_table {
styles: HashMap<String, Vec<stypat>>,
}
#[allow(non_upper_case_globals)]
pub static zstyletab: std::sync::LazyLock<std::sync::Mutex<style_table>> =
std::sync::LazyLock::new(|| std::sync::Mutex::new(style_table::new()));
impl style_table {
pub fn new() -> Self {
Self::default()
}
pub fn set(&mut self, pattern: &str, style: &str, values: Vec<String>, eval: bool) {
let style_patterns = self.styles.entry(style.to_string()).or_default();
if let Some(existing) = style_patterns.iter_mut().find(|p| p.pat == pattern) {
existing.vals = values; existing.eval = if eval {
Some(Box::new(crate::ported::zsh_h::eprog::default()))
} else { None }; return;
}
let mut weight: u64 = 0;
let mut tmp: u64 = 2;
let mut first = true;
for ch in pattern.chars() {
if first && ch == '*' { tmp = 0;
continue;
}
first = false;
if matches!(ch, '(' | '|' | '*' | '[' | '<' | '?' | '#' | '^') { tmp = 1;
}
if ch == ':' { weight += 1u64 << 32; first = true;
weight += tmp;
tmp = 2;
}
}
weight += tmp; let prog: Option<crate::ported::zsh_h::Patprog> = None;
let eval_eprog: Option<crate::ported::zsh_h::Eprog> = if eval {
Some(Box::new(crate::ported::zsh_h::eprog::default()))
} else {
None
};
let sp = stypat {
next: None, pat: pattern.to_string(), prog, weight, eval: eval_eprog, vals: values, };
let pos = style_patterns
.iter()
.position(|p| p.weight < weight)
.unwrap_or(style_patterns.len());
style_patterns.insert(pos, sp);
}
pub fn get(&self, context: &str, style: &str) -> Option<&[String]> {
self.styles.get(style).and_then(|patterns| {
patterns
.iter()
.find(|p| {
if p.pat == "*" {
true
} else {
crate::ported::pattern::patmatch(&p.pat, context)
}
})
.map(|p| p.vals.as_slice())
})
}
pub fn delete(&mut self, pattern: Option<&str>, style: Option<&str>) {
match (pattern, style) {
(None, None) => self.styles.clear(),
(Some(pat), None) => {
for patterns in self.styles.values_mut() {
patterns.retain(|p| p.pat != pat);
}
self.styles.retain(|_, v| !v.is_empty());
}
(Some(pat), Some(sty)) => {
if let Some(patterns) = self.styles.get_mut(sty) {
patterns.retain(|p| p.pat != pat);
if patterns.is_empty() {
self.styles.remove(sty);
}
}
}
(None, Some(sty)) => {
self.styles.remove(sty);
}
}
}
pub fn list(&self, context: Option<&str>) -> Vec<(String, String, Vec<String>)> {
let mut result = Vec::new();
for (style, patterns) in &self.styles {
for pat in patterns {
if let Some(ctx) = context {
let matches = if pat.pat == "*" {
true
} else {
crate::ported::pattern::patmatch(&pat.pat, ctx)
};
if !matches {
continue;
}
}
result.push((pat.pat.clone(), style.clone(), pat.vals.clone()));
}
}
result
}
pub fn list_styles(&self) -> Vec<&str> {
self.styles.keys().map(|s| s.as_str()).collect()
}
pub fn list_patterns(&self) -> Vec<&str> {
let mut patterns = Vec::new();
for pats in self.styles.values() {
for pat in pats {
if !patterns.contains(&pat.pat.as_str()) {
patterns.push(pat.pat.as_str());
}
}
}
patterns
}
pub fn test(&self, context: &str, style: &str, values: Option<&[&str]>) -> bool {
if let Some(found) = self.get(context, style) {
if let Some(test_vals) = values {
test_vals.iter().any(|v| found.contains(&v.to_string()))
} else {
matches!(
found.first().map(|s| s.as_str()),
Some("true" | "yes" | "on" | "1")
)
}
} else {
false
}
}
pub fn test_bool(&self, context: &str, style: &str) -> Option<bool> {
self.get(context, style).and_then(|vals| {
if vals.len() == 1 {
match vals[0].as_str() {
"yes" | "true" | "on" | "1" => Some(true),
"no" | "false" | "off" | "0" => Some(false),
_ => None,
}
} else {
None
}
})
}
}
pub fn zformat_substring(format: &str, specs: &HashMap<char, String>, presence: bool) -> String {
let mut effective: HashMap<char, String> = specs.clone();
effective.entry('%').or_insert_with(|| "%".to_string());
effective.entry(')').or_insert_with(|| ")".to_string());
let bytes: Vec<char> = format.chars().collect();
let mut out = String::with_capacity(bytes.len() + 16);
let mut idx = 0;
let _ = ZFormat::substring(
&bytes, &mut idx, &mut out, '\0', &effective, presence, false,
);
out
}
struct ZFormat;
impl ZFormat {
fn substring(
bytes: &[char],
idx: &mut usize,
out: &mut String,
endchar: char,
specs: &HashMap<char, String>,
presence: bool,
skip: bool,
) -> Option<()> {
while *idx < bytes.len() {
let c = bytes[*idx];
if endchar != '\0' && c == endchar {
return Some(());
}
if c != '%' {
if !skip {
out.push(c);
}
*idx += 1;
continue;
}
let start = *idx;
*idx += 1;
let mut right = false;
if *idx < bytes.len() && bytes[*idx] == '-' {
right = true;
*idx += 1;
}
let mut min: Option<i64> = None;
if *idx < bytes.len() && bytes[*idx].is_ascii_digit() {
let mut n: i64 = 0;
while *idx < bytes.len() && bytes[*idx].is_ascii_digit() {
n = n * 10 + bytes[*idx].to_digit(10).unwrap() as i64;
*idx += 1;
}
min = Some(n);
}
let testit = *idx < bytes.len() && bytes[*idx] == '(';
if testit && *idx + 1 < bytes.len() && bytes[*idx + 1] == '-' {
right = true;
*idx += 1;
}
let mut max: Option<i64> = None;
if *idx < bytes.len()
&& (bytes[*idx] == '.' || testit)
&& *idx + 1 < bytes.len()
&& bytes[*idx + 1].is_ascii_digit()
{
*idx += 1; let mut n: i64 = 0;
while *idx < bytes.len() && bytes[*idx].is_ascii_digit() {
n = n * 10 + bytes[*idx].to_digit(10).unwrap() as i64;
*idx += 1;
}
max = Some(n);
} else if *idx < bytes.len() && (bytes[*idx] == '.' || testit) {
*idx += 1;
}
if testit && *idx < bytes.len() {
let testval: i64 = min.or(max).unwrap_or(0);
let spec_char = bytes[*idx];
let actval: bool;
let spec_val = specs.get(&spec_char);
if let Some(sv) = spec_val.filter(|s| !s.is_empty()) {
if presence {
let cmp_val: i64 = if testval != 0 {
sv.chars().count() as i64
} else {
1
};
actval = if right {
testval < cmp_val
} else {
testval >= cmp_val
};
} else {
let signed_test = if right { -testval } else { testval };
let n: i64 = sv.parse().unwrap_or(0);
actval = (n - signed_test) != 0;
}
} else {
actval = if presence { !right } else { testval != 0 };
}
*idx += 1;
if *idx >= bytes.len() {
return None;
}
let endcharl = bytes[*idx];
*idx += 1;
ZFormat::substring(bytes, idx, out, endcharl, specs, presence, skip || actval)?;
if *idx < bytes.len() && bytes[*idx] == endcharl {
*idx += 1;
}
ZFormat::substring(bytes, idx, out, ')', specs, presence, skip || !actval)?;
if *idx < bytes.len() && bytes[*idx] == ')' {
*idx += 1;
}
continue;
}
if skip {
if *idx < bytes.len() {
*idx += 1;
}
continue;
}
if *idx < bytes.len() {
let spec_char = bytes[*idx];
*idx += 1;
if let Some(spec_val) = specs.get(&spec_char) {
let mut val_chars: Vec<char> = spec_val.chars().collect();
let len = val_chars.len() as i64;
let len = match max {
Some(m) if m >= 0 && len > m => {
val_chars.truncate(m as usize);
m
}
_ => len,
};
let outl = match min {
Some(m) if m >= 0 && m > len => m,
_ => len,
};
if len >= outl {
for &c in val_chars.iter().take(outl as usize) {
out.push(c);
}
} else {
let diff = (outl - len) as usize;
if right {
for _ in 0..diff {
out.push(' ');
}
for &c in val_chars.iter() {
out.push(c);
}
} else {
for &c in val_chars.iter() {
out.push(c);
}
for _ in 0..diff {
out.push(' ');
}
}
}
} else {
for &c in &bytes[start..*idx] {
out.push(c);
}
}
}
}
Some(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_style_pattern_weight() {
let mut t = style_table::new();
t.set("*", "s", vec!["broad".to_string()], false);
t.set(":completion:*", "s", vec!["mid".to_string()], false);
t.set(":completion:zsh:*", "s", vec!["narrow".to_string()],false);
assert_eq!(t.get(":completion:zsh:complete", "s").unwrap()[0], "narrow");
assert_eq!(t.get(":completion:bash:complete", "s").unwrap()[0], "mid");
assert_eq!(t.get(":other:thing", "s").unwrap()[0], "broad");
}
#[test]
fn zof_flags_are_distinct_powers_of_two() {
let all = [ZOF_ARG, ZOF_OPT, ZOF_MULT, ZOF_SAME, ZOF_MAP, ZOF_CYC, ZOF_GNUS, ZOF_GNUL];
let xor: i32 = all.iter().fold(0, |acc, &x| acc | x);
let sum: i32 = all.iter().sum();
assert_eq!(xor, sum, "ZOF_* bits must be disjoint");
for v in all {
assert!(v > 0 && (v & (v - 1)) == 0, "ZOF value {} is not a power of 2", v);
}
}
#[test]
fn test_style_pattern_matches() {
let mut t = style_table::new();
t.set(":completion:*", "s1", vec!["v".to_string()], false);
assert!(t.get(":completion:zsh:complete", "s1").is_some());
assert!(t.get(":other:zsh", "s1").is_none());
let mut t2 = style_table::new();
t2.set("*", "s2", vec!["v".to_string()], false);
assert!(t2.get("anything", "s2").is_some());
}
#[test]
fn test_style_table_set_get() {
let mut table = style_table::new();
table.set(":completion:*", "verbose", vec!["yes".to_string()], false);
let result = table.get(":completion:zsh", "verbose");
assert_eq!(result, Some(&["yes".to_string()][..]));
let result = table.get(":other", "verbose");
assert!(result.is_none());
}
#[test]
fn test_style_table_priority() {
let mut table = style_table::new();
table.set("*", "menu", vec!["no".to_string()], false);
table.set(":completion:*", "menu", vec!["yes".to_string()], false);
let result = table.get(":completion:zsh", "menu");
assert_eq!(result, Some(&["yes".to_string()][..]));
}
#[test]
fn test_style_table_delete() {
let mut table = style_table::new();
table.set("*", "style1", vec!["val".to_string()], false);
table.set("*", "style2", vec!["val".to_string()], false);
table.delete(None, Some("style1"));
assert!(table.get("test", "style1").is_none());
assert!(table.get("test", "style2").is_some());
}
#[test]
fn test_style_test_bool() {
let mut table = style_table::new();
table.set("*", "enabled", vec!["yes".to_string()], false);
table.set("*", "disabled", vec!["no".to_string()], false);
table.set(
"*",
"multiple",
vec!["a".to_string(), "b".to_string()],
false,
);
assert_eq!(table.test_bool("ctx", "enabled"), Some(true));
assert_eq!(table.test_bool("ctx", "disabled"), Some(false));
assert_eq!(table.test_bool("ctx", "multiple"), None);
}
#[test]
fn test_global_zstyletab_set_and_lookup() {
let key_style = "test_zutil_global_marker_style";
let key_pat = "test_zutil_global_marker_*";
{
let mut t = zstyletab.lock().unwrap();
t.set(key_pat, key_style,
vec!["yes".to_string()], false);
}
let found = lookupstyle("test_zutil_global_marker_x", key_style);
assert_eq!(found, vec!["yes".to_string()]);
assert_eq!(testforstyle("test_zutil_global_marker_x", key_style), 0);
assert_eq!(testforstyle("unmatched_ctx", "no_such_style_zzz"), 1);
{
let mut t = zstyletab.lock().unwrap();
t.delete(Some(key_pat), Some(key_style));
}
}
#[test]
fn test_zformat_basic() {
let mut specs = HashMap::new();
specs.insert('n', "test".to_string());
specs.insert('v', "42".to_string());
let result = zformat_substring("Name: %n, Value: %v", &specs, false);
assert_eq!(result, "Name: test, Value: 42");
}
#[test]
fn test_zformat_padding() {
let mut specs = HashMap::new();
specs.insert('n', "hi".to_string());
let result = zformat_substring("[%10n]", &specs, false);
assert_eq!(result, "[hi ]");
let result = zformat_substring("[%-10n]", &specs, false);
assert_eq!(result, "[ hi]");
}
#[test]
fn test_zformat_truncate() {
let mut specs = HashMap::new();
specs.insert('n', "hello world".to_string());
let result = zformat_substring("[%.5n]", &specs, false);
assert_eq!(result, "[hello]");
}
#[test]
fn test_zformat_escape() {
let specs = HashMap::new();
let result = zformat_substring("100%%", &specs, false);
assert_eq!(result, "100%");
}
}
pub fn bin_zregexparse(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if args.len() < 3 {
zwarnnam(nam, "not enough arguments");
return 1;
}
let var1 = &args[0]; let var2 = &args[1]; let subj = &args[2]; let _rparseargs = &args[3..]; let _ = (var1, var2, subj);
let oldext = crate::ported::zsh_h::isset(crate::ported::zsh_h::EXTENDEDGLOB); crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::EXTENDEDGLOB),
true,
);
crate::ported::mem::pushheap();
let mut ret;
let mut result = RParseResult { nullacts: Vec::new(), args: Vec::new() };
let parse_err = rparsealt(&mut result, std::ptr::null_mut()) != 0;
if parse_err { zwarnnam(nam, &format!("invalid regex : {}", args.last().map(|s| s.as_str()).unwrap_or("")));
ret = 3; } else {
ret = 0; }
if ret == 0 { let _ = OPT_ISSET(ops, b'c');
let _ = (var1, var2, subj);
}
crate::ported::mem::popheap(); crate::ported::options::opt_state_set(
&crate::ported::zsh_h::opt_name(crate::ported::zsh_h::EXTENDEDGLOB),
oldext,
); ret }
pub fn bin_zstyle(nam: &str, args: &[String], ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if args.is_empty() { let t = match zstyletab.lock() { Ok(g) => g, Err(_) => return 1 };
let mut out = std::io::stdout().lock();
for (pat, style, vals) in t.list(None) { let _ = write!(out, "{} {}", pat, style);
for v in &vals {
let _ = write!(out, " {}", v);
}
let _ = writeln!(out);
}
return 0; }
if OPT_ISSET(ops, b'L') || OPT_ISSET(ops, b'l') { let t = match zstyletab.lock() { Ok(g) => g, Err(_) => return 1 };
let mut out = std::io::stdout().lock();
for (pat, style, vals) in t.list(None) { let _ = write!(out, "zstyle {} {}", pat, style);
for v in &vals {
let _ = write!(out, " {}", v);
}
let _ = writeln!(out);
}
return 0; }
if OPT_ISSET(ops, b'd') { let pat = args.first().map(|s| s.as_str());
let sty = args.get(1).map(|s| s.as_str());
if let Ok(mut t) = zstyletab.lock() {
t.delete(pat, sty); }
return 0; }
if OPT_ISSET(ops, b's') || OPT_ISSET(ops, b'b') || OPT_ISSET(ops, b't')
|| OPT_ISSET(ops, b'T') || OPT_ISSET(ops, b'a')
|| OPT_ISSET(ops, b'e')
|| OPT_ISSET(ops, b'm')
{
if args.len() < 2 { return 1; }
let ctxt = &args[0]; let style = &args[1];
let vals = lookupstyle(ctxt, style); if OPT_ISSET(ops, b't') { let t = match zstyletab.lock() { Ok(g) => g, Err(_) => return 1 };
return if t.test(ctxt, style, None) { 0 } else { 1 };
}
if OPT_ISSET(ops, b'T') { let t = match zstyletab.lock() { Ok(g) => g, Err(_) => return 1 };
if t.get(ctxt, style).is_some() {
return if t.test(ctxt, style, None) { 0 } else { 1 };
}
return 0;
}
if OPT_ISSET(ops, b'm') { if args.len() < 3 { return 1; }
let pat = &args[2];
let prog = match crate::ported::pattern::patcompile(
pat,
crate::ported::zsh_h::PAT_STATIC,
None,
) {
Some(p) => p,
None => return 1,
};
for v in &vals { if crate::ported::pattern::pattry(&prog, v) { return 0; }
}
return 1; }
if OPT_ISSET(ops, b's') { if args.len() < 3 { return 1; }
let pname = &args[2];
if !vals.is_empty() {
let sep = args.get(3).map(|s| s.as_str()).unwrap_or(" "); let ret = vals.join(sep);
crate::ported::params::setsparam(pname, &ret);
return 0; }
crate::ported::params::setsparam(pname, ""); return 1; }
if OPT_ISSET(ops, b'b') { if args.len() < 3 { return 1; }
let pname = &args[2];
let truthy = vals.len() == 1 && matches!(vals[0].as_str(),
"yes" | "true" | "on" | "1");
let (ret, code) = if truthy { ("yes", 0) } else { ("no", 1) };
crate::ported::params::setsparam(pname, ret); return code; }
if OPT_ISSET(ops, b'a') { if args.len() < 3 { return 1; }
let pname = &args[2];
let found = !vals.is_empty();
crate::ported::params::setaparam(pname, if found { vals } else { Vec::new() });
return if found { 0 } else { 1 }; }
if OPT_ISSET(ops, b'e') {
if args.len() < 3 { return 1; }
let pname = &args[2];
if vals.is_empty() { return 1; }
let val = vals.join(" ");
crate::ported::params::setsparam(pname, &val);
return 0;
}
if vals.is_empty() { return 1; }
return 0;
}
if OPT_ISSET(ops, b'g') { if args.is_empty() { return 1; }
let pname = &args[0]; let pat_arg = args.get(1).map(|s| s.as_str()); let sty_arg = args.get(2).map(|s| s.as_str()); let mut out: Vec<String> = Vec::new();
let t = match zstyletab.lock() { Ok(g) => g, Err(_) => return 1 };
match (pat_arg, sty_arg) {
(None, _) => {
let mut seen: std::collections::HashSet<String> =
std::collections::HashSet::new();
for (p, _s, _v) in t.list(None) {
if seen.insert(p.clone()) { out.push(p); }
}
}
(Some(pat), None) => {
let mut seen: std::collections::HashSet<String> =
std::collections::HashSet::new();
for (p, s, _v) in t.list(None) {
if p == pat && seen.insert(s.clone()) { out.push(s); }
}
}
(Some(pat), Some(sty)) => {
if let Some(v) = t.get(pat, sty) {
out.extend(v.iter().cloned());
}
}
}
drop(t);
crate::ported::params::setaparam(pname, out); return 0;
}
if args.len() < 3 {
zwarnnam(nam, "not enough arguments"); return 1;
}
let ctxt = &args[0]; let style = &args[1];
let values: Vec<String> = args[2..].to_vec(); if let Ok(mut t) = zstyletab.lock() {
t.set(ctxt, style, values, false); }
0 }
pub fn bin_zparseopts(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
#[derive(Clone)]
struct Desc {
name: String,
flags: i32,
arr_name: Option<String>,
vals: Vec<Val>, }
#[derive(Clone)]
struct Val {
name: String, arg: Option<String>, }
let mut del = false; let mut flags_map = 0i32; let mut extract = false;
let mut fail = false;
let mut gnu = false;
let mut keep = false;
let mut assoc: Option<String> = None;
let mut paramsname: Option<String> = None;
let mut defarr: Option<String> = None;
let mut named_arrays: Vec<String> = Vec::new();
let mut i = 0usize;
while i < args.len() {
let o = &args[i];
if !o.starts_with('-') { break; }
if o.len() == 1 { break; } let bytes = o.as_bytes();
match bytes[1] {
b'-' if bytes.len() == 2 => { i += 1; break; } b'-' => { break; } b'D' if bytes.len() == 2 => { del = true; i += 1; }
b'E' if bytes.len() == 2 => { extract = true; i += 1; }
b'F' if bytes.len() == 2 => { fail = true; i += 1; }
b'G' if bytes.len() == 2 => { gnu = true; i += 1; }
b'K' if bytes.len() == 2 => { keep = true; i += 1; }
b'M' if bytes.len() == 2 => { flags_map |= ZOF_MAP; i += 1; }
b'a' => {
if defarr.is_some() {
zwarnnam(nam, "default array given more than once");
return 1;
}
let n = if o.len() > 2 { o[2..].to_string() }
else if i + 1 < args.len() { i += 1; args[i].clone() }
else { zwarnnam(nam, "missing array name"); return 1; };
defarr = Some(n);
i += 1;
}
b'A' => {
if assoc.is_some() {
zwarnnam(nam, "associative array given more than once");
return 1;
}
let n = if o.len() > 2 { o[2..].to_string() }
else if i + 1 < args.len() { i += 1; args[i].clone() }
else { zwarnnam(nam, "missing array name"); return 1; };
assoc = Some(n);
i += 1;
}
b'v' => {
if paramsname.is_some() {
zwarnnam(nam, "argv array given more than once");
return 1;
}
let n = if o.len() > 2 { o[2..].to_string() }
else if i + 1 < args.len() { i += 1; args[i].clone() }
else { zwarnnam(nam, "missing array name"); return 1; };
paramsname = Some(n);
i += 1;
}
_ => break, }
}
if i >= args.len() { zwarnnam(nam, "missing option descriptions");
return 1;
}
let mut descs: Vec<Desc> = Vec::new();
while i < args.len() {
let raw = &args[i];
i += 1;
if raw.is_empty() {
zwarnnam(nam, &format!("invalid option description: {}", raw));
return 1;
}
let bytes = raw.as_bytes();
let mut name = String::new();
let mut f = 0i32;
let mut p = 0usize;
while p < bytes.len() {
let c = bytes[p];
if c == b'\\' && p + 1 < bytes.len() {
name.push(bytes[p + 1] as char);
p += 2;
continue;
}
if p > 0 {
if c == b'+' { f |= ZOF_MULT; p += 1; break; }
if c == b':' || c == b'=' { break; }
}
name.push(c as char);
p += 1;
}
if p < bytes.len() && bytes[p] == b':' {
f |= ZOF_ARG;
p += 1;
if gnu {
f |= if name.len() > 1 { ZOF_GNUL } else { ZOF_GNUS };
}
if p < bytes.len() && bytes[p] == b':' { p += 1; f |= ZOF_OPT; }
if p < bytes.len() && bytes[p] == b'-' { p += 1; f |= ZOF_SAME; }
}
let mut arr_name: Option<String> = None;
if p < bytes.len() && bytes[p] == b'=' {
p += 1;
let arr = std::str::from_utf8(&bytes[p..]).unwrap_or("").to_string();
if !named_arrays.contains(&arr) { named_arrays.push(arr.clone()); }
arr_name = Some(arr);
f |= flags_map;
} else if p < bytes.len() {
zwarnnam(nam, &format!("invalid option description: {}", raw));
return 1;
} else if defarr.is_none() && assoc.is_none() {
zwarnnam(nam, &format!("no default array defined: {}", raw));
return 1;
}
if descs.iter().any(|d| d.name == name) {
zwarnnam(nam, &format!("option defined more than once: {}", name));
return 1;
}
descs.push(Desc { name, flags: f, arr_name, vals: Vec::new() });
}
let params_src = paramsname.clone().unwrap_or_else(|| "argv".to_string());
let mut params: Vec<String> = crate::fusevm_bridge::with_executor(|exec| {
if params_src == "argv" {
exec.pparams()
} else {
exec.array(¶ms_src).unwrap_or_default()
}
});
let mut new_params: Vec<String> = Vec::new(); let mut pi = 0usize;
let mut stopped = false;
while pi < params.len() {
let o_raw = params[pi].clone();
if !o_raw.starts_with('-') || (gnu && o_raw == "-") {
if extract {
if del { new_params.push(o_raw); }
pi += 1;
continue;
} else { stopped = true; break; }
}
if o_raw == "-" || o_raw == "--" {
if del && extract { new_params.push(o_raw); }
pi += 1;
stopped = true;
break;
}
let body = &o_raw[1..];
let whole_idx = descs.iter().position(|d|
body == d.name || body.starts_with(&d.name)
&& body.as_bytes().get(d.name.len()).is_some_and(|b| *b == b'=' || *b == 0));
let whole_match = whole_idx.map(|idx| {
let d = &descs[idx];
body == d.name ||
(body.starts_with(&d.name) && (
body.as_bytes().get(d.name.len()) == Some(&b'=')))
}).unwrap_or(false);
if whole_match {
let idx = whole_idx.unwrap();
let dn_len = descs[idx].name.len();
let dflags = descs[idx].flags;
let dname = descs[idx].name.clone();
if (dflags & ZOF_ARG) != 0 {
let e = &body[dn_len..]; if (dflags & ZOF_GNUL) != 0 && e.starts_with('=') { let arg = e[1..].to_string();
descs[idx].vals.push(Val { name: o_raw.clone(), arg: Some(arg) });
} else if !e.is_empty() { descs[idx].vals.push(Val { name: o_raw.clone(), arg: Some(e.to_string()) });
} else if (dflags & ZOF_OPT) == 0
|| ((dflags & (ZOF_GNUL | ZOF_GNUS)) == 0
&& pi + 1 < params.len()
&& !params[pi + 1].starts_with('-'))
{ if pi + 1 >= params.len() {
zwarnnam(nam,
&format!("missing argument for option: -{}", dname));
return 1;
}
pi += 1;
let arg = params[pi].clone();
descs[idx].vals.push(Val { name: o_raw.clone(), arg: Some(arg) });
} else { descs[idx].vals.push(Val { name: o_raw.clone(), arg: None });
}
} else {
descs[idx].vals.push(Val { name: o_raw.clone(), arg: None });
}
pi += 1;
continue;
}
let chars: Vec<char> = o_raw[1..].chars().collect();
let mut ci = 0usize;
let mut consumed_param = true;
while ci < chars.len() {
let ch = chars[ci];
let name1 = ch.to_string();
let didx = descs.iter().position(|d| d.name == name1);
let Some(idx) = didx else {
if fail {
if ch != '-' || ci > 0 {
zwarnnam(nam, &format!("bad option: -{}", ch));
} else {
zwarnnam(nam, &format!("bad option: -{}", chars.iter().collect::<String>()));
}
return 1;
}
consumed_param = false;
break;
};
let dflags = descs[idx].flags;
let dname = descs[idx].name.clone();
if (dflags & ZOF_ARG) != 0 {
if ci + 1 < chars.len() {
let arg: String = chars[ci + 1..].iter().collect();
descs[idx].vals.push(Val {
name: format!("-{}", ch),
arg: Some(arg),
});
break;
} else if (dflags & ZOF_OPT) == 0
|| ((dflags & (ZOF_GNUL | ZOF_GNUS)) == 0
&& pi + 1 < params.len()
&& !params[pi + 1].starts_with('-'))
{
if pi + 1 >= params.len() {
zwarnnam(nam, &format!("missing argument for option: -{}", dname));
return 1;
}
pi += 1;
let arg = params[pi].clone();
descs[idx].vals.push(Val { name: format!("-{}", ch), arg: Some(arg) });
} else {
descs[idx].vals.push(Val { name: format!("-{}", ch), arg: None });
}
} else {
descs[idx].vals.push(Val { name: format!("-{}", ch), arg: None });
}
ci += 1;
}
if !consumed_param {
if extract {
if del { new_params.push(o_raw); }
pi += 1;
continue;
} else {
stopped = true;
break;
}
}
pi += 1;
}
let _ = stopped;
if extract && del {
while pi < params.len() {
new_params.push(params[pi].clone());
pi += 1;
}
} else if del && !extract {
new_params = params[pi..].to_vec();
}
let mut arr_outputs: std::collections::BTreeMap<String, Vec<String>> =
std::collections::BTreeMap::new();
for d in &descs {
let target = d.arr_name.clone().or_else(|| defarr.clone());
let Some(tgt) = target else { continue };
let entry = arr_outputs.entry(tgt).or_default();
for v in &d.vals {
entry.push(v.name.clone());
if let Some(a) = &v.arg {
entry.push(a.clone());
}
}
}
for (name, vals) in arr_outputs {
if !keep || !vals.is_empty() {
crate::ported::params::setaparam(&name, vals);
}
}
if let Some(aname) = assoc {
let mut flat: Vec<String> = Vec::new();
for d in &descs {
if d.vals.is_empty() { continue; }
flat.push(format!("-{}", d.name));
let joined: String = d.vals.iter()
.filter_map(|v| v.arg.clone())
.collect::<Vec<_>>().join(" ");
flat.push(joined);
}
if !keep || !flat.is_empty() {
crate::ported::params::sethparam(&aname, flat);
}
}
if del {
if params_src == "argv" {
crate::fusevm_bridge::with_executor(|exec| {
exec.set_pparams(new_params.clone());
});
if let Ok(mut pp) = crate::ported::builtin::PPARAMS.lock() {
*pp = new_params;
}
} else {
crate::ported::params::setaparam(¶ms_src, new_params);
}
} else {
let _ = params;
}
0
}
pub fn bin_zformat(nam: &str, args: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut presence = 0i32; if args.is_empty() { crate::ported::utils::zwarnnam(nam,
&format!("invalid argument: {}", ""));
return 1;
}
let opt_arg = &args[0];
let bytes = opt_arg.as_bytes();
if bytes.is_empty() || bytes[0] != b'-' || bytes.len() != 2 { crate::ported::utils::zwarnnam(nam,
&format!("invalid argument: {}", opt_arg));
return 1; }
let opt = bytes[1]; let args = &args[1..];
match opt { b'F' | b'f' => { if opt == b'F' { presence = 1; } if args.len() < 2 { crate::ported::utils::zwarnnam(nam,
"missing arguments to -f/-F");
return 1;
}
let mut specs: HashMap<char, String> = HashMap::new(); specs.insert('%', "%".to_string()); specs.insert(')', ")".to_string()); for ap in &args[2..] { let ab = ap.as_bytes();
if ab.is_empty() || ab[0] == b'-' || ab[0] == b'.' || ab[0].is_ascii_digit()
|| ab.len() < 2 || ab[1] != b':' {
crate::ported::utils::zwarnnam(nam,
&format!("invalid argument: {}", ap)); return 1; }
specs.insert(ab[0] as char, ap[2..].to_string()); }
let out = zformat_substring(&args[1], &specs, presence != 0); crate::ported::params::setsparam(&args[0], &out); return 0; }
b'a' => { if args.len() < 2 { crate::ported::utils::zwarnnam(nam,
"missing arguments to -a");
return 1;
}
let mut pre = 0usize; let mut suf = 0usize; for ap in &args[2..] { let mut nbc = 0usize; let bytes = ap.as_bytes();
let mut cp_idx = 0usize;
while cp_idx < bytes.len() && bytes[cp_idx] != b':' { if bytes[cp_idx] == b'\\' && cp_idx + 1 < bytes.len() { cp_idx += 1;
nbc += 1;
}
cp_idx += 1;
}
if cp_idx < bytes.len() && bytes[cp_idx] == b':' && cp_idx + 1 < bytes.len() {
let d = cp_idx.saturating_sub(nbc); if d > pre { pre = d; } let s = bytes.len() - cp_idx - 1; if s > suf { suf = s; } }
}
let middle = &args[1]; let sl = middle.len(); let mut ret: Vec<String> = Vec::new(); for ap in &args[2..] { let bytes = ap.as_bytes();
let mut copy: Vec<u8> = Vec::with_capacity(bytes.len()); let mut k = 0usize;
let mut sep_at: Option<usize> = None;
while k < bytes.len() { if bytes[k] == b':' { sep_at = Some(copy.len()); break; }
if bytes[k] == b'\\' && k + 1 < bytes.len() { k += 1;
}
copy.push(bytes[k]); k += 1;
}
if let Some(left_len) = sep_at { let after = std::str::from_utf8(&bytes[(k + 1)..]).unwrap_or("");
let mut buf = String::with_capacity(pre + sl + after.len());
let prefix = std::str::from_utf8(©[..left_len]).unwrap_or("");
buf.push_str(prefix); for _ in prefix.chars().count()..pre { buf.push(' '); } buf.push_str(middle); buf.push_str(after); ret.push(buf); } else {
ret.push(String::from_utf8_lossy(©).into_owned()); }
}
if let Ok(mut tab) = crate::ported::params::paramtab().write() {
let pm: Param = Box::new(param {
node: hashnode {
next: None,
nam: args[0].clone(),
flags: PM_ARRAY as i32,
},
u_data: 0,
u_arr: Some(ret.clone()),
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None, gsu_i: None, gsu_f: None, gsu_a: None, gsu_h: None,
base: 0, width: 0, env: None, ename: None, old: None, level: 0,
});
tab.insert(args[0].clone(), pm);
}
let _ = sl;
return 0; }
_ => {}
}
crate::ported::utils::zwarnnam(nam, &format!("invalid option: -{}", opt as char));
1 }
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub struct zstyle_entry {
pub pattern: String,
pub style: String,
pub values: Vec<String>,
}
use crate::ported::zsh_h::module;
#[allow(unused_variables)]
pub fn setup_(m: *const module) -> i32 { 0
}
pub fn features_(m: *const module, features: &mut Vec<String>) -> i32 { *features = featuresarray(m, module_features());
0
}
pub fn enables_(m: *const module, enables: &mut Option<Vec<i32>>) -> i32 { handlefeatures(m, module_features(), enables)
}
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 { 0
}
pub fn cleanup_(m: *const module) -> i32 { setfeatureenables(m, module_features(), None)
}
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 { 0
}
use crate::ported::zsh_h::HashNode;
use crate::zsh_h::isset;
#[allow(non_camel_case_types)]
pub struct stypat {
pub next: Option<Box<stypat>>, pub pat: String, pub prog: Option<crate::ported::zsh_h::Patprog>, pub weight: u64, pub eval: Option<crate::ported::zsh_h::Eprog>, pub vals: Vec<String>, }
pub type Stypat = Box<stypat>;
#[allow(non_camel_case_types)]
pub struct style {
pub node: crate::ported::zsh_h::hashnode, pub pats: Option<Stypat>, }
pub type Style = Box<style>;
#[allow(non_camel_case_types)]
pub struct zoptdesc {
pub name: String,
pub flags: i32,
pub arg: i32,
pub vals: Vec<String>,
pub next: Option<Box<zoptdesc>>,
}
pub type Zoptdesc = Box<zoptdesc>;
#[allow(non_camel_case_types)]
pub struct zoptarr {
pub name: String,
pub vals: Vec<String>,
}
pub type Zoptarr = Box<zoptarr>;
#[allow(non_camel_case_types)]
pub struct zoptval {
pub name: String,
pub arg: String,
}
pub type Zoptval = Box<zoptval>;
pub struct RParseResult {
pub nullacts: Vec<String>,
pub args: Vec<String>,
}
#[allow(non_snake_case)]
pub fn add_opt_val(d: &mut zoptdesc, arg: String) { d.vals.push(arg);
}
#[allow(non_snake_case)]
pub fn addstyle(name: &str) -> Option<Style> { let mut s = style {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: name.to_string(),
flags: 0,
},
pats: None,
};
let _ = &mut s;
Some(Box::new(s))
}
#[allow(non_snake_case)]
pub fn appendactions(acts: &mut Vec<String>, branches: &mut Vec<String>) { for _bln in branches.iter() { for aln in acts.iter() { let _ = aln;
}
}
}
#[allow(non_snake_case)]
pub fn connectstates(out: &mut Vec<String>, in_: &mut Vec<String>) { for _oln in out.iter() { for _iln in in_.iter() { }
}
}
#[allow(non_snake_case)]
pub fn setstypat(style_name: &str, pat: &str, _prog: Option<crate::ported::zsh_h::Patprog>,
vals: Vec<String>, eval: i32) -> i32 {
if let Ok(mut t) = zstyletab.lock() {
t.set(pat, style_name, vals, eval != 0); 0
} else {
1
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn evalstyle(p: &Stypat) -> Vec<String> { Vec::new()
}
#[allow(non_snake_case)]
pub fn freematch(m: &mut MatchData) { m.r#match.take();
m.mbegin.take();
m.mend.take();
}
#[allow(non_snake_case)]
pub fn freestylenode(hn: HashNode) { let s: HashNode = hn;
drop(s);
}
#[allow(non_snake_case)]
pub fn freestylepatnode(p: Stypat) { drop(p);
}
#[allow(non_snake_case)]
pub fn freestypat(mut p: Stypat, s: Option<&mut style>, prev: Option<&mut stypat>) { let next = p.next.take(); let s_has_some = s.is_some();
if let Some(s_ref) = s { if let Some(prev_ref) = prev { prev_ref.next = next; } else {
s_ref.pats = next; }
}
freestylepatnode(p);
let _ = s_has_some;
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn get_opt_arr(name: &str) -> Option<Zoptarr> { None
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn get_opt_desc(name: &str) -> Option<Zoptdesc> { None
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn lookup_opt(str: &str) -> Option<Zoptdesc> { None
}
#[allow(non_snake_case)]
pub fn lookupstyle(ctxt: &str, style: &str) -> Vec<String> { match zstyletab.lock() { Ok(t) => t.get(ctxt, style)
.map(|v| v.to_vec()) .unwrap_or_default(),
Err(_) => Vec::new(),
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn map_opt_desc(start: Option<Zoptdesc>) -> Option<Zoptdesc> {
None
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn newzstyletable(size: i32, name: &str) -> Option<HashNode> {
None
}
#[allow(non_snake_case)]
pub fn prependactions(acts: &mut Vec<String>, branches: &mut Vec<String>) { for _bln in branches.iter() { for aln in acts.iter().rev() { let _ = aln;
}
}
}
#[allow(non_snake_case)]
pub fn printstylenode(hn: HashNode, printflags: i32) { let nam: String = hn.nam.clone();
let mut stdout = std::io::stdout().lock();
if printflags == 1 { let _ = writeln!(stdout, "{}", nam); return;
}
if let Ok(t) = zstyletab.lock() {
for (pat, style, vals) in t.list(None) { if style != nam { continue; }
let _ = write!(stdout, "zstyle ");
let _ = write!(stdout, "{} ", pat); let _ = write!(stdout, "{}", style); for v in &vals {
let _ = write!(stdout, " {}", v); }
let _ = writeln!(stdout); }
}
}
#[allow(non_snake_case)]
pub fn restorematch(m: &MatchData) {
let _ = m;
}
#[allow(non_snake_case)]
pub fn rmatch(
_sm: &RParseResult,
_subj: &str,
_var1: &str,
_var2: &str, _comp: i32,
) -> i32 {
0
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn rparsealt(result: &mut RParseResult, perr: *mut std::ffi::c_void) -> i32 {
0
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn rparseclo(result: &mut RParseResult, perr: *mut std::ffi::c_void) -> i32 {
0
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn rparseelt(result: &mut RParseResult, perr: *mut std::ffi::c_void) -> i32 {
0
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn rparseseq(result: &mut RParseResult, perr: *mut std::ffi::c_void) -> i32 {
0
}
#[allow(non_snake_case)]
pub fn savematch(m: &mut MatchData) { let mut a: Option<Vec<String>>; crate::ported::signals_h::queue_signals(); a = None;
m.r#match = a; a = None; m.mbegin = a; a = None; m.mend = a; crate::ported::signals_h::unqueue_signals(); }
#[allow(non_snake_case)]
pub fn scanpatstyles(hn: HashNode, spatflags: i32) { let _s: HashNode = hn;
match spatflags { 0 => { }
1 => { }
2 => { }
_ => {}
}
}
#[allow(non_snake_case)]
pub fn testforstyle(ctxt: &str, style: &str) -> i32 { let found = match zstyletab.lock() { Ok(t) => t.get(ctxt, style).is_some(), Err(_) => false,
};
if found { 0 } else { 1 } }
#[allow(non_snake_case)]
pub fn zalloc_default_array(size: i32) -> Vec<String> {
vec![String::new(); size.max(0) as usize]
}
use crate::ported::zsh_h::features as features_t;
use std::sync::{Mutex, OnceLock};
static MODULE_FEATURES: OnceLock<Mutex<features_t>> = OnceLock::new();
fn module_features() -> &'static Mutex<features_t> {
MODULE_FEATURES.get_or_init(|| Mutex::new(features_t {
bn_list: None,
bn_size: 4,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 0,
n_abstract: 0,
}))
}
fn featuresarray(_m: *const module, _f: &Mutex<features_t>) -> Vec<String> {
vec!["b:zformat".to_string(), "b:zparseopts".to_string(), "b:zregexparse".to_string(), "b:zstyle".to_string()]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<features_t>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 4]);
}
0
}
fn setfeatureenables(
_m: *const module,
_f: &Mutex<features_t>,
_e: Option<&[i32]>,
) -> i32 {
0
}