use std::borrow::Cow;
use std::fmt::Display;
use std::num::NonZero;
use std::str::FromStr;
use yash_env::semantics::ExitStatus;
use yash_env::signal::{Name, Number, RawNumber};
use yash_env::system::Signals;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Signal {
Name(Name),
Number(RawNumber),
}
impl FromStr for Signal {
type Err = <Name as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(number) = s.parse() {
Ok(Self::Number(number))
} else {
let mut s = Cow::Borrowed(s);
if s.contains(|c: char| c.is_ascii_lowercase()) {
s.to_mut().make_ascii_uppercase();
}
Ok(Self::Name(s.parse()?))
}
}
}
impl Display for Signal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Name(name) => name.fmt(f),
Self::Number(number) => number.fmt(f),
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct UnsupportedSignal;
impl Signal {
pub fn to_number<S: Signals>(self, system: &S) -> Result<Option<Number>, UnsupportedSignal> {
match self {
Signal::Name(name) => match system.signal_number_from_name(name) {
Some(number) => Ok(Some(number)),
None => Err(UnsupportedSignal),
},
Signal::Number(number) => match NonZero::new(number) {
None => Ok(None),
Some(number) => Ok(Some(Number::from_raw_unchecked(number))),
},
}
}
#[must_use]
pub fn to_name_and_number<S: Signals>(self, system: &S) -> Option<(Cow<'static, str>, Number)> {
match self {
Signal::Name(name) => Some((name.as_string(), system.signal_number_from_name(name)?)),
Signal::Number(number) => {
ExitStatus(number).to_signal(system, false)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::num::NonZero;
use yash_env::signal::UnknownNameError;
use yash_env::system::r#virtual::VirtualSystem;
use yash_env::system::r#virtual::{SIGHUP, SIGINT, SIGRTMAX, SIGRTMIN};
#[test]
fn signal_from_str_number() {
assert_eq!("0".parse(), Ok(Signal::Number(0)));
assert_eq!("1".parse(), Ok(Signal::Number(1)));
assert_eq!("999".parse(), Ok(Signal::Number(999)));
}
#[test]
fn signal_from_str_uppercase_name() {
assert_eq!("HUP".parse(), Ok(Signal::Name(Name::Hup)));
assert_eq!("INT".parse(), Ok(Signal::Name(Name::Int)));
assert_eq!("QUIT".parse(), Ok(Signal::Name(Name::Quit)));
}
#[test]
fn signal_from_str_lowercase_name() {
assert_eq!("hup".parse(), Ok(Signal::Name(Name::Hup)));
assert_eq!("int".parse(), Ok(Signal::Name(Name::Int)));
assert_eq!("quit".parse(), Ok(Signal::Name(Name::Quit)));
}
#[test]
fn signal_from_str_mixed_case_name() {
assert_eq!("Hup".parse(), Ok(Signal::Name(Name::Hup)));
assert_eq!("iNt".parse(), Ok(Signal::Name(Name::Int)));
assert_eq!("quIT".parse(), Ok(Signal::Name(Name::Quit)));
}
#[test]
fn signal_from_str_name_with_sig_prefix() {
assert_eq!("SIGHUP".parse::<Signal>(), Err(UnknownNameError));
}
#[test]
fn signal_name_to_number_supported() {
let system = VirtualSystem::new();
assert_eq!(Signal::Name(Name::Hup).to_number(&system), Ok(Some(SIGHUP)));
assert_eq!(Signal::Name(Name::Int).to_number(&system), Ok(Some(SIGINT)));
let next = Number::from_raw_unchecked(NonZero::new(SIGRTMIN.as_raw() + 1).unwrap());
assert_eq!(
Signal::Name(Name::Rtmin(1)).to_number(&system),
Ok(Some(next))
);
let prev = Number::from_raw_unchecked(NonZero::new(SIGRTMAX.as_raw() - 1).unwrap());
assert_eq!(
Signal::Name(Name::Rtmax(-1)).to_number(&system),
Ok(Some(prev))
);
}
#[test]
fn signal_name_to_number_unsupported() {
let system = VirtualSystem::new();
assert_eq!(
Signal::Name(Name::Rtmin(-1)).to_number(&system),
Err(UnsupportedSignal)
);
assert_eq!(
Signal::Name(Name::Rtmax(1)).to_number(&system),
Err(UnsupportedSignal)
);
}
#[test]
fn signal_0_to_number() {
let system = VirtualSystem::new();
assert_eq!(Signal::Number(0).to_number(&system), Ok(None));
}
#[test]
fn signal_number_to_number() {
let system = VirtualSystem::new();
assert_eq!(
Signal::Number(SIGHUP.as_raw()).to_number(&system),
Ok(Some(SIGHUP))
);
assert_eq!(
Signal::Number(SIGINT.as_raw()).to_number(&system),
Ok(Some(SIGINT))
);
let next = Number::from_raw_unchecked(NonZero::new(SIGRTMIN.as_raw() + 1).unwrap());
assert_eq!(
Signal::Number(next.as_raw()).to_number(&system),
Ok(Some(next))
);
let prev = Number::from_raw_unchecked(NonZero::new(SIGRTMAX.as_raw() - 1).unwrap());
assert_eq!(
Signal::Number(prev.as_raw()).to_number(&system),
Ok(Some(prev))
);
assert_eq!(
Signal::Number(9999).to_number(&system),
Ok(Some(Number::from_raw_unchecked(
NonZero::new(9999).unwrap()
)))
);
}
}