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"
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]]
name = "quote"
version = "1.0.7"
@ -1151,6 +1160,7 @@ dependencies = [
"pest",
"pest_derive",
"pretty_env_logger",
"quick-xml",
"rayon",
"regex",
"remove_dir_all",

View File

@ -59,6 +59,7 @@ open = "1.4.0"
unicode-width = "0.1.8"
textwrap = "0.12.1"
term_size = "0.3.2"
quick-xml = "0.18.1"
# Optional/http:
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
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() {
log::debug!(
"Config found for \"{}\": \n{:?}",
&module_name,
&module_config
);
} else {
log::trace!("No config found for \"{}\"", &module_name);
}
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
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() {
log::debug!(
"Custom config found for \"{}\": \n{:?}",
&module_name,
&module_config
);
} else {
log::trace!("No custom config found for \"{}\"", &module_name);
}
module_config
}
/// Get the table of all the registered custom modules, if any
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 {
@ -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
string include the following:
- '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 std::collections::HashMap;
use ansi_term::{Color, Style};
use crate::config::{ModuleConfig, RootModuleConfig};
use starship_module_config_derive::ModuleConfig;
#[derive(Clone, PartialEq)]
pub enum AwsItems {
All,
Region,
Profile,
}
use std::collections::HashMap;
#[derive(Clone, ModuleConfig)]
pub struct AwsConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub profile: SegmentConfig<'a>,
pub region: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
pub displayed_items: AwsItems,
pub region_aliases: HashMap<String, &'a str>,
}
impl<'a> RootModuleConfig<'a> for AwsConfig<'a> {
fn new() -> Self {
AwsConfig {
symbol: SegmentConfig::new("☁️ "),
profile: SegmentConfig::default(),
region: SegmentConfig::default(),
style: Color::Yellow.bold(),
format: "on [$symbol$profile(\\($region\\))]($style) ",
symbol: "☁️ ",
style: "bold yellow",
disabled: false,
displayed_items: AwsItems::All,
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;
#[derive(Clone, ModuleConfig)]
pub struct BatteryConfig<'a> {
pub full_symbol: SegmentConfig<'a>,
pub charging_symbol: SegmentConfig<'a>,
pub discharging_symbol: SegmentConfig<'a>,
pub unknown_symbol: Option<SegmentConfig<'a>>,
pub empty_symbol: Option<SegmentConfig<'a>>,
pub display: Vec<BatteryDisplayConfig>,
pub full_symbol: &'a str,
pub charging_symbol: &'a str,
pub discharging_symbol: &'a str,
pub unknown_symbol: Option<&'a str>,
pub empty_symbol: Option<&'a str>,
pub display: Vec<BatteryDisplayConfig<'a>>,
pub disabled: bool,
pub percentage: SegmentConfig<'a>,
pub format: &'a str,
}
impl<'a> RootModuleConfig<'a> for BatteryConfig<'a> {
fn new() -> Self {
BatteryConfig {
full_symbol: SegmentConfig::new(""),
charging_symbol: SegmentConfig::new(""),
discharging_symbol: SegmentConfig::new(""),
full_symbol: "",
charging_symbol: "",
discharging_symbol: "",
unknown_symbol: None,
empty_symbol: None,
format: "[$symbol$percentage]($style) ",
display: vec![BatteryDisplayConfig {
threshold: 10,
style: Color::Red.bold(),
style: "red bold",
}],
disabled: false,
percentage: SegmentConfig::default(),
}
}
}
#[derive(Clone, ModuleConfig)]
pub struct BatteryDisplayConfig {
pub struct BatteryDisplayConfig<'a> {
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;
#[derive(Clone, ModuleConfig)]
pub struct CharacterConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub error_symbol: SegmentConfig<'a>,
pub vicmd_symbol: SegmentConfig<'a>,
pub use_symbol_for_status: bool,
pub style_success: Style,
pub style_failure: Style,
pub format: &'a str,
pub success_symbol: &'a str,
pub error_symbol: &'a str,
pub vicmd_symbol: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for CharacterConfig<'a> {
fn new() -> Self {
CharacterConfig {
symbol: SegmentConfig::new(""),
error_symbol: SegmentConfig::new(""),
vicmd_symbol: SegmentConfig::new(""),
use_symbol_for_status: false,
style_success: Color::Green.bold(),
style_failure: Color::Red.bold(),
format: "$symbol ",
success_symbol: "[](bold green)",
error_symbol: "[](bold red)",
vicmd_symbol: "[](bold green)",
disabled: false,
}
}

View File

@ -1,13 +1,12 @@
use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)]
pub struct CmdDurationConfig<'a> {
pub min_time: i64,
pub prefix: &'a str,
pub style: Style,
pub format: &'a str,
pub style: &'a str,
pub show_milliseconds: bool,
pub disabled: bool,
}
@ -16,9 +15,9 @@ impl<'a> RootModuleConfig<'a> for CmdDurationConfig<'a> {
fn new() -> Self {
CmdDurationConfig {
min_time: 2_000,
prefix: "took ",
format: "took [$duration]($style) ",
show_milliseconds: false,
style: Color::Yellow.bold(),
style: "yellow bold",
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;
#[derive(Clone, ModuleConfig)]
pub struct CondaConfig<'a> {
pub truncation_length: usize,
pub symbol: SegmentConfig<'a>,
pub environment: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
@ -16,15 +15,9 @@ impl<'a> RootModuleConfig<'a> for CondaConfig<'a> {
fn new() -> Self {
CondaConfig {
truncation_length: 1,
symbol: SegmentConfig {
value: "C ",
style: None,
},
environment: SegmentConfig {
value: "",
style: None,
},
style: Color::Green.bold(),
format: "via [$symbol$environment]($style) ",
symbol: "🅒 ",
style: "green bold",
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;
#[derive(Clone, ModuleConfig)]
pub struct CrystalConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for CrystalConfig<'a> {
fn new() -> Self {
CrystalConfig {
symbol: SegmentConfig::new("🔮 "),
style: Color::Red.bold(),
format: "via [$symbol$version]($style) ",
symbol: "🔮 ",
style: "bold red",
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;
#[derive(Clone, Default, PartialEq)]
@ -14,15 +13,14 @@ pub struct Directories<'a>(pub Vec<&'a str>);
#[derive(Clone, ModuleConfig)]
pub struct CustomConfig<'a> {
pub symbol: Option<SegmentConfig<'a>>,
pub format: &'a str,
pub symbol: &'a str,
pub command: &'a str,
pub when: Option<&'a str>,
pub shell: VecOr<&'a str>,
pub description: &'a str,
pub style: Option<Style>,
pub style: &'a str,
pub disabled: bool,
pub prefix: Option<&'a str>,
pub suffix: Option<&'a str>,
pub files: Files<'a>,
pub extensions: Extensions<'a>,
pub directories: Directories<'a>,
@ -31,15 +29,14 @@ pub struct CustomConfig<'a> {
impl<'a> RootModuleConfig<'a> for CustomConfig<'a> {
fn new() -> Self {
CustomConfig {
symbol: None,
format: "[$symbol$output]($style) ",
symbol: "",
command: "",
when: None,
shell: VecOr::default(),
description: "<custom config>",
style: None,
style: "green bold",
disabled: false,
prefix: None,
suffix: None,
files: Files::default(),
extensions: Extensions::default(),
directories: Directories::default(),

View File

@ -1,7 +1,6 @@
use crate::config::{ModuleConfig, RootModuleConfig};
use std::collections::HashMap;
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)]
@ -11,8 +10,8 @@ pub struct DirectoryConfig<'a> {
pub substitutions: HashMap<String, &'a str>,
pub fish_style_pwd_dir_length: i64,
pub use_logical_path: bool,
pub prefix: &'a str,
pub style: Style,
pub format: &'a str,
pub style: &'a str,
pub disabled: bool,
}
@ -24,8 +23,8 @@ impl<'a> RootModuleConfig<'a> for DirectoryConfig<'a> {
fish_style_pwd_dir_length: 0,
substitutions: HashMap::new(),
use_logical_path: true,
prefix: "in ",
style: Color::Cyan.bold(),
format: "[$path]($style) ",
style: "cyan bold",
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;
#[derive(Clone, ModuleConfig)]
pub struct DockerContextConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub context: SegmentConfig<'a>,
pub style: Style,
pub symbol: &'a str,
pub style: &'a str,
pub format: &'a str,
pub only_with_files: bool,
pub disabled: bool,
}
@ -15,9 +14,9 @@ pub struct DockerContextConfig<'a> {
impl<'a> RootModuleConfig<'a> for DockerContextConfig<'a> {
fn new() -> Self {
DockerContextConfig {
symbol: SegmentConfig::new("🐳 "),
context: SegmentConfig::default(),
style: Color::Blue.bold(),
symbol: "🐳 ",
style: "blue bold",
format: "via [$symbol$context]($style) ",
only_with_files: true,
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;
#[derive(Clone, ModuleConfig)]
pub struct DotnetConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub heuristic: bool,
pub disabled: bool,
}
@ -15,9 +14,9 @@ pub struct DotnetConfig<'a> {
impl<'a> RootModuleConfig<'a> for DotnetConfig<'a> {
fn new() -> Self {
DotnetConfig {
symbol: SegmentConfig::new("•NET "),
version: SegmentConfig::default(),
style: Color::Blue.bold(),
format: "[$symbol$version( 🎯 $tfm)]($style) ",
symbol: "•NET ",
style: "blue bold",
heuristic: true,
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;
#[derive(Clone, ModuleConfig)]
pub struct ElixirConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub otp_version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for ElixirConfig<'a> {
fn new() -> Self {
ElixirConfig {
symbol: SegmentConfig::new("💧 "),
version: SegmentConfig::default(),
otp_version: SegmentConfig::default(),
style: Color::Purple.bold(),
format: "via [$symbol$version \\(OTP $otp_version\\)]($style) ",
symbol: "💧 ",
style: "bold purple",
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;
#[derive(Clone, ModuleConfig)]
pub struct ElmConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for ElmConfig<'a> {
fn new() -> Self {
ElmConfig {
symbol: SegmentConfig::new("🌳 "),
version: SegmentConfig::default(),
style: Color::Cyan.bold(),
format: "via [$symbol$version]($style) ",
symbol: "🌳 ",
style: "cyan bold",
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;
#[derive(Clone, ModuleConfig)]
pub struct EnvVarConfig<'a> {
pub symbol: Option<SegmentConfig<'a>>,
pub symbol: &'a str,
pub style: &'a str,
pub variable: Option<&'a str>,
pub default: Option<&'a str>,
pub prefix: &'a str,
pub suffix: &'a str,
pub style: Style,
pub format: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for EnvVarConfig<'a> {
fn new() -> Self {
EnvVarConfig {
symbol: None,
symbol: "",
style: "black bold dimmed",
variable: None,
default: None,
prefix: "",
suffix: "",
style: Color::Black.bold().dimmed(),
format: "with [$env_value]($style) ",
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;
#[derive(Clone, ModuleConfig)]
pub struct ErlangConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for ErlangConfig<'a> {
fn new() -> Self {
ErlangConfig {
symbol: SegmentConfig::new("🖧 "),
version: SegmentConfig::default(),
style: Color::Red.bold(),
format: "via [$symbol$version]($style) ",
symbol: "🖧 ",
style: "bold red",
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;
#[derive(Clone, ModuleConfig)]
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_symbol: &'a str,
pub branch_name: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for GitBranchConfig<'a> {
fn new() -> Self {
GitBranchConfig {
symbol: SegmentConfig::new(""),
format: "on [$symbol$branch]($style) ",
symbol: "",
style: "bold purple",
truncation_length: std::i64::MAX,
truncation_symbol: "",
branch_name: SegmentConfig::default(),
style: Color::Purple.bold(),
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;
#[derive(Clone, ModuleConfig)]
pub struct GitCommitConfig<'a> {
pub commit_hash_length: usize,
pub hash: SegmentConfig<'a>,
pub prefix: &'a str,
pub suffix: &'a str,
pub style: Style,
pub format: &'a str,
pub style: &'a str,
pub only_detached: bool,
pub disabled: bool,
}
@ -19,10 +16,8 @@ impl<'a> RootModuleConfig<'a> for GitCommitConfig<'a> {
GitCommitConfig {
// be consistent with git by default, which has DEFAULT_ABBREV set to 7
commit_hash_length: 7,
hash: SegmentConfig::default(),
prefix: "(",
suffix: ") ",
style: Color::Green.bold(),
format: "[\\($hash\\)]($style) ",
style: "green bold",
only_detached: true,
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;
#[derive(Clone, ModuleConfig)]
pub struct GitStateConfig<'a> {
pub rebase: SegmentConfig<'a>,
pub merge: SegmentConfig<'a>,
pub revert: SegmentConfig<'a>,
pub cherry_pick: SegmentConfig<'a>,
pub bisect: SegmentConfig<'a>,
pub am: SegmentConfig<'a>,
pub am_or_rebase: SegmentConfig<'a>,
pub progress_divider: SegmentConfig<'a>,
pub style: Style,
pub rebase: &'a str,
pub merge: &'a str,
pub revert: &'a str,
pub cherry_pick: &'a str,
pub bisect: &'a str,
pub am: &'a str,
pub am_or_rebase: &'a str,
pub style: &'a str,
pub format: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for GitStateConfig<'a> {
fn new() -> Self {
GitStateConfig {
rebase: SegmentConfig::new("REBASING"),
merge: SegmentConfig::new("MERGING"),
revert: SegmentConfig::new("REVERTING"),
cherry_pick: SegmentConfig::new("CHERRY-PICKING"),
bisect: SegmentConfig::new("BISECTING"),
am: SegmentConfig::new("AM"),
am_or_rebase: SegmentConfig::new("AM/REBASE"),
progress_divider: SegmentConfig::new("/"),
style: Color::Yellow.bold(),
rebase: "REBASING",
merge: "MERGING",
revert: "REVERTING",
cherry_pick: "CHERRY-PICKING",
bisect: "BISECTING",
am: "AM",
am_or_rebase: "AM/REBASE",
style: "bold yellow",
format: "[\\($state( $progress_current/$progress_total)\\)]($style) ",
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;
#[derive(Clone, ModuleConfig)]
pub struct GitStatusConfig<'a> {
pub stashed: SegmentConfig<'a>,
pub stashed_count: CountConfig,
pub ahead: SegmentConfig<'a>,
pub behind: SegmentConfig<'a>,
pub diverged: SegmentConfig<'a>,
pub show_sync_count: bool,
pub conflicted: SegmentConfig<'a>,
pub conflicted_count: CountConfig,
pub deleted: SegmentConfig<'a>,
pub deleted_count: CountConfig,
pub renamed: SegmentConfig<'a>,
pub renamed_count: CountConfig,
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 format: &'a str,
pub style: &'a str,
pub stashed: &'a str,
pub ahead: &'a str,
pub behind: &'a str,
pub diverged: &'a str,
pub conflicted: &'a str,
pub deleted: &'a str,
pub renamed: &'a str,
pub modified: &'a str,
pub staged: &'a str,
pub untracked: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for GitStatusConfig<'a> {
fn new() -> Self {
GitStatusConfig {
stashed: SegmentConfig::new("$"),
stashed_count: CountConfig::default(),
ahead: SegmentConfig::new(""),
behind: SegmentConfig::new(""),
diverged: SegmentConfig::new(""),
conflicted: SegmentConfig::new("="),
show_sync_count: false,
conflicted_count: CountConfig::default(),
deleted: SegmentConfig::new(""),
deleted_count: CountConfig::default(),
renamed: SegmentConfig::new("»"),
renamed_count: CountConfig::default(),
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(),
format: "([\\[$all_status$ahead_behind\\]]($style) )",
style: "red bold",
stashed: "\\$",
ahead: "",
behind: "",
diverged: "",
conflicted: "=",
deleted: "",
renamed: "»",
modified: "!",
staged: "+",
untracked: "?",
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;
#[derive(Clone, ModuleConfig)]
pub struct GoConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for GoConfig<'a> {
fn new() -> Self {
GoConfig {
symbol: SegmentConfig::new("🐹 "),
version: SegmentConfig::default(),
style: Color::Cyan.bold(),
format: "via [$symbol$version]($style) ",
symbol: "🐹 ",
style: "bold cyan",
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;
#[derive(Clone, ModuleConfig)]
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_symbol: &'a str,
pub branch_name: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for HgBranchConfig<'a> {
fn new() -> Self {
HgBranchConfig {
symbol: SegmentConfig::new(""),
symbol: "",
style: "bold purple",
format: "on [$symbol$branch]($style) ",
truncation_length: std::i64::MAX,
truncation_symbol: "",
branch_name: SegmentConfig::default(),
style: Color::Purple.bold(),
disabled: true,
}
}

View File

@ -1,15 +1,13 @@
use crate::config::{ModuleConfig, RootModuleConfig};
use ansi_term::{Color, Style};
use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)]
pub struct HostnameConfig<'a> {
pub ssh_only: bool,
pub prefix: &'a str,
pub suffix: &'a str,
pub trim_at: &'a str,
pub style: Style,
pub format: &'a str,
pub style: &'a str,
pub disabled: bool,
}
@ -17,10 +15,9 @@ impl<'a> RootModuleConfig<'a> for HostnameConfig<'a> {
fn new() -> Self {
HostnameConfig {
ssh_only: true,
prefix: "",
suffix: "",
trim_at: ".",
style: Color::Green.bold().dimmed(),
format: "on [$hostname]($style) ",
style: "green dimmed bold",
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;
#[derive(Clone, ModuleConfig)]
pub struct JavaConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub style: Style,
pub disabled: bool,
pub format: &'a str,
pub style: &'a str,
pub symbol: &'a str,
}
impl<'a> RootModuleConfig<'a> for JavaConfig<'a> {
fn new() -> Self {
JavaConfig {
symbol: SegmentConfig::new(""),
style: Color::Red.dimmed(),
format: "via [$symbol$version]($style) ",
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;
#[derive(Clone, ModuleConfig)]
pub struct JobsConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub threshold: i64,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for JobsConfig<'a> {
fn new() -> Self {
JobsConfig {
symbol: SegmentConfig::new(""),
threshold: 1,
style: Color::Blue.bold(),
format: "[$symbol$number]($style) ",
symbol: "",
style: "bold blue",
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;
#[derive(Clone, ModuleConfig)]
pub struct JuliaConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for JuliaConfig<'a> {
fn new() -> Self {
JuliaConfig {
symbol: SegmentConfig::new(""),
version: SegmentConfig::default(),
style: Color::Purple.bold(),
format: "via [$symbol$version]($style) ",
symbol: "",
style: "bold purple",
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 std::collections::HashMap;
#[derive(Clone, ModuleConfig)]
pub struct KubernetesConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub context: SegmentConfig<'a>,
pub namespace: SegmentConfig<'a>,
pub style: Style,
pub symbol: &'a str,
pub format: &'a str,
pub style: &'a str,
pub disabled: bool,
pub context_aliases: HashMap<String, &'a str>,
}
@ -17,10 +15,9 @@ pub struct KubernetesConfig<'a> {
impl<'a> RootModuleConfig<'a> for KubernetesConfig<'a> {
fn new() -> Self {
KubernetesConfig {
symbol: SegmentConfig::new(""),
context: SegmentConfig::default(),
namespace: SegmentConfig::default(),
style: Color::Cyan.bold(),
symbol: "",
format: "on [$symbol$context( \\($namespace\\))]($style) ",
style: "cyan bold",
disabled: true,
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;
#[derive(Clone, ModuleConfig)]
pub struct MemoryConfig<'a> {
pub show_percentage: bool,
pub show_swap: bool,
pub threshold: i64,
pub symbol: SegmentConfig<'a>,
pub separator: SegmentConfig<'a>,
pub ram: SegmentConfig<'a>,
pub swap: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub style: &'a str,
pub symbol: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for MemoryConfig<'a> {
fn new() -> Self {
MemoryConfig {
show_percentage: false,
show_swap: true,
threshold: 75,
symbol: SegmentConfig::new("🐏 "),
separator: SegmentConfig::new(" | "),
ram: SegmentConfig::default(),
swap: SegmentConfig::default(),
style: Color::White.bold().dimmed(),
format: "via $symbol[$ram( | $swap)]($style) ",
style: "white bold dimmed",
symbol: "🐏 ",
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;
#[derive(Clone, ModuleConfig)]
pub struct NimConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for NimConfig<'a> {
fn new() -> Self {
NimConfig {
symbol: SegmentConfig::new("👑 "),
version: SegmentConfig::default(),
style: Color::Yellow.bold(),
format: "via [$symbol$version]($style) ",
symbol: "👑 ",
style: "yellow bold",
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;
#[derive(Clone, ModuleConfig)]
pub struct NixShellConfig<'a> {
pub use_name: bool,
pub impure_msg: SegmentConfig<'a>,
pub pure_msg: SegmentConfig<'a>,
pub style: Style,
pub symbol: SegmentConfig<'a>,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub impure_msg: &'a str,
pub pure_msg: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for NixShellConfig<'a> {
fn new() -> Self {
NixShellConfig {
use_name: false,
impure_msg: SegmentConfig::new("impure"),
pure_msg: SegmentConfig::new("pure"),
style: Color::Blue.bold(),
symbol: SegmentConfig::new("❄️ "),
format: "via [$symbol$state( \\($name\\))]($style) ",
symbol: "❄️ ",
style: "bold blue",
impure_msg: "impure",
pure_msg: "pure",
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;
#[derive(Clone, ModuleConfig)]
pub struct NodejsConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for NodejsConfig<'a> {
fn new() -> Self {
NodejsConfig {
symbol: SegmentConfig::new(""),
style: Color::Green.bold(),
format: "via [$symbol$version]($style) ",
symbol: "",
style: "bold green",
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;
#[derive(Clone, ModuleConfig)]
pub struct OCamlConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for OCamlConfig<'a> {
fn new() -> Self {
OCamlConfig {
symbol: SegmentConfig::new("🐫 "),
version: SegmentConfig::default(),
style: Color::Yellow.bold(),
format: "via [$symbol$version]($style) ",
symbol: "🐫 ",
style: "bold yellow",
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;
#[derive(Clone, ModuleConfig)]
pub struct PackageConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub display_private: bool,
pub disabled: bool,
}
@ -14,8 +14,9 @@ pub struct PackageConfig<'a> {
impl<'a> RootModuleConfig<'a> for PackageConfig<'a> {
fn new() -> Self {
PackageConfig {
symbol: SegmentConfig::new("📦 "),
style: Color::Fixed(208).bold(),
format: "is [$symbol$version]($style) ",
symbol: "📦 ",
style: "208 bold",
display_private: 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;
#[derive(Clone, ModuleConfig)]
pub struct PhpConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub style: Style,
pub symbol: &'a str,
pub style: &'a str,
pub format: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for PhpConfig<'a> {
fn new() -> Self {
PhpConfig {
symbol: SegmentConfig::new("🐘 "),
style: Color::Fixed(147).bold(),
symbol: "🐘 ",
style: "147 bold",
format: "via [$symbol$version]($style) ",
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;
#[derive(Clone, ModuleConfig)]
pub struct PureScriptConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for PureScriptConfig<'a> {
fn new() -> Self {
PureScriptConfig {
symbol: SegmentConfig::new("<=> "),
version: SegmentConfig::default(),
style: Color::White.bold(),
format: "via [$symbol$version]($style) ",
symbol: "<=> ",
style: "bold white",
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;
#[derive(Clone, ModuleConfig)]
pub struct PythonConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub pyenv_prefix: SegmentConfig<'a>,
pub pyenv_version_name: bool,
pub python_binary: &'a str,
pub scan_for_pyfiles: bool,
pub style: Style,
pub format: &'a str,
pub style: &'a str,
pub symbol: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for PythonConfig<'a> {
fn new() -> Self {
PythonConfig {
symbol: SegmentConfig::new("🐍 "),
version: SegmentConfig::default(),
pyenv_prefix: SegmentConfig::new("pyenv "),
pyenv_version_name: false,
python_binary: "python",
scan_for_pyfiles: true,
style: Color::Yellow.bold(),
format: "via [$symbol$version( \\($virtualenv\\))]($style) ",
style: "yellow bold",
symbol: "🐍 ",
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;
#[derive(Clone, ModuleConfig)]
pub struct RubyConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for RubyConfig<'a> {
fn new() -> Self {
RubyConfig {
symbol: SegmentConfig::new("💎 "),
style: Color::Red.bold(),
format: "via [$symbol$version]($style) ",
symbol: "💎 ",
style: "bold red",
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;
#[derive(Clone, ModuleConfig)]
pub struct RustConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for RustConfig<'a> {
fn new() -> Self {
RustConfig {
symbol: SegmentConfig::new("🦀 "),
version: SegmentConfig::default(),
style: Color::Red.bold(),
format: "via [$symbol$version]($style) ",
symbol: "🦀 ",
style: "bold red",
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;
#[derive(Clone, ModuleConfig)]
pub struct SingularityConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub label: &'a str,
pub prefix: &'a str,
pub suffix: &'a str,
pub style: Style,
pub symbol: &'a str,
pub format: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for SingularityConfig<'a> {
fn new() -> Self {
SingularityConfig {
symbol: SegmentConfig::default(),
label: "",
prefix: "[",
suffix: "]",
style: Color::Blue.bold().dimmed(),
format: "[$symbol\\[$env\\]]($style) ",
symbol: "",
style: "blue bold dimmed",
disabled: false,
}
}

View File

@ -4,19 +4,14 @@ use starship_module_config_derive::ModuleConfig;
#[derive(Clone, ModuleConfig)]
pub struct StarshipRootConfig<'a> {
pub add_newline: bool,
pub prompt_order: Vec<&'a str>,
pub format: &'a str,
pub scan_timeout: u64,
}
impl<'a> RootModuleConfig<'a> for StarshipRootConfig<'a> {
fn new() -> Self {
StarshipRootConfig {
add_newline: true,
// 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![
pub const PROMPT_ORDER: [&str; 42] = [
"username",
"hostname",
"singularity",
@ -63,7 +58,12 @@ impl<'a> RootModuleConfig<'a> for StarshipRootConfig<'a> {
"battery",
"time",
"character",
],
];
impl<'a> RootModuleConfig<'a> for StarshipRootConfig<'a> {
fn new() -> Self {
StarshipRootConfig {
format: "\n$all",
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;
#[derive(Clone, ModuleConfig)]
pub struct TerraformConfig<'a> {
pub symbol: SegmentConfig<'a>,
pub workspace: SegmentConfig<'a>,
pub version: SegmentConfig<'a>,
pub show_version: bool,
pub style: Style,
pub format: &'a str,
pub symbol: &'a str,
pub style: &'a str,
pub disabled: bool,
}
impl<'a> RootModuleConfig<'a> for TerraformConfig<'a> {
fn new() -> Self {
TerraformConfig {
symbol: SegmentConfig::new("💠 "),
workspace: SegmentConfig::default(),
version: SegmentConfig::default(),
show_version: false,
style: Color::Fixed(105).bold(),
format: "via [$symbol$workspace]($style) ",
symbol: "💠 ",
style: "bold 105",
disabled: false,
}
}

View File

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

View File

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

View File

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

View File

@ -1,17 +1,103 @@
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 format: Vec<FormatElement<'a>>,
pub style: Vec<StyleElement<'a>>,
}
#[derive(Clone)]
pub enum FormatElement<'a> {
Text(Cow<'a, str>),
Variable(Cow<'a, str>),
TextGroup(TextGroup<'a>),
Conditional(Vec<FormatElement<'a>>),
}
#[derive(Clone)]
pub enum StyleElement<'a> {
Text(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"]
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 {
let mut inner_rules = textgroup.into_inner();
let format = inner_rules.next().unwrap();
@ -22,55 +34,32 @@ fn _parse_variable(variable: Pair<Rule>) -> &str {
}
fn _parse_text(text: Pair<Rule>) -> String {
let mut result = String::new();
for pair in text.into_inner() {
result.push_str(pair.as_str());
}
result
text.into_inner()
.map(|pair| pair.as_str().chars())
.flatten()
.collect()
}
fn _parse_format(format: Pair<Rule>) -> Vec<FormatElement> {
let mut result: Vec<FormatElement> = Vec::new();
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
format.into_inner().map(_parse_value).collect()
}
fn _parse_style(style: Pair<Rule>) -> Vec<StyleElement> {
let mut result: Vec<StyleElement> = Vec::new();
for pair in style.into_inner() {
match pair.as_rule() {
Rule::text => result.push(StyleElement::Text(_parse_text(pair).into())),
Rule::variable => result.push(StyleElement::Variable(_parse_variable(pair).into())),
style
.into_inner()
.map(|pair| match pair.as_rule() {
Rule::string => StyleElement::Text(pair.as_str().into()),
Rule::variable => StyleElement::Variable(_parse_variable(pair).into()),
_ => unreachable!(),
}
}
result
})
.collect()
}
pub fn parse(format: &str) -> Result<Vec<FormatElement>, Error<Rule>> {
let pairs = IdentParser::parse(Rule::expression, format)?;
let mut result: Vec<FormatElement> = Vec::new();
// Lifetime of Segment is the same as result
for pair in pairs.take_while(|pair| pair.as_rule() != Rule::EOI) {
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)
IdentParser::parse(Rule::expression, format).map(|pairs| {
pairs
.take_while(|pair| pair.as_rule() != Rule::EOI)
.map(_parse_value)
.collect()
})
}

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 }
value = _{ text | variable | textgroup }
value = _{ text | variable | textgroup | conditional }
variable = { "$" ~ variable_name }
variable_name = @{ char+ }
// Variable
//
// 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' | "_" }
text = { text_inner+ }
text_inner = _{ text_inner_char | escape }
text_inner_char = { !("[" | "]" | "(" | ")" | "$" | "\\") ~ ANY }
variable_scope = _{ "{" ~ variable_scoped_name ~ "}" }
variable_scoped_name = { scoped_char+ }
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 }
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 ~ ")" }
format = { (variable | text | textgroup)* }
style = { (variable | text)* }
format = { value* }
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 pest::error::Error;
use pest::error::Error as PestError;
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::segment::Segment;
@ -10,109 +14,263 @@ use super::model::*;
use super::parser::{parse, Rule};
#[derive(Clone)]
enum VariableValue {
Plain(String),
enum VariableValue<'a> {
Plain(Cow<'a, str>),
Styled(Vec<Segment>),
Meta(Vec<FormatElement<'a>>),
}
impl Default for VariableValue {
impl<'a> Default for VariableValue<'a> {
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> {
format: Vec<FormatElement<'a>>,
variables: VariableMapType,
variables: VariableMapType<'a>,
style_variables: StyleVariableMapType<'a>,
}
impl<'a> StringFormatter<'a> {
/// 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)
.map(|format| {
let variables = _get_variables(&format);
(format, variables)
// Cache all 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
pub fn map(mut self, mapper: impl Fn(&str) -> Option<String> + Sync) -> Self {
self.variables.par_iter_mut().for_each(|(key, value)| {
*value = mapper(key).map(VariableValue::Plain);
///
/// You should provide a function or closure that accepts the variable name `name: &str` as a
/// 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
}
/// Maps variable name to an array of segments
pub fn map_variables_to_segments(
mut self,
mapper: impl Fn(&str) -> Option<Vec<Segment>> + Sync,
) -> Self {
self.variables.par_iter_mut().for_each(|(key, value)| {
*value = mapper(key).map(VariableValue::Styled);
///
/// See `StringFormatter::map` for description on the parameters.
pub fn map_variables_to_segments<M>(mut self, mapper: M) -> Self
where
M: Fn(&str) -> Option<Result<Vec<Segment>, StringFormatterError>> + Sync,
{
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
}
/// 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>(
textgroup: TextGroup<'a>,
variables: &'a VariableMapType,
) -> Vec<Segment> {
let style = _parse_style(textgroup.style);
_parse_format(textgroup.format, style, &variables)
variables: &'a VariableMapType<'a>,
style_variables: &'a StyleVariableMapType<'a>,
) -> Result<Vec<Segment>, StringFormatterError> {
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> {
let style_string = style
.iter()
.flat_map(|style| match style {
StyleElement::Text(text) => text.as_ref().chars(),
StyleElement::Variable(variable) => {
log::warn!(
"Variable `{}` monitored in style string, which is not allowed",
&variable
);
"".chars()
fn _parse_style<'a>(
style: Vec<StyleElement>,
variables: &'a StyleVariableMapType<'a>,
) -> Option<Result<Style, StringFormatterError>> {
let style_strings = style
.into_iter()
.map(|style| match style {
StyleElement::Text(text) => Ok(text),
StyleElement::Variable(name) => {
let variable = variables.get(name.as_ref()).unwrap_or(&None);
match variable {
Some(style_string) => style_string.clone().map(|string| string),
None => Ok("".into()),
}
}
})
.collect::<String>();
.collect::<Result<Vec<Cow<str>>, StringFormatterError>>();
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>(
mut format: Vec<FormatElement<'a>>,
format: Vec<FormatElement<'a>>,
style: Option<Style>,
variables: &'a VariableMapType,
) -> Vec<Segment> {
let mut result: Vec<Segment> = Vec::new();
format.reverse();
while let Some(el) = format.pop() {
let mut segments = match el {
FormatElement::Text(text) => {
vec![_new_segment("_text".into(), text.into_owned(), style)]
}
variables: &'a VariableMapType<'a>,
style_variables: &'a StyleVariableMapType<'a>,
) -> Result<Vec<Segment>, StringFormatterError> {
let results: Result<Vec<Vec<Segment>>, StringFormatterError> = format
.into_iter()
.map(|el| {
match el {
FormatElement::Text(text) => Ok(vec![_new_segment("_text", text, style)]),
FormatElement::TextGroup(textgroup) => {
let textgroup = TextGroup {
format: textgroup.format,
style: textgroup.style,
};
_parse_textgroup(textgroup, &variables)
_parse_textgroup(textgroup, &variables, &style_variables)
}
FormatElement::Variable(name) => variables
.get(name.as_ref())
.map(|segments| {
let value = segments.clone().unwrap_or_default();
match value {
VariableValue::Styled(segments) => segments
.expect("Uncached variable found")
.as_ref()
.map(|segments| match segments.clone()? {
VariableValue::Styled(segments) => Ok(segments
.into_iter()
.map(|mut segment| {
// Derive upper style if the style of segments are none.
if !segment.has_style() {
if let Some(style) = style {
segment.set_style(style);
@ -120,80 +278,124 @@ impl<'a> StringFormatter<'a> {
}
segment
})
.collect(),
.collect()),
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)
})
}
let should_show: bool = _should_show_elements(&format, variables);
if should_show {
_parse_format(format, style, variables, style_variables)
} else {
Ok(Vec::new())
}
}
}
})
.unwrap_or_default(),
};
result.append(&mut segments);
.collect();
Ok(results?.into_iter().flatten().collect())
}
result
}
_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`
fn _get_variables<'a>(format: &[FormatElement<'a>]) -> VariableMapType {
let mut variables: VariableMapType = Default::default();
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) {
variables.insert(name.to_owned(), None);
}
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)
}
_ => {}
impl<'a> VariableHolder<String> for StringFormatter<'a> {
fn get_variables(&self) -> BTreeSet<String> {
BTreeSet::from_iter(self.variables.keys().cloned())
}
}
variables
impl<'a> StyleVariableHolder<String> for StringFormatter<'a> {
fn get_style_variables(&self) -> BTreeSet<String> {
BTreeSet::from_iter(self.style_variables.keys().cloned())
}
}
/// 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 {
_name: name,
value,
_name: name.into(),
value: value.into(),
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)]
mod tests {
use super::*;
use ansi_term::Color;
// match_next(result: Iter<Segment>, value, style)
// match_next(result: IterMut<Segment>, value, style)
macro_rules! match_next {
($iter:ident, $value:literal, $($style:tt)+) => {
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
}
@ -212,7 +414,7 @@ mod tests {
let style = Some(Color::Red.bold());
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();
match_next!(result_iter, "text", style);
}
@ -221,7 +423,7 @@ mod tests {
fn test_textgroup_text_only() {
const FORMAT_STR: &str = "[text](red bold)";
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();
match_next!(result_iter, "text", Some(Color::Red.bold()));
}
@ -233,20 +435,48 @@ mod tests {
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map(|variable| match variable {
"var1" => Some("text1".to_owned()),
"var1" => Some(Ok("text1".to_owned())),
_ => None,
});
let result = formatter.parse(None);
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
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]
fn test_escaped_chars() {
const FORMAT_STR: &str = r#"\\\[\$text\]\(red bold\)"#;
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();
match_next!(result_iter, r#"\[$text](red bold)"#, None);
}
@ -259,7 +489,7 @@ mod tests {
let inner_style = Some(Color::Blue.normal());
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();
match_next!(result_iter, "outer ", outer_style);
match_next!(result_iter, "middle ", middle_style);
@ -274,10 +504,10 @@ mod tests {
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.map(|variable| match variable {
"var" => Some("text".to_owned()),
"var" => Some(Ok("text".to_owned())),
_ => None,
});
let result = formatter.parse(None);
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "text", var_style);
}
@ -292,7 +522,7 @@ mod tests {
let formatter = StringFormatter::new(FORMAT_STR)
.unwrap()
.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("_2".to_owned(), "styled".to_owned(), styled_style),
_new_segment(
@ -300,16 +530,133 @@ mod tests {
"styled_no_modifier".to_owned(),
styled_no_modifier_style,
),
]),
])),
_ => None,
});
let result = formatter.parse(None);
let result = formatter.parse(None).unwrap();
let mut result_iter = result.iter();
match_next!(result_iter, "styless", var_style);
match_next!(result_iter, "styled", styled_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]
fn test_parse_error() {
// brackets without escape
@ -323,4 +670,21 @@ mod tests {
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::segment::Segment;
use crate::utils::wrap_colorseq_for_shell;
use ansi_term::Style;
use ansi_term::{ANSIString, ANSIStrings};
use std::fmt;
@ -66,17 +64,8 @@ pub struct Module<'a> {
/// The module's description
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.
segments: Vec<Segment>,
/// The suffix used to separate the current module from the next one.
suffix: Affix,
pub segments: Vec<Segment>,
}
impl<'a> Module<'a> {
@ -86,23 +75,10 @@ impl<'a> Module<'a> {
config,
_name: name.to_string(),
description: desc.to_string(),
style: Style::default(),
prefix: Affix::default_prefix(name),
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
pub fn set_segments(&mut self, segments: Vec<Segment>) {
self.segments = segments;
@ -123,31 +99,11 @@ impl<'a> Module<'a> {
self.segments.iter().all(|segment| segment.is_empty())
}
/// Get values of the module's segments
pub fn get_segments(&self) -> Vec<&str> {
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
/// `ANSIStrings()` to optimize ANSI codes
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> {
let mut ansi_strings = self
let ansi_strings = self
.segments
.iter()
.map(Segment::ansi_string)
.collect::<Vec<ANSIString>>();
ansi_strings.insert(0, self.prefix.ansi_string());
ansi_strings.push(self.suffix.ansi_string());
ansi_strings = match shell {
match shell {
Shell::Bash => ansi_strings_modified(ansi_strings, shell),
Shell::Zsh => ansi_strings_modified(ansi_strings, shell),
_ => 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>>()
}
/// 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)]
mod tests {
use super::*;
@ -268,10 +154,7 @@ mod tests {
config: None,
_name: name.to_string(),
description: desc.to_string(),
style: Style::default(),
prefix: Affix::default_prefix(name),
segments: Vec::new(),
suffix: Affix::default_suffix(name),
};
assert!(module.is_empty());
@ -285,10 +168,7 @@ mod tests {
config: None,
_name: name.to_string(),
description: desc.to_string(),
style: Style::default(),
prefix: Affix::default_prefix(name),
segments: vec![Segment::new("test_segment")],
suffix: Affix::default_suffix(name),
};
assert!(module.is_empty());

View File

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

View File

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

View File

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

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::formatter::StringFormatter;
/// Outputs the time it took the last command to execute
///
@ -28,19 +28,30 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None;
}
let config_min = config.min_time as u128;
if elapsed < config.min_time as u128 {
return None;
}
let module_color = match elapsed {
time if time < config_min => return None,
_ => config.style,
};
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"duration" => Some(Ok(render_time(elapsed, config.show_milliseconds))),
_ => None,
})
.parse(None)
});
module.set_style(module_color);
module.create_segment(
"cmd_duration",
&SegmentConfig::new(&render_time(elapsed, config.show_milliseconds)),
);
module.get_prefix().set_value(config.prefix);
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `cmd_duration`: \n{}", error);
return None;
}
});
Some(module)
}

View File

@ -1,10 +1,10 @@
use std::env;
use super::{Context, Module};
use super::{Context, Module, RootModuleConfig};
use super::utils::directory::truncate;
use crate::config::RootModuleConfig;
use crate::configs::conda::CondaConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current Conda environment
///
@ -17,14 +17,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
let mut module = context.new_module("conda");
let config = CondaConfig::try_load(module.config);
let config: CondaConfig = CondaConfig::try_load(module.config);
let conda_env = truncate(conda_env, config.truncation_length);
module.set_style(config.style);
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.create_segment("environment", &config.environment.with_value(&conda_env));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `conda`:\n{}", error);
return None;
}
});
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::formatter::StringFormatter;
use crate::utils;
/// Creates a module with the current Crystal version
@ -20,14 +21,34 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
}
let crystal_version = utils::exec_cmd("crystal", &["--version"])?.stdout;
let formatted_version = format_crystal_version(&crystal_version)?;
let mut module = context.new_module("crystal");
let config: CrystalConfig = CrystalConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
module.create_segment("version", &SegmentConfig::new(&formatted_version));
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|variable, _| match variable {
"symbol" => Some(config.symbol),
_ => None,
})
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"version" => format_crystal_version(&crystal_version).map(Ok),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `crystal`:\n{}", error);
return None;
}
});
Some(module)
}

View File

@ -1,10 +1,9 @@
use ansi_term::Color;
use std::io::Write;
use std::process::{Command, Output, Stdio};
use super::{Context, Module, RootModuleConfig};
use crate::{config::SegmentConfig, configs::custom::CustomConfig};
use crate::{configs::custom::CustomConfig, formatter::StringFormatter};
/// Creates a custom module with some configuration
///
@ -13,7 +12,7 @@ use crate::{config::SegmentConfig, configs::custom::CustomConfig};
/// command can be run -- if its result is 0, the module will be shown.
///
/// Finally, the content of the module itself is also set by a command.
pub fn module<'a>(name: &'a str, context: &'a Context) -> Option<Module<'a>> {
pub fn module<'a>(name: &str, context: &'a Context) -> Option<Module<'a>> {
let toml_config = context.config.get_custom_module_config(name).expect(
"modules::custom::module should only be called after ensuring that the module exists",
);
@ -44,35 +43,42 @@ pub fn module<'a>(name: &'a str, context: &'a Context) -> Option<Module<'a>> {
}
let mut module = Module::new(name, config.description, Some(toml_config));
let style = config.style.unwrap_or_else(|| Color::Green.bold());
if let Some(prefix) = config.prefix {
module.get_prefix().set_value(prefix);
}
if let Some(suffix) = config.suffix {
module.get_suffix().set_value(suffix);
}
let output = exec_command(config.command, &config.shell.0)?;
if let Some(symbol) = config.symbol {
module.create_segment("symbol", &symbol);
}
if let Some(output) = exec_command(config.command, &config.shell.0) {
let trimmed = output.trim();
if trimmed.is_empty() {
return None;
}
module.create_segment(
"output",
&SegmentConfig::new(&trimmed).with_style(Some(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 {
// This may result in multiple calls to `get_module_version` when a user have
// multiple `$version` variables defined in `format`.
"output" => Some(Ok(trimmed)),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `custom.{}`:\n{}", name, error);
return None;
}
});
Some(module)
} else {
None
}
}
/// 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::utils::directory::truncate;
use crate::config::{RootModuleConfig, SegmentConfig};
use crate::config::RootModuleConfig;
use crate::configs::directory::DirectoryConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current directory
///
@ -29,8 +30,6 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("directory");
let config: DirectoryConfig = DirectoryConfig::try_load(module.config);
module.set_style(config.style);
// Using environment PWD is the standard approach for determining logical path
// If this is None for any reason, we fall back to reading the os-provided path
let physical_current_dir = if config.use_logical_path {
@ -80,33 +79,39 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
// Substitutions could have changed the prefix, so don't allow them and
// fish-style path contraction together
if config.fish_style_pwd_dir_length > 0 && config.substitutions.is_empty() {
let fish_prefix = if config.fish_style_pwd_dir_length > 0 && config.substitutions.is_empty() {
// If user is using fish style path, we need to add the segment first
let contracted_home_dir = contract_path(&current_dir, &home_dir, HOME_SYMBOL);
let fish_style_dir = to_fish_style(
to_fish_style(
config.fish_style_pwd_dir_length as usize,
contracted_home_dir,
&truncated_dir_string,
);
)
} else {
String::from("")
};
let final_dir_string = format!("{}{}", fish_prefix, truncated_dir_string);
module.create_segment(
"path",
&SegmentConfig {
value: &fish_style_dir,
style: None,
},
);
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 {
"path" => Some(Ok(&final_dir_string)),
_ => None,
})
.parse(None)
});
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `directory`:\n{}", error);
return None;
}
module.create_segment(
"path",
&SegmentConfig {
value: &truncated_dir_string,
style: None,
},
);
module.get_prefix().set_value(config.prefix);
});
Some(module)
}

View File

@ -1,14 +1,17 @@
use std::env;
use std::path::PathBuf;
use super::{Context, Module, RootModuleConfig};
use crate::configs::docker_context::DockerContextConfig;
use crate::formatter::StringFormatter;
use crate::utils;
const DOCKER_CONFIG_FILE: &str = ".docker/config.json";
/// Creates a module with the currently active Docker context
///
/// Will display the Docker context if the following criteria are met:
/// - 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 value of `currentContext` is not `default`
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;
}
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);
let json = utils::read_file(config_path).ok()?;
if !docker_config.exists() {
return None;
}
let json = utils::read_file(docker_config).ok()?;
let parsed_json = serde_json::from_str(&json).ok()?;
match parsed_json {
@ -33,9 +44,31 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let current_context = root.get("currentContext")?;
match current_context {
serde_json::Value::String(ctx) => {
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
module.create_segment("context", &config.context.with_value(&ctx));
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 {
"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)
}
_ => None,

View File

@ -1,3 +1,5 @@
use quick_xml::events::Event;
use quick_xml::Reader;
use std::ffi::OsStr;
use std::iter::Iterator;
use std::ops::Deref;
@ -6,6 +8,7 @@ use std::str;
use super::{Context, Module, RootModuleConfig};
use crate::configs::dotnet::DotnetConfig;
use crate::formatter::StringFormatter;
use crate::utils;
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.
// Typically it is twice as fast as running `dotnet --version`.
let enable_heuristic = config.heuristic;
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 {
"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)?
estimate_dotnet_version(&dotnet_files, &context.current_dir, repo_root)
} else {
get_version_from_cli()?
get_version_from_cli()
};
version.map(|v| Ok(v.0))
}
"tfm" => find_current_tfm(&dotnet_files).map(Ok),
_ => None,
})
.parse(None)
});
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
module.create_segment("version", &config.version.with_value(&version.0));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `dotnet`:\n{}", error);
return None;
}
});
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>(
files: &[DotNetFile<'a>],
current_dir: &Path,

View File

@ -1,9 +1,10 @@
use regex::Regex;
use super::{Context, Module, RootModuleConfig};
use crate::configs::elixir::ElixirConfig;
use crate::formatter::StringFormatter;
use crate::utils;
use regex::Regex;
const ELIXIR_VERSION_PATTERN: &str = "\
Erlang/OTP (?P<otp>\\d+)[^\\n]+
@ -11,7 +12,7 @@ Elixir (?P<elixir>\\d[.\\d]+).*";
/// 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
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
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 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.create_segment("version", &config.version.with_value(&elixir_version));
module.create_segment(
"otp_version",
&config
.otp_version
.with_value(&format!(" (OTP {})", otp_version)),
);
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `elixir`:\n{}", error);
return None;
}
});
Some(module)
}
fn get_elixir_version() -> Option<(String, String)> {
use crate::utils;
let output = utils::exec_cmd("elixir", &["--version"])?.stdout;
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::formatter::StringFormatter;
use crate::utils;
/// 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 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 config: ElmConfig = ElmConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
module.create_segment("version", &SegmentConfig::new(&formatted_version));
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|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)
}

View File

@ -1,9 +1,10 @@
use std::env;
use super::{Context, Module, SegmentConfig};
use super::{Context, Module};
use crate::config::RootModuleConfig;
use crate::configs::env_var::EnvVarConfig;
use crate::formatter::StringFormatter;
/// 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 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.get_prefix().set_value("with ");
if let Some(symbol) = config.symbol {
module.create_segment("symbol", &symbol);
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `env_var`:\n{}", error);
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)
}

View File

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

View File

@ -3,6 +3,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{Context, Module, RootModuleConfig};
use crate::configs::git_branch::GitBranchConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the Git branch in the current directory
///
@ -10,12 +11,8 @@ use crate::configs::git_branch::GitBranchConfig;
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("git_branch");
let config = GitBranchConfig::try_load(module.config);
module.set_style(config.style);
module.get_prefix().set_value("on ");
let truncation_symbol = get_graphemes(config.truncation_symbol, 1);
module.create_segment("symbol", &config.symbol);
let truncation_symbol = get_first_grapheme(config.truncation_symbol);
// TODO: Once error handling is implemented, warn the user if their config
// truncation length is nonsensical
@ -31,29 +28,50 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let repo = context.get_repo().ok()?;
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(
"name",
&config.branch_name.with_value(&truncated_and_symbol),
);
let mut graphemes = get_graphemes(&branch_name);
let trunc_len = len.min(graphemes.len());
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)
}
fn get_graphemes(text: &str, length: usize) -> String {
fn get_first_grapheme(text: &str) -> &str {
UnicodeSegmentation::graphemes(text, true)
.take(length)
.collect::<Vec<&str>>()
.concat()
.next()
.unwrap_or("")
}
fn graphemes_len(text: &str) -> usize {
UnicodeSegmentation::graphemes(&text[..], true).count()
fn get_graphemes(text: &str) -> Vec<&str> {
UnicodeSegmentation::graphemes(text, true).collect()
}

View File

@ -2,23 +2,14 @@ use super::{Context, Module, RootModuleConfig};
use git2::Repository;
use crate::configs::git_commit::GitCommitConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the Git commit in the current directory
///
/// Will display the commit hash if the current directory is a git repo
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("git_commit");
let config = 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 config: GitCommitConfig = GitCommitConfig::try_load(module.config);
let repo = context.get_repo().ok()?;
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 head_commit = git_head.peel_to_commit().ok()?;
let commit_oid = head_commit.id();
module.create_segment(
"hash",
&config.hash.with_value(&id_to_hex_abbrev(
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 {
"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)
}

View File

@ -1,8 +1,9 @@
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::formatter::StringFormatter;
/// 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 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_root = repo.root.as_ref()?;
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 {
StateDescription::Label(label) => label,
StateDescription::LabelAndProgress(label, _) => label,
StateDescription::Clean => {
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|variable, _| match variable {
"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;
}
};
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)
}
@ -53,40 +54,57 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
fn get_state_description<'a>(
state: RepositoryState,
root: &'a std::path::PathBuf,
config: GitStateConfig<'a>,
) -> StateDescription<'a> {
config: &GitStateConfig<'a>,
) -> Option<StateDescription<'a>> {
match state {
RepositoryState::Clean => StateDescription::Clean,
RepositoryState::Merge => StateDescription::Label(StateLabel::new("merge", config.merge)),
RepositoryState::Revert => {
StateDescription::Label(StateLabel::new("revert", config.revert))
}
RepositoryState::RevertSequence => {
StateDescription::Label(StateLabel::new("revert", config.revert))
}
RepositoryState::CherryPick => {
StateDescription::Label(StateLabel::new("cherry_pick", config.cherry_pick))
}
RepositoryState::CherryPickSequence => {
StateDescription::Label(StateLabel::new("cherry_pick", config.cherry_pick))
}
RepositoryState::Bisect => {
StateDescription::Label(StateLabel::new("bisect", config.bisect))
}
RepositoryState::ApplyMailbox => StateDescription::Label(StateLabel::new("am", config.am)),
RepositoryState::ApplyMailboxOrRebase => {
StateDescription::Label(StateLabel::new("am_or_rebase", config.am_or_rebase))
}
RepositoryState::Rebase => describe_rebase(root, config.rebase),
RepositoryState::RebaseInteractive => describe_rebase(root, config.rebase),
RepositoryState::RebaseMerge => describe_rebase(root, config.rebase),
RepositoryState::Clean => None,
RepositoryState::Merge => Some(StateDescription {
label: config.merge,
current: None,
total: None,
}),
RepositoryState::Revert => Some(StateDescription {
label: config.revert,
current: None,
total: None,
}),
RepositoryState::RevertSequence => Some(StateDescription {
label: config.revert,
current: None,
total: None,
}),
RepositoryState::CherryPick => Some(StateDescription {
label: config.cherry_pick,
current: None,
total: None,
}),
RepositoryState::CherryPickSequence => Some(StateDescription {
label: config.cherry_pick,
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>(
root: &'a PathBuf,
rebase_config: SegmentConfig<'a>,
) -> StateDescription<'a> {
fn describe_rebase<'a>(root: &'a PathBuf, rebase_config: &'a str) -> StateDescription<'a> {
/*
* 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
@ -98,12 +116,12 @@ fn describe_rebase<'a>(
let dot_git = root.join(".git");
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()
};
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 quantity = contents.trim().parse::<usize>().ok()?;
Some(quantity)
@ -112,7 +130,7 @@ fn describe_rebase<'a>(
let paths_to_progress = |current_path: &str, total_path: &str| {
let current = file_to_usize(current_path)?;
let total = file_to_usize(total_path)?;
Some(StateProgress { current, total })
Some((current, total))
};
let progress = if has_path("rebase-merge") {
@ -123,32 +141,15 @@ fn describe_rebase<'a>(
None
};
match progress {
None => StateDescription::Label(StateLabel::new("rebase", rebase_config)),
Some(progress) => {
StateDescription::LabelAndProgress(StateLabel::new("rebase", rebase_config), progress)
}
StateDescription {
label: rebase_config,
current: Some(format!("{}", progress.unwrap().0)),
total: Some(format!("{}", progress.unwrap().1)),
}
}
enum StateDescription<'a> {
Clean,
Label(StateLabel<'a>),
LabelAndProgress(StateLabel<'a>, StateProgress),
}
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 }
}
struct StateDescription<'a> {
label: &'a str,
current: Option<String>,
total: Option<String>,
}

View File

@ -2,10 +2,13 @@ use git2::{Repository, Status};
use super::{Context, Module, RootModuleConfig};
use crate::config::SegmentConfig;
use crate::configs::git_status::{CountConfig, GitStatusConfig};
use std::borrow::BorrowMut;
use std::collections::HashMap;
use crate::configs::git_status::GitStatusConfig;
use crate::context::Repo;
use crate::formatter::StringFormatter;
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
///
@ -23,167 +26,224 @@ use std::collections::HashMap;
/// - `✘` — A file's deletion has been added to the staging area
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let repo = context.get_repo().ok()?;
let branch_name = repo.branch.as_ref()?;
let repo_root = repo.root.as_ref()?;
let mut repository = Repository::open(repo_root).ok()?;
let info = Arc::new(GitStatusInfo::load(repo));
let mut module = context.new_module("git_status");
let config: GitStatusConfig = GitStatusConfig::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_status = get_repo_status(repository.borrow_mut());
log::debug!("Repo status: {:?}", repo_status);
let ahead_behind = get_ahead_behind(&repository, branch_name);
if ahead_behind == Ok((0, 0)) {
log::trace!("No ahead/behind found");
} 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,
},
);
};
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|variable, _| match variable {
"all_status" => Some(ALL_STATUS_FORMAT),
_ => None,
})
.map_style(|variable: &str| match variable {
"style" => Some(Ok(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 {
module.create_segment("diverged", &config.diverged);
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)
});
if config.show_sync_count {
add_ahead(&mut module);
add_behind(&mut module);
module.set_segments(match parsed {
Ok(segments) => {
if segments.is_empty() {
return None;
} else {
segments
}
}
if ahead > 0 && behind == 0 {
add_ahead(&mut module);
}
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() {
Err(error) => {
log::warn!("Error in module `git_status`:\n{}", error);
return None;
}
});
Some(module)
}
fn create_segment_with_count<'a>(
module: &mut Module<'a>,
name: &str,
count: usize,
config: &SegmentConfig<'a>,
count_config: CountConfig,
) {
if count > 0 {
module.create_segment(name, &config);
if count_config.enabled {
module.create_segment(
&format!("{}_count", name),
&SegmentConfig::new(&count.to_string()).with_style(count_config.style),
);
struct GitStatusInfo<'a> {
repo: &'a Repo,
ahead_behind: RwLock<Option<Result<(usize, usize), git2::Error>>>,
repo_status: RwLock<Option<Result<RepoStatus, git2::Error>>>,
stashed_count: RwLock<Option<Result<usize, git2::Error>>>,
}
impl<'a> GitStatusInfo<'a> {
pub fn load(repo: &'a Repo) -> Self {
Self {
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)
}
}
@ -191,6 +251,8 @@ fn create_segment_with_count<'a>(
fn get_repo_status(repository: &mut Repository) -> Result<RepoStatus, git2::Error> {
let mut status_options = git2::StatusOptions::new();
let mut repo_status = RepoStatus::default();
match repository.config()?.get_entry("status.showUntrackedFiles") {
Ok(entry) => status_options.include_untracked(entry.value() != Some("no")),
_ => 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)
.include_unmodified(true);
let statuses: Vec<Status> = repository
.statuses(Some(&mut status_options))?
.iter()
.map(|s| s.status())
.collect();
let statuses = repository.statuses(Some(&mut status_options))?;
if statuses.is_empty() {
return Err(git2::Error::from_str("Repo has no status"));
}
let statuses_count = count_statuses(statuses);
let repo_status: RepoStatus = RepoStatus {
conflicted: *statuses_count.get("conflicted").unwrap_or(&0),
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)?,
};
statuses
.iter()
.map(|s| s.status())
.for_each(|status| repo_status.add(status));
Ok(repo_status)
}
fn count_statuses(statuses: Vec<Status>) -> HashMap<&'static str, usize> {
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> {
fn get_stashed_count(repository: &mut Repository) -> Result<usize, git2::Error> {
let mut count = 0;
repository.stash_foreach(|_, _, _| {
count += 1;
@ -303,5 +310,65 @@ struct RepoStatus {
modified: usize,
staged: 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 crate::configs::go::GoConfig;
use crate::formatter::StringFormatter;
use crate::utils;
/// 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 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.create_segment("symbol", &config.symbol);
let formatted_version =
format_go_version(&utils::exec_cmd("go", &["version"])?.stdout.as_str())?;
module.create_segment("version", &config.version.with_value(&formatted_version));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `golang`:\n{}", error);
return None;
}
});
Some(module)
}

View File

@ -3,6 +3,7 @@ use unicode_segmentation::UnicodeSegmentation;
use super::{Context, Module, RootModuleConfig};
use crate::configs::hg_branch::HgBranchConfig;
use crate::formatter::StringFormatter;
/// 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 config = HgBranchConfig::try_load(module.config);
module.set_style(config.style);
module.get_prefix().set_value("on ");
module.create_segment("symbol", &config.symbol);
let config: HgBranchConfig = HgBranchConfig::try_load(module.config);
// TODO: Once error handling is implemented, warn the user if their config
// truncation length is nonsensical
@ -46,10 +42,30 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
truncated_graphemes
};
module.create_segment(
"name",
&config.branch_name.with_value(&truncated_and_symbol),
);
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 {
"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)
}

View File

@ -1,10 +1,11 @@
use std::env;
use super::{Context, Module, SegmentConfig};
use super::{Context, Module};
use std::ffi::OsString;
use crate::config::RootModuleConfig;
use crate::configs::hostname::HostnameConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the system hostname
///
@ -42,10 +43,26 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
host.as_ref()
};
module.set_style(config.style);
let hostname_stacked = format!("{}{}{}", config.prefix, host, config.suffix);
module.create_segment("hostname", &SegmentConfig::new(&hostname_stacked));
module.get_prefix().set_value("on ");
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 {
"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)
}

View File

@ -1,6 +1,7 @@
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::utils;
@ -25,12 +26,31 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
Some(java_version) => {
let mut module = context.new_module("java");
let config: JavaConfig = JavaConfig::try_load(module.config);
module.set_style(config.style);
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)
}
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::formatter::StringFormatter;
/// Creates a segment to show if there are any active jobs running
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("jobs");
let config: JobsConfig = JobsConfig::try_load(module.config);
module.set_style(config.style);
let config = JobsConfig::try_load(module.config);
let props = &context.properties;
let num_of_jobs = props
@ -20,11 +18,37 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
if num_of_jobs == 0 {
return None;
}
module.create_segment("symbol", &config.symbol);
if num_of_jobs > config.threshold {
module.create_segment("number", &SegmentConfig::new(&num_of_jobs.to_string()));
let module_number = if num_of_jobs > config.threshold {
num_of_jobs.to_string()
} else {
"".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;
}
module.get_prefix().set_value("");
});
Some(module)
}

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig};
use crate::configs::julia::JuliaConfig;
use crate::formatter::StringFormatter;
use crate::utils;
/// 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 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.create_segment("symbol", &config.symbol);
let formatted_version =
format_julia_version(&utils::exec_cmd("julia", &["--version"])?.stdout.as_str())?;
module.create_segment("version", &config.version.with_value(&formatted_version));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `julia`:\n{}", error);
return None;
}
});
Some(module)
}

View File

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

View File

@ -1,5 +1,5 @@
use super::{Context, Module};
use crate::config::SegmentConfig;
use crate::segment::Segment;
/// Creates a module for the line break
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");
module.get_prefix().set_value("");
module.get_suffix().set_value("");
module.create_segment("character", &SegmentConfig::new(LINE_ENDING));
module.set_segments(vec![Segment {
_name: "line_break".to_string(),
style: None,
value: LINE_ENDING.to_string(),
}]);
Some(module)
}

View File

@ -4,6 +4,7 @@ use sysinfo::{RefreshKind, SystemExt};
use super::{Context, Module, RootModuleConfig, Shell};
use crate::configs::memory_usage::MemoryConfig;
use crate::formatter::StringFormatter;
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));
@ -12,6 +13,14 @@ fn format_kib(n_kib: u64) -> String {
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
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
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
// 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`
_ => "%",
};
@ -28,54 +37,52 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
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 used_memory_kib = system.get_used_memory();
let total_memory_kib = system.get_total_memory();
let percent_mem_used = (used_memory_kib as f64 / total_memory_kib as f64) * 100.;
let ram_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;
if percent_mem_used.round() < threshold as f64 {
if ram_used.round() < threshold as f64 {
return None;
}
let show_percentage = config.show_percentage;
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 ram = format_usage_total(used_memory_kib, total_memory_kib);
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 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 {
format!("{:.0}{}", percent_swap_used, percent_sign)
} else {
format!(
"{}/{}",
format_kib(used_swap_kib),
format_kib(total_swap_kib)
)
};
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 {
"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.create_segment("swap", &config.swap.with_value(&swap));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `memory_usage`:\n{}", error);
return None;
}
});
Some(module)
}

View File

@ -45,7 +45,7 @@ mod zig;
#[cfg(feature = "battery")]
mod battery;
use crate::config::{RootModuleConfig, SegmentConfig};
use crate::config::RootModuleConfig;
use crate::context::{Context, Shell};
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::formatter::StringFormatter;
use crate::utils;
/// Creates a module with the current Nim version
@ -19,16 +20,38 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
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 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.create_segment("version", &SegmentConfig::new(&formatted_nim_version));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `nim`:\n{}", error);
return None;
}
});
Some(module)
}

View File

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

View File

@ -1,34 +1,48 @@
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 regex::Regex;
use serde_json as json;
use super::{RootModuleConfig, SegmentConfig};
use crate::configs::package::PackageConfig;
/// 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)
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
let mut module = context.new_module("package");
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) {
Some(package_version) => {
module.set_style(config.style);
module.get_prefix().set_value("is ");
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(&module_version)),
_ => None,
})
.parse(None)
});
module.create_segment("symbol", &config.symbol);
module.create_segment("version", &SegmentConfig::new(&package_version));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `package`:\n{}", error);
return None;
}
});
Some(module)
}
None => None,
}
}
fn extract_cargo_version(file_contents: &str) -> Option<String> {
let cargo_toml: toml::Value = toml::from_str(file_contents).ok()?;

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig, SegmentConfig};
use super::{Context, Module, RootModuleConfig};
use crate::configs::php::PhpConfig;
use crate::formatter::StringFormatter;
use crate::utils;
/// 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) => {
let php_version = php_cmd_output.stdout;
let mut module = context.new_module("php");
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.create_segment("symbol", &config.symbol);
module.create_segment("version", &SegmentConfig::new(&formatted_version));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `php`:\n{}", error);
return None;
}
});
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::formatter::StringFormatter;
use crate::utils;
/// 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 formatted_version = Some(format!("v{}", purs_version.trim()))?;
let mut module = context.new_module("purescript");
let config: PureScriptConfig = PureScriptConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
module.create_segment("version", &SegmentConfig::new(&formatted_version));
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_meta(|variable, _| match variable {
"symbol" => Some(config.symbol),
_ => None,
})
.map_style(|variable| match variable {
"style" => Some(Ok(config.style)),
_ => None,
})
.map(|variable| match variable {
"version" => 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)
}

View File

@ -1,8 +1,9 @@
use std::env;
use std::path::Path;
use super::{Context, Module, RootModuleConfig, SegmentConfig};
use super::{Context, Module, RootModuleConfig};
use crate::configs::python::PythonConfig;
use crate::formatter::StringFormatter;
use crate::utils;
/// Creates a module with the current Python version
@ -41,25 +42,39 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
return None;
}
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
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()));
let python_version = if config.pyenv_version_name {
utils::exec_cmd("pyenv", &["version-name"])?.stdout
} else {
let python_version = get_python_version(&config.python_binary)?;
let formatted_version = format_python_version(&python_version);
module.create_segment("version", &SegmentConfig::new(&formatted_version));
let version = get_python_version(&config.python_binary)?;
format_python_version(&version)
};
let virtual_env = get_python_virtual_env();
if let Some(virtual_env) = get_python_virtual_env() {
module.create_segment(
"virtualenv",
&SegmentConfig::new(&format!(" ({})", virtual_env)),
);
};
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(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)
}

View File

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

View File

@ -5,6 +5,7 @@ use std::{env, fs};
use super::{Context, Module, RootModuleConfig};
use crate::configs::rust::RustConfig;
use crate::formatter::StringFormatter;
/// Creates a module with the current Rust version
///
@ -22,6 +23,39 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
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.
// 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()?)
};
let mut module = context.new_module("rust");
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)
Some(module_version)
}
fn env_rustup_toolchain() -> Option<String> {

View File

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

View File

@ -1,7 +1,9 @@
use super::{Context, Module, RootModuleConfig};
use crate::configs::terraform::TerraformConfig;
use crate::formatter::StringFormatter;
use crate::utils;
use std::env;
use std::io;
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 config: TerraformConfig = TerraformConfig::try_load(module.config);
module.set_style(config.style);
module.create_segment("symbol", &config.symbol);
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_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 {
let terraform_version =
format_terraform_version(&utils::exec_cmd("terraform", &["version"])?.stdout.as_str())?;
module.create_segment("version", &config.version.with_value(&terraform_version));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
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)
}

View File

@ -1,14 +1,11 @@
use chrono::{DateTime, FixedOffset, Local, NaiveTime, Utc};
use super::{Context, Module};
use crate::config::{RootModuleConfig, SegmentConfig};
use super::{Context, Module, RootModuleConfig};
use crate::configs::time::TimeConfig;
use crate::formatter::StringFormatter;
/// Outputs the current time
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
const TIME_PREFIX: &str = "at ";
let mut module = context.new_module("time");
let config: TimeConfig = TimeConfig::try_load(module.config);
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 time_format = config.format.unwrap_or(default_format);
let time_format = config.time_format.unwrap_or(default_format);
log::trace!(
"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())
};
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.create_segment(
"time",
&SegmentConfig {
value: &formatted_time_string,
style: None,
},
);
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `time`: \n{}", error);
return None;
}
});
Some(module)
}

View File

@ -1,8 +1,9 @@
use std::env;
use super::{Context, Module, RootModuleConfig, SegmentConfig};
use super::{Context, Module, RootModuleConfig};
use crate::configs::username::UsernameConfig;
use crate::formatter::StringFormatter;
use crate::utils;
/// 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);
if user != logname || ssh_connection.is_some() || user_uid == ROOT_UID || config.show_always {
let username = user?;
let parsed = StringFormatter::new(config.format).and_then(|formatter| {
formatter
.map_style(|variable| match variable {
"style" => {
let module_style = match user_uid {
Some(0) => config.style_root,
_ => config.style_user,
};
module.set_style(module_style);
module.create_segment("username", &SegmentConfig::new(&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)
} else {

View File

@ -1,6 +1,7 @@
use super::{Context, Module, RootModuleConfig};
use crate::configs::zig::ZigConfig;
use crate::formatter::StringFormatter;
use crate::utils;
/// 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 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.create_segment("version", &config.version.with_value(&zig_version));
module.set_segments(match parsed {
Ok(segments) => segments,
Err(error) => {
log::warn!("Error in module `zig`:\n{}", error);
return None;
}
});
Some(module)
}

View File

@ -1,14 +1,18 @@
use ansi_term::ANSIStrings;
use clap::ArgMatches;
use rayon::prelude::*;
use std::collections::BTreeSet;
use std::fmt::{self, Debug, Write as FmtWrite};
use std::io::{self, Write};
use unicode_width::UnicodeWidthChar;
use crate::configs::PROMPT_ORDER;
use crate::context::{Context, Shell};
use crate::formatter::{StringFormatter, VariableHolder};
use crate::module::Module;
use crate::module::ALL_MODULES;
use crate::modules;
use crate::segment::Segment;
pub fn prompt(args: ArgMatches) {
let context = Context::new(args);
@ -21,34 +25,53 @@ pub fn get_prompt(context: Context) -> String {
let config = context.config.get_root_config();
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
// breaks things (see #808,#824,#834). Should only be printed in fish.
if let Shell::Fish = context.shell {
buf.push_str("\x1b[J"); // An ASCII control code to clear screen
}
let modules = compute_modules(&context);
let mut print_without_prefix = true;
let printable = modules.iter();
for module in printable {
// Skip printing the prefix of a module after the line_break
if print_without_prefix {
let module_without_prefix = module.to_string_without_prefix(context.shell);
write!(buf, "{}", module_without_prefix).unwrap()
let formatter = if let Ok(formatter) = StringFormatter::new(config.format) {
formatter
} else {
let module = module.ansi_strings_for_shell(context.shell);
write!(buf, "{}", ANSIStrings(&module)).unwrap();
log::error!("Error parsing `format`");
buf.push_str(">");
return buf;
};
let modules = formatter.get_variables();
let formatter = formatter.map_variables_to_segments(|module| {
// 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 {
// Get segments from module
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
}
@ -72,16 +95,15 @@ pub fn explain(args: ArgMatches) {
desc: String,
}
let dont_print = vec!["line_break", "character"];
let dont_print = vec!["line_break"];
let modules = compute_modules(&context)
.into_iter()
.filter(|module| !dont_print.contains(&module.get_name().as_str()))
.map(|module| {
let ansi_strings = module.ansi_strings();
let value = module.get_segments().join("");
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),
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>> {
enum Mod<'a> {
Builtin(&'a str),
Custom(&'a str),
let mut prompt_order: Vec<Module<'a>> = Vec::new();
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);
impl Debug for DebugCustomModules<'_> {
@ -145,35 +194,33 @@ 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
let config_prompt_order = context.config.get_root_config().prompt_order;
for module in &config_prompt_order {
if ALL_MODULES.contains(module) {
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));
if !context.is_module_disabled_in_config(module) {
modules.push(modules::handle(module, &context));
}
} else if *module == "custom" {
} 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));
}
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) => prompt_order.push(Mod::Custom(&module[7..])),
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: {:?}",
@ -193,26 +240,17 @@ fn compute_modules<'a>(context: &'a Context) -> Vec<Module<'a>> {
module,
);
}
}
prompt_order
.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>>>()
modules.into_iter().flatten().collect()
}
fn should_add_implicit_custom_module(
custom_module: &str,
config: &toml::Value,
config_prompt_order: &[&str],
module_list: &BTreeSet<String>,
) -> bool {
let is_explicitly_specified = config_prompt_order.iter().any(|x| {
x.len() == 7 + custom_module.len() && &x[..7] == "custom." && &x[7..] == custom_module
});
let explicit_module_name = format!("custom.{}", custom_module);
let is_explicitly_specified = module_list.contains(&explicit_module_name);
if is_explicitly_specified {
// 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")
.env("AWS_REGION", "ap-northeast-2")
.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();
assert_eq!(expected, actual);
Ok(())
@ -37,7 +37,7 @@ fn region_set_with_alias() -> io::Result<()> {
ap-southeast-2 = "au"
})
.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();
assert_eq!(expected, actual);
Ok(())
@ -49,7 +49,7 @@ fn default_region_set() -> io::Result<()> {
.env("AWS_REGION", "ap-northeast-2")
.env("AWS_DEFAULT_REGION", "ap-northeast-1")
.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();
assert_eq!(expected, actual);
Ok(())
@ -112,7 +112,7 @@ region = us-east-2
let output = common::render_module("aws")
.env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref())
.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();
assert_eq!(expected, actual);
dir.close()
@ -155,10 +155,6 @@ fn profile_and_region_set_with_display_all() -> io::Result<()> {
let output = common::render_module("aws")
.env("AWS_PROFILE", "astronauts")
.env("AWS_REGION", "ap-northeast-1")
.use_config(toml::toml! {
[aws]
displayed_items = "all"
})
.output()?;
let expected = format!(
"on {} ",
@ -173,10 +169,6 @@ fn profile_and_region_set_with_display_all() -> io::Result<()> {
fn profile_set_with_display_all() -> io::Result<()> {
let output = common::render_module("aws")
.env("AWS_PROFILE", "astronauts")
.use_config(toml::toml! {
[aws]
displayed_items = "all"
})
.output()?;
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts"));
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<()> {
let output = common::render_module("aws")
.env("AWS_REGION", "ap-northeast-1")
.use_config(toml::toml! {
[aws]
displayed_items = "all"
})
.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();
assert_eq!(expected, actual);
Ok(())
@ -206,7 +194,7 @@ fn profile_and_region_set_with_display_region() -> io::Result<()> {
.env("AWS_DEFAULT_REGION", "ap-northeast-1")
.use_config(toml::toml! {
[aws]
displayed_items = "region"
format = "on [$symbol$region]($style) "
})
.output()?;
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")
.use_config(toml::toml! {
[aws]
displayed_items = "profile"
format = "on [$symbol$profile]($style) "
})
.output()?;
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")
.use_config(toml::toml! {
[aws]
displayed_items = "profile"
format = "on [$symbol$profile]($style) "
})
.output()?;
let expected = "";
let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ "));
let actual = String::from_utf8(output.stdout).unwrap();
assert_eq!(expected, actual);
Ok(())
@ -252,7 +240,7 @@ fn region_not_set_with_display_region() -> io::Result<()> {
let output = common::render_module("aws")
.use_config(toml::toml! {
[aws]
displayed_items = "region"
format = "on [$symbol$region]($style) "
})
.output()?;
let expected = "";

View File

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

View File

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

View File

@ -16,7 +16,7 @@ const EXE_PATH: &str = "./target/debug/starship.exe";
const EXE_PATH: &str = "./target/debug/starship";
/// 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);
command

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