From dd0b1a1aa2c36bf2df4db9c11a6517a018ffa100 Mon Sep 17 00:00:00 2001 From: Zhenhui Xie Date: Mon, 30 Sep 2019 20:10:35 +0800 Subject: refactor: Refactoring config (#383) This PR refactors config and puts configuration files for all modules in `configs/`. --- src/config.rs | 473 -------------------------------------------- src/context.rs | 294 --------------------------- src/init/mod.rs | 170 ---------------- src/init/starship.bash | 69 ------- src/init/starship.fish | 15 -- src/init/starship.zsh | 58 ------ src/lib.rs | 8 - src/main.rs | 136 ------------- src/module.rs | 328 ------------------------------ src/modules/aws.rs | 29 --- src/modules/battery.rs | 137 ------------- src/modules/character.rs | 69 ------- src/modules/cmd_duration.rs | 101 ---------- src/modules/directory.rs | 312 ----------------------------- src/modules/env_var.rs | 43 ---- src/modules/git_branch.rs | 65 ------ src/modules/git_state.rs | 162 --------------- src/modules/git_status.rs | 196 ------------------ src/modules/golang.rs | 82 -------- src/modules/hostname.rs | 41 ---- src/modules/java.rs | 105 ---------- src/modules/jobs.rs | 34 ---- src/modules/line_break.rs | 15 -- src/modules/memory_usage.rs | 91 --------- src/modules/mod.rs | 63 ------ src/modules/nix_shell.rs | 56 ------ src/modules/nodejs.rs | 49 ----- src/modules/package.rs | 166 ---------------- src/modules/python.rs | 122 ------------ src/modules/ruby.rs | 61 ------ src/modules/rust.rs | 291 --------------------------- src/modules/time.rs | 105 ---------- src/modules/username.rs | 53 ----- src/print.rs | 115 ----------- src/segment.rs | 66 ------- src/utils.rs | 12 -- 36 files changed, 4192 deletions(-) delete mode 100644 src/config.rs delete mode 100644 src/context.rs delete mode 100644 src/init/mod.rs delete mode 100644 src/init/starship.bash delete mode 100644 src/init/starship.fish delete mode 100644 src/init/starship.zsh delete mode 100644 src/lib.rs delete mode 100644 src/main.rs delete mode 100644 src/module.rs delete mode 100644 src/modules/aws.rs delete mode 100644 src/modules/battery.rs delete mode 100644 src/modules/character.rs delete mode 100644 src/modules/cmd_duration.rs delete mode 100644 src/modules/directory.rs delete mode 100644 src/modules/env_var.rs delete mode 100644 src/modules/git_branch.rs delete mode 100644 src/modules/git_state.rs delete mode 100644 src/modules/git_status.rs delete mode 100644 src/modules/golang.rs delete mode 100644 src/modules/hostname.rs delete mode 100644 src/modules/java.rs delete mode 100644 src/modules/jobs.rs delete mode 100644 src/modules/line_break.rs delete mode 100644 src/modules/memory_usage.rs delete mode 100644 src/modules/mod.rs delete mode 100644 src/modules/nix_shell.rs delete mode 100644 src/modules/nodejs.rs delete mode 100644 src/modules/package.rs delete mode 100644 src/modules/python.rs delete mode 100644 src/modules/ruby.rs delete mode 100644 src/modules/rust.rs delete mode 100644 src/modules/time.rs delete mode 100644 src/modules/username.rs delete mode 100644 src/print.rs delete mode 100644 src/segment.rs delete mode 100644 src/utils.rs (limited to 'src') diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 70c0754dc..000000000 --- a/src/config.rs +++ /dev/null @@ -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; - fn get_module_config(&self, module_name: &str) -> Option<&Table>; - - // Config accessor methods - fn get_as_bool(&self, key: &str) -> Option; - fn get_as_str(&self, key: &str) -> Option<&str>; - fn get_as_i64(&self, key: &str) -> Option; - fn get_as_array(&self, key: &str) -> Option<&Vec>; - fn get_as_ansi_style(&self, key: &str) -> Option; - fn get_as_segment_config(&self, key: &str) -> Option; - - // 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
{ - 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 { - 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 { - 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> { - 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 { - // 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 { - 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( - key: &str, - something: &Value, - casted_something: Option, -) { - 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:' (specifies that the color read should be a foreground color) - - 'bg:' (specifies that the color read should be a background color) - - 'underline' - - 'bold' - - 'italic' - - '' (see the parse_color_string doc for valid color strings) -*/ -fn parse_style_string(style_string: &str) -> Option { - 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 { - // 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::() { - 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, -} - -#[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)) - ); - } -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 3687eb74c..000000000 --- a/src/context.rs +++ /dev/null @@ -1,294 +0,0 @@ -use crate::config::Config; -use crate::module::Module; - -use clap::ArgMatches; -use git2::{Repository, RepositoryState}; -use once_cell::sync::OnceCell; -use std::env; -use std::ffi::OsStr; -use std::fs; -use std::path::{Path, PathBuf}; - -/// Context contains data or common methods that may be used by multiple modules. -/// The data contained within Context will be relevant to this particular rendering -/// of the prompt. -pub struct Context<'a> { - /// The deserialized configuration map from the user's `starship.toml` file. - pub config: toml::value::Table, - - /// The current working directory that starship is being called in. - pub current_dir: PathBuf, - - /// A vector containing the full paths of all the files in `current_dir`. - dir_files: OnceCell>, - - /// The map of arguments that were passed when starship was called. - pub arguments: ArgMatches<'a>, - - /// Private field to store Git information for modules who need it - repo: OnceCell, -} - -impl<'a> Context<'a> { - /// Identify the current working directory and create an instance of Context - /// for it. - pub fn new(arguments: ArgMatches) -> Context { - // Retrieve the "path" flag. If unavailable, use the current directory instead. - let path = arguments - .value_of("path") - .map(From::from) - .unwrap_or_else(|| env::current_dir().expect("Unable to identify current directory.")); - - Context::new_with_dir(arguments, path) - } - - /// Create a new instance of Context for the provided directory - pub fn new_with_dir(arguments: ArgMatches, dir: T) -> Context - where - T: Into, - { - let config = toml::value::Table::initialize(); - - // TODO: Currently gets the physical directory. Get the logical directory. - let current_dir = Context::expand_tilde(dir.into()); - - Context { - config, - arguments, - current_dir, - dir_files: OnceCell::new(), - repo: OnceCell::new(), - } - } - - /// Convert a `~` in a path to the home directory - fn expand_tilde(dir: PathBuf) -> PathBuf { - if dir.starts_with("~") { - let without_home = dir.strip_prefix("~").unwrap(); - return dirs::home_dir().unwrap().join(without_home); - } - dir - } - - /// Create a new module - pub fn new_module(&self, name: &str) -> Module { - let config = self.config.get_module_config(name); - - Module::new(name, config) - } - - /// Check the `disabled` configuration of the module - pub fn is_module_enabled(&self, name: &str) -> bool { - let config = self.config.get_module_config(name); - - // If the segment has "disabled" set to "true", don't show it - let disabled = config.and_then(|table| table.get_as_bool("disabled")); - - disabled != Some(true) - } - - // returns a new ScanDir struct with reference to current dir_files of context - // see ScanDir for methods - pub fn try_begin_scan(&'a self) -> Option> { - Some(ScanDir { - dir_files: self.get_dir_files().ok()?, - files: &[], - folders: &[], - extensions: &[], - }) - } - - /// Will lazily get repo root and branch when a module requests it. - pub fn get_repo(&self) -> Result<&Repo, std::io::Error> { - self.repo - .get_or_try_init(|| -> Result { - let repository = Repository::discover(&self.current_dir).ok(); - let branch = repository - .as_ref() - .and_then(|repo| get_current_branch(repo)); - let root = repository - .as_ref() - .and_then(|repo| repo.workdir().map(Path::to_path_buf)); - let state = repository.as_ref().map(|repo| repo.state()); - - Ok(Repo { - branch, - root, - state, - }) - }) - } - - pub fn get_dir_files(&self) -> Result<&Vec, std::io::Error> { - self.dir_files - .get_or_try_init(|| -> Result, std::io::Error> { - let dir_files = fs::read_dir(&self.current_dir)? - .filter_map(Result::ok) - .map(|entry| entry.path()) - .collect::>(); - - Ok(dir_files) - }) - } -} - -pub struct Repo { - /// If `current_dir` is a git repository or is contained within one, - /// this is the current branch name of that repo. - pub branch: Option, - - /// If `current_dir` is a git repository or is contained within one, - /// this is the path to the root of that repo. - pub root: Option, - - /// State - pub state: Option, -} - -// A struct of Criteria which will be used to verify current PathBuf is -// of X language, criteria can be set via the builder pattern -pub struct ScanDir<'a> { - dir_files: &'a Vec, - files: &'a [&'a str], - folders: &'a [&'a str], - extensions: &'a [&'a str], -} - -impl<'a> ScanDir<'a> { - pub const fn set_files(mut self, files: &'a [&'a str]) -> Self { - self.files = files; - self - } - - pub const fn set_extensions(mut self, extensions: &'a [&'a str]) -> Self { - self.extensions = extensions; - self - } - - pub const fn set_folders(mut self, folders: &'a [&'a str]) -> Self { - self.folders = folders; - self - } - - /// based on the current Pathbuf check to see - /// if any of this criteria match or exist and returning a boolean - pub fn is_match(&self) -> bool { - self.dir_files.iter().any(|path| { - if path.is_dir() { - path_has_name(path, self.folders) - } else { - path_has_name(path, self.files) || has_extension(path, self.extensions) - } - }) - } -} - -/// checks to see if the pathbuf matches a file or folder name -pub fn path_has_name<'a>(dir_entry: &PathBuf, names: &'a [&'a str]) -> bool { - let found_file_or_folder_name = names.iter().find(|file_or_folder_name| { - dir_entry - .file_name() - .and_then(OsStr::to_str) - .unwrap_or_default() - == **file_or_folder_name - }); - - match found_file_or_folder_name { - Some(name) => !name.is_empty(), - None => false, - } -} - -/// checks if pathbuf doesn't start with a dot and matches any provided extension -pub fn has_extension<'a>(dir_entry: &PathBuf, extensions: &'a [&'a str]) -> bool { - if let Some(file_name) = dir_entry.file_name() { - if file_name.to_string_lossy().starts_with('.') { - return false; - } - return extensions.iter().any(|ext| { - dir_entry - .extension() - .and_then(OsStr::to_str) - .map_or(false, |e| e == *ext) - }); - } - false -} - -fn get_current_branch(repository: &Repository) -> Option { - let head = repository.head().ok()?; - let shorthand = head.shorthand(); - - shorthand.map(std::string::ToString::to_string) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_path_has_name() { - let mut buf = PathBuf::from("/"); - let files = vec!["package.json"]; - - assert_eq!(path_has_name(&buf, &files), false); - - buf.set_file_name("some-file.js"); - assert_eq!(path_has_name(&buf, &files), false); - - buf.set_file_name("package.json"); - assert_eq!(path_has_name(&buf, &files), true); - } - - #[test] - fn test_has_extension() { - let mut buf = PathBuf::from("/"); - let extensions = vec!["js"]; - - assert_eq!(has_extension(&buf, &extensions), false); - - buf.set_file_name("some-file.rs"); - assert_eq!(has_extension(&buf, &extensions), false); - - buf.set_file_name(".some-file.js"); - assert_eq!(has_extension(&buf, &extensions), false); - - buf.set_file_name("some-file.js"); - assert_eq!(has_extension(&buf, &extensions), true) - } - - #[test] - fn test_criteria_scan_fails() { - let failing_criteria = ScanDir { - dir_files: &vec![PathBuf::new()], - files: &["package.json"], - extensions: &["js"], - folders: &["node_modules"], - }; - - // fails if buffer does not match any criteria - assert_eq!(failing_criteria.is_match(), false); - - let failing_dir_criteria = ScanDir { - dir_files: &vec![PathBuf::from("/package.js/dog.go")], - files: &["package.json"], - extensions: &["js"], - folders: &["node_modules"], - }; - - // fails when passed a pathbuf dir matches extension path - assert_eq!(failing_dir_criteria.is_match(), false); - } - - #[test] - fn test_criteria_scan_passes() { - let passing_criteria = ScanDir { - dir_files: &vec![PathBuf::from("package.json")], - files: &["package.json"], - extensions: &["js"], - folders: &["node_modules"], - }; - - assert_eq!(passing_criteria.is_match(), true); - } -} diff --git a/src/init/mod.rs b/src/init/mod.rs deleted file mode 100644 index 85f8f80d7..000000000 --- a/src/init/mod.rs +++ /dev/null @@ -1,170 +0,0 @@ -use std::ffi::OsStr; -use std::path::Path; -use std::{env, io}; - -/* We use a two-phase init here: the first phase gives a simple command to the -shell. This command evaluates a more complicated script using `source` and -process substitution. - -Directly using `eval` on a shell script causes it to be evaluated in -a single line, which sucks because things like comments will comment out the -rest of the script, and you have to spam semicolons everywhere. By using -source and process substitutions, we make it possible to comment and debug -the init scripts. - -In the future, this may be changed to just directly evaluating the initscript -using whatever mechanism is available in the host shell--this two-phase solution -has been developed as a compatibility measure with `eval $(starship init X)` -*/ - -fn path_to_starship() -> io::Result { - let current_exe = env::current_exe()? - .to_str() - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "can't convert to str"))? - .to_string(); - Ok(current_exe) -} - -/* This prints the setup stub, the short piece of code which sets up the main -init code. The stub produces the main init script, then evaluates it with -`source` and process substitution */ -pub fn init_stub(shell_name: &str) -> io::Result<()> { - log::debug!("Shell name: {}", shell_name); - - let shell_basename = Path::new(shell_name).file_stem().and_then(OsStr::to_str); - - let starship = path_to_starship()?.replace("\"", "\"'\"'\""); - - let setup_stub = match shell_basename { - Some("bash") => { - /* - * The standard bash bootstrap is: - * `source <(starship init bash --print-full-init)` - * - * Unfortunately there is an issue with bash 3.2 (the MacOS - * default) which prevents this from working. It does not support - * `source` with process substitution. - * - * There are more details here: https://stackoverflow.com/a/32596626 - * - * The workaround for MacOS is to use the `/dev/stdin` trick you - * see below. However, there are some systems with emulated POSIX - * environments which do not support `/dev/stdin`. For example, - * `Git Bash` within `Git for Windows and `Termux` on Android. - * - * Fortunately, these apps ship with recent-ish versions of bash. - * Git Bash is currently shipping bash 4.4 and Termux is shipping - * bash 5.0. - * - * Some testing has suggested that bash 4.0 is also incompatible - * with the standard bootstrap, whereas bash 4.1 appears to be - * consistently compatible. - * - * The upshot of all of this, is that we will use the standard - * bootstrap whenever the bash version is 4.1 or higher. Otherwise, - * we fall back to the `/dev/stdin` solution. - * - * More background can be found in these pull requests: - * https://github.com/starship/starship/pull/241 - * https://github.com/starship/starship/pull/278 - */ - let script = { - format!( - r#"if [ "${{BASH_VERSINFO[0]}}" -gt 4 ] || ([ "${{BASH_VERSINFO[0]}}" -eq 4 ] && [ "${{BASH_VERSINFO[1]}}" -ge 1 ]) -then -source <("{}" init bash --print-full-init) -else -source /dev/stdin <<<"$("{}" init bash --print-full-init)" -fi"#, - starship, starship - ) - }; - - Some(script) - } - Some("zsh") => { - let script = format!("source <(\"{}\" init zsh --print-full-init)", starship); - Some(script) - } - Some("fish") => { - // Fish does process substitution with pipes and psub instead of bash syntax - let script = format!( - "source (\"{}\" init fish --print-full-init | psub)", - starship - ); - Some(script) - } - None => { - println!( - "Invalid shell name provided: {}\\n\ - If this issue persists, please open an \ - issue in the starship repo: \\n\ - https://github.com/starship/starship/issues/new\\n\"", - shell_name - ); - None - } - Some(shell_basename) => { - println!( - "printf \"\\n{0} is not yet supported by starship.\\n\ - For the time being, we support bash, zsh, and fish.\\n\ - Please open an issue in the starship repo if you would like to \ - see support for {0}:\\nhttps://github.com/starship/starship/issues/new\"\\n\\n", - shell_basename - ); - None - } - }; - if let Some(script) = setup_stub { - print!("{}", script); - }; - Ok(()) -} - -/* This function (called when `--print-full-init` is passed to `starship init`) -prints out the main initialization script */ -pub fn init_main(shell_name: &str) -> io::Result<()> { - let starship_path = path_to_starship()?.replace("\"", "\"'\"'\""); - - let setup_script = match shell_name { - "bash" => Some(BASH_INIT), - "zsh" => Some(ZSH_INIT), - "fish" => Some(FISH_INIT), - _ => { - println!( - "printf \"Shell name detection failed on phase two init.\\n\ - This probably indicates a bug within starship: please open\\n\ - an issue at https://github.com/starship/starship/issues/new\\n\"" - ); - None - } - }; - if let Some(script) = setup_script { - // Set up quoting for starship path in case it has spaces. - let starship_path_string = format!("\"{}\"", starship_path); - let script = script.replace("::STARSHIP::", &starship_path_string); - print!("{}", script); - }; - Ok(()) -} - -/* GENERAL INIT SCRIPT NOTES - -Each init script will be passed as-is. Global notes for init scripts are in this -comment, with additional per-script comments in the strings themselves. - -JOBS: The argument to `--jobs` is quoted because MacOS's `wc` leaves whitespace -in the output. We pass it to starship and do the whitespace removal in Rust, -to avoid the cost of an additional shell fork every shell draw. - -Note that the init scripts are not in their final form--they are processed by -`starship init` prior to emitting the final form. In this processing, some tokens -are replaced, e.g. `::STARSHIP::` is replaced by the full path to the -starship binary. -*/ - -const BASH_INIT: &str = include_str!("starship.bash"); - -const ZSH_INIT: &str = include_str!("starship.zsh"); - -const FISH_INIT: &str = include_str!("starship.fish"); diff --git a/src/init/starship.bash b/src/init/starship.bash deleted file mode 100644 index da02f6e42..000000000 --- a/src/init/starship.bash +++ /dev/null @@ -1,69 +0,0 @@ -# We use PROMPT_COMMAND and the DEBUG trap to generate timing information. We try -# to avoid clobbering what we can, and try to give the user ways around our -# clobbers, if it's unavoidable. For example, PROMPT_COMMAND is appended to, -# and the DEBUG trap is layered with other traps, if it exists. - -# A bash quirk is that the DEBUG trap is fired every time a command runs, even -# if it's later on in the pipeline. If uncorrected, this could cause bad timing -# data for commands like `slow | slow | fast`, since the timer starts at the start -# of the "fast" command. - -# To solve this, we set a flag `PREEXEC_READY` when the prompt is drawn, and only -# start the timer if this flag is present. That way, timing is for the entire command, -# and not just a portion of it. - -# Will be run before *every* command (even ones in pipes!) -starship_preexec() { - # Avoid restarting the timer for commands in the same pipeline - if [ "$PREEXEC_READY" = "true" ]; then - PREEXEC_READY=false - STARSHIP_START_TIME=$(date +%s) - fi -} - -# Will be run before the prompt is drawn -starship_precmd() { - # Save the status, because commands in this pipeline will change $? - STATUS=$? - - # Run the bash precmd function, if it's set. If not set, evaluates to no-op - "${starship_precmd_user_func-:}" - - # Prepare the timer data, if needed. - if [[ $STARSHIP_START_TIME ]]; then - STARSHIP_END_TIME=$(date +%s) - STARSHIP_DURATION=$((STARSHIP_END_TIME - STARSHIP_START_TIME)) - PS1="$(::STARSHIP:: prompt --status=$STATUS --jobs="$(jobs -p | wc -l)" --cmd-duration=$STARSHIP_DURATION)" - unset STARSHIP_START_TIME - else - PS1="$(::STARSHIP:: prompt --status=$STATUS --jobs="$(jobs -p | wc -l)")" - fi - PREEXEC_READY=true; # Signal that we can safely restart the timer -} - -# If the user appears to be using https://github.com/rcaloras/bash-preexec, -# then hook our functions into their framework. -if [[ $preexec_functions ]]; then - preexec_functions+=(starship_preexec) - precmd_functions+=(starship_precmd) -else -# We want to avoid destroying an existing DEBUG hook. If we detect one, create -# a new function that runs both the existing function AND our function, then -# re-trap DEBUG to use this new function. This prevents a trap clobber. - dbg_trap="$(trap -p DEBUG | cut -d' ' -f3 | tr -d \')" - if [[ -z "$dbg_trap" ]]; then - trap starship_preexec DEBUG - elif [[ "$dbg_trap" != "starship_preexec" && "$dbg_trap" != "starship_preexec_all" ]]; then - function starship_preexec_all(){ - $dbg_trap; starship_preexec - } - trap starship_preexec_all DEBUG - fi - - # Finally, prepare the precmd function and set up the start time. - PROMPT_COMMAND="starship_precmd;$PROMPT_COMMAND" -fi - -# Set up the start time and STARSHIP_SHELL, which controls shell-specific sequences -STARSHIP_START_TIME=$(date +%s) -export STARSHIP_SHELL="bash" \ No newline at end of file diff --git a/src/init/starship.fish b/src/init/starship.fish deleted file mode 100644 index b2e88e5ee..000000000 --- a/src/init/starship.fish +++ /dev/null @@ -1,15 +0,0 @@ -function fish_prompt - switch "$fish_key_bindings" - case fish_hybrid_key_bindings fish_vi_key_bindings - set keymap "$fish_bind_mode" - case '*' - set keymap insert - end - set -l exit_code $status - # Account for changes in variable name between v2.7 and v3.0 - set -l CMD_DURATION "$CMD_DURATION$cmd_duration" - set -l starship_duration (math --scale=0 "$CMD_DURATION / 1000") - ::STARSHIP:: prompt --status=$exit_code --keymap=$keymap --cmd-duration=$starship_duration --jobs=(count (jobs -p)) -end -function fish_mode_prompt; end -export STARSHIP_SHELL="fish" diff --git a/src/init/starship.zsh b/src/init/starship.zsh deleted file mode 100644 index fdae70e12..000000000 --- a/src/init/starship.zsh +++ /dev/null @@ -1,58 +0,0 @@ -# ZSH has a quirk where `preexec` is only run if a command is actually run (i.e -# pressing ENTER at an empty command line will not cause preexec to fire). This -# can cause timing issues, as a user who presses "ENTER" without running a command -# will see the time to the start of the last command, which may be very large. - -# To fix this, we create STARSHIP_START_TIME upon preexec() firing, and destroy it -# after drawing the prompt. This ensures that the timing for one command is only -# ever drawn once (for the prompt immediately after it is run). - -zmodload zsh/parameter # Needed to access jobstates variable for NUM_JOBS - -# Will be run before every prompt draw -starship_precmd() { - # Save the status, because commands in this pipeline will change $? - STATUS=$? - - # Use length of jobstates array as number of jobs. Expansion fails inside - # quotes so we set it here and then use the value later on. - NUM_JOBS=$#jobstates - # Compute cmd_duration, if we have a time to consume - if [[ ! -z "${STARSHIP_START_TIME+1}" ]]; then - STARSHIP_END_TIME="$(date +%s)" - STARSHIP_DURATION=$((STARSHIP_END_TIME - STARSHIP_START_TIME)) - PROMPT="$(::STARSHIP:: prompt --status=$STATUS --cmd-duration=$STARSHIP_DURATION --jobs="$NUM_JOBS")" - unset STARSHIP_START_TIME - else - PROMPT="$(::STARSHIP:: prompt --status=$STATUS --jobs="$NUM_JOBS")" - fi -} -starship_preexec(){ - STARSHIP_START_TIME="$(date +%s)" -} - -# If precmd/preexec arrays are not already set, set them. If we don't do this, -# the code to detect whether starship_precmd is already in precmd_functions will -# fail because the array doesn't exist (and same for starship_preexec) -[[ -z "${precmd_functions+1}" ]] && precmd_functions=() -[[ -z "${preexec_functions+1}" ]] && preexec_functions=() - -# If starship precmd/preexec functions are already hooked, don't double-hook them -# to avoid unnecessary performance degradation in nested shells -if [[ ${precmd_functions[(ie)starship_precmd]} -gt ${#precmd_functions} ]]; then - precmd_functions+=(starship_precmd) -fi -if [[ ${preexec_functions[(ie)starship_preexec]} -gt ${#preexec_functions} ]]; then - preexec_functions+=(starship_preexec) -fi - -# Set up a function to redraw the prompt if the user switches vi modes -function zle-keymap-select -{ - PROMPT=$(::STARSHIP:: prompt --keymap=$KEYMAP --jobs="$(jobs | wc -l)") - zle reset-prompt -} - -STARSHIP_START_TIME="$(date +%s)" -zle -N zle-keymap-select -export STARSHIP_SHELL="zsh" diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index b8bef9afb..000000000 --- a/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Lib is present to allow for benchmarking -mod config; -pub mod context; -pub mod module; -pub mod modules; -pub mod print; -pub mod segment; -mod utils; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index a251c93e8..000000000 --- a/src/main.rs +++ /dev/null @@ -1,136 +0,0 @@ -#[macro_use] -extern crate clap; - -mod config; -mod context; -mod init; -mod module; -mod modules; -mod print; -mod segment; -mod utils; - -use crate::module::ALL_MODULES; -use clap::{App, AppSettings, 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 shell_arg = Arg::with_name("shell") - .value_name("SHELL") - .help( - "The name of the currently running shell\nCurrently supported options: bash, zsh, fish", - ) - .required(true); - - let cmd_duration_arg = Arg::with_name("cmd_duration") - .short("d") - .long("cmd-duration") - .value_name("CMD_DURATION") - .help("The execution duration of the last command, in seconds") - .takes_value(true); - - let keymap_arg = Arg::with_name("keymap") - .short("k") - .long("keymap") - .value_name("KEYMAP") - // fish/zsh only - .help("The keymap of fish/zsh") - .takes_value(true); - - let jobs_arg = Arg::with_name("jobs") - .short("j") - .long("jobs") - .value_name("JOBS") - .help("The number of currently running jobs") - .takes_value(true); - - let init_scripts_arg = Arg::with_name("print_full_init") - .long("print-full-init") - .help("Print the main initialization script (as opposed to the init stub)"); - - let matches = App::new("starship") - .about("The cross-shell prompt for astronauts. ☄🌌️") - // pull the version number from Cargo.toml - .version(crate_version!()) - // pull the authors from Cargo.toml - .author(crate_authors!()) - .after_help("https://github.com/starship/starship") - .setting(AppSettings::SubcommandRequiredElseHelp) - .subcommand( - SubCommand::with_name("init") - .about("Prints the shell function used to execute starship") - .arg(&shell_arg) - .arg(&init_scripts_arg), - ) - .subcommand( - SubCommand::with_name("prompt") - .about("Prints the full starship prompt") - .arg(&status_code_arg) - .arg(&path_arg) - .arg(&cmd_duration_arg) - .arg(&keymap_arg) - .arg(&jobs_arg), - ) - .subcommand( - SubCommand::with_name("module") - .about("Prints a specific prompt module") - .arg( - Arg::with_name("name") - .help("The name of the module to be printed") - .required(true) - .required_unless("list"), - ) - .arg( - Arg::with_name("list") - .short("l") - .long("list") - .help("List out all supported modules"), - ) - .arg(&status_code_arg) - .arg(&path_arg) - .arg(&cmd_duration_arg) - .arg(&keymap_arg) - .arg(&jobs_arg), - ) - .get_matches(); - - match matches.subcommand() { - ("init", Some(sub_m)) => { - let shell_name = sub_m.value_of("shell").expect("Shell name missing."); - if sub_m.is_present("print_full_init") { - init::init_main(shell_name).expect("can't init_main"); - } else { - init::init_stub(shell_name).expect("can't init_stub"); - } - } - ("prompt", Some(sub_m)) => print::prompt(sub_m.clone()), - ("module", Some(sub_m)) => { - if sub_m.is_present("list") { - println!("Supported modules list"); - println!("----------------------"); - for modules in ALL_MODULES { - println!("{}", modules); - } - } - if let Some(module_name) = sub_m.value_of("name") { - print::module(module_name, sub_m.clone()); - } - } - _ => {} - } -} diff --git a/src/module.rs b/src/module.rs deleted file mode 100644 index b9e71cbec..000000000 --- a/src/module.rs +++ /dev/null @@ -1,328 +0,0 @@ -use crate::config::Config; -use crate::config::SegmentConfig; -use crate::segment::Segment; -use ansi_term::Style; -use ansi_term::{ANSIString, ANSIStrings}; -use std::fmt; - -// List of all modules -pub const ALL_MODULES: &[&str] = &[ - "aws", - #[cfg(feature = "battery")] - "battery", - "character", - "cmd_duration", - "directory", - "env_var", - "git_branch", - "git_state", - "git_status", - "golang", - "hostname", - "java", - "jobs", - "line_break", - "memory_usage", - "nix_shell", - "nodejs", - "package", - "python", - "ruby", - "rust", - "time", - "username", -]; - -/// A module is a collection of segments showing data for a single integration -/// (e.g. The git module shows the current git branch and status) -pub struct Module<'a> { - /// The module's configuration map if available - config: Option<&'a toml::value::Table>, - - /// The module's name, to be used in configuration and logging. - _name: String, - - /// The styling to be inherited by all segments contained within this module. - style: Style, - - /// The prefix used to separate the current module from the previous one. - prefix: Affix, - - /// The collection of segments that compose this module. - segments: Vec, - - /// The suffix used to separate the current module from the next one. - suffix: Affix, -} - -impl<'a> Module<'a> { - /// Creates a module with no segments. - pub fn new(name: &str, config: Option<&'a toml::value::Table>) -> Module<'a> { - Module { - config, - _name: name.to_string(), - style: Style::default(), - prefix: Affix::default_prefix(name), - segments: Vec::new(), - suffix: Affix::default_suffix(name), - } - } - - /// Get a reference to a newly created segment in the module - pub fn new_segment(&mut self, name: &str, value: &str) -> &mut Segment { - let mut segment = Segment::new(name); - if let Some(segment_config) = self.config_value_segment_config(name) { - segment.set_style(segment_config.style.unwrap_or(self.style)); - segment.set_value(segment_config.value.unwrap_or(value)); - } else { - segment.set_style(self.style); - // Use the provided value unless overwritten by config - segment.set_value(self.config_value_str(name).unwrap_or(value)); - } - self.segments.push(segment); - - self.segments.last_mut().unwrap() - } - - /// Should config exists, get a reference to a newly created segment in the module - pub fn new_segment_if_config_exists(&mut self, name: &str) -> Option<&mut Segment> { - // Use the provided value unless overwritten by config - if let Some(value) = self.config_value_str(name) { - let mut segment = Segment::new(name); - segment.set_style(self.style); - segment.set_value(value); - self.segments.push(segment); - Some(self.segments.last_mut().unwrap()) - } else { - None - } - } - - /// Whether a module has non-empty segments - pub fn is_empty(&self) -> bool { - self.segments.iter().all(|segment| segment.is_empty()) - } - - /// Get the module's prefix - pub fn get_prefix(&mut self) -> &mut Affix { - &mut self.prefix - } - - /// Get the module's suffix - pub fn get_suffix(&mut self) -> &mut Affix { - &mut self.suffix - } - - /// Sets the style of the segment. - /// - /// Accepts either `Color` or `Style`. - pub fn set_style(&mut self, style: T) -> &mut Module<'a> - where - T: Into