use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Signal {
Terminate,
Interrupt,
Quit,
Hangup,
Pipe,
User1,
User2,
}
impl Signal {
pub const ALL: [Self; 7] = [
Self::Terminate,
Self::Interrupt,
Self::Quit,
Self::Hangup,
Self::Pipe,
Self::User1,
Self::User2,
];
#[must_use]
pub const fn description(self) -> &'static str {
match self {
Self::Terminate => "Terminate (SIGTERM / CTRL_CLOSE_EVENT)",
Self::Interrupt => "Interrupt (SIGINT / CTRL_C_EVENT)",
Self::Quit => "Quit (SIGQUIT / CTRL_BREAK_EVENT)",
Self::Hangup => "Hangup (SIGHUP / CTRL_SHUTDOWN_EVENT)",
Self::Pipe => "Pipe (SIGPIPE, Unix only)",
Self::User1 => "User1 (SIGUSR1, Unix only)",
Self::User2 => "User2 (SIGUSR2, Unix only)",
}
}
#[must_use]
pub const fn unix_number(self) -> Option<i32> {
match self {
Self::Hangup => Some(1),
Self::Interrupt => Some(2),
Self::Quit => Some(3),
Self::User1 => Some(10),
Self::User2 => Some(12),
Self::Pipe => Some(13),
Self::Terminate => Some(15),
}
}
#[must_use]
pub const fn is_unix_only(self) -> bool {
matches!(self, Self::Pipe | Self::User1 | Self::User2)
}
#[must_use]
pub const fn available_on_current_platform(self) -> bool {
if cfg!(unix) {
true
} else {
!self.is_unix_only()
}
}
pub(crate) const fn bit(self) -> u16 {
match self {
Self::Terminate => 1 << 0,
Self::Interrupt => 1 << 1,
Self::Quit => 1 << 2,
Self::Hangup => 1 << 3,
Self::Pipe => 1 << 4,
Self::User1 => 1 << 5,
Self::User2 => 1 << 6,
}
}
}
impl fmt::Display for Signal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.description())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SignalSet {
bits: u16,
}
impl SignalSet {
const ALL_BITS: u16 = 0b0111_1111;
#[must_use]
pub const fn empty() -> Self {
Self { bits: 0 }
}
#[must_use]
pub const fn all() -> Self {
Self {
bits: Self::ALL_BITS,
}
}
#[must_use]
pub const fn graceful() -> Self {
Self::empty()
.with(Signal::Terminate)
.with(Signal::Interrupt)
.with(Signal::Hangup)
}
#[must_use]
pub const fn standard() -> Self {
Self::graceful().with(Signal::Quit)
}
#[must_use]
pub const fn with(self, sig: Signal) -> Self {
Self {
bits: self.bits | sig.bit(),
}
}
#[must_use]
pub const fn without(self, sig: Signal) -> Self {
Self {
bits: self.bits & !sig.bit(),
}
}
#[must_use]
pub const fn contains(self, sig: Signal) -> bool {
(self.bits & sig.bit()) != 0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.bits == 0
}
#[must_use]
pub const fn len(self) -> usize {
self.bits.count_ones() as usize
}
#[must_use]
pub const fn iter(&self) -> SignalSetIter {
SignalSetIter {
set: *self,
index: 0,
}
}
}
impl Default for SignalSet {
fn default() -> Self {
Self::graceful()
}
}
impl IntoIterator for SignalSet {
type Item = Signal;
type IntoIter = SignalSetIter;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug, Clone)]
pub struct SignalSetIter {
set: SignalSet,
index: usize,
}
impl Iterator for SignalSetIter {
type Item = Signal;
fn next(&mut self) -> Option<Signal> {
while self.index < Signal::ALL.len() {
let sig = Signal::ALL[self.index];
self.index += 1;
if self.set.contains(sig) {
return Some(sig);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn signal_display_matches_description() {
for s in Signal::ALL {
assert_eq!(format!("{s}"), s.description());
}
}
#[test]
fn signal_unix_number_round_trip() {
assert_eq!(Signal::Terminate.unix_number(), Some(15));
assert_eq!(Signal::Interrupt.unix_number(), Some(2));
assert_eq!(Signal::Hangup.unix_number(), Some(1));
assert_eq!(Signal::Quit.unix_number(), Some(3));
assert_eq!(Signal::Pipe.unix_number(), Some(13));
assert_eq!(Signal::User1.unix_number(), Some(10));
assert_eq!(Signal::User2.unix_number(), Some(12));
}
#[test]
fn is_unix_only_is_correct() {
assert!(Signal::Pipe.is_unix_only());
assert!(Signal::User1.is_unix_only());
assert!(Signal::User2.is_unix_only());
assert!(!Signal::Terminate.is_unix_only());
assert!(!Signal::Interrupt.is_unix_only());
assert!(!Signal::Quit.is_unix_only());
assert!(!Signal::Hangup.is_unix_only());
}
#[test]
fn set_empty_and_all() {
assert!(SignalSet::empty().is_empty());
assert_eq!(SignalSet::empty().len(), 0);
assert_eq!(SignalSet::all().len(), 7);
}
#[test]
fn set_graceful_contents() {
let g = SignalSet::graceful();
assert!(g.contains(Signal::Terminate));
assert!(g.contains(Signal::Interrupt));
assert!(g.contains(Signal::Hangup));
assert!(!g.contains(Signal::Quit));
assert!(!g.contains(Signal::Pipe));
assert_eq!(g.len(), 3);
}
#[test]
fn set_standard_contents() {
let s = SignalSet::standard();
assert!(s.contains(Signal::Quit));
assert_eq!(s.len(), 4);
}
#[test]
fn with_without_idempotent() {
let s = SignalSet::empty()
.with(Signal::Terminate)
.with(Signal::Terminate);
assert_eq!(s.len(), 1);
let s = s.without(Signal::Terminate).without(Signal::Terminate);
assert!(s.is_empty());
}
#[test]
fn iter_canonical_order() {
let s = SignalSet::all();
let v: Vec<Signal> = s.iter().collect();
assert_eq!(v, Signal::ALL.to_vec());
}
#[test]
fn default_is_graceful() {
assert_eq!(SignalSet::default(), SignalSet::graceful());
}
}