use super::Job;
#[cfg(doc)]
use super::JobList;
use super::Pid;
use super::ProcessResult;
use super::ProcessState;
use crate::semantics::ExitStatus;
use crate::system::Signals;
use std::borrow::Cow;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Marker {
None,
CurrentJob,
PreviousJob,
}
impl Marker {
pub const fn as_char(self) -> char {
match self {
Marker::None => ' ',
Marker::CurrentJob => '+',
Marker::PreviousJob => '-',
}
}
}
impl Display for Marker {
fn fmt(&self, f: &mut Formatter) -> Result {
self.as_char().fmt(f)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum State<'a> {
Running,
Stopped { signal: Cow<'a, str> },
Exited(ExitStatus),
Signaled {
signal: Cow<'a, str>,
core_dump: bool,
},
}
impl std::fmt::Display for State<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self {
Self::Running => "Running".fmt(f),
Self::Exited(ExitStatus::SUCCESS) => "Done".fmt(f),
Self::Exited(exit_status) => format!("Done({exit_status})").fmt(f),
Self::Stopped { signal } => format!("Stopped(SIG{signal})").fmt(f),
Self::Signaled {
signal,
core_dump: false,
} => format!("Killed(SIG{signal})").fmt(f),
Self::Signaled {
signal,
core_dump: true,
} => format!("Killed(SIG{signal}: core dumped)").fmt(f),
}
}
}
impl State<'_> {
#[must_use]
pub fn from_process_state<S: Signals>(state: ProcessState, system: &S) -> Self {
match state {
ProcessState::Running => Self::Running,
ProcessState::Halted(result) => match result {
ProcessResult::Exited(status) => Self::Exited(status),
ProcessResult::Stopped(signal) => {
let signal = system.sig2str(signal).unwrap_or(Cow::Borrowed("???"));
Self::Stopped { signal }
}
ProcessResult::Signaled { signal, core_dump } => {
let signal = system.sig2str(signal).unwrap_or(Cow::Borrowed("???"));
Self::Signaled { signal, core_dump }
}
},
}
}
}
#[derive(Clone, Debug)]
pub struct Report<'a> {
pub number: usize,
pub marker: Marker,
pub pid: Option<Pid>,
pub state: State<'a>,
pub name: &'a str,
}
impl std::fmt::Display for Report<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "[{}] {} ", self.number, self.marker)?;
if let Some(pid) = self.pid {
write!(f, "{pid:5} ")?;
}
write!(f, "{:20} {}", self.state, self.name)
}
}
#[derive(Clone, Debug, Default)]
pub struct Accumulator {
pub current_job_index: Option<usize>,
pub previous_job_index: Option<usize>,
pub show_pid: bool,
pub pgid_only: bool,
pub print: String,
pub indices_reported: Vec<usize>,
}
impl Accumulator {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add<S: Signals>(&mut self, index: usize, job: &Job, system: &S) {
use std::fmt::Write as _;
if self.pgid_only {
writeln!(self.print, "{}", job.pid)
} else {
let report = Report {
number: index + 1,
marker: if self.current_job_index == Some(index) {
Marker::CurrentJob
} else if self.previous_job_index == Some(index) {
Marker::PreviousJob
} else {
Marker::None
},
pid: self.show_pid.then_some(job.pid),
state: State::from_process_state(job.state, system),
name: &job.name,
};
writeln!(self.print, "{report}")
}
.unwrap();
self.indices_reported.push(index);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn state_display() {
let state = State::Running;
assert_eq!(state.to_string(), "Running");
let state = State::Stopped {
signal: Cow::Borrowed("STOP"),
};
assert_eq!(state.to_string(), "Stopped(SIGSTOP)");
let state = State::Exited(ExitStatus::SUCCESS);
assert_eq!(state.to_string(), "Done");
let state = State::Exited(ExitStatus::NOT_FOUND);
assert_eq!(state.to_string(), "Done(127)");
let state = State::Signaled {
signal: Cow::Borrowed("KILL"),
core_dump: false,
};
assert_eq!(state.to_string(), "Killed(SIGKILL)");
let state = State::Signaled {
signal: Cow::Borrowed("QUIT"),
core_dump: true,
};
assert_eq!(state.to_string(), "Killed(SIGQUIT: core dumped)");
}
#[test]
fn report_display() {
let mut report = Report {
number: 1,
marker: Marker::None,
pid: None,
state: State::Running,
name: "echo ok",
};
assert_eq!(report.to_string(), "[1] Running echo ok");
report.number = 2;
assert_eq!(report.to_string(), "[2] Running echo ok");
report.marker = Marker::CurrentJob;
assert_eq!(report.to_string(), "[2] + Running echo ok");
report.pid = Some(Pid(42));
assert_eq!(
report.to_string(),
"[2] + 42 Running echo ok"
);
report.pid = Some(Pid(123456));
assert_eq!(
report.to_string(),
"[2] + 123456 Running echo ok"
);
report.name = "foo | bar";
assert_eq!(
report.to_string(),
"[2] + 123456 Running foo | bar"
);
}
}