yash_env/trap/
cond.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Items that define trap conditions
18
19use super::SignalSystem;
20#[cfg(doc)]
21use super::state::Action;
22use crate::signal;
23use itertools::Itertools as _;
24use std::borrow::Cow;
25use std::num::NonZero;
26
27/// Condition under which an [`Action`] is executed
28#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29#[non_exhaustive]
30pub enum Condition {
31    /// When the shell exits
32    Exit,
33    /// When the specified signal is delivered to the shell process
34    Signal(signal::Number),
35}
36
37impl From<signal::Number> for Condition {
38    fn from(number: signal::Number) -> Self {
39        Self::Signal(number)
40    }
41}
42
43/// Conversion from raw signal number to `Condition`
44///
45/// If the number is zero, the result is [`Condition::Exit`]. Otherwise, the
46/// result is [`Condition::Signal`] with the signal number.
47impl From<signal::RawNumber> for Condition {
48    fn from(number: signal::RawNumber) -> Self {
49        if let Ok(non_zero) = number.try_into() {
50            Self::Signal(signal::Number::from_raw_unchecked(non_zero))
51        } else {
52            Self::Exit
53        }
54    }
55}
56
57impl From<Condition> for signal::RawNumber {
58    fn from(cond: Condition) -> Self {
59        match cond {
60            Condition::Exit => 0,
61            Condition::Signal(number) => number.as_raw(),
62        }
63    }
64}
65
66impl Condition {
67    /// Converts this `Condition` to a `String`.
68    ///
69    /// The result is an uppercase string representing the condition such as
70    /// `"EXIT"` and `"TERM"`. Signal names are obtained from
71    /// [`signal::Name::as_string`]. This function depends on the signal system
72    /// to convert signal numbers to names.
73    #[must_use]
74    pub fn to_string<S: SignalSystem>(&self, system: &S) -> Cow<'static, str> {
75        match self {
76            Self::Exit => Cow::Borrowed("EXIT"),
77            Self::Signal(number) => system.signal_name_from_number(*number).as_string(),
78        }
79    }
80
81    /// Returns an iterator over all possible conditions.
82    ///
83    /// The iterator yields all the conditions supported by the given signal
84    /// system. The first condition is [`Condition::Exit`], followed by all the
85    /// signals in the ascending order of their signal numbers.
86    // TODO Most part of this function is duplicated from yash_builtin::kill::print::all_signals.
87    // Consider refactoring to share the code. Note that all_signals requires a System
88    // while this function requires a SignalSystem. Also note that all_signals does not
89    // deduplicate the signals.
90    pub fn iter<S: SignalSystem>(system: &S) -> impl Iterator<Item = Condition> + '_ {
91        let names = signal::Name::iter();
92        let non_real_time_count = names.len() - 2;
93        let non_real_time = names
94            .filter(|name| !matches!(name, signal::Name::Rtmin(_) | signal::Name::Rtmax(_)))
95            .filter_map(|name| Some(Condition::Signal(system.signal_number_from_name(name)?)));
96
97        let rtmin = system.signal_number_from_name(signal::Name::Rtmin(0));
98        let rtmax = system.signal_number_from_name(signal::Name::Rtmax(0));
99        let range = if let (Some(rtmin), Some(rtmax)) = (rtmin, rtmax) {
100            rtmin.as_raw()..=rtmax.as_raw()
101        } else {
102            #[allow(clippy::reversed_empty_ranges)]
103            {
104                0..=-1
105            }
106        };
107        let real_time_count = range.size_hint().1.unwrap_or_default();
108        let real_time = range.into_iter().map(|n| {
109            Condition::Signal(signal::Number::from_raw_unchecked(NonZero::new(n).unwrap()))
110        });
111
112        let mut conditions = Vec::with_capacity(1 + non_real_time_count + real_time_count);
113        conditions.push(Condition::Exit);
114        conditions.extend(non_real_time);
115        conditions.extend(real_time);
116        conditions.sort();
117        // Some names may share the same number, so deduplicate.
118        conditions.into_iter().dedup()
119    }
120}