diff options
author | Matan Kushner <hello@matchai.me> | 2019-07-02 16:12:53 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-02 16:12:53 -0400 |
commit | 463ec260247fa0e62d2ea14e237681a499955392 (patch) | |
tree | 0d7ed8d19187c298c2e7764f21387a919d21877c /src | |
parent | 2440ed60d0a315e6248c040d2a114f7deeea6260 (diff) |
feat: Add a `disabled` configuration option for modules (#86)
โข Add support for the disabled configuration option
This will allow you to selectively disable modules that you don't want or need. ๐
โข Overwrite starship configuration file path with STARSHIP_CONFIG environment variable
โข Write tests for the two configuration options that are available
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 63 | ||||
-rw-r--r-- | src/context.rs | 21 | ||||
-rw-r--r-- | src/main.rs | 50 | ||||
-rw-r--r-- | src/modules/battery.rs | 6 | ||||
-rw-r--r-- | src/modules/character.rs | 4 | ||||
-rw-r--r-- | src/modules/directory.rs | 4 | ||||
-rw-r--r-- | src/modules/git_branch.rs | 4 | ||||
-rw-r--r-- | src/modules/git_status.rs | 4 | ||||
-rw-r--r-- | src/modules/go.rs | 4 | ||||
-rw-r--r-- | src/modules/line_break.rs | 4 | ||||
-rw-r--r-- | src/modules/mod.rs | 24 | ||||
-rw-r--r-- | src/modules/nodejs.rs | 4 | ||||
-rw-r--r-- | src/modules/package.rs | 14 | ||||
-rw-r--r-- | src/modules/python.rs | 4 | ||||
-rw-r--r-- | src/modules/rust.rs | 11 | ||||
-rw-r--r-- | src/modules/username.rs | 4 | ||||
-rw-r--r-- | src/print.rs | 31 |
17 files changed, 160 insertions, 96 deletions
diff --git a/src/config.rs b/src/config.rs index 7c0509a2b..74ff447d2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use crate::utils; +use std::env; use dirs::home_dir; @@ -20,9 +21,33 @@ impl Config { /// Create a config from a starship configuration file fn config_from_file() -> Option<toml::value::Table> { - let file_path = home_dir()?.join(".config/starship.toml"); - let toml_content = utils::read_file(&file_path.to_str()?).ok()?; - log::trace!("Config file content: \n{}", &toml_content); + let file_path = match env::var("STARSHIP_CONFIG") { + Ok(path) => { + // Use $STARSHIP_CONFIG as the config path if available + log::debug!("STARSHIP_CONFIG is set: {}", &path); + path + } + Err(_) => { + // Default to using ~/.config/starhip.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 found: \n{:?}", &config); @@ -40,3 +65,35 @@ impl Config { module_config } } + +/// Extends `toml::value::Table` with useful methods +pub trait TableExt { + fn get_as_bool(&self, key: &str) -> Option<bool>; +} + +impl TableExt for toml::value::Table { + /// Get a key from a module's configuration as a boolean + fn get_as_bool(&self, key: &str) -> Option<bool> { + self.get(key).map(toml::Value::as_bool).unwrap_or(None) + } +} + +mod tests { + use super::*; + + #[test] + fn table_get_as_bool() { + let mut table = toml::value::Table::new(); + + // Use with boolean value + table.insert("boolean".to_string(), toml::value::Value::Boolean(true)); + assert_eq!(table.get_as_bool("boolean"), Some(true)); + + // Use with string value + table.insert( + "string".to_string(), + toml::value::Value::String("true".to_string()), + ); + assert_eq!(table.get_as_bool("string"), None); + } +} diff --git a/src/context.rs b/src/context.rs index fd0ccc825..f62ea76cd 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -use crate::config::Config; +use crate::config::{Config, TableExt}; use crate::module::Module; use clap::ArgMatches; @@ -75,8 +75,23 @@ impl<'a> Context<'a> { dir } - pub fn new_module(&self, name: &str) -> Module { - Module::new(name, self.config.get_module_config(name)) + /// Create a new module + /// + /// Will return `None` if the module is disabled by configuration, by setting + /// the `disabled` key to `true` in the configuration for that module. + pub fn new_module(&self, name: &str) -> Option<Module> { + let config = self.config.get_module_config(name); + + // If the segment has "disabled" set to "true", don't show it + let disabled = config + .map(|table| table.get_as_bool("disabled")) + .unwrap_or(None); + + if disabled == Some(true) { + return None; + } + + Some(Module::new(name, config)) } // returns a new ScanDir struct with reference to current dir_files of context diff --git a/src/main.rs b/src/main.rs index 70f68e2e5..371109e50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,20 @@ use clap::{App, Arg, SubCommand}; fn main() { pretty_env_logger::init(); + let status_code_arg = Arg::with_name("status_code") + .short("s") + .long("status") + .value_name("STATUS_CODE") + .help("The status code of the previously run command") + .takes_value(true); + + let path_arg = Arg::with_name("path") + .short("p") + .long("path") + .value_name("PATH") + .help("The path that the prompt should render for") + .takes_value(true); + let matches = App::new("Starship") .about("The cross-shell prompt for astronauts. โจ๐") // pull the version number from Cargo.toml @@ -24,22 +38,8 @@ fn main() { .subcommand( SubCommand::with_name("prompt") .about("Prints the full starship prompt") - .arg( - Arg::with_name("status_code") - .short("s") - .long("status") - .value_name("STATUS_CODE") - .help("The status code of the previously run command") - .takes_value(true), - ) - .arg( - Arg::with_name("path") - .short("p") - .long("path") - .value_name("PATH") - .help("The path that the prompt should render for ($PWD by default)") - .takes_value(true), - ), + .arg(&status_code_arg) + .arg(&path_arg), ) .subcommand( SubCommand::with_name("module") @@ -49,22 +49,8 @@ fn main() { .help("The name of the module to be printed") .required(true), ) - .arg( - Arg::with_name("status_code") - .short("s") - .long("status") - .value_name("STATUS_CODE") - .help("The status code of the previously run command") - .takes_value(true), - ) - .arg( - Arg::with_name("path") - .short("p") - .long("path") - .value_name("PATH") - .help("The path the prompt should render for ($PWD by default)") - .takes_value(true), - ), + .arg(&status_code_arg) + .arg(&path_arg), ) .get_matches(); diff --git a/src/modules/battery.rs b/src/modules/battery.rs index 75ac1c554..048abe05f 100644 --- a/src/modules/battery.rs +++ b/src/modules/battery.rs @@ -3,7 +3,7 @@ use ansi_term::Color; use super::{Context, Module}; /// Creates a segment for the battery percentage and charging state -pub fn segment<'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 = "โฃ"; @@ -22,7 +22,7 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { } // TODO: Set style based on percentage when threshold is modifiable - let mut module = context.new_module("battery"); + let mut module = context.new_module("battery")?; module.set_style(Color::Red.bold()); module.get_prefix().set_value(""); @@ -61,7 +61,7 @@ fn get_battery_status() -> Option<BatteryStatus> { Some(battery_status) } Some(Err(e)) => { - log::debug!("Unable to access battery information:\n{}", e); + log::debug!("Unable to access battery information:\n{}", &e); None } None => { diff --git a/src/modules/character.rs b/src/modules/character.rs index 1a55cf78f..447fd759e 100644 --- a/src/modules/character.rs +++ b/src/modules/character.rs @@ -9,12 +9,12 @@ use ansi_term::Color; /// (green by default) /// - If the exit-code was anything else, the arrow will be formatted with /// `COLOR_FAILURE` (red by default) -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { const PROMPT_CHAR: &str = "โ"; let color_success = Color::Green.bold(); let color_failure = Color::Red.bold(); - let mut module = context.new_module("char"); + let mut module = context.new_module("char")?; module.get_prefix().set_value(""); let symbol = module.new_segment("symbol", PROMPT_CHAR); diff --git a/src/modules/directory.rs b/src/modules/directory.rs index 8e719c452..a2cafac16 100644 --- a/src/modules/directory.rs +++ b/src/modules/directory.rs @@ -12,12 +12,12 @@ use super::{Context, Module}; /// /// **Truncation** /// Paths will be limited in length to `3` path components by default. -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { const HOME_SYMBOL: &str = "~"; const DIR_TRUNCATION_LENGTH: usize = 3; let module_color = Color::Cyan.bold(); - let mut module = context.new_module("directory"); + let mut module = context.new_module("directory")?; module.set_style(module_color); let current_dir = &context.current_dir; diff --git a/src/modules/git_branch.rs b/src/modules/git_branch.rs index 010c4eccb..d2861c47b 100644 --- a/src/modules/git_branch.rs +++ b/src/modules/git_branch.rs @@ -5,13 +5,13 @@ use super::{Context, Module}; /// Creates a segment with the Git branch in the current directory /// /// Will display the branch name if the current directory is a git repo -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let branch_name = context.branch_name.as_ref()?; const GIT_BRANCH_CHAR: &str = "๎ "; let segment_color = Color::Purple.bold(); - let mut module = context.new_module("git_branch"); + let mut module = context.new_module("git_branch")?; module.set_style(segment_color); module.get_prefix().set_value("on "); diff --git a/src/modules/git_status.rs b/src/modules/git_status.rs index 6db29ba12..c5ec57e4d 100644 --- a/src/modules/git_status.rs +++ b/src/modules/git_status.rs @@ -17,7 +17,7 @@ use super::{Context, Module}; /// - `+` โ A new file has been added to the staging area /// - `ยป` โ A renamed file has been added to the staging area /// - `โ` โ A file's deletion has been added to the staging area -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { // This is the order that the sections will appear in const GIT_STATUS_CONFLICTED: &str = "="; const GIT_STATUS_AHEAD: &str = "โก"; @@ -35,7 +35,7 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { let repository = Repository::open(repo_root).ok()?; let module_style = Color::Red.bold(); - let mut module = context.new_module("git_status"); + let mut module = context.new_module("git_status")?; module.get_prefix().set_value("[").set_style(module_style); module.get_suffix().set_value("] ").set_style(module_style); module.set_style(module_style); diff --git a/src/modules/go.rs b/src/modules/go.rs index e4b689aff..a1b192e4a 100644 --- a/src/modules/go.rs +++ b/src/modules/go.rs @@ -13,7 +13,7 @@ use super::{Context, Module}; /// - Current directory contains a `Gopkg.lock` file /// - Current directory contains a `.go` file /// - Current directory contains a `Godeps` directory -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let is_go_project = context .new_scan_dir() .set_files(&["go.mod", "go.sum", "glide.yaml", "Gopkg.yml", "Gopkg.lock"]) @@ -30,7 +30,7 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { const GO_CHAR: &str = "๐น "; let module_color = Color::Cyan.bold(); - let mut module = context.new_module("go"); + let mut module = context.new_module("go")?; module.set_style(module_color); let formatted_version = format_go_version(go_version)?; diff --git a/src/modules/line_break.rs b/src/modules/line_break.rs index 0b11367ec..b86f85e38 100644 --- a/src/modules/line_break.rs +++ b/src/modules/line_break.rs @@ -1,10 +1,10 @@ use super::{Context, Module}; /// Creates a segment for the line break -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { const LINE_ENDING: &str = "\n"; - let mut module = context.new_module("line_break"); + let mut module = context.new_module("line_break")?; module.get_prefix().set_value(""); module.get_suffix().set_value(""); diff --git a/src/modules/mod.rs b/src/modules/mod.rs index cf1c64018..30a459ef1 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -16,18 +16,18 @@ use crate::module::Module; pub fn handle<'a>(module: &str, context: &'a Context) -> Option<Module<'a>> { match module { - "dir" | "directory" => directory::segment(context), - "char" | "character" => character::segment(context), - "node" | "nodejs" => nodejs::segment(context), - "rust" | "rustlang" => rust::segment(context), - "python" => python::segment(context), - "go" | "golang" => go::segment(context), - "line_break" => line_break::segment(context), - "package" => package::segment(context), - "git_branch" => git_branch::segment(context), - "git_status" => git_status::segment(context), - "username" => username::segment(context), - "battery" => battery::segment(context), + "dir" | "directory" => directory::module(context), + "char" | "character" => character::module(context), + "node" | "nodejs" => nodejs::module(context), + "rust" | "rustlang" => rust::module(context), + "python" => python::module(context), + "go" | "golang" => go::module(context), + "line_break" => line_break::module(context), + "package" => package::module(context), + "git_branch" => git_branch::module(context), + "git_status" => git_status::module(context), + "username" => username::module(context), + "battery" => battery::module(context), _ => panic!("Unknown module: {}", module), } diff --git a/src/modules/nodejs.rs b/src/modules/nodejs.rs index e542acf1e..9aa5a4376 100644 --- a/src/modules/nodejs.rs +++ b/src/modules/nodejs.rs @@ -9,7 +9,7 @@ use super::{Context, Module}; /// - Current directory contains a `.js` file /// - Current directory contains a `package.json` file /// - Current directory contains a `node_modules` directory -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let is_js_project = context .new_scan_dir() .set_files(&["package.json"]) @@ -26,7 +26,7 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { const NODE_CHAR: &str = "โฌข "; let module_color = Color::Green.bold(); - let mut module = context.new_module("node"); + let mut module = context.new_module("node")?; module.set_style(module_color); let formatted_version = node_version.trim(); diff --git a/src/modules/package.rs b/src/modules/package.rs index a9af63891..527d5eaec 100644 --- a/src/modules/package.rs +++ b/src/modules/package.rs @@ -8,13 +8,13 @@ use toml; /// Creates a segment with the current package version /// /// Will display if a version is defined for your Node.js or Rust project (if one exists) -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { match get_package_version() { Some(package_version) => { const PACKAGE_CHAR: &str = "๐ฆ "; let module_color = Color::Red.bold(); - let mut module = context.new_module("package"); + let mut module = context.new_module("package")?; module.set_style(module_color); module.get_prefix().set_value("is "); @@ -75,19 +75,21 @@ mod tests { #[test] fn test_extract_cargo_version() { - let cargo_with_version = r#" + let cargo_with_version = toml::toml! { [package] name = "starship" version = "0.1.0" - "#; + } + .to_string(); let expected_version = Some("v0.1.0".to_string()); assert_eq!(extract_cargo_version(&cargo_with_version), expected_version); - let cargo_without_version = r#" + let cargo_without_version = toml::toml! { [package] name = "starship" - "#; + } + .to_string(); let expected_version = None; assert_eq!( diff --git a/src/modules/python.rs b/src/modules/python.rs index 8b72e5187..a1a36cd3a 100644 --- a/src/modules/python.rs +++ b/src/modules/python.rs @@ -10,7 +10,7 @@ use super::{Context, Module}; /// - Current directory contains a `.python-version` file /// - Current directory contains a `requirements.txt` file /// - Current directory contains a `pyproject.toml` file -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let is_py_project = context .new_scan_dir() .set_files(&["requirements.txt", ".python-version", "pyproject.toml"]) @@ -26,7 +26,7 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { const PYTHON_CHAR: &str = "๐ "; let module_color = Color::Yellow.bold(); - let mut module = context.new_module("python"); + let mut module = context.new_module("python")?; module.set_style(module_color); let formatted_version = format_python_version(python_version); diff --git a/src/modules/rust.rs b/src/modules/rust.rs index 9ccdcc5d2..057344064 100644 --- a/src/modules/rust.rs +++ b/src/modules/rust.rs @@ -8,7 +8,7 @@ use super::{Context, Module}; /// 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 `Cargo.toml` file -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let is_rs_project = context .new_scan_dir() .set_files(&["Cargo.toml"]) @@ -24,7 +24,7 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { const RUST_CHAR: &str = "๐ฆ "; let module_color = Color::Red.bold(); - let mut module = context.new_module("rust"); + let mut module = context.new_module("rust")?; module.set_style(module_color); let formatted_version = format_rustc_version(rust_version); @@ -38,14 +38,14 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { } fn get_rust_version() -> Option<String> { - match Command::new("rustc").arg("-V").output() { + match Command::new("rustc").arg("--version").output() { Ok(output) => Some(String::from_utf8(output.stdout).unwrap()), Err(_) => None, } } fn format_rustc_version(mut rustc_stdout: String) -> String { - let offset = &rustc_stdout.find('(').unwrap(); + let offset = &rustc_stdout.find('(').unwrap_or(rustc_stdout.len()); let formatted_version: String = rustc_stdout.drain(..offset).collect(); format!("v{}", formatted_version.replace("rustc", "").trim()) @@ -65,5 +65,8 @@ mod tests { let stable_input = String::from("rustc 1.34.0 (91856ed52 2019-04-10)"); assert_eq!(format_rustc_version(stable_input), "v1.34.0"); + + let version_without_hash = String::from("rustc 1.34.0"); + assert_eq!(format_rustc_version(version_without_hash), "v1.34.0"); } } diff --git a/src/modules/username.rs b/src/modules/username.rs index 8885791b9..73295b79e 100644 --- a/src/modules/username.rs +++ b/src/modules/username.rs @@ -10,7 +10,7 @@ use super::{Context, Module}; /// - The current user isn't the same as the one that is logged in ($LOGNAME != $USER) /// - The current user is root (UID = 0) /// - The user is currently connected as an SSH session ($SSH_CONNECTION) -pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { +pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> { let user = env::var("USER").ok(); let logname = env::var("LOGNAME").ok(); let ssh_connection = env::var("SSH_CONNECTION").ok(); @@ -18,7 +18,7 @@ pub fn segment<'a>(context: &'a Context) -> Option<Module<'a>> { let mut module_color = Color::Yellow.bold(); if user != logname || ssh_connection.is_some() || is_root(&mut module_color) { - let mut module = context.new_module("username"); + let mut module = context.new_module("username")?; module.set_style(module_color); module.new_segment("username", user?); diff --git a/src/print.rs b/src/print.rs index 71d453e34..ae712e123 100644 --- a/src/print.rs +++ b/src/print.rs @@ -6,21 +6,22 @@ use crate::context::Context; use crate::module::Module; use crate::modules; +const PROMPT_ORDER: &[&str] = &[ + "battery", + "username", + "directory", + "git_branch", + "git_status", + "package", + "nodejs", + "rust", + "python", + "go", + "line_break", + "character", +]; + pub fn prompt(args: ArgMatches) { - let prompt_order = vec![ - "battery", - "username", - "directory", - "git_branch", - "git_status", - "package", - "nodejs", - "rust", - "python", - "go", - "line_break", - "character", - ]; let context = Context::new(args); let stdout = io::stdout(); @@ -29,7 +30,7 @@ pub fn prompt(args: ArgMatches) { // Write a new line before the prompt writeln!(handle).unwrap(); - let modules = prompt_order + let modules = PROMPT_ORDER .par_iter() .map(|module| modules::handle(module, &context)) // Compute modules .flatten() |