use std::io::{self, Write};
use std::path::{Path, PathBuf};
use crate::ported::zsh_h::{
isset, opt_name, AUTONAMEDIRS, BEEP, MULTIBYTE, POSIXIDENTIFIERS,
PRINTEIGHTBIT, XTRACE,
};
use std::sync::atomic::Ordering;
use crate::ported::zsh_h::{QT_NONE, QT_BACKSLASH, QT_SINGLE, QT_DOUBLE, QT_DOLLARS};
use crate::ported::zsh_h::{QT_BACKTICK, QT_SINGLE_OPTIONAL, QT_BACKSLASH_PATTERN, QT_BACKSLASH_SHOWNULL};
use std::os::unix::io::RawFd;
use std::time::UNIX_EPOCH;
use libc::{S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};
use std::io::Read;
use crate::ported::ztype_h::{TYPTAB, TYPTAB_FLAGS, ZTF_INIT, ICNTRL, IDIGIT, IALNUM, IWORD, IIDENT, IUSER, IALPHA, IBLANK, INBLANK, ITOK, IMETA, INULL};
use std::os::unix::fs::PermissionsExt;
use crate::ported::zsh_h::{hashnode, nameddir, ND_NOABBREV, ND_USERNAME};
use crate::ported::ztype_h::{ISPECIAL, ZTF_SP_COMMA};
use std::os::unix::fs::MetadataExt;
use crate::ported::ztype_h::ZTF_BANGCHAR;
use crate::ported::ztype_h::ISEP;
use std::ffi::CString;
use crate::ported::zsh_h::{EMULATE_KSH, EMULATE_SH, EMULATION};
use std::sync::Mutex;
use std::sync::atomic::AtomicI64;
pub static mut SCRIPT_NAME: Option<String> = None;
pub static mut SCRIPT_FILENAME: Option<String> = None;
static SCRIPTNAME: std::sync::OnceLock<std::sync::Mutex<Option<String>>> =
std::sync::OnceLock::new();
static ARGZERO: std::sync::OnceLock<std::sync::Mutex<Option<String>>> =
std::sync::OnceLock::new();
#[allow(non_upper_case_globals)]
pub static errflag: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
static NOERRS: std::sync::OnceLock<std::sync::Mutex<i32>> = std::sync::OnceLock::new();
static LINENO: std::sync::OnceLock<std::sync::Mutex<i32>> = std::sync::OnceLock::new();
static SHINSTDIN_OPT: std::sync::OnceLock<std::sync::Mutex<bool>> = std::sync::OnceLock::new();
pub const ERRFLAG_ERROR: i32 = 1;
pub static INCOMPFUNC: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static RESETNEEDED: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static WINCHANGED: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
fn scriptname_lock() -> &'static std::sync::Mutex<Option<String>> {
SCRIPTNAME.get_or_init(|| std::sync::Mutex::new(None))
}
fn argzero_lock() -> &'static std::sync::Mutex<Option<String>> {
ARGZERO.get_or_init(|| std::sync::Mutex::new(None))
}
fn noerrs_lock() -> &'static std::sync::Mutex<i32> {
NOERRS.get_or_init(|| std::sync::Mutex::new(0))
}
fn lineno_lock() -> &'static std::sync::Mutex<i32> {
LINENO.get_or_init(|| std::sync::Mutex::new(0))
}
fn shinstdin_lock() -> &'static std::sync::Mutex<bool> {
SHINSTDIN_OPT.get_or_init(|| std::sync::Mutex::new(false))
}
pub fn set_scriptname(name: Option<String>) {
*scriptname_lock().lock().unwrap() = name;
}
pub fn scriptname_get() -> Option<String> {
scriptname_lock().lock().unwrap().clone()
}
pub fn locallevel() -> i32 {
crate::ported::params::locallevel.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn inc_locallevel() {
crate::ported::params::locallevel.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn dec_locallevel() {
crate::ported::params::locallevel.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
pub fn set_argzero(name: Option<String>) {
*argzero_lock().lock().unwrap() = name;
}
pub fn argzero() -> Option<String> {
argzero_lock().lock().unwrap().clone()
}
pub fn set_noerrs(v: i32) {
*noerrs_lock().lock().unwrap() = v;
}
pub fn set_locallevel(v: i32) {
crate::ported::params::locallevel.store(v, std::sync::atomic::Ordering::Relaxed);
}
pub fn set_lineno(v: i32) {
*lineno_lock().lock().unwrap() = v;
}
pub fn set_shinstdin(v: bool) {
*shinstdin_lock().lock().unwrap() = v;
}
fn zwarning(cmd: Option<&str>, msg: &str) {
let _ = unsafe { libc::isatty(2) };
let scriptname = scriptname_lock().lock().unwrap().clone();
let argzero = argzero_lock().lock().unwrap().clone();
let locallevel = crate::ported::params::locallevel
.load(std::sync::atomic::Ordering::Relaxed);
let shinstdin = *shinstdin_lock().lock().unwrap();
let prefix: String = scriptname
.or(argzero)
.unwrap_or_default();
let stderr_handle = std::io::stderr();
let mut stderr_lock = stderr_handle.lock();
if let Some(cmd) = cmd {
if !shinstdin || locallevel != 0 {
let _ = stderr_lock.write_all(nicezputs(&prefix).as_bytes());
let _ = stderr_lock.write_all(b":");
}
let _ = stderr_lock.write_all(nicezputs(cmd).as_bytes());
let _ = stderr_lock.write_all(b":");
} else {
let to_emit = if shinstdin && locallevel == 0 {
"zsh"
} else {
prefix.as_str()
};
let _ = stderr_lock.write_all(nicezputs(to_emit).as_bytes());
let _ = stderr_lock.write_all(b":");
}
let lineno = *lineno_lock().lock().unwrap();
if (!shinstdin || locallevel != 0) && lineno != 0 {
let _ = stderr_lock.write_all(format!("{}: ", lineno).as_bytes());
} else {
let _ = stderr_lock.write_all(b" ");
}
let _ = stderr_lock.write_all(msg.as_bytes());
let _ = stderr_lock.write_all(b"\n");
let _ = stderr_lock.flush();
}
pub fn zerr(msg: &str) { let noerrs = *noerrs_lock().lock().unwrap();
if errflag.load(Ordering::Relaxed) != 0 || noerrs != 0 { if noerrs < 2 { errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); }
return; }
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); zwarning(None, msg); }
pub fn zerrnam(cmd: &str, msg: &str) { let noerrs = *noerrs_lock().lock().unwrap();
if errflag.load(Ordering::Relaxed) != 0 || noerrs != 0 { return;
}
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed); zwarning(Some(cmd), msg); }
pub fn zwarn(msg: &str) { let noerrs = *noerrs_lock().lock().unwrap();
if errflag.load(Ordering::Relaxed) != 0 || noerrs != 0 { return;
}
zwarning(None, msg); }
pub fn zwarnnam(cmd: &str, msg: &str) { let noerrs = *noerrs_lock().lock().unwrap();
if errflag.load(Ordering::Relaxed) != 0 || noerrs != 0 { return;
}
zwarning(Some(cmd), msg); }
pub fn zerrmsg(msg: &str, errno: Option<i32>) { let lineno = *lineno_lock().lock().unwrap();
let shinstdin = *shinstdin_lock().lock().unwrap();
let locallevel = crate::ported::params::locallevel
.load(std::sync::atomic::Ordering::Relaxed);
if (!shinstdin || locallevel != 0) && lineno != 0 {
eprint!("{}: ", lineno);
}
if let Some(e) = errno {
eprintln!("{}: {}", msg, std::io::Error::from_raw_os_error(e));
} else {
eprintln!("{}", msg);
}
}
pub fn nicechar(c: char) -> String { if c.is_ascii_control() {
match c {
'\n' => "\\n".to_string(),
'\t' => "\\t".to_string(),
'\r' => "\\r".to_string(),
'\x1b' => "\\e".to_string(),
_ => format!("^{}", ((c as u8) + 64) as char),
}
} else if c == '\x7f' {
"^?".to_string()
} else {
c.to_string()
}
}
pub fn nicezputs(s: &str) -> String { s.chars().map(nicechar).collect()
}
pub fn tulower(c: char) -> char { c.to_lowercase().next().unwrap_or(c)
}
pub fn tuupper(c: char) -> char { c.to_uppercase().next().unwrap_or(c)
}
pub fn isident(s: &str) -> bool { let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub fn zsleep(us: i64) -> i32 { let mut sleeptime = libc::timespec { tv_sec: (us / 1_000_000) as libc::time_t,
tv_nsec: ((us % 1_000_000) * 1000) as libc::c_long,
};
#[cfg(unix)]
{
loop { let mut rem = libc::timespec { tv_sec: 0, tv_nsec: 0 };
let ret = unsafe { libc::nanosleep(&sleeptime, &mut rem) }; if ret == 0 { return 1;
}
let err = std::io::Error::last_os_error()
.raw_os_error().unwrap_or(0);
if err != libc::EINTR { return 0; }
sleeptime = rem; }
}
#[cfg(not(unix))]
{
let _ = sleeptime;
std::thread::sleep(std::time::Duration::from_micros(us.max(0) as u64));
1
}
}
pub fn zclose(fd: i32) { #[cfg(unix)]
unsafe {
libc::close(fd);
}
}
pub fn adjustcolumns() -> usize { #[cfg(unix)]
{
unsafe {
let mut ws: libc::winsize = std::mem::zeroed();
if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 && ws.ws_col > 0 {
return ws.ws_col as usize;
}
}
}
crate::ported::params::getsparam("COLUMNS")
.and_then(|s| s.parse().ok())
.unwrap_or(80)
}
pub fn adjustlines() -> usize { #[cfg(unix)]
{
unsafe {
let mut ws: libc::winsize = std::mem::zeroed();
if libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) == 0 && ws.ws_row > 0 {
return ws.ws_row as usize;
}
}
}
crate::ported::params::getsparam("LINES")
.and_then(|s| s.parse().ok())
.unwrap_or(24)
}
pub fn qflag_quotetype(count: u32) -> i32 {
match count {
0 => QT_NONE,
1 => QT_BACKSLASH,
2 => QT_SINGLE,
3 => QT_DOUBLE,
_ => QT_DOLLARS,
}
}
fn ispecial(c: char) -> bool {
matches!(
c,
'|' | '&'
| ';'
| '<'
| '>'
| '('
| ')'
| '$'
| '`'
| '"'
| '\''
| '\\'
| ' '
| '\t'
| '\n'
| '='
| '['
| ']'
| '*'
| '?'
| '#'
| '~'
| '{'
| '}'
| '!'
| '^'
)
}
pub fn quotestring(s: &str, quote_type: i32) -> String { if s.is_empty() {
return if quote_type == QT_NONE {
String::new()
} else if quote_type == QT_BACKSLASH || quote_type == QT_BACKSLASH_SHOWNULL {
"''".to_string()
} else if quote_type == QT_SINGLE || quote_type == QT_SINGLE_OPTIONAL {
"''".to_string()
} else if quote_type == QT_DOUBLE {
"\"\"".to_string()
} else if quote_type == QT_DOLLARS {
"$''".to_string()
} else {
String::new()
};
}
if quote_type == QT_NONE {
s.to_string()
} else if quote_type == QT_BACKSLASH_PATTERN {
let mut result = String::with_capacity(s.len() * 2);
for c in s.chars() {
if matches!(c, '*' | '?' | '[' | ']' | '<' | '>' | '(' | ')' | '|' | '#' | '^' | '~') {
result.push('\\');
}
result.push(c);
}
result
} else if quote_type == QT_BACKSLASH || quote_type == QT_BACKSLASH_SHOWNULL {
let mut result = String::with_capacity(s.len() * 2);
for c in s.chars() {
if ispecial(c) {
result.push('\\');
}
result.push(c);
}
result
} else if quote_type == QT_SINGLE {
let mut result = String::with_capacity(s.len() + 4);
result.push('\'');
for c in s.chars() {
if c == '\'' {
result.push_str("'\\''");
} else if c == '\n' {
result.push_str("'$'\\n''");
} else {
result.push(c);
}
}
result.push('\'');
result
} else if quote_type == QT_SINGLE_OPTIONAL {
let needs_quoting = s.chars().any(ispecial);
if !needs_quoting {
return s.to_string();
}
let mut result = String::with_capacity(s.len() + 4);
let mut in_quotes = false;
for c in s.chars() {
if c == '\'' {
if in_quotes { result.push('\''); in_quotes = false; }
result.push_str("\\'");
} else if ispecial(c) {
if !in_quotes { result.push('\''); in_quotes = true; }
result.push(c);
} else {
if in_quotes { result.push('\''); in_quotes = false; }
result.push(c);
}
}
if in_quotes { result.push('\''); }
result
} else if quote_type == QT_DOUBLE {
let mut result = String::with_capacity(s.len() + 4);
result.push('"');
for c in s.chars() {
if matches!(c, '$' | '`' | '"' | '\\') {
result.push('\\');
}
result.push(c);
}
result.push('"');
result
} else if quote_type == QT_DOLLARS {
let mut result = String::with_capacity(s.len() + 4);
result.push_str("$'");
for c in s.chars() {
match c {
'\\' | '\'' => { result.push('\\'); result.push(c); }
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
'\x1b' => result.push_str("\\e"),
'\x07' => result.push_str("\\a"),
'\x08' => result.push_str("\\b"),
'\x0c' => result.push_str("\\f"),
'\x0b' => result.push_str("\\v"),
c if c.is_ascii_control() => {
result.push_str(&format!("\\{:03o}", c as u8));
}
c => result.push(c),
}
}
result.push('\'');
result
} else if quote_type == QT_BACKTICK {
s.replace('`', "\\`")
} else {
s.to_string()
}
}
pub fn sepsplit(s: &str, sep: Option<&str>, allownull: bool) -> Vec<String> { let s = if s.starts_with('\x00') && s.len() > 1 {
&s[1..]
} else {
s
};
match sep {
None => spacesplit(s, allownull),
Some("") => {
if allownull {
s.chars().map(|c| c.to_string()).collect()
} else {
s.chars()
.map(|c| c.to_string())
.filter(|c| !c.is_empty())
.collect()
}
}
Some(sep) => {
let parts: Vec<String> = s.split(sep).map(|p| p.to_string()).collect();
if allownull {
parts
} else {
parts.into_iter().filter(|p| !p.is_empty()).collect()
}
}
}
}
pub fn spacesplit(s: &str, allownull: bool) -> Vec<String> { if allownull {
s.split([' ', '\t', '\n']).map(|p| p.to_string()).collect()
} else {
s.split_whitespace().map(|p| p.to_string()).collect()
}
}
pub fn sepjoin(arr: &[String], sep: Option<&str>) -> String { if arr.is_empty() {
return String::new();
}
let sep = sep.unwrap_or(" ");
arr.join(sep)
}
pub fn zstrtol(s: &str, base: i32) -> (i64, &str) { zstrtol_underscore(s, base, false) }
pub fn zstrtoul_underscore(s: &str) -> Option<u64> { let s = s.trim();
let s = s.strip_prefix('+').unwrap_or(s);
let (base, rest) = if s.starts_with("0x") || s.starts_with("0X") {
(16, &s[2..])
} else if s.starts_with("0b") || s.starts_with("0B") {
(2, &s[2..])
} else if s.starts_with('0') && s.len() > 1 {
(8, &s[1..])
} else {
(10, s)
};
let rest = rest.replace('_', "");
u64::from_str_radix(&rest, base).ok()
}
pub fn convbase(val: i64, base: u32) -> String {
if base == 0 || base == 10 {
return val.to_string();
}
let neg = val < 0;
let abs = if neg { (val as i128).wrapping_neg() as u128 } else { val as u128 };
let s = match base {
2 => format!("2#{:b}", abs),
8 => format!("8#{:o}", abs),
16 => format!("16#{:X}", abs),
r if (2..=36).contains(&r) => {
let digits = "0123456789abcdefghijklmnopqrstuvwxyz".as_bytes();
let mut tmp = abs;
let mut buf = String::new();
if tmp == 0 { buf.push('0'); }
while tmp > 0 {
buf.push(digits[(tmp % r as u128) as usize] as char);
tmp /= r as u128;
}
format!("{}#{}", r, buf.chars().rev().collect::<String>())
}
_ => val.to_string(),
};
if neg { format!("-{}", s) } else { s }
}
pub fn setblock_fd(fd: i32, blocking: bool) -> bool { #[cfg(unix)]
{
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL, 0) };
if flags < 0 {
return false;
}
let new_flags = if blocking {
flags & !libc::O_NONBLOCK
} else {
flags | libc::O_NONBLOCK
};
if new_flags != flags {
unsafe { libc::fcntl(fd, libc::F_SETFL, new_flags) >= 0 }
} else {
true
}
}
#[cfg(not(unix))]
{
let _ = (fd, blocking);
false
}
}
pub fn read_poll(fd: i32, timeout_us: i64) -> bool { #[cfg(unix)]
{
let mut fds = [libc::pollfd {
fd: fd as RawFd,
events: libc::POLLIN,
revents: 0,
}];
let timeout_ms = (timeout_us / 1000) as i32;
let result = unsafe { libc::poll(fds.as_mut_ptr(), 1, timeout_ms) };
result > 0 && (fds[0].revents & libc::POLLIN) != 0
}
#[cfg(not(unix))]
{
let _ = (fd, timeout_us);
false
}
}
pub fn checkglobqual(s: &str) -> bool { if !s.ends_with(')') {
return false;
}
let mut depth = 0;
let mut in_bracket = false;
for c in s.chars() {
match c {
'[' if !in_bracket => in_bracket = true,
']' if in_bracket => in_bracket = false,
'(' if !in_bracket => depth += 1,
')' if !in_bracket => {
if depth > 0 {
depth -= 1;
} else {
return false;
}
}
_ => {}
}
}
depth == 0
}
pub fn spdist(s: &str, t: &str, max_dist: usize) -> usize { let s_chars: Vec<char> = s.chars().collect();
let t_chars: Vec<char> = t.chars().collect();
let m = s_chars.len();
let n = t_chars.len();
if m.abs_diff(n) > max_dist {
return max_dist + 1;
}
let mut prev: Vec<usize> = (0..=n).collect();
let mut curr = vec![0; n + 1];
for i in 1..=m {
curr[0] = i;
for j in 1..=n {
let cost = if s_chars[i - 1] == t_chars[j - 1] {
0
} else {
1
};
curr[j] = (prev[j] + 1).min(curr[j - 1] + 1).min(prev[j - 1] + cost);
}
std::mem::swap(&mut prev, &mut curr);
}
prev[n]
}
pub fn gettempname(prefix: Option<&str>, _use_heap: bool) -> Option<String> { let suffix = if prefix.is_some() { ".XXXXXX" } else { "XXXXXX" }; crate::ported::signals::queue_signals(); let prefix_owned: String = match prefix { Some(p) => p.to_string(),
None => crate::ported::params::getsparam("TMPPREFIX")
.unwrap_or_else(|| crate::ported::config_h::DEFAULT_TMPPREFIX.to_string()),
};
let template = format!("{}{}", prefix_owned, suffix); let pid = std::process::id();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let unique = format!("{:x}{:x}", pid, nanos & 0xffffff);
let name = template.replace("XXXXXX", &unique);
crate::ported::signals::unqueue_signals(); Some(name)
}
pub fn has_token(s: &str) -> bool { s.bytes().any(|b| (0x83..=0x9f).contains(&b))
}
pub fn arrlen<T>(s: &[T]) -> usize { s.len()
}
pub fn dupstrpfx(s: &str, len: usize) -> String { let bytes = s.as_bytes();
let take = len.min(bytes.len());
String::from_utf8_lossy(&bytes[..take]).into_owned()
}
pub fn unmeta(s: &str) -> String { let bytes = s.as_bytes();
if !bytes.iter().any(|&b| b == Meta) {
return s.to_string();
}
let mut buf = bytes.to_vec();
let len = unmetafy(&mut buf); buf.truncate(len);
String::from_utf8_lossy(&buf).into_owned()
}
pub fn ztrlen(s: &str) -> usize { let mut len = 0;
let chars: Vec<char> = s.chars().collect();
let mut i = 0;
while i < chars.len() {
len += 1;
if chars[i] as u32 == Meta as u32 && i + 1 < chars.len() {
i += 2;
} else {
i += 1;
}
}
len
}
pub fn ztrcmp(s1: &str, s2: &str) -> std::cmp::Ordering { let b1 = s1.as_bytes();
let b2 = s2.as_bytes();
let mut i1 = 0;
let mut i2 = 0;
while i1 < b1.len() && i2 < b2.len() && b1[i1] == b2[i2] {
i1 += 1;
i2 += 1;
}
let c1: i32 = if i1 >= b1.len() {
-1
} else if b1[i1] == Meta && i1 + 1 < b1.len() {
(b1[i1 + 1] ^ 32) as i32
} else {
b1[i1] as i32
};
let c2: i32 = if i2 >= b2.len() {
-1
} else if b2[i2] == Meta && i2 + 1 < b2.len() {
(b2[i2 + 1] ^ 32) as i32
} else {
b2[i2] as i32
};
c1.cmp(&c2)
}
pub fn ztrsub(t: &str, s: &str) -> usize { ztrlen(&t[..t.len().saturating_sub(s.len())])
}
pub fn statuidprint(uid: u32) -> Option<String> {
#[cfg(unix)]
{
let pwd = unsafe { libc::getpwuid(uid) };
if pwd.is_null() {
return None;
}
let name = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_name) };
name.to_str().ok().map(|s| s.to_string())
}
#[cfg(not(unix))]
{
let _ = uid;
None
}
}
pub fn ztrdup(s: &str) -> String { s.to_string()
}
pub fn ztrncpy(s: &str, n: usize) -> String {
s.chars().take(n).collect()
}
pub fn dyncat(s1: &str, s2: &str) -> String { format!("{}{}", s1, s2)
}
pub fn tricat(s1: &str, s2: &str, s3: &str) -> String { format!("{}{}{}", s1, s2, s3)
}
pub fn bicat(s1: &str, s2: &str) -> String { format!("{}{}", s1, s2)
}
pub fn wordcount(s: &str, sep: Option<&str>, mul: i32) -> i32 { let bytes = s.as_bytes();
if let Some(sep) = sep {
let sep_bytes = sep.as_bytes();
let sl = sep_bytes.len();
let mut r: i32 = 1;
let mut pos = 0;
while pos <= bytes.len() {
let rest = &bytes[pos..];
let c_offset = match sep_bytes.is_empty() {
true => Some(0usize),
false => rest
.windows(sl)
.position(|w| w == sep_bytes),
};
let Some(c) = c_offset else { break };
let after_off = pos + c + sl;
let following_nonempty = after_off < bytes.len();
let cond_b = sl != 0 || following_nonempty;
if (c != 0 || mul != 0) && cond_b {
r += 1;
}
if sl == 0 {
pos += 1;
} else {
pos += c + sl;
}
}
r
} else {
let mut s_pos = 0usize;
let t_orig = s_pos;
let mut r: i32 = 0;
if mul <= 0 {
while s_pos < bytes.len() && iwsep(bytes[s_pos] as char) {
s_pos += 1;
}
}
let has_word_now = s_pos < bytes.len()
&& !iwsep(bytes[s_pos] as char);
if has_word_now || (mul < 0 && t_orig != s_pos) {
r += 1;
}
while s_pos < bytes.len() {
let word_start = s_pos;
while s_pos < bytes.len() && !iwsep(bytes[s_pos] as char) {
s_pos += 1;
}
if s_pos > word_start && mul <= 0 {
while s_pos < bytes.len() && iwsep(bytes[s_pos] as char) {
s_pos += 1;
}
}
if s_pos < bytes.len() && iwsep(bytes[s_pos] as char) {
s_pos += 1;
}
let t_after = s_pos;
if mul <= 0 {
while s_pos < bytes.len() && iwsep(bytes[s_pos] as char) {
s_pos += 1;
}
}
if s_pos < bytes.len() {
r += 1;
} else {
if mul < 0 && t_after != s_pos {
r += 1;
}
break;
}
}
r
}
}
pub fn zjoin(arr: &[String], delim: char) -> String {
arr.join(&delim.to_string())
}
pub fn colonsplit(s: &str, uniq: bool) -> Vec<String> {
let mut result = Vec::new();
for item in s.split(':') {
if !item.is_empty() {
if uniq && result.contains(&item.to_string()) {
continue;
}
result.push(item.to_string());
}
}
result
}
pub fn skipwsep(s: &str) -> (&str, usize) {
let bytes = s.as_bytes();
let mut i: usize = 0;
let mut count: usize = 0;
while i < bytes.len() {
let b = if bytes[i] == Meta && i + 1 < bytes.len() {
bytes[i + 1] ^ 32
} else {
bytes[i]
};
if !iwsep_byte(b) {
break;
}
if bytes[i] == Meta {
i += 1;
}
i += 1;
count += 1;
}
(&s[i..], count)
}
pub fn iwsep(c: char) -> bool {
c == ' ' || c == '\t' || c == '\n'
}
#[inline]
pub fn iwsep_byte(b: u8) -> bool {
b == b' ' || b == b'\t' || b == b'\n'
}
pub fn imeta(c: char) -> bool {
(c as u32) >= Meta as u32
}
pub fn ztrftime(fmt: &str, time: std::time::SystemTime) -> String {
let duration = time.duration_since(UNIX_EPOCH).unwrap_or_default();
let secs = duration.as_secs() as i64;
#[cfg(unix)]
unsafe {
let tm = libc::localtime(&secs);
if tm.is_null() {
return String::new();
}
let mut buf = vec![0u8; 256];
let c_fmt = std::ffi::CString::new(fmt).unwrap_or_default();
let len = libc::strftime(
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
c_fmt.as_ptr(),
tm,
);
if len > 0 {
buf.truncate(len);
String::from_utf8_lossy(&buf).to_string()
} else {
String::new()
}
}
#[cfg(not(unix))]
{
let _ = (fmt, secs);
String::new()
}
}
pub fn gethostname() -> String {
#[cfg(unix)]
{
let mut buf = vec![0u8; 256];
unsafe {
if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len()) == 0 {
let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
return String::from_utf8_lossy(&buf[..len]).to_string();
}
}
}
crate::ported::params::getsparam("HOST")
.or_else(|| crate::ported::params::getsparam("HOSTNAME"))
.unwrap_or_else(|| "localhost".to_string())
}
pub fn zgetcwd() -> Option<String> {
std::env::current_dir()
.ok()
.map(|p| p.to_string_lossy().to_string())
}
pub fn zchdir(path: &str) -> bool {
std::env::set_current_dir(path).is_ok()
}
pub fn realpath(path: &str) -> Option<String> {
std::fs::canonicalize(path)
.ok()
.map(|p| p.to_string_lossy().to_string())
}
pub fn xsymlinks(s: &str) -> std::io::Result<String> { if s.is_empty() {
return Ok(String::new());
}
let path = if !s.starts_with('/') {
let cwd = std::env::current_dir()?;
format!("{}/{}", cwd.display(), s)
} else {
s.to_string()
};
let mut result = Vec::new();
for component in path.split('/') {
match component {
"" | "." => continue,
".." => {
if !result.is_empty() && result.last() != Some(&"..") {
result.pop();
} else if result.is_empty() && !path.starts_with('/') {
result.push("..");
}
}
c => result.push(c),
}
}
if path.starts_with('/') {
Ok(format!("/{}", result.join("/")))
} else if result.is_empty() {
Ok(".".to_string())
} else {
Ok(result.join("/"))
}
}
pub fn mkdir(path: &str) -> bool {
std::fs::create_dir(path).is_ok()
}
pub fn symlink(src: &str, dst: &str) -> bool {
#[cfg(unix)]
{
std::os::unix::fs::symlink(src, dst).is_ok()
}
#[cfg(not(unix))]
{
let _ = (src, dst);
false
}
}
pub fn readlink(path: &str) -> Option<String> {
std::fs::read_link(path)
.ok()
.map(|p| p.to_string_lossy().to_string())
}
pub fn getenv(name: &str) -> Option<String> {
std::env::var(name).ok()
}
pub fn setenv(name: &str, value: &str) {
std::env::set_var(name, value);
}
pub fn unsetenv(name: &str) {
std::env::remove_var(name);
}
pub fn getuid() -> u32 {
#[cfg(unix)]
unsafe {
libc::getuid()
}
#[cfg(not(unix))]
0
}
pub fn geteuid() -> u32 {
#[cfg(unix)]
unsafe {
libc::geteuid()
}
#[cfg(not(unix))]
0
}
pub fn getgid() -> u32 {
#[cfg(unix)]
unsafe {
libc::getgid()
}
#[cfg(not(unix))]
0
}
pub fn getegid() -> u32 {
#[cfg(unix)]
unsafe {
libc::getegid()
}
#[cfg(not(unix))]
0
}
pub fn getpid() -> i32 {
std::process::id() as i32
}
pub fn getppid() -> i32 {
#[cfg(unix)]
unsafe {
libc::getppid()
}
#[cfg(not(unix))]
0
}
pub fn printtime(secs: i64) -> String {
let hours = secs / 3600;
let mins = (secs % 3600) / 60;
let secs = secs % 60;
if hours > 0 {
format!("{}:{:02}:{:02}", hours, mins, secs)
} else {
format!("{}:{:02}", mins, secs)
}
}
pub fn slashsplit(s: &str) -> Vec<String> { s.split('/')
.filter(|s| !s.is_empty())
.map(String::from)
.collect()
}
pub fn equalsplit(s: &str) -> Option<(String, String)> {
let eq = s.find('=')?;
Some((s[..eq].to_string(), s[eq + 1..].to_string()))
}
pub fn mkarray(s: Option<&str>) -> Vec<String> {
match s {
Some(val) => vec![val.to_string()],
None => Vec::new(),
}
}
pub fn freearray(s: Vec<String>) {
}
pub fn strpfx(s: &str, t: &str) -> bool {
t.starts_with(s)
}
pub fn strsfx(s: &str, t: &str) -> bool {
t.ends_with(s)
}
pub fn zbeep() { crate::ported::signals::queue_signals(); if let Ok(zbeep) = std::env::var("ZBEEP") { let (decoded, _) = getkeystring(&zbeep); #[cfg(unix)]
{
let shtty = crate::ported::init::SHTTY.load(Ordering::Relaxed);
if shtty != -1 {
let _ = write_loop(shtty, decoded.as_bytes()); } else {
eprint!("{}", decoded);
}
}
#[cfg(not(unix))]
eprint!("{}", decoded);
} else if isset(crate::ported::zsh_h::BEEP) { #[cfg(unix)]
{
let shtty = crate::ported::init::SHTTY.load(Ordering::Relaxed);
if shtty != -1 {
let _ = write_loop(shtty, b"\x07"); } else {
eprint!("\x07");
}
}
#[cfg(not(unix))]
eprint!("\x07");
}
crate::ported::signals::unqueue_signals(); }
pub fn mode_to_octal(mode: u32) -> i32 {
#[cfg(unix)]
#[cfg(not(unix))]
{
let _ = mode;
return 0;
}
#[cfg(unix)]
{
let m = mode as u32;
let mut o: i32 = 0;
if m & S_ISUID as u32 != 0 { o |= 0o4000; }
if m & S_ISGID as u32 != 0 { o |= 0o2000; }
if m & S_ISVTX as u32 != 0 { o |= 0o1000; }
if m & S_IRUSR as u32 != 0 { o |= 0o0400; }
if m & S_IWUSR as u32 != 0 { o |= 0o0200; }
if m & S_IXUSR as u32 != 0 { o |= 0o0100; }
if m & S_IRGRP as u32 != 0 { o |= 0o0040; }
if m & S_IWGRP as u32 != 0 { o |= 0o0020; }
if m & S_IXGRP as u32 != 0 { o |= 0o0010; }
if m & S_IROTH as u32 != 0 { o |= 0o0004; }
if m & S_IWOTH as u32 != 0 { o |= 0o0002; }
if m & S_IXOTH as u32 != 0 { o |= 0o0001; }
o
}
}
pub fn upchdir(n: usize) -> io::Result<()> {
let mut path = String::new();
for i in 0..n {
if i > 0 {
path.push('/');
}
path.push_str("..");
}
std::env::set_current_dir(&path)?;
Ok(())
}
pub fn lchdir(path: &str) -> io::Result<()> {
let resolved = if path.starts_with('/') {
PathBuf::from(path)
} else {
let cwd = std::env::current_dir()?;
cwd.join(path)
};
std::env::set_current_dir(&resolved)?;
Ok(())
}
pub fn adjustwinsize() -> (usize, usize) {
(adjustcolumns(), adjustlines())
}
pub fn spckword(word: &str, candidates: &[&str], threshold: usize) -> Option<String> { let mut best = None;
let mut best_dist = threshold + 1;
for &candidate in candidates {
let dist = spdist(word, candidate, threshold);
if dist < best_dist {
best_dist = dist;
best = Some(candidate.to_string());
}
}
best
}
pub fn getquery(prompt: &str, valid_chars: &str) -> Option<char> {
eprint!("{}", prompt);
let _ = io::stderr().flush();
let mut buf = [0u8; 1];
#[cfg(unix)]
{
if std::io::stdin().read_exact(&mut buf).is_ok() {
let c = buf[0] as char;
if valid_chars.is_empty() || valid_chars.contains(c) {
return Some(c);
}
}
}
None
}
pub fn read1char() -> Option<char> {
#[cfg(unix)]
{
let mut buf = [0u8; 1];
if std::io::stdin().read_exact(&mut buf).is_ok() {
return Some(buf[0] as char);
}
}
None
}
pub fn checkrmall(s: &str) -> bool {
if let Some(c) = getquery(
&format!("zsh: sure you want to delete all of {}? [yn] ", s),
"yn",
) {
c == 'y' || c == 'Y'
} else {
false
}
}
pub fn xsymlink(path: &str) -> Option<String> { if !path.starts_with('/') {
return None;
}
match std::fs::canonicalize(path) {
Ok(p) => Some(p.to_string_lossy().into_owned()),
Err(_) => {
zwarn("path expansion failed, using root directory");
Some("/".to_string())
}
}
}
pub fn privasserted() -> bool {
#[cfg(unix)]
{
if unsafe { libc::geteuid() } == 0 {
return true;
}
}
#[cfg(all(target_os = "linux", feature = "libcap"))]
{
if let Ok(text) = crate::ported::modules::cap::cap_get_proc() {
if !text.is_empty() && text != "=" {
return true;
}
}
}
false
}
pub fn findpwd(s: &str) -> Option<String> { if s.starts_with('/') { return xsymlink(s); }
let pwd = crate::ported::params::getsparam("PWD")
.or_else(|| std::env::current_dir().ok()
.map(|p| p.to_string_lossy().into_owned()))
.unwrap_or_default(); let prefix: &str = if pwd.len() > 1 { &pwd } else { "" }; let combined = format!("{}/{}", prefix, s); xsymlink(&combined) }
pub fn fprintdir(s: &str) -> String { match finddir(s) { None => unmeta(s), Some(rendered) => rendered, }
}
pub fn arrdup(s: &[String]) -> Vec<String> {
s.to_vec()
}
pub fn arrdup_max(s: &[String], max: usize) -> Vec<String> {
s.iter().take(max).cloned().collect()
}
pub fn read_loop(fd: i32, buf: &mut [u8]) -> io::Result<usize> {
#[cfg(unix)]
{
let mut total = 0;
while total < buf.len() {
let n = unsafe {
libc::read(
fd,
buf[total..].as_mut_ptr() as *mut libc::c_void,
buf.len() - total,
)
};
if n <= 0 {
if n < 0 {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(e);
}
break;
}
total += n as usize;
}
Ok(total)
}
#[cfg(not(unix))]
{
let _ = (fd, buf);
Err(io::Error::new(io::ErrorKind::Unsupported, "not unix"))
}
}
pub fn write_loop(fd: i32, buf: &[u8]) -> io::Result<usize> {
#[cfg(unix)]
{
let mut total = 0;
while total < buf.len() {
let n = unsafe {
libc::write(
fd,
buf[total..].as_ptr() as *const libc::c_void,
buf.len() - total,
)
};
if n <= 0 {
if n < 0 {
let e = io::Error::last_os_error();
if e.kind() == io::ErrorKind::Interrupted {
continue;
}
return Err(e);
}
break;
}
total += n as usize;
}
Ok(total)
}
#[cfg(not(unix))]
{
let _ = (fd, buf);
Err(io::Error::new(io::ErrorKind::Unsupported, "not unix"))
}
}
pub fn redup(x: i32, y: i32) -> i32 { let mut ret = y; #[cfg(unix)]
{
if x < 0 { zclose(y); } else if x != y { if unsafe { libc::dup2(x, y) } == -1 { ret = -1; }
zclose(x); }
}
#[cfg(not(unix))]
{
let _ = (x, y);
}
ret }
pub fn itype_end(s: &str, allow_digits_start: bool) -> usize {
let mut chars = s.chars().peekable();
let mut pos = 0;
if let Some(&first) = chars.peek() {
if !allow_digits_start && first.is_ascii_digit() {
return 0;
}
if !first.is_alphanumeric() && first != '_' && first != '.' {
return 0;
}
}
for c in s.chars() {
if c.is_alphanumeric() || c == '_' || c == '.' {
pos += c.len_utf8();
} else {
break;
}
}
pos
}
pub fn inittyptab() {
{
let mut flags = TYPTAB_FLAGS.lock().unwrap();
if (*flags & ZTF_INIT) == 0 {
*flags = ZTF_INIT;
}
}
let mut t = TYPTAB.lock().unwrap();
for slot in t.iter_mut() { *slot = 0; }
for c in 0..32u32 {
t[c as usize] = ICNTRL as u32;
t[(c + 128) as usize] = ICNTRL as u32;
}
t[127] = ICNTRL as u32;
for c in (b'0' as usize)..=(b'9' as usize) {
t[c] = (IDIGIT | IALNUM | IWORD | IIDENT | IUSER) as u32;
}
for c in (b'a' as usize)..=(b'z' as usize) {
let upper = c - (b'a' as usize) + (b'A' as usize);
let bits = (IALPHA | IALNUM | IIDENT | IUSER | IWORD) as u32;
t[c] = bits;
t[upper] = bits;
}
t[b'_' as usize] = (IIDENT | IUSER) as u32;
t[b'-' as usize] = IUSER as u32;
t[b'.' as usize] = IUSER as u32;
t[b' ' as usize] |= (IBLANK | INBLANK) as u32;
t[b'\t' as usize] |= (IBLANK | INBLANK) as u32;
t[b'\n' as usize] |= INBLANK as u32;
{
use crate::ported::zsh_h::{Marker, META};
t[META as usize] |= IMETA as u32;
t[Marker as usize] |= IMETA as u32;
}
{
use crate::ported::zsh_h::{LAST_NORMAL_TOK, Pound};
let lo = Pound as usize;
let hi = LAST_NORMAL_TOK as usize;
for t0 in lo..=hi {
t[t0] |= (ITOK | IMETA) as u32;
}
}
{
use crate::ported::zsh_h::{Nularg, Snull};
let lo = Snull as usize;
let hi = Nularg as usize;
for t0 in lo..=hi {
t[t0] |= (ITOK | IMETA | INULL) as u32;
}
}
}
pub fn findsep(s: &str, sep: Option<&str>) -> Option<usize> {
match sep {
Some(sep) if sep.len() == 1 => s.find(sep.chars().next().unwrap()),
Some(sep) => s.find(sep),
None => {
s.find(|c: char| c.is_ascii_whitespace())
}
}
}
pub fn findword<'a>(s: &'a str, sep: Option<&'a str>) -> Option<(&'a str, &'a str)> {
let s = match sep {
Some(_) => s,
None => s.trim_start(),
};
if s.is_empty() {
return None;
}
match sep {
Some(sep) => {
if let Some(pos) = s.find(sep) {
Some((&s[..pos], &s[pos + sep.len()..]))
} else {
Some((s, ""))
}
}
None => {
let end = s.find(|c: char| c.is_ascii_whitespace()).unwrap_or(s.len());
Some((&s[..end], &s[end..]))
}
}
}
pub fn getkeystring(s: &str) -> (String, usize) { let mut result = String::new();
let mut chars = s.chars().peekable();
let mut consumed = 0;
while let Some(c) = chars.next() {
consumed += c.len_utf8();
if c != '\\' {
result.push(c);
continue;
}
match chars.next() {
Some('n') => {
result.push('\n');
consumed += 1;
}
Some('t') => {
result.push('\t');
consumed += 1;
}
Some('r') => {
result.push('\r');
consumed += 1;
}
Some('e') | Some('E') => {
result.push('\x1b');
consumed += 1;
}
Some('a') => {
result.push('\x07');
consumed += 1;
}
Some('b') => {
result.push('\x08');
consumed += 1;
}
Some('f') => {
result.push('\x0c');
consumed += 1;
}
Some('v') => {
result.push('\x0b');
consumed += 1;
}
Some('\\') => {
result.push('\\');
consumed += 1;
}
Some('\'') => {
result.push('\'');
consumed += 1;
}
Some('"') => {
result.push('"');
consumed += 1;
}
Some('x') => {
consumed += 1;
let mut hex = String::new();
for _ in 0..2 {
if let Some(&c) = chars.peek() {
if c.is_ascii_hexdigit() {
hex.push(chars.next().unwrap());
consumed += 1;
} else {
break;
}
}
}
if let Ok(val) = u8::from_str_radix(&hex, 16) {
result.push(val as char);
}
}
Some('u') => {
consumed += 1;
let mut hex = String::new();
for _ in 0..4 {
if let Some(&c) = chars.peek() {
if c.is_ascii_hexdigit() {
hex.push(chars.next().unwrap());
consumed += 1;
} else {
break;
}
}
}
if let Ok(val) = u32::from_str_radix(&hex, 16) {
if let Some(c) = char::from_u32(val) {
result.push(c);
}
}
}
Some('U') => {
consumed += 1;
let mut hex = String::new();
for _ in 0..8 {
if let Some(&c) = chars.peek() {
if c.is_ascii_hexdigit() {
hex.push(chars.next().unwrap());
consumed += 1;
} else {
break;
}
}
}
if let Ok(val) = u32::from_str_radix(&hex, 16) {
if let Some(c) = char::from_u32(val) {
result.push(c);
}
}
}
Some(c @ '0'..='7') => {
consumed += 1;
let mut oct = String::new();
oct.push(c);
for _ in 0..2 {
if let Some(&c) = chars.peek() {
if ('0'..='7').contains(&c) {
oct.push(chars.next().unwrap());
consumed += 1;
} else {
break;
}
}
}
if let Ok(val) = u8::from_str_radix(&oct, 8) {
result.push(val as char);
}
}
Some('c') => {
consumed += 1;
if let Some(c) = chars.next() {
consumed += 1;
result.push((c as u8 & 0x1f) as char);
}
}
Some(c) => {
consumed += 1;
result.push('\\');
result.push(c);
}
None => {
result.push('\\');
}
}
}
(result, consumed)
}
pub const GETKEY_OCTAL_ESC: u32 = 1 << 0; pub const GETKEY_EMACS: u32 = 1 << 1; pub const GETKEY_BACKSLASH_C: u32 = 1 << 3;
pub const GETKEYS_PRINT: u32 = GETKEY_OCTAL_ESC | GETKEY_EMACS | GETKEY_BACKSLASH_C;
pub fn getkeystring_with(s: &str, how: u32) -> (String, usize) { let mut result = String::new();
let mut chars = s.chars().peekable();
let mut consumed = 0;
while let Some(c) = chars.next() {
consumed += c.len_utf8();
if c != '\\' {
result.push(c);
continue;
}
match chars.next() {
Some('n') => { result.push('\n'); consumed += 1; }
Some('t') => { result.push('\t'); consumed += 1; }
Some('r') => { result.push('\r'); consumed += 1; }
Some('e') | Some('E') => { result.push('\x1b'); consumed += 1; }
Some('a') => { result.push('\x07'); consumed += 1; }
Some('b') => { result.push('\x08'); consumed += 1; }
Some('f') => { result.push('\x0c'); consumed += 1; }
Some('v') => { result.push('\x0b'); consumed += 1; }
Some('\\') => { result.push('\\'); consumed += 1; }
Some('\'') => { result.push('\''); consumed += 1; }
Some('"') => { result.push('"'); consumed += 1; }
Some('x') => {
consumed += 1;
let mut hex = String::new();
for _ in 0..2 {
if let Some(&c) = chars.peek() {
if c.is_ascii_hexdigit() {
hex.push(chars.next().unwrap());
consumed += 1;
} else { break; }
}
}
if let Ok(val) = u8::from_str_radix(&hex, 16) {
result.push(val as char);
}
}
Some(d) if d.is_digit(8) && (how & GETKEY_OCTAL_ESC) != 0 => {
consumed += 1;
let mut oct = String::from(d);
for _ in 0..2 {
if let Some(&c) = chars.peek() {
if c.is_digit(8) {
oct.push(chars.next().unwrap());
consumed += 1;
} else { break; }
}
}
if let Ok(val) = u8::from_str_radix(&oct, 8) {
result.push(val as char);
}
}
Some(c) => {
consumed += 1;
if (how & GETKEY_EMACS) == 0 {
result.push('\\');
}
result.push(c);
}
None => { result.push('\\'); }
}
}
(result, consumed)
}
pub fn ucs4toutf8(codepoint: u32) -> Option<String> {
char::from_u32(codepoint).map(|c| c.to_string())
}
pub fn hasspecial(s: &str) -> bool {
s.chars().any(ispecial)
}
static ATTACHTTY_EP: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
#[cfg(unix)]
pub fn attachtty(pgrp: i32) {
if !(crate::ported::zsh_h::jobbing() && crate::ported::zsh_h::interact()) {
return; }
let shtty = crate::ported::init::SHTTY.load(Ordering::Relaxed); if shtty == -1 {
return;
}
let ep = ATTACHTTY_EP.load(Ordering::Relaxed);
let rc = unsafe { libc::tcsetpgrp(shtty, pgrp) }; if rc == -1 && ep == 0 { let mypgrp_val = *crate::ported::jobs::MYPGRP .get_or_init(|| std::sync::Mutex::new(0))
.lock().unwrap();
if pgrp != mypgrp_val
&& unsafe { libc::kill(-pgrp, 0) } == -1
{
attachtty(mypgrp_val); } else {
let errno_val = std::io::Error::last_os_error().raw_os_error()
.unwrap_or(0);
if errno_val != libc::ENOTTY { zwarn(&format!("can't set tty pgrp: {}", std::io::Error::from_raw_os_error(errno_val)));
let _ = std::io::stderr().flush(); }
crate::ported::options::opt_state_set("monitor", false); ATTACHTTY_EP.store(1, Ordering::Relaxed); }
} else if rc != -1 { *crate::ported::jobs::LAST_ATTACHED_PGRP .get_or_init(|| std::sync::Mutex::new(0))
.lock().unwrap() = pgrp;
}
}
#[cfg(unix)]
pub fn gettygrp() -> i32 { let shtty = crate::ported::init::SHTTY.load(Ordering::Relaxed);
if shtty == -1 { return -1; }
unsafe { libc::tcgetpgrp(shtty) } }
pub fn zreaddir(path: &str) -> Vec<String> {
match std::fs::read_dir(path) {
Ok(entries) => entries
.filter_map(|e| e.ok())
.filter_map(|e| e.file_name().into_string().ok())
.filter(|s| s != "." && s != "..")
.collect(),
Err(_) => Vec::new(),
}
}
pub fn zsetupterm() { static TERM_COUNT: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
TERM_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); }
pub fn zdeleteterm() { static TERM_COUNT: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
if TERM_COUNT.load(std::sync::atomic::Ordering::Relaxed) > 0 {
TERM_COUNT.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); }
}
pub fn putraw(c: char) -> i32 { print!("{}", c); 0 }
pub fn putshout(c: char) -> i32 { print!("{}", c); 0 }
pub fn nicechar_sel(c: char, quotable: bool) -> String {
if quotable && ispecial(c) {
format!("\\{}", c)
} else {
nicechar(c)
}
}
pub fn mb_charinit() {
}
pub fn wcs_nicechar_sel(c: char, quotable: bool) -> String {
nicechar_sel(c, quotable)
}
pub fn wcs_nicechar(c: char) -> String {
nicechar(c)
}
pub fn is_wcs_nicechar(c: char) -> bool { let cv = c as u32;
let printable = !c.is_control() && cv >= 0x20;
let print_eight = isset(crate::ported::zsh_h::PRINTEIGHTBIT); if !printable && (cv < 0x80 || !print_eight) {
if cv == 0x7f || c == '\n' || c == '\t' || cv < 0x20 { return true;
}
if cv >= 0x80 { return cv >= 0x100 || is_nicechar(c); }
}
false }
pub fn zwcwidth(wc: char) -> usize {
unicode_width::UnicodeWidthChar::width(wc).unwrap_or(1)
}
pub fn pathprog(prog: &str) -> Option<PathBuf> {
if prog.contains('/') {
let p = PathBuf::from(prog);
return if p.exists() { Some(p) } else { None };
}
if let Some(path_var) = crate::ported::params::getsparam("PATH") {
for dir in path_var.split(':') {
let full_path = PathBuf::from(dir).join(prog);
#[cfg(unix)]
{
if let Ok(meta) = std::fs::metadata(&full_path) {
if meta.is_file() && meta.permissions().mode() & 0o111 != 0 {
return Some(full_path);
}
}
}
#[cfg(not(unix))]
{
if full_path.is_file() {
return Some(full_path);
}
}
}
}
None
}
pub fn print_if_link(s: &str, all: bool) { if !s.starts_with('/') { return;
}
if all { let mut start = s.to_string();
loop { match xsymlinks(&start) {
Ok(target) if !target.is_empty() && target != start => {
print!(" -> "); print!("{}", if target.is_empty() { "/" } else { &target }); start = target; }
_ => break, }
}
} else { let s_at_entry = s.to_string(); if let Ok(resolved) = std::fs::canonicalize(s) { let r = resolved.to_string_lossy().into_owned();
if r != s_at_entry { print!(" -> "); print!("{}", if r.is_empty() { "/" } else { &r }); }
}
}
let _ = std::io::stdout().flush();
}
pub fn substnamedir(s: &str) -> String { match finddir(s) { None => quotestring(s, crate::ported::zsh_h::QT_BACKSLASH), Some(rendered) => rendered, }
}
pub fn finddir_scan(path: &str) -> Option<(String, String)> { let table = crate::ported::hashnameddir::nameddirtab().lock().ok()?;
let mut best: Option<(String, String, usize)> = None;
for (name, nd) in table.iter() {
if path.starts_with(nd.dir.as_str()) {
let len = nd.dir.len();
let rest = &path[len..];
if (rest.is_empty() || rest.starts_with('/'))
&& best.as_ref().map_or(true, |b| len > b.2)
{
best = Some((name.clone(), rest.to_string(), len));
}
}
}
best.map(|(n, r, _)| (n, r))
}
pub fn finddir(path: &str) -> Option<String> { let home = std::env::var("HOME").unwrap_or_default(); if !home.is_empty() && home.len() > 1 && path.starts_with(&home) { let rest = &path[home.len()..];
if rest.is_empty() || rest.starts_with('/') {
return Some(format!("~{}", rest));
}
}
if let Some((name, rest)) = finddir_scan(path) { return Some(format!("~{}{}", name, rest));
}
if let Some(reply) = subst_string_by_hook(
"zsh_directory_name", Some("d"), path)
{
if reply.len() >= 2 { if let Ok(len) = reply[1].parse::<usize>() {
if len <= path.len() {
let prefix = &path[..len];
let _ = prefix;
return Some(format!("~[{}]{}", reply[0], &path[len..]));
}
}
}
}
None }
pub fn adduserdir(name: &str, dir: &str, flags: i32, always: bool) {
if !crate::ported::zsh_h::interact() { return; } if let Ok(t) = crate::ported::hashnameddir::nameddirtab().lock() {
if (flags & ND_USERNAME) != 0 && t.contains_key(name) { return;
}
if !always
&& !isset(crate::ported::zsh_h::AUTONAMEDIRS)
&& !t.contains_key(name)
{ return;
}
}
if dir.is_empty() || !dir.starts_with('/') { let _ = crate::ported::hashnameddir::removenameddirnode(name); return;
}
let mut trimmed = dir.trim_end_matches('/').to_string(); if trimmed.is_empty() {
trimmed = dir.to_string(); }
let final_flags = if name == "PWD" || name == "OLDPWD" { flags | ND_NOABBREV
} else {
flags
};
let nd = nameddir {
node: hashnode { next: None, nam: name.to_string(), flags: final_flags },
dir: trimmed,
diff: 0,
};
crate::ported::hashnameddir::addnameddirnode(name, nd);
}
pub fn getnameddir(name: &str) -> Option<String> { if let Ok(t) = crate::ported::hashnameddir::nameddirtab().lock() {
if let Some(nd) = t.get(name) { return Some(nd.dir.clone());
}
}
if let Some(s) = crate::ported::params::getsparam(name) {
if s.starts_with('/') {
adduserdir(name, &s, 0, true); return Some(s);
}
}
#[cfg(unix)]
{
let cn = std::ffi::CString::new(name).ok()?;
let pw = unsafe { libc::getpwnam(cn.as_ptr()) };
if !pw.is_null() {
let dir = unsafe {
std::ffi::CStr::from_ptr((*pw).pw_dir).to_string_lossy().into_owned()
};
return Some(dir);
}
}
None
}
pub fn dircmp(s: &str, t: &str) -> bool {
let s = s.trim_end_matches('/');
let t = t.trim_end_matches('/');
s == t
}
pub fn checkmailpath(paths: &[String]) -> Vec<String> {
let mut messages = Vec::new();
for path in paths {
let (file, msg) = if let Some(pos) = path.find('?') {
(&path[..pos], Some(&path[pos + 1..]))
} else {
(path.as_str(), None)
};
if let Ok(meta) = std::fs::metadata(file) {
if let Ok(modified) = meta.modified() {
if let Ok(elapsed) = modified.elapsed() {
if elapsed.as_secs() < 60 {
let default_msg = format!("You have new mail in {}", file);
messages.push(msg.unwrap_or(&default_msg).to_string());
}
}
}
}
}
messages
}
#[cfg(unix)]
pub fn gettyinfo() -> Option<libc::termios> { let shtty = crate::ported::init::SHTTY.load(Ordering::Relaxed);
if shtty == -1 { return None;
}
fdgettyinfo(shtty).ok() }
#[cfg(unix)]
pub fn settyinfo(ti: &libc::termios) -> bool { let shtty = crate::ported::init::SHTTY.load(Ordering::Relaxed);
if shtty == -1 { return false;
}
fdsettyinfo(shtty, ti).is_ok() }
pub fn check_fd_table(fd: i32) -> bool {
#[cfg(unix)]
{
unsafe { libc::fcntl(fd, libc::F_GETFD) != -1 }
}
#[cfg(not(unix))]
{
let _ = fd;
false
}
}
pub fn movefd(fd: i32) -> i32 {
#[cfg(unix)]
{
if fd < 10 {
let new_fd = unsafe { libc::fcntl(fd, libc::F_DUPFD, 10) };
if new_fd >= 0 {
unsafe { libc::close(fd) };
unsafe { libc::fcntl(new_fd, libc::F_SETFD, libc::FD_CLOEXEC) };
return new_fd;
}
}
fd
}
#[cfg(not(unix))]
{
fd
}
}
pub fn addmodulefd(fd: i32) {
#[cfg(unix)]
{
unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) };
}
#[cfg(not(unix))]
{
let _ = fd;
}
fdtable_set(fd, crate::ported::zsh_h::FDT_MODULE);
}
static FDTABLE: std::sync::OnceLock<std::sync::Mutex<Vec<i32>>> =
std::sync::OnceLock::new();
fn fdtable_lock() -> &'static std::sync::Mutex<Vec<i32>> {
FDTABLE.get_or_init(|| std::sync::Mutex::new(Vec::new()))
}
pub fn fdtable_get(fd: i32) -> i32 { if fd < 0 { return crate::ported::zsh_h::FDT_UNUSED; }
let g = fdtable_lock().lock().unwrap();
g.get(fd as usize).copied().unwrap_or(crate::ported::zsh_h::FDT_UNUSED)
}
pub fn fdtable_set(fd: i32, kind: i32) { if fd < 0 { return; }
let mut g = fdtable_lock().lock().unwrap();
if (fd as usize) >= g.len() {
g.resize((fd as usize) + 1, crate::ported::zsh_h::FDT_UNUSED);
}
g[fd as usize] = kind;
}
pub fn addlockfd(fd: i32, cloexec: bool) {
#[cfg(unix)]
{
if cloexec {
unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) };
}
}
#[cfg(not(unix))]
{
let _ = (fd, cloexec);
}
}
pub fn zcloselockfd(fd: i32) -> i32 { zclose(fd); 0 }
pub fn zstrtol_underscore(s: &str, base: i32, underscore: bool) -> (i64, &str) { let bytes = s.as_bytes();
let mut i = 0usize;
while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
i += 1;
}
let neg = if i < bytes.len() && (bytes[i] == b'-') { i += 1;
true
} else if i < bytes.len() && bytes[i] == b'+' { i += 1;
false
} else {
false
};
let mut base = base;
if base == 0 { if i >= bytes.len() || bytes[i] != b'0' { base = 10; } else {
i += 1; if i < bytes.len() && (bytes[i] == b'x' || bytes[i] == b'X') { base = 16;
i += 1;
} else if i < bytes.len() && (bytes[i] == b'b' || bytes[i] == b'B') { base = 2;
i += 1;
} else { base = 8; }
}
}
let inp_idx = i; if base < 2 || base > 36 { zerr(&format!(
"invalid base (must be 2 to 36 inclusive): {}", base
)); return (0, s); }
let mut calc: u64 = 0; let mut trunc_idx: Option<usize> = None; if base <= 10 { let max_d = b'0' + base as u8;
while i < bytes.len() {
let c = bytes[i];
if c >= b'0' && c < max_d {
if trunc_idx.is_none() {
let newcalc = calc.wrapping_mul(base as u64).wrapping_add((c - b'0') as u64);
if newcalc < calc { trunc_idx = Some(i); } else {
calc = newcalc;
}
}
i += 1;
} else if underscore && c == b'_' { i += 1;
} else {
break;
}
}
} else { while i < bytes.len() {
let c = bytes[i];
let digit = if c.is_ascii_digit() { Some((c - b'0') as u32)
} else if c >= b'a' && c < b'a' + base as u8 - 10 { Some(((c & 0x1f) + 9) as u32) } else if c >= b'A' && c < b'A' + base as u8 - 10 { Some(((c & 0x1f) + 9) as u32)
} else if underscore && c == b'_' { i += 1;
continue;
} else {
None
};
match digit {
Some(d) => {
if trunc_idx.is_none() {
let newcalc = calc.wrapping_mul(base as u64).wrapping_add(d as u64);
if newcalc < calc { trunc_idx = Some(i); } else {
calc = newcalc;
}
}
i += 1;
}
None => break,
}
}
}
if trunc_idx.is_none() && (calc as i64) < 0 { let top_bit_only = calc & !(1u64 << 63); if !neg || top_bit_only != 0 { trunc_idx = Some(i.saturating_sub(1)); calc /= base as u64; }
}
if let Some(t) = trunc_idx { let digits = t.saturating_sub(inp_idx);
let inp_str = &s[inp_idx..];
zwarn(&format!(
"number truncated after {} digits: {}", digits, inp_str
)); }
let result = if neg { -(calc as i64)
} else {
calc as i64
};
(result, &s[i..])
}
pub fn timespec_diff_us(t1: &std::time::Instant, t2: &std::time::Instant) -> i64 {
if *t2 > *t1 {
t2.duration_since(*t1).as_micros() as i64
} else {
-(t1.duration_since(*t2).as_micros() as i64)
}
}
pub fn zmonotime() -> i64 { #[cfg(unix)]
{
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe {
libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts); }
ts.tv_sec as i64 }
#[cfg(not(unix))]
{
0
}
}
pub fn zsleep_random(max_us: i64, end_time: i64) -> i32 { let now = zmonotime(); let r16 = unsafe { libc::rand() } & 0xFFFF; let mut r: i64 = (max_us >> 16) * (r16 as i64); while r != 0 && now + (r / 1_000_000) > end_time { r >>= 1; }
if r != 0 { zsleep(r) } else {
0 }
}
pub fn noquery(purge: bool) -> i32 { let mut val: libc::c_int = 0; #[cfg(unix)]
{
let shtty = crate::ported::init::SHTTY.load(Ordering::Relaxed); if shtty == -1 {
return 0;
}
unsafe { libc::ioctl(shtty, libc::FIONREAD, &mut val as *mut libc::c_int);
}
if purge { let mut c: u8 = 0;
for _ in 0..val { let _ = unsafe { libc::read(shtty, &mut c as *mut u8 as *mut libc::c_void, 1)
};
}
}
}
val }
pub fn spscan(name: &str, candidates: &[String], threshold: usize) -> Option<String> {
let mut best = None;
let mut best_dist = threshold + 1;
for candidate in candidates {
let dist = spdist(name, candidate, threshold);
if dist < best_dist {
best_dist = dist;
best = Some(candidate.clone());
}
}
best
}
pub fn getshfunc(nam: &str) -> Option<String> {
let tab = crate::ported::hashtable::shfunctab_lock()
.read()
.expect("shfunctab poisoned");
tab.get(nam).and_then(|f| f.body.clone())
}
pub fn makecommaspecial(yesno: bool) { let mut flags = TYPTAB_FLAGS.lock().unwrap();
let mut tab = TYPTAB.lock().unwrap();
if yesno { *flags |= ZTF_SP_COMMA; tab[b',' as usize] |= ISPECIAL as u32; } else {
*flags &= !ZTF_SP_COMMA; tab[b',' as usize] &= !(ISPECIAL as u32); }
}
pub fn zarrdup(s: &[String]) -> Vec<String> {
s.to_vec()
}
pub fn spname(name: &str, dir: &str) -> Option<String> {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return None,
};
let mut best = None;
let mut best_dist = 4;
for entry in entries.flatten() {
if let Some(entry_name) = entry.file_name().to_str() {
let dist = spdist(name, entry_name, best_dist);
if dist < best_dist {
best_dist = dist;
best = Some(entry_name.to_string());
}
}
}
best
}
pub fn mindist(dir: &str, name: &str) -> Option<(String, usize)> {
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return None,
};
let mut best = None;
let mut best_dist = 4;
for entry in entries.flatten() {
if let Some(entry_name) = entry.file_name().to_str() {
let dist = spdist(name, entry_name, best_dist);
if dist < best_dist {
best_dist = dist;
best = Some(entry_name.to_string());
}
}
}
best.map(|name| (name, best_dist))
}
#[allow(non_upper_case_globals)]
pub const Meta: u8 = 0x83;
#[inline]
pub fn imeta_byte(b: u8) -> bool {
b >= Meta
}
pub fn unmetafy(s: &mut Vec<u8>) -> usize { let mut p: usize = 0;
while p < s.len() && s[p] != Meta {
p += 1;
}
let mut t: usize = p;
while p < s.len() {
let b = s[p];
s[t] = b;
p += 1;
if b == Meta && p < s.len() {
s[t] = s[p] ^ 32;
p += 1;
}
t += 1;
}
s.truncate(t);
t
}
pub fn metalen(s: &str, len: usize) -> usize {
let bytes = s.as_bytes();
let mut count = 0;
let mut i = 0;
while i < len.min(bytes.len()) {
if bytes[i] == 0x83 {
i += 2;
} else {
i += 1;
}
count += 1;
}
count
}
pub fn nicedup(s: &str) -> String {
sb_niceformat(s)
}
pub fn niceztrlen(s: &str) -> usize {
sb_niceformat(s).len()
}
pub fn dquotedztrdup(s: &str) -> String {
let mut out = String::with_capacity(s.len() * 4 + 2);
out.push('"');
let mut pending = false;
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
let c = if bytes[i] == Meta && i + 1 < bytes.len() {
i += 2;
(bytes[i - 1] ^ 32) as char
} else {
i += 1;
bytes[i - 1] as char
};
match c {
'\\' => {
if pending {
out.push('\\');
}
out.push('\\');
pending = true;
}
'"' | '$' | '`' => {
if pending {
out.push('\\');
}
out.push('\\');
out.push(c);
pending = false;
}
other => {
out.push(other);
pending = false;
}
}
}
if pending {
out.push('\\');
}
out.push('"');
metafy(&out)
}
pub fn restoredir(d: &mut crate::ported::zsh_h::dirsav) -> i32 {
if let Some(name) = d.dirname.as_ref() {
if name.starts_with('/') {
return match std::env::set_current_dir(name) {
Ok(_) => 0,
Err(_) => -1,
};
}
}
let mut err: i32 = 0;
#[cfg(unix)]
if d.dirfd >= 0 {
let rc = unsafe { libc::fchdir(d.dirfd) };
if rc == 0 {
if d.dirname.is_none() {
return 0;
}
let name = d.dirname.as_ref().unwrap();
if std::env::set_current_dir(name).is_err() {
unsafe { libc::close(d.dirfd) };
d.dirfd = -1;
err = -2;
}
} else {
unsafe { libc::close(d.dirfd) };
d.dirfd = -1;
err = -1;
}
} else if d.level > 0 {
let _ = upchdir(d.level as usize);
} else if d.level < 0 {
err = -1;
}
if (d.dev != 0 || d.ino != 0) && err == 0 {
if let Ok(meta) = std::fs::metadata(".") {
if meta.ino() != d.ino || meta.dev() != d.dev {
err = -1;
}
} else {
err = -1;
}
}
err
}
pub fn convfloat(dval: f64, digits: i32, flags: u32) -> String {
crate::params::convfloat(dval, digits, flags)
}
pub fn convfloat_underscore(dval: f64, underscore: i32) -> String {
crate::params::convfloat_underscore(dval, underscore)
}
pub fn ucs4tomb(wval: u32, buf: &mut [u8]) -> i32 {
extern "C" {
fn wctomb(s: *mut libc::c_char, wc: libc::wchar_t) -> libc::c_int;
}
let mut local = [0i8; 16];
let count = unsafe {
wctomb(local.as_mut_ptr(), wval as libc::wchar_t)
};
if count < 0 {
zerr("character not in range");
return -1;
}
let n = count as usize;
if n > buf.len() {
zerr("character not in range");
return -1;
}
for i in 0..n {
buf[i] = local[i] as u8;
}
count
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sepsplit() {
assert_eq!(sepsplit("a:b:c", Some(":"), false), vec!["a", "b", "c"]);
assert_eq!(sepsplit("a::b", Some(":"), false), vec!["a", "b"]);
assert_eq!(sepsplit("a::b", Some(":"), true), vec!["a", "", "b"]);
}
#[test]
fn test_unmetafy_no_meta_byte_passes_through() {
let mut buf = b"hello".to_vec();
let n = unmetafy(&mut buf);
assert_eq!(n, 5);
assert_eq!(&buf, b"hello");
}
#[test]
fn test_unmetafy_collapses_meta_escapes() {
let mut buf = vec![0x83, b'a' ^ 32];
let n = unmetafy(&mut buf);
assert_eq!(n, 1);
assert_eq!(buf, vec![b'a']);
}
#[test]
fn test_unmetafy_mixed_prefix_then_meta() {
let mut buf = vec![b'X', b'Y', 0x83, 0xFF ^ 32, b'Z'];
let n = unmetafy(&mut buf);
assert_eq!(n, 4);
assert_eq!(buf, vec![b'X', b'Y', 0xFF, b'Z']);
}
#[test]
fn test_unmetafy_returns_self_value() {
let mut buf = vec![
b'A',
0x83, b'B' ^ 32, 0x83, b'C' ^ 32, b'D',
];
let n = unmetafy(&mut buf);
assert_eq!(n, 4);
assert_eq!(buf, b"ABCD".to_vec());
}
#[test]
fn test_imeta_byte_threshold() {
assert!(!imeta_byte(0x82));
assert!(imeta_byte(Meta));
assert!(imeta_byte(0xFF));
}
#[test]
fn test_meta_constant_value() {
assert_eq!(Meta, 0x83);
}
#[test]
#[cfg(unix)]
fn test_mode_to_octal_canonical_bits() {
use libc::{S_IRUSR, S_IWUSR, S_IXUSR};
let mode = (S_IRUSR | S_IWUSR | S_IXUSR) as u32;
assert_eq!(mode_to_octal(mode), 0o700);
let all = (S_IRUSR | S_IWUSR | S_IXUSR) as u32 * (1 + 8 + 64);
let m = (libc::S_IRUSR
| libc::S_IWUSR
| libc::S_IXUSR
| libc::S_IRGRP
| libc::S_IWGRP
| libc::S_IXGRP
| libc::S_IROTH
| libc::S_IWOTH
| libc::S_IXOTH) as u32;
assert_eq!(mode_to_octal(m), 0o777);
let _ = all;
}
#[test]
#[cfg(unix)]
fn test_mode_to_octal_setuid_setgid_sticky() {
use libc::{S_ISGID, S_ISUID, S_ISVTX};
assert_eq!(mode_to_octal(S_ISUID as u32), 0o4000);
assert_eq!(mode_to_octal(S_ISGID as u32), 0o2000);
assert_eq!(mode_to_octal(S_ISVTX as u32), 0o1000);
let all = (S_ISUID | S_ISGID | S_ISVTX) as u32;
assert_eq!(mode_to_octal(all), 0o7000);
}
#[test]
#[cfg(unix)]
fn test_mailstat_plain_file_returns_native_stat() {
use std::os::unix::fs::MetadataExt;
let mut st: libc::stat = unsafe { std::mem::zeroed() };
let rc = mailstat("/etc/hosts", &mut st);
if rc == 0 {
assert_eq!(st.st_nlink as u64, std::fs::metadata("/etc/hosts").unwrap().nlink());
}
}
#[test]
fn test_mailstat_nonexistent_returns_neg1() {
let mut st: libc::stat = unsafe { std::mem::zeroed() };
assert_eq!(mailstat("/nonexistent/path/does/not/exist", &mut st), -1);
}
#[test]
fn test_mailstat_directory_without_maildir_subdirs() {
let mut st: libc::stat = unsafe { std::mem::zeroed() };
let rc = mailstat("/tmp", &mut st);
assert_eq!(rc, 0);
assert_eq!(st.st_nlink, 1);
assert_eq!(st.st_size, 0);
assert_eq!(st.st_blocks, 0);
#[cfg(unix)]
{
assert_eq!(st.st_mode & libc::S_IFDIR, 0);
assert_ne!(st.st_mode & libc::S_IFREG, 0);
}
}
#[test]
fn test_dupstrpfx_byte_counted() {
assert_eq!(dupstrpfx("hello", 3), "hel");
assert_eq!(dupstrpfx("hi", 10), "hi");
assert_eq!(dupstrpfx("anything", 0), "");
}
#[test]
fn test_metafy_passes_through_ascii() {
assert_eq!(metafy("hello"), "hello");
assert_eq!(metafy(""), "");
}
#[test]
fn test_metafy_imeta_predicate_matches_c_macro() {
for b in 0u8..0x83 {
assert!(!imeta_byte(b), "byte {:#x} should NOT be imeta", b);
}
for b in 0x83u8..=0xff {
assert!(imeta_byte(b), "byte {:#x} should be imeta", b);
}
}
#[test]
fn test_ztrcmp_meta_aware() {
assert_eq!(ztrcmp("foo", "foo"), std::cmp::Ordering::Equal);
assert_eq!(ztrcmp("foo", "foz"), std::cmp::Ordering::Less);
assert_eq!(ztrcmp("foo", "foobar"), std::cmp::Ordering::Less);
let s_meta = unsafe { std::str::from_utf8_unchecked(&[0x83, b'a' ^ 32]) };
let s_plain = "a";
assert_eq!(ztrcmp(s_meta, s_plain), std::cmp::Ordering::Equal);
}
#[test]
fn test_skipwsep_skips_runs() {
let (rest, n) = skipwsep(" x");
assert_eq!(rest, "x");
assert_eq!(n, 3);
let (rest, n) = skipwsep("foo");
assert_eq!(rest, "foo");
assert_eq!(n, 0);
let (rest, n) = skipwsep(" \t\nbar");
assert_eq!(rest, "bar");
assert_eq!(n, 3);
}
#[test]
fn test_imeta_macro_threshold() {
assert!(!imeta(0x82 as char));
assert!(imeta(Meta as char));
assert!(imeta(0xff as char));
assert!(!imeta(' '));
assert!(!imeta('A'));
}
#[test]
fn test_unmeta_routes_through_unmetafy() {
assert_eq!(unmeta("plain"), "plain");
}
#[test]
fn test_iwsep_includes_newline() {
assert!(iwsep('\n'));
assert!(iwsep('\t'));
assert!(iwsep(' '));
assert!(!iwsep('a'));
}
#[test]
fn test_mailstat_aggregates_maildir() {
use std::fs;
use std::io::Write;
let tmp = std::env::temp_dir().join(format!(
"zshrs_mailstat_test_{}",
std::process::id()
));
let _ = fs::remove_dir_all(&tmp);
fs::create_dir_all(tmp.join("cur")).unwrap();
fs::create_dir_all(tmp.join("new")).unwrap();
fs::create_dir_all(tmp.join("tmp")).unwrap();
let mut f = fs::File::create(tmp.join("new").join("msg1")).unwrap();
f.write_all(b"hello").unwrap();
let mut f = fs::File::create(tmp.join("new").join("msg2")).unwrap();
f.write_all(b"world!").unwrap();
let mut f = fs::File::create(tmp.join("cur").join("msg3")).unwrap();
f.write_all(b"third").unwrap();
let mut st: libc::stat = unsafe { std::mem::zeroed() };
let rc = mailstat(tmp.to_str().unwrap(), &mut st);
assert_eq!(rc, 0, "maildir should stat");
assert_eq!(st.st_blocks, 3, "3 messages total across new/ + cur/");
assert_eq!(st.st_size, 5 + 6 + 5, "5+6+5 bytes total");
let _ = fs::remove_dir_all(&tmp);
}
#[test]
fn test_spacesplit() {
assert_eq!(spacesplit("a b c", false), vec!["a", "b", "c"]);
assert_eq!(spacesplit("a b", false), vec!["a", "b"]);
}
#[test]
fn test_sepjoin() {
assert_eq!(
sepjoin(&["a".into(), "b".into(), "c".into()], Some(":")),
"a:b:c"
);
assert_eq!(sepjoin(&["a".into(), "b".into()], None), "a b");
}
#[test]
fn test_isident() {
assert!(isident("foo"));
assert!(isident("_bar"));
assert!(isident("baz123"));
assert!(!isident("123abc"));
assert!(!isident("foo-bar"));
}
#[test]
fn test_nicechar() {
assert_eq!(nicechar('\n'), "\\n");
assert_eq!(nicechar('\t'), "\\t");
assert_eq!(nicechar('a'), "a");
}
#[test]
fn test_quotedzputs_single_quote_wrap() {
assert_eq!(quotedzputs("simple"), "simple");
assert_eq!(quotedzputs("has space"), "'has space'");
assert_eq!(quotedzputs("it's"), "'it'\\''s'");
}
#[test]
fn test_quotestring_backslash() {
assert_eq!(quotestring("hello", crate::ported::zsh_h::QT_BACKSLASH), "hello");
assert_eq!(
quotestring("has space", crate::ported::zsh_h::QT_BACKSLASH),
"has\\ space"
);
assert_eq!(quotestring("$var", crate::ported::zsh_h::QT_BACKSLASH), "\\$var");
}
#[test]
fn test_quotestring_single() {
assert_eq!(quotestring("hello", crate::ported::zsh_h::QT_SINGLE), "'hello'");
assert_eq!(quotestring("it's", crate::ported::zsh_h::QT_SINGLE), "'it'\\''s'");
}
#[test]
fn test_quotestring_double() {
assert_eq!(quotestring("hello", crate::ported::zsh_h::QT_DOUBLE), "\"hello\"");
assert_eq!(
quotestring("say \"hi\"", crate::ported::zsh_h::QT_DOUBLE),
"\"say \\\"hi\\\"\""
);
}
#[test]
fn test_quotestring_dollars() {
assert_eq!(quotestring("hello", crate::ported::zsh_h::QT_DOLLARS), "$'hello'");
assert_eq!(
quotestring("line\nbreak", crate::ported::zsh_h::QT_DOLLARS),
"$'line\\nbreak'"
);
assert_eq!(
quotestring("tab\there", crate::ported::zsh_h::QT_DOLLARS),
"$'tab\\there'"
);
}
#[test]
fn test_quotestring_pattern() {
assert_eq!(quotestring("*.txt", crate::ported::zsh_h::QT_BACKSLASH_PATTERN), "\\*.txt");
assert_eq!(
quotestring("file[1]", crate::ported::zsh_h::QT_BACKSLASH_PATTERN),
"file\\[1\\]"
);
}
#[test]
fn test_quotetype_from_q_count() {
assert_eq!(qflag_quotetype(1), crate::ported::zsh_h::QT_BACKSLASH);
assert_eq!(qflag_quotetype(2), crate::ported::zsh_h::QT_SINGLE);
assert_eq!(qflag_quotetype(3), crate::ported::zsh_h::QT_DOUBLE);
assert_eq!(qflag_quotetype(4), crate::ported::zsh_h::QT_DOLLARS);
}
#[test]
fn test_tulower_tuupper() {
assert_eq!(tulower('A'), 'a');
assert_eq!(tuupper('a'), 'A');
assert_eq!(tulower('1'), '1');
}
#[test]
fn test_wordcount_ifs_default() {
assert_eq!(wordcount("a b c", None, 0), 3);
assert_eq!(wordcount(" a b ", None, 0), 2);
assert_eq!(wordcount("", None, 0), 0);
assert_eq!(wordcount("foo", None, 0), 1);
}
#[test]
fn test_wordcount_with_explicit_sep() {
assert_eq!(wordcount("a:b:c", Some(":"), 0), 3);
assert_eq!(wordcount("a::b", Some(":"), 1), 3);
assert_eq!(wordcount("a::b", Some(":"), 0), 2);
}
#[test]
fn test_ucs4tomb_ascii() {
let mut buf = [0u8; 8];
let n = ucs4tomb('A' as u32, &mut buf);
assert!(n == 1);
assert_eq!(buf[0], b'A');
}
#[test]
fn test_is_mb_niceformat_plain_ascii() {
assert!(!is_mb_niceformat("hello world"));
}
#[test]
fn test_is_mb_niceformat_with_control_char() {
assert!(is_mb_niceformat("a\tb"));
assert!(is_mb_niceformat("a\x07b"));
}
}
pub fn zz_plural_z_alpha() {}
pub fn is_nicechar(c: char) -> bool {
c.is_ascii_control() || !c.is_ascii()
}
pub fn freestr(_s: String) {}
pub fn gettempfile(prefix: Option<&str>) -> Option<(i32, String)> { #[cfg(unix)]
{
crate::ported::signals::queue_signals(); let old_umask = unsafe { libc::umask(0o177) }; let mut failures = 0; let mut result: Option<(i32, String)> = None;
loop {
let fn_ = match gettempname(prefix, false) { Some(n) => n,
None => break,
};
let cn = match std::ffi::CString::new(fn_.clone()) {
Ok(c) => c,
Err(_) => break,
};
let fd = unsafe {
libc::open(
cn.as_ptr(),
libc::O_RDWR | libc::O_CREAT | libc::O_EXCL, 0o600 as libc::c_int,
)
};
if fd >= 0 {
result = Some((fd, fn_));
break;
}
let err = std::io::Error::last_os_error()
.raw_os_error()
.unwrap_or(0);
if err != libc::EEXIST { break;
}
failures += 1;
if failures >= 16 { break;
}
}
unsafe { libc::umask(old_umask); } crate::ported::signals::unqueue_signals(); result
}
#[cfg(not(unix))]
{
let _ = prefix;
None
}
}
pub fn strucpy(s: &str, t: bool) -> String {
if t {
s.to_uppercase()
} else {
s.to_string()
}
}
pub fn struncpy(s: &str, t: usize, n: bool) -> String {
let s: String = s.chars().take(t).collect();
if n {
s.to_uppercase()
} else {
s
}
}
pub fn arrlen_ge<T>(arr: &[T], n: usize) -> bool { arr.len() >= n
}
pub fn arrlen_gt<T>(arr: &[T], n: usize) -> bool { arr.len() > n
}
pub fn arrlen_lt<T>(arr: &[T], n: usize) -> bool { arr.len() < n
}
pub fn setblock_stdin() -> i32 {
setblock_fd(0, false);
0
}
pub fn ztrftimebuf(bufsizeptr: &mut i32, decr: i32) -> i32 {
if *bufsizeptr <= decr {
return 1;
}
*bufsizeptr -= decr;
0
}
pub fn subst_string_by_func(func_name: &str, arg1: Option<&str>, orig: &str)
-> Option<Vec<String>> {
let osc = crate::ported::builtin::SFCONTEXT.load(Ordering::Relaxed); let osm = crate::ported::builtin::STOPMSG.load(Ordering::Relaxed);
let old_incompfunc = INCOMPFUNC.load(Ordering::Relaxed);
let mut args: Vec<String> = Vec::with_capacity(3); args.push(func_name.to_string()); if let Some(a) = arg1 { args.push(a.to_string()); }
args.push(orig.to_string()); crate::ported::builtin::SFCONTEXT .store(crate::ported::zsh_h::SFC_SUBST, Ordering::Relaxed);
INCOMPFUNC.store(0, Ordering::Relaxed);
let rc = callhookfunc(func_name, Some(&args), false); let ret: Option<Vec<String>> = if rc != 0 {
None } else {
std::env::var("reply").ok().map(|s| s.split('\x00').map(|p| p.to_string()).collect::<Vec<_>>()
)
};
crate::ported::builtin::SFCONTEXT.store(osc, Ordering::Relaxed); crate::ported::builtin::STOPMSG.store(osm, Ordering::Relaxed); INCOMPFUNC.store(old_incompfunc, Ordering::Relaxed); ret }
pub fn makebangspecial(yesno: bool) { let bc = crate::ported::hist::bangchar.load(Ordering::SeqCst) as usize;
if bc == 0 || bc >= 256 {
return;
}
let flags = *TYPTAB_FLAGS.lock().unwrap();
let mut tab = TYPTAB.lock().unwrap();
if !yesno { tab[bc] &= !(ISPECIAL as u32); } else if (flags & ZTF_BANGCHAR) != 0 { tab[bc] |= ISPECIAL as u32; }
}
pub fn wcsiblank(wc: char) -> bool {
wc.is_whitespace() && wc != '\n'
}
pub fn wcsitype(c: char, itype: u32) -> bool { if !isset(crate::ported::zsh_h::MULTIBYTE) { if (c as u32) < 256 {
let tab = TYPTAB.lock().unwrap();
return (tab[c as usize] & itype) != 0;
}
return false;
}
if (c as u32) < 128 { let tab = TYPTAB.lock().unwrap();
return (tab[c as usize] & itype) != 0;
}
let cls = itype as u16;
if cls == IIDENT { if isset(crate::ported::zsh_h::POSIXIDENTIFIERS) {
return false; }
return c.is_alphanumeric(); }
if cls == IWORD { if c.is_alphanumeric() { return true; } if unicode_width::UnicodeWidthChar::width(c).unwrap_or(1) == 0 { return true;
}
if let Ok(w) = std::env::var("WORDCHARS") { return w.chars().any(|x| x == c);
}
return false;
}
if cls == ISEP { if let Ok(ifs) = std::env::var("IFS") { return ifs.chars().any(|x| x == c);
}
return false;
}
let _ = IALNUM;
c.is_alphanumeric() }
pub fn wcs_zarrdup(s: &[String]) -> Vec<String> {
s.to_vec()
}
#[cfg(unix)]
pub fn setcbreak() -> bool {
if let Some(mut ti) = gettyinfo() {
ti.c_lflag &= !(libc::ICANON | libc::ECHO);
ti.c_cc[libc::VMIN] = 1;
ti.c_cc[libc::VTIME] = 0;
settyinfo(&ti)
} else {
false
}
}
#[cfg(not(unix))]
pub fn setcbreak() -> bool {
false
}
pub fn ztrdup_metafy(s: &str) -> String {
metafy(s)
}
pub fn unmeta_one(s: &str) -> (char, usize) {
let bytes = s.as_bytes();
if bytes.is_empty() {
return ('\0', 0);
}
if bytes[0] == 0x83 && bytes.len() > 1 {
((bytes[1] ^ 32) as char, 2)
} else {
(bytes[0] as char, 1)
}
}
pub fn ztrlenend(s: &str, eptr: usize) -> usize {
let bytes = s.as_bytes();
let cap = eptr.min(bytes.len());
let mut l = 0;
let mut i = 0;
while i < cap {
if bytes[i] == Meta {
i += 2;
} else {
i += 1;
}
l += 1;
}
l
}
pub fn mb_metacharlenconv_r(s: &str, pos: usize) -> (usize, Option<char>) {
if let Some(c) = s[pos..].chars().next() {
(c.len_utf8(), Some(c))
} else {
(0, None)
}
}
pub fn mb_metastrlenend(ptr: &str, width: bool, eptr: usize) -> usize {
if width {
ptr[..eptr.min(ptr.len())]
.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(1))
.sum()
} else {
ptr[..eptr.min(ptr.len())].chars().count()
}
}
pub fn mb_charlenconv_r(s: &str, pos: usize) -> (usize, Option<char>) {
mb_metacharlenconv_r(s, pos)
}
pub fn mb_charlenconv(s: &str, pos: usize) -> usize {
s[pos..].chars().next().map(|c| c.len_utf8()).unwrap_or(0)
}
pub fn sb_niceformat(s: &str) -> String {
let mut result = String::new();
for c in s.chars() {
if c.is_ascii_control() {
result.push_str(&nicechar(c));
} else {
result.push(c);
}
}
result
}
pub fn is_sb_niceformat(s: &str) -> bool {
s.chars().any(|c| c.is_ascii_control())
}
pub fn addunprintable(c: char) -> String {
if c.is_ascii_control() {
if (c as u8) < 32 {
format!("^{}", (c as u8 + 64) as char)
} else {
"^?".to_string()
}
} else if !c.is_ascii() {
format!("\\u{:04x}", c as u32)
} else {
c.to_string()
}
}
pub fn dquotedzputs(s: &str) -> String {
let mut result = String::with_capacity(s.len() + 2);
result.push('"');
for c in s.chars() {
match c {
'$' | '`' | '"' | '\\' => {
result.push('\\');
result.push(c);
}
'\n' => result.push_str("\\n"),
_ => result.push(c),
}
}
result.push('"');
result
}
pub fn init_dirsav() -> crate::ported::zsh_h::dirsav { crate::ported::zsh_h::dirsav {
dirfd: -1, level: 0, dirname: std::env::current_dir()
.ok()
.map(|p| p.to_string_lossy().to_string()),
dev: 0, ino: 0, }
}
pub fn dputs(msg: &str) {
#[cfg(debug_assertions)]
{
eprintln!("BUG: {}", msg);
}
#[cfg(not(debug_assertions))]
{
let _ = msg;
}
}
pub fn chuck(s: &mut String, pos: usize) { if pos < s.len() {
s.remove(pos);
}
}
pub fn arrlen_le<T>(arr: &[T], n: usize) -> bool { arr.len() <= n
}
pub fn skipparens(s: &str, open: char, close: char) -> usize { let mut depth = 0;
for (i, c) in s.char_indices() {
if c == open {
depth += 1;
} else if c == close {
depth -= 1;
if depth == 0 {
return i + c.len_utf8();
}
}
}
s.len()
}
pub fn subst_string_by_hook(name: &str, arg1: Option<&str>, orig: &str)
-> Option<Vec<String>> {
let mut ret: Option<Vec<String>> = None;
if getshfunc(name).is_some() { ret = subst_string_by_func(name, arg1, orig); }
if ret.is_none() { let arrnam = format!("{}_hook", name); if let Ok(text) = std::env::var(&arrnam) { for f in text.split('\x00') { if f.is_empty() { continue; }
if getshfunc(f).is_some() { ret = subst_string_by_func(f, arg1, orig); if ret.is_some() { break; }
}
}
}
}
ret }
pub fn hmkarray(s: &str) -> Vec<String> {
if s.is_empty() {
Vec::new()
} else {
vec![s.to_string()]
}
}
pub fn nicedupstring(s: &str) -> String {
sb_niceformat(s)
}
pub fn mailstat(path: &str, st: &mut libc::stat) -> i32 { let c_path = match CString::new(path) {
Ok(c) => c,
Err(_) => return -1,
};
let i = unsafe { libc::stat(c_path.as_ptr(), st as *mut _) }; if i != 0 || (st.st_mode & libc::S_IFMT) != libc::S_IFDIR { return i; }
st.st_nlink = 1; st.st_mode &= !libc::S_IFDIR; st.st_mode |= libc::S_IFREG; st.st_size = 0; st.st_blocks = 0; let cur_path = match CString::new(format!("{}/cur", path)) {
Ok(c) => c,
Err(_) => return 0,
};
let mut sub: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::stat(cur_path.as_ptr(), &mut sub) } != 0 || (sub.st_mode & libc::S_IFMT) != libc::S_IFDIR {
return 0; }
st.st_atime = sub.st_atime; let tmp_path = match CString::new(format!("{}/tmp", path)) {
Ok(c) => c,
Err(_) => return 0,
};
if unsafe { libc::stat(tmp_path.as_ptr(), &mut sub) } != 0 || (sub.st_mode & libc::S_IFMT) != libc::S_IFDIR {
return 0; }
st.st_mtime = sub.st_mtime; let new_path = match CString::new(format!("{}/new", path)) {
Ok(c) => c,
Err(_) => return 0,
};
if unsafe { libc::stat(new_path.as_ptr(), &mut sub) } != 0 || (sub.st_mode & libc::S_IFMT) != libc::S_IFDIR {
return 0; }
st.st_mtime = sub.st_mtime; let mut atime: libc::time_t = 0; let mut mtime: libc::time_t = 0; for sub_name in ["new", "cur"] {
let dir = format!("{}/{}", path, sub_name);
let entries = match std::fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => return 0,
};
for entry in entries.flatten() {
let name = entry.file_name();
let name_bytes = name.as_encoded_bytes();
if name_bytes.first() == Some(&b'.') { continue;
}
let entry_path = match CString::new(entry.path().to_string_lossy().as_bytes()) {
Ok(c) => c,
Err(_) => continue,
};
let mut entry_st: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::stat(entry_path.as_ptr(), &mut entry_st) } != 0 { continue;
}
st.st_size += entry_st.st_size; st.st_blocks += 1; if entry_st.st_atime != entry_st.st_mtime && entry_st.st_atime > atime { atime = entry_st.st_atime;
}
if entry_st.st_mtime > mtime { mtime = entry_st.st_mtime;
}
}
}
if atime != 0 { st.st_atime = atime;
}
if mtime != 0 { st.st_mtime = mtime;
}
0 }
pub(crate) fn base64_decode(s: &str) -> Vec<u8> {
let decode_char = |c: u8| -> Option<u8> {
match c {
b'A'..=b'Z' => Some(c - b'A'),
b'a'..=b'z' => Some(c - b'a' + 26),
b'0'..=b'9' => Some(c - b'0' + 52),
b'+' => Some(62),
b'/' => Some(63),
_ => None,
}
};
let bytes = s.as_bytes();
let mut out = Vec::with_capacity(s.len() / 4 * 3);
let mut i = 0;
while i + 4 <= bytes.len() {
let chunk = &bytes[i..i + 4];
let pad = chunk.iter().filter(|&&c| c == b'=').count();
let v0 = decode_char(chunk[0]).unwrap_or(0) as u32;
let v1 = decode_char(chunk[1]).unwrap_or(0) as u32;
let v2 = decode_char(chunk[2]).unwrap_or(0) as u32;
let v3 = decode_char(chunk[3]).unwrap_or(0) as u32;
let n = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3;
out.push(((n >> 16) & 0xff) as u8);
if pad < 2 {
out.push(((n >> 8) & 0xff) as u8);
}
if pad < 1 {
out.push((n & 0xff) as u8);
}
i += 4;
}
out
}
pub(crate) fn bufferwords(s: &str) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let mut cur = String::new();
let chars: Vec<char> = s.chars().collect();
let mut i = 0;
let flush = |out: &mut Vec<String>, cur: &mut String| {
if !cur.is_empty() {
out.push(std::mem::take(cur));
}
};
while i < chars.len() {
let c = chars[i];
match c {
' ' | '\t' | '\n' => {
flush(&mut out, &mut cur);
i += 1;
}
';' | '&' | '|' | '<' | '>' | '(' | ')' => {
flush(&mut out, &mut cur);
let mut tok = String::new();
tok.push(c);
while i + 1 < chars.len()
&& chars[i + 1] == c
&& matches!(c, '&' | '|' | ';' | '<' | '>')
{
tok.push(c);
i += 1;
}
out.push(tok);
i += 1;
}
'\'' => {
i += 1;
while i < chars.len() && chars[i] != '\'' {
cur.push(chars[i]);
i += 1;
}
if i < chars.len() {
i += 1; }
}
'"' => {
i += 1;
while i < chars.len() && chars[i] != '"' {
if chars[i] == '\\' && i + 1 < chars.len() {
i += 1;
cur.push(chars[i]);
i += 1;
continue;
}
cur.push(chars[i]);
i += 1;
}
if i < chars.len() {
i += 1; }
}
'\\' if i + 1 < chars.len() => {
cur.push(chars[i + 1]);
i += 2;
}
_ => {
cur.push(c);
i += 1;
}
}
}
flush(&mut out, &mut cur);
out
}
pub(crate) fn ispwd(pwd: &str) -> bool {
if !pwd.starts_with('/') {
return false;
}
let pwd_meta = match std::fs::metadata(pwd) {
Ok(m) => m,
Err(_) => return false,
};
let dot_meta = match std::fs::metadata(".") {
Ok(m) => m,
Err(_) => return false,
};
if pwd_meta.dev() != dot_meta.dev() || pwd_meta.ino() != dot_meta.ino() {
return false;
}
for comp in pwd.split('/') {
if comp == "." || comp == ".." {
return false;
}
}
true
}
#[allow(non_snake_case)]
pub(crate) fn quotedzputs(s: &str) -> String {
if s.is_empty() {
return "''".to_string();
}
let needs_quote = s.chars().any(|c| {
matches!(
c,
'#' | '$'
| '^'
| '*'
| '('
| ')'
| '='
| '|'
| '{'
| '}'
| '['
| ']'
| '`'
| '<'
| '>'
| '?'
| '~'
| ';'
| '&'
| '\n'
| '\t'
| ' '
| '\\'
| '\''
| '"'
)
});
if !needs_quote {
s.to_string()
} else {
let inner = s.replace('\'', "'\\''");
format!("'{}'", inner)
}
}
pub(crate) fn printprompt4() {
if !isset(XTRACE) {
return;
}
let posix = EMULATION(EMULATE_KSH | EMULATE_SH); let prefix_template = crate::ported::params::getsparam("PS4")
.or_else(|| crate::ported::params::getsparam("PROMPT4"))
.unwrap_or_else(|| {
if posix {
"+ ".to_string() } else {
"+%N:%i> ".to_string() }
});
let saved = isset(XTRACE);
crate::ported::options::opt_state_set(
&opt_name(XTRACE), false,
);
let prefix = crate::prompt::expand_prompt(&prefix_template);
crate::ported::options::opt_state_set(
&opt_name(XTRACE), saved,
);
eprint!("{}", prefix);
}
pub(crate) fn zexpandtabs(
s: &str,
width: i32,
startpos: i32,
all_tabs: bool,
out: &mut String,
) -> i32 {
let mut startpos = startpos;
let mut at_start = true;
for c in s.chars() {
if c == '\t' {
if all_tabs || at_start {
if width <= 0 || startpos % width == 0 {
out.push(' ');
startpos += 1;
}
if width > 0 {
while startpos % width != 0 {
out.push(' ');
startpos += 1;
}
}
} else {
let rem = startpos % width;
startpos += width - rem;
out.push('\t');
}
continue;
} else if c == '\n' || c == '\r' {
out.push(c);
startpos = 0;
at_start = true;
continue;
}
at_start = false;
out.push(c);
startpos += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as i32;
}
startpos
}
pub fn get_username() -> String {
static CACHE: Mutex<Option<(u32, String)>> = Mutex::new(None);
let current_uid = unsafe { libc::getuid() };
let mut guard = CACHE.lock().unwrap();
if let Some((uid, name)) = &*guard {
if *uid == current_uid {
return name.clone();
}
}
let name = unsafe {
let pw = libc::getpwuid(current_uid);
if pw.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr((*pw).pw_name)
.to_string_lossy()
.into_owned()
}
};
*guard = Some((current_uid, name.clone()));
name
}
static PREPROMPT_FNS: std::sync::Mutex<Vec<fn()>> = std::sync::Mutex::new(Vec::new());
pub fn addprepromptfn(func: fn()) { PREPROMPT_FNS.lock().unwrap().push(func);
}
pub fn delprepromptfn(func: fn()) { let mut list = PREPROMPT_FNS.lock().unwrap();
if let Some(pos) = list.iter().position(|f| *f as usize == func as usize) {
list.remove(pos);
}
}
static TIMED_FNS: std::sync::Mutex<Vec<(i64, fn())>> = std::sync::Mutex::new(Vec::new());
pub fn addtimedfn(func: fn(), when: i64) { let mut list = TIMED_FNS.lock().unwrap();
let pos = list.iter().position(|(w, _)| when < *w).unwrap_or(list.len());
list.insert(pos, (when, func));
}
pub fn deltimedfn(func: fn()) { let mut list = TIMED_FNS.lock().unwrap();
if let Some(pos) = list.iter().position(|(_, f)| *f as usize == func as usize) {
list.remove(pos);
}
}
pub fn callhookfunc(name: &str, args: Option<&[String]>, arrayp: bool) -> i32 {
let mut stat: i32 = 1;
let shf_exists = crate::ported::hashtable::shfunctab_lock()
.read()
.map(|t| t.get(name).is_some())
.unwrap_or(false);
if shf_exists {
stat = 0;
}
if arrayp {
let arr_name = format!("{}_functions", name);
let arr = crate::ported::params::paramtab().read()
.ok()
.and_then(|t| t.get(&arr_name).and_then(|p| p.u_arr.clone()))
.unwrap_or_default();
for fn_name in arr {
let exists = crate::ported::hashtable::shfunctab_lock()
.read()
.map(|t| t.get(&fn_name).is_some())
.unwrap_or(false);
if exists {
stat = 0;
}
}
}
stat
}
pub fn preprompt() {
static LAST_PERIODIC: AtomicI64 = AtomicI64::new(0);
callhookfunc("precmd", None, true);
let period = crate::ported::params::getiparam("PERIOD");
if period > 0 {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
if now > LAST_PERIODIC.load(Ordering::Relaxed) + period
&& callhookfunc("periodic", None, true) == 0
{
LAST_PERIODIC.store(now, Ordering::Relaxed);
}
}
let snapshot: Vec<fn()> = PREPROMPT_FNS.lock().unwrap().clone();
for f in snapshot {
f();
}
}
#[cfg(unix)]
pub fn fdgettyinfo(fd: i32) -> std::io::Result<libc::termios> {
let mut tio: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(fd, &mut tio) } == -1 {
Err(std::io::Error::last_os_error())
} else {
Ok(tio)
}
}
#[cfg(not(unix))]
pub fn fdgettyinfo(_fd: i32) -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Unsupported, "no termios"))
}
#[cfg(unix)]
pub fn fdsettyinfo(SHTTY: i32, ti: &libc::termios) -> std::io::Result<()> {
loop {
if unsafe { libc::tcsetattr(SHTTY, libc::TCSADRAIN, ti) } != -1 {
return Ok(());
}
let err = std::io::Error::last_os_error();
if err.kind() != std::io::ErrorKind::Interrupted {
return Err(err);
}
}
}
#[cfg(not(unix))]
pub fn fdsettyinfo(SHTTY: i32, ti: &()) -> std::io::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Unsupported, "no termios"))
}
pub fn mb_niceformat(s: &str) -> String {
let unmeta = self::unmeta(s);
let mut out = String::with_capacity(unmeta.len());
for c in unmeta.chars() {
out.push_str(&nicechar(c));
}
out
}
pub fn is_mb_niceformat(s: &str) -> bool {
let mut bytes = s.as_bytes().to_vec();
let umlen = unmetafy(&mut bytes);
bytes.truncate(umlen);
let mut i = 0;
while i < bytes.len() {
let remaining = &bytes[i..];
match std::str::from_utf8(remaining) {
Ok(s) => {
for c in s.chars() {
if (c as u32) < 0x20 || c == '\x7f' || (c as u32) > 0x7e {
return true;
}
}
return false;
}
Err(e) => {
let valid_up_to = e.valid_up_to();
if valid_up_to > 0 {
let valid = std::str::from_utf8(&remaining[..valid_up_to])
.expect("valid_up_to slice");
for c in valid.chars() {
if (c as u32) < 0x20 || c == '\x7f' || (c as u32) > 0x7e {
return true;
}
}
i += valid_up_to;
continue;
}
let b = remaining[0];
if is_nicechar(b as char) {
return true;
}
i += 1;
}
}
}
false
}
pub fn zputs(s: &str) -> std::io::Result<()> {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\u{83}' {
if let Some(next) = chars.next() {
let b = next as u32;
out.push(char::from_u32(b ^ 32).unwrap_or(next));
}
} else if (c as u32) >= 0x83 && (c as u32) <= 0x9b {
continue;
} else {
out.push(c);
}
}
std::io::stdout().lock().write_all(out.as_bytes())
}
pub fn mb_metacharlenconv(s: &str) -> (usize, Option<char>) {
let bytes = s.as_bytes();
if bytes.is_empty() {
return (0, None);
}
if bytes[0] == 0x83 && bytes.len() >= 2 {
let raw = bytes[1] as u32 ^ 32;
return (2, char::from_u32(raw));
}
if bytes[0] <= 0x7f {
return (1, Some(bytes[0] as char));
}
if let Some(c) = s.chars().next() {
return (c.len_utf8(), Some(c));
}
(1, None)
}
pub fn metacharlenconv(s: &str) -> (usize, Option<char>) {
let bytes = s.as_bytes();
if bytes.is_empty() {
return (0, None);
}
if bytes[0] == 0x83 && bytes.len() >= 2 {
let raw = bytes[1] as u32 ^ 32;
return (2, char::from_u32(raw));
}
(1, Some(bytes[0] as char))
}
pub fn charlenconv(s: &str, len: usize) -> (usize, Option<char>) {
if len == 0 {
return (0, None);
}
let bytes = s.as_bytes();
if bytes.is_empty() {
return (0, None);
}
(1, Some(bytes[0] as char))
}
pub fn metafy(buf: &str) -> String { let bytes = buf.as_bytes();
let mut out = Vec::with_capacity(bytes.len());
for &b in bytes {
if imeta_byte(b) {
out.push(Meta);
out.push(b ^ 32);
} else {
out.push(b);
}
}
String::from_utf8(out.clone())
.unwrap_or_else(|_| String::from_utf8_lossy(&out).into_owned())
}
pub fn set_widearray(mb_array: &str) -> Vec<char> {
let mut bytes = mb_array.as_bytes().to_vec();
unmetafy(&mut bytes);
match std::str::from_utf8(&bytes) {
Ok(s) => s.chars().collect(),
Err(_) => Vec::new(),
}
}