From cfff77043e95df8b02b9f3242e42b87d0d94249a Mon Sep 17 00:00:00 2001 From: Hugues Morisset Date: Sun, 3 Jan 2021 04:09:13 +0100 Subject: [PATCH] feat(status): Convert known status code to their meaning (#1948) User is able to choose if their want to display the meaning of known status code in place of number. --- docs/config/README.md | 38 ++++--- src/configs/status.rs | 12 +++ src/modules/status.rs | 239 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 275 insertions(+), 14 deletions(-) diff --git a/docs/config/README.md b/docs/config/README.md index 02381655..c4bb4bcd 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -2262,20 +2262,31 @@ To enable it, set `disabled` to `false` in your configuration file. ### Options -| Option | Default | Description | -| ---------- | --------------------------- | ------------------------------------------------------ | -| `format` | `[$symbol$status]($style) ` | The format of the module | -| `symbol` | `"✖"` | A format string representing the symbol for the status | -| `style` | `"bold red"` | The style for the module. | -| `disabled` | `true` | Disables the `status` module. | +| Option | Default | Description | +| ------------------------- | --------------------------- | ------------------------------------------------------ | +| `format` | `[$symbol$status]($style) ` | The format of the module | +| `symbol` | `"✖"` | The symbol displayed on program error | +| `not_executable_symbol` | `"🚫"` | The symbol displayed when file isn't executable | +| `not_found_symbol` | `"🔍"` | The symbol displayed when the command can't be found | +| `sigint_symbol` | `"🧱"` | The symbol displayed on SIGINT (Ctrl + c) | +| `signal_symbol` | `"⚡"` | The symbol displayed on any signal | +| `style` | `"bold red"` | The style for the module. | +| `recognize_signal_code` | `true` | Enable signal mapping from exit code | +| `map_symbol` | `false` | Enable symbols mapping from exit code | +| `disabled` | `true` | Disables the `status` module. | ### Variables -| Variable | Example | Description | -| -------- | ------- | ------------------------------------ | -| status | `127` | The exit code of the last command | -| symbol | | Mirrors the value of option `symbol` | -| style\* | | Mirrors the value of option `style` | +| Variable | Example | Description | +| ----------------------- | ------- | ----------------------------------------------------------------------- | +| status | `127` | The exit code of the last command | +| int | `127` | The exit code of the last command | +| common_meaning | `ERROR` | Meaning of the code if not a signal | +| signal_number | `9` | Signal number corresponding to the exit code, only if signalled | +| signal_name | `KILL` | Name of the signal corresponding to the exit code, only if signalled | +| maybe_int | `7` | Contains the exit code number when no meaning has been found | +| symbol | | Mirrors the value of option `symbol` | +| style\* | | Mirrors the value of option `style` | \*: This variable can only be used as a part of a style string @@ -2287,8 +2298,9 @@ To enable it, set `disabled` to `false` in your configuration file. [status] style = "bg:blue" -symbol = "💣 " -format = '[\[$symbol$status\]]($style) ' +symbol = "🔴" +format = '[\[$symbol $status_common_meaning$status_signal_name$status_maybe_int\]]($style) ' +map_symbol = true disabled = false ``` diff --git a/src/configs/status.rs b/src/configs/status.rs index dde8766c..567e3ad6 100644 --- a/src/configs/status.rs +++ b/src/configs/status.rs @@ -6,7 +6,13 @@ use starship_module_config_derive::ModuleConfig; pub struct StatusConfig<'a> { pub format: &'a str, pub symbol: &'a str, + pub not_executable_symbol: &'a str, + pub not_found_symbol: &'a str, + pub sigint_symbol: &'a str, + pub signal_symbol: &'a str, pub style: &'a str, + pub map_symbol: bool, + pub recognize_signal_code: bool, pub disabled: bool, } @@ -15,7 +21,13 @@ impl<'a> RootModuleConfig<'a> for StatusConfig<'a> { StatusConfig { format: "[$symbol$status]($style) ", symbol: "✖", + not_executable_symbol: "🚫", + not_found_symbol: "🔍", + sigint_symbol: "🧱", + signal_symbol: "⚡", style: "bold red", + map_symbol: false, + recognize_signal_code: true, disabled: true, } } diff --git a/src/modules/status.rs b/src/modules/status.rs index d985864a..024f2ad9 100644 --- a/src/modules/status.rs +++ b/src/modules/status.rs @@ -3,6 +3,9 @@ use super::{Context, Module, RootModuleConfig}; use crate::configs::status::StatusConfig; use crate::formatter::StringFormatter; +type ExitCode = i64; +type SignalNumber = u32; + /// Creates a module with the status of the last command /// /// Will display the status only if it is not 0 @@ -24,10 +27,44 @@ pub fn module<'a>(context: &'a Context) -> Option> { return None; }; + let exit_code_int: ExitCode = match exit_code.parse() { + Ok(i) => i, + Err(_) => return None, + }; + + let common_meaning = status_common_meaning(exit_code_int); + + let raw_signal_number = match config.recognize_signal_code { + true => status_to_signal(exit_code_int), + false => None, + }; + let signal_number = raw_signal_number.map(|sn| sn.to_string()); + let signal_name = raw_signal_number + .and_then(|sn| status_signal_name(sn).or_else(|| signal_number.as_deref())); + + // If not a signal and not a common meaning, it should at least print the raw exit code number + let maybe_exit_code_number = match common_meaning.is_none() && signal_name.is_none() { + true => Some(exit_code), + false => None, + }; + let parsed = StringFormatter::new(config.format).and_then(|formatter| { formatter .map_meta(|var, _| match var { - "symbol" => Some(config.symbol), + "symbol" => match exit_code_int { + 126 if config.map_symbol => Some(config.not_executable_symbol), + 127 if config.map_symbol => Some(config.not_found_symbol), + 130 if config.recognize_signal_code && config.map_symbol => { + Some(config.sigint_symbol) + } + x if (129..256).contains(&x) + && config.recognize_signal_code + && config.map_symbol => + { + Some(config.signal_symbol) + } + _ => Some(config.symbol), + }, _ => None, }) .map_style(|variable| match variable { @@ -36,6 +73,11 @@ pub fn module<'a>(context: &'a Context) -> Option> { }) .map(|variable| match variable { "status" => Some(Ok(exit_code)), + "int" => Some(Ok(exit_code)), + "maybe_int" => Ok(maybe_exit_code_number.as_deref()).transpose(), + "common_meaning" => Ok(common_meaning.as_deref()).transpose(), + "signal_number" => Ok(signal_number.as_deref()).transpose(), + "signal_name" => Ok(signal_name.as_deref()).transpose(), _ => None, }) .parse(None) @@ -52,6 +94,56 @@ pub fn module<'a>(context: &'a Context) -> Option> { } } +fn status_common_meaning(ex: ExitCode) -> Option<&'static str> { + // Over 128 are Signal exit code + if ex > 128 { + return None; + } + match ex { + 1 => Some("ERROR"), + 2 => Some("USAGE"), + 126 => Some("NOPERM"), + 127 => Some("NOTFOUND"), + _ => None, + } +} + +fn status_to_signal(ex: ExitCode) -> Option { + if ex < 129 { + return None; + } + let sn = ex - 128; + Some(sn as u32) +} + +fn status_signal_name(signal: SignalNumber) -> Option<&'static str> { + match signal { + 1 => Some("HUP"), // 128 + 1 + 2 => Some("INT"), // 128 + 2 + 3 => Some("QUIT"), // 128 + 3 + 4 => Some("ILL"), // 128 + 4 + 5 => Some("TRAP"), // 128 + 5 + 6 => Some("IOT"), // 128 + 6 + 7 => Some("BUS"), // 128 + 7 + 8 => Some("FPE"), // 128 + 8 + 9 => Some("KILL"), // 128 + 9 + 10 => Some("USR1"), // 128 + 10 + 11 => Some("SEGV"), // 128 + 11 + 12 => Some("USR2"), // 128 + 12 + 13 => Some("PIPE"), // 128 + 13 + 14 => Some("ALRM"), // 128 + 14 + 15 => Some("TERM"), // 128 + 15 + 16 => Some("STKFLT"), // 128 + 16 + 17 => Some("CHLD"), // 128 + 17 + 18 => Some("CONT"), // 128 + 18 + 19 => Some("STOP"), // 128 + 19 + 20 => Some("TSTP"), // 128 + 20 + 21 => Some("TTIN"), // 128 + 21 + 22 => Some("TTOU"), // 128 + 22 + _ => None, + } +} + #[cfg(test)] mod tests { use ansi_term::Color; @@ -106,6 +198,151 @@ mod tests { let actual = ModuleRenderer::new("status") .config(toml::toml! { [status] + symbol = "✖" + disabled = false + }) + .status(*status) + .collect(); + assert_eq!(expected, actual); + } + + Ok(()) + } + + #[test] + fn signal_name() -> io::Result<()> { + let exit_values = [1, 2, 126, 127, 130, 101]; + let exit_values_name = [ + Some("ERROR"), + Some("USAGE"), + Some("NOPERM"), + Some("NOTFOUND"), + Some("INT"), + None, + ]; + + for (status, name) in exit_values.iter().zip(exit_values_name.iter()) { + let expected = name.map(|n| n.to_string()); + let actual = ModuleRenderer::new("status") + .config(toml::toml! { + [status] + format = "$common_meaning$signal_name" + disabled = false + }) + .status(*status) + .collect(); + assert_eq!(expected, actual); + } + + Ok(()) + } + + #[test] + fn exit_code_name_no_signal() -> io::Result<()> { + let exit_values = [1, 2, 126, 127, 130, 101, 132]; + let exit_values_name = [ + Some("ERROR"), + Some("USAGE"), + Some("NOPERM"), + Some("NOTFOUND"), + None, + None, + None, + ]; + + for (status, name) in exit_values.iter().zip(exit_values_name.iter()) { + let expected = name.map(|n| n.to_string()); + let actual = ModuleRenderer::new("status") + .config(toml::toml! { + [status] + format = "$common_meaning$signal_name" + recognize_signal_code = false + disabled = false + }) + .status(*status) + .collect(); + assert_eq!(expected, actual); + } + + Ok(()) + } + + #[test] + fn maybe_exit_code_number() -> io::Result<()> { + let exit_values = [1, 2, 126, 127, 130, 101, 6, -3]; + let exit_values_name = [ + None, + None, + None, + None, + None, + Some("101"), + Some("6"), + Some("-3"), + ]; + + for (status, name) in exit_values.iter().zip(exit_values_name.iter()) { + let expected = name.map(|n| n.to_string()); + let actual = ModuleRenderer::new("status") + .config(toml::toml! { + [status] + format = "$maybe_int" + disabled = false + }) + .status(*status) + .collect(); + assert_eq!(expected, actual); + } + + Ok(()) + } + + #[test] + fn special_symbols() -> io::Result<()> { + let exit_values = [1, 126, 127, 130, 131]; + let exit_values_name = ["🔴", "🚫", "🔍", "🧱", "⚡"]; + + for (status, name) in exit_values.iter().zip(exit_values_name.iter()) { + let expected = Some(name.to_string()); + let actual = ModuleRenderer::new("status") + .config(toml::toml! { + [status] + format = "$symbol" + symbol = "🔴" + not_executable_symbol = "🚫" + not_found_symbol = "🔍" + sigint_symbol = "🧱" + signal_symbol = "⚡" + recognize_signal_code = true + map_symbol = true + disabled = false + }) + .status(*status) + .collect(); + assert_eq!(expected, actual); + } + + Ok(()) + } + + #[test] + fn special_symbols_no_signals() -> io::Result<()> { + let exit_values = [1, 126, 127, 130, 131]; + let exit_values_name = ["🔴", "🚫", "🔍", "🔴", "🔴"]; + + for (status, name) in exit_values.iter().zip(exit_values_name.iter()) { + let expected = Some(name.to_string()); + let actual = ModuleRenderer::new("status") + .config(toml::toml! { + [status] + format = "$symbol" + symbol = "🔴" + not_executable_symbol = "🚫" + not_found_symbol = "🔍" + sigint_symbol = "🧱" + signal_symbol = "⚡" + recognize_signal_code = false + map_symbol = true disabled = false }) .status(*status)