diff --git a/docs/config/README.md b/docs/config/README.md index 253e6214..283d7eed 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -178,6 +178,7 @@ format = """ $username\ $hostname\ +$shlvl\ $kubernetes\ $directory\ $git_branch\ @@ -1985,6 +1986,42 @@ The module will be shown if any of the following conditions are met: format = "via [⚙️ $version](red bold)" ``` +## SHLVL + +The `shlvl` module shows the current SHLVL ("shell level") environment variable, if it is +set to a number and meets or exceeds the specified threshold. + +### Options + +| Variable | Default | Description | +| ----------- | ---------------------------- | ------------------------------------------------ | +| `threshold` | `2` | Display threshold. | +| `format` | `"[$symbol$shlvl]($style) "` | The format for the module. | +| `symbol` | `"↕️ "` | The symbol used to represent the SHLVL. | +| `style` | `"bold yellow"` | The style for the module. | +| `disabled` | `true` | Disables the `shlvl` module. | + +### Variables + +| Variable | Example | Description | +| -------- | --------- | ------------------------------------ | +| shlvl | `3` | The current value of SHLVL | +| 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 + +### Example + +```toml +# ~/.config/starship.toml + +[shlvl] +disabled = false +format = "$shlvl level(s) down" +threshold = 3 +``` + ## Singularity The `singularity` module shows the current singularity image, if inside a container diff --git a/src/configs/mod.rs b/src/configs/mod.rs index 0f6d5f85..1d961b3a 100644 --- a/src/configs/mod.rs +++ b/src/configs/mod.rs @@ -39,6 +39,7 @@ pub mod purescript; pub mod python; pub mod ruby; pub mod rust; +pub mod shlvl; pub mod singularity; mod starship_root; pub mod swift; diff --git a/src/configs/shlvl.rs b/src/configs/shlvl.rs new file mode 100644 index 00000000..82dc6cda --- /dev/null +++ b/src/configs/shlvl.rs @@ -0,0 +1,24 @@ +use crate::config::{ModuleConfig, RootModuleConfig}; + +use starship_module_config_derive::ModuleConfig; + +#[derive(Clone, ModuleConfig)] +pub struct ShLvlConfig<'a> { + pub threshold: i64, + pub format: &'a str, + pub symbol: &'a str, + pub style: &'a str, + pub disabled: bool, +} + +impl<'a> RootModuleConfig<'a> for ShLvlConfig<'a> { + fn new() -> Self { + ShLvlConfig { + threshold: 2, + format: "[$symbol$shlvl]($style) ", + symbol: "↕️ ", // extra space for emoji + style: "bold yellow", + disabled: true, + } + } +} diff --git a/src/configs/starship_root.rs b/src/configs/starship_root.rs index b5e913d8..ad507c4d 100644 --- a/src/configs/starship_root.rs +++ b/src/configs/starship_root.rs @@ -14,6 +14,7 @@ pub struct StarshipRootConfig<'a> { pub const PROMPT_ORDER: &[&str] = &[ "username", "hostname", + "shlvl", "singularity", "kubernetes", "directory", diff --git a/src/module.rs b/src/module.rs index 8292cb99..e8d40f66 100644 --- a/src/module.rs +++ b/src/module.rs @@ -52,6 +52,7 @@ pub const ALL_MODULES: &[&str] = &[ "php", "swift", "terraform", + "shlvl", "singularity", "time", "username", diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 55d54854..6de38c4a 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -40,6 +40,7 @@ mod purescript; mod python; mod ruby; mod rust; +mod shlvl; mod singularity; mod swift; mod terraform; @@ -100,6 +101,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option> { "python" => python::module(context), "ruby" => ruby::module(context), "rust" => rust::module(context), + "shlvl" => shlvl::module(context), "singularity" => singularity::module(context), "swift" => swift::module(context), "terraform" => terraform::module(context), @@ -158,6 +160,7 @@ pub fn description(module: &str) -> &'static str { "ruby" => "The currently installed version of Ruby", "rust" => "The currently installed version of Rust", "swift" => "The currently installed version of Swift", + "shlvl" => "The current value of SHLVL", "terraform" => "The currently selected terraform workspace and version", "time" => "The current local time", "username" => "The active user's username", diff --git a/src/modules/shlvl.rs b/src/modules/shlvl.rs new file mode 100644 index 00000000..19fb735a --- /dev/null +++ b/src/modules/shlvl.rs @@ -0,0 +1,53 @@ +use std::env; + +use super::{Context, Module}; + +use crate::config::RootModuleConfig; +use crate::configs::shlvl::ShLvlConfig; +use crate::formatter::StringFormatter; + +const SHLVL_ENV_VAR: &str = "SHLVL"; + +pub fn module<'a>(context: &'a Context) -> Option> { + let shlvl = get_shlvl_value()?; + + let mut module = context.new_module("shlvl"); + let config: ShLvlConfig = ShLvlConfig::try_load(module.config); + + if config.disabled || shlvl < config.threshold { + return None; + } + + let shlvl_str = &shlvl.to_string(); + + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|var, _| match var { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "shlvl" => Some(Ok(shlvl_str)), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `shlvl`:\n{}", error); + return None; + } + }); + + Some(module) +} + +fn get_shlvl_value() -> Option { + env::var(SHLVL_ENV_VAR).ok()?.parse::().ok() +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 6c59fa68..558a867e 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -18,6 +18,7 @@ mod jobs; mod modules; mod nix_shell; mod python; +mod shlvl; mod singularity; mod terraform; mod time; diff --git a/tests/testsuite/shlvl.rs b/tests/testsuite/shlvl.rs new file mode 100644 index 00000000..5a5d299e --- /dev/null +++ b/tests/testsuite/shlvl.rs @@ -0,0 +1,159 @@ +use ansi_term::{Color, Style}; +use std::io; + +use crate::common; +use crate::common::TestCommand; + +const SHLVL_ENV_VAR: &str = "SHLVL"; + +fn style() -> Style { + // default style + Color::Yellow.bold() +} + +#[test] +fn empty_config() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + }) + .env(SHLVL_ENV_VAR, "2") + .output()?; + let expected = ""; + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn enabled() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .output()?; + let expected = format!("{} ", style().paint("↕️ 2")); + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn no_level() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + disabled = false + }) + .output()?; + let expected = ""; + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn enabled_config_level_1() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + disabled = false + }) + .env(SHLVL_ENV_VAR, "1") + .output()?; + let expected = ""; + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn lower_threshold() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + threshold = 1 + disabled = false + }) + .env(SHLVL_ENV_VAR, "1") + .output()?; + let expected = format!("{} ", style().paint("↕️ 1")); + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn higher_threshold() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + threshold = 3 + disabled = false + }) + .env(SHLVL_ENV_VAR, "1") + .output()?; + let expected = ""; + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn custom_style() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + style = "Red Underline" + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .output()?; + let expected = format!("{} ", Color::Red.underline().paint("↕️ 2")); + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn custom_symbol() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + symbol = "shlvl is " + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .output()?; + let expected = format!("{} ", style().paint("shlvl is 2")); + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +} + +#[test] +fn formatting() -> io::Result<()> { + let output = common::render_module("shlvl") + .env_clear() + .use_config(toml::toml! { + [shlvl] + format = "$symbol going down [$shlvl]($style) GOING UP " + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .output()?; + let expected = format!("↕️ going down {} GOING UP ", style().paint("2")); + let actual = String::from_utf8(output.stdout).unwrap(); + assert_eq!(expected, actual); + Ok(()) +}