feat: allow loading configuration values from the environment

This commit is contained in:
David Knaack 2022-03-02 16:01:54 +01:00
parent 36134d896b
commit bf0c17b600
3 changed files with 174 additions and 3 deletions

View File

@ -6552,4 +6552,4 @@
]
}
}
}
}

View File

@ -44,6 +44,25 @@ Or for Cmd (Windows) would be adding this line to your `starship.lua`:
os.setenv('STARSHIP_CONFIG', 'C:\\Users\\user\\example\\non\\default\\path\\starship.toml')
```
### Loading additional configuration from the environment
In addition to the configuration file, starship also allows you to set configuration values by using environment variables. The variables should be formatted like this: `STARSHIP_CONFIG__SECTION_NAME__KEY=value`. The configuration keys in the environment need to start with `STARSHIP_CONFIG__` and table keys need to be separated with `__`.
In most POSIX-like shells, you can set the `disabled` key of the `status` module to `false` with the following:
```sh
export STARSHIP_CONFIG__STATUS__DISABLED=false
```
Equivalently in PowerShell (Windows) would be adding this line:
```powershell
$ENV:STARSHIP_CONFIG__STATUS__DISABLED = "false"
```
Other shells may use a different syntax to set environment variables.
At this time, only simple values like such as numbers, floats, booleans and strings can be set with environment variables. This method cannot be used to set complex values like tables or arrays. If the environment values cannot be directly parsed as TOML values, they are treated as strings. This means that `true` is parsed as a boolean when it's not quoted, but is parsed as a string when it is quoted. In contrast, when a value matches neither a number, float, nor boolean like `starship` is found, it is treated as a string.
### Logging
By default starship logs warnings and errors into a file named `~/.cache/starship/session_${STARSHIP_SESSION_KEY}.log`, where the session key is corresponding to an instance of your terminal.

View File

@ -124,11 +124,18 @@ pub struct StarshipConfig {
impl StarshipConfig {
/// Initialize the Config struct
pub fn initialize(config_file_path: &Option<OsString>) -> Self {
Self::config_from_file(config_file_path)
#[cfg_attr(test, allow(unused_mut))]
let mut out = Self::config_from_file(config_file_path)
.map(|config| Self {
config: Some(config),
})
.unwrap_or_default()
.unwrap_or_default();
// Avoid tainting config in tests
#[cfg(not(test))]
out.load_from_env();
out
}
/// Create a config from a starship configuration file
@ -173,6 +180,68 @@ impl StarshipConfig {
}
}
/// Load additional config values from the environment
/// Variables are prefixed with `STARSHIP_` and delimited with `__`
/// e.g. `STARSHIP_CONFIG__JAVA__DISABLED`
pub fn load_from_env(&mut self) {
for (key, value) in std::env::vars() {
if let Some(name) = key.strip_prefix("STARSHIP_CONFIG__") {
log::debug!("Loading config from environment: {name}={value}");
if let Err(e) = self.load_key_from_env(name, &value) {
log::error!("Unable to load config {name}={value} from environment: {e}");
}
}
}
log::debug!("Config with env: {:?}", &self.config);
}
/// Load a config value from a `__` delimited uppercase key name (environment variable)
pub fn load_key_from_env(&mut self, name: &str, value: &str) -> Result<(), String> {
let config = match self.config.as_mut() {
Some(c) => c,
None => {
self.config = Some(toml::value::Table::new());
self.config.as_mut().unwrap()
}
};
let mut keys = name.split("__").map(str::to_ascii_lowercase);
let first_key = match keys.next() {
Some(key) if !key.is_empty() => key,
_ => return Err("Empty table keys are not supported.".to_owned()),
};
let mut current_item = config
.entry(first_key)
.or_insert(toml::Value::Table(toml::value::Table::new()));
for key in keys {
if key.is_empty() {
return Err("Empty table keys are not supported.".to_owned());
}
let table = match current_item.as_table_mut() {
Some(t) => t,
None => return Err(format!("{key} is not a table.")),
};
if !table.contains_key(&key) {
table.insert(key.clone(), toml::Value::Table(toml::value::Table::new()));
}
current_item = table.get_mut(&key).unwrap();
}
let new_value = parse_toml_value(value);
log::trace!("Setting config value: {:?}", &new_value);
*current_item = new_value;
Ok(())
}
/// Get the subset of the table for a module by its name
pub fn get_module_config(&self, module_name: &str) -> Option<&Value> {
let module_config = self.get_config(&[module_name]);
@ -508,6 +577,24 @@ fn parse_color_string(
predefined_color
}
/// Parses a string as a simple TOML value (String, Integer, etc.)
/// TODO: support complex values like arrays/tables?
fn parse_toml_value(value: &str) -> Value {
use toml_edit::Value as EValue;
if let Ok(t) = value.parse::<EValue>() {
// Support for parsing quoted values, to allow parsing "true" as a string
match t {
EValue::String(s) => return Value::String(s.into_value()),
EValue::Integer(s) => return Value::Integer(s.into_value()),
EValue::Float(s) => return Value::Float(s.into_value()),
EValue::Boolean(s) => return Value::Boolean(s.into_value()),
_ => (),
}
};
Value::String(value.to_owned())
}
fn get_palette<'a>(
palettes: &'a HashMap<String, Palette>,
palette_name: Option<&str>,
@ -994,6 +1081,71 @@ mod tests {
);
}
fn test_config() -> StarshipConfig {
StarshipConfig {
config: Some(toml::toml! {
[status]
disabled = false
}),
}
}
#[test]
fn test_env_config_wrong_type() {
let mut cfg = test_config();
assert!(cfg
.load_key_from_env("status__disabled__not_a_table", "true")
.is_err());
assert!(cfg.config.unwrap()["status"]["disabled"].is_bool(),);
}
#[test]
fn test_env_config_simple() {
let mut cfg = test_config();
assert!(!cfg.config.as_ref().unwrap()["status"]["disabled"]
.as_bool()
.unwrap());
cfg.load_key_from_env("status__disabled", "true").unwrap();
assert!(cfg.config.unwrap()["status"]["disabled"].as_bool().unwrap());
}
#[test]
fn test_value_config_parse() {
assert!(parse_toml_value("true").as_bool().unwrap());
assert_eq!(parse_toml_value("0").as_integer().unwrap(), 0);
assert!(parse_toml_value("0.0").is_float());
assert_eq!(parse_toml_value("a string").as_str().unwrap(), "a string");
assert_eq!(parse_toml_value("\"true\"").as_str().unwrap(), "true");
}
#[test]
fn test_update_config_empty() {
let mut cfg = test_config();
assert!(cfg.load_key_from_env("", "true").is_err());
assert!(cfg.load_key_from_env("______", "true").is_err());
assert!(cfg.load_key_from_env("a__a__a____a__a", "true").is_err());
assert!(cfg.load_key_from_env("a__a__a__a__a__", "true").is_err());
assert!(cfg.load_key_from_env("__a__a__a__a__a", "true").is_err());
}
#[test]
fn test_update_config_deep() {
let mut cfg = test_config();
cfg.load_key_from_env("a__b__c__d__e__f__g__h", "true")
.unwrap();
assert!(cfg.config.unwrap()["a"]["b"]["c"]["d"]["e"]["f"]["g"]["h"]
.as_bool()
.unwrap())
}
#[test]
fn table_get_colors_palette() {
// Test using colors defined in palette