Skip to content

Commit

Permalink
feat(env_var): Add support for env_var.VAR in format (#4497)
Browse files Browse the repository at this point in the history
Co-Authored-By: Segev Finer <24731903+segevfiner@users.noreply.github.com>

Co-authored-by: Segev Finer <24731903+segevfiner@users.noreply.github.com>
  • Loading branch information
davidkna and segevfiner committed Dec 28, 2022
1 parent f183a4e commit 5d4cb6f
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 172 deletions.
4 changes: 4 additions & 0 deletions .github/config-schema.json
Expand Up @@ -2834,6 +2834,10 @@
"disabled": {
"default": false,
"type": "boolean"
},
"description": {
"default": "<env_var module>",
"type": "string"
}
},
"additionalProperties": false
Expand Down
23 changes: 16 additions & 7 deletions docs/config/README.md
Expand Up @@ -1380,6 +1380,14 @@ The module will be shown only if any of the following conditions are met:

::: tip

The order in which env_var modules are shown can be individually set by including
`${env_var.foo}` in the top level `format` (as it includes a dot, you need to use `${...}`).
By default, the `env_var` module will simply show all env_var modules in the order they were defined.

:::

::: tip

Multiple environmental variables can be displayed by using a `.`. (see example)
If the `variable` configuration option is not set, the module will display value of variable under the name of text after the `.` character.

Expand All @@ -1396,13 +1404,14 @@ default = 'unknown user'

### Options

| Option | Default | Description |
| ---------- | ------------------------------ | ---------------------------------------------------------------------------- |
| `symbol` | `''` | The symbol used before displaying the variable value. |
| `variable` | | The environment variable to be displayed. |
| `default` | | The default value to be displayed when the selected variable is not defined. |
| `format` | `'with [$env_value]($style) '` | The format for the module. |
| `disabled` | `false` | Disables the `env_var` module. |
| Option | Default | Description |
| ------------- | ------------------------------ | ---------------------------------------------------------------------------- |
| `symbol` | `""` | The symbol used before displaying the variable value. |
| `variable` | | The environment variable to be displayed. |
| `default` | | The default value to be displayed when the selected variable is not defined. |
| `format` | `"with [$env_value]($style) "` | The format for the module. |
| `description` | `"<env_var module>"` | The description of the module that is shown when running `starship explain`. |
| `disabled` | `false` | Disables the `env_var` module. |

### Variables

Expand Down
2 changes: 2 additions & 0 deletions src/configs/env_var.rs
Expand Up @@ -16,6 +16,7 @@ pub struct EnvVarConfig<'a> {
pub default: Option<&'a str>,
pub format: &'a str,
pub disabled: bool,
pub description: &'a str,
}

impl<'a> Default for EnvVarConfig<'a> {
Expand All @@ -27,6 +28,7 @@ impl<'a> Default for EnvVarConfig<'a> {
default: None,
format: "with [$env_value]($style) ",
disabled: false,
description: "<env_var module>",
}
}
}
9 changes: 0 additions & 9 deletions src/context.rs
Expand Up @@ -238,15 +238,6 @@ impl<'a> Context<'a> {
disabled == Some(true)
}

/// Return whether the specified custom module has a `disabled` option set to true.
/// If it doesn't exist, `None` is returned.
pub fn is_custom_module_disabled_in_config(&self, name: &str) -> Option<bool> {
let config = self.config.get_custom_module_config(name)?;
let disabled = Some(config).and_then(|table| table.as_table()?.get("disabled")?.as_bool());

Some(disabled == Some(true))
}

