use crate::command_data::Run;
use crate::parse::parse_line;
use crate::platform::{OsSignal, Pid, Platform, Sys, TermSettings, STDIN_FILENO};
use std::collections::HashMap;
use std::ffi::{OsStr, OsString};
use std::fmt::{Display, Formatter};
use std::{env, fmt, io};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum JobStatus {
New,
Running,
Stopped,
Done,
}
impl fmt::Display for JobStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
JobStatus::New => write!(f, "New"),
JobStatus::Running => write!(f, "Running"),
JobStatus::Stopped => write!(f, "Stopped"),
JobStatus::Done => write!(f, "Done"),
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum PidStatus {
Running(Pid),
Done(Pid, i32), Error(Pid),
Signaled(Pid, OsSignal),
}
impl PidStatus {
pub fn pid(&self) -> Pid {
match self {
PidStatus::Running(pid) => *pid,
PidStatus::Done(pid, _) => *pid,
PidStatus::Error(pid) => *pid,
PidStatus::Signaled(pid, _) => *pid,
}
}
}
#[derive(Clone, Debug)]
pub struct Job {
id: u32,
shell_pid: Pid,
pgid: Pid,
pids: Vec<PidStatus>,
names: Vec<String>,
status: JobStatus,
interactive: bool,
stealth: bool, }
impl Job {
fn new(id: u32, shell_pid: Pid, interactive: bool) -> Self {
Self {
id,
shell_pid,
pgid: shell_pid,
pids: vec![],
names: vec![],
status: JobStatus::New,
interactive,
stealth: false,
}
}
pub fn is_empty(&self) -> bool {
self.pids.is_empty()
}
pub fn pgid(&self) -> Pid {
self.pgid
}
pub fn pids(&self) -> &[PidStatus] {
&self.pids[..]
}
pub fn status(&self) -> JobStatus {
self.status
}
pub fn mark_stopped(&mut self) {
self.status = JobStatus::Stopped;
}
pub fn mark_running(&mut self) {
self.status = JobStatus::Running;
}
pub fn mark_done(&mut self) {
self.status = JobStatus::Done;
}
pub fn add_process(&mut self, pid: Pid, name: impl Into<String>) {
if self.pids.is_empty() {
self.pgid = pid;
}
self.pids.push(PidStatus::Running(pid));
self.names.push(name.into());
}
pub fn process_done(&mut self, pid: Pid, status: i32) {
for proc in self.pids.iter_mut() {
if proc.pid() == pid {
*proc = PidStatus::Done(pid, status);
return;
}
}
panic!("process {pid} not part of job");
}
pub fn process_error(&mut self, pid: Pid) {
for proc in self.pids.iter_mut() {
if proc.pid() == pid {
*proc = PidStatus::Error(pid);
return;
}
}
panic!("process {pid} not part of job");
}
pub fn process_signaled(&mut self, pid: Pid, signal: OsSignal) {
for proc in self.pids.iter_mut() {
if proc.pid() == pid {
*proc = PidStatus::Signaled(pid, signal);
return;
}
}
panic!("process {pid} not part of job");
}
pub fn reverse(&mut self) {
self.pids.reverse();
self.names.reverse();
}
pub fn set_stealth(&mut self, stealth: bool) {
self.stealth = stealth;
}
pub fn stealth(&self) -> bool {
self.stealth
}
pub fn interactive(&self) -> bool {
self.interactive
}
pub fn shell_pid(&self) -> Pid {
self.shell_pid
}
}
impl Display for Job {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(
f,
"[{}]\t{}\t{:?}\t{:?}",
self.id, self.status, self.pids, self.names
)
}
}
pub struct Jobs {
next_job: u32,
jobs: Vec<Job>,
shell_pid: Pid,
interactive: bool,
term_settings: Option<TermSettings>,
alias: HashMap<String, Run>,
local_vars: HashMap<OsString, OsString>,
}
impl Jobs {
pub fn new(interactive: bool) -> Self {
let shell_pid = Sys::getpid();
let term_settings = if interactive {
if let Ok(term_setting) = Sys::get_term_settings(STDIN_FILENO) {
Some(term_setting)
} else {
None
}
} else {
None
};
Self {
next_job: 0,
jobs: vec![],
shell_pid,
interactive,
term_settings,
alias: HashMap::new(),
local_vars: HashMap::new(),
}
}
pub fn cap_term(&mut self) {
self.term_settings = if let Ok(term_setting) = Sys::get_term_settings(STDIN_FILENO) {
Some(term_setting)
} else {
None
};
}
pub fn new_job(&mut self) -> Job {
if self.jobs.is_empty() {
self.next_job = 0;
}
self.next_job += 1;
Job::new(self.next_job, self.shell_pid, self.interactive)
}
pub fn push_job(&mut self, job: Job) {
self.jobs.push(job);
}
pub fn set_no_tty(&mut self) {
self.term_settings = None;
}
pub fn set_interactive(&mut self, interactive: bool) {
self.interactive = interactive;
}
pub fn get_job_mut(&mut self, job_id: u32) -> Option<&mut Job> {
self.jobs.iter_mut().find(|job| job.id == job_id)
}
pub fn reap_procs(&mut self) {
for job in self.jobs.iter_mut() {
if let JobStatus::Running = job.status() {
let pids: Vec<Pid> = job.pids().iter().map(|s| s.pid()).collect();
for pid in &pids {
Sys::try_wait_pid(*pid, job);
}
let mut done = true;
for pid_status in job.pids() {
if let PidStatus::Running(_) = pid_status {
done = false;
}
}
if done {
job.mark_done();
}
}
}
let jobs_len = self.jobs.len();
for i in (0..jobs_len).rev() {
if let JobStatus::Done = self.jobs[i].status() {
if !self.jobs[i].stealth() {
eprintln!("{}", self.jobs[i]);
}
self.jobs.remove(i);
}
}
}
pub fn foreground_job(&mut self, job_num: u32) {
let term_settings = self.term_settings.clone();
if let Some(job) = self.get_job_mut(job_num) {
if let Err(err) = Sys::foreground_job(job, &term_settings) {
eprintln!("Error making job {job_num} foreground in parent: {err}");
}
} else {
eprintln!("job number {job_num} is invalid");
}
}
pub fn background_job(&mut self, job_num: u32) {
if let Some(job) = self.get_job_mut(job_num) {
if let Err(err) = Sys::background_job(job) {
eprintln!("Error making job {job_num} background in parent: {err}");
}
} else {
eprintln!("job number {job_num} is invalid");
}
}
pub fn restore_terminal(&self) {
if let Some(term_settings) = &self.term_settings {
if let Err(err) = Sys::restore_terminal(term_settings, self.shell_pid) {
eprintln!("Error resetting shell terminal settings: {}", err);
}
}
}
pub fn get_alias<S: AsRef<str>>(&self, name: S) -> Option<Run> {
self.alias.get(name.as_ref()).cloned()
}
pub fn clear_aliases(&mut self) {
self.alias.clear();
}
pub fn remove_alias<S: AsRef<str>>(&mut self, name: S) -> Option<Run> {
self.alias.remove(name.as_ref())
}
pub fn add_alias(&mut self, name: String, value: String) -> Result<(), io::Error> {
let runj = parse_line(self, &value)?;
self.alias.insert(name, runj.into_run());
Ok(())
}
pub fn add_alias_run(&mut self, name: String, value: Run) {
self.alias.insert(name, value);
}
pub fn print_alias(&self, name: String) {
if let Some(value) = self.alias.get(&name) {
println!("alias {name}='{value}'");
} else {
eprintln!("no alias set for: {name}");
}
}
pub fn print_all_alias(&self) {
for (name, value) in &self.alias {
println!("alias {name}='{value}'");
}
}
pub fn set_local_var(&mut self, key: OsString, val: OsString) {
self.local_vars.insert(key, val);
}
pub fn get_local_var(&self, key: &OsStr) -> Option<&OsStr> {
self.local_vars.get(key).map(|s| s as &OsStr)
}
pub fn remove_local_var(&mut self, key: &OsStr) -> Option<OsString> {
self.local_vars.remove(key)
}
pub fn get_env_or_local_var(&self, key: &OsStr) -> Option<OsString> {
if let Some(val) = env::var_os(key) {
Some(val)
} else {
self.get_local_var(key).map(|val| val.to_os_string())
}
}
}
impl Display for Jobs {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
for job in &self.jobs {
writeln!(f, "{job}")?;
}
Ok(())
}
}