use crate::extensions::emulation_startup::{personality, Personality};
pub fn times_field(ticks: i64, clktck: i64) -> String {
let clktck = if clktck <= 0 { 100 } else { clktck };
let mins = ticks / (60 * clktck);
let p = personality();
let secs = if p == Personality::Zsh {
(ticks / clktck) % clktck
} else {
(ticks / clktck) % 60
};
match p {
Personality::Bash => {
let ms = (ticks * 1000 / clktck) % 1000;
format!("{mins}m{secs}.{ms:03}s")
}
Personality::Dash => {
let us = (ticks * 1_000_000 / clktck) % 1_000_000;
format!("{mins}m{secs}.{us:06}s")
}
Personality::Ksh93 => {
let ms = (ticks * 1000 / clktck) % 1000;
format!("{mins}m{secs:02}.{ms:03}s")
}
Personality::Mksh | Personality::Pdksh => {
let cs = (ticks * 100 / clktck) % 100;
format!("{mins}m{secs:02}.{cs:02}s")
}
_ => {
let cs = (ticks * 100 / clktck) % 100;
format!("{mins}m{secs}.{cs:02}s")
}
}
}
pub fn kill_list(sigs: &[(i32, String)]) -> Option<String> {
let mut out = String::new();
match personality() {
Personality::Zsh => return None,
Personality::Bash => {
for (i, (num, name)) in sigs.iter().enumerate() {
out.push_str(&format!("{num:2}) SIG{name}"));
if i % 5 == 4 {
out.push('\n');
} else {
out.push('\t');
}
}
if !sigs.len().is_multiple_of(5) {
out.push('\n');
}
}
Personality::Dash => {
out.push_str("0\n");
for (_, name) in sigs {
out.push_str(name);
out.push('\n');
}
}
Personality::Ksh93 | Personality::Sh | Personality::Csh => {
let ksh = personality() == Personality::Ksh93;
for (num, name) in sigs {
if ksh && *num == 6 {
out.push_str("IOT");
} else {
out.push_str(name);
}
out.push('\n');
}
}
Personality::Mksh | Personality::Pdksh => {
let half = sigs.len().div_ceil(2);
let left_width = 40usize;
for i in 0..half {
let (num, name) = &sigs[i];
let cell = format!("{num:2} {name:>6} {}", signal_description(*num));
match sigs.get(i + half) {
Some((rnum, rname)) => out.push_str(&format!(
"{cell:<left_width$}{rnum:2} {rname:>6} {}\n",
signal_description(*rnum)
)),
None => {
out.push_str(cell.trim_end());
out.push('\n');
}
}
}
}
}
Some(out)
}
fn signal_description(num: i32) -> String {
let p = unsafe { libc::strsignal(num) };
if p.is_null() {
return String::new();
}
let text = unsafe { std::ffi::CStr::from_ptr(p) }
.to_string_lossy()
.into_owned();
match text.rsplit_once(": ") {
Some((head, tail)) if !tail.is_empty() && tail.bytes().all(|b| b.is_ascii_digit()) => {
head.to_string()
}
_ => text,
}
}
pub fn hash_header() -> Option<&'static str> {
match personality() {
Personality::Bash => Some("hits\tcommand"),
_ => None,
}
}
pub fn hash_entry(name: &str, path: &str) -> Option<String> {
match personality() {
Personality::Bash => Some(format!("{:>4}\t{path}", 0)),
Personality::Dash => Some(path.to_string()),
_ => {
let _ = name;
None
}
}
}
struct SetOpt {
name: &'static str,
zsh: Option<&'static str>,
invert: bool,
default_on: bool,
}
const fn o(name: &'static str, zsh: &'static str) -> SetOpt {
SetOpt {
name,
zsh: Some(zsh),
invert: false,
default_on: false,
}
}
const fn inv(name: &'static str, zsh: &'static str) -> SetOpt {
SetOpt {
name,
zsh: Some(zsh),
invert: true,
default_on: false,
}
}
const fn fixed(name: &'static str, default_on: bool) -> SetOpt {
SetOpt {
name,
zsh: None,
invert: false,
default_on,
}
}
const DASH_SET_O: &[SetOpt] = &[
o("errexit", "errexit"),
o("noglob", "noglob"),
o("ignoreeof", "ignoreeof"),
o("interactive", "interactive"),
o("monitor", "monitor"),
o("noexec", "noexec"),
o("stdin", "stdin"),
o("xtrace", "xtrace"),
o("verbose", "verbose"),
o("vi", "vi"),
o("emacs", "emacs"),
o("noclobber", "noclobber"),
o("allexport", "allexport"),
o("notify", "notify"),
o("nounset", "nounset"),
o("nolog", "nolog"),
fixed("debug", false),
];
const KSH93_SET_O: &[SetOpt] = &[
o("allexport", "allexport"),
fixed("backslashctrl", true),
o("bgnice", "bgnice"),
o("braceexpand", "braceexpand"),
inv("clobber", "noclobber"),
o("emacs", "emacs"),
o("errexit", "errexit"),
inv("exec", "noexec"),
fixed("functrace", false),
inv("glob", "noglob"),
fixed("globcasedetect", false),
fixed("globstar", false),
fixed("gmacs", false),
fixed("histexpand", false),
fixed("histreedit", false),
fixed("histverify", false),
o("ignoreeof", "ignoreeof"),
o("interactive", "interactive"),
fixed("keyword", false),
fixed("letoctal", false),
inv("log", "nolog"),
o("login_shell", "loginshell"),
o("markdirs", "markdirs"),
o("monitor", "monitor"),
fixed("multiline", true),
o("notify", "notify"),
o("pipefail", "pipefail"),
fixed("posix", false),
o("privileged", "privileged"),
fixed("rc", false),
o("restricted", "restricted"),
fixed("showme", false),
o("trackall", "trackall"),
inv("unset", "nounset"),
o("verbose", "verbose"),
o("vi", "vi"),
fixed("viraw", true),
o("xtrace", "xtrace"),
];
const MKSH_SET_O: &[SetOpt] = &[
o("allexport", "allexport"),
o("bgnice", "bgnice"),
o("braceexpand", "braceexpand"),
fixed("emacs", true),
o("errexit", "errexit"),
fixed("gmacs", false),
o("ignoreeof", "ignoreeof"),
fixed("inherit-xtrace", true),
o("interactive", "interactive"),
fixed("keyword", false),
o("login", "login"),
o("markdirs", "markdirs"),
o("monitor", "monitor"),
o("noclobber", "noclobber"),
o("noexec", "noexec"),
o("noglob", "noglob"),
fixed("nohup", true),
o("nolog", "nolog"),
o("notify", "notify"),
o("nounset", "nounset"),
o("physical", "physical"),
o("pipefail", "pipefail"),
fixed("posix", false),
o("privileged", "privileged"),
o("restricted", "restricted"),
fixed("sh", false),
o("stdin", "stdin"),
fixed("trackall", true),
fixed("utf8-mode", false),
o("verbose", "verbose"),
o("vi", "vi"),
fixed("vi-esccomplete", false),
fixed("vi-tabcomplete", true),
fixed("viraw", false),
o("xtrace", "xtrace"),
];
impl SetOpt {
fn state(&self) -> bool {
match self.zsh {
Some(name) => {
let on = crate::ported::options::opt_state_get(name).unwrap_or(self.default_on);
if self.invert {
!on
} else {
on
}
}
None => self.default_on,
}
}
}
pub fn special_builtin_error_aborts() -> bool {
matches!(
personality(),
Personality::Ksh93
| Personality::Mksh
| Personality::Pdksh
| Personality::Dash
| Personality::Sh
)
}
pub fn special_builtin_abort_status(usage_error: bool, builtin_status: i32) -> i32 {
match personality() {
Personality::Dash | Personality::Sh => 2,
Personality::Mksh | Personality::Pdksh => 1,
Personality::Ksh93 if usage_error => 2,
_ => builtin_status,
}
}
pub fn alias_always_quotes() -> bool {
matches!(personality(), Personality::Dash | Personality::Sh)
}
pub fn set_o_listing() -> Option<String> {
set_o_render(false)
}
pub fn set_plus_o_listing() -> Option<String> {
match personality() {
Personality::Dash | Personality::Sh => set_o_render(true),
Personality::Ksh93 => Some(compact_set_plus_o(
KSH93_SET_O,
KSH93_DEFAULT_ON,
"set --default",
"--",
)),
Personality::Mksh | Personality::Pdksh => Some(compact_set_plus_o(
MKSH_SET_O,
MKSH_DEFAULT_ON,
"set -o .reset",
"-o ",
)),
_ => None,
}
}
const KSH93_DEFAULT_ON: &[&str] = &["backslashctrl", "clobber", "exec", "glob", "log", "unset"];
const MKSH_DEFAULT_ON: &[&str] = &[
"emacs",
"inherit-xtrace",
"nohup",
"trackall",
"vi-tabcomplete",
];
fn compact_set_plus_o(table: &[SetOpt], defaults: &[&str], reset: &str, prefix: &str) -> String {
let mut out = String::from(reset);
for e in table {
if e.state() && !defaults.contains(&e.name) {
out.push(' ');
out.push_str(prefix);
out.push_str(e.name);
}
}
out.push('\n');
out
}
fn set_o_render(reusable: bool) -> Option<String> {
let (table, width) = match personality() {
Personality::Dash | Personality::Sh => (DASH_SET_O, 15usize),
Personality::Ksh93 => (KSH93_SET_O, 24),
Personality::Mksh | Personality::Pdksh => (MKSH_SET_O, 14),
_ => return None,
};
let on = |b: bool| if b { "on" } else { "off" };
if reusable {
let mut out = String::new();
for e in table {
out.push_str(&format!(
"set {}o {}\n",
if e.state() { "-" } else { "+" },
e.name
));
}
return Some(out);
}
let mut out = String::from("Current option settings\n");
if matches!(personality(), Personality::Mksh | Personality::Pdksh) {
let rows = table.len().div_ceil(4);
for r in 0..rows {
let mut line = String::new();
for c in 0..4 {
let Some(e) = table.get(c * rows + r) else {
continue;
};
line.push_str(&format!("{:<width$} {:<5}", e.name, on(e.state())));
}
out.push_str(line.trim_end());
out.push('\n');
}
return Some(out);
}
for e in table {
out.push_str(&format!("{:<width$} {}\n", e.name, on(e.state())));
}
Some(out)
}