brush_core/
traps.rs

1//! Facilities for configuring trap handlers.
2
3use std::str::FromStr;
4use std::{collections::HashMap, fmt::Display};
5
6use itertools::Itertools as _;
7
8use crate::{error, sys};
9
10/// Type of signal that can be trapped in the shell.
11#[derive(Clone, Copy, Eq, Hash, PartialEq)]
12pub enum TrapSignal {
13    /// A system signal.
14    Signal(sys::signal::Signal),
15    /// The `DEBUG` trap.
16    Debug,
17    /// The `ERR` trap.
18    Err,
19    /// The `EXIT` trap.
20    Exit,
21    /// The `RETURN` trp.
22    Return,
23}
24
25impl Display for TrapSignal {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.write_str(self.as_str())
28    }
29}
30
31impl TrapSignal {
32    /// Returns all possible values of [`TrapSignal`].
33    pub fn iterator() -> impl Iterator<Item = Self> {
34        const SIGNALS: &[TrapSignal] = &[TrapSignal::Debug, TrapSignal::Err, TrapSignal::Exit];
35
36        let iter = itertools::chain!(
37            SIGNALS.iter().copied(),
38            sys::signal::Signal::iterator().map(TrapSignal::Signal)
39        );
40
41        iter
42    }
43
44    /// Converts [`TrapSignal`] into its corresponding signal name as a [`&'static str`](str)
45    pub const fn as_str(self) -> &'static str {
46        match self {
47            Self::Signal(s) => s.as_str(),
48            Self::Debug => "DEBUG",
49            Self::Err => "ERR",
50            Self::Exit => "EXIT",
51            Self::Return => "RETURN",
52        }
53    }
54}
55
56/// Formats [`Iterator<Item = TrapSignal>`](TrapSignal)  to the provided writer.
57///
58/// # Arguments
59///
60/// * `f` - Any type that implements [`std::io::Write`].
61/// * `it` - An iterator over the signals that will be formatted into the `f`.
62pub fn format_signals(
63    mut f: impl std::io::Write,
64    it: impl Iterator<Item = TrapSignal>,
65) -> Result<(), error::Error> {
66    let it = it
67        .filter_map(|s| i32::try_from(s).ok().map(|n| (s, n)))
68        .sorted_by(|a, b| Ord::cmp(&a.1, &b.1))
69        .format_with("\n", |s, f| f(&format_args!("{}) {}", s.1, s.0)));
70    write!(f, "{it}")?;
71    Ok(())
72}
73
74// implement s.parse::<TrapSignal>()
75impl FromStr for TrapSignal {
76    type Err = error::Error;
77    fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
78        if let Ok(n) = s.parse::<i32>() {
79            Self::try_from(n)
80        } else {
81            Self::try_from(s)
82        }
83    }
84}
85
86// from a signal number
87impl TryFrom<i32> for TrapSignal {
88    type Error = error::Error;
89    fn try_from(value: i32) -> Result<Self, Self::Error> {
90        // NOTE: DEBUG and ERR are real-time signals, defined based on NSIG or SIGRTMAX (is not
91        // available on bsd-like systems),
92        // and don't have persistent numbers across platforms, so we skip them here.
93        Ok(match value {
94            0 => Self::Exit,
95            value => Self::Signal(
96                sys::signal::Signal::try_from(value)
97                    .map_err(|_| error::ErrorKind::InvalidSignal(value.to_string()))?,
98            ),
99        })
100    }
101}
102
103// from a signal name
104impl TryFrom<&str> for TrapSignal {
105    type Error = error::Error;
106    fn try_from(value: &str) -> Result<Self, Self::Error> {
107        #[allow(unused_mut, reason = "only mutated on some platforms")]
108        let mut s = value.to_ascii_uppercase();
109
110        Ok(match s.as_str() {
111            "DEBUG" => Self::Debug,
112            "ERR" => Self::Err,
113            "EXIT" => Self::Exit,
114            "RETURN" => Self::Return,
115            _ => {
116                // Bash compatibility:
117                // support for signal names without the `SIG` prefix, for example `HUP` -> `SIGHUP`
118                if !s.starts_with("SIG") {
119                    s.insert_str(0, "SIG");
120                }
121                sys::signal::Signal::from_str(s.as_str())
122                    .map(TrapSignal::Signal)
123                    .map_err(|_| error::ErrorKind::InvalidSignal(value.into()))?
124            }
125        })
126    }
127}
128
129/// Error type used when failing to convert a `TrapSignal` to a number.
130#[derive(Debug, Clone, Copy)]
131pub struct TrapSignalNumberError;
132
133impl TryFrom<TrapSignal> for i32 {
134    type Error = TrapSignalNumberError;
135    fn try_from(value: TrapSignal) -> Result<Self, Self::Error> {
136        Ok(match value {
137            TrapSignal::Signal(s) => s as Self,
138            TrapSignal::Exit => 0,
139            _ => return Err(TrapSignalNumberError),
140        })
141    }
142}
143
144/// Configuration for trap handlers in the shell.
145#[derive(Clone, Default)]
146pub struct TrapHandlerConfig {
147    /// Registered handlers for traps; maps signal type to command.
148    pub(crate) handlers: HashMap<TrapSignal, String>,
149    /// Current depth of the handler stack.
150    pub(crate) handler_depth: i32,
151}
152
153impl TrapHandlerConfig {
154    /// Iterates over the registered handlers for trap signals.
155    pub fn iter_handlers(&self) -> impl Iterator<Item = (TrapSignal, &str)> {
156        self.handlers
157            .iter()
158            .map(|(signal, cmd)| (*signal, cmd.as_str()))
159    }
160
161    /// Tries to find the handler associated with the given signal.
162    ///
163    /// # Arguments
164    ///
165    /// * `signal_type` - The type of signal to get the handler for.
166    pub fn get_handler(&self, signal_type: TrapSignal) -> Option<&str> {
167        self.handlers.get(&signal_type).map(|s| s.as_str())
168    }
169
170    /// Registers a handler for a trap signal.
171    ///
172    /// # Arguments
173    ///
174    /// * `signal_type` - The type of signal to register a handler for.
175    /// * `command` - The command to execute when the signal is trapped.
176    pub fn register_handler(&mut self, signal_type: TrapSignal, command: String) {
177        let _ = self.handlers.insert(signal_type, command);
178    }
179
180    /// Removes handlers for a trap signal.
181    ///
182    /// # Arguments
183    ///
184    /// * `signal_type` - The type of signal to remove handlers for.
185    pub fn remove_handlers(&mut self, signal_type: TrapSignal) {
186        self.handlers.remove(&signal_type);
187    }
188}