mirror of
https://github.com/starship/starship
synced 2024-10-04 15:00:10 +00:00
refactor: Refactoring config (#383)
This PR refactors config and puts configuration files for all modules in `configs/`.
This commit is contained in:
parent
9e9eb6a8ef
commit
dd0b1a1aa2
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,5 +1,5 @@
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
target/
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
@ -15,6 +15,5 @@ Cargo.lock
|
||||||
.idea/
|
.idea/
|
||||||
/*.iml
|
/*.iml
|
||||||
|
|
||||||
# Compiled files for documentation
|
# Vim swap files
|
||||||
docs/node_modules
|
*.swp
|
||||||
docs/.vuepress/dist/
|
|
||||||
|
|
442
Cargo.lock
generated
442
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
54
Cargo.toml
54
Cargo.toml
|
@ -1,49 +1,5 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "starship"
|
members = [
|
||||||
version = "0.19.0"
|
"starship",
|
||||||
edition = "2018"
|
"starship_module_config_derive",
|
||||||
authors = ["Matan Kushner <hello@matchai.me>"]
|
]
|
||||||
homepage = "https://starship.rs"
|
|
||||||
documentation = "https://starship.rs/guide/"
|
|
||||||
repository = "https://github.com/starship/starship"
|
|
||||||
readme = "README.md"
|
|
||||||
license = "ISC"
|
|
||||||
keywords = ["prompt", "shell", "bash", "fish", "zsh"]
|
|
||||||
categories = ["command-line-utilities"]
|
|
||||||
description = """
|
|
||||||
The cross-shell prompt for astronauts. ☄🌌️
|
|
||||||
"""
|
|
||||||
exclude = ["docs/**/*"]
|
|
||||||
|
|
||||||
[badges]
|
|
||||||
azure-devops = { project = "starship-control/starship", pipeline = "Starship Test Suite" }
|
|
||||||
is-it-maintained-issue-resolution = { repository = "starship/starship" }
|
|
||||||
is-it-maintained-open-issues = { repository = "starship/starship" }
|
|
||||||
maintenance = { status = "actively-developed" }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["battery"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap = "2.33.0"
|
|
||||||
ansi_term = "0.12.1"
|
|
||||||
dirs = "2.0.2"
|
|
||||||
git2 = { version = "0.10.1", default-features = false, features = [] }
|
|
||||||
toml = "0.5.3"
|
|
||||||
serde_json = "1.0.40"
|
|
||||||
rayon = "1.2.0"
|
|
||||||
pretty_env_logger = "0.3.1"
|
|
||||||
log = "0.4.8"
|
|
||||||
# battery is optional (on by default) because the crate doesn't currently build for Termux
|
|
||||||
# see: https://github.com/svartalf/rust-battery/issues/33
|
|
||||||
battery = { version = "0.7.4", optional = true }
|
|
||||||
path-slash = "0.1.1"
|
|
||||||
unicode-segmentation = "1.3.0"
|
|
||||||
gethostname = "0.2.0"
|
|
||||||
once_cell = "1.2.0"
|
|
||||||
chrono = "0.4"
|
|
||||||
sysinfo = "0.9.5"
|
|
||||||
byte-unit = "3.0.3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tempfile = "3.1.0"
|
|
||||||
|
|
473
src/config.rs
473
src/config.rs
|
@ -1,473 +0,0 @@
|
||||||
use crate::utils;
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
use dirs::home_dir;
|
|
||||||
use toml::value::Table;
|
|
||||||
use toml::value::Value;
|
|
||||||
|
|
||||||
use ansi_term::Color;
|
|
||||||
|
|
||||||
pub trait Config {
|
|
||||||
fn initialize() -> Table;
|
|
||||||
fn config_from_file() -> Option<Table>;
|
|
||||||
fn get_module_config(&self, module_name: &str) -> Option<&Table>;
|
|
||||||
|
|
||||||
// Config accessor methods
|
|
||||||
fn get_as_bool(&self, key: &str) -> Option<bool>;
|
|
||||||
fn get_as_str(&self, key: &str) -> Option<&str>;
|
|
||||||
fn get_as_i64(&self, key: &str) -> Option<i64>;
|
|
||||||
fn get_as_array(&self, key: &str) -> Option<&Vec<Value>>;
|
|
||||||
fn get_as_ansi_style(&self, key: &str) -> Option<ansi_term::Style>;
|
|
||||||
fn get_as_segment_config(&self, key: &str) -> Option<SegmentConfig>;
|
|
||||||
|
|
||||||
// Internal implementation for accessors
|
|
||||||
fn get_config(&self, key: &str) -> Option<&Value>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config for Table {
|
|
||||||
/// Initialize the Config struct
|
|
||||||
fn initialize() -> Table {
|
|
||||||
if let Some(file_data) = Self::config_from_file() {
|
|
||||||
return file_data;
|
|
||||||
}
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a config from a starship configuration file
|
|
||||||
fn config_from_file() -> Option<Table> {
|
|
||||||
let file_path = if let Ok(path) = env::var("STARSHIP_CONFIG") {
|
|
||||||
// Use $STARSHIP_CONFIG as the config path if available
|
|
||||||
log::debug!("STARSHIP_CONFIG is set: \n{}", &path);
|
|
||||||
path
|
|
||||||
} else {
|
|
||||||
// Default to using ~/.config/starship.toml
|
|
||||||
log::debug!("STARSHIP_CONFIG is not set");
|
|
||||||
let config_path = home_dir()?.join(".config/starship.toml");
|
|
||||||
let config_path_str = config_path.to_str()?.to_owned();
|
|
||||||
log::debug!("Using default config path: {}", config_path_str);
|
|
||||||
config_path_str
|
|
||||||
};
|
|
||||||
|
|
||||||
let toml_content = match utils::read_file(&file_path) {
|
|
||||||
Ok(content) => {
|
|
||||||
log::trace!("Config file content: \n{}", &content);
|
|
||||||
Some(content)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::debug!("Unable to read config file content: \n{}", &e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
let config = toml::from_str(&toml_content).ok()?;
|
|
||||||
log::debug!("Config parsed: \n{:?}", &config);
|
|
||||||
Some(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the config value for a given key
|
|
||||||
fn get_config(&self, key: &str) -> Option<&Value> {
|
|
||||||
log::trace!("Looking for config key \"{}\"", key);
|
|
||||||
let value = self.get(key);
|
|
||||||
log_if_key_found(key, value);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the subset of the table for a module by its name
|
|
||||||
fn get_module_config(&self, key: &str) -> Option<&Table> {
|
|
||||||
log::trace!("Looking for module key \"{}\"", key);
|
|
||||||
let value = self.get(key);
|
|
||||||
log_if_key_found(key, value);
|
|
||||||
value.and_then(|value| {
|
|
||||||
let casted = Value::as_table(value);
|
|
||||||
log_if_type_correct(key, value, casted);
|
|
||||||
casted
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a key from a module's configuration as a boolean
|
|
||||||
fn get_as_bool(&self, key: &str) -> Option<bool> {
|
|
||||||
log::trace!("Looking for boolean key \"{}\"", key);
|
|
||||||
let value = self.get(key);
|
|
||||||
log_if_key_found(key, value);
|
|
||||||
value.and_then(|value| {
|
|
||||||
let casted = Value::as_bool(value);
|
|
||||||
log_if_type_correct(key, value, casted);
|
|
||||||
casted
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a key from a module's configuration as a string
|
|
||||||
fn get_as_str(&self, key: &str) -> Option<&str> {
|
|
||||||
log::trace!("Looking for string key \"{}\"", key);
|
|
||||||
let value = self.get(key);
|
|
||||||
log_if_key_found(key, value);
|
|
||||||
value.and_then(|value| {
|
|
||||||
let casted = Value::as_str(value);
|
|
||||||
log_if_type_correct(key, value, casted);
|
|
||||||
casted
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a key from a module's configuration as an integer
|
|
||||||
fn get_as_i64(&self, key: &str) -> Option<i64> {
|
|
||||||
log::trace!("Looking for integer key \"{}\"", key);
|
|
||||||
let value = self.get(key);
|
|
||||||
log_if_key_found(key, value);
|
|
||||||
value.and_then(|value| {
|
|
||||||
let casted = Value::as_integer(value);
|
|
||||||
log_if_type_correct(key, value, casted);
|
|
||||||
casted
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a key from a module's configuration as a vector
|
|
||||||
fn get_as_array(&self, key: &str) -> Option<&Vec<Value>> {
|
|
||||||
log::trace!("Looking for array key \"{}\"", key);
|
|
||||||
let value = self.get(key);
|
|
||||||
log_if_key_found(key, value);
|
|
||||||
value.and_then(|value| {
|
|
||||||
let casted = Value::as_array(value);
|
|
||||||
log_if_type_correct(key, value, casted);
|
|
||||||
casted
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a text key and attempt to interpret it into an ANSI style.
|
|
||||||
fn get_as_ansi_style(&self, key: &str) -> Option<ansi_term::Style> {
|
|
||||||
// TODO: This should probably not unwrap to an empty new Style but inform the user about the problem
|
|
||||||
self.get_as_str(key)
|
|
||||||
.map(|x| parse_style_string(x).unwrap_or_default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a key from a module's configuration as a segment config.
|
|
||||||
///
|
|
||||||
/// The config can be
|
|
||||||
///
|
|
||||||
/// - a string, will be interpreted as value.
|
|
||||||
/// - a table with optional { value, style } keys.
|
|
||||||
/// If omitted, default value will be used.
|
|
||||||
///
|
|
||||||
/// Returns `Some(SegmentConfig)` if key exists in the configuration, else `None`.
|
|
||||||
fn get_as_segment_config(&self, key: &str) -> Option<SegmentConfig> {
|
|
||||||
self.get_config(key).and_then(|segment_config: &Value| {
|
|
||||||
match segment_config {
|
|
||||||
toml::Value::String(value) => Some(SegmentConfig {
|
|
||||||
value: Some(value.as_str()),
|
|
||||||
style: None,
|
|
||||||
}),
|
|
||||||
toml::Value::Table(config_table) => Some(SegmentConfig {
|
|
||||||
value: config_table.get_as_str("value"),
|
|
||||||
style: config_table.get_as_ansi_style("style"),
|
|
||||||
}),
|
|
||||||
_ => {
|
|
||||||
log::debug!(
|
|
||||||
"Expected \"{}\" to be a string or config table. Instead received {} of type {}.",
|
|
||||||
key,
|
|
||||||
segment_config,
|
|
||||||
segment_config.type_str()
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_if_key_found(key: &str, something: Option<&Value>) {
|
|
||||||
if something.is_some() {
|
|
||||||
log::trace!("Value found for \"{}\": {:?}", key, &something);
|
|
||||||
} else {
|
|
||||||
log::trace!("No value found for \"{}\"", key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_if_type_correct<T: std::fmt::Debug>(
|
|
||||||
key: &str,
|
|
||||||
something: &Value,
|
|
||||||
casted_something: Option<T>,
|
|
||||||
) {
|
|
||||||
if let Some(casted) = casted_something {
|
|
||||||
log::trace!(
|
|
||||||
"Value under key \"{}\" has the expected type. Proceeding with {:?} which was build from {:?}.",
|
|
||||||
key,
|
|
||||||
casted,
|
|
||||||
something
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log::debug!(
|
|
||||||
"Value under key \"{}\" did not have the expected type. Instead received {} of type {}.",
|
|
||||||
key,
|
|
||||||
something,
|
|
||||||
something.type_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 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)
|
|
||||||
- 'bg:<color>' (specifies that the color read should be a background color)
|
|
||||||
- 'underline'
|
|
||||||
- 'bold'
|
|
||||||
- 'italic'
|
|
||||||
- '<color>' (see the parse_color_string doc for valid color strings)
|
|
||||||
*/
|
|
||||||
fn parse_style_string(style_string: &str) -> Option<ansi_term::Style> {
|
|
||||||
style_string
|
|
||||||
.split_whitespace()
|
|
||||||
.fold(Some(ansi_term::Style::new()), |maybe_style, token| {
|
|
||||||
maybe_style.and_then(|style| {
|
|
||||||
let token = token.to_lowercase();
|
|
||||||
|
|
||||||
// Check for FG/BG identifiers and strip them off if appropriate
|
|
||||||
// If col_fg is true, color the foreground. If it's false, color the background.
|
|
||||||
let (token, col_fg) = if token.as_str().starts_with("fg:") {
|
|
||||||
(token.trim_start_matches("fg:").to_owned(), true)
|
|
||||||
} else if token.as_str().starts_with("bg:") {
|
|
||||||
(token.trim_start_matches("bg:").to_owned(), false)
|
|
||||||
} else {
|
|
||||||
(token, true) // Bare colors are assumed to color the foreground
|
|
||||||
};
|
|
||||||
|
|
||||||
match token.as_str() {
|
|
||||||
"underline" => Some(style.underline()),
|
|
||||||
"bold" => Some(style.bold()),
|
|
||||||
"italic" => Some(style.italic()),
|
|
||||||
"dimmed" => Some(style.dimmed()),
|
|
||||||
"none" => None,
|
|
||||||
|
|
||||||
// Try to see if this token parses as a valid color string
|
|
||||||
color_string => parse_color_string(color_string).map(|ansi_color| {
|
|
||||||
if col_fg {
|
|
||||||
style.fg(ansi_color)
|
|
||||||
} else {
|
|
||||||
style.on(ansi_color)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Parse a string that represents a color setting, returning None if this fails
|
|
||||||
There are three valid color formats:
|
|
||||||
- #RRGGBB (a hash followed by an RGB hex)
|
|
||||||
- u8 (a number from 0-255, representing an ANSI color)
|
|
||||||
- colstring (one of the 16 predefined color strings)
|
|
||||||
*/
|
|
||||||
fn parse_color_string(color_string: &str) -> Option<ansi_term::Color> {
|
|
||||||
// Parse RGB hex values
|
|
||||||
log::trace!("Parsing color_string: {}", color_string);
|
|
||||||
if color_string.starts_with('#') {
|
|
||||||
log::trace!(
|
|
||||||
"Attempting to read hexadecimal color string: {}",
|
|
||||||
color_string
|
|
||||||
);
|
|
||||||
let r: u8 = u8::from_str_radix(&color_string[1..3], 16).ok()?;
|
|
||||||
let g: u8 = u8::from_str_radix(&color_string[3..5], 16).ok()?;
|
|
||||||
let b: u8 = u8::from_str_radix(&color_string[5..7], 16).ok()?;
|
|
||||||
log::trace!("Read RGB color string: {},{},{}", r, g, b);
|
|
||||||
return Some(Color::RGB(r, g, b));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse a u8 (ansi color)
|
|
||||||
if let Result::Ok(ansi_color_num) = color_string.parse::<u8>() {
|
|
||||||
log::trace!("Read ANSI color string: {}", ansi_color_num);
|
|
||||||
return Some(Color::Fixed(ansi_color_num));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any predefined color strings
|
|
||||||
// There are no predefined enums for bright colors, so we use Color::Fixed
|
|
||||||
let predefined_color = match color_string.to_lowercase().as_str() {
|
|
||||||
"black" => Some(Color::Black),
|
|
||||||
"red" => Some(Color::Red),
|
|
||||||
"green" => Some(Color::Green),
|
|
||||||
"yellow" => Some(Color::Yellow),
|
|
||||||
"blue" => Some(Color::Blue),
|
|
||||||
"purple" => Some(Color::Purple),
|
|
||||||
"cyan" => Some(Color::Cyan),
|
|
||||||
"white" => Some(Color::White),
|
|
||||||
"bright-black" => Some(Color::Fixed(8)), // "bright-black" is dark grey
|
|
||||||
"bright-red" => Some(Color::Fixed(9)),
|
|
||||||
"bright-green" => Some(Color::Fixed(10)),
|
|
||||||
"bright-yellow" => Some(Color::Fixed(11)),
|
|
||||||
"bright-blue" => Some(Color::Fixed(12)),
|
|
||||||
"bright-purple" => Some(Color::Fixed(13)),
|
|
||||||
"bright-cyan" => Some(Color::Fixed(14)),
|
|
||||||
"bright-white" => Some(Color::Fixed(15)),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if predefined_color.is_some() {
|
|
||||||
log::trace!("Read predefined color: {}", color_string);
|
|
||||||
} else {
|
|
||||||
log::debug!("Could not parse color in string: {}", color_string);
|
|
||||||
}
|
|
||||||
predefined_color
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SegmentConfig<'a> {
|
|
||||||
pub value: Option<&'a str>,
|
|
||||||
pub style: Option<ansi_term::Style>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use ansi_term::Style;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_nonexisting() {
|
|
||||||
let table = toml::value::Table::new();
|
|
||||||
assert_eq!(table.get_as_bool("boolean"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_config() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
table.insert(String::from("config"), Value::Boolean(true));
|
|
||||||
assert_eq!(table.get_config("config"), Some(&Value::Boolean(true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_as_bool() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
|
|
||||||
table.insert(String::from("boolean"), Value::Boolean(true));
|
|
||||||
assert_eq!(table.get_as_bool("boolean"), Some(true));
|
|
||||||
|
|
||||||
table.insert(String::from("string"), Value::String(String::from("true")));
|
|
||||||
assert_eq!(table.get_as_bool("string"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_as_str() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
|
|
||||||
table.insert(String::from("string"), Value::String(String::from("hello")));
|
|
||||||
assert_eq!(table.get_as_str("string"), Some("hello"));
|
|
||||||
|
|
||||||
table.insert(String::from("boolean"), Value::Boolean(true));
|
|
||||||
assert_eq!(table.get_as_str("boolean"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_as_i64() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
|
|
||||||
table.insert(String::from("integer"), Value::Integer(82));
|
|
||||||
assert_eq!(table.get_as_i64("integer"), Some(82));
|
|
||||||
|
|
||||||
table.insert(String::from("string"), Value::String(String::from("82")));
|
|
||||||
assert_eq!(table.get_as_bool("string"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_as_array() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
|
|
||||||
table.insert(
|
|
||||||
String::from("array"),
|
|
||||||
Value::Array(vec![Value::Integer(1), Value::Integer(2)]),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_array("array"),
|
|
||||||
Some(&vec![Value::Integer(1), Value::Integer(2)])
|
|
||||||
);
|
|
||||||
|
|
||||||
table.insert(String::from("string"), Value::String(String::from("82")));
|
|
||||||
assert_eq!(table.get_as_array("string"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_styles_bold_italic_underline_green_dimmy_silly_caps() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
|
|
||||||
table.insert(
|
|
||||||
String::from("mystyle"),
|
|
||||||
Value::String(String::from("bOlD ItAlIc uNdErLiNe GrEeN dimmed")),
|
|
||||||
);
|
|
||||||
assert!(table.get_as_ansi_style("mystyle").unwrap().is_bold);
|
|
||||||
assert!(table.get_as_ansi_style("mystyle").unwrap().is_italic);
|
|
||||||
assert!(table.get_as_ansi_style("mystyle").unwrap().is_underline);
|
|
||||||
assert!(table.get_as_ansi_style("mystyle").unwrap().is_dimmed);
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_ansi_style("mystyle").unwrap(),
|
|
||||||
ansi_term::Style::new()
|
|
||||||
.bold()
|
|
||||||
.italic()
|
|
||||||
.underline()
|
|
||||||
.dimmed()
|
|
||||||
.fg(Color::Green)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_styles_plain_and_broken_styles() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
// Test a "plain" style with no formatting
|
|
||||||
table.insert(String::from("plainstyle"), Value::String(String::from("")));
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_ansi_style("plainstyle").unwrap(),
|
|
||||||
ansi_term::Style::new()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test a string that's clearly broken
|
|
||||||
table.insert(
|
|
||||||
String::from("broken"),
|
|
||||||
Value::String(String::from("djklgfhjkldhlhk;j")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_ansi_style("broken").unwrap(),
|
|
||||||
ansi_term::Style::new()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test a string that's nullified by `none`
|
|
||||||
table.insert(
|
|
||||||
String::from("nullified"),
|
|
||||||
Value::String(String::from("fg:red bg:green bold none")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_ansi_style("nullified").unwrap(),
|
|
||||||
ansi_term::Style::new()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test a string that's nullified by `none` at the start
|
|
||||||
table.insert(
|
|
||||||
String::from("nullified-start"),
|
|
||||||
Value::String(String::from("none fg:red bg:green bold")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_ansi_style("nullified-start").unwrap(),
|
|
||||||
ansi_term::Style::new()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn table_get_styles_ordered() {
|
|
||||||
let mut table = toml::value::Table::new();
|
|
||||||
|
|
||||||
// Test a background style with inverted order (also test hex + ANSI)
|
|
||||||
table.insert(
|
|
||||||
String::from("flipstyle"),
|
|
||||||
Value::String(String::from("bg:#050505 underline fg:120")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_ansi_style("flipstyle").unwrap(),
|
|
||||||
Style::new()
|
|
||||||
.underline()
|
|
||||||
.fg(Color::Fixed(120))
|
|
||||||
.on(Color::RGB(5, 5, 5))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test that the last color style is always the one used
|
|
||||||
table.insert(
|
|
||||||
String::from("multistyle"),
|
|
||||||
Value::String(String::from("bg:120 bg:125 bg:127 fg:127 122 125")),
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
table.get_as_ansi_style("multistyle").unwrap(),
|
|
||||||
Style::new().fg(Color::Fixed(125)).on(Color::Fixed(127))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
115
src/print.rs
115
src/print.rs
|
@ -1,115 +0,0 @@
|
||||||
use clap::ArgMatches;
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::context::Context;
|
|
||||||
use crate::module::Module;
|
|
||||||
use crate::module::ALL_MODULES;
|
|
||||||
use crate::modules;
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
const DEFAULT_PROMPT_ORDER: &[&str] = &[
|
|
||||||
"username",
|
|
||||||
"hostname",
|
|
||||||
"directory",
|
|
||||||
"git_branch",
|
|
||||||
"git_state",
|
|
||||||
"git_status",
|
|
||||||
"package",
|
|
||||||
"nodejs",
|
|
||||||
"ruby",
|
|
||||||
"rust",
|
|
||||||
"python",
|
|
||||||
"golang",
|
|
||||||
"java",
|
|
||||||
"nix_shell",
|
|
||||||
"memory_usage",
|
|
||||||
"aws",
|
|
||||||
"env_var",
|
|
||||||
"cmd_duration",
|
|
||||||
"line_break",
|
|
||||||
"jobs",
|
|
||||||
#[cfg(feature = "battery")]
|
|
||||||
"battery",
|
|
||||||
"time",
|
|
||||||
"character",
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn prompt(args: ArgMatches) {
|
|
||||||
let context = Context::new(args);
|
|
||||||
let config = &context.config;
|
|
||||||
|
|
||||||
let stdout = io::stdout();
|
|
||||||
let mut handle = stdout.lock();
|
|
||||||
|
|
||||||
// Write a new line before the prompt
|
|
||||||
if config.get_as_bool("add_newline") != Some(false) {
|
|
||||||
writeln!(handle).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut prompt_order: Vec<&str> = Vec::new();
|
|
||||||
|
|
||||||
// Write out a custom prompt order
|
|
||||||
if let Some(modules) = config.get_as_array("prompt_order") {
|
|
||||||
// if prompt_order = [] use default_prompt_order
|
|
||||||
if !modules.is_empty() {
|
|
||||||
for module in modules {
|
|
||||||
let str_value = module.as_str();
|
|
||||||
|
|
||||||
if let Some(value) = str_value {
|
|
||||||
if ALL_MODULES.contains(&value) {
|
|
||||||
prompt_order.push(value);
|
|
||||||
} else {
|
|
||||||
log::debug!(
|
|
||||||
"Expected prompt_order to contain value from {:?}. Instead received {}",
|
|
||||||
ALL_MODULES,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::debug!(
|
|
||||||
"Expected prompt_order to be an array of strings. Instead received {} of type {}",
|
|
||||||
module,
|
|
||||||
module.type_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prompt_order = DEFAULT_PROMPT_ORDER.to_vec();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prompt_order = DEFAULT_PROMPT_ORDER.to_vec();
|
|
||||||
}
|
|
||||||
|
|
||||||
let modules = &prompt_order
|
|
||||||
.par_iter()
|
|
||||||
.filter(|module| context.is_module_enabled(module))
|
|
||||||
.map(|module| modules::handle(module, &context)) // Compute modules
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<Module>>(); // Remove segments set to `None`
|
|
||||||
|
|
||||||
let mut printable = modules.iter();
|
|
||||||
|
|
||||||
// Print the first module without its prefix
|
|
||||||
if let Some(first_module) = printable.next() {
|
|
||||||
let module_without_prefix = first_module.to_string_without_prefix();
|
|
||||||
write!(handle, "{}", module_without_prefix).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print all remaining modules
|
|
||||||
printable.for_each(|module| write!(handle, "{}", module).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn module(module_name: &str, args: ArgMatches) {
|
|
||||||
let context = Context::new(args);
|
|
||||||
|
|
||||||
// If the module returns `None`, print an empty string
|
|
||||||
let module = modules::handle(module_name, &context)
|
|
||||||
.map(|m| m.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
print!("{}", module);
|
|
||||||
}
|
|
54
starship/Cargo.toml
Normal file
54
starship/Cargo.toml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
[package]
|
||||||
|
name = "starship"
|
||||||
|
version = "0.20.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Matan Kushner <hello@matchai.me>"]
|
||||||
|
homepage = "https://starship.rs"
|
||||||
|
documentation = "https://starship.rs/guide/"
|
||||||
|
repository = "https://github.com/starship/starship"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "ISC"
|
||||||
|
keywords = ["prompt", "shell", "bash", "fish", "zsh"]
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
description = """
|
||||||
|
The cross-shell prompt for astronauts. ☄🌌️
|
||||||
|
"""
|
||||||
|
exclude = ["docs/**/*"]
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
azure-devops = { project = "starship-control/starship", pipeline = "Starship Test Suite" }
|
||||||
|
is-it-maintained-issue-resolution = { repository = "starship/starship" }
|
||||||
|
is-it-maintained-open-issues = { repository = "starship/starship" }
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["battery"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = "2.33.0"
|
||||||
|
ansi_term = "0.12.1"
|
||||||
|
dirs = "2.0.2"
|
||||||
|
git2 = { version = "0.10.1", default-features = false, features = [] }
|
||||||
|
toml = "0.5.3"
|
||||||
|
serde_json = "1.0.40"
|
||||||
|
rayon = "1.2.0"
|
||||||
|
pretty_env_logger = "0.3.1"
|
||||||
|
log = "0.4.8"
|
||||||
|
# battery is optional (on by default) because the crate doesn't currently build for Termux
|
||||||
|
# see: https://github.com/svartalf/rust-battery/issues/33
|
||||||
|
battery = { version = "0.7.4", optional = true }
|
||||||
|
path-slash = "0.1.1"
|
||||||
|
unicode-segmentation = "1.3.0"
|
||||||
|
gethostname = "0.2.0"
|
||||||
|
once_cell = "1.2.0"
|
||||||
|
chrono = "0.4"
|
||||||
|
sysinfo = "0.9.5"
|
||||||
|
byte-unit = "3.0.3"
|
||||||
|
starship_module_config_derive = { version = "0.20", path = "../starship_module_config_derive" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.1.0"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "starship"
|
||||||
|
path = "src/main.rs"
|
590
starship/src/config.rs
Normal file
590
starship/src/config.rs
Normal file
|
@ -0,0 +1,590 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
use crate::configs::StarshipRootConfig;
|
||||||
|
use crate::utils;
|
||||||
|
use ansi_term::{Color, Style};
|
||||||
|
|
||||||
|
use std::clone::Clone;
|
||||||
|
use std::marker::Sized;
|
||||||
|
|
||||||
|
use dirs::home_dir;
|
||||||
|
use std::env;
|
||||||
|
use toml::Value;
|
||||||
|
|
||||||
|
/// Root config of a module.
|
||||||
|
pub trait RootModuleConfig<'a>
|
||||||
|
where
|
||||||
|
Self: ModuleConfig<'a>,
|
||||||
|
{
|
||||||
|
/// Create a new root module config with default values.
|
||||||
|
fn new() -> Self;
|
||||||
|
|
||||||
|
/// Load root module config from given Value and fill unset variables with default
|
||||||
|
/// values.
|
||||||
|
fn load(config: &'a Value) -> Self {
|
||||||
|
Self::new().load_config(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function that will call RootModuleConfig::load(config) if config is Some,
|
||||||
|
/// or RootModuleConfig::new() if config is None.
|
||||||
|
fn try_load(config: Option<&'a Value>) -> Self {
|
||||||
|
if let Some(config) = config {
|
||||||
|
Self::load(config)
|
||||||
|
} else {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsable config.
|
||||||
|
pub trait ModuleConfig<'a>
|
||||||
|
where
|
||||||
|
Self: Sized + Clone,
|
||||||
|
{
|
||||||
|
/// Construct a `ModuleConfig` from a toml value.
|
||||||
|
fn from_config(_config: &'a Value) -> Option<Self> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge `self` with config from a toml table.
|
||||||
|
fn load_config(&self, config: &'a Value) -> Self {
|
||||||
|
Self::from_config(config).unwrap_or_else(|| self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add logging to default implementations
|
||||||
|
impl<'a> ModuleConfig<'a> for &'a str {
|
||||||
|
fn from_config(config: &'a Value) -> Option<Self> {
|
||||||
|
config.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuleConfig<'a> for Style {
|
||||||
|
fn from_config(config: &Value) -> Option<Self> {
|
||||||
|
parse_style_string(config.as_str()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuleConfig<'a> for bool {
|
||||||
|
fn from_config(config: &Value) -> Option<Self> {
|
||||||
|
config.as_bool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuleConfig<'a> for i64 {
|
||||||
|
fn from_config(config: &Value) -> Option<Self> {
|
||||||
|
config.as_integer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuleConfig<'a> for f64 {
|
||||||
|
fn from_config(config: &Value) -> Option<Self> {
|
||||||
|
config.as_float()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> ModuleConfig<'a> for Vec<T>
|
||||||
|
where
|
||||||
|
T: ModuleConfig<'a>,
|
||||||
|
{
|
||||||
|
fn from_config(config: &'a Value) -> Option<Self> {
|
||||||
|
config
|
||||||
|
.as_array()?
|
||||||
|
.iter()
|
||||||
|
.map(|value| T::from_config(value))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> ModuleConfig<'a> for Option<T>
|
||||||
|
where
|
||||||
|
T: ModuleConfig<'a> + Sized,
|
||||||
|
{
|
||||||
|
fn from_config(config: &'a Value) -> Option<Self> {
|
||||||
|
Some(T::from_config(config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Root config of starship.
|
||||||
|
pub struct StarshipConfig {
|
||||||
|
pub config: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StarshipConfig {
|
||||||
|
/// Initialize the Config struct
|
||||||
|
pub fn initialize() -> Self {
|
||||||
|
if let Some(file_data) = Self::config_from_file() {
|
||||||
|
StarshipConfig {
|
||||||
|
config: Some(file_data),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StarshipConfig {
|
||||||
|
config: Some(Value::Table(toml::value::Table::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a config from a starship configuration file
|
||||||
|
fn config_from_file() -> Option<Value> {
|
||||||
|
let file_path = if let Ok(path) = env::var("STARSHIP_CONFIG") {
|
||||||
|
// Use $STARSHIP_CONFIG as the config path if available
|
||||||
|
log::debug!("STARSHIP_CONFIG is set: \n{}", &path);
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
// Default to using ~/.config/starship.toml
|
||||||
|
log::debug!("STARSHIP_CONFIG is not set");
|
||||||
|
let config_path = home_dir()?.join(".config/starship.toml");
|
||||||
|
let config_path_str = config_path.to_str()?.to_owned();
|
||||||
|
log::debug!("Using default config path: {}", config_path_str);
|
||||||
|
config_path_str
|
||||||
|
};
|
||||||
|
|
||||||
|
let toml_content = match utils::read_file(&file_path) {
|
||||||
|
Ok(content) => {
|
||||||
|
log::trace!("Config file content: \n{}", &content);
|
||||||
|
Some(content)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("Unable to read config file content: \n{}", &e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let config = toml::from_str(&toml_content).ok()?;
|
||||||
|
log::debug!("Config parsed: \n{:?}", &config);
|
||||||
|
Some(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_root_config(&self) -> StarshipRootConfig {
|
||||||
|
if let Some(root_config) = &self.config {
|
||||||
|
StarshipRootConfig::load(root_config)
|
||||||
|
} else {
|
||||||
|
StarshipRootConfig::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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> {
|
||||||
|
/// Mutably set value
|
||||||
|
pub fn set_value(&mut self, value: &'a str) {
|
||||||
|
self.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutably set style
|
||||||
|
pub fn set_style(&mut self, style: Style) {
|
||||||
|
self.style = Some(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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: Style) -> Self {
|
||||||
|
Self {
|
||||||
|
value: self.value,
|
||||||
|
style: Some(style),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
- 'bg:<color>' (specifies that the color read should be a background color)
|
||||||
|
- 'underline'
|
||||||
|
- 'bold'
|
||||||
|
- 'italic'
|
||||||
|
- '<color>' (see the parse_color_string doc for valid color strings)
|
||||||
|
*/
|
||||||
|
fn parse_style_string(style_string: &str) -> Option<ansi_term::Style> {
|
||||||
|
style_string
|
||||||
|
.split_whitespace()
|
||||||
|
.fold(Some(ansi_term::Style::new()), |maybe_style, token| {
|
||||||
|
maybe_style.and_then(|style| {
|
||||||
|
let token = token.to_lowercase();
|
||||||
|
|
||||||
|
// Check for FG/BG identifiers and strip them off if appropriate
|
||||||
|
// If col_fg is true, color the foreground. If it's false, color the background.
|
||||||
|
let (token, col_fg) = if token.as_str().starts_with("fg:") {
|
||||||
|
(token.trim_start_matches("fg:").to_owned(), true)
|
||||||
|
} else if token.as_str().starts_with("bg:") {
|
||||||
|
(token.trim_start_matches("bg:").to_owned(), false)
|
||||||
|
} else {
|
||||||
|
(token, true) // Bare colors are assumed to color the foreground
|
||||||
|
};
|
||||||
|
|
||||||
|
match token.as_str() {
|
||||||
|
"underline" => Some(style.underline()),
|
||||||
|
"bold" => Some(style.bold()),
|
||||||
|
"italic" => Some(style.italic()),
|
||||||
|
"dimmed" => Some(style.dimmed()),
|
||||||
|
"none" => None,
|
||||||
|
|
||||||
|
// Try to see if this token parses as a valid color string
|
||||||
|
color_string => parse_color_string(color_string).map(|ansi_color| {
|
||||||
|
if col_fg {
|
||||||
|
style.fg(ansi_color)
|
||||||
|
} else {
|
||||||
|
style.on(ansi_color)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse a string that represents a color setting, returning None if this fails
|
||||||
|
There are three valid color formats:
|
||||||
|
- #RRGGBB (a hash followed by an RGB hex)
|
||||||
|
- u8 (a number from 0-255, representing an ANSI color)
|
||||||
|
- colstring (one of the 16 predefined color strings)
|
||||||
|
*/
|
||||||
|
fn parse_color_string(color_string: &str) -> Option<ansi_term::Color> {
|
||||||
|
// Parse RGB hex values
|
||||||
|
log::trace!("Parsing color_string: {}", color_string);
|
||||||
|
if color_string.starts_with('#') {
|
||||||
|
log::trace!(
|
||||||
|
"Attempting to read hexadecimal color string: {}",
|
||||||
|
color_string
|
||||||
|
);
|
||||||
|
let r: u8 = u8::from_str_radix(&color_string[1..3], 16).ok()?;
|
||||||
|
let g: u8 = u8::from_str_radix(&color_string[3..5], 16).ok()?;
|
||||||
|
let b: u8 = u8::from_str_radix(&color_string[5..7], 16).ok()?;
|
||||||
|
log::trace!("Read RGB color string: {},{},{}", r, g, b);
|
||||||
|
return Some(Color::RGB(r, g, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a u8 (ansi color)
|
||||||
|
if let Result::Ok(ansi_color_num) = color_string.parse::<u8>() {
|
||||||
|
log::trace!("Read ANSI color string: {}", ansi_color_num);
|
||||||
|
return Some(Color::Fixed(ansi_color_num));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any predefined color strings
|
||||||
|
// There are no predefined enums for bright colors, so we use Color::Fixed
|
||||||
|
let predefined_color = match color_string.to_lowercase().as_str() {
|
||||||
|
"black" => Some(Color::Black),
|
||||||
|
"red" => Some(Color::Red),
|
||||||
|
"green" => Some(Color::Green),
|
||||||
|
"yellow" => Some(Color::Yellow),
|
||||||
|
"blue" => Some(Color::Blue),
|
||||||
|
"purple" => Some(Color::Purple),
|
||||||
|
"cyan" => Some(Color::Cyan),
|
||||||
|
"white" => Some(Color::White),
|
||||||
|
"bright-black" => Some(Color::Fixed(8)), // "bright-black" is dark grey
|
||||||
|
"bright-red" => Some(Color::Fixed(9)),
|
||||||
|
"bright-green" => Some(Color::Fixed(10)),
|
||||||
|
"bright-yellow" => Some(Color::Fixed(11)),
|
||||||
|
"bright-blue" => Some(Color::Fixed(12)),
|
||||||
|
"bright-purple" => Some(Color::Fixed(13)),
|
||||||
|
"bright-cyan" => Some(Color::Fixed(14)),
|
||||||
|
"bright-white" => Some(Color::Fixed(15)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if predefined_color.is_some() {
|
||||||
|
log::trace!("Read predefined color: {}", color_string);
|
||||||
|
} else {
|
||||||
|
log::debug!("Could not parse color in string: {}", color_string);
|
||||||
|
}
|
||||||
|
predefined_color
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use starship_module_config_derive::ModuleConfig;
|
||||||
|
use toml;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_config() {
|
||||||
|
#[derive(Clone, ModuleConfig)]
|
||||||
|
struct TestConfig<'a> {
|
||||||
|
pub symbol: &'a str,
|
||||||
|
pub disabled: bool,
|
||||||
|
pub some_array: Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = toml::toml! {
|
||||||
|
symbol = "T "
|
||||||
|
disabled = true
|
||||||
|
some_array = ["A"]
|
||||||
|
};
|
||||||
|
let default_config = TestConfig {
|
||||||
|
symbol: "S ",
|
||||||
|
disabled: false,
|
||||||
|
some_array: vec!["A", "B", "C"],
|
||||||
|
};
|
||||||
|
let rust_config = default_config.load_config(&config);
|
||||||
|
|
||||||
|
assert_eq!(rust_config.symbol, "T ");
|
||||||
|
assert_eq!(rust_config.disabled, true);
|
||||||
|
assert_eq!(rust_config.some_array, vec!["A"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_nested_config() {
|
||||||
|
#[derive(Clone, ModuleConfig)]
|
||||||
|
struct TestConfig<'a> {
|
||||||
|
pub untracked: SegmentDisplayConfig<'a>,
|
||||||
|
pub modified: SegmentDisplayConfig<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone, ModuleConfig)]
|
||||||
|
struct SegmentDisplayConfig<'a> {
|
||||||
|
pub value: &'a str,
|
||||||
|
pub style: Style,
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = toml::toml! {
|
||||||
|
untracked.value = "x"
|
||||||
|
modified = { value = "•", style = "red" }
|
||||||
|
};
|
||||||
|
|
||||||
|
let default_config = TestConfig {
|
||||||
|
untracked: SegmentDisplayConfig {
|
||||||
|
value: "?",
|
||||||
|
style: Color::Red.bold(),
|
||||||
|
},
|
||||||
|
modified: SegmentDisplayConfig {
|
||||||
|
value: "!",
|
||||||
|
style: Color::Red.bold(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let git_status_config = default_config.load_config(&config);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
git_status_config.untracked,
|
||||||
|
SegmentDisplayConfig {
|
||||||
|
value: "x",
|
||||||
|
style: Color::Red.bold(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
git_status_config.modified,
|
||||||
|
SegmentDisplayConfig {
|
||||||
|
value: "•",
|
||||||
|
style: Color::Red.normal(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_optional_config() {
|
||||||
|
#[derive(Clone, ModuleConfig)]
|
||||||
|
struct TestConfig<'a> {
|
||||||
|
pub optional: Option<&'a str>,
|
||||||
|
pub hidden: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = toml::toml! {
|
||||||
|
optional = "test"
|
||||||
|
};
|
||||||
|
let default_config = TestConfig {
|
||||||
|
optional: None,
|
||||||
|
hidden: None,
|
||||||
|
};
|
||||||
|
let rust_config = default_config.load_config(&config);
|
||||||
|
|
||||||
|
assert_eq!(rust_config.optional, Some("test"));
|
||||||
|
assert_eq!(rust_config.hidden, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_enum_config() {
|
||||||
|
#[derive(Clone, ModuleConfig)]
|
||||||
|
struct TestConfig {
|
||||||
|
pub switch_a: Switch,
|
||||||
|
pub switch_b: Switch,
|
||||||
|
pub switch_c: Switch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
enum Switch {
|
||||||
|
ON,
|
||||||
|
OFF,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuleConfig<'a> for Switch {
|
||||||
|
fn from_config(config: &'a Value) -> Option<Self> {
|
||||||
|
match config.as_str()? {
|
||||||
|
"on" => Some(Self::ON),
|
||||||
|
"off" => Some(Self::OFF),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = toml::toml! {
|
||||||
|
switch_a = "on"
|
||||||
|
switch_b = "any"
|
||||||
|
};
|
||||||
|
let default_config = TestConfig {
|
||||||
|
switch_a: Switch::OFF,
|
||||||
|
switch_b: Switch::OFF,
|
||||||
|
switch_c: Switch::OFF,
|
||||||
|
};
|
||||||
|
let rust_config = default_config.load_config(&config);
|
||||||
|
|
||||||
|
assert_eq!(rust_config.switch_a, Switch::ON);
|
||||||
|
assert_eq!(rust_config.switch_b, Switch::OFF);
|
||||||
|
assert_eq!(rust_config.switch_c, Switch::OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_string() {
|
||||||
|
let config = Value::String(String::from("S"));
|
||||||
|
assert_eq!(<&str>::from_config(&config).unwrap(), "S");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_bool() {
|
||||||
|
let config = Value::Boolean(true);
|
||||||
|
assert_eq!(<bool>::from_config(&config).unwrap(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_i64() {
|
||||||
|
let config = Value::Integer(42);
|
||||||
|
assert_eq!(<i64>::from_config(&config).unwrap(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_style() {
|
||||||
|
let config = Value::from("red bold");
|
||||||
|
assert_eq!(<Style>::from_config(&config).unwrap(), Color::Red.bold());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_vec() {
|
||||||
|
let config: Value = Value::Array(vec![Value::from("S")]);
|
||||||
|
assert_eq!(<Vec<&str>>::from_config(&config).unwrap(), vec!["S"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_option() {
|
||||||
|
let config: Value = Value::String(String::from("S"));
|
||||||
|
assert_eq!(<Option<&str>>::from_config(&config).unwrap(), Some("S"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_get_styles_bold_italic_underline_green_dimmy_silly_caps() {
|
||||||
|
let config = Value::from("bOlD ItAlIc uNdErLiNe GrEeN diMMeD");
|
||||||
|
let mystyle = <Style>::from_config(&config).unwrap();
|
||||||
|
assert!(mystyle.is_bold);
|
||||||
|
assert!(mystyle.is_italic);
|
||||||
|
assert!(mystyle.is_underline);
|
||||||
|
assert!(mystyle.is_dimmed);
|
||||||
|
assert_eq!(
|
||||||
|
mystyle,
|
||||||
|
ansi_term::Style::new()
|
||||||
|
.bold()
|
||||||
|
.italic()
|
||||||
|
.underline()
|
||||||
|
.dimmed()
|
||||||
|
.fg(Color::Green)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_get_styles_plain_and_broken_styles() {
|
||||||
|
// Test a "plain" style with no formatting
|
||||||
|
let config = Value::from("");
|
||||||
|
let plain_style = <Style>::from_config(&config).unwrap();
|
||||||
|
assert_eq!(plain_style, ansi_term::Style::new());
|
||||||
|
|
||||||
|
// Test a string that's clearly broken
|
||||||
|
let config = Value::from("djklgfhjkldhlhk;j");
|
||||||
|
assert!(<Style>::from_config(&config).is_none());
|
||||||
|
|
||||||
|
// Test a string that's nullified by `none`
|
||||||
|
let config = Value::from("fg:red bg:green bold none");
|
||||||
|
assert!(<Style>::from_config(&config).is_none());
|
||||||
|
|
||||||
|
// Test a string that's nullified by `none` at the start
|
||||||
|
let config = Value::from("none fg:red bg:green bold");
|
||||||
|
assert!(<Style>::from_config(&config).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_get_styles_ordered() {
|
||||||
|
// Test a background style with inverted order (also test hex + ANSI)
|
||||||
|
let config = Value::from("bg:#050505 underline fg:120");
|
||||||
|
let flipped_style = <Style>::from_config(&config).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
flipped_style,
|
||||||
|
Style::new()
|
||||||
|
.underline()
|
||||||
|
.fg(Color::Fixed(120))
|
||||||
|
.on(Color::RGB(5, 5, 5))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that the last color style is always the one used
|
||||||
|
let config = Value::from("bg:120 bg:125 bg:127 fg:127 122 125");
|
||||||
|
let multi_style = <Style>::from_config(&config).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
multi_style,
|
||||||
|
Style::new().fg(Color::Fixed(125)).on(Color::Fixed(127))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
38
starship/src/configs/battery.rs
Normal file
38
starship/src/configs/battery.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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: &'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>,
|
||||||
|
pub disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RootModuleConfig<'a> for BatteryConfig<'a> {
|
||||||
|
fn new() -> Self {
|
||||||
|
BatteryConfig {
|
||||||
|
full_symbol: "•",
|
||||||
|
charging_symbol: "↑",
|
||||||
|
discharging_symbol: "↓",
|
||||||
|
unknown_symbol: None,
|
||||||
|
empty_symbol: None,
|
||||||
|
display: vec![BatteryDisplayConfig {
|
||||||
|
threshold: 10,
|
||||||
|
style: Color::Red.bold(),
|
||||||
|
}],
|
||||||
|
disabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, ModuleConfig)]
|
||||||
|
pub struct BatteryDisplayConfig {
|
||||||
|
pub threshold: i64,
|
||||||
|
pub style: Style,
|
||||||
|
}
|
49
starship/src/configs/mod.rs
Normal file
49
starship/src/configs/mod.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
pub mod battery;
|
||||||
|
pub mod rust;
|
||||||
|
|
||||||
|
use crate::config::{ModuleConfig, RootModuleConfig};
|
||||||
|
|
||||||
|
use starship_module_config_derive::ModuleConfig;
|
||||||
|
|
||||||
|
#[derive(Clone, ModuleConfig)]
|
||||||
|
pub struct StarshipRootConfig<'a> {
|
||||||
|
pub add_newline: bool,
|
||||||
|
pub prompt_order: Vec<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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![
|
||||||
|
"username",
|
||||||
|
"hostname",
|
||||||
|
"directory",
|
||||||
|
"git_branch",
|
||||||
|
"git_state",
|
||||||
|
"git_status",
|
||||||
|
"package",
|
||||||
|
"nodejs",
|
||||||
|
"ruby",
|
||||||
|
"rust",
|
||||||
|
"python",
|
||||||
|
"golang",
|
||||||
|
"java",
|
||||||
|
"nix_shell",
|
||||||
|
"memory_usage",
|
||||||
|
"aws",
|
||||||
|
"env_var",
|
||||||
|
"cmd_duration",
|
||||||
|
"line_break",
|
||||||
|
"jobs",
|
||||||
|
#[cfg(feature = "battery")]
|
||||||
|
"battery",
|
||||||
|
"time",
|
||||||
|
"character",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
starship/src/configs/rust.rs
Normal file
57
starship/src/configs/rust.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::config::{ModuleConfig, RootModuleConfig, SegmentConfig};
|
||||||
|
|
||||||
|
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 disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is what the macro adds.
|
||||||
|
impl<'a> ModuleConfig<'a> for RustConfig<'a> {
|
||||||
|
fn load_config(&self, config: &'a toml::Value) -> Self {
|
||||||
|
let mut new_module_config = self.clone();
|
||||||
|
if let toml::Value::Table(config) = config {
|
||||||
|
if let Some(config_str) = config.get("symbol") {
|
||||||
|
new_module_config.symbol = new_module_config.symbol.load_config(config_str);
|
||||||
|
}
|
||||||
|
if let Some(config_str) = config.get("disabled") {
|
||||||
|
new_module_config.disabled = new_module_config.disabled.load_config(config_str);
|
||||||
|
}
|
||||||
|
if let Some(config_str) = config.get("style") {
|
||||||
|
new_module_config.style = new_module_config.style.load_config(config_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_module_config
|
||||||
|
}
|
||||||
|
fn from_config(config: &'a toml::Value) -> Option<Self> {
|
||||||
|
let config = config.as_table()?;
|
||||||
|
Some(RustConfig {
|
||||||
|
symbol: <&'a str>::from_config(config.get("symbol")?)?,
|
||||||
|
style: <Style>::from_config(config.get("style")?)?,
|
||||||
|
disabled: <bool>::from_config(config.get("disabled")?)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
impl<'a> RootModuleConfig<'a> for RustConfig<'a> {
|
||||||
|
fn new() -> Self {
|
||||||
|
RustConfig {
|
||||||
|
symbol: SegmentConfig {
|
||||||
|
value: "🦀 ",
|
||||||
|
style: None,
|
||||||
|
},
|
||||||
|
version: SegmentConfig {
|
||||||
|
value: "",
|
||||||
|
style: None,
|
||||||
|
},
|
||||||
|
style: Color::Red.bold(),
|
||||||
|
disabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::config::Config;
|
use crate::config::StarshipConfig;
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
@ -14,7 +14,7 @@ use std::path::{Path, PathBuf};
|
||||||
/// of the prompt.
|
/// of the prompt.
|
||||||
pub struct Context<'a> {
|
pub struct Context<'a> {
|
||||||
/// The deserialized configuration map from the user's `starship.toml` file.
|
/// The deserialized configuration map from the user's `starship.toml` file.
|
||||||
pub config: toml::value::Table,
|
pub config: StarshipConfig,
|
||||||
|
|
||||||
/// The current working directory that starship is being called in.
|
/// The current working directory that starship is being called in.
|
||||||
pub current_dir: PathBuf,
|
pub current_dir: PathBuf,
|
||||||
|
@ -47,7 +47,7 @@ impl<'a> Context<'a> {
|
||||||
where
|
where
|
||||||
T: Into<PathBuf>,
|
T: Into<PathBuf>,
|
||||||
{
|
{
|
||||||
let config = toml::value::Table::initialize();
|
let config = StarshipConfig::initialize();
|
||||||
|
|
||||||
// TODO: Currently gets the physical directory. Get the logical directory.
|
// TODO: Currently gets the physical directory. Get the logical directory.
|
||||||
let current_dir = Context::expand_tilde(dir.into());
|
let current_dir = Context::expand_tilde(dir.into());
|
||||||
|
@ -82,7 +82,7 @@ impl<'a> Context<'a> {
|
||||||
let config = self.config.get_module_config(name);
|
let config = self.config.get_module_config(name);
|
||||||
|
|
||||||
// If the segment has "disabled" set to "true", don't show it
|
// If the segment has "disabled" set to "true", don't show it
|
||||||
let disabled = config.and_then(|table| table.get_as_bool("disabled"));
|
let disabled = config.and_then(|table| table.as_table()?.get("disabled")?.as_bool());
|
||||||
|
|
||||||
disabled != Some(true)
|
disabled != Some(true)
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
// Lib is present to allow for benchmarking
|
// Lib is present to allow for benchmarking
|
||||||
mod config;
|
pub mod config;
|
||||||
|
pub mod configs;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
pub mod modules;
|
pub mod modules;
|
|
@ -2,6 +2,7 @@
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod configs;
|
||||||
mod context;
|
mod context;
|
||||||
mod init;
|
mod init;
|
||||||
mod module;
|
mod module;
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::config::Config;
|
use crate::config::{ModuleConfig, SegmentConfig};
|
||||||
use crate::config::SegmentConfig;
|
|
||||||
use crate::segment::Segment;
|
use crate::segment::Segment;
|
||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
use ansi_term::{ANSIString, ANSIStrings};
|
use ansi_term::{ANSIString, ANSIStrings};
|
||||||
|
@ -37,7 +36,7 @@ pub const ALL_MODULES: &[&str] = &[
|
||||||
/// (e.g. The git module shows the current git branch and status)
|
/// (e.g. The git module shows the current git branch and status)
|
||||||
pub struct Module<'a> {
|
pub struct Module<'a> {
|
||||||
/// The module's configuration map if available
|
/// The module's configuration map if available
|
||||||
config: Option<&'a toml::value::Table>,
|
pub config: Option<&'a toml::Value>,
|
||||||
|
|
||||||
/// The module's name, to be used in configuration and logging.
|
/// The module's name, to be used in configuration and logging.
|
||||||
_name: String,
|
_name: String,
|
||||||
|
@ -57,7 +56,7 @@ pub struct Module<'a> {
|
||||||
|
|
||||||
impl<'a> Module<'a> {
|
impl<'a> Module<'a> {
|
||||||
/// Creates a module with no segments.
|
/// Creates a module with no segments.
|
||||||
pub fn new(name: &str, config: Option<&'a toml::value::Table>) -> Module<'a> {
|
pub fn new(name: &str, config: Option<&'a toml::Value>) -> Module<'a> {
|
||||||
Module {
|
Module {
|
||||||
config,
|
config,
|
||||||
_name: name.to_string(),
|
_name: name.to_string(),
|
||||||
|
@ -69,22 +68,42 @@ impl<'a> Module<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to a newly created segment in the module
|
/// Get a reference to a newly created segment in the module
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.20.0",
|
||||||
|
note = "please use `module.create_segment()` instead"
|
||||||
|
)]
|
||||||
pub fn new_segment(&mut self, name: &str, value: &str) -> &mut Segment {
|
pub fn new_segment(&mut self, name: &str, value: &str) -> &mut Segment {
|
||||||
let mut segment = Segment::new(name);
|
let mut segment = Segment::new(name);
|
||||||
if let Some(segment_config) = self.config_value_segment_config(name) {
|
let segment_config_mock = SegmentConfig { value, style: None };
|
||||||
|
|
||||||
|
if let Some(module_config) = self.config {
|
||||||
|
let segment_config = segment_config_mock.load_config(&module_config);
|
||||||
segment.set_style(segment_config.style.unwrap_or(self.style));
|
segment.set_style(segment_config.style.unwrap_or(self.style));
|
||||||
segment.set_value(segment_config.value.unwrap_or(value));
|
segment.set_value(segment_config.value);
|
||||||
} else {
|
} else {
|
||||||
segment.set_style(self.style);
|
segment.set_style(segment_config_mock.style.unwrap_or(self.style));
|
||||||
// Use the provided value unless overwritten by config
|
segment.set_value(segment_config_mock.value);
|
||||||
segment.set_value(self.config_value_str(name).unwrap_or(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.segments.push(segment);
|
||||||
|
self.segments.last_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.push(segment);
|
||||||
|
|
||||||
self.segments.last_mut().unwrap()
|
self.segments.last_mut().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should config exists, get a reference to a newly created segment in the module
|
/// Should config exists, get a reference to a newly created segment in the module
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.20.0",
|
||||||
|
note = "please use `module.create_segment()` instead"
|
||||||
|
)]
|
||||||
pub fn new_segment_if_config_exists(&mut self, name: &str) -> Option<&mut Segment> {
|
pub fn new_segment_if_config_exists(&mut self, name: &str) -> Option<&mut Segment> {
|
||||||
// Use the provided value unless overwritten by config
|
// Use the provided value unless overwritten by config
|
||||||
if let Some(value) = self.config_value_str(name) {
|
if let Some(value) = self.config_value_str(name) {
|
||||||
|
@ -151,34 +170,48 @@ impl<'a> Module<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a module's config value as a string
|
/// Get a module's config value as a string
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.20.0",
|
||||||
|
note = "please use <RootModuleConfig>::try_load(module.config) instead"
|
||||||
|
)]
|
||||||
pub fn config_value_str(&self, key: &str) -> Option<&str> {
|
pub fn config_value_str(&self, key: &str) -> Option<&str> {
|
||||||
self.config.and_then(|config| config.get_as_str(key))
|
<&str>::from_config(self.config?.as_table()?.get(key)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a module's config value as an int
|
/// Get a module's config value as an int
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.20.0",
|
||||||
|
note = "please use <RootModuleConfig>::try_load(module.config) instead"
|
||||||
|
)]
|
||||||
pub fn config_value_i64(&self, key: &str) -> Option<i64> {
|
pub fn config_value_i64(&self, key: &str) -> Option<i64> {
|
||||||
self.config.and_then(|config| config.get_as_i64(key))
|
<i64>::from_config(self.config?.as_table()?.get(key)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a module's config value as a bool
|
/// Get a module's config value as a bool
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.20.0",
|
||||||
|
note = "please use <RootModuleConfig>::try_load(module.config) instead"
|
||||||
|
)]
|
||||||
pub fn config_value_bool(&self, key: &str) -> Option<bool> {
|
pub fn config_value_bool(&self, key: &str) -> Option<bool> {
|
||||||
self.config.and_then(|config| config.get_as_bool(key))
|
<bool>::from_config(self.config?.as_table()?.get(key)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a module's config value as a style
|
/// Get a module's config value as a style
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.20.0",
|
||||||
|
note = "please use <RootModuleConfig>::try_load(module.config) instead"
|
||||||
|
)]
|
||||||
pub fn config_value_style(&self, key: &str) -> Option<Style> {
|
pub fn config_value_style(&self, key: &str) -> Option<Style> {
|
||||||
self.config.and_then(|config| config.get_as_ansi_style(key))
|
<Style>::from_config(self.config?.as_table()?.get(key)?)
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a module's config value as an array
|
|
||||||
pub fn config_value_array(&self, key: &str) -> Option<&Vec<toml::Value>> {
|
|
||||||
self.config.and_then(|config| config.get_as_array(key))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a module's config value as a table of segment config
|
/// Get a module's config value as a table of segment config
|
||||||
|
#[deprecated(
|
||||||
|
since = "0.20.0",
|
||||||
|
note = "please use <RootModuleConfig>::try_load(module.config) instead"
|
||||||
|
)]
|
||||||
pub fn config_value_segment_config(&self, key: &str) -> Option<SegmentConfig> {
|
pub fn config_value_segment_config(&self, key: &str) -> Option<SegmentConfig> {
|
||||||
self.config
|
<SegmentConfig>::from_config(self.config?.as_table()?.get(key)?)
|
||||||
.and_then(|config| config.get_as_segment_config(key))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
use ansi_term::{Color, Style};
|
|
||||||
|
|
||||||
use super::{Context, Module};
|
use super::{Context, Module};
|
||||||
use crate::config::Config;
|
use crate::config::RootModuleConfig;
|
||||||
|
use crate::configs::battery::BatteryConfig;
|
||||||
|
|
||||||
/// Creates a module for the battery percentage and charging state
|
/// Creates a module for the battery percentage and charging state
|
||||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||||
const BATTERY_FULL: &str = "•";
|
|
||||||
const BATTERY_CHARGING: &str = "⇡";
|
|
||||||
const BATTERY_DISCHARGING: &str = "⇣";
|
|
||||||
// TODO: Update when v1.0 printing refactor is implemented to only
|
// TODO: Update when v1.0 printing refactor is implemented to only
|
||||||
// print escapes in a prompt context.
|
// print escapes in a prompt context.
|
||||||
let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default();
|
let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default();
|
||||||
|
@ -20,37 +16,39 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||||
let BatteryStatus { state, percentage } = battery_status;
|
let BatteryStatus { state, percentage } = battery_status;
|
||||||
|
|
||||||
let mut module = context.new_module("battery");
|
let mut module = context.new_module("battery");
|
||||||
|
let battery_config = BatteryConfig::try_load(module.config);
|
||||||
|
|
||||||
// Parse config under `display`
|
// Parse config under `display`
|
||||||
let display_styles = get_display_styles(&module);
|
let display_styles = &battery_config.display;
|
||||||
let display_style = display_styles.iter().find(|display_style| {
|
let display_style = display_styles
|
||||||
let BatteryDisplayStyle { threshold, .. } = display_style;
|
.iter()
|
||||||
percentage <= *threshold as f32
|
.find(|display_style| percentage <= display_style.threshold as f32);
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(display_style) = display_style {
|
if let Some(display_style) = display_style {
|
||||||
let BatteryDisplayStyle { style, .. } = display_style;
|
|
||||||
|
|
||||||
// Set style based on percentage
|
// Set style based on percentage
|
||||||
module.set_style(*style);
|
module.set_style(display_style.style);
|
||||||
module.get_prefix().set_value("");
|
module.get_prefix().set_value("");
|
||||||
|
|
||||||
match state {
|
match state {
|
||||||
battery::State::Full => {
|
battery::State::Full => {
|
||||||
module.new_segment("full_symbol", BATTERY_FULL);
|
module.new_segment("full_symbol", battery_config.full_symbol);
|
||||||
}
|
}
|
||||||
battery::State::Charging => {
|
battery::State::Charging => {
|
||||||
module.new_segment("charging_symbol", BATTERY_CHARGING);
|
module.new_segment("charging_symbol", battery_config.charging_symbol);
|
||||||
}
|
}
|
||||||
battery::State::Discharging => {
|
battery::State::Discharging => {
|
||||||
module.new_segment("discharging_symbol", BATTERY_DISCHARGING);
|
module.new_segment("discharging_symbol", battery_config.discharging_symbol);
|
||||||
}
|
}
|
||||||
battery::State::Unknown => {
|
battery::State::Unknown => {
|
||||||
log::debug!("Unknown detected");
|
log::debug!("Unknown detected");
|
||||||
module.new_segment_if_config_exists("unknown_symbol")?;
|
if let Some(unknown_symbol) = battery_config.unknown_symbol {
|
||||||
|
module.new_segment("unknown_symbol", unknown_symbol);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
battery::State::Empty => {
|
battery::State::Empty => {
|
||||||
module.new_segment_if_config_exists("empty_symbol")?;
|
if let Some(empty_symbol) = battery_config.empty_symbol {
|
||||||
|
module.new_segment("empty_symbol", empty_symbol);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
log::debug!("Unhandled battery state `{}`", state);
|
log::debug!("Unhandled battery state `{}`", state);
|
||||||
|
@ -70,28 +68,6 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_display_styles(module: &Module) -> Vec<BatteryDisplayStyle> {
|
|
||||||
if let Some(display_configs) = module.config_value_array("display") {
|
|
||||||
let mut display_styles: Vec<BatteryDisplayStyle> = vec![];
|
|
||||||
for display_config in display_configs.iter() {
|
|
||||||
if let toml::Value::Table(config) = display_config {
|
|
||||||
if let Some(display_style) = BatteryDisplayStyle::from_config(config) {
|
|
||||||
display_styles.push(display_style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return display styles as long as display array exists, even if it is empty.
|
|
||||||
display_styles
|
|
||||||
} else {
|
|
||||||
// Default display styles: [{ threshold = 10, style = "red bold" }]
|
|
||||||
vec![BatteryDisplayStyle {
|
|
||||||
threshold: 10,
|
|
||||||
style: Color::Red.bold(),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_battery_status() -> Option<BatteryStatus> {
|
fn get_battery_status() -> Option<BatteryStatus> {
|
||||||
let battery_manager = battery::Manager::new().ok()?;
|
let battery_manager = battery::Manager::new().ok()?;
|
||||||
match battery_manager.batteries().ok()?.next() {
|
match battery_manager.batteries().ok()?.next() {
|
||||||
|
@ -119,19 +95,3 @@ struct BatteryStatus {
|
||||||
percentage: f32,
|
percentage: f32,
|
||||||
state: battery::State,
|
state: battery::State,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct BatteryDisplayStyle {
|
|
||||||
threshold: i64,
|
|
||||||
style: Style,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BatteryDisplayStyle {
|
|
||||||
/// construct battery display style from toml table
|
|
||||||
pub fn from_config(config: &toml::value::Table) -> Option<BatteryDisplayStyle> {
|
|
||||||
let threshold = config.get_as_i64("threshold")?;
|
|
||||||
let style = config.get_as_ansi_style("style")?;
|
|
||||||
|
|
||||||
Some(BatteryDisplayStyle { threshold, style })
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
use ansi_term::Color;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Command, Output};
|
use std::process::{Command, Output};
|
||||||
|
@ -6,14 +5,15 @@ use std::{env, fs};
|
||||||
|
|
||||||
use super::{Context, Module};
|
use super::{Context, Module};
|
||||||
|
|
||||||
|
use crate::config::RootModuleConfig;
|
||||||
|
use crate::configs::rust::RustConfig;
|
||||||
|
|
||||||
/// Creates a module with the current Rust version
|
/// Creates a module with the current Rust version
|
||||||
///
|
///
|
||||||
/// Will display the Rust version if any of the following criteria are met:
|
/// Will display the Rust version if any of the following criteria are met:
|
||||||
/// - Current directory contains a file with a `.rs` extension
|
/// - Current directory contains a file with a `.rs` extension
|
||||||
/// - Current directory contains a `Cargo.toml` file
|
/// - Current directory contains a `Cargo.toml` file
|
||||||
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||||
const RUST_CHAR: &str = "🦀 ";
|
|
||||||
|
|
||||||
let is_rs_project = context
|
let is_rs_project = context
|
||||||
.try_begin_scan()?
|
.try_begin_scan()?
|
||||||
.set_files(&["Cargo.toml"])
|
.set_files(&["Cargo.toml"])
|
||||||
|
@ -59,13 +59,11 @@ pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut module = context.new_module("rust");
|
let mut module = context.new_module("rust");
|
||||||
|
let config = RustConfig::try_load(module.config);
|
||||||
|
module.set_style(config.style);
|
||||||
|
|
||||||
let module_style = module
|
module.create_segment("symbol", &config.symbol);
|
||||||
.config_value_style("style")
|
module.create_segment("version", &config.version.with_value(&module_version));
|
||||||
.unwrap_or_else(|| Color::Red.bold());
|
|
||||||
module.set_style(module_style);
|
|
||||||
module.new_segment("symbol", RUST_CHAR);
|
|
||||||
module.new_segment("version", &module_version);
|
|
||||||
|
|
||||||
Some(module)
|
Some(module)
|
||||||
}
|
}
|
65
starship/src/print.rs
Normal file
65
starship/src/print.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use clap::ArgMatches;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::module::Module;
|
||||||
|
use crate::module::ALL_MODULES;
|
||||||
|
use crate::modules;
|
||||||
|
|
||||||
|
pub fn prompt(args: ArgMatches) {
|
||||||
|
let context = Context::new(args);
|
||||||
|
let config = context.config.get_root_config();
|
||||||
|
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let mut handle = stdout.lock();
|
||||||
|
|
||||||
|
// Write a new line before the prompt
|
||||||
|
if config.add_newline {
|
||||||
|
writeln!(handle).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prompt_order: Vec<&str> = Vec::new();
|
||||||
|
|
||||||
|
// Write out a custom prompt order
|
||||||
|
for module in config.prompt_order {
|
||||||
|
if ALL_MODULES.contains(&module) {
|
||||||
|
prompt_order.push(module);
|
||||||
|
} else {
|
||||||
|
log::debug!(
|
||||||
|
"Expected prompt_order to contain value from {:?}. Instead received {}",
|
||||||
|
ALL_MODULES,
|
||||||
|
module,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let modules = &prompt_order
|
||||||
|
.par_iter()
|
||||||
|
.filter(|module| context.is_module_enabled(module))
|
||||||
|
.map(|module| modules::handle(module, &context)) // Compute modules
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<Module>>(); // Remove segments set to `None`
|
||||||
|
|
||||||
|
let mut printable = modules.iter();
|
||||||
|
|
||||||
|
// Print the first module without its prefix
|
||||||
|
if let Some(first_module) = printable.next() {
|
||||||
|
let module_without_prefix = first_module.to_string_without_prefix();
|
||||||
|
write!(handle, "{}", module_without_prefix).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print all remaining modules
|
||||||
|
printable.for_each(|module| write!(handle, "{}", module).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn module(module_name: &str, args: ArgMatches) {
|
||||||
|
let context = Context::new(args);
|
||||||
|
|
||||||
|
// If the module returns `None`, print an empty string
|
||||||
|
let module = modules::handle(module_name, &context)
|
||||||
|
.map(|m| m.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
print!("{}", module);
|
||||||
|
}
|
28
starship_module_config_derive/Cargo.toml
Normal file
28
starship_module_config_derive/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
[package]
|
||||||
|
name = "starship_module_config_derive"
|
||||||
|
version = "0.20.0"
|
||||||
|
edition = "2018"
|
||||||
|
authors = ["Matan Kushner <hello@matchai.me>"]
|
||||||
|
homepage = "https://starship.rs"
|
||||||
|
documentation = "https://starship.rs/guide/"
|
||||||
|
repository = "https://github.com/starship/starship"
|
||||||
|
readme = "README.md"
|
||||||
|
license = "ISC"
|
||||||
|
keywords = ["prompt", "shell", "bash", "fish", "zsh"]
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
description = """
|
||||||
|
The cross-shell prompt for astronauts. ☄🌌️
|
||||||
|
"""
|
||||||
|
exclude = ["docs/**/*"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "starship_module_config_derive"
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
proc-macro2 = "~1"
|
||||||
|
quote = "~1"
|
||||||
|
syn = "~1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
starship = { version = "0.20.0", path = "../starship" }
|
76
starship_module_config_derive/src/lib.rs
Normal file
76
starship_module_config_derive/src/lib.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
extern crate proc_macro;
|
||||||
|
extern crate proc_macro2;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
#[proc_macro_derive(ModuleConfig)]
|
||||||
|
pub fn derive_module_config(input: TokenStream) -> TokenStream {
|
||||||
|
let dinput = parse_macro_input!(input as DeriveInput);
|
||||||
|
impl_module_config(dinput)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_module_config(dinput: DeriveInput) -> proc_macro::TokenStream {
|
||||||
|
let struct_ident = &dinput.ident;
|
||||||
|
let (_impl_generics, ty_generics, where_clause) = dinput.generics.split_for_impl();
|
||||||
|
|
||||||
|
let mut from_config = quote! {};
|
||||||
|
let mut load_config = quote! {};
|
||||||
|
|
||||||
|
if let syn::Data::Struct(data) = dinput.data {
|
||||||
|
if let syn::Fields::Named(fields_named) = data.fields {
|
||||||
|
let mut load_tokens = quote! {};
|
||||||
|
let mut from_tokens = quote! {};
|
||||||
|
|
||||||
|
for field in fields_named.named.iter() {
|
||||||
|
let ident = field.ident.as_ref().unwrap();
|
||||||
|
let ty = &field.ty;
|
||||||
|
|
||||||
|
let new_load_tokens = quote! {
|
||||||
|
if let Some(config_str) = config.get(stringify!(#ident)) {
|
||||||
|
new_module_config.#ident = new_module_config.#ident.load_config(config_str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let new_from_tokens = quote! {
|
||||||
|
#ident: <#ty>::from_config(config.get(stringify!(#ident))?)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
load_tokens = quote! {
|
||||||
|
#load_tokens
|
||||||
|
#new_load_tokens
|
||||||
|
};
|
||||||
|
from_tokens = quote! {
|
||||||
|
#from_tokens
|
||||||
|
#new_from_tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config = quote! {
|
||||||
|
fn load_config(&self, config: &'a toml::Value) -> Self {
|
||||||
|
let mut new_module_config = self.clone();
|
||||||
|
if let toml::Value::Table(config) = config {
|
||||||
|
#load_tokens
|
||||||
|
}
|
||||||
|
new_module_config
|
||||||
|
}
|
||||||
|
};
|
||||||
|
from_config = quote! {
|
||||||
|
fn from_config(config: &'a toml::Value) -> Option<Self> {
|
||||||
|
let config = config.as_table()?;
|
||||||
|
|
||||||
|
Some(#struct_ident {
|
||||||
|
#from_tokens
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenStream::from(quote! {
|
||||||
|
impl<'a> ModuleConfig<'a> for #struct_ident #ty_generics #where_clause {
|
||||||
|
#from_config
|
||||||
|
#load_config
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -52,17 +52,8 @@ RUN python --version
|
||||||
RUN USER=nonroot cargo new --bin /src/starship
|
RUN USER=nonroot cargo new --bin /src/starship
|
||||||
WORKDIR /src/starship
|
WORKDIR /src/starship
|
||||||
|
|
||||||
# We want dependencies cached, so copy those first
|
# Copy the whole project
|
||||||
COPY ./Cargo.lock ./Cargo.lock
|
COPY . .
|
||||||
COPY ./Cargo.toml ./Cargo.toml
|
|
||||||
|
|
||||||
# Cargo.toml will fail to parse without my_benchmark
|
|
||||||
RUN mkdir benches
|
|
||||||
RUN touch benches/my_benchmark.rs
|
|
||||||
|
|
||||||
# This is a dummy build to get dependencies cached
|
|
||||||
RUN cargo build --release \
|
|
||||||
&& rm -rf src target/debug/starship*
|
|
||||||
|
|
||||||
# "-Z unstable-options" is required for "--include-ignored"
|
# "-Z unstable-options" is required for "--include-ignored"
|
||||||
CMD ["cargo", "test", "--", "-Z", "unstable-options", "--include-ignored"]
|
CMD ["cargo", "test", "--", "-Z", "unstable-options", "--include-ignored"]
|
||||||
|
|
Loading…
Reference in a new issue