diff options
author | Zhenhui Xie <xiezh0831@yahoo.co.jp> | 2020-07-08 06:45:32 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-07 18:45:32 -0400 |
commit | ec76fafff08933f6f31fb99ea974bdb5ae97a0af (patch) | |
tree | bb2c822cdf291635f03d27677c419488ecf77f53 /src/modules | |
parent | 0f52b7b12e8c1a2060aa873a68032937dfa2c044 (diff) |
feat: refactor modules to use format strings (#1374)
Diffstat (limited to 'src/modules')
43 files changed, 1602 insertions, 858 deletions
diff --git a/src/modules/aws.rs b/src/modules/aws.rs index 72df171ad..1248642d4 100644 --- a/src/modules/aws.rs +++ b/src/modules/aws.rs @@ -7,7 +7,8 @@ use std::str::FromStr; use super::{Context, Module, RootModuleConfig}; -use crate::configs::aws::{AwsConfig, AwsItems}; +use crate::configs::aws::AwsConfig; +use crate::formatter::StringFormatter; type Profile = String; type Region = String; @@ -51,73 +52,64 @@ fn get_aws_profile_and_region() -> (Option<Profile>, Option<Region>) { env::var("AWS_VAULT") .or_else(|_| env::var("AWS_PROFILE")) .ok(), - env::var("AWS_REGION").ok(), - env::var("AWS_DEFAULT_REGION").ok(), - ) { - (Some(p), Some(_), Some(dr)) => (Some(p), Some(dr)), - (Some(p), Some(r), None) => (Some(p), Some(r)), - (None, Some(r), None) => (None, Some(r)), - (Some(p), None, Some(dr)) => (Some(p), Some(dr)), - (Some(ref p), None, None) => (Some(p.to_owned()), get_aws_region_from_config(Some(p))), - (None, None, Some(dr)) => (None, Some(dr)), - (None, Some(_), Some(dr)) => (None, Some(dr)), - (None, None, None) => (None, get_aws_region_from_config(None)), - } -} - -fn get_aws_region() -> Option<Region> { - match ( - env::var("AWS_REGION").ok(), - env::var("AWS_DEFAULT_REGION").ok(), + env::var("AWS_DEFAULT_REGION") + .or_else(|_| env::var("AWS_REGION")) + .ok(), ) { - (Some(r), None) => Some(r), - (None, Some(dr)) => Some(dr), - (Some(_), Some(dr)) => Some(dr), - (None, None) => get_aws_region_from_config(None), + (Some(p), Some(r)) => (Some(p), Some(r)), + (None, Some(r)) => (None, Some(r)), + (Some(ref p), None) => (Some(p.to_owned()), get_aws_region_from_config(Some(p))), + (None, None) => (None, get_aws_region_from_config(None)), } } -fn alias_region(region: &str, aliases: &HashMap<String, &str>) -> String { - match aliases.get(region) { - None => region.to_string(), +fn alias_region(region: String, aliases: &HashMap<String, &str>) -> String { + match aliases.get(®ion) { + None => region, Some(alias) => (*alias).to_string(), } } pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { - const AWS_PREFIX: &str = "on "; - let mut module = context.new_module("aws"); let config: AwsConfig = AwsConfig::try_load(module.config); - module.set_style(config.style); - - module.get_prefix().set_value(AWS_PREFIX); - - module.create_segment("symbol", &config.symbol); - match config.displayed_items { - AwsItems::All => { - let (aws_profile, aws_region) = get_aws_profile_and_region(); - - let aws_segment = match (&aws_profile, &aws_region) { - (None, None) => return None, - (Some(p), Some(r)) => format!("{}({})", p, alias_region(r, &config.region_aliases)), - (Some(p), None) => p.to_string(), - (None, Some(r)) => alias_region(r, &config.region_aliases), - }; - module.create_segment("all", &config.region.with_value(&aws_segment)); - } - AwsItems::Profile => { - let aws_profile = env::var("AWS_PROFILE").ok()?; + let (aws_profile, aws_region) = get_aws_profile_and_region(); + if aws_profile.is_none() && aws_region.is_none() { + return None; + } - module.create_segment("profile", &config.profile.with_value(&aws_profile)); - } - AwsItems::Region => { - let aws_region = alias_region(&get_aws_region()?, &config.region_aliases); + let mapped_region = if let Some(aws_region) = aws_region { + Some(alias_region(aws_region, &config.region_aliases)) + } else { + None + }; - module.create_segment("region", &config.region.with_value(&aws_region)); + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|variable, _| match variable { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "profile" => aws_profile.as_ref().map(Ok), + "region" => mapped_region.as_ref().map(Ok), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::error!("Error in module `aws`: \n{}", error); + return None; } - }; + }); Some(module) } diff --git a/src/modules/battery.rs b/src/modules/battery.rs index 55e248e57..0ce96d8cc 100644 --- a/src/modules/battery.rs +++ b/src/modules/battery.rs @@ -1,6 +1,8 @@ use super::{Context, Module, RootModuleConfig, Shell}; use crate::configs::battery::BatteryConfig; +use crate::formatter::StringFormatter; + /// Creates a module for the battery percentage and charging state pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { // TODO: Update when v1.0 printing refactor is implemented to only @@ -14,60 +16,58 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let BatteryStatus { state, percentage } = battery_status; let mut module = context.new_module("battery"); - let battery_config: BatteryConfig = BatteryConfig::try_load(module.config); + let config: BatteryConfig = BatteryConfig::try_load(module.config); - // Parse config under `display` - let display_styles = &battery_config.display; - let display_style = display_styles + // Parse config under `display`. + // Select the first style that match the threshold, + // if all thresholds are lower do not display battery module. + let display_style = config + .display .iter() - .find(|display_style| percentage <= display_style.threshold as f32); + .find(|display_style| percentage <= display_style.threshold as f32)?; - if let Some(display_style) = display_style { - // Set style based on percentage - module.set_style(display_style.style); - module.get_prefix().set_value(""); + // Parse the format string and build the module + match StringFormatter::new(config.format) { + Ok(formatter) => { + let formatter = formatter + .map_meta(|variable, _| match variable { + "symbol" => match state { + battery::State::Full => Some(config.full_symbol), + battery::State::Charging => Some(config.charging_symbol), + battery::State::Discharging => Some(config.discharging_symbol), + battery::State::Unknown => config.unknown_symbol, + battery::State::Empty => config.empty_symbol, + _ => { + log::debug!("Unhandled battery state `{}`", state); + None + } + }, + _ => None, + }) + .map_style(|style| match style { + "style" => Some(Ok(display_style.style)), + _ => None, + }) + .map(|variable| match variable { + "percentage" => Some(Ok(format!("{}{}", percentage.round(), percentage_char))), + _ => None, + }); - match state { - battery::State::Full => { - module.create_segment("full_symbol", &battery_config.full_symbol); - } - battery::State::Charging => { - module.create_segment("charging_symbol", &battery_config.charging_symbol); - } - battery::State::Discharging => { - module.create_segment("discharging_symbol", &battery_config.discharging_symbol); - } - battery::State::Unknown => { - log::debug!("Unknown detected"); - if let Some(unknown_symbol) = battery_config.unknown_symbol { - module.create_segment("unknown_symbol", &unknown_symbol); + match formatter.parse(None) { + Ok(format_string) => { + module.set_segments(format_string); + Some(module) } - } - battery::State::Empty => { - if let Some(empty_symbol) = battery_config.empty_symbol { - module.create_segment("empty_symbol", &empty_symbol); + Err(e) => { + log::warn!("Cannot parse `battery.format`: {}", e); + None } } - _ => { - log::debug!("Unhandled battery state `{}`", state); - return None; - } } - - let mut percent_string = Vec::<String>::with_capacity(2); - // Round the percentage to a whole number - percent_string.push(percentage.round().to_string()); - percent_string.push(percentage_char.to_string()); - module.create_segment( - "percentage", - &battery_config - .percentage - .with_value(percent_string.join("").as_ref()), - ); - - Some(module) - } else { - None + Err(e) => { + log::warn!("Cannot load `battery.format`: {}", e); + None + } } } diff --git a/src/modules/character.rs b/src/modules/character.rs index e0c0ffe9e..fc4351951 100644 --- a/src/modules/character.rs +++ b/src/modules/character.rs @@ -1,14 +1,15 @@ use super::{Context, Module, RootModuleConfig, Shell}; use crate::configs::character::CharacterConfig; +use crate::formatter::StringFormatter; /// Creates a module for the prompt character /// -/// The character segment prints an arrow character in a color dependant on the exit- -/// code of the last executed command: -/// - If the exit-code was "0", the arrow will be formatted with `style_success` -/// (green by default) -/// - If the exit-code was anything else, the arrow will be formatted with -/// `style_failure` (red by default) +/// The character segment prints an arrow character in a color dependant on the +/// exit-code of the last executed command: +/// - If the exit-code was "0", it will be formatted with `success_symbol` +/// (green arrow by default) +/// - If the exit-code was anything else, it will be formatted with +/// `error_symbol` (red arrow by default) pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { enum ShellEditMode { Normal, @@ -19,12 +20,11 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let mut module = context.new_module("character"); let config: CharacterConfig = CharacterConfig::try_load(module.config); - module.get_prefix().set_value(""); let props = &context.properties; - let exit_code_default = std::string::String::from("0"); + let exit_code_default = String::from("0"); let exit_code = props.get("status_code").unwrap_or(&exit_code_default); - let keymap_default = std::string::String::from("viins"); + let keymap_default = String::from("viins"); let keymap = props.get("keymap").unwrap_or(&keymap_default); let exit_success = exit_code == "0"; @@ -38,22 +38,33 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { _ => ASSUMED_MODE, }; - if exit_success { - module.set_style(config.style_success); - } else { - module.set_style(config.style_failure); + let symbol = match mode { + ShellEditMode::Normal => config.vicmd_symbol, + ShellEditMode::Insert => { + if exit_success { + config.success_symbol + } else { + config.error_symbol + } + } }; - /* If an error symbol is set in the config, use symbols to indicate - success/failure, in addition to color */ - if config.use_symbol_for_status && !exit_success { - module.create_segment("error_symbol", &config.error_symbol) - } else { - match mode { - ShellEditMode::Normal => module.create_segment("vicmd_symbol", &config.vicmd_symbol), - ShellEditMode::Insert => module.create_segment("symbol", &config.symbol), + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|variable, _| match variable { + "symbol" => Some(symbol), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `character`:\n{}", error); + return None; } - }; + }); Some(module) } diff --git a/src/modules/cmd_duration.rs b/src/modules/cmd_duration.rs index 3555701d6..3a10f8f13 100644 --- a/src/modules/cmd_duration.rs +++ b/src/modules/cmd_duration.rs @@ -1,7 +1,7 @@ -use super::{Context, Module, SegmentConfig}; +use super::{Context, Module, RootModuleConfig}; -use crate::config::RootModuleConfig; use crate::configs::cmd_duration::CmdDurationConfig; +use crate::formatter::StringFormatter; /// Outputs the time it took the last command to execute /// @@ -28,19 +28,30 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { return None; } - let config_min = config.min_time as u128; + if elapsed < config.min_time as u128 { + return None; + } - let module_color = match elapsed { - time if time < config_min => return None, - _ => config.style, - }; + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "duration" => Some(Ok(render_time(elapsed, config.show_milliseconds))), + _ => None, + }) + .parse(None) + }); - module.set_style(module_color); - module.create_segment( - "cmd_duration", - &SegmentConfig::new(&render_time(elapsed, config.show_milliseconds)), - ); - module.get_prefix().set_value(config.prefix); + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `cmd_duration`: \n{}", error); + return None; + } + }); Some(module) } diff --git a/src/modules/conda.rs b/src/modules/conda.rs index 13c57874a..635e8f358 100644 --- a/src/modules/conda.rs +++ b/src/modules/conda.rs @@ -1,10 +1,10 @@ use std::env; -use super::{Context, Module}; +use super::{Context, Module, RootModuleConfig}; use super::utils::directory::truncate; -use crate::config::RootModuleConfig; use crate::configs::conda::CondaConfig; +use crate::formatter::StringFormatter; /// Creates a module with the current Conda environment /// @@ -17,14 +17,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { } let mut module = context.new_module("conda"); - let config = CondaConfig::try_load(module.config); + let config: CondaConfig = CondaConfig::try_load(module.config); let conda_env = truncate(conda_env, config.truncation_length); - module.set_style(config.style); - - module.create_segment("symbol", &config.symbol); - module.create_segment("environment", &config.environment.with_value(&conda_env)); + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|variable, _| match variable { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "environment" => Some(Ok(conda_env.as_str())), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `conda`:\n{}", error); + return None; + } + }); Some(module) } diff --git a/src/modules/crystal.rs b/src/modules/crystal.rs index 322631ff0..0aad994ac 100644 --- a/src/modules/crystal.rs +++ b/src/modules/crystal.rs @@ -1,6 +1,7 @@ -use super::{Context, Module, RootModuleConfig, SegmentConfig}; +use super::{Context, Module, RootModuleConfig}; use crate::configs::crystal::CrystalConfig; +use crate::formatter::StringFormatter; use crate::utils; /// Creates a module with the current Crystal version @@ -20,14 +21,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { } let crystal_version = utils::exec_cmd("crystal", &["--version"])?.stdout; - let formatted_version = format_crystal_version(&crystal_version)?; let mut module = context.new_module("crystal"); let config: CrystalConfig = CrystalConfig::try_load(module.config); - module.set_style(config.style); - module.create_segment("symbol", &config.symbol); - module.create_segment("version", &SegmentConfig::new(&formatted_version)); + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|variable, _| match variable { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "version" => format_crystal_version(&crystal_version).map(Ok), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `crystal`:\n{}", error); + return None; + } + }); Some(module) } diff --git a/src/modules/custom.rs b/src/modules/custom.rs index 51a3a49a5..bb28b49af 100644 --- a/src/modules/custom.rs +++ b/src/modules/custom.rs @@ -1,10 +1,9 @@ -use ansi_term::Color; use std::io::Write; use std::process::{Command, Output, Stdio}; use super::{Context, Module, RootModuleConfig}; -use crate::{config::SegmentConfig, configs::custom::CustomConfig}; +use crate::{configs::custom::CustomConfig, formatter::StringFormatter}; /// Creates a custom module with some configuration /// @@ -13,7 +12,7 @@ use crate::{config::SegmentConfig, configs::custom::CustomConfig}; /// command can be run -- if its result is 0, the module will be shown. /// /// Finally, the content of the module itself is also set by a command. -pub fn module<'a>(name: &'a str, context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(name: &str, context: &'a Context) -> Option<Module<'a>> { let toml_config = context.config.get_custom_module_config(name).expect( "modules::custom::module should only be called after ensuring that the module exists", ); @@ -44,35 +43,42 @@ pub fn module<'a>(name: &'a str, context: &'a Context) -> Option<Module<'a>> { } let mut module = Module::new(name, config.description, Some(toml_config)); - let style = config.style.unwrap_or_else(|| Color::Green.bold()); - if let Some(prefix) = config.prefix { - module.get_prefix().set_value(prefix); - } - if let Some(suffix) = config.suffix { - module.get_suffix().set_value(suffix); - } + let output = exec_command(config.command, &config.shell.0)?; - if let Some(symbol) = config.symbol { - module.create_segment("symbol", &symbol); + let trimmed = output.trim(); + if trimmed.is_empty() { + return None; } - if let Some(output) = exec_command(config.command, &config.shell.0) { - let trimmed = output.trim(); - - if trimmed.is_empty() { + 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 { + // This may result in multiple calls to `get_module_version` when a user have + // multiple `$version` variables defined in `format`. + "output" => Some(Ok(trimmed)), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `custom.{}`:\n{}", name, error); return None; } + }); - module.create_segment( - "output", - &SegmentConfig::new(&trimmed).with_style(Some(style)), - ); - - Some(module) - } else { - None - } + Some(module) } /// Return the invoking shell, using `shell` and fallbacking in order to STARSHIP_SHELL and "sh" diff --git a/src/modules/directory.rs b/src/modules/directory.rs index 795770622..31f48a2c0 100644 --- a/src/modules/directory.rs +++ b/src/modules/directory.rs @@ -7,8 +7,9 @@ use unicode_segmentation::UnicodeSegmentation; use super::{Context, Module}; use super::utils::directory::truncate; -use crate::config::{RootModuleConfig, SegmentConfig}; +use crate::config::RootModuleConfig; use crate::configs::directory::DirectoryConfig; +use crate::formatter::StringFormatter; /// Creates a module with the current directory /// @@ -29,8 +30,6 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let mut module = context.new_module("directory"); let config: DirectoryConfig = DirectoryConfig::try_load(module.config); - module.set_style(config.style); - // Using environment PWD is the standard approach for determining logical path // If this is None for any reason, we fall back to reading the os-provided path let physical_current_dir = if config.use_logical_path { @@ -80,33 +79,39 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { // Substitutions could have changed the prefix, so don't allow them and // fish-style path contraction together - if config.fish_style_pwd_dir_length > 0 && config.substitutions.is_empty() { + let fish_prefix = if config.fish_style_pwd_dir_length > 0 && config.substitutions.is_empty() { // If user is using fish style path, we need to add the segment first let contracted_home_dir = contract_path(¤t_dir, &home_dir, HOME_SYMBOL); - let fish_style_dir = to_fish_style( + to_fish_style( config.fish_style_pwd_dir_length as usize, contracted_home_dir, &truncated_dir_string, - ); - - module.create_segment( - "path", - &SegmentConfig { - value: &fish_style_dir, - style: None, - }, - ); - } - - module.create_segment( - "path", - &SegmentConfig { - value: &truncated_dir_string, - style: None, - }, - ); - - module.get_prefix().set_value(config.prefix); + ) + } else { + String::from("") + }; + let final_dir_string = format!("{}{}", fish_prefix, truncated_dir_string); + + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + |