diff --git a/docs/.vuepress/public/presets/toml/nerd-font-symbols.toml b/docs/.vuepress/public/presets/toml/nerd-font-symbols.toml index 26c8da83..bdd64540 100644 --- a/docs/.vuepress/public/presets/toml/nerd-font-symbols.toml +++ b/docs/.vuepress/public/presets/toml/nerd-font-symbols.toml @@ -1,6 +1,9 @@ [aws] symbol = " " +[buf] +symbol = " " + [conda] symbol = " " diff --git a/docs/.vuepress/public/presets/toml/no-runtime-versions.toml b/docs/.vuepress/public/presets/toml/no-runtime-versions.toml index ca2d82d1..e549f41c 100644 --- a/docs/.vuepress/public/presets/toml/no-runtime-versions.toml +++ b/docs/.vuepress/public/presets/toml/no-runtime-versions.toml @@ -1,3 +1,6 @@ +[buf] +format = "via [$symbol]($style)" + [cmake] format = "via [$symbol]($style)" diff --git a/docs/config/README.md b/docs/config/README.md index 3110023a..2cdf24b4 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -206,6 +206,7 @@ $git_status\ $hg_branch\ $docker_context\ $package\ +$buf\ $cmake\ $cobol\ $container\ @@ -455,6 +456,45 @@ discharging_symbol = "💦" # when capacity is over 30%, the battery indicator will not be displayed ``` +## Buf + +The `buf` module shows the currently installed version of [Buf](https://buf.build). By default, the module is shown if all of the following conditions are met: + +- The [`buf`](https://github.com/bufbuild/buf) CLI is installed. +- The current directory contains a [`buf.yaml`](https://docs.buf.build/configuration/v1/buf-yaml), [`buf.gen.yaml`](https://docs.buf.build/configuration/v1/buf-gen-yaml), or [`buf.work.yaml`](https://docs.buf.build/configuration/v1/buf-work-yaml) configuration file. + +### Options + +| Option | Default | Description | +| ------------------- | ---------------------------------------------------------- | ----------------------------------------------------- | +| `format` | `'with [$symbol($version \(Buf $buf_version\) )]($style)'` | The format for the `buf` module. | +| `version_format` | `"v${raw}"` | The version format. | +| `symbol` | `"🦬 "` | The symbol used before displaying the version of Buf. | +| `detect_extensions` | `[]` | Which extensions should trigger this module. | +| `detect_files` | `["buf.yaml", "buf.gen.yaml", "buf.work.yaml"]` | Which filenames should trigger this module. | +| `detect_folders` | `[]` | Which folders should trigger this modules. | +| `style` | `"bold blue"` | The style for the module. | +| `disabled` | `false` | Disables the `elixir` module. | + +### Variables + +| Variable | Example | Description | +| ------------- | -------- | ------------------------------------ | +| `buf_version` | `v1.0.0` | The version of `buf` | +| `symbol` | | Mirrors the value of option `symbol` | +| `style`* | | Mirrors the value of option `style` | + +*: This variable can only be used as a part of a style string + +### Example + +```toml +# ~/.config/starship.toml + +[buf] +symbol = "🦬 " +``` + ## Character The `character` module shows a character (usually an arrow) beside where the text diff --git a/src/configs/buf.rs b/src/configs/buf.rs new file mode 100644 index 00000000..f4fd0a18 --- /dev/null +++ b/src/configs/buf.rs @@ -0,0 +1,31 @@ +use crate::config::ModuleConfig; + +use serde::Serialize; +use starship_module_config_derive::ModuleConfig; + +#[derive(Clone, ModuleConfig, Serialize)] +pub struct BufConfig<'a> { + pub format: &'a str, + pub version_format: &'a str, + pub symbol: &'a str, + pub style: &'a str, + pub disabled: bool, + pub detect_extensions: Vec<&'a str>, + pub detect_files: Vec<&'a str>, + pub detect_folders: Vec<&'a str>, +} + +impl<'a> Default for BufConfig<'a> { + fn default() -> Self { + BufConfig { + format: "with [$symbol ($version)]($style)", + version_format: "v${raw}", + symbol: "", + style: "bold blue", + disabled: false, + detect_extensions: vec![], + detect_files: vec!["buf.yaml", "buf.gen.yaml", "buf.work.yaml"], + detect_folders: vec![], + } + } +} diff --git a/src/configs/mod.rs b/src/configs/mod.rs index 02bdfb3d..a510cf82 100644 --- a/src/configs/mod.rs +++ b/src/configs/mod.rs @@ -6,6 +6,7 @@ use starship_module_config_derive::ModuleConfig; pub mod aws; pub mod azure; pub mod battery; +pub mod buf; pub mod character; pub mod cmake; pub mod cmd_duration; @@ -90,6 +91,7 @@ pub struct FullConfig<'a> { aws: aws::AwsConfig<'a>, azure: azure::AzureConfig<'a>, battery: battery::BatteryConfig<'a>, + buf: buf::BufConfig<'a>, character: character::CharacterConfig<'a>, cmake: cmake::CMakeConfig<'a>, cmd_duration: cmd_duration::CmdDurationConfig<'a>, @@ -171,6 +173,7 @@ impl<'a> Default for FullConfig<'a> { aws: Default::default(), azure: Default::default(), battery: Default::default(), + buf: Default::default(), character: Default::default(), cmake: Default::default(), cmd_duration: Default::default(), diff --git a/src/configs/starship_root.rs b/src/configs/starship_root.rs index 86f12798..0a519b0f 100644 --- a/src/configs/starship_root.rs +++ b/src/configs/starship_root.rs @@ -69,6 +69,7 @@ pub const PROMPT_ORDER: &[&str] = &[ "vagrant", "zig", // ↑ Toolchain version modules ↑ + "buf", "nix_shell", "conda", "memory_usage", diff --git a/src/module.rs b/src/module.rs index 2b1da506..a385243f 100644 --- a/src/module.rs +++ b/src/module.rs @@ -12,6 +12,7 @@ pub const ALL_MODULES: &[&str] = &[ "azure", #[cfg(feature = "battery")] "battery", + "buf", "character", "cmake", "cmd_duration", diff --git a/src/modules/buf.rs b/src/modules/buf.rs new file mode 100644 index 00000000..78fccc93 --- /dev/null +++ b/src/modules/buf.rs @@ -0,0 +1,123 @@ +use super::{Context, Module, RootModuleConfig}; + +use crate::configs::buf::BufConfig; +use crate::formatter::StringFormatter; +use crate::formatter::VersionFormatter; + +pub fn module<'a>(context: &'a Context) -> Option> { + let mut module = context.new_module("buf"); + let config: BufConfig = BufConfig::try_load(module.config); + + let is_buf_project = context + .try_begin_scan()? + .set_files(&config.detect_files) + .set_extensions(&config.detect_extensions) + .set_folders(&config.detect_folders) + .is_match(); + + if !is_buf_project { + return None; + } + + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|variable, _| match variable { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "version" => { + let buf_version = + parse_buf_version(&context.exec_cmd("buf", &["--version"])?.stdout)?; + VersionFormatter::format_module_version( + module.get_name(), + &buf_version, + config.version_format, + ) + } + .map(Ok), + _ => None, + }) + .parse(None, Some(context)) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `buf`:\n{}", error); + return None; + } + }); + + Some(module) +} + +fn parse_buf_version(buf_version: &str) -> Option { + Some(buf_version.split_whitespace().next()?.to_string()) +} + +#[cfg(test)] +mod tests { + use super::parse_buf_version; + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::fs::File; + use std::io; + + #[test] + fn buf_version() { + let ok_versions = ["1.0.0", "1.1.0-dev"]; + let not_ok_versions = ["foo", "1.0"]; + + let all_some = ok_versions.iter().all(|&v| parse_buf_version(v).is_some()); + let all_none = not_ok_versions + .iter() + .any(|&v| parse_buf_version(v).is_some()); + + assert!(all_some); + assert!(all_none); + } + + #[test] + fn folder_without_buf_config() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let actual = ModuleRenderer::new("buf").path(dir.path()).collect(); + let expected = None; + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_buf_config() { + let ok_files = ["buf.yaml", "buf.gen.yaml", "buf.work.yaml"]; + let not_ok_files = ["buf.json"]; + + for file in ok_files { + let dir = tempfile::tempdir().unwrap(); + File::create(dir.path().join(file)) + .unwrap() + .sync_all() + .unwrap(); + let actual = ModuleRenderer::new("buf").path(dir.path()).collect(); + let expected = Some(format!("with {}", Color::Blue.bold().paint(" v1.0.0"))); + assert_eq!(expected, actual); + dir.close().unwrap(); + } + + for file in not_ok_files { + let dir = tempfile::tempdir().unwrap(); + File::create(dir.path().join(file)) + .unwrap() + .sync_all() + .unwrap(); + let actual = ModuleRenderer::new("buf").path(dir.path()).collect(); + let expected = None; + assert_eq!(expected, actual); + dir.close().unwrap(); + } + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index da724a05..6a392a73 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -1,6 +1,7 @@ // While adding out new module add out module to src/module.rs ALL_MODULES const array also. mod aws; mod azure; +mod buf; mod character; mod cmake; mod cmd_duration; @@ -90,6 +91,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option> { "azure" => azure::module(context), #[cfg(feature = "battery")] "battery" => battery::module(context), + "buf" => buf::module(context), "character" => character::module(context), "cmake" => cmake::module(context), "cmd_duration" => cmd_duration::module(context), @@ -179,6 +181,7 @@ pub fn description(module: &str) -> &'static str { "aws" => "The current AWS region and profile", "azure" => "The current Azure subscription", "battery" => "The current charge of the device's battery and its current charging status", + "buf" => "The currently installed version of the Buf CLI", "character" => { "A character (usually an arrow) beside where the text is entered in your terminal" } diff --git a/src/utils.rs b/src/utils.rs index 4205b5a9..9f55f0c0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -129,6 +129,10 @@ pub fn mock_cmd + Debug, U: AsRef + Debug>( ) -> Option> { let command = display_command(&cmd, args); let out = match command.as_str() { + "buf --version" => Some(CommandOutput { + stdout: String::from("1.0.0"), + stderr: String::default(), + }), "cobc -version" => Some(CommandOutput { stdout: String::from("\ cobc (GnuCOBOL) 3.1.2.0