use crate::ported::builtin::BUILTINS;
use crate::ported::hashnameddir::{addnameddirnode, nameddirtab};
use crate::ported::hashtable::{
aliastab_lock, cmdnam_hashed, cmdnamtab_lock, createaliasnode, shfunctab_lock,
sufaliastab_lock,
};
use crate::ported::hist::hist_ring;
use crate::ported::jobs::{getjob, selectjobtab, sigmsg};
use crate::ported::module::MODULESTAB;
use crate::ported::options::{dosetopt, opt_state_set, optlookup};
use crate::ported::params::{deleteparamtable, getsparam, getstrvalue, realparamtab};
use crate::ported::utils::zwarn;
use crate::ported::zsh_h::{
ALIAS_GLOBAL, ALIAS_SUFFIX, DISABLED, FS_EVAL, FS_SOURCE, HashNode, HashTable, INTERACTIVE,
ND_USERNAME, PM_ARRAY, PM_AUTOLOAD, PM_EFLOAT, PM_EXPORTED, PM_FFLOAT, PM_HASHED, PM_HIDE,
PM_HIDEVAL, PM_INTEGER, PM_LEFT, PM_LOWER, PM_NAMEREF, PM_READONLY, PM_RIGHT_B, PM_RIGHT_Z,
PM_SCALAR, PM_SPECIAL, PM_TAGGED, PM_TIED, PM_TYPE, PM_UNIQUE, PM_UNSET, PM_UPPER, Param,
SCANPM_MATCHVAL, SCANPM_WANTKEYS, SCANPM_WANTVALS, SP_RUNNING, STAT_DONE, STAT_NOPRINT,
STAT_STOPPED, ScanFunc, hashnode, hashtable, isset, module, nameddir, opt_name, param,
value,
};
use std::collections::HashMap;
use std::path::PathBuf;
use crate::zsh_h::{shfunc, HASHED};
use std::sync::{Mutex, OnceLock};
use crate::DPUTS;
pub fn paramtypestr(pm: ¶m) -> String {
let f: u32 = pm.node.flags as u32;
if (f & PM_UNSET) != 0 {
return String::new(); }
if (f & PM_AUTOLOAD) != 0 {
return "undefined".to_string(); }
let mut val: String = match PM_TYPE(f) {
PM_SCALAR => "scalar".to_string(), PM_NAMEREF => "nameref".to_string(), PM_ARRAY => "array".to_string(), PM_INTEGER => "integer".to_string(), PM_EFLOAT | PM_FFLOAT => "float".to_string(), PM_HASHED => "association".to_string(), _ => {
DPUTS!(true, "BUG: type not handled in parameter"); String::new() }
};
if pm.level != 0 {
val.push_str("-local");
} if (f & PM_LEFT) != 0 {
val.push_str("-left");
} if (f & PM_RIGHT_B) != 0 {
val.push_str("-right_blanks");
} if (f & PM_RIGHT_Z) != 0 {
val.push_str("-right_zeros");
} if (f & PM_LOWER) != 0 {
val.push_str("-lower");
} if (f & PM_UPPER) != 0 {
val.push_str("-upper");
} if (f & PM_READONLY) != 0 {
val.push_str("-readonly");
} if (f & PM_TAGGED) != 0 {
val.push_str("-tag");
} if (f & PM_TIED) != 0 {
val.push_str("-tied");
} if (f & PM_EXPORTED) != 0 {
val.push_str("-export");
} if (f & PM_UNIQUE) != 0 {
val.push_str("-unique");
} if (f & PM_HIDE) != 0 {
val.push_str("-hide");
} if (f & PM_HIDEVAL) != 0 {
val.push_str("-hideval");
} if (f & PM_SPECIAL) != 0 {
val.push_str("-special");
}
val }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmparameter(ht: *mut HashTable, name: &str) -> Option<Param> {
let value = {
let tab = crate::ported::params::paramtab().read().unwrap();
tab.get(name)
.map(|pm| paramtypestr(pm))
.unwrap_or_default()
};
let found = !value.is_empty();
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
(PM_SCALAR | PM_READONLY) as i32
} else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
Some(pm) }
#[cfg(test)]
mod paramtypestr_tests {
use super::*;
use crate::ported::zsh_h::param;
fn make_pm(flags: u32, level: i32) -> param {
param {
node: hashnode {
next: None,
nam: String::new(),
flags: flags as i32,
},
u_data: 0,
u_arr: None,
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,
}
}
#[test]
fn paramtypestr_matches_c_dispatch() {
let _g = crate::test_util::global_state_lock();
assert_eq!(paramtypestr(&make_pm(PM_SCALAR, 0)), "scalar");
assert_eq!(
paramtypestr(&make_pm(PM_ARRAY | PM_EXPORTED, 1)),
"array-local-export",
);
assert_eq!(paramtypestr(&make_pm(PM_UNSET, 0)), "");
}
}
#[allow(non_snake_case)]
pub fn scanpmparameters(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
let func = match func {
Some(f) => f,
None => return,
}; let entries: Vec<(String, u32, String)> = {
let tab = crate::ported::params::paramtab()
.read()
.expect("paramtab poisoned");
tab.iter()
.filter(|(_, p)| (p.node.flags as u32 & PM_UNSET) == 0) .map(|(name, p)| {
let want_val = (flags as u32 & (SCANPM_WANTVALS | SCANPM_MATCHVAL)) != 0
|| (flags as u32 & SCANPM_WANTKEYS) == 0; let val = if want_val {
paramtypestr(p)
} else {
String::new()
};
(name.clone(), p.node.flags as u32, val)
})
.collect()
};
for (name, _orig_flags, val) in entries {
let pm = param {
node: hashnode {
next: None,
nam: name,
flags: (PM_SCALAR | PM_READONLY) as i32, },
u_data: 0,
u_arr: None,
u_str: Some(val), 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,
};
func(&Box::new(pm.node), flags); }
}
#[allow(non_snake_case)]
pub fn setpmcommand(pm: Param, value: String) {
let cn = cmdnam_hashed(&pm.node.nam, &value); if let Ok(mut tab) = cmdnamtab_lock().write() {
tab.add(cn); }
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn unsetpmcommand(pm: Param, exp: i32) {
if let Ok(mut tab) = cmdnamtab_lock().write() {
let _hn = tab.remove(&pm.node.nam);
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn setpmcommands(pm: Param, ht: *mut HashTable) {
let mut i: i32; let mut hn: Option<HashNode>;
if ht.is_null() {
return; }
let ht_ref: &hashtable = unsafe { &**ht };
i = 0; while i < ht_ref.hsize {
hn = ht_ref.nodes.get(i as usize).and_then(|n| n.clone()); while let Some(node) = hn.clone() {
let mut v = value {
pm: None, arr: Vec::new(), scanflags: 0, valflags: 0, start: 0, end: -1, };
let path = getstrvalue(Some(&mut v));
let cn = cmdnam_hashed(&node.nam, &path);
if let Ok(mut tab) = cmdnamtab_lock().write() {
tab.add(cn);
}
hn = node.next; }
i += 1; }
if !ht.is_null() {
let owned: HashTable = unsafe { std::ptr::read(ht) }; deleteparamtable(Some(owned)); }
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmcommand(ht: *mut HashTable, name: &str) -> Option<Param> {
let entry_exists = cmdnamtab_lock()
.read()
.ok()
.and_then(|g| g.get(name).cloned())
.is_some();
if !entry_exists && crate::ported::zsh_h::isset(crate::ported::zsh_h::HASHLISTALL) {
if let Some(path) = crate::ported::params::getsparam("PATH") {
let path_arr: Vec<String> =
path.split(':').map(|s| s.to_string()).collect();
crate::ported::hashtable::fillcmdnamtable(&path_arr); }
}
let g = cmdnamtab_lock().read().ok()?;
let entry = g.get(name); let (value, found) = if let Some(cmd) = entry {
let v = if (cmd.node.flags & HASHED as i32) != 0 {
cmd.cmd.clone().unwrap_or_default() } else {
let dir = cmd
.name
.as_ref()
.and_then(|v| v.first().cloned()) .unwrap_or_else(|| {
getsparam("PATH")
.and_then(|p| p.split(':').next().map(|s| s.to_string()))
.unwrap_or_default()
});
format!("{}/{}", dir, name) };
(v, true)
} else {
(String::new(), false) };
let mut pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
PM_SCALAR as i32
} else {
(PM_SCALAR | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
let _ = &mut pm;
Some(pm) }
#[allow(non_snake_case)]
pub fn scanpmcommands(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
if crate::ported::zsh_h::isset(crate::ported::zsh_h::HASHLISTALL) {
if let Some(path) = getsparam("PATH") {
let path_arr: Vec<String> =
path.split(':').map(|s| s.to_string()).collect();
crate::ported::hashtable::fillcmdnamtable(&path_arr); }
}
let cmds: Vec<(String, bool, String)> = {
let g = cmdnamtab_lock().read().unwrap();
g.iter()
.map(|(name, cmd)| {
let hashed = (cmd.node.flags & HASHED as i32) != 0;
let value = if hashed {
cmd.cmd.clone().unwrap_or_default() } else {
let dir = cmd
.name
.as_ref()
.and_then(|v| v.first().cloned()) .unwrap_or_else(|| {
getsparam("PATH")
.and_then(|p| p.split(':').next().map(|s| s.to_string()))
.unwrap_or_default()
});
format!("{}/{}", dir, name) };
(name.clone(), hashed, value)
})
.collect()
};
let _ = (PM_SCALAR, SCANPM_WANTVALS, SCANPM_MATCHVAL, SCANPM_WANTKEYS);
if let Some(f) = func {
for (name, _hashed, _value) in &cmds {
let node = Box::new(hashnode {
next: None,
nam: name.clone(),
flags: 0,
});
f(&node, flags); }
}
let _ = cmds;
}
#[allow(non_snake_case)]
pub fn setfunction(name: &str, mut val: String, dis: i32) {
let value: String; let mut shf: shfunc; let prog: Option<crate::ported::zsh_h::eprog>;
value = val.clone();
val = crate::ported::utils::metafy(&val);
prog = crate::ported::exec::parse_string(&val, 1); if prog.is_none() {
zwarn(
&format!("invalid function definition: {}", value),
);
return; }
shf = shfunc {
node: hashnode {
next: None,
nam: name.to_string(),
flags: dis, },
filename: None,
lineno: 0,
funcdef: prog.clone().map(Box::new), redir: None,
sticky: None,
body: Some(val.clone()), };
crate::ported::exec::shfunc_set_sticky(&mut shf);
if name.len() >= 4 && &name[..4] == "TRAP" {
if let Some(_sn) = crate::ported::signals::getsigidx(&name[4..]) { }
}
if let Ok(mut tab) = shfunctab_lock().write() {
tab.add(shf);
}
}
#[allow(non_snake_case)]
pub fn setpmfunction(pm: Param, value: String) {
let nam = pm.node.nam.clone();
setfunction(&nam, value, 0) }
#[allow(non_snake_case)]
pub fn setpmdisfunction(pm: Param, value: String) {
let nam = pm.node.nam.clone();
setfunction(&nam, value, DISABLED) }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn unsetpmfunction(pm: Param, exp: i32) {
if let Ok(mut tab) = shfunctab_lock().write() {
let _hn = tab.remove(&pm.node.nam);
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn setfunctions(pm: Param, ht: *mut HashTable, dis: i32) {
let mut i: i32; let mut hn: Option<HashNode>;
if ht.is_null() {
return; }
let ht_ref: &hashtable = unsafe { &**ht };
i = 0; while i < ht_ref.hsize {
hn = ht_ref.nodes.get(i as usize).and_then(|n| n.clone()); while let Some(node) = hn.clone() {
let mut v = value {
pm: None, arr: Vec::new(), scanflags: 0, valflags: 0, start: 0, end: -1, };
setfunction(
&node.nam,
getstrvalue(Some(&mut v)),
dis,
);
hn = node.next; }
i += 1; }
if !ht.is_null() {
let owned: HashTable = unsafe { std::ptr::read(ht) }; deleteparamtable(Some(owned)); }
}
#[allow(non_snake_case)]
pub fn setpmfunctions(pm: Param, ht: *mut HashTable) {
setfunctions(pm, ht, 0) }
#[allow(non_snake_case)]
pub fn setpmdisfunctions(pm: Param, ht: *mut HashTable) {
setfunctions(pm, ht, DISABLED) }
#[allow(non_snake_case)]
pub fn getfunction(_ht: *mut HashTable, name: &str, _dis: i32) -> Option<Param> {
let g = shfunctab_lock().read().ok()?;
let entry = g.get(name); let (value, found) = if let Some(shf) = entry {
let body = shf.body.as_deref();
let v = match body {
None => "builtin autoload -X".to_string(), Some(text) => format!("\t{}", text), };
(v, true)
} else {
(String::new(), false) };
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
PM_SCALAR as i32
}
else {
(PM_SCALAR | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
Some(pm) }
#[allow(non_snake_case)]
pub fn getpmfunction(ht: *mut HashTable, name: &str) -> Option<Param> {
getfunction(ht, name, 0) }
#[allow(non_snake_case)]
pub fn getpmdisfunction(ht: *mut HashTable, name: &str) -> Option<Param> {
getfunction(ht, name, DISABLED) }
#[allow(non_snake_case)]
pub fn scanfunctions(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
_dis: i32,
) {
let names: Vec<String> = if let Ok(g) = shfunctab_lock().read() {
g.iter().map(|(n, _)| n.clone()).collect() } else {
Vec::new()
};
if let Some(f) = func {
for name in names {
let node = Box::new(hashnode {
next: None,
nam: name,
flags: 0, });
f(&node, flags); }
}
}
#[allow(non_snake_case)]
pub fn scanpmfunctions(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanfunctions(ht, func, flags, 0) }
#[allow(non_snake_case)]
pub fn scanpmdisfunctions(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanfunctions(ht, func, flags, DISABLED) }
#[allow(non_snake_case)]
pub fn getfunction_source(_ht: *mut HashTable, name: &str, _dis: i32) -> Option<Param> {
let g = shfunctab_lock().read().ok()?;
let entry = g.get(name);
let (value, found) = if let Some(shf) = entry {
let fname = shf.filename.as_deref().unwrap_or("");
(format!("{}:0", fname), true)
} else {
(String::new(), false) };
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
(PM_SCALAR | PM_READONLY) as i32
}
else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
Some(pm) }
#[allow(non_snake_case)]
pub fn scanfunctions_source(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
_dis: i32,
) {
let names: Vec<String> = if let Ok(g) = shfunctab_lock().read() {
g.iter().map(|(n, _)| n.clone()).collect() } else {
Vec::new()
};
if let Some(f) = func {
for name in names {
let node = Box::new(hashnode {
next: None,
nam: name,
flags: 0, });
f(&node, flags); }
}
}
#[allow(non_snake_case)]
pub fn getpmfunction_source(ht: *mut HashTable, name: &str) -> Option<Param> {
getfunction_source(ht, name, 0) }
#[allow(non_snake_case)]
pub fn getpmdisfunction_source(ht: *mut HashTable, name: &str) -> Option<Param> {
getfunction_source(ht, name, 1) }
#[allow(non_snake_case)]
pub fn scanpmfunction_source(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanfunctions_source(ht, func, flags, 0) }
#[allow(non_snake_case)]
pub fn scanpmdisfunction_source(
ht: *mut HashTable, func: Option<ScanFunc>,
flags: i32,
) {
scanfunctions_source(ht, func, flags, 1) }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn funcstackgetfn(pm: *mut param) -> Vec<String> {
let stack = FUNCSTACK.lock().map(|s| s.clone()).unwrap_or_default();
stack.iter().rev().map(|f| f.name.clone()).collect() }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn functracegetfn(pm: *mut param) -> Vec<String> {
let f_stack = FUNCSTACK.lock().map(|s| s.clone()).unwrap_or_default(); let num = f_stack.len(); let mut ret: Vec<String> = Vec::with_capacity(num + 1); for f in f_stack.iter().rev() {
let caller = f.caller.as_deref().unwrap_or(""); let colonpair = format!("{}:{}", caller, f.lineno); ret.push(colonpair); }
ret }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn funcsourcetracegetfn(pm: *mut param) -> Vec<String> {
let f_stack = FUNCSTACK.lock().map(|s| s.clone()).unwrap_or_default(); let num = f_stack.len(); let mut ret: Vec<String> = Vec::with_capacity(num + 1); for f in f_stack.iter().rev() {
let fname = f.filename.as_deref().unwrap_or(""); let colonpair = format!("{}:{}", fname, f.flineno); ret.push(colonpair); }
ret }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn funcfiletracegetfn(pm: *mut param) -> Vec<String> {
let stack = FUNCSTACK.lock().map(|s| s.clone()).unwrap_or_default();
let mut ret: Vec<String> = Vec::with_capacity(stack.len());
let n = stack.len();
for i in 0..n {
let f = &stack[n - 1 - i];
let prev: Option<&crate::ported::zsh_h::funcstack> = if i + 1 < n {
Some(&stack[n - 1 - i - 1])
} else {
None
};
let parent_is_source = match prev {
None => true, Some(p) => p.tp == FS_SOURCE,
};
if parent_is_source {
ret.push(format!(
"{}:{}",
f.caller.as_deref().unwrap_or(""), f.lineno
));
} else if let Some(prev) = prev {
let mut flineno = prev.flineno + f.lineno;
if prev.tp == FS_EVAL {
flineno -= 1;
}
let fname = prev.filename.as_deref().unwrap_or("");
ret.push(format!("{}:{}", fname, flineno));
}
}
ret
}
#[allow(non_snake_case)]
pub fn getbuiltin(_ht: *mut HashTable, name: &str, _dis: i32) -> Option<Param> {
let entry = BUILTINS
.iter() .find(|b| b.node.nam == name);
let (value, found) = if let Some(_bn) = entry {
("defined".to_string(), true) } else {
(String::new(), false) };
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
(PM_SCALAR | PM_READONLY) as i32
}
else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
Some(pm) }
#[allow(non_snake_case)]
pub fn getpmbuiltin(ht: *mut HashTable, name: &str) -> Option<Param> {
getbuiltin(ht, name, 0) }
#[allow(non_snake_case)]
pub fn getpmdisbuiltin(ht: *mut HashTable, name: &str) -> Option<Param> {
getbuiltin(ht, name, DISABLED) }
#[allow(non_snake_case)]
pub fn scanbuiltins(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
_dis: i32,
) {
let _ = flags;
if let Some(f) = func {
for b in BUILTINS.iter() {
let node = Box::new(hashnode {
next: None,
nam: b.node.nam.clone(),
flags: 0, });
f(&node, flags); }
}
}
#[allow(non_snake_case)]
pub fn scanpmbuiltins(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanbuiltins(ht, func, flags, 0) }
#[allow(non_snake_case)]
pub fn scanpmdisbuiltins(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanbuiltins(ht, func, flags, DISABLED) }
fn getreswords(dis: i32) -> Vec<String> {
let g = match crate::ported::hashtable::reswdtab_lock().read() {
Ok(g) => g,
Err(_) => return Vec::new(),
};
let mut ret: Vec<String> = Vec::with_capacity(g.iter().count() + 1); for (name, node) in g.iter() {
let disabled = (node.node.flags & DISABLED as i32) != 0;
let pass = if dis != 0 { disabled } else { !disabled }; if pass {
ret.push(name.clone()); }
}
ret }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn reswordsgetfn(pm: *mut param) -> Vec<String> {
getreswords(0) }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn disreswordsgetfn(pm: *mut param) -> Vec<String> {
getreswords(DISABLED) }
#[allow(non_snake_case)]
fn getpatchars(dis: i32) -> Vec<String> {
let mut ret: Vec<String> = Vec::new();
let zpc_count = crate::ported::zsh_h::ZPC_COUNT as usize;
for i in 0..zpc_count {
let _ = i;
}
let _ = dis;
ret.shrink_to_fit();
ret
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn patcharsgetfn(pm: *mut param) -> Vec<String> {
getpatchars(0) }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn dispatcharsgetfn(pm: *mut param) -> Vec<String> {
getpatchars(1) }
#[allow(non_snake_case)]
pub fn setpmoption(pm: Param, value: String) {
let val = value.as_str();
if val != "on" && val != "off" {
zwarn(&format!("invalid value: {}", value)); return;
}
let nam = pm.node.nam.clone();
let n = optlookup(&nam); if n == 0 {
zwarn(&format!("no such option: {}", nam)); return;
}
let on = val == "on";
dosetopt(n, on as i32, 0); }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn unsetpmoption(pm: Param, exp: i32) {
let n = optlookup(&pm.node.nam);
if n != 0 {
dosetopt(n, 0, 0); }
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn setpmoptions(pm: Param, ht: *mut HashTable) {
let mut i: i32; let mut hn: Option<HashNode>;
if ht.is_null() {
return; }
let ht_ref: &hashtable = unsafe { &**ht };
i = 0; while i < ht_ref.hsize {
hn = ht_ref.nodes.get(i as usize).and_then(|n| n.clone()); while let Some(node) = hn.clone() {
let mut v = value {
pm: None, arr: Vec::new(), scanflags: 0,
valflags: 0,
start: 0, end: -1, };
let val: String;
val = getstrvalue(Some(&mut v)); if val.is_empty() || (val != "on" && val != "off") {
zwarn(
&format!("invalid value: {}", val),
);
} else {
let n = optlookup(&node.nam);
let on: i32 = if val != "off" { 1 } else { 0 };
if n == 0 || dosetopt(n, on, 0) != 0 {
zwarn(
&format!("can't change option: {}", node.nam),
);
}
}
hn = node.next; }
i += 1; }
if !ht.is_null() {
let owned: HashTable = unsafe { std::ptr::read(ht) }; deleteparamtable(Some(owned)); }
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmoption(ht: *mut HashTable, name: &str) -> Option<Param> {
let optno = optlookup(name); let (value, found) = if optno != 0 {
let on = crate::ported::options::opt_state_get(name)
.unwrap_or_else(|| {
let stripped = name.strip_prefix("no").unwrap_or(name);
let s = crate::ported::options::opt_state_get(stripped).unwrap_or(false);
if optno < 0 {
!s
} else {
s
}
});
(
if on { "on".to_string() } else { "off".to_string() },
true,
)
} else {
(String::new(), false) };
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
(PM_SCALAR | PM_READONLY) as i32
}
else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
Some(pm) }
#[allow(non_snake_case)]
pub fn scanpmoptions(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
let names: Vec<String> = crate::ported::options::ZSH_OPTIONS_SET
.iter()
.map(|s| s.to_string())
.collect();
if let Some(f) = func {
for nm in names {
let node = Box::new(hashnode {
next: None,
nam: nm,
flags: 0,
});
f(&node, flags); }
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmmodule(_ht: *mut HashTable, name: &str) -> Option<Param> {
let modtab = MODULESTAB.lock().unwrap();
let module_present = modtab.modules.contains_key(name);
let autoload_present = modtab.autoload_builtins.values().any(|v| v == name)
|| modtab.autoload_conditions.values().any(|v| v == name)
|| modtab.autoload_params.values().any(|v| v == name)
|| modtab.autoload_mathfuncs.values().any(|v| v == name);
drop(modtab);
let typ = if module_present {
Some("loaded".to_string())
}
else if autoload_present {
Some("autoloaded".to_string())
}
else {
None
}; let (val, extra_flags) = match typ {
Some(s) => (s, 0), None => (String::new(), (PM_UNSET | PM_SPECIAL) as i32), };
Some(Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: (PM_SCALAR | PM_READONLY) as i32 | extra_flags,
},
u_data: 0,
u_arr: None,
u_str: Some(val),
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,
}))
}
#[allow(non_snake_case)]
pub fn scanpmmodules(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
let func = match func {
Some(f) => f,
None => return,
};
let mut done: std::collections::HashSet<String> = std::collections::HashSet::new(); let pm_flags = (PM_SCALAR | PM_READONLY) as i32; let emit = |name: &str, val: &str| -> hashnode {
let _ = val; hashnode {
next: None,
nam: name.to_string(),
flags: pm_flags,
}
};
let modules: Vec<String> = {
let tab = MODULESTAB.lock().unwrap();
tab.modules.keys().cloned().collect() };
for name in modules {
done.insert(name.clone()); let node = emit(&name, "loaded"); func(&Box::new(node), flags); }
let bt = crate::ported::builtin::createbuiltintable();
for (_nam, b) in bt.iter() {
if (b.node.flags & crate::ported::zsh_h::BINF_ADDED as i32) == 0 {
if let Some(opt) = b.optstr.as_ref() {
if done.insert(opt.clone()) {
let node = emit(opt, "autoloaded"); func(&Box::new(node), flags); }
}
}
}
let cond_modules: Vec<String> = crate::ported::module::CONDTAB
.lock()
.unwrap()
.iter()
.filter_map(|p| p.module.clone())
.collect();
for m in cond_modules {
if done.insert(m.clone()) {
let node = emit(&m, "autoloaded");
func(&Box::new(node), flags); }
}
let auto_param_modules: Vec<String> = {
let tab = MODULESTAB.lock().unwrap();
tab.autoload_params.values().cloned().collect() };
for m in auto_param_modules {
if done.insert(m.clone()) {
let node = emit(&m, "autoloaded");
func(&Box::new(node), flags); }
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn dirssetfn(pm: *mut param, x: Vec<String>) {
let incleanup = INCLEANUP.load(std::sync::atomic::Ordering::Relaxed); if incleanup == 0 {
if let Ok(mut d) = DIRSTACK.lock() {
d.clear(); for entry in &x {
d.push(entry.clone()); }
}
}
drop(x);
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn dirsgetfn(pm: *mut param) -> Vec<String> {
DIRSTACK.lock().map(|d| d.clone()).unwrap_or_default() }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmhistory(ht: *mut HashTable, name: &str) -> Option<Param> {
let num: i64 = name.parse().ok()?; let value = crate::ported::hist::quietgethist(num) .map(|e| e.node.nam.clone());
let (val, found) = match value {
Some(v) => (v, true),
None => (String::new(), false), };
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: if found {
(PM_SCALAR | PM_READONLY) as i32
} else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
},
},
u_data: 0,
u_arr: None,
u_str: Some(val), 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,
});
Some(pm) }
#[allow(non_snake_case)]
pub fn scanpmhistory(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
let func = match func {
Some(f) => f,
None => return,
};
let entries: Vec<(i64, String)> = {
let ring = hist_ring.lock().unwrap(); ring.iter()
.rev() .map(|h| (h.histnum, h.node.nam.clone()))
.collect()
};
let want_val = (flags as u32 & (SCANPM_WANTVALS | SCANPM_MATCHVAL)) != 0
|| (flags as u32 & SCANPM_WANTKEYS) == 0;
for (histnum, cmd) in entries {
let pm = param {
node: hashnode {
next: None,
nam: crate::ported::params::convbase(histnum, 10), flags: (PM_SCALAR | PM_READONLY) as i32, },
u_data: 0,
u_arr: None,
u_str: if want_val { Some(cmd) } else { 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,
};
func(&Box::new(pm.node), flags); }
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn histwgetfn(pm: *mut param) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let zleline: String = crate::ported::zle::zle_main::ZLELINE
.lock()
.unwrap()
.iter()
.collect();
let cursor = crate::ported::zle::zle_main::ZLECS
.load(std::sync::atomic::Ordering::SeqCst);
let (bw, _) = crate::ported::hist::bufferwords(&zleline, cursor);
out.extend(bw); if let Ok(ring) = hist_ring.lock() {
for he in ring.iter().rev() {
let hstr = he.node.nam.as_bytes();
let len = hstr.len() as i32;
let nwords = he.nwords as i32;
let mut iw = nwords - 1;
while iw >= 0 {
let i2 = (iw as usize) * 2;
if i2 + 1 >= he.words.len() {
break;
}
let wbegin = he.words[i2] as i32; let wend = he.words[i2 + 1] as i32; if wbegin < 0 || wbegin >= len || wend < 0 || wend > len {
break;
}
let slice = &hstr[wbegin as usize..wend as usize]; if let Ok(s) = std::str::from_utf8(slice) {
out.push(s.to_string()); }
iw -= 1;
}
}
}
out }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn pmjobtext(_jtab: *mut std::ffi::c_void, job: i32) -> String {
let (jtab, _jmax) = selectjobtab(); let job_idx = job as usize;
if let Some(j) = jtab.get(job_idx) {
j.procs
.iter()
.map(|p| p.text.clone())
.collect::<Vec<_>>()
.join(" | ") } else {
String::new()
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmjobtext(ht: *mut HashTable, name: &str) -> Option<Param> {
let (jtab, jmax) = selectjobtab();
let (job, pend_nonempty) = match name.parse::<i32>() {
Ok(n) => (n, false),
Err(_) => (0, true),
};
let job = if pend_nonempty {
getjob(name, "")
} else {
job
};
if job >= 1 && (job as usize) <= jmax {
if let Some(j) = jtab.get(job as usize) {
if j.stat != 0 && !j.procs.is_empty() && (j.stat & STAT_NOPRINT) == 0 {
let text = pmjobtext(std::ptr::null_mut(), job);
let mut pm = make_empty_special_pm(name);
pm.node.flags = (PM_SCALAR | PM_READONLY) as i32;
pm.u_str = Some(text);
return Some(pm);
}
}
}
let mut pm = make_empty_special_pm(name);
pm.node.flags = (PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32;
Some(pm)
}
#[allow(non_snake_case)]
pub fn scanpmjobtexts(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
let func = match func {
Some(f) => f,
None => return,
};
let (jtab, jmax) = selectjobtab(); let want_val = (flags as u32 & (SCANPM_WANTVALS | SCANPM_MATCHVAL)) != 0
|| (flags as u32 & SCANPM_WANTKEYS) == 0;
for job in 1..=jmax {
if let Some(j) = jtab.get(job) {
if j.stat != 0 && !j.procs.is_empty() && (j.stat & STAT_NOPRINT) == 0 {
let val = if want_val {
pmjobtext(std::ptr::null_mut(), job as i32)
} else {
String::new()
}; let pm = param {
node: hashnode {
next: None,
nam: format!("{}", job), flags: (PM_SCALAR | PM_READONLY) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(val),
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,
};
func(&Box::new(pm.node), flags); }
}
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn pmjobstate(_jtab: *mut std::ffi::c_void, job: i32) -> String {
let curjob = *crate::ported::jobs::CURJOB
.get_or_init(|| Mutex::new(-1))
.lock()
.unwrap();
let prevjob = *crate::ported::jobs::PREVJOB
.get_or_init(|| Mutex::new(-1))
.lock()
.unwrap();
let cp = if job == curjob {
":+"
}
else if job == prevjob {
":-"
}
else {
":"
}; let (jtab, _jmax) = selectjobtab();
let job_idx = job as usize;
let j = match jtab.get(job_idx) {
Some(j) => j,
None => return String::new(),
};
let mut ret = if (j.stat & STAT_DONE) != 0 {
format!("done{cp}")
} else if (j.stat & STAT_STOPPED) != 0 {
format!("suspended{cp}")
} else {
format!("running{cp}") };
for pn in &j.procs {
let state = if pn.status == SP_RUNNING {
"running".to_string()
} else if pn.status >= 0 && (pn.status & 0xff) == 0 {
let code = (pn.status >> 8) & 0xff;
if code != 0 {
format!("exit {code}")
} else {
"done".to_string()
}
} else if (pn.status & 0xff) == 0x7f {
sigmsg((pn.status >> 8) & 0xff).to_string()
} else if (pn.status & 0x80) != 0 {
format!(
"{} (core dumped)",
sigmsg(pn.status & 0x7f)
)
} else {
sigmsg(pn.status & 0x7f).to_string() };
ret.push_str(&format!(":{}={}", pn.pid, state)); }
ret
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmjobstate(ht: *mut HashTable, name: &str) -> Option<Param> {
let (jtab, jmax) = selectjobtab(); let (job, pend_nonempty) = match name.parse::<i32>() {
Ok(n) => (n, false),
Err(_) => (0, true),
};
let job = if pend_nonempty {
getjob(name, "")
} else {
job
};
if job >= 1 && (job as usize) <= jmax {
if let Some(j) = jtab.get(job as usize) {
if j.stat != 0 && !j.procs.is_empty() && (j.stat & STAT_NOPRINT) == 0 {
let state = pmjobstate(std::ptr::null_mut(), job);
let mut pm = make_empty_special_pm(name);
pm.node.flags = (PM_SCALAR | PM_READONLY) as i32;
pm.u_str = Some(state);
return Some(pm);
}
}
}
let mut pm = make_empty_special_pm(name);
pm.node.flags = (PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32;
Some(pm)
}
#[allow(non_snake_case)]
pub fn scanpmjobstates(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
let func = match func {
Some(f) => f,
None => return,
};
let (jtab, jmax) = selectjobtab(); let want_val = (flags as u32 & (SCANPM_WANTVALS | SCANPM_MATCHVAL)) != 0
|| (flags as u32 & SCANPM_WANTKEYS) == 0;
for job in 1..=jmax {
if let Some(j) = jtab.get(job) {
if j.stat != 0 && !j.procs.is_empty() && (j.stat & STAT_NOPRINT) == 0 {
let val = if want_val {
pmjobstate(std::ptr::null_mut(), job as i32)
} else {
String::new()
}; let pm = param {
node: hashnode {
next: None,
nam: format!("{}", job), flags: (PM_SCALAR | PM_READONLY) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(val),
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,
};
func(&Box::new(pm.node), flags); }
}
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn pmjobdir(_jtab: *mut std::ffi::c_void, job: i32) -> String {
let (jtab, _jmax) = selectjobtab();
let job_idx = job as usize;
if let Some(j) = jtab.get(job_idx) {
if let Some(pwd) = j.pwd.as_ref() {
return pwd.clone();
} }
std::env::current_dir()
.ok()
.and_then(|p| p.to_str().map(String::from))
.unwrap_or_default()
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmjobdir(ht: *mut HashTable, name: &str) -> Option<Param> {
let (jtab, jmax) = selectjobtab(); let (job, pend_nonempty) = match name.parse::<i32>() {
Ok(n) => (n, false),
Err(_) => (0, true),
};
let job = if pend_nonempty {
getjob(name, "")
} else {
job
};
if job >= 1 && (job as usize) <= jmax {
if let Some(j) = jtab.get(job as usize) {
if j.stat != 0 && !j.procs.is_empty() && (j.stat & STAT_NOPRINT) == 0 {
let dir = pmjobdir(std::ptr::null_mut(), job);
let mut pm = make_empty_special_pm(name);
pm.node.flags = (PM_SCALAR | PM_READONLY) as i32;
pm.u_str = Some(dir);
return Some(pm);
}
}
}
let mut pm = make_empty_special_pm(name);
pm.node.flags = (PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32;
Some(pm)
}
#[allow(non_snake_case)]
pub fn scanpmjobdirs(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
let func = match func {
Some(f) => f,
None => return,
};
let (jtab, jmax) = selectjobtab(); let want_val = (flags as u32 & (SCANPM_WANTVALS | SCANPM_MATCHVAL)) != 0
|| (flags as u32 & SCANPM_WANTKEYS) == 0;
for job in 1..=jmax {
if let Some(j) = jtab.get(job) {
if j.stat != 0 && !j.procs.is_empty() && (j.stat & STAT_NOPRINT) == 0 {
let val = if want_val {
pmjobdir(std::ptr::null_mut(), job as i32)
} else {
String::new()
}; let pm = param {
node: hashnode {
next: None,
nam: format!("{}", job), flags: (PM_SCALAR | PM_READONLY) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(val),
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,
};
func(&Box::new(pm.node), flags); }
}
}
}
#[allow(non_snake_case)]
pub fn setpmnameddir(pm: Param, value: String) {
let nd = nameddir {
node: hashnode {
next: None,
nam: pm.node.nam.clone(),
flags: 0,
},
dir: value, diff: 0,
};
addnameddirnode(&pm.node.nam, nd);
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn unsetpmnameddir(pm: Param, exp: i32) {
if let Ok(mut tab) = nameddirtab().lock() {
let _hd = tab.remove(&pm.node.nam);
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn setpmnameddirs(pm: Param, ht: *mut HashTable) {
let mut i: i32; let mut hn: Option<HashNode>; let mut next: Option<HashNode>;
if ht.is_null() {
return; }
if let Ok(mut tab) = nameddirtab().lock() {
i = 0;
let _ = i; let to_remove: Vec<String> = tab
.iter()
.filter(|(_, nd)| (nd.node.flags & ND_USERNAME) == 0) .map(|(k, _)| k.clone())
.collect();
for k in to_remove {
tab.remove(&k); }
}
let ht_ref: &hashtable = unsafe { &**ht };
i = 0; while i < ht_ref.hsize {
hn = ht_ref.nodes.get(i as usize).and_then(|n| n.clone()); while let Some(node) = hn.clone() {
next = node.next.clone(); let mut v = value {
pm: None, arr: Vec::new(), scanflags: 0,
valflags: 0,
start: 0, end: -1, };
let val: String;
val = getstrvalue(Some(&mut v)); if val.is_empty() {
zwarn("invalid value: ''"); } else {
let nd = nameddir {
node: hashnode {
next: None,
nam: node.nam.clone(),
flags: 0, },
dir: val, diff: 0,
};
addnameddirnode(&node.nam, nd); }
hn = next; }
i += 1; }
let saved_interactive = isset(INTERACTIVE); opt_state_set(
&opt_name(INTERACTIVE),
false,
); if !ht.is_null() {
let owned: HashTable = unsafe { std::ptr::read(ht) }; deleteparamtable(Some(owned)); }
opt_state_set(
&opt_name(INTERACTIVE),
saved_interactive,
); }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmnameddir(ht: *mut HashTable, name: &str) -> Option<Param> {
let cname = std::ffi::CString::new(name).ok()?;
let pwd = unsafe { libc::getpwnam(cname.as_ptr()) }; let (value, found) = if !pwd.is_null() {
let dir = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_dir) };
(dir.to_string_lossy().into_owned(), true)
} else {
(String::new(), false)
};
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: if found {
(PM_SCALAR | PM_READONLY) as i32
} else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
},
},
u_data: 0,
u_arr: None,
u_str: Some(value),
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,
});
Some(pm)
}
#[allow(non_snake_case)]
pub fn scanpmnameddirs(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
if let Some(f) = func {
unsafe {
libc::setpwent();
} loop {
let pwd = unsafe { libc::getpwent() }; if pwd.is_null() {
break;
}
let name = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_name) };
let node = Box::new(hashnode {
next: None,
nam: name.to_string_lossy().into_owned(), flags: 0,
});
f(&node, flags); }
unsafe {
libc::endpwent();
} }
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmuserdir(ht: *mut HashTable, name: &str) -> Option<Param> {
let cname = std::ffi::CString::new(name).ok()?;
let pwd = unsafe { libc::getpwnam(cname.as_ptr()) }; let (value, found) = if !pwd.is_null() {
let dir = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_dir) };
(dir.to_string_lossy().into_owned(), true) } else {
(String::new(), false) };
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
(PM_SCALAR | PM_READONLY) as i32
}
else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
Some(pm) }
#[allow(non_snake_case)]
pub fn scanpmuserdirs(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
if let Some(f) = func {
unsafe {
libc::setpwent();
} loop {
let pwd = unsafe { libc::getpwent() }; if pwd.is_null() {
break;
}
let name = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_name) };
let node = Box::new(hashnode {
next: None,
nam: name.to_string_lossy().into_owned(), flags: 0,
});
f(&node, flags); }
unsafe {
libc::endpwent();
} }
}
#[allow(non_snake_case)]
pub fn setalias(
_ht: *mut HashTable,
pm: Param,
value: String, flags: i32,
) {
let name = (*pm).node.nam.clone();
let mut tab = aliastab_lock()
.write()
.expect("aliastab poisoned");
tab.add(crate::ported::hashtable::createaliasnode(
&name,
&value,
flags as u32,
));
}
#[allow(non_snake_case)]
pub fn setpmralias(pm: Param, value: String) {
setalias(std::ptr::null_mut(), pm, value, 0) }
#[allow(non_snake_case)]
pub fn setpmdisralias(pm: Param, value: String) {
setalias(std::ptr::null_mut(), pm, value, DISABLED) }
#[allow(non_snake_case)]
pub fn setpmgalias(pm: Param, value: String) {
setalias(std::ptr::null_mut(), pm, value, ALIAS_GLOBAL) }
#[allow(non_snake_case)]
pub fn setpmdisgalias(pm: Param, value: String) {
setalias(std::ptr::null_mut(), pm, value, ALIAS_GLOBAL | DISABLED) }
#[allow(non_snake_case)]
pub fn setpmsalias(pm: Param, value: String) {
setalias(std::ptr::null_mut(), pm, value, ALIAS_SUFFIX) }
#[allow(non_snake_case)]
pub fn setpmdissalias(pm: Param, value: String) {
setalias(std::ptr::null_mut(), pm, value, ALIAS_SUFFIX | DISABLED) }
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn unsetpmalias(pm: Param, exp: i32) {
if let Ok(mut tab) = aliastab_lock().write() {
let _hd = tab.remove(&pm.node.nam);
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn unsetpmsalias(pm: Param, exp: i32) {
if let Ok(mut tab) = sufaliastab_lock().write() {
let _hd = tab.remove(&pm.node.nam);
}
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn setaliases(
alht: *mut HashTable,
pm: Param, ht: *mut HashTable,
flags: i32,
) {
if ht.is_null() {
return; }
let mut keys_to_drop: Vec<String> = Vec::new(); {
let tab = aliastab_lock().read().expect("aliastab poisoned");
for (name, alias) in tab.iter() {
if (alias.node.flags as i32) == flags {
keys_to_drop.push(name.clone()); }
}
}
if !keys_to_drop.is_empty() {
let mut tab = aliastab_lock().write().expect("aliastab poisoned");
for name in keys_to_drop {
let _ = tab.remove(&name); }
}
unsafe {
let _ht_ref = &*ht; let _ = createaliasnode("", "", flags as u32); }
let _ = pm; let _ = alht; }
#[allow(non_snake_case)]
pub fn setpmraliases(pm: Param, ht: *mut HashTable) {
setaliases(std::ptr::null_mut(), pm, ht, 0) }
#[allow(non_snake_case)]
pub fn setpmdisraliases(pm: Param, ht: *mut HashTable) {
setaliases(std::ptr::null_mut(), pm, ht, DISABLED) }
#[allow(non_snake_case)]
pub fn setpmgaliases(pm: Param, ht: *mut HashTable) {
setaliases(std::ptr::null_mut(), pm, ht, ALIAS_GLOBAL) }
#[allow(non_snake_case)]
pub fn setpmdisgaliases(pm: Param, ht: *mut HashTable) {
setaliases(std::ptr::null_mut(), pm, ht, ALIAS_GLOBAL | DISABLED) }
#[allow(non_snake_case)]
pub fn setpmsaliases(pm: Param, ht: *mut HashTable) {
setaliases(std::ptr::null_mut(), pm, ht, ALIAS_SUFFIX) }
#[allow(non_snake_case)]
pub fn setpmdissaliases(pm: Param, ht: *mut HashTable) {
setaliases(std::ptr::null_mut(), pm, ht, ALIAS_SUFFIX | DISABLED) }
#[allow(non_snake_case)]
pub fn assignaliasdefs(
pm: *mut param, flags: i32,
) {
if !pm.is_null() {
unsafe {
(*pm).node.flags = PM_SCALAR as i32;
} }
let handler = match flags {
0 => "pmralias_gsu", f if f == ALIAS_GLOBAL => "pmgalias_gsu", f if f == ALIAS_SUFFIX => "pmsalias_gsu", f if f == DISABLED => "pmdisralias_gsu", f if f == ALIAS_GLOBAL | DISABLED => "pmdisgalias_gsu", f if f == ALIAS_SUFFIX | DISABLED => "pmdissalias_gsu", _ => return,
};
if !pm.is_null() {
let name = unsafe { (*pm).node.nam.clone() };
let m = ALIAS_GSU_HANDLER
.get_or_init(|| Mutex::new(HashMap::new()));
if let Ok(mut g) = m.lock() {
g.insert(name, handler.to_string());
}
}
}
#[allow(non_snake_case)]
pub fn getalias(
_alht: *mut HashTable,
_ht: *mut HashTable, name: &str,
flags: i32,
) -> Option<Param> {
let table = if (flags & ALIAS_SUFFIX) != 0 {
sufaliastab_lock()
} else {
aliastab_lock()
};
let g = table.read().ok()?;
let entry = g.get(name); let (value, found) = if let Some(al) = entry {
if al.node.flags == flags {
(al.text.clone(), true) } else {
(String::new(), false) }
} else {
(String::new(), false) };
let mut pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: 0,
},
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
assignaliasdefs(&mut *pm as *mut _, flags); if !found {
pm.node.flags |= (PM_UNSET | PM_SPECIAL) as i32; }
Some(pm) }
#[allow(non_snake_case)]
pub fn getpmralias(ht: *mut HashTable, name: &str) -> Option<Param> {
getalias(std::ptr::null_mut(), ht, name, 0) }
#[allow(non_snake_case)]
pub fn getpmdisralias(ht: *mut HashTable, name: &str) -> Option<Param> {
getalias(std::ptr::null_mut(), ht, name, DISABLED) }
#[allow(non_snake_case)]
pub fn getpmgalias(ht: *mut HashTable, name: &str) -> Option<Param> {
getalias(std::ptr::null_mut(), ht, name, ALIAS_GLOBAL) }
#[allow(non_snake_case)]
pub fn getpmdisgalias(ht: *mut HashTable, name: &str) -> Option<Param> {
getalias(std::ptr::null_mut(), ht, name, DISABLED) }
#[allow(non_snake_case)]
pub fn getpmsalias(ht: *mut HashTable, name: &str) -> Option<Param> {
getalias(std::ptr::null_mut(), ht, name, 0) }
#[allow(non_snake_case)]
pub fn getpmdissalias(ht: *mut HashTable, name: &str) -> Option<Param> {
getalias(std::ptr::null_mut(), ht, name, DISABLED) }
#[allow(non_snake_case)]
pub fn scanaliases(
_alht: *mut HashTable,
_ht: *mut HashTable, func: Option<ScanFunc>,
pmflags: i32,
alflags: i32,
) {
if let Some(f) = func {
let lock = if alflags == ALIAS_SUFFIX {
sufaliastab_lock()
} else {
aliastab_lock()
};
if let Ok(tab) = lock.read() {
for (_, alias) in tab.iter() {
if alflags != 0
&& alflags != ALIAS_SUFFIX
&& (alias.node.flags & alflags) == 0
{
continue;
}
let node = Box::new(hashnode {
next: None,
nam: alias.node.nam.clone(), flags: alias.node.flags,
});
f(&node, pmflags); }
}
}
}
#[allow(non_snake_case)]
pub fn scanpmraliases(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanaliases(std::ptr::null_mut(), ht, func, flags, 0) }
#[allow(non_snake_case)]
pub fn scanpmdisraliases(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanaliases(std::ptr::null_mut(), ht, func, flags, DISABLED) }
#[allow(non_snake_case)]
pub fn scanpmgaliases(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanaliases(std::ptr::null_mut(), ht, func, flags, ALIAS_GLOBAL) }
#[allow(non_snake_case)]
pub fn scanpmdisgaliases(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanaliases(
std::ptr::null_mut(),
ht,
func,
flags, ALIAS_GLOBAL | DISABLED,
)
}
#[allow(non_snake_case)]
pub fn scanpmsaliases(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanaliases(
std::ptr::null_mut(),
ht,
func,
flags, ALIAS_SUFFIX,
)
}
#[allow(non_snake_case)]
pub fn scanpmdissaliases(
ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
scanaliases(
std::ptr::null_mut(),
ht,
func,
flags, ALIAS_SUFFIX | DISABLED,
)
}
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub fn getpmusergroups(ht: *mut HashTable, name: &str) -> Option<Param> {
let cname = std::ffi::CString::new(name).ok()?;
let grp = unsafe { libc::getgrnam(cname.as_ptr()) }; let (value, found) = if !grp.is_null() {
let gid = unsafe { (*grp).gr_gid };
(gid.to_string(), true) } else {
(String::new(), false) };
let pm = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(), flags: if found {
(PM_SCALAR | PM_READONLY) as i32
}
else {
(PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32
}, },
u_data: 0,
u_arr: None,
u_str: Some(value), 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,
});
Some(pm) }
#[allow(non_snake_case)]
pub fn scanpmusergroups(
_ht: *mut HashTable,
func: Option<ScanFunc>, flags: i32,
) {
if let Some(f) = func {
unsafe {
libc::setgrent();
} loop {
let grp = unsafe { libc::getgrent() }; if grp.is_null() {
break;
}
let name = unsafe { std::ffi::CStr::from_ptr((*grp).gr_name) };
let node = Box::new(hashnode {
next: None,
nam: name.to_string_lossy().into_owned(), flags: 0,
});
f(&node, flags); }
unsafe {
libc::endgrent();
} }
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy)]
pub struct pardef {
pub name: &'static str, pub flags: i32, pub getnfn: usize, pub scantfn: usize, pub hash_gsu: usize, pub array_gsu: usize, pub pm: usize, }
pub type HashGetFn = fn(*mut HashTable, &str) -> Option<Param>;
pub type HashScanFn = fn(*mut HashTable, Option<crate::ported::zsh_h::ScanFunc>, i32);
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub struct PartabHashEntry {
pub name: &'static str,
pub flags: i32,
pub getfn: HashGetFn,
pub scanfn: HashScanFn,
}
pub static PARTAB: &[PartabHashEntry] = &[
PartabHashEntry {
name: "aliases",
flags: PM_HASHED as i32, getfn: getpmralias,
scanfn: scanpmraliases,
},
PartabHashEntry {
name: "builtins",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmbuiltin,
scanfn: scanpmbuiltins,
},
PartabHashEntry {
name: "commands",
flags: PM_HASHED as i32, getfn: getpmcommand,
scanfn: scanpmcommands,
},
PartabHashEntry {
name: "dis_aliases",
flags: PM_HASHED as i32, getfn: getpmdisralias,
scanfn: scanpmdisraliases,
},
PartabHashEntry {
name: "dis_builtins",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmdisbuiltin,
scanfn: scanpmdisbuiltins,
},
PartabHashEntry {
name: "dis_functions",
flags: PM_HASHED as i32, getfn: getpmdisfunction,
scanfn: scanpmdisfunctions,
},
PartabHashEntry {
name: "dis_galiases",
flags: PM_HASHED as i32, getfn: getpmdisgalias,
scanfn: scanpmdisgaliases,
},
PartabHashEntry {
name: "dis_saliases",
flags: PM_HASHED as i32, getfn: getpmdissalias,
scanfn: scanpmdissaliases,
},
PartabHashEntry {
name: "functions",
flags: PM_HASHED as i32, getfn: getpmfunction,
scanfn: scanpmfunctions,
},
PartabHashEntry {
name: "galiases",
flags: PM_HASHED as i32, getfn: getpmgalias,
scanfn: scanpmgaliases,
},
PartabHashEntry {
name: "history",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmhistory,
scanfn: scanpmhistory,
},
PartabHashEntry {
name: "jobdirs",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmjobdir,
scanfn: scanpmjobdirs,
},
PartabHashEntry {
name: "jobstates",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmjobstate,
scanfn: scanpmjobstates,
},
PartabHashEntry {
name: "jobtexts",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmjobtext,
scanfn: scanpmjobtexts,
},
PartabHashEntry {
name: "modules",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmmodule,
scanfn: scanpmmodules,
},
PartabHashEntry {
name: "nameddirs",
flags: PM_HASHED as i32, getfn: getpmnameddir,
scanfn: scanpmnameddirs,
},
PartabHashEntry {
name: "options",
flags: PM_HASHED as i32, getfn: getpmoption,
scanfn: scanpmoptions,
},
PartabHashEntry {
name: "parameters",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmparameter,
scanfn: scanpmparameters,
},
PartabHashEntry {
name: "saliases",
flags: PM_HASHED as i32, getfn: getpmsalias,
scanfn: scanpmsaliases,
},
PartabHashEntry {
name: "userdirs",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmuserdir,
scanfn: scanpmuserdirs,
},
PartabHashEntry {
name: "usergroups",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmusergroups,
scanfn: scanpmusergroups,
},
PartabHashEntry {
name: "dis_functions_source",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmdisfunction_source,
scanfn: scanpmdisfunction_source,
},
PartabHashEntry {
name: "functions_source",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: getpmfunction_source,
scanfn: scanpmfunction_source,
},
PartabHashEntry {
name: "mapfile",
flags: PM_HASHED as i32, getfn: crate::ported::modules::mapfile::getpmmapfile,
scanfn: crate::ported::modules::mapfile::scanpmmapfile,
},
PartabHashEntry {
name: "terminfo",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: crate::ported::modules::terminfo::getterminfo,
scanfn: crate::ported::modules::terminfo::scanterminfo,
},
PartabHashEntry {
name: "termcap",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: crate::ported::modules::termcap::gettermcap,
scanfn: crate::ported::modules::termcap::scantermcap,
},
PartabHashEntry {
name: "widgets",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: crate::ported::zle::zleparameter::getpmwidgets,
scanfn: crate::ported::zle::zleparameter::scanpmwidgets,
},
PartabHashEntry {
name: "sysparams",
flags: PM_HASHED as i32 | PM_READONLY as i32, getfn: crate::ported::modules::system::getpmsysparams,
scanfn: crate::ported::modules::system::scanpmsysparams,
},
];
pub type ArrayGetFn = fn(pm: *mut param) -> Vec<String>;
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
pub struct PartabArrayEntry {
pub name: &'static str,
pub flags: i32,
pub getfn: ArrayGetFn,
}
pub static PARTAB_ARRAY: &[PartabArrayEntry] = &[
PartabArrayEntry {
name: "historywords",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: histwgetfn,
},
PartabArrayEntry {
name: "dirstack",
flags: PM_ARRAY as i32, getfn: dirsgetfn,
},
PartabArrayEntry {
name: "dis_patchars",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: dispatcharsgetfn,
},
PartabArrayEntry {
name: "dis_reswords",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: disreswordsgetfn,
},
PartabArrayEntry {
name: "funcfiletrace",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: funcfiletracegetfn,
},
PartabArrayEntry {
name: "funcsourcetrace",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: funcsourcetracegetfn,
},
PartabArrayEntry {
name: "funcstack",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: funcstackgetfn,
},
PartabArrayEntry {
name: "functrace",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: functracegetfn,
},
PartabArrayEntry {
name: "patchars",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: patcharsgetfn,
},
PartabArrayEntry {
name: "reswords",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: reswordsgetfn,
},
PartabArrayEntry {
name: "historywords",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: histwgetfn,
},
PartabArrayEntry {
name: "errnos",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: crate::ported::modules::system::errnosgetfn,
},
PartabArrayEntry {
name: "keymaps",
flags: PM_ARRAY as i32 | PM_READONLY as i32, getfn: crate::ported::zle::zleparameter::keymapsgetfn,
},
];
#[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 {
INCLEANUP.store(1, std::sync::atomic::Ordering::Relaxed); let ret = handlefeatures(m, module_features(), enables); INCLEANUP.store(0, std::sync::atomic::Ordering::Relaxed); ret }
#[allow(unused_variables)]
pub fn boot_(m: *const module) -> i32 {
0
}
pub fn cleanup_(m: *const module) -> i32 {
INCLEANUP.store(1, std::sync::atomic::Ordering::Relaxed); let ret = setfeatureenables(m, module_features(), None); INCLEANUP.store(0, std::sync::atomic::Ordering::Relaxed); ret }
#[allow(unused_variables)]
pub fn finish_(m: *const module) -> i32 {
0
}
static ALIAS_GSU_HANDLER: OnceLock<
Mutex<HashMap<String, String>>,
> = OnceLock::new();
pub static DIRSTACK: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub static INCLEANUP: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static FUNCSTACK: Mutex<Vec<crate::ported::zsh_h::funcstack>> =
Mutex::new(Vec::new());
fn make_empty_special_pm(name: &str) -> Param {
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: (PM_SCALAR | PM_READONLY | PM_UNSET | PM_SPECIAL) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(String::new()),
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,
})
}
static MODULE_FEATURES: OnceLock<Mutex<crate::ported::zsh_h::features>> = OnceLock::new();
fn featuresarray(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>) -> Vec<String> {
vec![
"p:aliases".to_string(),
"p:builtins".to_string(),
"p:commands".to_string(),
"p:dirstack".to_string(),
"p:dis_aliases".to_string(),
"p:dis_builtins".to_string(),
"p:dis_functions".to_string(),
"p:dis_functions_source".to_string(),
"p:dis_galiases".to_string(),
"p:dis_patchars".to_string(),
"p:dis_reswords".to_string(),
"p:dis_saliases".to_string(),
"p:funcfiletrace".to_string(),
"p:funcsourcetrace".to_string(),
"p:funcstack".to_string(),
"p:functions".to_string(),
"p:functions_source".to_string(),
"p:functrace".to_string(),
"p:galiases".to_string(),
"p:history".to_string(),
"p:historywords".to_string(),
"p:jobdirs".to_string(),
"p:jobstates".to_string(),
"p:jobtexts".to_string(),
"p:modules".to_string(),
"p:nameddirs".to_string(),
"p:options".to_string(),
"p:parameters".to_string(),
"p:patchars".to_string(),
"p:reswords".to_string(),
"p:saliases".to_string(),
"p:userdirs".to_string(),
"p:usergroups".to_string(),
]
}
fn handlefeatures(
_m: *const module,
_f: &Mutex<crate::ported::zsh_h::features>,
enables: &mut Option<Vec<i32>>,
) -> i32 {
if enables.is_none() {
*enables = Some(vec![1; 33]);
}
0
}
fn setfeatureenables(_m: *const module, _f: &Mutex<crate::ported::zsh_h::features>, _e: Option<&[i32]>) -> i32 {
0
}
fn module_features() -> &'static Mutex<crate::ported::zsh_h::features> {
MODULE_FEATURES.get_or_init(|| {
Mutex::new(crate::ported::zsh_h::features {
bn_list: None,
bn_size: 0,
cd_list: None,
cd_size: 0,
mf_list: None,
mf_size: 0,
pd_list: None,
pd_size: 33,
n_abstract: 0,
})
})
}
#[cfg(test)]
mod scan_callback_tests {
use super::*;
use std::sync::atomic::{AtomicI32, Ordering};
use crate::ported::zsh_h::param;
static COLLECTED_COUNT: AtomicI32 = AtomicI32::new(0);
static LAST_NAME_LEN: AtomicI32 = AtomicI32::new(0);
fn counting_func(node: &HashNode, _flags: i32) {
COLLECTED_COUNT.fetch_add(1, Ordering::SeqCst);
LAST_NAME_LEN.store(node.nam.len() as i32, Ordering::SeqCst);
}
fn reset_counters() {
COLLECTED_COUNT.store(0, Ordering::SeqCst);
LAST_NAME_LEN.store(0, Ordering::SeqCst);
}
#[test]
fn scanpmparameters_invokes_func_per_param() {
let _g = crate::test_util::global_state_lock();
reset_counters();
let pm = param {
node: hashnode {
next: None,
nam: "ZSHRS_TEST_SP_A".to_string(),
flags: PM_SCALAR as i32,
},
u_data: 0,
u_arr: None,
u_str: Some("v".to_string()),
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,
};
realparamtab()
.write()
.unwrap()
.insert("ZSHRS_TEST_SP_A".to_string(), Box::new(pm));
scanpmparameters(std::ptr::null_mut(), Some(counting_func), 0);
let observed = COLLECTED_COUNT.load(Ordering::SeqCst);
realparamtab()
.write()
.unwrap()
.remove("ZSHRS_TEST_SP_A");
assert!(
observed >= 1,
"callback fires at least once for the seeded param (got {})",
observed
);
}
#[test]
fn scanpmhistory_empty_ring_invokes_zero_callbacks() {
let _g = crate::test_util::global_state_lock();
reset_counters();
let snapshot: Vec<_> = hist_ring
.lock()
.unwrap()
.drain(..)
.collect();
scanpmhistory(std::ptr::null_mut(), Some(counting_func), 0);
let observed = COLLECTED_COUNT.load(Ordering::SeqCst);
hist_ring
.lock()
.unwrap()
.extend(snapshot);
assert_eq!(observed, 0);
}
#[test]
fn pmjobtext_empty_jobtab_returns_empty() {
let _g = crate::test_util::global_state_lock();
let s = pmjobtext(std::ptr::null_mut(), 1);
assert_eq!(s, "");
}
#[test]
fn pmjobstate_out_of_range_returns_empty() {
let _g = crate::test_util::global_state_lock();
let s = pmjobstate(std::ptr::null_mut(), 9999);
assert_eq!(s, "");
}
#[test]
fn pmjobdir_missing_job_falls_back_to_cwd() {
let _g = crate::test_util::global_state_lock();
let s = pmjobdir(std::ptr::null_mut(), 9999);
assert!(
!s.is_empty(),
"fallback to cwd must produce a non-empty path"
);
assert!(
s.starts_with('/') || s == "" || cfg!(not(unix)),
"Unix path must be absolute (got {s:?})"
);
}
#[test]
fn getpmmodule_unknown_module_marks_unset() {
let _g = crate::test_util::global_state_lock();
let pm = getpmmodule(std::ptr::null_mut(), "definitely_not_a_loaded_module_xyz")
.expect("must return Some");
assert_eq!(
pm.u_str.as_deref(),
Some(""),
"unknown module → empty value string"
);
assert_ne!(
pm.node.flags & PM_UNSET as i32,
0,
"unknown module must set PM_UNSET"
);
assert_ne!(
pm.node.flags & PM_SPECIAL as i32,
0,
"unknown module must set PM_SPECIAL"
);
}
}
#[cfg(test)]
mod setalias_tests {
use super::*;
use crate::ported::zsh_h::param;
#[test]
fn setalias_inserts_entry_into_aliastab() {
let _g = crate::test_util::global_state_lock();
let pm = param {
node: hashnode {
next: None,
nam: "zshrs_test_alias_x".to_string(),
flags: PM_SCALAR as i32,
},
u_data: 0,
u_arr: None,
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,
};
setalias(std::ptr::null_mut(), Box::new(pm), "echo hi".to_string(), 0);
let tab = aliastab_lock()
.read()
.expect("aliastab poisoned");
let entry = tab.get("zshrs_test_alias_x");
assert!(entry.is_some(), "setalias must add to aliastab");
if let Some(a) = entry {
assert_eq!(a.text, "echo hi", "alias value matches createaliasnode arg");
}
}
}
#[cfg(test)]
mod paramtypestr_table_tests {
use super::*;
use crate::ported::zsh_h::param;
fn pm(flags: u32) -> param {
param {
node: hashnode {
next: None,
nam: String::new(),
flags: flags as i32,
},
u_data: 0,
u_arr: None,
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,
}
}
#[test]
fn integer_param_renders_as_integer() {
let _g = crate::test_util::global_state_lock();
assert_eq!(paramtypestr(&pm(PM_INTEGER)), "integer");
}
#[test]
fn float_e_param_renders_as_float() {
let _g = crate::test_util::global_state_lock();
assert_eq!(paramtypestr(&pm(PM_EFLOAT)), "float");
}
#[test]
fn float_f_param_renders_as_float() {
let _g = crate::test_util::global_state_lock();
assert_eq!(paramtypestr(&pm(PM_FFLOAT)), "float");
}
#[test]
fn array_param_renders_as_array() {
let _g = crate::test_util::global_state_lock();
assert_eq!(paramtypestr(&pm(PM_ARRAY)), "array");
}
#[test]
fn hashed_param_renders_as_association() {
let _g = crate::test_util::global_state_lock();
assert_eq!(paramtypestr(&pm(PM_HASHED)), "association");
}
#[test]
fn readonly_modifier_appears_after_type_name() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_INTEGER | PM_READONLY));
assert!(
s.contains("readonly"),
"PM_READONLY must appear in type-string (got {s:?})"
);
}
#[test]
fn exported_modifier_renders_as_export_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_INTEGER | PM_EXPORTED));
assert!(
s.contains("-export"),
"PM_EXPORTED must produce '-export' suffix (got {s:?})"
);
}
#[test]
fn local_modifier_renders_when_level_nonzero() {
let _g = crate::test_util::global_state_lock();
let mut p = pm(PM_INTEGER);
p.level = 1;
let s = paramtypestr(&p);
assert!(
s.contains("-local"),
"level>0 must add '-local' (got {s:?})"
);
}
#[test]
fn unset_param_renders_as_empty_string() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_INTEGER | PM_UNSET | PM_READONLY));
assert_eq!(
s, "",
"c:48,91-92 — PM_UNSET wins over every type + modifier"
);
}
#[test]
fn autoload_param_renders_as_undefined() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
paramtypestr(&pm(PM_AUTOLOAD)),
"undefined",
"c:49-50 — PM_AUTOLOAD → 'undefined'"
);
let s = paramtypestr(&pm(PM_AUTOLOAD | PM_INTEGER | PM_READONLY));
assert_eq!(
s, "undefined",
"c:49-50 — PM_AUTOLOAD precedence over type+modifier"
);
}
#[test]
fn scalar_param_renders_as_scalar() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
paramtypestr(&pm(PM_SCALAR)),
"scalar",
"c:53 — bare PM_SCALAR (zero type bits) → 'scalar'"
);
}
#[test]
fn nameref_param_renders_as_nameref() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
paramtypestr(&pm(PM_NAMEREF)),
"nameref",
"c:54 — PM_NAMEREF → 'nameref'"
);
}
#[test]
fn left_modifier_renders_dash_left_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_LEFT));
assert!(
s.contains("-left"),
"c:65-66 — PM_LEFT → '-left' (got {s:?})"
);
}
#[test]
fn right_b_modifier_renders_dash_right_blanks_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_RIGHT_B));
assert!(
s.contains("-right_blanks"),
"c:67-68 — PM_RIGHT_B → '-right_blanks' (got {s:?})"
);
}
#[test]
fn right_z_modifier_renders_dash_right_zeros_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_RIGHT_Z));
assert!(
s.contains("-right_zeros"),
"c:69-70 — PM_RIGHT_Z → '-right_zeros' (got {s:?})"
);
}
#[test]
fn lower_modifier_renders_dash_lower_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_LOWER));
assert!(
s.contains("-lower"),
"c:71-72 — PM_LOWER → '-lower' (got {s:?})"
);
}
#[test]
fn upper_modifier_renders_dash_upper_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_UPPER));
assert!(
s.contains("-upper"),
"c:73-74 — PM_UPPER → '-upper' (got {s:?})"
);
}
#[test]
fn tagged_modifier_renders_dash_tag_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_TAGGED));
assert!(
s.contains("-tag"),
"c:77-78 — PM_TAGGED → '-tag' (got {s:?})"
);
}
#[test]
fn tied_modifier_renders_dash_tied_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_TIED));
assert!(
s.contains("-tied"),
"c:79-80 — PM_TIED → '-tied' (got {s:?})"
);
}
#[test]
fn unique_modifier_renders_dash_unique_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_UNIQUE));
assert!(
s.contains("-unique"),
"c:83-84 — PM_UNIQUE → '-unique' (got {s:?})"
);
}
#[test]
fn hide_modifier_renders_dash_hide_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_HIDE));
assert!(
s.contains("-hide"),
"c:85-86 — PM_HIDE → '-hide' (got {s:?})"
);
}
#[test]
fn hideval_modifier_renders_dash_hideval_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_HIDEVAL));
assert!(
s.contains("-hideval"),
"c:87-88 — PM_HIDEVAL → '-hideval' (got {s:?})"
);
}
#[test]
fn special_modifier_renders_dash_special_suffix() {
let _g = crate::test_util::global_state_lock();
let s = paramtypestr(&pm(PM_SCALAR | PM_SPECIAL));
assert!(
s.contains("-special"),
"c:89-90 — PM_SPECIAL → '-special' (got {s:?})"
);
}
#[test]
fn multiple_modifiers_concatenate_in_c_source_order() {
let _g = crate::test_util::global_state_lock();
let mut p = pm(PM_INTEGER | PM_LEFT | PM_READONLY | PM_EXPORTED);
p.level = 1;
let s = paramtypestr(&p);
let i_left = s.find("-left").expect("missing -left");
let i_ro = s.find("-readonly").expect("missing -readonly");
let i_exp = s.find("-export").expect("missing -export");
let i_local = s.find("-local").expect("missing -local");
assert!(i_local < i_left, "c:63-64 — -local must precede -left");
assert!(i_left < i_ro, "c:65-76 — -left must precede -readonly");
assert!(i_ro < i_exp, "c:75-82 — -readonly must precede -export");
}
fn mk_pm(flags: u32, level: i32) -> param {
param {
node: hashnode { next: None, nam: String::new(), flags: flags as i32 },
u_data: 0,
u_arr: None,
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,
}
}
#[test]
fn paramtypestr_scalar_with_no_flags_is_scalar() {
let pm = mk_pm(PM_SCALAR, 0);
assert_eq!(paramtypestr(&pm), "scalar");
}
#[test]
fn paramtypestr_integer_flag_is_integer() {
let pm = mk_pm(PM_INTEGER, 0);
assert_eq!(paramtypestr(&pm), "integer");
}
#[test]
fn paramtypestr_array_flag_is_array() {
let pm = mk_pm(PM_ARRAY, 0);
assert_eq!(paramtypestr(&pm), "array");
}
#[test]
fn paramtypestr_hashed_flag_is_association() {
let pm = mk_pm(PM_HASHED, 0);
assert_eq!(paramtypestr(&pm), "association");
}
#[test]
fn paramtypestr_nameref_flag_is_nameref() {
let pm = mk_pm(PM_NAMEREF, 0);
assert_eq!(paramtypestr(&pm), "nameref");
}
#[test]
fn paramtypestr_efloat_flag_is_float() {
let pm = mk_pm(PM_EFLOAT, 0);
assert_eq!(paramtypestr(&pm), "float");
}
#[test]
fn paramtypestr_ffloat_flag_is_float() {
let pm = mk_pm(PM_FFLOAT, 0);
assert_eq!(paramtypestr(&pm), "float");
}
#[test]
fn paramtypestr_unset_short_circuits_to_empty() {
let pm = mk_pm(PM_UNSET | PM_INTEGER, 0);
assert_eq!(paramtypestr(&pm), "");
}
#[test]
fn paramtypestr_autoload_overrides_type_to_undefined() {
let pm = mk_pm(PM_AUTOLOAD | PM_INTEGER, 0);
assert_eq!(paramtypestr(&pm), "undefined");
}
#[test]
fn paramtypestr_integer_readonly_combo() {
let pm = mk_pm(PM_INTEGER | PM_READONLY, 0);
assert_eq!(paramtypestr(&pm), "integer-readonly");
}
#[test]
fn paramtypestr_scalar_exported_combo() {
let pm = mk_pm(PM_SCALAR | PM_EXPORTED, 0);
assert_eq!(paramtypestr(&pm), "scalar-export");
}
#[test]
fn paramtypestr_array_unique_combo() {
let pm = mk_pm(PM_ARRAY | PM_UNIQUE, 0);
assert_eq!(paramtypestr(&pm), "array-unique");
}
#[test]
fn paramtypestr_scalar_lower_combo() {
let pm = mk_pm(PM_SCALAR | PM_LOWER, 0);
assert_eq!(paramtypestr(&pm), "scalar-lower");
}
#[test]
fn paramtypestr_scalar_upper_combo() {
let pm = mk_pm(PM_SCALAR | PM_UPPER, 0);
assert_eq!(paramtypestr(&pm), "scalar-upper");
}
#[test]
fn paramtypestr_scalar_left_combo() {
let pm = mk_pm(PM_SCALAR | PM_LEFT, 0);
assert_eq!(paramtypestr(&pm), "scalar-left");
}
#[test]
fn paramtypestr_scalar_right_blanks_combo() {
let pm = mk_pm(PM_SCALAR | PM_RIGHT_B, 0);
assert_eq!(paramtypestr(&pm), "scalar-right_blanks");
}
#[test]
fn paramtypestr_scalar_right_zeros_combo() {
let pm = mk_pm(PM_SCALAR | PM_RIGHT_Z, 0);
assert_eq!(paramtypestr(&pm), "scalar-right_zeros");
}
#[test]
fn paramtypestr_scalar_hide_combo() {
let pm = mk_pm(PM_SCALAR | PM_HIDE, 0);
assert_eq!(paramtypestr(&pm), "scalar-hide");
}
#[test]
fn paramtypestr_scalar_hideval_combo() {
let pm = mk_pm(PM_SCALAR | PM_HIDEVAL, 0);
assert_eq!(paramtypestr(&pm), "scalar-hideval");
}
#[test]
fn paramtypestr_scalar_special_combo() {
let pm = mk_pm(PM_SCALAR | PM_SPECIAL, 0);
assert_eq!(paramtypestr(&pm), "scalar-special");
}
#[test]
fn paramtypestr_scalar_tied_combo() {
let pm = mk_pm(PM_SCALAR | PM_TIED, 0);
assert_eq!(paramtypestr(&pm), "scalar-tied");
}
#[test]
fn paramtypestr_scalar_tagged_combo() {
let pm = mk_pm(PM_SCALAR | PM_TAGGED, 0);
assert_eq!(paramtypestr(&pm), "scalar-tag");
}
#[test]
fn paramtypestr_level_nonzero_adds_local_suffix() {
let pm = mk_pm(PM_SCALAR, 1);
assert_eq!(paramtypestr(&pm), "scalar-local");
}
#[test]
fn paramtypestr_level_zero_no_local_suffix() {
let pm = mk_pm(PM_SCALAR, 0);
assert!(
!paramtypestr(&pm).contains("-local"),
"no -local when level=0"
);
}
#[test]
fn paramtypestr_many_flags_combined() {
let pm = mk_pm(
PM_INTEGER | PM_READONLY | PM_EXPORTED | PM_TIED | PM_UNIQUE,
2,
);
let s = paramtypestr(&pm);
assert!(s.starts_with("integer"));
assert!(s.contains("-local"));
assert!(s.contains("-readonly"));
assert!(s.contains("-export"));
assert!(s.contains("-tied"));
assert!(s.contains("-unique"));
}
#[test]
fn parameter_corpus_paramtypestr_unset_is_empty() {
let pm = mk_pm(PM_UNSET, 0);
assert_eq!(paramtypestr(&pm), "");
}
#[test]
fn parameter_corpus_paramtypestr_autoload_is_undefined() {
let pm = mk_pm(PM_AUTOLOAD, 0);
assert_eq!(paramtypestr(&pm), "undefined");
}
#[test]
fn parameter_corpus_paramtypestr_scalar() {
let pm = mk_pm(PM_SCALAR, 0);
assert_eq!(paramtypestr(&pm), "scalar");
}
#[test]
fn parameter_corpus_paramtypestr_array() {
let pm = mk_pm(PM_ARRAY, 0);
assert_eq!(paramtypestr(&pm), "array");
}
#[test]
fn parameter_corpus_paramtypestr_integer() {
let pm = mk_pm(PM_INTEGER, 0);
assert_eq!(paramtypestr(&pm), "integer");
}
#[test]
fn parameter_corpus_paramtypestr_efloat() {
let pm = mk_pm(PM_EFLOAT, 0);
assert_eq!(paramtypestr(&pm), "float");
}
#[test]
fn parameter_corpus_paramtypestr_ffloat() {
let pm = mk_pm(PM_FFLOAT, 0);
assert_eq!(paramtypestr(&pm), "float");
}
#[test]
fn parameter_corpus_paramtypestr_hashed() {
let pm = mk_pm(PM_HASHED, 0);
assert_eq!(paramtypestr(&pm), "association");
}
#[test]
fn parameter_corpus_paramtypestr_nameref() {
let pm = mk_pm(PM_NAMEREF, 0);
assert_eq!(paramtypestr(&pm), "nameref");
}
#[test]
fn parameter_corpus_paramtypestr_scalar_local() {
let pm = mk_pm(PM_SCALAR, 2);
assert_eq!(paramtypestr(&pm), "scalar-local");
}
#[test]
fn parameter_corpus_paramtypestr_scalar_left() {
let pm = mk_pm(PM_SCALAR | PM_LEFT, 0);
assert_eq!(paramtypestr(&pm), "scalar-left");
}
#[test]
fn parameter_corpus_paramtypestr_integer_readonly() {
let pm = mk_pm(PM_INTEGER | PM_READONLY, 0);
assert_eq!(paramtypestr(&pm), "integer-readonly");
}
}