1
0
mirror of https://github.com/Llewellynvdm/starship.git synced 2024-12-01 01:04:00 +00:00

feat: refactor modules to use format strings (#1374)

This commit is contained in:
Zhenhui Xie 2020-07-08 06:45:32 +08:00 committed by GitHub
parent 0f52b7b12e
commit ec76fafff0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
115 changed files with 4133 additions and 2544 deletions

10
Cargo.lock generated
View File

@ -867,6 +867,15 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.7" version = "1.0.7"
@ -1151,6 +1160,7 @@ dependencies = [
"pest", "pest",
"pest_derive", "pest_derive",
"pretty_env_logger", "pretty_env_logger",
"quick-xml",
"rayon", "rayon",
"regex", "regex",
"remove_dir_all", "remove_dir_all",

View File

@ -59,6 +59,7 @@ open = "1.4.0"
unicode-width = "0.1.8" unicode-width = "0.1.8"
textwrap = "0.12.1" textwrap = "0.12.1"
term_size = "0.3.2" term_size = "0.3.2"
quick-xml = "0.18.1"
# Optional/http: # Optional/http:
attohttpc = { version = "0.15.0", optional = true, default-features = false, features = ["tls", "form"] } attohttpc = { version = "0.15.0", optional = true, default-features = false, features = ["tls", "form"] }

File diff suppressed because it is too large Load Diff

View File

@ -235,37 +235,84 @@ impl StarshipConfig {
/// Get the subset of the table for a module by its name /// Get the subset of the table for a module by its name
pub fn get_module_config(&self, module_name: &str) -> Option<&Value> { pub fn get_module_config(&self, module_name: &str) -> Option<&Value> {
let module_config = self.config.as_ref()?.as_table()?.get(module_name); let module_config = self.get_config(&[module_name]);
if module_config.is_some() { if module_config.is_some() {
log::debug!( log::debug!(
"Config found for \"{}\": \n{:?}", "Config found for \"{}\": \n{:?}",
&module_name, &module_name,
&module_config &module_config
); );
} else {
log::trace!("No config found for \"{}\"", &module_name);
} }
module_config module_config
} }
/// Get the value of the config in a specific path
pub fn get_config(&self, path: &[&str]) -> Option<&Value> {
let mut prev_table = self.config.as_ref()?.as_table()?;
assert_ne!(
path.len(),
0,
"Starship::get_config called with an empty path"
);
let (table_options, _) = path.split_at(path.len() - 1);
// Assumes all keys except the last in path has a table
for option in table_options {
match prev_table.get(*option) {
Some(value) => match value.as_table() {
Some(value) => {
prev_table = value;
}
None => {
log::trace!(
"No config found for \"{}\": \"{}\" is not a table",
path.join("."),
&option
);
return None;
}
},
None => {
log::trace!(
"No config found for \"{}\": Option \"{}\" not found",
path.join("."),
&option
);
return None;
}
}
}
let last_option = path.last().unwrap();
let value = prev_table.get(*last_option);
if value.is_none() {
log::trace!(
"No config found for \"{}\": Option \"{}\" not found",
path.join("."),
&last_option
);
};
value
}
/// Get the subset of the table for a custom module by its name /// Get the subset of the table for a custom module by its name
pub fn get_custom_module_config(&self, module_name: &str) -> Option<&Value> { pub fn get_custom_module_config(&self, module_name: &str) -> Option<&Value> {
let module_config = self.get_custom_modules()?.get(module_name); let module_config = self.get_config(&["custom", module_name]);
if module_config.is_some() { if module_config.is_some() {
log::debug!( log::debug!(
"Custom config found for \"{}\": \n{:?}", "Custom config found for \"{}\": \n{:?}",
&module_name, &module_name,
&module_config &module_config
); );
} else {
log::trace!("No custom config found for \"{}\"", &module_name);
} }
module_config module_config
} }
/// Get the table of all the registered custom modules, if any /// Get the table of all the registered custom modules, if any
pub fn get_custom_modules(&self) -> Option<&toml::value::Table> { pub fn get_custom_modules(&self) -> Option<&toml::value::Table> {
self.config.as_ref()?.as_table()?.get("custom")?.as_table() self.get_config(&["custom"])?.as_table()
} }
pub fn get_root_config(&self) -> StarshipRootConfig { pub fn get_root_config(&self) -> StarshipRootConfig {
@ -277,75 +324,6 @@ impl StarshipConfig {
} }
} }
#[derive(Clone)]
pub struct SegmentConfig<'a> {
pub value: &'a str,
pub style: Option<Style>,
}
impl<'a> ModuleConfig<'a> for SegmentConfig<'a> {
fn from_config(config: &'a Value) -> Option<Self> {
match config {
Value::String(ref config_str) => Some(Self {
value: config_str,
style: None,
}),
Value::Table(ref config_table) => Some(Self {
value: config_table.get("value")?.as_str()?,
style: config_table.get("style").and_then(<Style>::from_config),
}),
_ => None,
}
}
fn load_config(&self, config: &'a Value) -> Self {
let mut new_config = self.clone();
match config {
Value::String(ref config_str) => {
new_config.value = config_str;
}
Value::Table(ref config_table) => {
if let Some(Value::String(value)) = config_table.get("value") {
new_config.value = value;
};
if let Some(style) = config_table.get("style") {
new_config.style = <Style>::from_config(style);
};
}
_ => {}
};
new_config
}
}
impl<'a> SegmentConfig<'a> {
pub fn new(value: &'a str) -> Self {
Self { value, style: None }
}
/// Immutably set value
pub fn with_value(&self, value: &'a str) -> Self {
Self {
value,
style: self.style,
}
}
/// Immutably set style
pub fn with_style(&self, style: Option<Style>) -> Self {
Self {
value: self.value,
style,
}
}
}
impl Default for SegmentConfig<'static> {
fn default() -> Self {
Self::new("")
}
}
/** Parse a style string which represents an ansi style. Valid tokens in the style /** Parse a style string which represents an ansi style. Valid tokens in the style
string include the following: string include the following:
- 'fg:<color>' (specifies that the color read should be a foreground color) - 'fg:<color>' (specifies that the color read should be a foreground color)

View File

@ -1,48 +1,24 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use std::collections::HashMap;
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
use std::collections::HashMap;
#[derive(Clone, PartialEq)]
pub enum AwsItems {
All,
Region,
Profile,
}
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct AwsConfig<'a> { pub struct AwsConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub profile: SegmentConfig<'a>, pub symbol: &'a str,
pub region: SegmentConfig<'a>, pub style: &'a str,
pub style: Style,
pub disabled: bool, pub disabled: bool,
pub displayed_items: AwsItems,
pub region_aliases: HashMap<String, &'a str>, pub region_aliases: HashMap<String, &'a str>,
} }
impl<'a> RootModuleConfig<'a> for AwsConfig<'a> { impl<'a> RootModuleConfig<'a> for AwsConfig<'a> {
fn new() -> Self { fn new() -> Self {
AwsConfig { AwsConfig {
symbol: SegmentConfig::new("☁️ "), format: "on [$symbol$profile(\\($region\\))]($style) ",
profile: SegmentConfig::default(), symbol: "☁️ ",
region: SegmentConfig::default(), style: "bold yellow",
style: Color::Yellow.bold(),
disabled: false, disabled: false,
displayed_items: AwsItems::All,
region_aliases: HashMap::new(), region_aliases: HashMap::new(),
} }
} }
} }
impl<'a> ModuleConfig<'a> for AwsItems {
fn from_config(config: &toml::Value) -> Option<Self> {
match config.as_str()? {
"all" => Some(AwsItems::All),
"region" => Some(AwsItems::Region),
"profile" => Some(AwsItems::Profile),
_ => None,
}
}
}

View File

@ -1,40 +1,39 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct BatteryConfig<'a> { pub struct BatteryConfig<'a> {
pub full_symbol: SegmentConfig<'a>, pub full_symbol: &'a str,
pub charging_symbol: SegmentConfig<'a>, pub charging_symbol: &'a str,
pub discharging_symbol: SegmentConfig<'a>, pub discharging_symbol: &'a str,
pub unknown_symbol: Option<SegmentConfig<'a>>, pub unknown_symbol: Option<&'a str>,
pub empty_symbol: Option<SegmentConfig<'a>>, pub empty_symbol: Option<&'a str>,
pub display: Vec<BatteryDisplayConfig>, pub display: Vec<BatteryDisplayConfig<'a>>,
pub disabled: bool, pub disabled: bool,
pub percentage: SegmentConfig<'a>, pub format: &'a str,
} }
impl<'a> RootModuleConfig<'a> for BatteryConfig<'a> { impl<'a> RootModuleConfig<'a> for BatteryConfig<'a> {
fn new() -> Self { fn new() -> Self {
BatteryConfig { BatteryConfig {
full_symbol: SegmentConfig::new(""), full_symbol: "",
charging_symbol: SegmentConfig::new(""), charging_symbol: "",
discharging_symbol: SegmentConfig::new(""), discharging_symbol: "",
unknown_symbol: None, unknown_symbol: None,
empty_symbol: None, empty_symbol: None,
format: "[$symbol$percentage]($style) ",
display: vec![BatteryDisplayConfig { display: vec![BatteryDisplayConfig {
threshold: 10, threshold: 10,
style: Color::Red.bold(), style: "red bold",
}], }],
disabled: false, disabled: false,
percentage: SegmentConfig::default(),
} }
} }
} }
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct BatteryDisplayConfig { pub struct BatteryDisplayConfig<'a> {
pub threshold: i64, pub threshold: i64,
pub style: Style, pub style: &'a str,
} }

View File

@ -1,28 +1,23 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct CharacterConfig<'a> { pub struct CharacterConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub error_symbol: SegmentConfig<'a>, pub success_symbol: &'a str,
pub vicmd_symbol: SegmentConfig<'a>, pub error_symbol: &'a str,
pub use_symbol_for_status: bool, pub vicmd_symbol: &'a str,
pub style_success: Style,
pub style_failure: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for CharacterConfig<'a> { impl<'a> RootModuleConfig<'a> for CharacterConfig<'a> {
fn new() -> Self { fn new() -> Self {
CharacterConfig { CharacterConfig {
symbol: SegmentConfig::new(""), format: "$symbol ",
error_symbol: SegmentConfig::new(""), success_symbol: "[](bold green)",
vicmd_symbol: SegmentConfig::new(""), error_symbol: "[](bold red)",
use_symbol_for_status: false, vicmd_symbol: "[](bold green)",
style_success: Color::Green.bold(),
style_failure: Color::Red.bold(),
disabled: false, disabled: false,
} }
} }

View File

@ -1,13 +1,12 @@
use crate::config::{ModuleConfig, RootModuleConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct CmdDurationConfig<'a> { pub struct CmdDurationConfig<'a> {
pub min_time: i64, pub min_time: i64,
pub prefix: &'a str, pub format: &'a str,
pub style: Style, pub style: &'a str,
pub show_milliseconds: bool, pub show_milliseconds: bool,
pub disabled: bool, pub disabled: bool,
} }
@ -16,9 +15,9 @@ impl<'a> RootModuleConfig<'a> for CmdDurationConfig<'a> {
fn new() -> Self { fn new() -> Self {
CmdDurationConfig { CmdDurationConfig {
min_time: 2_000, min_time: 2_000,
prefix: "took ", format: "took [$duration]($style) ",
show_milliseconds: false, show_milliseconds: false,
style: Color::Yellow.bold(), style: "yellow bold",
disabled: false, disabled: false,
} }
} }

View File

@ -1,14 +1,13 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct CondaConfig<'a> { pub struct CondaConfig<'a> {
pub truncation_length: usize, pub truncation_length: usize,
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub environment: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
@ -16,15 +15,9 @@ impl<'a> RootModuleConfig<'a> for CondaConfig<'a> {
fn new() -> Self { fn new() -> Self {
CondaConfig { CondaConfig {
truncation_length: 1, truncation_length: 1,
symbol: SegmentConfig { format: "via [$symbol$environment]($style) ",
value: "C ", symbol: "🅒 ",
style: None, style: "green bold",
},
environment: SegmentConfig {
value: "",
style: None,
},
style: Color::Green.bold(),
disabled: false, disabled: false,
} }
} }

View File

@ -1,20 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct CrystalConfig<'a> { pub struct CrystalConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub style: Style, pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for CrystalConfig<'a> { impl<'a> RootModuleConfig<'a> for CrystalConfig<'a> {
fn new() -> Self { fn new() -> Self {
CrystalConfig { CrystalConfig {
symbol: SegmentConfig::new("🔮 "), format: "via [$symbol$version]($style) ",
style: Color::Red.bold(), symbol: "🔮 ",
style: "bold red",
disabled: false, disabled: false,
} }
} }

View File

@ -1,6 +1,5 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig, VecOr}; use crate::config::{ModuleConfig, RootModuleConfig, VecOr};
use ansi_term::Style;
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, Default, PartialEq)] #[derive(Clone, Default, PartialEq)]
@ -14,15 +13,14 @@ pub struct Directories<'a>(pub Vec<&'a str>);
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct CustomConfig<'a> { pub struct CustomConfig<'a> {
pub symbol: Option<SegmentConfig<'a>>, pub format: &'a str,
pub symbol: &'a str,
pub command: &'a str, pub command: &'a str,
pub when: Option<&'a str>, pub when: Option<&'a str>,
pub shell: VecOr<&'a str>, pub shell: VecOr<&'a str>,
pub description: &'a str, pub description: &'a str,
pub style: Option<Style>, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
pub prefix: Option<&'a str>,
pub suffix: Option<&'a str>,
pub files: Files<'a>, pub files: Files<'a>,
pub extensions: Extensions<'a>, pub extensions: Extensions<'a>,
pub directories: Directories<'a>, pub directories: Directories<'a>,
@ -31,15 +29,14 @@ pub struct CustomConfig<'a> {
impl<'a> RootModuleConfig<'a> for CustomConfig<'a> { impl<'a> RootModuleConfig<'a> for CustomConfig<'a> {
fn new() -> Self { fn new() -> Self {
CustomConfig { CustomConfig {
symbol: None, format: "[$symbol$output]($style) ",
symbol: "",
command: "", command: "",
when: None, when: None,
shell: VecOr::default(), shell: VecOr::default(),
description: "<custom config>", description: "<custom config>",
style: None, style: "green bold",
disabled: false, disabled: false,
prefix: None,
suffix: None,
files: Files::default(), files: Files::default(),
extensions: Extensions::default(), extensions: Extensions::default(),
directories: Directories::default(), directories: Directories::default(),

View File

@ -1,7 +1,6 @@
use crate::config::{ModuleConfig, RootModuleConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use std::collections::HashMap; use std::collections::HashMap;
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
@ -11,8 +10,8 @@ pub struct DirectoryConfig<'a> {
pub substitutions: HashMap<String, &'a str>, pub substitutions: HashMap<String, &'a str>,
pub fish_style_pwd_dir_length: i64, pub fish_style_pwd_dir_length: i64,
pub use_logical_path: bool, pub use_logical_path: bool,
pub prefix: &'a str, pub format: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
@ -24,8 +23,8 @@ impl<'a> RootModuleConfig<'a> for DirectoryConfig<'a> {
fish_style_pwd_dir_length: 0, fish_style_pwd_dir_length: 0,
substitutions: HashMap::new(), substitutions: HashMap::new(),
use_logical_path: true, use_logical_path: true,
prefix: "in ", format: "[$path]($style) ",
style: Color::Cyan.bold(), style: "cyan bold",
disabled: false, disabled: false,
} }
} }

View File

@ -1,13 +1,12 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct DockerContextConfig<'a> { pub struct DockerContextConfig<'a> {
pub symbol: SegmentConfig<'a>, pub symbol: &'a str,
pub context: SegmentConfig<'a>, pub style: &'a str,
pub style: Style, pub format: &'a str,
pub only_with_files: bool, pub only_with_files: bool,
pub disabled: bool, pub disabled: bool,
} }
@ -15,9 +14,9 @@ pub struct DockerContextConfig<'a> {
impl<'a> RootModuleConfig<'a> for DockerContextConfig<'a> { impl<'a> RootModuleConfig<'a> for DockerContextConfig<'a> {
fn new() -> Self { fn new() -> Self {
DockerContextConfig { DockerContextConfig {
symbol: SegmentConfig::new("🐳 "), symbol: "🐳 ",
context: SegmentConfig::default(), style: "blue bold",
style: Color::Blue.bold(), format: "via [$symbol$context]($style) ",
only_with_files: true, only_with_files: true,
disabled: false, disabled: false,
} }

View File

@ -1,25 +1,24 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style}; use starship_module_config_derive::ModuleConfig;
use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)]
#[derive(Clone, ModuleConfig)] pub struct DotnetConfig<'a> {
pub struct DotnetConfig<'a> { pub format: &'a str,
pub symbol: SegmentConfig<'a>, pub symbol: &'a str,
pub version: SegmentConfig<'a>, pub style: &'a str,
pub style: Style, pub heuristic: bool,
pub heuristic: bool, pub disabled: bool,
pub disabled: bool, }
}
impl<'a> RootModuleConfig<'a> for DotnetConfig<'a> {
impl<'a> RootModuleConfig<'a> for DotnetConfig<'a> { fn new() -> Self {
fn new() -> Self { DotnetConfig {
DotnetConfig { format: "[$symbol$version( 🎯 $tfm)]($style) ",
symbol: SegmentConfig::new("•NET "), symbol: "•NET ",
version: SegmentConfig::default(), style: "blue bold",
style: Color::Blue.bold(), heuristic: true,
heuristic: true, disabled: false,
disabled: false, }
} }
} }
}

View File

@ -1,24 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct ElixirConfig<'a> { pub struct ElixirConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub otp_version: SegmentConfig<'a>, pub style: &'a str,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for ElixirConfig<'a> { impl<'a> RootModuleConfig<'a> for ElixirConfig<'a> {
fn new() -> Self { fn new() -> Self {
ElixirConfig { ElixirConfig {
symbol: SegmentConfig::new("💧 "), format: "via [$symbol$version \\(OTP $otp_version\\)]($style) ",
version: SegmentConfig::default(), symbol: "💧 ",
otp_version: SegmentConfig::default(), style: "bold purple",
style: Color::Purple.bold(),
disabled: false, disabled: false,
} }
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct ElmConfig<'a> { pub struct ElmConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for ElmConfig<'a> { impl<'a> RootModuleConfig<'a> for ElmConfig<'a> {
fn new() -> Self { fn new() -> Self {
ElmConfig { ElmConfig {
symbol: SegmentConfig::new("🌳 "), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "🌳 ",
style: Color::Cyan.bold(), style: "cyan bold",
disabled: false, disabled: false,
} }
} }

View File

@ -1,28 +1,25 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct EnvVarConfig<'a> { pub struct EnvVarConfig<'a> {
pub symbol: Option<SegmentConfig<'a>>, pub symbol: &'a str,
pub style: &'a str,
pub variable: Option<&'a str>, pub variable: Option<&'a str>,
pub default: Option<&'a str>, pub default: Option<&'a str>,
pub prefix: &'a str, pub format: &'a str,
pub suffix: &'a str,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for EnvVarConfig<'a> { impl<'a> RootModuleConfig<'a> for EnvVarConfig<'a> {
fn new() -> Self { fn new() -> Self {
EnvVarConfig { EnvVarConfig {
symbol: None, symbol: "",
style: "black bold dimmed",
variable: None, variable: None,
default: None, default: None,
prefix: "", format: "with [$env_value]($style) ",
suffix: "",
style: Color::Black.bold().dimmed(),
disabled: false, disabled: false,
} }
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct ErlangConfig<'a> { pub struct ErlangConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for ErlangConfig<'a> { impl<'a> RootModuleConfig<'a> for ErlangConfig<'a> {
fn new() -> Self { fn new() -> Self {
ErlangConfig { ErlangConfig {
symbol: SegmentConfig::new("🖧 "), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "🖧 ",
style: Color::Red.bold(), style: "bold red",
disabled: false, disabled: false,
} }
} }

View File

@ -1,26 +1,25 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct GitBranchConfig<'a> { pub struct GitBranchConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub truncation_length: i64, pub truncation_length: i64,
pub truncation_symbol: &'a str, pub truncation_symbol: &'a str,
pub branch_name: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for GitBranchConfig<'a> { impl<'a> RootModuleConfig<'a> for GitBranchConfig<'a> {
fn new() -> Self { fn new() -> Self {
GitBranchConfig { GitBranchConfig {
symbol: SegmentConfig::new(""), format: "on [$symbol$branch]($style) ",
symbol: "",
style: "bold purple",
truncation_length: std::i64::MAX, truncation_length: std::i64::MAX,
truncation_symbol: "", truncation_symbol: "",
branch_name: SegmentConfig::default(),
style: Color::Purple.bold(),
disabled: false, disabled: false,
} }
} }

View File

@ -1,15 +1,12 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct GitCommitConfig<'a> { pub struct GitCommitConfig<'a> {
pub commit_hash_length: usize, pub commit_hash_length: usize,
pub hash: SegmentConfig<'a>, pub format: &'a str,
pub prefix: &'a str, pub style: &'a str,
pub suffix: &'a str,
pub style: Style,
pub only_detached: bool, pub only_detached: bool,
pub disabled: bool, pub disabled: bool,
} }
@ -19,10 +16,8 @@ impl<'a> RootModuleConfig<'a> for GitCommitConfig<'a> {
GitCommitConfig { GitCommitConfig {
// be consistent with git by default, which has DEFAULT_ABBREV set to 7 // be consistent with git by default, which has DEFAULT_ABBREV set to 7
commit_hash_length: 7, commit_hash_length: 7,
hash: SegmentConfig::default(), format: "[\\($hash\\)]($style) ",
prefix: "(", style: "green bold",
suffix: ") ",
style: Color::Green.bold(),
only_detached: true, only_detached: true,
disabled: false, disabled: false,
} }

View File

@ -1,34 +1,33 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct GitStateConfig<'a> { pub struct GitStateConfig<'a> {
pub rebase: SegmentConfig<'a>, pub rebase: &'a str,
pub merge: SegmentConfig<'a>, pub merge: &'a str,
pub revert: SegmentConfig<'a>, pub revert: &'a str,
pub cherry_pick: SegmentConfig<'a>, pub cherry_pick: &'a str,
pub bisect: SegmentConfig<'a>, pub bisect: &'a str,
pub am: SegmentConfig<'a>, pub am: &'a str,
pub am_or_rebase: SegmentConfig<'a>, pub am_or_rebase: &'a str,
pub progress_divider: SegmentConfig<'a>, pub style: &'a str,
pub style: Style, pub format: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for GitStateConfig<'a> { impl<'a> RootModuleConfig<'a> for GitStateConfig<'a> {
fn new() -> Self { fn new() -> Self {
GitStateConfig { GitStateConfig {
rebase: SegmentConfig::new("REBASING"), rebase: "REBASING",
merge: SegmentConfig::new("MERGING"), merge: "MERGING",
revert: SegmentConfig::new("REVERTING"), revert: "REVERTING",
cherry_pick: SegmentConfig::new("CHERRY-PICKING"), cherry_pick: "CHERRY-PICKING",
bisect: SegmentConfig::new("BISECTING"), bisect: "BISECTING",
am: SegmentConfig::new("AM"), am: "AM",
am_or_rebase: SegmentConfig::new("AM/REBASE"), am_or_rebase: "AM/REBASE",
progress_divider: SegmentConfig::new("/"), style: "bold yellow",
style: Color::Yellow.bold(), format: "[\\($state( $progress_current/$progress_total)\\)]($style) ",
disabled: false, disabled: false,
} }
} }

View File

@ -1,65 +1,40 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct GitStatusConfig<'a> { pub struct GitStatusConfig<'a> {
pub stashed: SegmentConfig<'a>, pub format: &'a str,
pub stashed_count: CountConfig, pub style: &'a str,
pub ahead: SegmentConfig<'a>, pub stashed: &'a str,
pub behind: SegmentConfig<'a>, pub ahead: &'a str,
pub diverged: SegmentConfig<'a>, pub behind: &'a str,
pub show_sync_count: bool, pub diverged: &'a str,
pub conflicted: SegmentConfig<'a>, pub conflicted: &'a str,
pub conflicted_count: CountConfig, pub deleted: &'a str,
pub deleted: SegmentConfig<'a>, pub renamed: &'a str,
pub deleted_count: CountConfig, pub modified: &'a str,
pub renamed: SegmentConfig<'a>, pub staged: &'a str,
pub renamed_count: CountConfig, pub untracked: &'a str,
pub modified: SegmentConfig<'a>,
pub modified_count: CountConfig,
pub staged: SegmentConfig<'a>,
pub staged_count: CountConfig,
pub untracked: SegmentConfig<'a>,
pub untracked_count: CountConfig,
pub prefix: &'a str,
pub suffix: &'a str,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for GitStatusConfig<'a> { impl<'a> RootModuleConfig<'a> for GitStatusConfig<'a> {
fn new() -> Self { fn new() -> Self {
GitStatusConfig { GitStatusConfig {
stashed: SegmentConfig::new("$"), format: "([\\[$all_status$ahead_behind\\]]($style) )",
stashed_count: CountConfig::default(), style: "red bold",
ahead: SegmentConfig::new(""), stashed: "\\$",
behind: SegmentConfig::new(""), ahead: "",
diverged: SegmentConfig::new(""), behind: "",
conflicted: SegmentConfig::new("="), diverged: "",
show_sync_count: false, conflicted: "=",
conflicted_count: CountConfig::default(), deleted: "",
deleted: SegmentConfig::new(""), renamed: "»",
deleted_count: CountConfig::default(), modified: "!",
renamed: SegmentConfig::new("»"), staged: "+",
renamed_count: CountConfig::default(), untracked: "?",
modified: SegmentConfig::new("!"),
modified_count: CountConfig::default(),
staged: SegmentConfig::new("+"),
staged_count: CountConfig::default(),
untracked: SegmentConfig::new("?"),
untracked_count: CountConfig::default(),
prefix: "[",
suffix: "] ",
style: Color::Red.bold(),
disabled: false, disabled: false,
} }
} }
} }
#[derive(Clone, Copy, ModuleConfig, Default)]
pub struct CountConfig {
pub enabled: bool,
pub style: Option<Style>,
}

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct GoConfig<'a> { pub struct GoConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for GoConfig<'a> { impl<'a> RootModuleConfig<'a> for GoConfig<'a> {
fn new() -> Self { fn new() -> Self {
GoConfig { GoConfig {
symbol: SegmentConfig::new("🐹 "), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "🐹 ",
style: Color::Cyan.bold(), style: "bold cyan",
disabled: false, disabled: false,
} }
} }

View File

@ -1,26 +1,25 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct HgBranchConfig<'a> { pub struct HgBranchConfig<'a> {
pub symbol: SegmentConfig<'a>, pub symbol: &'a str,
pub style: &'a str,
pub format: &'a str,
pub truncation_length: i64, pub truncation_length: i64,
pub truncation_symbol: &'a str, pub truncation_symbol: &'a str,
pub branch_name: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for HgBranchConfig<'a> { impl<'a> RootModuleConfig<'a> for HgBranchConfig<'a> {
fn new() -> Self { fn new() -> Self {
HgBranchConfig { HgBranchConfig {
symbol: SegmentConfig::new(""), symbol: "",
style: "bold purple",
format: "on [$symbol$branch]($style) ",
truncation_length: std::i64::MAX, truncation_length: std::i64::MAX,
truncation_symbol: "", truncation_symbol: "",
branch_name: SegmentConfig::default(),
style: Color::Purple.bold(),
disabled: true, disabled: true,
} }
} }

View File

@ -1,15 +1,13 @@
use crate::config::{ModuleConfig, RootModuleConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct HostnameConfig<'a> { pub struct HostnameConfig<'a> {
pub ssh_only: bool, pub ssh_only: bool,
pub prefix: &'a str,
pub suffix: &'a str,
pub trim_at: &'a str, pub trim_at: &'a str,
pub style: Style, pub format: &'a str,
pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
@ -17,10 +15,9 @@ impl<'a> RootModuleConfig<'a> for HostnameConfig<'a> {
fn new() -> Self { fn new() -> Self {
HostnameConfig { HostnameConfig {
ssh_only: true, ssh_only: true,
prefix: "",
suffix: "",
trim_at: ".", trim_at: ".",
style: Color::Green.bold().dimmed(), format: "on [$hostname]($style) ",
style: "green dimmed bold",
disabled: false, disabled: false,
} }
} }

View File

@ -1,21 +1,22 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct JavaConfig<'a> { pub struct JavaConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool, pub disabled: bool,
pub format: &'a str,
pub style: &'a str,
pub symbol: &'a str,
} }
impl<'a> RootModuleConfig<'a> for JavaConfig<'a> { impl<'a> RootModuleConfig<'a> for JavaConfig<'a> {
fn new() -> Self { fn new() -> Self {
JavaConfig { JavaConfig {
symbol: SegmentConfig::new(""), format: "via [$symbol$version]($style) ",
style: Color::Red.dimmed(),
disabled: false, disabled: false,
style: "red dimmed",
symbol: "",
} }
} }
} }

View File

@ -1,22 +1,23 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct JobsConfig<'a> { pub struct JobsConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub threshold: i64, pub threshold: i64,
pub style: Style, pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for JobsConfig<'a> { impl<'a> RootModuleConfig<'a> for JobsConfig<'a> {
fn new() -> Self { fn new() -> Self {
JobsConfig { JobsConfig {
symbol: SegmentConfig::new(""),
threshold: 1, threshold: 1,
style: Color::Blue.bold(), format: "[$symbol$number]($style) ",
symbol: "",
style: "bold blue",
disabled: false, disabled: false,
} }
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct JuliaConfig<'a> { pub struct JuliaConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for JuliaConfig<'a> { impl<'a> RootModuleConfig<'a> for JuliaConfig<'a> {
fn new() -> Self { fn new() -> Self {
JuliaConfig { JuliaConfig {
symbol: SegmentConfig::new(""), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "",
style: Color::Purple.bold(), style: "bold purple",
disabled: false, disabled: false,
} }
} }

View File

@ -1,15 +1,13 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct KubernetesConfig<'a> { pub struct KubernetesConfig<'a> {
pub symbol: SegmentConfig<'a>, pub symbol: &'a str,
pub context: SegmentConfig<'a>, pub format: &'a str,
pub namespace: SegmentConfig<'a>, pub style: &'a str,
pub style: Style,
pub disabled: bool, pub disabled: bool,
pub context_aliases: HashMap<String, &'a str>, pub context_aliases: HashMap<String, &'a str>,
} }
@ -17,10 +15,9 @@ pub struct KubernetesConfig<'a> {
impl<'a> RootModuleConfig<'a> for KubernetesConfig<'a> { impl<'a> RootModuleConfig<'a> for KubernetesConfig<'a> {
fn new() -> Self { fn new() -> Self {
KubernetesConfig { KubernetesConfig {
symbol: SegmentConfig::new(""), symbol: "",
context: SegmentConfig::default(), format: "on [$symbol$context( \\($namespace\\))]($style) ",
namespace: SegmentConfig::default(), style: "cyan bold",
style: Color::Cyan.bold(),
disabled: true, disabled: true,
context_aliases: HashMap::new(), context_aliases: HashMap::new(),
} }

View File

@ -1,32 +1,23 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct MemoryConfig<'a> { pub struct MemoryConfig<'a> {
pub show_percentage: bool,
pub show_swap: bool,
pub threshold: i64, pub threshold: i64,
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub separator: SegmentConfig<'a>, pub style: &'a str,
pub ram: SegmentConfig<'a>, pub symbol: &'a str,
pub swap: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for MemoryConfig<'a> { impl<'a> RootModuleConfig<'a> for MemoryConfig<'a> {
fn new() -> Self { fn new() -> Self {
MemoryConfig { MemoryConfig {
show_percentage: false,
show_swap: true,
threshold: 75, threshold: 75,
symbol: SegmentConfig::new("🐏 "), format: "via $symbol[$ram( | $swap)]($style) ",
separator: SegmentConfig::new(" | "), style: "white bold dimmed",
ram: SegmentConfig::default(), symbol: "🐏 ",
swap: SegmentConfig::default(),
style: Color::White.bold().dimmed(),
disabled: true, disabled: true,
} }
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct NimConfig<'a> { pub struct NimConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for NimConfig<'a> { impl<'a> RootModuleConfig<'a> for NimConfig<'a> {
fn new() -> Self { fn new() -> Self {
NimConfig { NimConfig {
symbol: SegmentConfig::new("👑 "), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "👑 ",
style: Color::Yellow.bold(), style: "yellow bold",
disabled: false, disabled: false,
} }
} }

View File

@ -1,26 +1,25 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct NixShellConfig<'a> { pub struct NixShellConfig<'a> {
pub use_name: bool, pub format: &'a str,
pub impure_msg: SegmentConfig<'a>, pub symbol: &'a str,
pub pure_msg: SegmentConfig<'a>, pub style: &'a str,
pub style: Style, pub impure_msg: &'a str,
pub symbol: SegmentConfig<'a>, pub pure_msg: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for NixShellConfig<'a> { impl<'a> RootModuleConfig<'a> for NixShellConfig<'a> {
fn new() -> Self { fn new() -> Self {
NixShellConfig { NixShellConfig {
use_name: false, format: "via [$symbol$state( \\($name\\))]($style) ",
impure_msg: SegmentConfig::new("impure"), symbol: "❄️ ",
pure_msg: SegmentConfig::new("pure"), style: "bold blue",
style: Color::Blue.bold(), impure_msg: "impure",
symbol: SegmentConfig::new("❄️ "), pure_msg: "pure",
disabled: false, disabled: false,
} }
} }

View File

@ -1,20 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct NodejsConfig<'a> { pub struct NodejsConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub style: Style, pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for NodejsConfig<'a> { impl<'a> RootModuleConfig<'a> for NodejsConfig<'a> {
fn new() -> Self { fn new() -> Self {
NodejsConfig { NodejsConfig {
symbol: SegmentConfig::new(""), format: "via [$symbol$version]($style) ",
style: Color::Green.bold(), symbol: "",
style: "bold green",
disabled: false, disabled: false,
} }
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct OCamlConfig<'a> { pub struct OCamlConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for OCamlConfig<'a> { impl<'a> RootModuleConfig<'a> for OCamlConfig<'a> {
fn new() -> Self { fn new() -> Self {
OCamlConfig { OCamlConfig {
symbol: SegmentConfig::new("🐫 "), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "🐫 ",
style: Color::Yellow.bold(), style: "bold yellow",
disabled: false, disabled: false,
} }
} }

View File

@ -1,12 +1,12 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct PackageConfig<'a> { pub struct PackageConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub style: Style, pub symbol: &'a str,
pub style: &'a str,
pub display_private: bool, pub display_private: bool,
pub disabled: bool, pub disabled: bool,
} }
@ -14,8 +14,9 @@ pub struct PackageConfig<'a> {
impl<'a> RootModuleConfig<'a> for PackageConfig<'a> { impl<'a> RootModuleConfig<'a> for PackageConfig<'a> {
fn new() -> Self { fn new() -> Self {
PackageConfig { PackageConfig {
symbol: SegmentConfig::new("📦 "), format: "is [$symbol$version]($style) ",
style: Color::Fixed(208).bold(), symbol: "📦 ",
style: "208 bold",
display_private: false, display_private: false,
disabled: false, disabled: false,
} }

View File

@ -1,20 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct PhpConfig<'a> { pub struct PhpConfig<'a> {
pub symbol: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub format: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for PhpConfig<'a> { impl<'a> RootModuleConfig<'a> for PhpConfig<'a> {
fn new() -> Self { fn new() -> Self {
PhpConfig { PhpConfig {
symbol: SegmentConfig::new("🐘 "), symbol: "🐘 ",
style: Color::Fixed(147).bold(), style: "147 bold",
format: "via [$symbol$version]($style) ",
disabled: false, disabled: false,
} }
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct PureScriptConfig<'a> { pub struct PureScriptConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for PureScriptConfig<'a> { impl<'a> RootModuleConfig<'a> for PureScriptConfig<'a> {
fn new() -> Self { fn new() -> Self {
PureScriptConfig { PureScriptConfig {
symbol: SegmentConfig::new("<=> "), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "<=> ",
style: Color::White.bold(), style: "bold white",
disabled: false, disabled: false,
} }
} }

View File

@ -1,30 +1,27 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct PythonConfig<'a> { pub struct PythonConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub pyenv_prefix: SegmentConfig<'a>,
pub pyenv_version_name: bool, pub pyenv_version_name: bool,
pub python_binary: &'a str, pub python_binary: &'a str,
pub scan_for_pyfiles: bool, pub scan_for_pyfiles: bool,
pub style: Style, pub format: &'a str,
pub style: &'a str,
pub symbol: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for PythonConfig<'a> { impl<'a> RootModuleConfig<'a> for PythonConfig<'a> {
fn new() -> Self { fn new() -> Self {
PythonConfig { PythonConfig {
symbol: SegmentConfig::new("🐍 "),
version: SegmentConfig::default(),
pyenv_prefix: SegmentConfig::new("pyenv "),
pyenv_version_name: false, pyenv_version_name: false,
python_binary: "python", python_binary: "python",
scan_for_pyfiles: true, scan_for_pyfiles: true,
style: Color::Yellow.bold(), format: "via [$symbol$version( \\($virtualenv\\))]($style) ",
style: "yellow bold",
symbol: "🐍 ",
disabled: false, disabled: false,
} }
} }

View File

@ -1,20 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct RubyConfig<'a> { pub struct RubyConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub style: Style, pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for RubyConfig<'a> { impl<'a> RootModuleConfig<'a> for RubyConfig<'a> {
fn new() -> Self { fn new() -> Self {
RubyConfig { RubyConfig {
symbol: SegmentConfig::new("💎 "), format: "via [$symbol$version]($style) ",
style: Color::Red.bold(), symbol: "💎 ",
style: "bold red",
disabled: false, disabled: false,
} }
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct RustConfig<'a> { pub struct RustConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for RustConfig<'a> { impl<'a> RootModuleConfig<'a> for RustConfig<'a> {
fn new() -> Self { fn new() -> Self {
RustConfig { RustConfig {
symbol: SegmentConfig::new("🦀 "), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "🦀 ",
style: Color::Red.bold(), style: "bold red",
disabled: false, disabled: false,
} }
} }

View File

@ -1,26 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct SingularityConfig<'a> { pub struct SingularityConfig<'a> {
pub symbol: SegmentConfig<'a>, pub symbol: &'a str,
pub label: &'a str, pub format: &'a str,
pub prefix: &'a str, pub style: &'a str,
pub suffix: &'a str,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for SingularityConfig<'a> { impl<'a> RootModuleConfig<'a> for SingularityConfig<'a> {
fn new() -> Self { fn new() -> Self {
SingularityConfig { SingularityConfig {
symbol: SegmentConfig::default(), format: "[$symbol\\[$env\\]]($style) ",
label: "", symbol: "",
prefix: "[", style: "blue bold dimmed",
suffix: "]",
style: Color::Blue.bold().dimmed(),
disabled: false, disabled: false,
} }
} }

View File

@ -4,66 +4,66 @@ use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct StarshipRootConfig<'a> { pub struct StarshipRootConfig<'a> {
pub add_newline: bool, pub format: &'a str,
pub prompt_order: Vec<&'a str>,
pub scan_timeout: u64, pub scan_timeout: u64,
} }
// List of default prompt order
// NOTE: If this const value is changed then Default prompt order subheading inside
// prompt heading of config docs needs to be updated according to changes made here.
pub const PROMPT_ORDER: [&str; 42] = [
"username",
"hostname",
"singularity",
"kubernetes",
"directory",
"git_branch",
"git_commit",
"git_state",
"git_status",
"hg_branch",
"docker_context",
"package",
// ↓ Toolchain version modules ↓
// (Let's keep these sorted alphabetically)
"dotnet",
"elixir",
"elm",
"erlang",
"golang",
"java",
"julia",
"nim",
"nodejs",
"ocaml",
"php",
"purescript",
"python",
"ruby",
"rust",
"terraform",
"zig",
// ↑ Toolchain version modules ↑
"nix_shell",
"conda",
"memory_usage",
"aws",
"env_var",
"crystal",
"cmd_duration",
"custom",
"line_break",
"jobs",
#[cfg(feature = "battery")]
"battery",
"time",
"character",
];
impl<'a> RootModuleConfig<'a> for StarshipRootConfig<'a> { impl<'a> RootModuleConfig<'a> for StarshipRootConfig<'a> {
fn new() -> Self { fn new() -> Self {
StarshipRootConfig { StarshipRootConfig {
add_newline: true, format: "\n$all",
// List of default prompt order
// NOTE: If this const value is changed then Default prompt order subheading inside
// prompt heading of config docs needs to be updated according to changes made here.
prompt_order: vec![
"username",
"hostname",
"singularity",
"kubernetes",
"directory",
"git_branch",
"git_commit",
"git_state",
"git_status",
"hg_branch",
"docker_context",
"package",
// ↓ Toolchain version modules ↓
// (Let's keep these sorted alphabetically)
"dotnet",
"elixir",
"elm",
"erlang",
"golang",
"java",
"julia",
"nim",
"nodejs",
"ocaml",
"php",
"purescript",
"python",
"ruby",
"rust",
"terraform",
"zig",
// ↑ Toolchain version modules ↑
"nix_shell",
"conda",
"memory_usage",
"aws",
"env_var",
"crystal",
"cmd_duration",
"custom",
"line_break",
"jobs",
#[cfg(feature = "battery")]
"battery",
"time",
"character",
],
scan_timeout: 30, scan_timeout: 30,
} }
} }

View File

@ -1,26 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct TerraformConfig<'a> { pub struct TerraformConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub workspace: SegmentConfig<'a>, pub symbol: &'a str,
pub version: SegmentConfig<'a>, pub style: &'a str,
pub show_version: bool,
pub style: Style,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for TerraformConfig<'a> { impl<'a> RootModuleConfig<'a> for TerraformConfig<'a> {
fn new() -> Self { fn new() -> Self {
TerraformConfig { TerraformConfig {
symbol: SegmentConfig::new("💠 "), format: "via [$symbol$workspace]($style) ",
workspace: SegmentConfig::default(), symbol: "💠 ",
version: SegmentConfig::default(), style: "bold 105",
show_version: false,
style: Color::Fixed(105).bold(),
disabled: false, disabled: false,
} }
} }

View File

@ -1,13 +1,13 @@
use crate::config::{ModuleConfig, RootModuleConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct TimeConfig<'a> { pub struct TimeConfig<'a> {
pub format: &'a str,
pub style: &'a str,
pub use_12hr: bool, pub use_12hr: bool,
pub format: Option<&'a str>, pub time_format: Option<&'a str>,
pub style: Style,
pub disabled: bool, pub disabled: bool,
pub utc_time_offset: &'a str, pub utc_time_offset: &'a str,
pub time_range: &'a str, pub time_range: &'a str,
@ -16,9 +16,10 @@ pub struct TimeConfig<'a> {
impl<'a> RootModuleConfig<'a> for TimeConfig<'a> { impl<'a> RootModuleConfig<'a> for TimeConfig<'a> {
fn new() -> Self { fn new() -> Self {
TimeConfig { TimeConfig {
format: "at [$time]($style) ",
style: "bold yellow",
use_12hr: false, use_12hr: false,
format: None, time_format: None,
style: Color::Yellow.bold(),
disabled: true, disabled: true,
utc_time_offset: "local", utc_time_offset: "local",
time_range: "-", time_range: "-",

View File

@ -1,21 +1,22 @@
use crate::config::{ModuleConfig, RootModuleConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct UsernameConfig { pub struct UsernameConfig<'a> {
pub style_root: Style, pub format: &'a str,
pub style_user: Style, pub style_root: &'a str,
pub style_user: &'a str,
pub show_always: bool, pub show_always: bool,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for UsernameConfig { impl<'a> RootModuleConfig<'a> for UsernameConfig<'a> {
fn new() -> Self { fn new() -> Self {
UsernameConfig { UsernameConfig {
style_root: Color::Red.bold(), format: "via [$user]($style) ",
style_user: Color::Yellow.bold(), style_root: "red bold",
style_user: "yellow bold",
show_always: false, show_always: false,
disabled: false, disabled: false,
} }

View File

@ -1,22 +1,21 @@
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig}; use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig; use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)] #[derive(Clone, ModuleConfig)]
pub struct ZigConfig<'a> { pub struct ZigConfig<'a> {
pub symbol: SegmentConfig<'a>, pub format: &'a str,
pub version: SegmentConfig<'a>, pub symbol: &'a str,
pub style: Style, pub style: &'a str,
pub disabled: bool, pub disabled: bool,
} }
impl<'a> RootModuleConfig<'a> for ZigConfig<'a> { impl<'a> RootModuleConfig<'a> for ZigConfig<'a> {
fn new() -> Self { fn new() -> Self {
ZigConfig { ZigConfig {
symbol: SegmentConfig::new(""), format: "via [$symbol$version]($style) ",
version: SegmentConfig::default(), symbol: "",
style: Color::Yellow.bold(), style: "bold yellow",
disabled: false, disabled: false,
} }
} }

View File

@ -2,4 +2,5 @@ pub mod model;
mod parser; mod parser;
pub mod string_formatter; pub mod string_formatter;
pub use model::{StyleVariableHolder, VariableHolder};
pub use string_formatter::StringFormatter; pub use string_formatter::StringFormatter;

View File

@ -1,17 +1,103 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::BTreeSet;
/// Type that holds a number of variables of type `T`
pub trait VariableHolder<T> {
fn get_variables(&self) -> BTreeSet<T>;
}
/// Type that holds a number of style variables of type `T`
pub trait StyleVariableHolder<T> {
fn get_style_variables(&self) -> BTreeSet<T>;
}
#[derive(Clone)]
pub struct TextGroup<'a> { pub struct TextGroup<'a> {
pub format: Vec<FormatElement<'a>>, pub format: Vec<FormatElement<'a>>,
pub style: Vec<StyleElement<'a>>, pub style: Vec<StyleElement<'a>>,
} }
#[derive(Clone)]
pub enum FormatElement<'a> { pub enum FormatElement<'a> {
Text(Cow<'a, str>), Text(Cow<'a, str>),
Variable(Cow<'a, str>), Variable(Cow<'a, str>),
TextGroup(TextGroup<'a>), TextGroup(TextGroup<'a>),
Conditional(Vec<FormatElement<'a>>),
} }
#[derive(Clone)]
pub enum StyleElement<'a> { pub enum StyleElement<'a> {
Text(Cow<'a, str>), Text(Cow<'a, str>),
Variable(Cow<'a, str>), Variable(Cow<'a, str>),
} }
impl<'a> VariableHolder<Cow<'a, str>> for FormatElement<'a> {
fn get_variables(&self) -> BTreeSet<Cow<'a, str>> {
match self {
FormatElement::Variable(var) => {
let mut variables = BTreeSet::new();
variables.insert(var.clone());
variables
}
FormatElement::TextGroup(textgroup) => textgroup.format.get_variables(),
FormatElement::Conditional(format) => format.get_variables(),
_ => Default::default(),
}
}
}
impl<'a> VariableHolder<Cow<'a, str>> for Vec<FormatElement<'a>> {
fn get_variables(&self) -> BTreeSet<Cow<'a, str>> {
self.iter().fold(BTreeSet::new(), |mut acc, el| {
acc.extend(el.get_variables());
acc
})
}
}
impl<'a> VariableHolder<Cow<'a, str>> for &[FormatElement<'a>] {
fn get_variables(&self) -> BTreeSet<Cow<'a, str>> {
self.iter().fold(BTreeSet::new(), |mut acc, el| {
acc.extend(el.get_variables());
acc
})
}
}
impl<'a> StyleVariableHolder<Cow<'a, str>> for StyleElement<'a> {
fn get_style_variables(&self) -> BTreeSet<Cow<'a, str>> {
match self {
StyleElement::Variable(var) => {
let mut variables = BTreeSet::new();
variables.insert(var.clone());
variables
}
_ => Default::default(),
}
}
}
impl<'a> StyleVariableHolder<Cow<'a, str>> for Vec<StyleElement<'a>> {
fn get_style_variables(&self) -> BTreeSet<Cow<'a, str>> {
self.iter().fold(BTreeSet::new(), |mut acc, el| {
acc.extend(el.get_style_variables());
acc
})
}
}
impl<'a> StyleVariableHolder<Cow<'a, str>> for Vec<FormatElement<'a>> {
fn get_style_variables(&self) -> BTreeSet<Cow<'a, str>> {
self.iter().fold(BTreeSet::new(), |mut acc, el| match el {
FormatElement::TextGroup(textgroup) => {
acc.extend(textgroup.style.get_style_variables());
acc
}
FormatElement::Conditional(format) => {
acc.extend(format.get_style_variables());
acc
}
_ => acc,
})
}
}

View File

@ -6,6 +6,18 @@ use super::model::*;
#[grammar = "formatter/spec.pest"] #[grammar = "formatter/spec.pest"]
struct IdentParser; struct IdentParser;
fn _parse_value(value: Pair<Rule>) -> FormatElement {
match value.as_rule() {
Rule::text => FormatElement::Text(_parse_text(value).into()),
Rule::variable => FormatElement::Variable(_parse_variable(value).into()),
Rule::textgroup => FormatElement::TextGroup(_parse_textgroup(value)),
Rule::conditional => {
FormatElement::Conditional(_parse_format(value.into_inner().next().unwrap()))
}
_ => unreachable!(),
}
}
fn _parse_textgroup(textgroup: Pair<Rule>) -> TextGroup { fn _parse_textgroup(textgroup: Pair<Rule>) -> TextGroup {
let mut inner_rules = textgroup.into_inner(); let mut inner_rules = textgroup.into_inner();
let format = inner_rules.next().unwrap(); let format = inner_rules.next().unwrap();
@ -22,55 +34,32 @@ fn _parse_variable(variable: Pair<Rule>) -> &str {
} }
fn _parse_text(text: Pair<Rule>) -> String { fn _parse_text(text: Pair<Rule>) -> String {
let mut result = String::new(); text.into_inner()
for pair in text.into_inner() { .map(|pair| pair.as_str().chars())
result.push_str(pair.as_str()); .flatten()
} .collect()
result
} }
fn _parse_format(format: Pair<Rule>) -> Vec<FormatElement> { fn _parse_format(format: Pair<Rule>) -> Vec<FormatElement> {
let mut result: Vec<FormatElement> = Vec::new(); format.into_inner().map(_parse_value).collect()
for pair in format.into_inner() {
match pair.as_rule() {
Rule::text => result.push(FormatElement::Text(_parse_text(pair).into())),
Rule::variable => result.push(FormatElement::Variable(_parse_variable(pair).into())),
Rule::textgroup => result.push(FormatElement::TextGroup(_parse_textgroup(pair))),
_ => unreachable!(),
}
}
result
} }
fn _parse_style(style: Pair<Rule>) -> Vec<StyleElement> { fn _parse_style(style: Pair<Rule>) -> Vec<StyleElement> {
let mut result: Vec<StyleElement> = Vec::new(); style
.into_inner()
for pair in style.into_inner() { .map(|pair| match pair.as_rule() {
match pair.as_rule() { Rule::string => StyleElement::Text(pair.as_str().into()),
Rule::text => result.push(StyleElement::Text(_parse_text(pair).into())), Rule::variable => StyleElement::Variable(_parse_variable(pair).into()),
Rule::variable => result.push(StyleElement::Variable(_parse_variable(pair).into())),
_ => unreachable!(), _ => unreachable!(),
} })
} .collect()
result
} }
pub fn parse(format: &str) -> Result<Vec<FormatElement>, Error<Rule>> { pub fn parse(format: &str) -> Result<Vec<FormatElement>, Error<Rule>> {
let pairs = IdentParser::parse(Rule::expression, format)?; IdentParser::parse(Rule::expression, format).map(|pairs| {
let mut result: Vec<FormatElement> = Vec::new(); pairs
.take_while(|pair| pair.as_rule() != Rule::EOI)
// Lifetime of Segment is the same as result .map(_parse_value)
for pair in pairs.take_while(|pair| pair.as_rule() != Rule::EOI) { .collect()
match pair.as_rule() { })
Rule::text => result.push(FormatElement::Text(_parse_text(pair).into())),
Rule::variable => result.push(FormatElement::Variable(_parse_variable(pair).into())),
Rule::textgroup => result.push(FormatElement::TextGroup(_parse_textgroup(pair))),
_ => unreachable!(),
}
}
Ok(result)
} }

View File

@ -1,16 +1,51 @@
// Expression
//
// The expression of the format string.
//
// Should be started with SOI and ended with EOI, with a format string in it.
expression = _{ SOI ~ value* ~ EOI } expression = _{ SOI ~ value* ~ EOI }
value = _{ text | variable | textgroup } value = _{ text | variable | textgroup | conditional }
variable = { "$" ~ variable_name } // Variable
variable_name = @{ char+ } //
// A variable is defined as one of the following:
//
// - A valid variable name followed by a `$` character (`$[a-zA-Z_][a-zA-Z0-9_]*`),
// e.g. `$variable`.
//
// - Some texts wrapped in a curly bracket (`${[^\(\)\[\]\\\${}]+}`),
// e.g. `${env:HOST}`.
variable = { "$" ~ (variable_name | variable_scope) }
variable_name = @{ ('a'..'z' | 'A'..'Z' | "_") ~ char* }
char = _{ 'a'..'z' | 'A'..'Z' | '0'..'9' | "_" } char = _{ 'a'..'z' | 'A'..'Z' | '0'..'9' | "_" }
text = { text_inner+ } variable_scope = _{ "{" ~ variable_scoped_name ~ "}" }
text_inner = _{ text_inner_char | escape } variable_scoped_name = { scoped_char+ }
text_inner_char = { !("[" | "]" | "(" | ")" | "$" | "\\") ~ ANY } scoped_char = _{ !(escaped_char | "{" | "}") ~ ANY }
// Text
//
// Texts can be one of `string` or `escaped_char`, where string is one or more of
// unescapable chars.
//
// This is implemented so as to ensure all functional characters are escaped.
text = { (string | escape)+ }
string = @{ text_inner_char+ }
text_inner_char = { !escaped_char ~ ANY }
escape = _{ "\\" ~ escaped_char } escape = _{ "\\" ~ escaped_char }
escaped_char = { "[" | "]" | "(" | ")" | "\\" | "$" } escaped_char = { "[" | "]" | "(" | ")" | "\\" | "$" }
// TextGroup
//
// A textgroup is a pair of `format` and `style` (`[format](style)`)
//
// - `format`: A format string, can contain any number of variables, texts or textgroups.
// - `style`: A style string, can contain any number of variables or texts.
textgroup = { "[" ~ format ~ "]" ~ "(" ~ style ~ ")" } textgroup = { "[" ~ format ~ "]" ~ "(" ~ style ~ ")" }
format = { (variable | text | textgroup)* } format = { value* }
style = { (variable | text)* } style = { (variable | string)* }
// Conditional
//
// A conditional format string that won't render if all the containing variables are empty.
conditional = { "(" ~ format ~ ")" }

View File

@ -1,7 +1,11 @@
use ansi_term::Style; use ansi_term::Style;
use pest::error::Error; use pest::error::Error as PestError;
use rayon::prelude::*; use rayon::prelude::*;
use std::collections::BTreeMap; use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;
use std::fmt;
use std::iter::FromIterator;
use crate::config::parse_style_string; use crate::config::parse_style_string;
use crate::segment::Segment; use crate::segment::Segment;
@ -10,109 +14,263 @@ use super::model::*;
use super::parser::{parse, Rule}; use super::parser::{parse, Rule};
#[derive(Clone)] #[derive(Clone)]
enum VariableValue { enum VariableValue<'a> {
Plain(String), Plain(Cow<'a, str>),
Styled(Vec<Segment>), Styled(Vec<Segment>),
Meta(Vec<FormatElement<'a>>),
} }
impl Default for VariableValue { impl<'a> Default for VariableValue<'a> {
fn default() -> Self { fn default() -> Self {
VariableValue::Plain(String::new()) VariableValue::Plain(Cow::Borrowed(""))
} }
} }
type VariableMapType = BTreeMap<String, Option<VariableValue>>; type VariableMapType<'a> =
BTreeMap<String, Option<Result<VariableValue<'a>, StringFormatterError>>>;
type StyleVariableMapType<'a> =
BTreeMap<String, Option<Result<Cow<'a, str>, StringFormatterError>>>;
#[derive(Debug, Clone)]
pub enum StringFormatterError {
Custom(String),
Parse(PestError<Rule>),
}
impl fmt::Display for StringFormatterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Custom(error) => write!(f, "{}", error),
Self::Parse(error) => write!(f, "{}", error),
}
}
}
impl Error for StringFormatterError {}
impl From<String> for StringFormatterError {
fn from(error: String) -> Self {
StringFormatterError::Custom(error)
}
}
pub struct StringFormatter<'a> { pub struct StringFormatter<'a> {
format: Vec<FormatElement<'a>>, format: Vec<FormatElement<'a>>,
variables: VariableMapType, variables: VariableMapType<'a>,
style_variables: StyleVariableMapType<'a>,
} }
impl<'a> StringFormatter<'a> { impl<'a> StringFormatter<'a> {
/// Creates an instance of StringFormatter from a format string /// Creates an instance of StringFormatter from a format string
pub fn new(format: &'a str) -> Result<Self, Error<Rule>> { ///
/// This method will throw an Error when the given format string fails to parse.
pub fn new(format: &'a str) -> Result<Self, StringFormatterError> {
parse(format) parse(format)
.map(|format| { .map(|format| {
let variables = _get_variables(&format); // Cache all variables
(format, variables) let variables = VariableMapType::from_iter(
format
.get_variables()
.into_iter()
.map(|key| (key.to_string(), None))
.collect::<Vec<(String, Option<_>)>>(),
);
let style_variables = StyleVariableMapType::from_iter(
format
.get_style_variables()
.into_iter()
.map(|key| (key.to_string(), None))
.collect::<Vec<(String, Option<_>)>>(),
);
(format, variables, style_variables)
}) })
.map(|(format, variables)| Self { format, variables }) .map(|(format, variables, style_variables)| Self {
format,
variables,
style_variables,
})
.map_err(StringFormatterError::Parse)
} }
/// Maps variable name to its value /// Maps variable name to its value
pub fn map(mut self, mapper: impl Fn(&str) -> Option<String> + Sync) -> Self { ///
self.variables.par_iter_mut().for_each(|(key, value)| { /// You should provide a function or closure that accepts the variable name `name: &str` as a
*value = mapper(key).map(VariableValue::Plain); /// parameter and returns the one of the following values:
}); ///
/// - `None`: This variable will be reserved for further mappers. If it is `None` when
/// `self.parse()` is called, it will be dropped.
///
/// - `Some(Err(StringFormatterError))`: This variable will throws `StringFormatterError` when
/// `self.parse()` is called. Return this if some fatal error occurred and the format string
/// should not be rendered.
///
/// - `Some(Ok(_))`: The value of this variable will be displayed in the format string.
///
pub fn map<T, M>(mut self, mapper: M) -> Self
where
T: Into<Cow<'a, str>>,
M: Fn(&str) -> Option<Result<T, StringFormatterError>> + Sync,
{
self.variables
.par_iter_mut()
.filter(|(_, value)| value.is_none())
.for_each(|(key, value)| {
*value = mapper(key).map(|var| var.map(|var| VariableValue::Plain(var.into())));
});
self
}
/// Maps a meta-variable to a format string containing other variables.
///
/// This function should be called **before** other map methods so that variables found in
/// the format strings of meta-variables can be cached properly.
///
/// See `StringFormatter::map` for description on the parameters.
pub fn map_meta<M>(mut self, mapper: M) -> Self
where
M: Fn(&str, &BTreeSet<String>) -> Option<&'a str> + Sync,
{
let variables = self.get_variables();
let (variables, style_variables) = self
.variables
.iter_mut()
.filter(|(_, value)| value.is_none())
.fold(
(VariableMapType::new(), StyleVariableMapType::new()),
|(mut v, mut sv), (key, value)| {
*value = mapper(key, &variables).map(|format| {
StringFormatter::new(format).map(|formatter| {
let StringFormatter {
format,
mut variables,
mut style_variables,
} = formatter;
// Add variables in meta variables to self
v.append(&mut variables);
sv.append(&mut style_variables);
VariableValue::Meta(format)
})
});
(v, sv)
},
);
self.variables.extend(variables);
self.style_variables.extend(style_variables);
self self
} }
/// Maps variable name to an array of segments /// Maps variable name to an array of segments
pub fn map_variables_to_segments( ///
mut self, /// See `StringFormatter::map` for description on the parameters.
mapper: impl Fn(&str) -> Option<Vec<Segment>> + Sync, pub fn map_variables_to_segments<M>(mut self, mapper: M) -> Self
) -> Self { where
self.variables.par_iter_mut().for_each(|(key, value)| { M: Fn(&str) -> Option<Result<Vec<Segment>, StringFormatterError>> + Sync,
*value = mapper(key).map(VariableValue::Styled); {
}); self.variables
.par_iter_mut()
.filter(|(_, value)| value.is_none())
.for_each(|(key, value)| {
*value = mapper(key).map(|var| var.map(VariableValue::Styled));
});
self
}
/// Maps variable name in a style string to its value
///
/// See `StringFormatter::map` for description on the parameters.
pub fn map_style<T, M>(mut self, mapper: M) -> Self
where
T: Into<Cow<'a, str>>,
M: Fn(&str) -> Option<Result<T, StringFormatterError>> + Sync,
{
self.style_variables
.par_iter_mut()
.filter(|(_, value)| value.is_none())
.for_each(|(key, value)| {
*value = mapper(key).map(|var| var.map(|var| var.into()));
});
self self
} }
/// Parse the format string and consume self. /// Parse the format string and consume self.
pub fn parse(self, default_style: Option<Style>) -> Vec<Segment> { ///
/// This method will throw an Error in the following conditions:
///
/// - Format string in meta variables fails to parse
/// - Variable mapper returns an error.
pub fn parse(self, default_style: Option<Style>) -> Result<Vec<Segment>, StringFormatterError> {
fn _parse_textgroup<'a>( fn _parse_textgroup<'a>(
textgroup: TextGroup<'a>, textgroup: TextGroup<'a>,
variables: &'a VariableMapType, variables: &'a VariableMapType<'a>,
) -> Vec<Segment> { style_variables: &'a StyleVariableMapType<'a>,
let style = _parse_style(textgroup.style); ) -> Result<Vec<Segment>, StringFormatterError> {
_parse_format(textgroup.format, style, &variables) let style = _parse_style(textgroup.style, style_variables);
_parse_format(
textgroup.format,
style.transpose()?,
&variables,
&style_variables,
)
} }
fn _parse_style(style: Vec<StyleElement>) -> Option<Style> { fn _parse_style<'a>(
let style_string = style style: Vec<StyleElement>,
.iter() variables: &'a StyleVariableMapType<'a>,
.flat_map(|style| match style { ) -> Option<Result<Style, StringFormatterError>> {
StyleElement::Text(text) => text.as_ref().chars(), let style_strings = style
StyleElement::Variable(variable) => { .into_iter()
log::warn!( .map(|style| match style {
"Variable `{}` monitored in style string, which is not allowed", StyleElement::Text(text) => Ok(text),
&variable StyleElement::Variable(name) => {
); let variable = variables.get(name.as_ref()).unwrap_or(&None);
"".chars() match variable {
Some(style_string) => style_string.clone().map(|string| string),
None => Ok("".into()),
}
} }
}) })
.collect::<String>(); .collect::<Result<Vec<Cow<str>>, StringFormatterError>>();
parse_style_string(&style_string) style_strings
.map(|style_strings| {
let style_string: String =
style_strings.iter().flat_map(|s| s.chars()).collect();
parse_style_string(&style_string)
})
.transpose()
} }
fn _parse_format<'a>( fn _parse_format<'a>(
mut format: Vec<FormatElement<'a>>, format: Vec<FormatElement<'a>>,
style: Option<Style>, style: Option<Style>,
variables: &'a VariableMapType, variables: &'a VariableMapType<'a>,
) -> Vec<Segment> { style_variables: &'a StyleVariableMapType<'a>,
let mut result: Vec<Segment> = Vec::new(); ) -> Result<Vec<Segment>, StringFormatterError> {
let results: Result<Vec<Vec<Segment>>, StringFormatterError> = format
format.reverse(); .into_iter()
while let Some(el) = format.pop() { .map(|el| {
let mut segments = match el { match el {
FormatElement::Text(text) => { FormatElement::Text(text) => Ok(vec![_new_segment("_text", text, style)]),
vec![_new_segment("_text".into(), text.into_owned(), style)] FormatElement::TextGroup(textgroup) => {
} let textgroup = TextGroup {
FormatElement::TextGroup(textgroup) => { format: textgroup.format,
let textgroup = TextGroup { style: textgroup.style,
format: textgroup.format, };
style: textgroup.style, _parse_textgroup(textgroup, &variables, &style_variables)
}; }
_parse_textgroup(textgroup, &variables) FormatElement::Variable(name) => variables
} .get(name.as_ref())
FormatElement::Variable(name) => variables .expect("Uncached variable found")
.get(name.as_ref()) .as_ref()
.map(|segments| { .map(|segments| match segments.clone()? {
let value = segments.clone().unwrap_or_default(); VariableValue::Styled(segments) => Ok(segments
match value {
VariableValue::Styled(segments) => segments
.into_iter() .into_iter()
.map(|mut segment| { .map(|mut segment| {
// Derive upper style if the style of segments are none.
if !segment.has_style() { if !segment.has_style() {
if let Some(style) = style { if let Some(style) = style {
segment.set_style(style); segment.set_style(style);
@ -120,80 +278,124 @@ impl<'a> StringFormatter<'a> {
} }
segment segment
}) })
.collect(), .collect()),
VariableValue::Plain(text) => { VariableValue::Plain(text) => {
vec![_new_segment(name.to_string(), text, style)] Ok(vec![_new_segment(name, text, style)])
} }
VariableValue::Meta(format) => {
let formatter = StringFormatter {
format,
variables: _clone_without_meta(variables),
style_variables: style_variables.clone(),
};
formatter.parse(style)
}
})
.unwrap_or_else(|| Ok(Vec::new())),
FormatElement::Conditional(format) => {
// Show the conditional format string if all the variables inside are not
// none.
fn _should_show_elements<'a>(
format_elements: &[FormatElement],
variables: &'a VariableMapType<'a>,
) -> bool {
format_elements.get_variables().iter().any(|var| {
variables
.get(var.as_ref())
.map(|map_result| {
let map_result = map_result.as_ref();
map_result
.and_then(|result| result.as_ref().ok())
.map(|result| match result {
// If the variable is a meta variable, also
// check the format string inside it.
VariableValue::Meta(meta_elements) => {
let meta_variables =
_clone_without_meta(variables);
_should_show_elements(
&meta_elements,
&meta_variables,
)
}
_ => true,
})
// The variable is None or Err, or a meta variable
// that shouldn't show
.unwrap_or(false)
})
// Can't find the variable in format string
.unwrap_or(false)
})
} }
})
.unwrap_or_default(),
};
result.append(&mut segments);
}
result let should_show: bool = _should_show_elements(&format, variables);
if should_show {
_parse_format(format, style, variables, style_variables)
} else {
Ok(Vec::new())
}
}
}
})
.collect();
Ok(results?.into_iter().flatten().collect())
} }
_parse_format(self.format, default_style, &self.variables) _parse_format(
self.format,
default_style,
&self.variables,
&self.style_variables,
)
} }
} }
/// Extract variable names from an array of `FormatElement` into a `BTreeMap` impl<'a> VariableHolder<String> for StringFormatter<'a> {
fn _get_variables<'a>(format: &[FormatElement<'a>]) -> VariableMapType { fn get_variables(&self) -> BTreeSet<String> {
let mut variables: VariableMapType = Default::default(); BTreeSet::from_iter(self.variables.keys().cloned())
fn _push_variables_from_textgroup<'a>(
variables: &mut VariableMapType,
textgroup: &'a TextGroup<'a>,
) {
for el in &textgroup.format {
match el {
FormatElement::Variable(name) => _push_variable(variables, name.as_ref()),
FormatElement::TextGroup(textgroup) => {
_push_variables_from_textgroup(variables, &textgroup)
}
_ => {}
}
}
for el in &textgroup.style {
if let StyleElement::Variable(name) = el {
_push_variable(variables, name.as_ref())
}
}
} }
}
fn _push_variable<'a>(variables: &mut VariableMapType, name: &'a str) { impl<'a> StyleVariableHolder<String> for StringFormatter<'a> {
variables.insert(name.to_owned(), None); fn get_style_variables(&self) -> BTreeSet<String> {
BTreeSet::from_iter(self.style_variables.keys().cloned())
} }
for el in format {
match el {
FormatElement::Variable(name) => _push_variable(&mut variables, name.as_ref()),
FormatElement::TextGroup(textgroup) => {
_push_variables_from_textgroup(&mut variables, &textgroup)
}
_ => {}
}
}
variables
} }
/// Helper function to create a new segment /// Helper function to create a new segment
fn _new_segment(name: String, value: String, style: Option<Style>) -> Segment { fn _new_segment(
name: impl Into<String>,
value: impl Into<String>,
style: Option<Style>,
) -> Segment {
Segment { Segment {
_name: name, _name: name.into(),
value, value: value.into(),
style, style,
} }
} }
fn _clone_without_meta<'a>(variables: &VariableMapType<'a>) -> VariableMapType<'a> {
VariableMapType::from_iter(variables.iter().map(|(key, value)| {
let value = match value {
Some(Ok(value)) => match value {
VariableValue::Meta(_) => None,
other => Some(Ok(other.clone())),
},
Some(Err(e)) => Some(Err(e.clone())),
None => None,
};
(key.clone(), value)
}))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use ansi_term::Color; use ansi_term::Color;
// match_next(result: Iter<Segment>, value, style) // match_next(result: IterMut<Segment>, value, style)
macro_rules! match_next { macro_rules! match_next {
($iter:ident, $value:literal, $($style:tt)+) => { ($iter:ident, $value:literal, $($style:tt)+) => {
let _next = $iter.next().unwrap(); let _next = $iter.next().unwrap();
@ -202,7 +404,7 @@ mod tests {
} }
} }
fn empty_mapper(_: &str) -> Option<String> { fn empty_mapper(_: &str) -> Option<Result<String, StringFormatterError>> {
None None
} }
@ -212,7 +414,7 @@ mod tests {
let style = Some(Color::Red.bold()); let style = Some(Color::Red.bold());
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let result = formatter.parse(style); let result = formatter.parse(style).unwrap();
let mut result_iter = result.iter(); let mut result_iter = result.iter();
match_next!(result_iter, "text", style); match_next!(result_iter, "text", style);
} }
@ -221,7 +423,7 @@ mod tests {
fn test_textgroup_text_only() { fn test_textgroup_text_only() {
const FORMAT_STR: &str = "[text](red bold)"; const FORMAT_STR: &str = "[text](red bold)";
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let result = formatter.parse(None); let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter(); let mut result_iter = result.iter();
match_next!(result_iter, "text", Some(Color::Red.bold())); match_next!(result_iter, "text", Some(Color::Red.bold()));
} }
@ -233,20 +435,48 @@ mod tests {
let formatter = StringFormatter::new(FORMAT_STR) let formatter = StringFormatter::new(FORMAT_STR)
.unwrap() .unwrap()
.map(|variable| match variable { .map(|variable| match variable {
"var1" => Some("text1".to_owned()), "var1" => Some(Ok("text1".to_owned())),
_ => None, _ => None,
}); });
let result = formatter.parse(None); let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter(); let mut result_iter = result.iter();
match_next!(result_iter, "text1", None); match_next!(result_iter, "text1", None);
} }
#[test]
fn test_variable_in_style() {
const FORMAT_STR: &str = "[root]($style)";
let root_style = Some(Color::Red.bold());
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map_style(|variable| match variable {
"style" => Some(Ok("red bold".to_owned())),
_ => None,
});
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "root", root_style);
}
#[test]
fn test_scoped_variable() {
const FORMAT_STR: &str = "${env:PWD}";
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map(|variable| Some(Ok(format!("${{{}}}", variable))));
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "${env:PWD}", None);
}
#[test] #[test]
fn test_escaped_chars() { fn test_escaped_chars() {
const FORMAT_STR: &str = r#"\\\[\$text\]\(red bold\)"#; const FORMAT_STR: &str = r#"\\\[\$text\]\(red bold\)"#;
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let result = formatter.parse(None); let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter(); let mut result_iter = result.iter();
match_next!(result_iter, r#"\[$text](red bold)"#, None); match_next!(result_iter, r#"\[$text](red bold)"#, None);
} }
@ -259,7 +489,7 @@ mod tests {
let inner_style = Some(Color::Blue.normal()); let inner_style = Some(Color::Blue.normal());
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper); let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let result = formatter.parse(outer_style); let result = formatter.parse(outer_style).unwrap();
let mut result_iter = result.iter(); let mut result_iter = result.iter();
match_next!(result_iter, "outer ", outer_style); match_next!(result_iter, "outer ", outer_style);
match_next!(result_iter, "middle ", middle_style); match_next!(result_iter, "middle ", middle_style);
@ -274,10 +504,10 @@ mod tests {
let formatter = StringFormatter::new(FORMAT_STR) let formatter = StringFormatter::new(FORMAT_STR)
.unwrap() .unwrap()
.map(|variable| match variable { .map(|variable| match variable {
"var" => Some("text".to_owned()), "var" => Some(Ok("text".to_owned())),
_ => None, _ => None,
}); });
let result = formatter.parse(None); let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter(); let mut result_iter = result.iter();
match_next!(result_iter, "text", var_style); match_next!(result_iter, "text", var_style);
} }
@ -292,7 +522,7 @@ mod tests {
let formatter = StringFormatter::new(FORMAT_STR) let formatter = StringFormatter::new(FORMAT_STR)
.unwrap() .unwrap()
.map_variables_to_segments(|variable| match variable { .map_variables_to_segments(|variable| match variable {
"var" => Some(vec![ "var" => Some(Ok(vec![
_new_segment("_1".to_owned(), "styless".to_owned(), None), _new_segment("_1".to_owned(), "styless".to_owned(), None),
_new_segment("_2".to_owned(), "styled".to_owned(), styled_style), _new_segment("_2".to_owned(), "styled".to_owned(), styled_style),
_new_segment( _new_segment(
@ -300,16 +530,133 @@ mod tests {
"styled_no_modifier".to_owned(), "styled_no_modifier".to_owned(),
styled_no_modifier_style, styled_no_modifier_style,
), ),
]), ])),
_ => None, _ => None,
}); });
let result = formatter.parse(None); let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter(); let mut result_iter = result.iter();
match_next!(result_iter, "styless", var_style); match_next!(result_iter, "styless", var_style);
match_next!(result_iter, "styled", styled_style); match_next!(result_iter, "styled", styled_style);
match_next!(result_iter, "styled_no_modifier", styled_no_modifier_style); match_next!(result_iter, "styled_no_modifier", styled_no_modifier_style);
} }
#[test]
fn test_meta_variable() {
const FORMAT_STR: &str = "$all";
const FORMAT_STR__ALL: &str = "$a$b";
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map_meta(|var, _| match var {
"all" => Some(FORMAT_STR__ALL),
_ => None,
})
.map(|var| match var {
"a" => Some(Ok("$a")),
"b" => Some(Ok("$b")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$a", None);
match_next!(result_iter, "$b", None);
}
#[test]
fn test_multiple_mapper() {
const FORMAT_STR: &str = "$a$b$c";
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map(|var| match var {
"a" => Some(Ok("$a")),
"b" => Some(Ok("$b")),
_ => None,
})
.map(|var| match var {
"b" => Some(Ok("$B")),
"c" => Some(Ok("$c")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$a", None);
match_next!(result_iter, "$b", None);
match_next!(result_iter, "$c", None);
}
#[test]
fn test_conditional() {
const FORMAT_STR: &str = "($some) should render but ($none) shouldn't";
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map(|var| match var {
"some" => Some(Ok("$some")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$some", None);
match_next!(result_iter, " should render but ", None);
match_next!(result_iter, " shouldn't", None);
}
#[test]
fn test_nested_conditional() {
const FORMAT_STR: &str = "($some ($none)) and ($none ($some))";
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map(|var| match var {
"some" => Some(Ok("$some")),
_ => None,
});
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "$some", None);
match_next!(result_iter, " ", None);
match_next!(result_iter, " and ", None);
match_next!(result_iter, " ", None);
match_next!(result_iter, "$some", None);
}
#[test]
fn test_conditional_meta_variable() {
const FORMAT_STR: &str = r"(\[$all\]) ";
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map_meta(|var, _| match var {
"all" => Some("$some"),
_ => None,
});
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, " ", None);
}
#[test]
fn test_variable_holder() {
const FORMAT_STR: &str = "($a [($b) $c](none $s)) $d [t]($t)";
let expected_variables =
BTreeSet::from_iter(vec!["a", "b", "c", "d"].into_iter().map(String::from));
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let variables = formatter.get_variables();
assert_eq!(variables, expected_variables);
}
#[test]
fn test_style_variable_holder() {
const FORMAT_STR: &str = "($a [($b) $c](none $s)) $d [t]($t)";
let expected_variables = BTreeSet::from_iter(vec!["s", "t"].into_iter().map(String::from));
let formatter = StringFormatter::new(FORMAT_STR).unwrap().map(empty_mapper);
let variables = formatter.get_style_variables();
assert_eq!(variables, expected_variables);
}
#[test] #[test]
fn test_parse_error() { fn test_parse_error() {
// brackets without escape // brackets without escape
@ -323,4 +670,21 @@ mod tests {
assert!(StringFormatter::new(FORMAT_STR).is_err()); assert!(StringFormatter::new(FORMAT_STR).is_err());
} }
} }
#[test]
fn test_variable_error() {
const FORMAT_STR: &str = "$never$some";
let never_error = StringFormatterError::Custom("NEVER".to_owned());
let segments = StringFormatter::new(FORMAT_STR).and_then(|formatter| {
formatter
.map(|var| match var {
"some" => Some(Ok("some")),
"never" => Some(Err(never_error.clone())),
_ => None,
})
.parse(None)
});
assert!(segments.is_err());
}
} }

View File

@ -1,8 +1,6 @@
use crate::config::SegmentConfig;
use crate::context::Shell; use crate::context::Shell;
use crate::segment::Segment; use crate::segment::Segment;
use crate::utils::wrap_colorseq_for_shell; use crate::utils::wrap_colorseq_for_shell;
use ansi_term::Style;
use ansi_term::{ANSIString, ANSIStrings}; use ansi_term::{ANSIString, ANSIStrings};
use std::fmt; use std::fmt;
@ -66,17 +64,8 @@ pub struct Module<'a> {
/// The module's description /// The module's description
description: String, description: String,
/// The styling to be inherited by all segments contained within this module.
style: Style,
/// The prefix used to separate the current module from the previous one.
prefix: Affix,
/// The collection of segments that compose this module. /// The collection of segments that compose this module.
segments: Vec<Segment>, pub segments: Vec<Segment>,
/// The suffix used to separate the current module from the next one.
suffix: Affix,
} }
impl<'a> Module<'a> { impl<'a> Module<'a> {
@ -86,23 +75,10 @@ impl<'a> Module<'a> {
config, config,
_name: name.to_string(), _name: name.to_string(),
description: desc.to_string(), description: desc.to_string(),
style: Style::default(),
prefix: Affix::default_prefix(name),
segments: Vec::new(), segments: Vec::new(),
suffix: Affix::default_suffix(name),
} }
} }
/// Get a reference to a newly created segment in the module
pub fn create_segment(&mut self, name: &str, segment_config: &SegmentConfig) -> &mut Segment {
let mut segment = Segment::new(name);
segment.set_style(segment_config.style.unwrap_or(self.style));
segment.set_value(segment_config.value);
self.segments.push(segment);
self.segments.last_mut().unwrap()
}
/// Set segments in module /// Set segments in module
pub fn set_segments(&mut self, segments: Vec<Segment>) { pub fn set_segments(&mut self, segments: Vec<Segment>) {
self.segments = segments; self.segments = segments;
@ -123,31 +99,11 @@ impl<'a> Module<'a> {
self.segments.iter().all(|segment| segment.is_empty()) self.segments.iter().all(|segment| segment.is_empty())
} }
/// Get values of the module's segments
pub fn get_segments(&self) -> Vec<&str> { pub fn get_segments(&self) -> Vec<&str> {
self.segments.iter().map(Segment::get_value).collect() self.segments.iter().map(Segment::get_value).collect()
} }
/// Get the module's prefix
pub fn get_prefix(&mut self) -> &mut Affix {
&mut self.prefix
}
/// Get the module's suffix
pub fn get_suffix(&mut self) -> &mut Affix {
&mut self.suffix
}
/// Sets the style of the segment.
///
/// Accepts either `Color` or `Style`.
pub fn set_style<T>(&mut self, style: T) -> &mut Module<'a>
where
T: Into<Style>,
{
self.style = style.into();
self
}
/// Returns a vector of colored ANSIString elements to be later used with /// Returns a vector of colored ANSIString elements to be later used with
/// `ANSIStrings()` to optimize ANSI codes /// `ANSIStrings()` to optimize ANSI codes
pub fn ansi_strings(&self) -> Vec<ANSIString> { pub fn ansi_strings(&self) -> Vec<ANSIString> {
@ -155,26 +111,17 @@ impl<'a> Module<'a> {
} }
pub fn ansi_strings_for_shell(&self, shell: Shell) -> Vec<ANSIString> { pub fn ansi_strings_for_shell(&self, shell: Shell) -> Vec<ANSIString> {
let mut ansi_strings = self let ansi_strings = self
.segments .segments
.iter() .iter()
.map(Segment::ansi_string) .map(Segment::ansi_string)
.collect::<Vec<ANSIString>>(); .collect::<Vec<ANSIString>>();
ansi_strings.insert(0, self.prefix.ansi_string()); match shell {
ansi_strings.push(self.suffix.ansi_string());
ansi_strings = match shell {
Shell::Bash => ansi_strings_modified(ansi_strings, shell), Shell::Bash => ansi_strings_modified(ansi_strings, shell),
Shell::Zsh => ansi_strings_modified(ansi_strings, shell), Shell::Zsh => ansi_strings_modified(ansi_strings, shell),
_ => ansi_strings, _ => ansi_strings,
}; }
ansi_strings
}
pub fn to_string_without_prefix(&self, shell: Shell) -> String {
ANSIStrings(&self.ansi_strings_for_shell(shell)[1..]).to_string()
} }
} }
@ -195,67 +142,6 @@ fn ansi_strings_modified(ansi_strings: Vec<ANSIString>, shell: Shell) -> Vec<ANS
.collect::<Vec<ANSIString>>() .collect::<Vec<ANSIString>>()
} }
/// Module affixes are to be used for the prefix or suffix of a module.
pub struct Affix {
/// The affix's name, to be used in configuration and logging.
_name: String,
/// The affix's style.
style: Style,
/// The string value of the affix.
value: String,
}
impl Affix {
pub fn default_prefix(name: &str) -> Self {
Self {
_name: format!("{}_prefix", name),
style: Style::default(),
value: "via ".to_string(),
}
}
pub fn default_suffix(name: &str) -> Self {
Self {
_name: format!("{}_suffix", name),
style: Style::default(),
value: " ".to_string(),
}
}
/// Sets the style of the module.
///
/// Accepts either `Color` or `Style`.
pub fn set_style<T>(&mut self, style: T) -> &mut Self
where
T: Into<Style>,
{
self.style = style.into();
self
}
/// Sets the value of the module.
pub fn set_value<T>(&mut self, value: T) -> &mut Self
where
T: Into<String>,
{
self.value = value.into();
self
}
/// Generates the colored ANSIString output.
pub fn ansi_string(&self) -> ANSIString {
self.style.paint(&self.value)
}
}
impl fmt::Display for Affix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.ansi_string())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -268,10 +154,7 @@ mod tests {
config: None, config: None,
_name: name.to_string(), _name: name.to_string(),
description: desc.to_string(), description: desc.to_string(),
style: Style::default(),
prefix: Affix::default_prefix(name),
segments: Vec::new(), segments: Vec::new(),
suffix: Affix::default_suffix(name),
}; };
assert!(module.is_empty()); assert!(module.is_empty());
@ -285,10 +168,7 @@ mod tests {
config: None, config: None,
_name: name.to_string(), _name: name.to_string(),
description: desc.to_string(), description: desc.to_string(),
style: Style::default(),
prefix: Affix::default_prefix(name),
segments: vec![Segment::new("test_segment")], segments: vec![Segment::new("test_segment")],
suffix: Affix::default_suffix(name),
}; };
assert!(module.is_empty()); assert!(module.is_empty());

View File

@ -7,7 +7,8 @@ use std::str::FromStr;
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::aws::{AwsConfig, AwsItems}; use crate::configs::aws::AwsConfig;
use crate::formatter::StringFormatter;
type Profile = String; type Profile = String;
type Region = String; type Region = String;
@ -51,73 +52,64 @@ fn get_aws_profile_and_region() -> (Option<Profile>, Option<Region>) {
env::var("AWS_VAULT") env::var("AWS_VAULT")
.or_else(|_| env::var("AWS_PROFILE")) .or_else(|_| env::var("AWS_PROFILE"))
.ok(), .ok(),
env::var("AWS_REGION").ok(), env::var("AWS_DEFAULT_REGION")
env::var("AWS_DEFAULT_REGION").ok(), .or_else(|_| env::var("AWS_REGION"))
.ok(),
) { ) {
(Some(p), Some(_), Some(dr)) => (Some(p), Some(dr)), (Some(p), Some(r)) => (Some(p), Some(r)),
(Some(p), Some(r), None) => (Some(p), Some(r)), (None, Some(r)) => (None, Some(r)),
(None, Some(r), None) => (None, Some(r)), (Some(ref p), None) => (Some(p.to_owned()), get_aws_region_from_config(Some(p))),
(Some(p), None, Some(dr)) => (Some(p), Some(dr)), (None, None) => (None, get_aws_region_from_config(None)),
(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> { fn alias_region(region: String, aliases: &HashMap<String, &str>) -> String {
match ( match aliases.get(&region) {
env::var("AWS_REGION").ok(), None => region,
env::var("AWS_DEFAULT_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),
}
}
fn alias_region(region: &str, aliases: &HashMap<String, &str>) -> String {
match aliases.get(region) {
None => region.to_string(),
Some(alias) => (*alias).to_string(), Some(alias) => (*alias).to_string(),
} }
} }
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
const AWS_PREFIX: &str = "on ";
let mut module = context.new_module("aws"); let mut module = context.new_module("aws");
let config: AwsConfig = AwsConfig::try_load(module.config); let config: AwsConfig = AwsConfig::try_load(module.config);
module.set_style(config.style); let (aws_profile, aws_region) = get_aws_profile_and_region();
if aws_profile.is_none() && aws_region.is_none() {
return None;
}
module.get_prefix().set_value(AWS_PREFIX); let mapped_region = if let Some(aws_region) = aws_region {
Some(alias_region(aws_region, &config.region_aliases))
module.create_segment("symbol", &config.symbol); } else {
match config.displayed_items { None
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()?;
module.create_segment("profile", &config.profile.with_value(&aws_profile));
}
AwsItems::Region => {
let aws_region = alias_region(&get_aws_region()?, &config.region_aliases);
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) Some(module)
} }

View File

@ -1,6 +1,8 @@
use super::{Context, Module, RootModuleConfig, Shell}; use super::{Context, Module, RootModuleConfig, Shell};
use crate::configs::battery::BatteryConfig; use crate::configs::battery::BatteryConfig;
use crate::formatter::StringFormatter;
/// Creates a module for the battery percentage and charging state /// Creates a module for the battery percentage and charging state
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
// TODO: Update when v1.0 printing refactor is implemented to only // 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 BatteryStatus { state, percentage } = battery_status;
let mut module = context.new_module("battery"); 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` // Parse config under `display`.
let display_styles = &battery_config.display; // Select the first style that match the threshold,
let display_style = display_styles // if all thresholds are lower do not display battery module.
let display_style = config
.display
.iter() .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 { // Parse the format string and build the module
// Set style based on percentage match StringFormatter::new(config.format) {
module.set_style(display_style.style); Ok(formatter) => {
module.get_prefix().set_value(""); 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 { match formatter.parse(None) {
battery::State::Full => { Ok(format_string) => {
module.create_segment("full_symbol", &battery_config.full_symbol); module.set_segments(format_string);
} Some(module)
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);
} }
} Err(e) => {
battery::State::Empty => { log::warn!("Cannot parse `battery.format`: {}", e);
if let Some(empty_symbol) = battery_config.empty_symbol { None
module.create_segment("empty_symbol", &empty_symbol);
} }
} }
_ => {
log::debug!("Unhandled battery state `{}`", state);
return None;
}
} }
Err(e) => {
let mut percent_string = Vec::<String>::with_capacity(2); log::warn!("Cannot load `battery.format`: {}", e);
// Round the percentage to a whole number None
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
} }
} }

View File

@ -1,14 +1,15 @@
use super::{Context, Module, RootModuleConfig, Shell}; use super::{Context, Module, RootModuleConfig, Shell};
use crate::configs::character::CharacterConfig; use crate::configs::character::CharacterConfig;
use crate::formatter::StringFormatter;
/// Creates a module for the prompt character /// Creates a module for the prompt character
/// ///
/// The character segment prints an arrow character in a color dependant on the exit- /// The character segment prints an arrow character in a color dependant on the
/// code of the last executed command: /// exit-code of the last executed command:
/// - If the exit-code was "0", the arrow will be formatted with `style_success` /// - If the exit-code was "0", it will be formatted with `success_symbol`
/// (green by default) /// (green arrow by default)
/// - If the exit-code was anything else, the arrow will be formatted with /// - If the exit-code was anything else, it will be formatted with
/// `style_failure` (red by default) /// `error_symbol` (red arrow by default)
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
enum ShellEditMode { enum ShellEditMode {
Normal, Normal,
@ -19,12 +20,11 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("character"); let mut module = context.new_module("character");
let config: CharacterConfig = CharacterConfig::try_load(module.config); let config: CharacterConfig = CharacterConfig::try_load(module.config);
module.get_prefix().set_value("");
let props = &context.properties; 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 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 keymap = props.get("keymap").unwrap_or(&keymap_default);
let exit_success = exit_code == "0"; let exit_success = exit_code == "0";
@ -38,22 +38,33 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
_ => ASSUMED_MODE, _ => ASSUMED_MODE,
}; };
if exit_success { let symbol = match mode {
module.set_style(config.style_success); ShellEditMode::Normal => config.vicmd_symbol,
} else { ShellEditMode::Insert => {
module.set_style(config.style_failure); if exit_success {
}; config.success_symbol
} else {
/* If an error symbol is set in the config, use symbols to indicate config.error_symbol
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) Some(module)
} }

View File

@ -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::configs::cmd_duration::CmdDurationConfig;
use crate::formatter::StringFormatter;
/// Outputs the time it took the last command to execute /// 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; return None;
} }
let config_min = config.min_time as u128; if elapsed < config.min_time as u128 {
return None;
}
let module_color = match elapsed { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
time if time < config_min => return None, formatter
_ => config.style, .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.set_segments(match parsed {
module.create_segment( Ok(segments) => segments,
"cmd_duration", Err(error) => {
&SegmentConfig::new(&render_time(elapsed, config.show_milliseconds)), log::warn!("Error in module `cmd_duration`: \n{}", error);
); return None;
module.get_prefix().set_value(config.prefix); }
});
Some(module) Some(module)
} }

View File

@ -1,10 +1,10 @@
use std::env; use std::env;
use super::{Context, Module}; use super::{Context, Module, RootModuleConfig};
use super::utils::directory::truncate; use super::utils::directory::truncate;
use crate::config::RootModuleConfig;
use crate::configs::conda::CondaConfig; use crate::configs::conda::CondaConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current Conda environment /// 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 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); let conda_env = truncate(conda_env, config.truncation_length);
module.set_style(config.style); 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.create_segment("symbol", &config.symbol); module.set_segments(match parsed {
module.create_segment("environment", &config.environment.with_value(&conda_env)); Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `conda`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::crystal::CrystalConfig; use crate::configs::crystal::CrystalConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Crystal version /// 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 crystal_version = utils::exec_cmd("crystal", &["--version"])?.stdout;
let formatted_version = format_crystal_version(&crystal_version)?;
let mut module = context.new_module("crystal"); let mut module = context.new_module("crystal");
let config: CrystalConfig = CrystalConfig::try_load(module.config); let config: CrystalConfig = CrystalConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("version", &SegmentConfig::new(&formatted_version)); 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) Some(module)
} }

View File

@ -1,10 +1,9 @@
use ansi_term::Color;
use std::io::Write; use std::io::Write;
use std::process::{Command, Output, Stdio}; use std::process::{Command, Output, Stdio};
use super::{Context, Module, RootModuleConfig}; 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 /// 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. /// 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. /// 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( let toml_config = context.config.get_custom_module_config(name).expect(
"modules::custom::module should only be called after ensuring that the module exists", "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 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 { let output = exec_command(config.command, &config.shell.0)?;
module.get_prefix().set_value(prefix);
} let trimmed = output.trim();
if let Some(suffix) = config.suffix { if trimmed.is_empty() {
module.get_suffix().set_value(suffix); return None;
} }
if let Some(symbol) = config.symbol { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("symbol", &symbol); 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)
});
if let Some(output) = exec_command(config.command, &config.shell.0) { module.set_segments(match parsed {
let trimmed = output.trim(); Ok(segments) => segments,
Err(error) => {
if trimmed.is_empty() { log::warn!("Error in module `custom.{}`:\n{}", name, error);
return None; return None;
} }
});
module.create_segment( Some(module)
"output",
&SegmentConfig::new(&trimmed).with_style(Some(style)),
);
Some(module)
} else {
None
}
} }
/// Return the invoking shell, using `shell` and fallbacking in order to STARSHIP_SHELL and "sh" /// Return the invoking shell, using `shell` and fallbacking in order to STARSHIP_SHELL and "sh"

View File

@ -7,8 +7,9 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{Context, Module}; use super::{Context, Module};
use super::utils::directory::truncate; use super::utils::directory::truncate;
use crate::config::{RootModuleConfig, SegmentConfig}; use crate::config::RootModuleConfig;
use crate::configs::directory::DirectoryConfig; use crate::configs::directory::DirectoryConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current directory /// 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 mut module = context.new_module("directory");
let config: DirectoryConfig = DirectoryConfig::try_load(module.config); let config: DirectoryConfig = DirectoryConfig::try_load(module.config);
module.set_style(config.style);
// Using environment PWD is the standard approach for determining logical path // 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 // 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 { 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 // Substitutions could have changed the prefix, so don't allow them and
// fish-style path contraction together // 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 // If user is using fish style path, we need to add the segment first
let contracted_home_dir = contract_path(&current_dir, &home_dir, HOME_SYMBOL); let contracted_home_dir = contract_path(&current_dir, &home_dir, HOME_SYMBOL);
let fish_style_dir = to_fish_style( to_fish_style(
config.fish_style_pwd_dir_length as usize, config.fish_style_pwd_dir_length as usize,
contracted_home_dir, contracted_home_dir,
&truncated_dir_string, &truncated_dir_string,
); )
} else {
String::from("")
};
let final_dir_string = format!("{}{}", fish_prefix, truncated_dir_string);
module.create_segment( let parsed = StringFormatter::new(config.format).and_then(|formatter| {
"path", formatter
&SegmentConfig { .map_style(|variable| match variable {
value: &fish_style_dir, "style" => Some(Ok(config.style)),
style: None, _ => None,
}, })
); .map(|variable| match variable {
} "path" => Some(Ok(&final_dir_string)),
_ => None,
})
.parse(None)
});
module.create_segment( module.set_segments(match parsed {
"path", Ok(segments) => segments,
&SegmentConfig { Err(error) => {
value: &truncated_dir_string, log::warn!("Error in module `directory`:\n{}", error);
style: None, return None;
}, }
); });
module.get_prefix().set_value(config.prefix);
Some(module) Some(module)
} }

View File

@ -1,14 +1,17 @@
use std::env;
use std::path::PathBuf;
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::docker_context::DockerContextConfig; use crate::configs::docker_context::DockerContextConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
const DOCKER_CONFIG_FILE: &str = ".docker/config.json";
/// Creates a module with the currently active Docker context /// Creates a module with the currently active Docker context
/// ///
/// Will display the Docker context if the following criteria are met: /// Will display the Docker context if the following criteria are met:
/// - There is a file named `$HOME/.docker/config.json` /// - There is a file named `$HOME/.docker/config.json`
/// - Or a file named `$DOCKER_CONFIG/config.json`
/// - The file is JSON and contains a field named `currentContext` /// - The file is JSON and contains a field named `currentContext`
/// - The value of `currentContext` is not `default` /// - The value of `currentContext` is not `default`
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
@ -23,9 +26,17 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
{ {
return None; return None;
} }
let docker_config = PathBuf::from(
&env::var_os("DOCKER_CONFIG")
.unwrap_or(dirs_next::home_dir()?.join(".docker").into_os_string()),
)
.join("config.json");
let config_path = dirs_next::home_dir()?.join(DOCKER_CONFIG_FILE); if !docker_config.exists() {
let json = utils::read_file(config_path).ok()?; return None;
}
let json = utils::read_file(docker_config).ok()?;
let parsed_json = serde_json::from_str(&json).ok()?; let parsed_json = serde_json::from_str(&json).ok()?;
match parsed_json { match parsed_json {
@ -33,9 +44,31 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let current_context = root.get("currentContext")?; let current_context = root.get("currentContext")?;
match current_context { match current_context {
serde_json::Value::String(ctx) => { serde_json::Value::String(ctx) => {
module.set_style(config.style); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("symbol", &config.symbol); formatter
module.create_segment("context", &config.context.with_value(&ctx)); .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 {
"context" => Some(Ok(ctx)),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `docker_context`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }
_ => None, _ => None,

View File

@ -1,3 +1,5 @@
use quick_xml::events::Event;
use quick_xml::Reader;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::iter::Iterator; use std::iter::Iterator;
use std::ops::Deref; use std::ops::Deref;
@ -6,6 +8,7 @@ use std::str;
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::dotnet::DotnetConfig; use crate::configs::dotnet::DotnetConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
type JValue = serde_json::Value; type JValue = serde_json::Value;
@ -48,20 +51,90 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
// Internally, this module uses its own mechanism for version detection. // Internally, this module uses its own mechanism for version detection.
// Typically it is twice as fast as running `dotnet --version`. // Typically it is twice as fast as running `dotnet --version`.
let enable_heuristic = config.heuristic; let enable_heuristic = config.heuristic;
let version = if enable_heuristic {
let repo_root = context.get_repo().ok().and_then(|r| r.root.as_deref());
estimate_dotnet_version(&dotnet_files, &context.current_dir, repo_root)?
} else {
get_version_from_cli()?
};
module.set_style(config.style); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("symbol", &config.symbol); formatter
module.create_segment("version", &config.version.with_value(&version.0)); .map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"symbol" => Some(Ok(config.symbol)),
_ => None,
})
.map(|variable| match variable {
"version" => {
let version = if enable_heuristic {
let repo_root = context.get_repo().ok().and_then(|r| r.root.as_deref());
estimate_dotnet_version(&dotnet_files, &context.current_dir, repo_root)
} else {
get_version_from_cli()
};
version.map(|v| Ok(v.0))
}
"tfm" => find_current_tfm(&dotnet_files).map(Ok),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `dotnet`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }
fn find_current_tfm<'a>(files: &[DotNetFile<'a>]) -> Option<String> {
let get_file_of_type = |t: FileType| files.iter().find(|f| f.file_type == t);
let relevant_file = get_file_of_type(FileType::ProjectFile)?;
get_tfm_from_project_file(relevant_file.path)
}
fn get_tfm_from_project_file(path: &Path) -> Option<String> {
let project_file = utils::read_file(path).ok()?;
let mut reader = Reader::from_str(&project_file);
reader.trim_text(true);
let mut in_tfm = false;
let mut buf = Vec::new();
loop {
match reader.read_event(&mut buf) {
// for triggering namespaced events, use this instead:
// match reader.read_namespaced_event(&mut buf) {
Ok(Event::Start(ref e)) => {
// for namespaced:
// Ok((ref namespace_value, Event::Start(ref e)))
match e.name() {
b"TargetFrameworks" => in_tfm = true,
b"TargetFramework" => in_tfm = true,
_ => in_tfm = false,
}
}
// unescape and decode the text event using the reader encoding
Ok(Event::Text(e)) => {
if in_tfm {
return e.unescape_and_decode(&reader).ok();
}
}
Ok(Event::Eof) => break, // exits the loop when reaching end of file
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
_ => (), // There are several other `Event`s we do not consider here
}
// if we don't keep a borrow elsewhere, we can clear the buffer to keep memory usage low
buf.clear();
}
None
}
fn estimate_dotnet_version<'a>( fn estimate_dotnet_version<'a>(
files: &[DotNetFile<'a>], files: &[DotNetFile<'a>],
current_dir: &Path, current_dir: &Path,

View File

@ -1,9 +1,10 @@
use regex::Regex;
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::elixir::ElixirConfig; use crate::configs::elixir::ElixirConfig;
use crate::formatter::StringFormatter;
use crate::utils;
use regex::Regex;
const ELIXIR_VERSION_PATTERN: &str = "\ const ELIXIR_VERSION_PATTERN: &str = "\
Erlang/OTP (?P<otp>\\d+)[^\\n]+ Erlang/OTP (?P<otp>\\d+)[^\\n]+
@ -11,7 +12,7 @@ Elixir (?P<elixir>\\d[.\\d]+).*";
/// Create a module with the current Elixir version /// Create a module with the current Elixir version
/// ///
/// Will display the Rust version if any of the following criteria are met: /// Will display the Elixir version if any of the following criteria are met:
/// - Current directory contains a `mix.exs` file /// - Current directory contains a `mix.exs` file
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let is_elixir_project = context.try_begin_scan()?.set_files(&["mix.exs"]).is_match(); let is_elixir_project = context.try_begin_scan()?.set_files(&["mix.exs"]).is_match();
@ -24,23 +25,36 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("elixir"); let mut module = context.new_module("elixir");
let config = ElixirConfig::try_load(module.config); let config = ElixirConfig::try_load(module.config);
module.set_style(config.style); 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 {
"version" => Some(Ok(&elixir_version)),
"otp_version" => Some(Ok(&otp_version)),
_ => None,
})
.parse(None)
});
module.create_segment("symbol", &config.symbol); module.set_segments(match parsed {
module.create_segment("version", &config.version.with_value(&elixir_version)); Ok(segments) => segments,
module.create_segment( Err(error) => {
"otp_version", log::warn!("Error in module `elixir`:\n{}", error);
&config return None;
.otp_version }
.with_value(&format!(" (OTP {})", otp_version)), });
);
Some(module) Some(module)
} }
fn get_elixir_version() -> Option<(String, String)> { fn get_elixir_version() -> Option<(String, String)> {
use crate::utils;
let output = utils::exec_cmd("elixir", &["--version"])?.stdout; let output = utils::exec_cmd("elixir", &["--version"])?.stdout;
parse_elixir_version(&output) parse_elixir_version(&output)

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::elm::ElmConfig; use crate::configs::elm::ElmConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Elm version /// Creates a module with the current Elm version
@ -24,14 +25,35 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
let elm_version = utils::exec_cmd("elm", &["--version"])?.stdout; let elm_version = utils::exec_cmd("elm", &["--version"])?.stdout;
let formatted_version = Some(format!("v{}", elm_version.trim()))?; let module_version = Some(format!("v{}", elm_version.trim()))?;
let mut module = context.new_module("elm"); let mut module = context.new_module("elm");
let config: ElmConfig = ElmConfig::try_load(module.config); let config: ElmConfig = ElmConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("version", &SegmentConfig::new(&formatted_version)); 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 {
"version" => Some(Ok(&module_version)),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `elm`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,9 +1,10 @@
use std::env; use std::env;
use super::{Context, Module, SegmentConfig}; use super::{Context, Module};
use crate::config::RootModuleConfig; use crate::config::RootModuleConfig;
use crate::configs::env_var::EnvVarConfig; use crate::configs::env_var::EnvVarConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the value of the chosen environment variable /// Creates a module with the value of the chosen environment variable
/// ///
@ -16,17 +17,30 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let config: EnvVarConfig = EnvVarConfig::try_load(module.config); let config: EnvVarConfig = EnvVarConfig::try_load(module.config);
let env_value = get_env_value(config.variable?, config.default)?; let env_value = get_env_value(config.variable?, config.default)?;
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 {
"env_value" => Some(Ok(&env_value)),
_ => None,
})
.parse(None)
});
module.set_style(config.style); module.set_segments(match parsed {
module.get_prefix().set_value("with "); Ok(segments) => segments,
Err(error) => {
if let Some(symbol) = config.symbol { log::warn!("Error in module `env_var`:\n{}", error);
module.create_segment("symbol", &symbol); return None;
} }
});
// TODO: Use native prefix and suffix instead of stacking custom ones together with env_value.
let env_var_stacked = format!("{}{}{}", config.prefix, env_value, config.suffix);
module.create_segment("env_var", &SegmentConfig::new(&env_var_stacked));
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::erlang::ErlangConfig; use crate::configs::erlang::ErlangConfig;
use crate::formatter::StringFormatter;
/// Create a module with the current Erlang version /// Create a module with the current Erlang version
/// ///
@ -17,14 +18,33 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
let erlang_version = get_erlang_version()?;
let mut module = context.new_module("erlang"); let mut module = context.new_module("erlang");
let config = ErlangConfig::try_load(module.config); let config = ErlangConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("version", &config.version.with_value(&erlang_version)); 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" => get_erlang_version().map(Ok),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `erlang`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -3,6 +3,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::git_branch::GitBranchConfig; use crate::configs::git_branch::GitBranchConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the Git branch in the current directory /// Creates a module with the Git branch in the current directory
/// ///
@ -10,15 +11,11 @@ use crate::configs::git_branch::GitBranchConfig;
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("git_branch"); let mut module = context.new_module("git_branch");
let config = GitBranchConfig::try_load(module.config); let config = GitBranchConfig::try_load(module.config);
module.set_style(config.style);
module.get_prefix().set_value("on "); let truncation_symbol = get_first_grapheme(config.truncation_symbol);
let truncation_symbol = get_graphemes(config.truncation_symbol, 1);
module.create_segment("symbol", &config.symbol);
// TODO: Once error handling is implemented, warn the user if their config // TODO: Once error handling is implemented, warn the user if their config
// truncation length is nonsensical // truncation length is nonsensical
let len = if config.truncation_length <= 0 { let len = if config.truncation_length <= 0 {
log::warn!( log::warn!(
"\"truncation_length\" should be a positive value, found {}", "\"truncation_length\" should be a positive value, found {}",
@ -31,29 +28,50 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let repo = context.get_repo().ok()?; let repo = context.get_repo().ok()?;
let branch_name = repo.branch.as_ref()?; let branch_name = repo.branch.as_ref()?;
let truncated_graphemes = get_graphemes(&branch_name, len);
// The truncation symbol should only be added if we truncated
let truncated_and_symbol = if len < graphemes_len(&branch_name) {
truncated_graphemes + &truncation_symbol
} else {
truncated_graphemes
};
module.create_segment( let mut graphemes = get_graphemes(&branch_name);
"name", let trunc_len = len.min(graphemes.len());
&config.branch_name.with_value(&truncated_and_symbol),
); if trunc_len < graphemes.len() {
// The truncation symbol should only be added if we truncate
graphemes[trunc_len] = truncation_symbol;
graphemes.truncate(trunc_len + 1)
}
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 {
"branch" => Some(Ok(graphemes.concat())),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `git_branch`: \n{}", error);
return None;
}
});
Some(module) Some(module)
} }
fn get_graphemes(text: &str, length: usize) -> String { fn get_first_grapheme(text: &str) -> &str {
UnicodeSegmentation::graphemes(text, true) UnicodeSegmentation::graphemes(text, true)
.take(length) .next()
.collect::<Vec<&str>>() .unwrap_or("")
.concat()
} }
fn graphemes_len(text: &str) -> usize { fn get_graphemes(text: &str) -> Vec<&str> {
UnicodeSegmentation::graphemes(&text[..], true).count() UnicodeSegmentation::graphemes(text, true).collect()
} }

View File

@ -2,23 +2,14 @@ use super::{Context, Module, RootModuleConfig};
use git2::Repository; use git2::Repository;
use crate::configs::git_commit::GitCommitConfig; use crate::configs::git_commit::GitCommitConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the Git commit in the current directory /// Creates a module with the Git commit in the current directory
/// ///
/// Will display the commit hash if the current directory is a git repo /// Will display the commit hash if the current directory is a git repo
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("git_commit"); let mut module = context.new_module("git_commit");
let config = GitCommitConfig::try_load(module.config); let config: GitCommitConfig = GitCommitConfig::try_load(module.config);
module
.get_prefix()
.set_value(config.prefix)
.set_style(config.style);
module
.get_suffix()
.set_value(config.suffix)
.set_style(config.style);
module.set_style(config.style);
let repo = context.get_repo().ok()?; let repo = context.get_repo().ok()?;
let repo_root = repo.root.as_ref()?; let repo_root = repo.root.as_ref()?;
@ -32,13 +23,30 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let git_head = git_repo.head().ok()?; let git_head = git_repo.head().ok()?;
let head_commit = git_head.peel_to_commit().ok()?; let head_commit = git_head.peel_to_commit().ok()?;
let commit_oid = head_commit.id(); let commit_oid = head_commit.id();
module.create_segment(
"hash", let parsed = StringFormatter::new(config.format).and_then(|formatter| {
&config.hash.with_value(&id_to_hex_abbrev( formatter
commit_oid.as_bytes(), .map_style(|variable| match variable {
config.commit_hash_length, "style" => Some(Ok(config.style)),
)), _ => None,
); })
.map(|variable| match variable {
"hash" => Some(Ok(id_to_hex_abbrev(
commit_oid.as_bytes(),
config.commit_hash_length,
))),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `git_commit`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,8 +1,9 @@
use git2::RepositoryState; use git2::RepositoryState;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::git_state::GitStateConfig; use crate::configs::git_state::GitStateConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the state of the git repository at the current directory /// Creates a module with the state of the git repository at the current directory
/// ///
@ -12,37 +13,37 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("git_state"); let mut module = context.new_module("git_state");
let config: GitStateConfig = GitStateConfig::try_load(module.config); let config: GitStateConfig = GitStateConfig::try_load(module.config);
module.set_style(config.style);
module.get_prefix().set_value("(");
module.get_suffix().set_value(") ");
let repo = context.get_repo().ok()?; let repo = context.get_repo().ok()?;
let repo_root = repo.root.as_ref()?; let repo_root = repo.root.as_ref()?;
let repo_state = repo.state?; let repo_state = repo.state?;
let state_description = get_state_description(repo_state, repo_root, config); let state_description = get_state_description(repo_state, repo_root, &config)?;
let label = match &state_description { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
StateDescription::Label(label) => label, formatter
StateDescription::LabelAndProgress(label, _) => label, .map_meta(|variable, _| match variable {
StateDescription::Clean => { "state" => Some(state_description.label),
_ => None,
})
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"progress_current" => state_description.current.as_ref().map(Ok),
"progress_total" => state_description.total.as_ref().map(Ok),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `git_state`:\n{}", error);
return None; return None;
} }
}; });
module.create_segment(label.name, &label.segment);
if let StateDescription::LabelAndProgress(_, progress) = &state_description {
module.create_segment(
"progress_current",
&SegmentConfig::new(&format!(" {}", progress.current)),
);
module.create_segment("progress_divider", &SegmentConfig::new("/"));
module.create_segment(
"progress_total",
&SegmentConfig::new(&format!("{}", progress.total)),
);
}
Some(module) Some(module)
} }
@ -53,40 +54,57 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
fn get_state_description<'a>( fn get_state_description<'a>(
state: RepositoryState, state: RepositoryState,
root: &'a std::path::PathBuf, root: &'a std::path::PathBuf,
config: GitStateConfig<'a>, config: &GitStateConfig<'a>,
) -> StateDescription<'a> { ) -> Option<StateDescription<'a>> {
match state { match state {
RepositoryState::Clean => StateDescription::Clean, RepositoryState::Clean => None,
RepositoryState::Merge => StateDescription::Label(StateLabel::new("merge", config.merge)), RepositoryState::Merge => Some(StateDescription {
RepositoryState::Revert => { label: config.merge,
StateDescription::Label(StateLabel::new("revert", config.revert)) current: None,
} total: None,
RepositoryState::RevertSequence => { }),
StateDescription::Label(StateLabel::new("revert", config.revert)) RepositoryState::Revert => Some(StateDescription {
} label: config.revert,
RepositoryState::CherryPick => { current: None,
StateDescription::Label(StateLabel::new("cherry_pick", config.cherry_pick)) total: None,
} }),
RepositoryState::CherryPickSequence => { RepositoryState::RevertSequence => Some(StateDescription {
StateDescription::Label(StateLabel::new("cherry_pick", config.cherry_pick)) label: config.revert,
} current: None,
RepositoryState::Bisect => { total: None,
StateDescription::Label(StateLabel::new("bisect", config.bisect)) }),
} RepositoryState::CherryPick => Some(StateDescription {
RepositoryState::ApplyMailbox => StateDescription::Label(StateLabel::new("am", config.am)), label: config.cherry_pick,
RepositoryState::ApplyMailboxOrRebase => { current: None,
StateDescription::Label(StateLabel::new("am_or_rebase", config.am_or_rebase)) total: None,
} }),
RepositoryState::Rebase => describe_rebase(root, config.rebase), RepositoryState::CherryPickSequence => Some(StateDescription {
RepositoryState::RebaseInteractive => describe_rebase(root, config.rebase), label: config.cherry_pick,
RepositoryState::RebaseMerge => describe_rebase(root, config.rebase), current: None,
total: None,
}),
RepositoryState::Bisect => Some(StateDescription {
label: config.bisect,
current: None,
total: None,
}),
RepositoryState::ApplyMailbox => Some(StateDescription {
label: config.am,
current: None,
total: None,
}),
RepositoryState::ApplyMailboxOrRebase => Some(StateDescription {
label: config.am_or_rebase,
current: None,
total: None,
}),
RepositoryState::Rebase => Some(describe_rebase(root, config.rebase)),
RepositoryState::RebaseInteractive => Some(describe_rebase(root, config.rebase)),
RepositoryState::RebaseMerge => Some(describe_rebase(root, config.rebase)),
} }
} }
fn describe_rebase<'a>( fn describe_rebase<'a>(root: &'a PathBuf, rebase_config: &'a str) -> StateDescription<'a> {
root: &'a PathBuf,
rebase_config: SegmentConfig<'a>,
) -> StateDescription<'a> {
/* /*
* Sadly, libgit2 seems to have some issues with reading the state of * Sadly, libgit2 seems to have some issues with reading the state of
* interactive rebases. So, instead, we'll poke a few of the .git files * interactive rebases. So, instead, we'll poke a few of the .git files
@ -98,12 +116,12 @@ fn describe_rebase<'a>(
let dot_git = root.join(".git"); let dot_git = root.join(".git");
let has_path = |relative_path: &str| { let has_path = |relative_path: &str| {
let path = dot_git.join(Path::new(relative_path)); let path = dot_git.join(PathBuf::from(relative_path));
path.exists() path.exists()
}; };
let file_to_usize = |relative_path: &str| { let file_to_usize = |relative_path: &str| {
let path = dot_git.join(Path::new(relative_path)); let path = dot_git.join(PathBuf::from(relative_path));
let contents = crate::utils::read_file(path).ok()?; let contents = crate::utils::read_file(path).ok()?;
let quantity = contents.trim().parse::<usize>().ok()?; let quantity = contents.trim().parse::<usize>().ok()?;
Some(quantity) Some(quantity)
@ -112,7 +130,7 @@ fn describe_rebase<'a>(
let paths_to_progress = |current_path: &str, total_path: &str| { let paths_to_progress = |current_path: &str, total_path: &str| {
let current = file_to_usize(current_path)?; let current = file_to_usize(current_path)?;
let total = file_to_usize(total_path)?; let total = file_to_usize(total_path)?;
Some(StateProgress { current, total }) Some((current, total))
}; };
let progress = if has_path("rebase-merge") { let progress = if has_path("rebase-merge") {
@ -123,32 +141,15 @@ fn describe_rebase<'a>(
None None
}; };
match progress { StateDescription {
None => StateDescription::Label(StateLabel::new("rebase", rebase_config)), label: rebase_config,
Some(progress) => { current: Some(format!("{}", progress.unwrap().0)),
StateDescription::LabelAndProgress(StateLabel::new("rebase", rebase_config), progress) total: Some(format!("{}", progress.unwrap().1)),
}
} }
} }
enum StateDescription<'a> { struct StateDescription<'a> {
Clean, label: &'a str,
Label(StateLabel<'a>), current: Option<String>,
LabelAndProgress(StateLabel<'a>, StateProgress), total: Option<String>,
}
struct StateLabel<'a> {
name: &'static str,
segment: SegmentConfig<'a>,
}
struct StateProgress {
current: usize,
total: usize,
}
impl<'a> StateLabel<'a> {
fn new(name: &'static str, segment: SegmentConfig<'a>) -> Self {
Self { name, segment }
}
} }

View File

@ -2,10 +2,13 @@ use git2::{Repository, Status};
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::config::SegmentConfig; use crate::configs::git_status::GitStatusConfig;
use crate::configs::git_status::{CountConfig, GitStatusConfig}; use crate::context::Repo;
use std::borrow::BorrowMut; use crate::formatter::StringFormatter;
use std::collections::HashMap; use crate::segment::Segment;
use std::sync::{Arc, RwLock};
const ALL_STATUS_FORMAT: &str = "$conflicted$stashed$deleted$renamed$modified$staged$untracked";
/// Creates a module with the Git branch in the current directory /// Creates a module with the Git branch in the current directory
/// ///
@ -23,174 +26,233 @@ use std::collections::HashMap;
/// - `✘` — A file's deletion has been added to the staging area /// - `✘` — A file's deletion has been added to the staging area
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let repo = context.get_repo().ok()?; let repo = context.get_repo().ok()?;
let branch_name = repo.branch.as_ref()?; let info = Arc::new(GitStatusInfo::load(repo));
let repo_root = repo.root.as_ref()?;
let mut repository = Repository::open(repo_root).ok()?;
let mut module = context.new_module("git_status"); let mut module = context.new_module("git_status");
let config: GitStatusConfig = GitStatusConfig::try_load(module.config); let config: GitStatusConfig = GitStatusConfig::try_load(module.config);
module let parsed = StringFormatter::new(config.format).and_then(|formatter| {
.get_prefix() formatter
.set_value(config.prefix) .map_meta(|variable, _| match variable {
.set_style(config.style); "all_status" => Some(ALL_STATUS_FORMAT),
module _ => None,
.get_suffix() })
.set_value(config.suffix) .map_style(|variable: &str| match variable {
.set_style(config.style); "style" => Some(Ok(config.style)),
module.set_style(config.style); _ => None,
})
.map_variables_to_segments(|variable: &str| {
let info = Arc::clone(&info);
let segments = match variable {
"stashed" => info.get_stashed().and_then(|count| {
format_count(config.stashed, "git_status.stashed", count)
}),
"ahead_behind" => info.get_ahead_behind().and_then(|(ahead, behind)| {
if ahead > 0 && behind > 0 {
format_text(config.diverged, "git_status.diverged", |variable| {
match variable {
"ahead_count" => Some(ahead.to_string()),
"behind_count" => Some(behind.to_string()),
_ => None,
}
})
} else if ahead > 0 && behind == 0 {
format_count(config.ahead, "git_status.ahead", ahead)
} else if behind > 0 && ahead == 0 {
format_count(config.behind, "git_status.behind", behind)
} else {
None
}
}),
"conflicted" => info.get_conflicted().and_then(|count| {
format_count(config.conflicted, "git_status.conflicted", count)
}),
"deleted" => info.get_deleted().and_then(|count| {
format_count(config.deleted, "git_status.deleted", count)
}),
"renamed" => info.get_renamed().and_then(|count| {
format_count(config.renamed, "git_status.renamed", count)
}),
"modified" => info.get_modified().and_then(|count| {
format_count(config.modified, "git_status.modified", count)
}),
"staged" => info
.get_staged()
.and_then(|count| format_count(config.staged, "git_status.staged", count)),
"untracked" => info.get_untracked().and_then(|count| {
format_count(config.untracked, "git_status.untracked", count)
}),
_ => None,
};
segments.map(Ok)
})
.parse(None)
});
let repo_status = get_repo_status(repository.borrow_mut()); module.set_segments(match parsed {
log::debug!("Repo status: {:?}", repo_status); Ok(segments) => {
if segments.is_empty() {
let ahead_behind = get_ahead_behind(&repository, branch_name); return None;
if ahead_behind == Ok((0, 0)) { } else {
log::trace!("No ahead/behind found"); segments
} else {
log::debug!("Repo ahead/behind: {:?}", ahead_behind);
}
// Add the conflicted segment
if let Ok(repo_status) = repo_status {
create_segment_with_count(
&mut module,
"conflicted",
repo_status.conflicted,
&config.conflicted,
config.conflicted_count,
);
}
// Add the ahead/behind segment
if let Ok((ahead, behind)) = ahead_behind {
let add_ahead = |m: &mut Module<'a>| {
create_segment_with_count(
m,
"ahead",
ahead,
&config.ahead,
CountConfig {
enabled: config.show_sync_count,
style: None,
},
);
};
let add_behind = |m: &mut Module<'a>| {
create_segment_with_count(
m,
"behind",
behind,
&config.behind,
CountConfig {
enabled: config.show_sync_count,
style: None,
},
);
};
if ahead > 0 && behind > 0 {
module.create_segment("diverged", &config.diverged);
if config.show_sync_count {
add_ahead(&mut module);
add_behind(&mut module);
} }
} }
Err(error) => {
if ahead > 0 && behind == 0 { log::warn!("Error in module `git_status`:\n{}", error);
add_ahead(&mut module); return None;
} }
});
if behind > 0 && ahead == 0 {
add_behind(&mut module);
}
}
// Add the stashed segment
if let Ok(repo_status) = repo_status {
create_segment_with_count(
&mut module,
"stashed",
repo_status.stashed,
&config.stashed,
config.stashed_count,
);
}
// Add all remaining status segments
if let Ok(repo_status) = repo_status {
create_segment_with_count(
&mut module,
"deleted",
repo_status.deleted,
&config.deleted,
config.deleted_count,
);
create_segment_with_count(
&mut module,
"renamed",
repo_status.renamed,
&config.renamed,
config.renamed_count,
);
create_segment_with_count(
&mut module,
"modified",
repo_status.modified,
&config.modified,
config.modified_count,
);
create_segment_with_count(
&mut module,
"staged",
repo_status.staged,
&config.staged,
config.staged_count,
);
create_segment_with_count(
&mut module,
"untracked",
repo_status.untracked,
&config.untracked,
config.untracked_count,
);
}
if module.is_empty() {
return None;
}
Some(module) Some(module)
} }
fn create_segment_with_count<'a>( struct GitStatusInfo<'a> {
module: &mut Module<'a>, repo: &'a Repo,
name: &str, ahead_behind: RwLock<Option<Result<(usize, usize), git2::Error>>>,
count: usize, repo_status: RwLock<Option<Result<RepoStatus, git2::Error>>>,
config: &SegmentConfig<'a>, stashed_count: RwLock<Option<Result<usize, git2::Error>>>,
count_config: CountConfig, }
) {
if count > 0 {
module.create_segment(name, &config);
if count_config.enabled { impl<'a> GitStatusInfo<'a> {
module.create_segment( pub fn load(repo: &'a Repo) -> Self {
&format!("{}_count", name), Self {
&SegmentConfig::new(&count.to_string()).with_style(count_config.style), repo,
); ahead_behind: RwLock::new(None),
repo_status: RwLock::new(None),
stashed_count: RwLock::new(None),
} }
} }
fn get_branch_name(&self) -> String {
self.repo
.branch
.clone()
.unwrap_or_else(|| String::from("master"))
}
fn get_repository(&self) -> Option<Repository> {
// bare repos don't have a branch name, so `repo.branch.as_ref` would return None,
// but git treats "master" as the default branch name
let repo_root = self.repo.root.as_ref()?;
Repository::open(repo_root).ok()
}
pub fn get_ahead_behind(&self) -> Option<(usize, usize)> {
{
let data = self.ahead_behind.read().unwrap();
if let Some(result) = data.as_ref() {
return match result.as_ref() {
Ok(ahead_behind) => Some(*ahead_behind),
Err(error) => {
log::warn!("Warn: get_ahead_behind: {}", error);
None
}
};
};
}
{
let repo = self.get_repository()?;
let branch_name = self.get_branch_name();
let mut data = self.ahead_behind.write().unwrap();
*data = Some(get_ahead_behind(&repo, &branch_name));
match data.as_ref().unwrap() {
Ok(ahead_behind) => Some(*ahead_behind),
Err(error) => {
log::warn!("Warn: get_ahead_behind: {}", error);
None
}
}
}
}
pub fn get_repo_status(&self) -> Option<RepoStatus> {
{
let data = self.repo_status.read().unwrap();
if let Some(result) = data.as_ref() {
return match result.as_ref() {
Ok(repo_status) => Some(*repo_status),
Err(error) => {
log::warn!("Warn: get_repo_status: {}", error);
None
}
};
};
}
{
let mut repo = self.get_repository()?;
let mut data = self.repo_status.write().unwrap();
*data = Some(get_repo_status(&mut repo));
match data.as_ref().unwrap() {
Ok(repo_status) => Some(*repo_status),
Err(error) => {
log::warn!("Warn: get_repo_status: {}", error);
None
}
}
}
}
pub fn get_stashed(&self) -> Option<usize> {
{
let data = self.stashed_count.read().unwrap();
if let Some(result) = data.as_ref() {
return match result.as_ref() {
Ok(stashed_count) => Some(*stashed_count),
Err(error) => {
log::warn!("Warn: get_stashed_count: {}", error);
None
}
};
};
}
{
let mut repo = self.get_repository()?;
let mut data = self.stashed_count.write().unwrap();
*data = Some(get_stashed_count(&mut repo));
match data.as_ref().unwrap() {
Ok(stashed_count) => Some(*stashed_count),
Err(error) => {
log::warn!("Warn: get_stashed_count: {}", error);
None
}
}
}
}
pub fn get_conflicted(&self) -> Option<usize> {
self.get_repo_status().map(|data| data.conflicted)
}
pub fn get_deleted(&self) -> Option<usize> {
self.get_repo_status().map(|data| data.deleted)
}
pub fn get_renamed(&self) -> Option<usize> {
self.get_repo_status().map(|data| data.renamed)
}
pub fn get_modified(&self) -> Option<usize> {
self.get_repo_status().map(|data| data.modified)
}
pub fn get_staged(&self) -> Option<usize> {
self.get_repo_status().map(|data| data.staged)
}
pub fn get_untracked(&self) -> Option<usize> {
self.get_repo_status().map(|data| data.untracked)
}
} }
/// Gets the number of files in various git states (staged, modified, deleted, etc...) /// Gets the number of files in various git states (staged, modified, deleted, etc...)
fn get_repo_status(repository: &mut Repository) -> Result<RepoStatus, git2::Error> { fn get_repo_status(repository: &mut Repository) -> Result<RepoStatus, git2::Error> {
let mut status_options = git2::StatusOptions::new(); let mut status_options = git2::StatusOptions::new();
let mut repo_status = RepoStatus::default();
match repository.config()?.get_entry("status.showUntrackedFiles") { match repository.config()?.get_entry("status.showUntrackedFiles") {
Ok(entry) => status_options.include_untracked(entry.value() != Some("no")), Ok(entry) => status_options.include_untracked(entry.value() != Some("no")),
_ => status_options.include_untracked(true), _ => status_options.include_untracked(true),
@ -201,76 +263,21 @@ fn get_repo_status(repository: &mut Repository) -> Result<RepoStatus, git2::Erro
.renames_index_to_workdir(true) .renames_index_to_workdir(true)
.include_unmodified(true); .include_unmodified(true);
let statuses: Vec<Status> = repository let statuses = repository.statuses(Some(&mut status_options))?;
.statuses(Some(&mut status_options))?
.iter()
.map(|s| s.status())
.collect();
if statuses.is_empty() { if statuses.is_empty() {
return Err(git2::Error::from_str("Repo has no status")); return Err(git2::Error::from_str("Repo has no status"));
} }
let statuses_count = count_statuses(statuses); statuses
.iter()
let repo_status: RepoStatus = RepoStatus { .map(|s| s.status())
conflicted: *statuses_count.get("conflicted").unwrap_or(&0), .for_each(|status| repo_status.add(status));
deleted: *statuses_count.get("deleted").unwrap_or(&0),
renamed: *statuses_count.get("renamed").unwrap_or(&0),
modified: *statuses_count.get("modified").unwrap_or(&0),
staged: *statuses_count.get("staged").unwrap_or(&0),
untracked: *statuses_count.get("untracked").unwrap_or(&0),
stashed: stashed_count(repository)?,
};
Ok(repo_status) Ok(repo_status)
} }
fn count_statuses(statuses: Vec<Status>) -> HashMap<&'static str, usize> { fn get_stashed_count(repository: &mut Repository) -> Result<usize, git2::Error> {
let mut predicates: HashMap<&'static str, fn(git2::Status) -> bool> = HashMap::new();
predicates.insert("conflicted", is_conflicted);
predicates.insert("deleted", is_deleted);
predicates.insert("renamed", is_renamed);
predicates.insert("modified", is_modified);
predicates.insert("staged", is_staged);
predicates.insert("untracked", is_untracked);
statuses.iter().fold(HashMap::new(), |mut map, status| {
for (key, predicate) in predicates.iter() {
if predicate(*status) {
let entry = map.entry(key).or_insert(0);
*entry += 1;
}
}
map
})
}
fn is_conflicted(status: Status) -> bool {
status.is_conflicted()
}
fn is_deleted(status: Status) -> bool {
status.is_wt_deleted() || status.is_index_deleted()
}
fn is_renamed(status: Status) -> bool {
status.is_wt_renamed() || status.is_index_renamed()
}
fn is_modified(status: Status) -> bool {
status.is_wt_modified()
}
fn is_staged(status: Status) -> bool {
status.is_index_modified() || status.is_index_new()
}
fn is_untracked(status: Status) -> bool {
status.is_wt_new()
}
fn stashed_count(repository: &mut Repository) -> Result<usize, git2::Error> {
let mut count = 0; let mut count = 0;
repository.stash_foreach(|_, _, _| { repository.stash_foreach(|_, _, _| {
count += 1; count += 1;
@ -303,5 +310,65 @@ struct RepoStatus {
modified: usize, modified: usize,
staged: usize, staged: usize,
untracked: usize, untracked: usize,
stashed: usize, }
impl RepoStatus {
fn is_conflicted(status: Status) -> bool {
status.is_conflicted()
}
fn is_deleted(status: Status) -> bool {
status.is_wt_deleted() || status.is_index_deleted()
}
fn is_renamed(status: Status) -> bool {
status.is_wt_renamed() || status.is_index_renamed()
}
fn is_modified(status: Status) -> bool {
status.is_wt_modified()
}
fn is_staged(status: Status) -> bool {
status.is_index_modified() || status.is_index_new()
}
fn is_untracked(status: Status) -> bool {
status.is_wt_new()
}
fn add(&mut self, s: Status) {
self.conflicted += RepoStatus::is_conflicted(s) as usize;
self.deleted += RepoStatus::is_deleted(s) as usize;
self.renamed += RepoStatus::is_renamed(s) as usize;
self.modified += RepoStatus::is_modified(s) as usize;
self.staged += RepoStatus::is_staged(s) as usize;
self.untracked += RepoStatus::is_untracked(s) as usize;
}
}
fn format_text<F>(format_str: &str, config_path: &str, mapper: F) -> Option<Vec<Segment>>
where
F: Fn(&str) -> Option<String> + Send + Sync,
{
if let Ok(formatter) = StringFormatter::new(format_str) {
formatter
.map(|variable| mapper(variable).map(Ok))
.parse(None)
.ok()
} else {
log::error!("Error parsing format string `{}`", &config_path);
None
}
}
fn format_count(format_str: &str, config_path: &str, count: usize) -> Option<Vec<Segment>> {
if count == 0 {
return None;
}
format_text(format_str, config_path, |variable| match variable {
"count" => Some(count.to_string()),
_ => None,
})
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::go::GoConfig; use crate::configs::go::GoConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Go version /// Creates a module with the current Go version
@ -34,14 +35,33 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
let mut module = context.new_module("golang"); let mut module = context.new_module("golang");
let config: GoConfig = GoConfig::try_load(module.config); let config = GoConfig::try_load(module.config);
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 {
"version" => {
format_go_version(&utils::exec_cmd("go", &["version"])?.stdout.as_str()).map(Ok)
}
_ => None,
})
.parse(None)
});
module.set_style(config.style); module.set_segments(match parsed {
module.create_segment("symbol", &config.symbol); Ok(segments) => segments,
Err(error) => {
let formatted_version = log::warn!("Error in module `golang`:\n{}", error);
format_go_version(&utils::exec_cmd("go", &["version"])?.stdout.as_str())?; return None;
module.create_segment("version", &config.version.with_value(&formatted_version)); }
});
Some(module) Some(module)
} }

View File

@ -3,6 +3,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::hg_branch::HgBranchConfig; use crate::configs::hg_branch::HgBranchConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the Hg bookmark or branch in the current directory /// Creates a module with the Hg bookmark or branch in the current directory
/// ///
@ -15,12 +16,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
let mut module = context.new_module("hg_branch"); let mut module = context.new_module("hg_branch");
let config = HgBranchConfig::try_load(module.config); let config: HgBranchConfig = HgBranchConfig::try_load(module.config);
module.set_style(config.style);
module.get_prefix().set_value("on ");
module.create_segment("symbol", &config.symbol);
// TODO: Once error handling is implemented, warn the user if their config // TODO: Once error handling is implemented, warn the user if their config
// truncation length is nonsensical // truncation length is nonsensical
@ -46,10 +42,30 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
truncated_graphemes truncated_graphemes
}; };
module.create_segment( let parsed = StringFormatter::new(config.format).and_then(|formatter| {
"name", formatter
&config.branch_name.with_value(&truncated_and_symbol), .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 {
"branch" => Some(Ok(truncated_and_symbol.as_str())),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `hg_branch`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,10 +1,11 @@
use std::env; use std::env;
use super::{Context, Module, SegmentConfig}; use super::{Context, Module};
use std::ffi::OsString; use std::ffi::OsString;
use crate::config::RootModuleConfig; use crate::config::RootModuleConfig;
use crate::configs::hostname::HostnameConfig; use crate::configs::hostname::HostnameConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the system hostname /// Creates a module with the system hostname
/// ///
@ -42,10 +43,26 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
host.as_ref() host.as_ref()
}; };
module.set_style(config.style); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
let hostname_stacked = format!("{}{}{}", config.prefix, host, config.suffix); formatter
module.create_segment("hostname", &SegmentConfig::new(&hostname_stacked)); .map_style(|variable| match variable {
module.get_prefix().set_value("on "); "style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"hostname" => Some(Ok(host)),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `hostname`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use crate::configs::java::JavaConfig; use crate::configs::java::JavaConfig;
use crate::formatter::StringFormatter;
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::modules::utils::java_version_parser; use crate::modules::utils::java_version_parser;
use crate::utils; use crate::utils;
@ -25,12 +26,31 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(java_version) => { Some(java_version) => {
let mut module = context.new_module("java"); let mut module = context.new_module("java");
let config: JavaConfig = JavaConfig::try_load(module.config); let config: JavaConfig = JavaConfig::try_load(module.config);
module.set_style(config.style);
let formatted_version = format_java_version(java_version)?; let formatted_version = format_java_version(java_version)?;
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(|var, _| match var {
"symbol" => Some(config.symbol),
_ => None,
})
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"version" => Some(Ok(&formatted_version)),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `java`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }
None => None, None => None,

View File

@ -1,14 +1,12 @@
use super::{Context, Module}; use super::{Context, Module, RootModuleConfig};
use crate::config::{RootModuleConfig, SegmentConfig};
use crate::configs::jobs::JobsConfig; use crate::configs::jobs::JobsConfig;
use crate::formatter::StringFormatter;
/// Creates a segment to show if there are any active jobs running /// Creates a segment to show if there are any active jobs running
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("jobs"); let mut module = context.new_module("jobs");
let config: JobsConfig = JobsConfig::try_load(module.config); let config = JobsConfig::try_load(module.config);
module.set_style(config.style);
let props = &context.properties; let props = &context.properties;
let num_of_jobs = props let num_of_jobs = props
@ -20,11 +18,37 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
if num_of_jobs == 0 { if num_of_jobs == 0 {
return None; return None;
} }
module.create_segment("symbol", &config.symbol);
if num_of_jobs > config.threshold { let module_number = if num_of_jobs > config.threshold {
module.create_segment("number", &SegmentConfig::new(&num_of_jobs.to_string())); num_of_jobs.to_string()
} } else {
module.get_prefix().set_value(""); "".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 {
"number" => Some(Ok(module_number.clone())),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `jobs`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::julia::JuliaConfig; use crate::configs::julia::JuliaConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Julia version /// Creates a module with the current Julia version
@ -21,14 +22,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
let mut module = context.new_module("julia"); let mut module = context.new_module("julia");
let config: JuliaConfig = JuliaConfig::try_load(module.config); let config = JuliaConfig::try_load(module.config);
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 {
"version" => {
format_julia_version(&utils::exec_cmd("julia", &["--version"])?.stdout.as_str())
.map(Ok)
}
_ => None,
})
.parse(None)
});
module.set_style(config.style); module.set_segments(match parsed {
module.create_segment("symbol", &config.symbol); Ok(segments) => segments,
Err(error) => {
let formatted_version = log::warn!("Error in module `julia`:\n{}", error);
format_julia_version(&utils::exec_cmd("julia", &["--version"])?.stdout.as_str())?; return None;
module.create_segment("version", &config.version.with_value(&formatted_version)); }
});
Some(module) Some(module)
} }

View File

@ -6,10 +6,9 @@ use std::path;
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::kubernetes::KubernetesConfig; use crate::configs::kubernetes::KubernetesConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
const KUBERNETES_PREFIX: &str = "on ";
fn get_kube_context(contents: &str) -> Option<(String, String)> { fn get_kube_context(contents: &str) -> Option<(String, String)> {
let yaml_docs = YamlLoader::load_from_str(&contents).ok()?; let yaml_docs = YamlLoader::load_from_str(&contents).ok()?;
if yaml_docs.is_empty() { if yaml_docs.is_empty() {
@ -63,23 +62,44 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
}; };
module.set_style(config.style); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.get_prefix().set_value(KUBERNETES_PREFIX); 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 {
"context" => match config.context_aliases.get(&kube_ctx) {
None => Some(Ok(kube_ctx.as_str())),
Some(&alias) => Some(Ok(alias)),
},
_ => None,
})
.map(|variable| match variable {
"namespace" => {
if kube_ns != "" {
Some(Ok(kube_ns.as_str()))
} else {
None
}
}
_ => None,
})
.parse(None)
});
module.create_segment("symbol", &config.symbol); module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `kubernetes`: \n{}", error);
return None;
}
});
let displayed_context = match config.context_aliases.get(&kube_ctx) {
None => &kube_ctx,
Some(&alias) => alias,
};
module.create_segment("context", &config.context.with_value(&displayed_context));
if kube_ns != "" {
module.create_segment(
"namespace",
&config.namespace.with_value(&format!(" ({})", kube_ns)),
);
}
Some(module) Some(module)
} }
None => None, None => None,

View File

@ -1,5 +1,5 @@
use super::{Context, Module}; use super::{Context, Module};
use crate::config::SegmentConfig; use crate::segment::Segment;
/// Creates a module for the line break /// Creates a module for the line break
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
@ -7,10 +7,11 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("line_break"); let mut module = context.new_module("line_break");
module.get_prefix().set_value(""); module.set_segments(vec![Segment {
module.get_suffix().set_value(""); _name: "line_break".to_string(),
style: None,
module.create_segment("character", &SegmentConfig::new(LINE_ENDING)); value: LINE_ENDING.to_string(),
}]);
Some(module) Some(module)
} }

View File

@ -4,6 +4,7 @@ use sysinfo::{RefreshKind, SystemExt};
use super::{Context, Module, RootModuleConfig, Shell}; use super::{Context, Module, RootModuleConfig, Shell};
use crate::configs::memory_usage::MemoryConfig; use crate::configs::memory_usage::MemoryConfig;
use crate::formatter::StringFormatter;
fn format_kib(n_kib: u64) -> String { fn format_kib(n_kib: u64) -> String {
let byte = Byte::from_unit(n_kib as f64, ByteUnit::KiB).unwrap_or_else(|_| Byte::from_bytes(0)); let byte = Byte::from_unit(n_kib as f64, ByteUnit::KiB).unwrap_or_else(|_| Byte::from_bytes(0));
@ -12,6 +13,14 @@ fn format_kib(n_kib: u64) -> String {
display_bytes display_bytes
} }
fn format_pct(pct_number: f64, pct_sign: &str) -> String {
format!("{:.0}{}", pct_number, pct_sign)
}
fn format_usage_total(usage: u64, total: u64) -> String {
format!("{}/{}", format_kib(usage), format_kib(total))
}
/// Creates a module with system memory usage information /// Creates a module with system memory usage information
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("memory_usage"); let mut module = context.new_module("memory_usage");
@ -19,7 +28,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
// TODO: Update when v1.0 printing refactor is implemented to only // TODO: Update when v1.0 printing refactor is implemented to only
// print escapes in a prompt context. // print escapes in a prompt context.
let percent_sign = match context.shell { let pct_sign = match context.shell {
Shell::Zsh => "%%", // % is an escape in zsh, see PROMPT in `man zshmisc` Shell::Zsh => "%%", // % is an escape in zsh, see PROMPT in `man zshmisc`
_ => "%", _ => "%",
}; };
@ -28,54 +37,52 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
let system = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory()); let system = sysinfo::System::new_with_specifics(RefreshKind::new().with_memory());
let used_memory_kib = system.get_used_memory(); let used_memory_kib = system.get_used_memory();
let total_memory_kib = system.get_total_memory(); let total_memory_kib = system.get_total_memory();
let ram_used = (used_memory_kib as f64 / total_memory_kib as f64) * 100.;
let percent_mem_used = (used_memory_kib as f64 / total_memory_kib as f64) * 100.; let ram_pct = format_pct(ram_used, pct_sign);
let threshold = config.threshold; let threshold = config.threshold;
if ram_used.round() < threshold as f64 {
if percent_mem_used.round() < threshold as f64 {
return None; return None;
} }
let show_percentage = config.show_percentage; let ram = format_usage_total(used_memory_kib, total_memory_kib);
let ram = if show_percentage {
format!("{:.0}{}", percent_mem_used, percent_sign)
} else {
format!(
"{}/{}",
format_kib(used_memory_kib),
format_kib(total_memory_kib)
)
};
module.create_segment("ram", &config.ram.with_value(&ram));
// swap only shown if enabled and there is swap on the system
let total_swap_kib = system.get_total_swap(); let total_swap_kib = system.get_total_swap();
if config.show_swap && total_swap_kib > 0 { let used_swap_kib = system.get_used_swap();
let used_swap_kib = system.get_used_swap(); let percent_swap_used = (used_swap_kib as f64 / total_swap_kib as f64) * 100.;
let percent_swap_used = (used_swap_kib as f64 / total_swap_kib as f64) * 100.; let swap_pct = format_pct(percent_swap_used, pct_sign);
let swap = format_usage_total(used_swap_kib, total_swap_kib);
let swap = if show_percentage { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
format!("{:.0}{}", percent_swap_used, percent_sign) formatter
} else { .map_meta(|var, _| match var {
format!( "symbol" => Some(config.symbol),
"{}/{}", _ => None,
format_kib(used_swap_kib), })
format_kib(total_swap_kib) .map_style(|variable| match variable {
) "style" => Some(Ok(config.style)),
}; _ => None,
})
.map(|variable| match variable {
"ram" => Some(Ok(&ram)),
"ram_pct" => Some(Ok(&ram_pct)),
// swap only shown if there is swap on the system
"swap" if total_swap_kib > 0 => Some(Ok(&swap)),
"swap_pct" if total_swap_kib > 0 => Some(Ok(&swap_pct)),
_ => None,
})
.parse(None)
});
module.create_segment("separator", &config.separator); module.set_segments(match parsed {
module.create_segment("swap", &config.swap.with_value(&swap)); Ok(segments) => segments,
} Err(error) => {
log::warn!("Error in module `memory_usage`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -45,7 +45,7 @@ mod zig;
#[cfg(feature = "battery")] #[cfg(feature = "battery")]
mod battery; mod battery;
use crate::config::{RootModuleConfig, SegmentConfig}; use crate::config::RootModuleConfig;
use crate::context::{Context, Shell}; use crate::context::{Context, Shell};
use crate::module::Module; use crate::module::Module;

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::nim::NimConfig; use crate::configs::nim::NimConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Nim version /// Creates a module with the current Nim version
@ -19,16 +20,38 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
let nim_version_output = utils::exec_cmd("nim", &["--version"])?.stdout;
let formatted_nim_version = format!("v{}", parse_nim_version(&nim_version_output)?);
let mut module = context.new_module("nim"); let mut module = context.new_module("nim");
let config = NimConfig::try_load(module.config); let config = NimConfig::try_load(module.config);
module.set_style(config.style); 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 {
"version" => utils::exec_cmd("nim", &["--version"])
.map(|command_output| command_output.stdout)
.and_then(|nim_version_output| {
Some(format!("v{}", parse_nim_version(&nim_version_output)?))
})
.map(Ok),
_ => None,
})
.parse(None)
});
module.create_segment("symbol", &config.symbol); module.set_segments(match parsed {
module.create_segment("version", &SegmentConfig::new(&formatted_nim_version)); Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `nim`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,11 +1,9 @@
use std::env; use std::env;
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::nix_shell::NixShellConfig; use crate::configs::nix_shell::NixShellConfig;
use crate::formatter::StringFormatter;
// IN_NIX_SHELL should be "pure" or "impure" but lorri uses "1" for "impure"
// https://github.com/target/lorri/issues/140
/// Creates a module showing if inside a nix-shell /// Creates a module showing if inside a nix-shell
/// ///
@ -13,43 +11,53 @@ use crate::configs::nix_shell::NixShellConfig;
/// determine if it's inside a nix-shell and the name of it. /// determine if it's inside a nix-shell and the name of it.
/// ///
/// The following options are availables: /// The following options are availables:
/// - use_name (bool) // print the name of the nix-shell
/// - impure_msg (string) // change the impure msg /// - impure_msg (string) // change the impure msg
/// - pure_msg (string) // change the pure msg /// - pure_msg (string) // change the pure msg
/// ///
/// Will display the following: /// Will display the following:
/// - name (pure) // use_name == true in a pure nix-shell /// - pure (name) // $name == "name" in a pure nix-shell
/// - name (impure) // use_name == true in an impure nix-shell /// - impure (name) // $name == "name" in an impure nix-shell
/// - pure // use_name == false in a pure nix-shell /// - pure // $name == "" in a pure nix-shell
/// - impure // use_name == false in an impure nix-shell /// - impure // $name == "" in an impure nix-shell
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("nix_shell"); let mut module = context.new_module("nix_shell");
let config: NixShellConfig = NixShellConfig::try_load(module.config); let config: NixShellConfig = NixShellConfig::try_load(module.config);
module.set_style(config.style); let shell_name = env::var("name").ok();
module.create_segment("symbol", &config.symbol);
let shell_type = env::var("IN_NIX_SHELL").ok()?; let shell_type = env::var("IN_NIX_SHELL").ok()?;
let shell_type_segment: SegmentConfig = match shell_type.as_ref() { let shell_type_format = match shell_type.as_ref() {
"1" | "impure" => config.impure_msg, "impure" => config.impure_msg,
"pure" => config.pure_msg, "pure" => config.pure_msg,
_ => { _ => {
return None; return None;
} }
}; };
if config.use_name { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
if let Ok(name) = env::var("name") { formatter
module.create_segment( .map_meta(|variable, _| match variable {
"nix_shell", "symbol" => Some(config.symbol),
&shell_type_segment.with_value(&format!("{} ({})", name, shell_type_segment.value)), "state" => Some(shell_type_format),
); _ => None,
} else { })
module.create_segment("nix_shell", &shell_type_segment); .map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"name" => shell_name.as_ref().map(Ok),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `nix_shell`:\n{}", error);
return None;
} }
} else { });
module.create_segment("nix_shell", &shell_type_segment);
}
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::nodejs::NodejsConfig; use crate::configs::nodejs::NodejsConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Node.js version /// Creates a module with the current Node.js version
@ -27,16 +28,32 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
let node_version = utils::exec_cmd("node", &["--version"])?.stdout;
let mut module = context.new_module("nodejs"); let mut module = context.new_module("nodejs");
let config: NodejsConfig = NodejsConfig::try_load(module.config); let config = NodejsConfig::try_load(module.config);
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 {
"version" => Some(Ok(utils::exec_cmd("node", &["--version"])?.stdout)),
_ => None,
})
.parse(None)
});
module.set_style(config.style); module.set_segments(match parsed {
Ok(segments) => segments,
let formatted_version = node_version.trim(); Err(error) => {
module.create_segment("symbol", &config.symbol); log::warn!("Error in module `nodejs`:\n{}", error);
module.create_segment("version", &SegmentConfig::new(formatted_version)); return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::ocaml::OCamlConfig; use crate::configs::ocaml::OCamlConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current OCaml version /// Creates a module with the current OCaml version
@ -35,14 +36,33 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
utils::exec_cmd("ocaml", &["-vnum"])?.stdout utils::exec_cmd("ocaml", &["-vnum"])?.stdout
}; };
let formatted_version = format!("v{}", &ocaml_version.trim());
let mut module = context.new_module("ocaml"); let mut module = context.new_module("ocaml");
let config = OCamlConfig::try_load(module.config); let config: OCamlConfig = OCamlConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("version", &SegmentConfig::new(&formatted_version)); 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" => Some(Ok(format!("v{}", &ocaml_version))),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `ocaml`: \n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,33 +1,47 @@
use std::path::PathBuf; use std::path::PathBuf;
use super::{Context, Module}; use super::{Context, Module, RootModuleConfig};
use crate::configs::package::PackageConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
use regex::Regex; use regex::Regex;
use serde_json as json; use serde_json as json;
use super::{RootModuleConfig, SegmentConfig};
use crate::configs::package::PackageConfig;
/// Creates a module with the current package version /// Creates a module with the current package version
/// ///
/// Will display if a version is defined for your Node.js or Rust project (if one exists) /// Will display if a version is defined for your Node.js or Rust project (if one exists)
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("package"); let mut module = context.new_module("package");
let config: PackageConfig = PackageConfig::try_load(module.config); let config: PackageConfig = PackageConfig::try_load(module.config);
let module_version = get_package_version(&context.current_dir, &config)?;
match get_package_version(&context.current_dir, &config) { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
Some(package_version) => { formatter
module.set_style(config.style); .map_meta(|var, _| match var {
module.get_prefix().set_value("is "); "symbol" => Some(config.symbol),
_ => None,
})
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"version" => Some(Ok(&module_version)),
_ => None,
})
.parse(None)
});
module.create_segment("symbol", &config.symbol); module.set_segments(match parsed {
module.create_segment("version", &SegmentConfig::new(&package_version)); Ok(segments) => segments,
Err(error) => {
Some(module) log::warn!("Error in module `package`:\n{}", error);
return None;
} }
None => None, });
}
Some(module)
} }
fn extract_cargo_version(file_contents: &str) -> Option<String> { fn extract_cargo_version(file_contents: &str) -> Option<String> {

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::php::PhpConfig; use crate::configs::php::PhpConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current PHP version /// Creates a module with the current PHP version
@ -27,16 +28,33 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
], ],
) { ) {
Some(php_cmd_output) => { Some(php_cmd_output) => {
let php_version = php_cmd_output.stdout;
let mut module = context.new_module("php"); let mut module = context.new_module("php");
let config: PhpConfig = PhpConfig::try_load(module.config); let config: PhpConfig = PhpConfig::try_load(module.config);
module.set_style(config.style); 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_php_version(&php_cmd_output.stdout).map(Ok),
_ => None,
})
.parse(None)
});
let formatted_version = format_php_version(&php_version)?; module.set_segments(match parsed {
module.create_segment("symbol", &config.symbol); Ok(segments) => segments,
module.create_segment("version", &SegmentConfig::new(&formatted_version)); Err(error) => {
log::warn!("Error in module `php`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::purescript::PureScriptConfig; use crate::configs::purescript::PureScriptConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current PureScript version /// Creates a module with the current PureScript version
@ -20,14 +21,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
let purs_version = utils::exec_cmd("purs", &["--version"])?.stdout; let purs_version = utils::exec_cmd("purs", &["--version"])?.stdout;
let formatted_version = Some(format!("v{}", purs_version.trim()))?;
let mut module = context.new_module("purescript"); let mut module = context.new_module("purescript");
let config: PureScriptConfig = PureScriptConfig::try_load(module.config); let config: PureScriptConfig = PureScriptConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("version", &SegmentConfig::new(&formatted_version)); 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" => Some(Ok(format!("v{}", purs_version.trim()))),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `purescript`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,8 +1,9 @@
use std::env; use std::env;
use std::path::Path; use std::path::Path;
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::python::PythonConfig; use crate::configs::python::PythonConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Python version /// Creates a module with the current Python version
@ -41,25 +42,39 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
module.set_style(config.style); let python_version = if config.pyenv_version_name {
module.create_segment("symbol", &config.symbol); utils::exec_cmd("pyenv", &["version-name"])?.stdout
if config.pyenv_version_name {
let python_version = utils::exec_cmd("pyenv", &["version-name"])?.stdout;
module.create_segment("pyenv_prefix", &config.pyenv_prefix);
module.create_segment("version", &SegmentConfig::new(&python_version.trim()));
} else { } else {
let python_version = get_python_version(&config.python_binary)?; let version = get_python_version(&config.python_binary)?;
let formatted_version = format_python_version(&python_version); format_python_version(&version)
module.create_segment("version", &SegmentConfig::new(&formatted_version));
}; };
let virtual_env = get_python_virtual_env();
if let Some(virtual_env) = get_python_virtual_env() { let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment( formatter
"virtualenv", .map_meta(|var, _| match var {
&SegmentConfig::new(&format!(" ({})", virtual_env)), "symbol" => Some(config.symbol),
); _ => None,
}; })
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"version" => Some(Ok(python_version.trim())),
"virtualenv" => virtual_env.as_ref().map(|e| Ok(e.trim())),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `python`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::ruby::RubyConfig; use crate::configs::ruby::RubyConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Ruby version /// Creates a module with the current Ruby version
@ -19,15 +20,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
let ruby_version = utils::exec_cmd("ruby", &["-v"])?.stdout;
let formatted_version = format_ruby_version(&ruby_version)?;
let mut module = context.new_module("ruby"); let mut module = context.new_module("ruby");
let config: RubyConfig = RubyConfig::try_load(module.config); let config = RubyConfig::try_load(module.config);
module.set_style(config.style); 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 {
"version" => {
format_ruby_version(&utils::exec_cmd("ruby", &["-v"])?.stdout.as_str()).map(Ok)
}
_ => None,
})
.parse(None)
});
module.create_segment("symbol", &config.symbol); module.set_segments(match parsed {
module.create_segment("version", &SegmentConfig::new(&formatted_version)); Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `ruby`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -5,6 +5,7 @@ use std::{env, fs};
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::rust::RustConfig; use crate::configs::rust::RustConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current Rust version /// Creates a module with the current Rust version
/// ///
@ -22,6 +23,39 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None; return None;
} }
let mut module = context.new_module("rust");
let config = RustConfig::try_load(module.config);
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`.
"version" => get_module_version(context).map(Ok),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `rust`:\n{}", error);
return None;
}
});
Some(module)
}
fn get_module_version(context: &Context) -> Option<String> {
// `$CARGO_HOME/bin/rustc(.exe) --version` may attempt installing a rustup toolchain. // `$CARGO_HOME/bin/rustc(.exe) --version` may attempt installing a rustup toolchain.
// https://github.com/starship/starship/issues/417 // https://github.com/starship/starship/issues/417
// //
@ -56,14 +90,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
format_rustc_version(execute_rustc_version()?) format_rustc_version(execute_rustc_version()?)
}; };
let mut module = context.new_module("rust"); Some(module_version)
let config = RustConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
module.create_segment("version", &config.version.with_value(&module_version));
Some(module)
} }
fn env_rustup_toolchain() -> Option<String> { fn env_rustup_toolchain() -> Option<String> {

View File

@ -1,28 +1,44 @@
use std::env; use std::env;
use super::{Context, Module, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::config::RootModuleConfig;
use crate::configs::singularity::SingularityConfig; use crate::configs::singularity::SingularityConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current Singularity image /// Creates a module with the current Singularity image
/// ///
/// Will display the Singularity image if `$SINGULARITY_NAME` is set. /// Will display the Singularity image if `$SINGULARITY_NAME` is set.
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let singularity_env = env::var("SINGULARITY_NAME").unwrap_or_else(|_| "".into()); let singularity_env = env::var("SINGULARITY_NAME").ok();
if singularity_env.trim().is_empty() { singularity_env.as_ref()?;
return None;
}
let mut module = context.new_module("singularity"); let mut module = context.new_module("singularity");
let config = SingularityConfig::try_load(module.config); let config: SingularityConfig = SingularityConfig::try_load(module.config);
module.get_prefix().set_value(config.label); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.set_style(config.style); formatter
module.create_segment("symbol", &config.symbol); .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 {
"env" => singularity_env.as_ref().map(Ok),
_ => None,
})
.parse(None)
});
let env_var_stacked = format!("{}{}{}", config.prefix, singularity_env, config.suffix); module.set_segments(match parsed {
module.create_segment("singularity", &SegmentConfig::new(&env_var_stacked)); Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `singularity`: \n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,7 +1,9 @@
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::terraform::TerraformConfig; use crate::configs::terraform::TerraformConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
use std::env; use std::env;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
@ -25,20 +27,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("terraform"); let mut module = context.new_module("terraform");
let config: TerraformConfig = TerraformConfig::try_load(module.config); let config: TerraformConfig = TerraformConfig::try_load(module.config);
module.set_style(config.style); let parsed = StringFormatter::new(config.format).and_then(|formatter| {
module.create_segment("symbol", &config.symbol); 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_terraform_version(
&utils::exec_cmd("terraform", &["version"])?.stdout.as_str(),
)
.map(Ok),
"workspace" => get_terraform_workspace(&context.current_dir).map(Ok),
_ => None,
})
.parse(None)
});
if config.show_version { module.set_segments(match parsed {
let terraform_version = Ok(segments) => segments,
format_terraform_version(&utils::exec_cmd("terraform", &["version"])?.stdout.as_str())?; Err(error) => {
module.create_segment("version", &config.version.with_value(&terraform_version)); log::warn!("Error in module `terraform`:\n{}", error);
} return None;
}
let terraform_workspace = &get_terraform_workspace(&context.current_dir)?; });
module.create_segment(
"workspace",
&config.workspace.with_value(&terraform_workspace),
);
Some(module) Some(module)
} }

View File

@ -1,14 +1,11 @@
use chrono::{DateTime, FixedOffset, Local, NaiveTime, Utc}; use chrono::{DateTime, FixedOffset, Local, NaiveTime, Utc};
use super::{Context, Module}; use super::{Context, Module, RootModuleConfig};
use crate::config::{RootModuleConfig, SegmentConfig};
use crate::configs::time::TimeConfig; use crate::configs::time::TimeConfig;
use crate::formatter::StringFormatter;
/// Outputs the current time /// Outputs the current time
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
const TIME_PREFIX: &str = "at ";
let mut module = context.new_module("time"); let mut module = context.new_module("time");
let config: TimeConfig = TimeConfig::try_load(module.config); let config: TimeConfig = TimeConfig::try_load(module.config);
if config.disabled { if config.disabled {
@ -23,7 +20,7 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
} }
let default_format = if config.use_12hr { "%r" } else { "%T" }; let default_format = if config.use_12hr { "%r" } else { "%T" };
let time_format = config.format.unwrap_or(default_format); let time_format = config.time_format.unwrap_or(default_format);
log::trace!( log::trace!(
"Timer module is enabled with format string: {}", "Timer module is enabled with format string: {}",
@ -44,17 +41,26 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
format_time(&time_format, Local::now()) format_time(&time_format, Local::now())
}; };
module.set_style(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 {
"time" => Some(Ok(&formatted_time_string)),
_ => None,
})
.parse(None)
});
module.get_prefix().set_value(TIME_PREFIX); module.set_segments(match parsed {
Ok(segments) => segments,
module.create_segment( Err(error) => {
"time", log::warn!("Error in module `time`: \n{}", error);
&SegmentConfig { return None;
value: &formatted_time_string, }
style: None, });
},
);
Some(module) Some(module)
} }

View File

@ -1,8 +1,9 @@
use std::env; use std::env;
use super::{Context, Module, RootModuleConfig, SegmentConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::username::UsernameConfig; use crate::configs::username::UsernameConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current user's username /// Creates a module with the current user's username
@ -23,13 +24,32 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let config: UsernameConfig = UsernameConfig::try_load(module.config); let config: UsernameConfig = UsernameConfig::try_load(module.config);
if user != logname || ssh_connection.is_some() || user_uid == ROOT_UID || config.show_always { if user != logname || ssh_connection.is_some() || user_uid == ROOT_UID || config.show_always {
let module_style = match user_uid { let username = user?;
Some(0) => config.style_root, let parsed = StringFormatter::new(config.format).and_then(|formatter| {
_ => config.style_user, formatter
}; .map_style(|variable| match variable {
"style" => {
module.set_style(module_style); let module_style = match user_uid {
module.create_segment("username", &SegmentConfig::new(&user?)); Some(0) => config.style_root,
_ => config.style_user,
};
Some(Ok(module_style))
}
_ => None,
})
.map(|variable| match variable {
"user" => Some(Ok(&username)),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `username`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} else { } else {

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig}; use super::{Context, Module, RootModuleConfig};
use crate::configs::zig::ZigConfig; use crate::configs::zig::ZigConfig;
use crate::formatter::StringFormatter;
use crate::utils; use crate::utils;
/// Creates a module with the current Zig version /// Creates a module with the current Zig version
@ -26,10 +27,30 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("zig"); let mut module = context.new_module("zig");
let config = ZigConfig::try_load(module.config); let config = ZigConfig::try_load(module.config);
module.set_style(config.style); 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" => Some(Ok(zig_version.clone())),
_ => None,
})
.parse(None)
});
module.create_segment("symbol", &config.symbol); module.set_segments(match parsed {
module.create_segment("version", &config.version.with_value(&zig_version)); Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `zig`:\n{}", error);
return None;
}
});
Some(module) Some(module)
} }

View File

@ -1,14 +1,18 @@
use ansi_term::ANSIStrings; use ansi_term::ANSIStrings;
use clap::ArgMatches; use clap::ArgMatches;
use rayon::prelude::*; use rayon::prelude::*;
use std::collections::BTreeSet;
use std::fmt::{self, Debug, Write as FmtWrite}; use std::fmt::{self, Debug, Write as FmtWrite};
use std::io::{self, Write}; use std::io::{self, Write};
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
use crate::configs::PROMPT_ORDER;
use crate::context::{Context, Shell}; use crate::context::{Context, Shell};
use crate::formatter::{StringFormatter, VariableHolder};
use crate::module::Module; use crate::module::Module;
use crate::module::ALL_MODULES; use crate::module::ALL_MODULES;
use crate::modules; use crate::modules;
use crate::segment::Segment;
pub fn prompt(args: ArgMatches) { pub fn prompt(args: ArgMatches) {
let context = Context::new(args); let context = Context::new(args);
@ -21,34 +25,53 @@ pub fn get_prompt(context: Context) -> String {
let config = context.config.get_root_config(); let config = context.config.get_root_config();
let mut buf = String::new(); let mut buf = String::new();
// Write a new line before the prompt
if config.add_newline {
writeln!(buf).unwrap();
}
// A workaround for a fish bug (see #739,#279). Applying it to all shells // A workaround for a fish bug (see #739,#279). Applying it to all shells
// breaks things (see #808,#824,#834). Should only be printed in fish. // breaks things (see #808,#824,#834). Should only be printed in fish.
if let Shell::Fish = context.shell { if let Shell::Fish = context.shell {
buf.push_str("\x1b[J"); // An ASCII control code to clear screen buf.push_str("\x1b[J"); // An ASCII control code to clear screen
} }
let modules = compute_modules(&context); let formatter = if let Ok(formatter) = StringFormatter::new(config.format) {
formatter
let mut print_without_prefix = true; } else {
let printable = modules.iter(); log::error!("Error parsing `format`");
buf.push_str(">");
for module in printable { return buf;
// Skip printing the prefix of a module after the line_break };
if print_without_prefix { let modules = formatter.get_variables();
let module_without_prefix = module.to_string_without_prefix(context.shell); let formatter = formatter.map_variables_to_segments(|module| {
write!(buf, "{}", module_without_prefix).unwrap() // Make $all display all modules
if module == "all" {
Some(Ok(PROMPT_ORDER
.par_iter()
.flat_map(|module| {
handle_module(module, &context, &modules)
.into_iter()
.flat_map(|module| module.segments)
.collect::<Vec<Segment>>()
})
.collect::<Vec<_>>()))
} else if context.is_module_disabled_in_config(&module) {
None
} else { } else {
let module = module.ansi_strings_for_shell(context.shell); // Get segments from module
write!(buf, "{}", ANSIStrings(&module)).unwrap(); Some(Ok(handle_module(module, &context, &modules)
.into_iter()
.flat_map(|module| module.segments)
.collect::<Vec<Segment>>()))
} }
});
print_without_prefix = module.get_name() == "line_break" // Creates a root module and prints it.
} let mut root_module = Module::new("Starship Root", "The root module", None);
root_module.set_segments(
formatter
.parse(None)
.expect("Unexpected error returned in root format variables"),
);
let module_strings = root_module.ansi_strings_for_shell(context.shell);
write!(buf, "{}", ANSIStrings(&module_strings)).unwrap();
buf buf
} }
@ -72,16 +95,15 @@ pub fn explain(args: ArgMatches) {
desc: String, desc: String,
} }
let dont_print = vec!["line_break", "character"]; let dont_print = vec!["line_break"];
let modules = compute_modules(&context) let modules = compute_modules(&context)
.into_iter() .into_iter()
.filter(|module| !dont_print.contains(&module.get_name().as_str())) .filter(|module| !dont_print.contains(&module.get_name().as_str()))
.map(|module| { .map(|module| {
let ansi_strings = module.ansi_strings();
let value = module.get_segments().join(""); let value = module.get_segments().join("");
ModuleInfo { ModuleInfo {
value: ansi_term::ANSIStrings(&ansi_strings[1..ansi_strings.len() - 1]).to_string(), value: ansi_term::ANSIStrings(&module.ansi_strings()).to_string(),
value_len: value.chars().count() + count_wide_chars(&value), value_len: value.chars().count() + count_wide_chars(&value),
desc: module.get_description().to_owned(), desc: module.get_description().to_owned(),
} }
@ -132,11 +154,38 @@ pub fn explain(args: ArgMatches) {
} }
fn compute_modules<'a>(context: &'a Context) -> Vec<Module<'a>> { fn compute_modules<'a>(context: &'a Context) -> Vec<Module<'a>> {
enum Mod<'a> { let mut prompt_order: Vec<Module<'a>> = Vec::new();
Builtin(&'a str),
Custom(&'a str), let config = context.config.get_root_config();
let formatter = if let Ok(formatter) = StringFormatter::new(config.format) {
formatter
} else {
log::error!("Error parsing `format`");
return Vec::new();
};
let modules = formatter.get_variables();
for module in &modules {
// Manually add all modules if `$all` is encountered
if module == "all" {
for module in PROMPT_ORDER.iter() {
let modules = handle_module(module, &context, &modules);
prompt_order.extend(modules.into_iter());
}
} else {
let modules = handle_module(module, &context, &modules);
prompt_order.extend(modules.into_iter());
}
} }
prompt_order
}
fn handle_module<'a>(
module: &str,
context: &'a Context,
module_list: &BTreeSet<String>,
) -> Vec<Module<'a>> {
struct DebugCustomModules<'tmp>(&'tmp toml::value::Table); struct DebugCustomModules<'tmp>(&'tmp toml::value::Table);
impl Debug for DebugCustomModules<'_> { impl Debug for DebugCustomModules<'_> {
@ -145,74 +194,63 @@ fn compute_modules<'a>(context: &'a Context) -> Vec<Module<'a>> {
} }
} }
let mut prompt_order: Vec<Mod> = Vec::new(); let mut modules: Vec<Option<Module>> = Vec::new();
// Write out a custom prompt order if ALL_MODULES.contains(&module) {
let config_prompt_order = context.config.get_root_config().prompt_order; // Write out a module if it isn't disabled
if !context.is_module_disabled_in_config(module) {
for module in &config_prompt_order { modules.push(modules::handle(module, &context));
if ALL_MODULES.contains(module) {
// Write out a module if it isn't disabled
if !context.is_module_disabled_in_config(*module) {
prompt_order.push(Mod::Builtin(module));
}
} else if *module == "custom" {
// Write out all custom modules, except for those that are explicitly set
if let Some(custom_modules) = context.config.get_custom_modules() {
for (custom_module, config) in custom_modules {
if should_add_implicit_custom_module(
custom_module,
config,
&config_prompt_order,
) {
prompt_order.push(Mod::Custom(custom_module));
}
}
}
} else if module.starts_with("custom.") {
// Write out a custom module if it isn't disabled (and it exists...)
match context.is_custom_module_disabled_in_config(&module[7..]) {
Some(true) => (), // Module is disabled, we don't add it to the prompt
Some(false) => prompt_order.push(Mod::Custom(&module[7..])),
None => match context.config.get_custom_modules() {
Some(modules) => log::debug!(
"prompt_order contains custom module \"{}\", but no configuration was provided. Configuration for the following modules were provided: {:?}",
module,
DebugCustomModules(modules),
),
None => log::debug!(
"prompt_order contains custom module \"{}\", but no configuration was provided.",
module,
),
},
}
} else {
log::debug!(
"Expected prompt_order to contain value from {:?}. Instead received {}",
ALL_MODULES,
module,
);
} }
} else if module == "custom" {
// Write out all custom modules, except for those that are explicitly set
if let Some(custom_modules) = context.config.get_custom_modules() {
let custom_modules = custom_modules
.iter()
.map(|(custom_module, config)| {
if should_add_implicit_custom_module(custom_module, config, &module_list) {
modules::custom::module(custom_module, &context)
} else {
None
}
})
.collect::<Vec<Option<Module<'a>>>>();
modules.extend(custom_modules)
}
} else if module.starts_with("custom.") {
// Write out a custom module if it isn't disabled (and it exists...)
match context.is_custom_module_disabled_in_config(&module[7..]) {
Some(true) => (), // Module is disabled, we don't add it to the prompt
Some(false) => modules.push(modules::custom::module(&module[7..], &context)),
None => match context.config.get_custom_modules() {
Some(modules) => log::debug!(
"prompt_order contains custom module \"{}\", but no configuration was provided. Configuration for the following modules were provided: {:?}",
module,
DebugCustomModules(modules),
),
None => log::debug!(
"prompt_order contains custom module \"{}\", but no configuration was provided.",
module,
),
},
}
} else {
log::debug!(
"Expected prompt_order to contain value from {:?}. Instead received {}",
ALL_MODULES,
module,
);
} }
prompt_order modules.into_iter().flatten().collect()
.par_iter()
.map(|module| match module {
Mod::Builtin(builtin) => modules::handle(builtin, context),
Mod::Custom(custom) => modules::custom::module(custom, context),
}) // Compute segments
.flatten() // Remove segments set to `None`
.collect::<Vec<Module<'a>>>()
} }
fn should_add_implicit_custom_module( fn should_add_implicit_custom_module(
custom_module: &str, custom_module: &str,
config: &toml::Value, config: &toml::Value,
config_prompt_order: &[&str], module_list: &BTreeSet<String>,
) -> bool { ) -> bool {
let is_explicitly_specified = config_prompt_order.iter().any(|x| { let explicit_module_name = format!("custom.{}", custom_module);
x.len() == 7 + custom_module.len() && &x[..7] == "custom." && &x[7..] == custom_module let is_explicitly_specified = module_list.contains(&explicit_module_name);
});
if is_explicitly_specified { if is_explicitly_specified {
// The module is already specified explicitly, so we skip it // The module is already specified explicitly, so we skip it

View File

@ -22,7 +22,7 @@ fn region_set() -> io::Result<()> {
let output = common::render_module("aws") let output = common::render_module("aws")
.env("AWS_REGION", "ap-northeast-2") .env("AWS_REGION", "ap-northeast-2")
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-2")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (ap-northeast-2)"));
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
Ok(()) Ok(())
@ -37,7 +37,7 @@ fn region_set_with_alias() -> io::Result<()> {
ap-southeast-2 = "au" ap-southeast-2 = "au"
}) })
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ au")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (au)"));
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
Ok(()) Ok(())
@ -49,7 +49,7 @@ fn default_region_set() -> io::Result<()> {
.env("AWS_REGION", "ap-northeast-2") .env("AWS_REGION", "ap-northeast-2")
.env("AWS_DEFAULT_REGION", "ap-northeast-1") .env("AWS_DEFAULT_REGION", "ap-northeast-1")
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-1")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (ap-northeast-1)"));
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
Ok(()) Ok(())
@ -112,7 +112,7 @@ region = us-east-2
let output = common::render_module("aws") let output = common::render_module("aws")
.env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref())
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ us-east-1")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (us-east-1)"));
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
dir.close() dir.close()
@ -155,10 +155,6 @@ fn profile_and_region_set_with_display_all() -> io::Result<()> {
let output = common::render_module("aws") let output = common::render_module("aws")
.env("AWS_PROFILE", "astronauts") .env("AWS_PROFILE", "astronauts")
.env("AWS_REGION", "ap-northeast-1") .env("AWS_REGION", "ap-northeast-1")
.use_config(toml::toml! {
[aws]
displayed_items = "all"
})
.output()?; .output()?;
let expected = format!( let expected = format!(
"on {} ", "on {} ",
@ -173,10 +169,6 @@ fn profile_and_region_set_with_display_all() -> io::Result<()> {
fn profile_set_with_display_all() -> io::Result<()> { fn profile_set_with_display_all() -> io::Result<()> {
let output = common::render_module("aws") let output = common::render_module("aws")
.env("AWS_PROFILE", "astronauts") .env("AWS_PROFILE", "astronauts")
.use_config(toml::toml! {
[aws]
displayed_items = "all"
})
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts"));
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
@ -188,12 +180,8 @@ fn profile_set_with_display_all() -> io::Result<()> {
fn region_set_with_display_all() -> io::Result<()> { fn region_set_with_display_all() -> io::Result<()> {
let output = common::render_module("aws") let output = common::render_module("aws")
.env("AWS_REGION", "ap-northeast-1") .env("AWS_REGION", "ap-northeast-1")
.use_config(toml::toml! {
[aws]
displayed_items = "all"
})
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-1")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (ap-northeast-1)"));
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
Ok(()) Ok(())
@ -206,7 +194,7 @@ fn profile_and_region_set_with_display_region() -> io::Result<()> {
.env("AWS_DEFAULT_REGION", "ap-northeast-1") .env("AWS_DEFAULT_REGION", "ap-northeast-1")
.use_config(toml::toml! { .use_config(toml::toml! {
[aws] [aws]
displayed_items = "region" format = "on [$symbol$region]($style) "
}) })
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-1")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-1"));
@ -222,7 +210,7 @@ fn profile_and_region_set_with_display_profile() -> io::Result<()> {
.env("AWS_REGION", "ap-northeast-1") .env("AWS_REGION", "ap-northeast-1")
.use_config(toml::toml! { .use_config(toml::toml! {
[aws] [aws]
displayed_items = "profile" format = "on [$symbol$profile]($style) "
}) })
.output()?; .output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts")); let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts"));
@ -237,10 +225,10 @@ fn region_set_with_display_profile() -> io::Result<()> {
.env("AWS_REGION", "ap-northeast-1") .env("AWS_REGION", "ap-northeast-1")
.use_config(toml::toml! { .use_config(toml::toml! {
[aws] [aws]
displayed_items = "profile" format = "on [$symbol$profile]($style) "
}) })
.output()?; .output()?;
let expected = ""; let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ "));
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
Ok(()) Ok(())
@ -252,7 +240,7 @@ fn region_not_set_with_display_region() -> io::Result<()> {
let output = common::render_module("aws") let output = common::render_module("aws")
.use_config(toml::toml! { .use_config(toml::toml! {
[aws] [aws]
displayed_items = "region" format = "on [$symbol$region]($style) "
}) })
.output()?; .output()?;
let expected = ""; let expected = "";

View File

@ -4,7 +4,7 @@ use std::io;
use crate::common::{self, TestCommand}; use crate::common::{self, TestCommand};
#[test] #[test]
fn char_module_success_status() -> io::Result<()> { fn success_status() -> io::Result<()> {
let expected = format!("{} ", Color::Green.bold().paint("")); let expected = format!("{} ", Color::Green.bold().paint(""));
// Status code 0 // Status code 0
@ -23,7 +23,7 @@ fn char_module_success_status() -> io::Result<()> {
} }
#[test] #[test]
fn char_module_failure_status() -> io::Result<()> { fn failure_status() -> io::Result<()> {
let expected = format!("{} ", Color::Red.bold().paint("")); let expected = format!("{} ", Color::Red.bold().paint(""));
let exit_values = ["1", "54321", "-5000"]; let exit_values = ["1", "54321", "-5000"];
@ -39,9 +39,9 @@ fn char_module_failure_status() -> io::Result<()> {
} }
#[test] #[test]
fn char_module_symbolyes_status() -> io::Result<()> { fn custom_symbol() -> io::Result<()> {
let expected_fail = format!("{} ", Color::Red.bold().paint("")); let expected_fail = format!("{} ", Color::Red.bold().paint(""));
let expected_success = format!("{} ", Color::Green.bold().paint("")); let expected_success = format!("{} ", Color::Green.bold().paint(""));
let exit_values = ["1", "54321", "-5000"]; let exit_values = ["1", "54321", "-5000"];
@ -51,7 +51,9 @@ fn char_module_symbolyes_status() -> io::Result<()> {
let output = common::render_module("character") let output = common::render_module("character")
.use_config(toml::toml! { .use_config(toml::toml! {
[character] [character]
use_symbol_for_status = true success_symbol = "[➜](bold green)"
error_symbol = "[✖](bold red)"
}) })
.arg(arg) .arg(arg)
.output()?; .output()?;
@ -63,7 +65,8 @@ fn char_module_symbolyes_status() -> io::Result<()> {
let output = common::render_module("character") let output = common::render_module("character")
.use_config(toml::toml! { .use_config(toml::toml! {
[character] [character]
use_symbol_for_status = true success_symbol = "[➜](bold green)"
error_symbol = "[✖](bold red)"
}) })
.arg("--status=0") .arg("--status=0")
.output()?; .output()?;
@ -74,11 +77,10 @@ fn char_module_symbolyes_status() -> io::Result<()> {
} }
#[test] #[test]
fn char_module_zsh_keymap() -> io::Result<()> { fn zsh_keymap() -> io::Result<()> {
let expected_vicmd = ""; let expected_vicmd = format!("{} ", Color::Green.bold().paint(""));
// TODO make this less... well, stupid when ANSI escapes can be mocked out let expected_specified = format!("{} ", Color::Green.bold().paint("V"));
let expected_specified = "I HIGHLY DOUBT THIS WILL SHOW UP IN OTHER OUTPUT"; let expected_other = format!("{} ", Color::Green.bold().paint(""));
let expected_other = "";
// zle keymap is vicmd // zle keymap is vicmd
let output = common::render_module("character") let output = common::render_module("character")
@ -86,19 +88,19 @@ fn char_module_zsh_keymap() -> io::Result<()> {
.arg("--keymap=vicmd") .arg("--keymap=vicmd")
.output()?; .output()?;
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert!(actual.contains(&expected_vicmd)); assert_eq!(expected_vicmd, actual);
// specified vicmd character // specified vicmd character
let output = common::render_module("character") let output = common::render_module("character")
.use_config(toml::toml! { .use_config(toml::toml! {
[character] [character]
vicmd_symbol = "I HIGHLY DOUBT THIS WILL SHOW UP IN OTHER OUTPUT" vicmd_symbol = "[V](bold green)"
}) })
.env("STARSHIP_SHELL", "zsh") .env("STARSHIP_SHELL", "zsh")
.arg("--keymap=vicmd") .arg("--keymap=vicmd")
.output()?; .output()?;
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert!(actual.contains(&expected_specified)); assert_eq!(expected_specified, actual);
// zle keymap is other // zle keymap is other
let output = common::render_module("character") let output = common::render_module("character")
@ -106,17 +108,16 @@ fn char_module_zsh_keymap() -> io::Result<()> {
.arg("--keymap=visual") .arg("--keymap=visual")
.output()?; .output()?;
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert!(actual.contains(&expected_other)); assert_eq!(expected_other, actual);
Ok(()) Ok(())
} }
#[test] #[test]
fn char_module_fish_keymap() -> io::Result<()> { fn fish_keymap() -> io::Result<()> {
let expected_vicmd = ""; let expected_vicmd = format!("{} ", Color::Green.bold().paint(""));
// TODO make this less... well, stupid when ANSI escapes can be mocked out let expected_specified = format!("{} ", Color::Green.bold().paint("V"));
let expected_specified = "I HIGHLY DOUBT THIS WILL SHOW UP IN OTHER OUTPUT"; let expected_other = format!("{} ", Color::Green.bold().paint(""));
let expected_other = "";
// fish keymap is default // fish keymap is default
let output = common::render_module("character") let output = common::render_module("character")
@ -124,19 +125,19 @@ fn char_module_fish_keymap() -> io::Result<()> {
.arg("--keymap=default") .arg("--keymap=default")
.output()?; .output()?;
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert!(actual.contains(&expected_vicmd)); assert_eq!(expected_vicmd, actual);
// specified vicmd character // specified vicmd character
let output = common::render_module("character") let output = common::render_module("character")
.use_config(toml::toml! { .use_config(toml::toml! {
[character] [character]
vicmd_symbol = "I HIGHLY DOUBT THIS WILL SHOW UP IN OTHER OUTPUT" vicmd_symbol = "[V](bold green)"
}) })
.env("STARSHIP_SHELL", "fish") .env("STARSHIP_SHELL", "fish")
.arg("--keymap=default") .arg("--keymap=default")
.output()?; .output()?;
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert!(actual.contains(&expected_specified)); assert_eq!(expected_specified, actual);
// fish keymap is other // fish keymap is other
let output = common::render_module("character") let output = common::render_module("character")
@ -144,7 +145,7 @@ fn char_module_fish_keymap() -> io::Result<()> {
.arg("--keymap=visual") .arg("--keymap=visual")
.output()?; .output()?;
let actual = String::from_utf8(output.stdout).unwrap(); let actual = String::from_utf8(output.stdout).unwrap();
assert!(actual.contains(&expected_other)); assert_eq!(expected_other, actual);
Ok(()) Ok(())
} }

View File

@ -64,7 +64,7 @@ fn config_1s_duration_prefix_underwent() -> io::Result<()> {
let output = common::render_module("cmd_duration") let output = common::render_module("cmd_duration")
.use_config(toml::toml! { .use_config(toml::toml! {
[cmd_duration] [cmd_duration]
prefix = "underwent " format = "underwent [$duration]($style) "
}) })
.arg("--cmd-duration=1000") .arg("--cmd-duration=1000")
.output()?; .output()?;
@ -80,7 +80,7 @@ fn config_5s_duration_prefix_underwent() -> io::Result<()> {
let output = common::render_module("cmd_duration") let output = common::render_module("cmd_duration")
.use_config(toml::toml! { .use_config(toml::toml! {
[cmd_duration] [cmd_duration]
prefix = "underwent " format = "underwent [$duration]($style) "
}) })
.arg("--cmd-duration=5000") .arg("--cmd-duration=5000")
.output()?; .output()?;

View File

@ -16,7 +16,7 @@ const EXE_PATH: &str = "./target/debug/starship.exe";
const EXE_PATH: &str = "./target/debug/starship"; const EXE_PATH: &str = "./target/debug/starship";
/// Render the full starship prompt /// Render the full starship prompt
pub fn render_prompt() -> process::Command { pub fn _render_prompt() -> process::Command {
let mut command = process::Command::new(EXE_PATH); let mut command = process::Command::new(EXE_PATH);
command command

Some files were not shown because too many files have changed in this diff Show More