// returns a new ScanDir struct with reference to current dir_files of context
// see ScanDir for methods
pub fn try_begin_scan(&'a self) -> Option<ScanDir<'a>> {
Expand Down
1 change: 0 additions & 1 deletion src/module.rs
Expand Up @@ -31,7 +31,6 @@ pub const ALL_MODULES: &[&str] = &[
"dotnet",
"elixir",
"elm",
"env_var",
"erlang",
"fennel",
"fill",
Expand Down
125 changes: 83 additions & 42 deletions src/modules/custom.rs
@@ -1,9 +1,9 @@
use std::env;
use std::fmt::{self, Debug};
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
use std::time::Duration;
use std::time::Instant;

use process_control::{ChildExt, Control, Output};

Expand All @@ -22,19 +22,20 @@ use crate::{
///
/// Finally, the content of the module itself is also set by a command.
pub fn module<'a>(name: &str, context: &'a Context) -> Option<Module<'a>> {
let start: Instant = Instant::now();
let toml_config = context.config.get_custom_module_config(name).expect(
"modules::custom::module should only be called after ensuring that the module exists",
);
let toml_config = get_config(name, context)?;
let config = CustomConfig::load(toml_config);
if config.disabled {
return None;
}

if let Some(os) = config.os {
if os != env::consts::OS && !(os == "unix" && cfg!(unix)) {
return None;
}
}

let mut module = Module::new(name, config.description, Some(toml_config));
// Note: Forward config if `Module` ends up needing `config`
let mut module = Module::new(&format!("custom.{name}"), config.description, None);

let mut is_match = context
.try_begin_scan()?
Expand All @@ -48,48 +49,74 @@ pub fn module<'a>(name: &str, context: &'a Context) -> Option<Module<'a>> {
Either::First(b) => b,
Either::Second(s) => exec_when(s, &config, context),
};

if !is_match {
return None;
}
}

if is_match {
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_no_escaping(|variable| match variable {
"output" => {
let output = exec_command(config.command, context, &config)?;
let trimmed = output.trim();

if trimmed.is_empty() {
None
} else {
Some(Ok(trimmed.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_no_escaping(|variable| match variable {
"output" => {
let output = exec_command(config.command, context, &config)?;
let trimmed = output.trim();

if trimmed.is_empty() {
None
} else {
Some(Ok(trimmed.to_string()))
}
_ => None,
})
.parse(None, Some(context))
});

match parsed {
Ok(segments) => module.set_segments(segments),
Err(error) => {
log::warn!("Error in module `custom.{}`:\n{}", name, error);
}
};
}
let elapsed = start.elapsed();
log::trace!("Took {:?} to compute custom module {:?}", elapsed, name);
module.duration = elapsed;
}
_ => None,
})
.parse(None, Some(context))
});

match parsed {
Ok(segments) => module.set_segments(segments),
Err(error) => {
log::warn!("Error in module `custom.{}`:\n{}", name, error);
}
};
Some(module)
}

/// Gets the TOML config for the custom module, handling the case where the module is not defined
fn get_config<'a>(module_name: &str, context: &'a Context<'a>) -> Option<&'a toml::Value> {
struct DebugCustomModules<'tmp>(&'tmp toml::value::Table);

impl Debug for DebugCustomModules<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_list().entries(self.0.keys()).finish()
}
}

let config = context.config.get_custom_module_config(module_name);

if config.is_some() {
return config;
} else if let Some(modules) = context.config.get_custom_modules() {
log::debug!(
"top level format contains custom module {module_name:?}, but no configuration was provided. Configuration for the following modules were provided: {:?}",
DebugCustomModules(modules),
);
} else {
log::debug!(
"top level format contains custom module {module_name:?}, but no configuration was provided.",
);
};
None
}

/// Return the invoking shell, using `shell` and fallbacking in order to `STARSHIP_SHELL` and "sh"/"cmd"
fn get_shell<'a, 'b>(
shell_args: &'b [&'a str],
Expand Down Expand Up @@ -680,4 +707,18 @@ mod tests {

dir.close()
}

#[test]
fn disabled() {
let actual = ModuleRenderer::new("custom.test")
.config(toml::toml! {
[custom.test]
disabled = true
when = true
format = "test"
})
.collect();
let expected = None;
assert_eq!(expected, actual);
}
}

0 comments on commit 5d4cb6f

Please sign in to comment.