diff --git a/docs/config/README.md b/docs/config/README.md index 13bd3684..c5215067 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -206,6 +206,7 @@ $golang\ $helm\ $java\ $julia\ +$kotlin\ $nim\ $nodejs\ $ocaml\ @@ -1442,6 +1443,50 @@ The module will be shown if any of the following conditions are met: symbol = "∴ " ``` +## Kotlin + +The `kotlin` module shows the currently installed version of Kotlin. +The module will be shown if any of the following conditions are met: + +- The current directory contains a `.kt` or a `.kts` file + +### Options + +| Option | Default | Description | +| --------------- | ---------------------------------- | ----------------------------------------------------------------------------- | +| `format` | `"via [$symbol$version]($style) "` | The format for the module. | +| `symbol` | `"🅺 "` | A format string representing the symbol of Kotlin. | +| `style` | `"bold blue"` | The style for the module. | +| `kotlin_binary` | `"kotlin"` | Configures the kotlin binary that Starship executes when getting the version. | +| `disabled` | `false` | Disables the `kotlin` module. | + +### Variables + +| Variable | Example | Description | +| -------- | --------- | ------------------------------------ | +| version | `v1.4.21` | The version of `kotlin` | +| 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 + +[kotlin] +symbol = "🅺 " +``` + +```toml +# ~/.config/starship.toml + +[kotlin] +# Uses the Kotlin Compiler binary to get the installed version +kotlin_binary = "kotlinc" +``` + ## Kubernetes Displays the current Kubernetes context name and, if set, the namespace from the kubeconfig file. diff --git a/src/configs/kotlin.rs b/src/configs/kotlin.rs new file mode 100644 index 00000000..9e65da2c --- /dev/null +++ b/src/configs/kotlin.rs @@ -0,0 +1,24 @@ +use crate::config::{ModuleConfig, RootModuleConfig}; + +use starship_module_config_derive::ModuleConfig; + +#[derive(Clone, ModuleConfig)] +pub struct KotlinConfig<'a> { + pub format: &'a str, + pub symbol: &'a str, + pub style: &'a str, + pub kotlin_binary: &'a str, + pub disabled: bool, +} + +impl<'a> RootModuleConfig<'a> for KotlinConfig<'a> { + fn new() -> Self { + KotlinConfig { + format: "via [$symbol$version]($style) ", + symbol: "🅺 ", + style: "bold blue", + kotlin_binary: "kotlin", + disabled: false, + } + } +} diff --git a/src/configs/mod.rs b/src/configs/mod.rs index 19da4f47..156474c0 100644 --- a/src/configs/mod.rs +++ b/src/configs/mod.rs @@ -26,6 +26,7 @@ pub mod hostname; pub mod java; pub mod jobs; pub mod julia; +pub mod kotlin; pub mod kubernetes; pub mod lua; pub mod memory_usage; diff --git a/src/configs/starship_root.rs b/src/configs/starship_root.rs index 5b6c0249..554e2418 100644 --- a/src/configs/starship_root.rs +++ b/src/configs/starship_root.rs @@ -38,6 +38,7 @@ pub const PROMPT_ORDER: &[&str] = &[ "helm", "java", "julia", + "kotlin", "lua", "nim", "nodejs", diff --git a/src/module.rs b/src/module.rs index b65184a2..2f436ea0 100644 --- a/src/module.rs +++ b/src/module.rs @@ -36,6 +36,7 @@ pub const ALL_MODULES: &[&str] = &[ "java", "jobs", "julia", + "kotlin", "kubernetes", "line_break", "lua", diff --git a/src/modules/kotlin.rs b/src/modules/kotlin.rs new file mode 100644 index 00000000..31d979b4 --- /dev/null +++ b/src/modules/kotlin.rs @@ -0,0 +1,174 @@ +use super::{Context, Module, RootModuleConfig}; + +use crate::configs::kotlin::KotlinConfig; +use crate::formatter::StringFormatter; +use crate::utils; + +use regex::Regex; +const KOTLIN_VERSION_PATTERN: &str = "(?P[\\d\\.]+[\\d\\.]+[\\d\\.]+)"; + +/// Creates a module with the current Kotlin version +/// +/// Will display the Kotlin version if any of the following criteria are met: +/// - Current directory contains a file with a `.kt` or `.kts` extension +pub fn module<'a>(context: &'a Context) -> Option> { + let is_kotlin_project = context + .try_begin_scan()? + .set_extensions(&["kt", "kts"]) + .is_match(); + + if !is_kotlin_project { + return None; + } + + let mut module = context.new_module("kotlin"); + let config = KotlinConfig::try_load(module.config); + let kotlin_version = format_kotlin_version(&get_kotlin_version(&config.kotlin_binary)?)?; + let parsed = StringFormatter::new(config.format).and_then(|formatter| { + formatter + .map_meta(|var, _| match var { + "symbol" => Some(config.symbol), + _ => None, + }) + .map_style(|variable| match variable { + "style" => Some(Ok(config.style)), + _ => None, + }) + .map(|variable| match variable { + "version" => Some(Ok(&kotlin_version)), + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `kotlin`:\n{}", error); + return None; + } + }); + + Some(module) +} + +fn get_kotlin_version(kotlin_binary: &str) -> Option { + match utils::exec_cmd(kotlin_binary, &["-version"]) { + Some(output) => { + if output.stdout.is_empty() { + Some(output.stderr) + } else { + Some(output.stdout) + } + } + None => None, + } +} + +fn format_kotlin_version(kotlin_stdout: &str) -> Option { + // kotlin -version output looks like this: + // Kotlin version 1.4.21-release-411 (JRE 14.0.1+7) + + // kotlinc -version output looks like this: + // info: kotlinc-jvm 1.4.21 (JRE 14.0.1+7) + let re = Regex::new(KOTLIN_VERSION_PATTERN).ok()?; + let captures = re.captures(kotlin_stdout)?; + let version = &captures["version"]; + Some(format!("v{}", version)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::fs::File; + use std::io; + + #[test] + fn folder_without_kotlin_files() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let actual = ModuleRenderer::new("kotlin").path(dir.path()).collect(); + let expected = None; + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_kotlin_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.kt"))?.sync_all()?; + let actual = ModuleRenderer::new("kotlin").path(dir.path()).collect(); + let expected = Some(format!("via {} ", Color::Blue.bold().paint("🅺 v1.4.21"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_kotlin_script_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.kts"))?.sync_all()?; + let actual = ModuleRenderer::new("kotlin").path(dir.path()).collect(); + let expected = Some(format!("via {} ", Color::Blue.bold().paint("🅺 v1.4.21"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn kotlin_binary_is_kotlin_runtime() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.kt"))?.sync_all()?; + + let config = toml::toml! { + [kotlin] + kotlin_binary = "kotlin" + }; + + let actual = ModuleRenderer::new("kotlin") + .path(dir.path()) + .config(config) + .collect(); + + let expected = Some(format!("via {} ", Color::Blue.bold().paint("🅺 v1.4.21"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn kotlin_binary_is_kotlin_compiler() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.kt"))?.sync_all()?; + + let config = toml::toml! { + [kotlin] + kotlin_binary = "kotlinc" + }; + + let actual = ModuleRenderer::new("kotlin") + .path(dir.path()) + .config(config) + .collect(); + + let expected = Some(format!("via {} ", Color::Blue.bold().paint("🅺 v1.4.21"))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn test_format_kotlin_version_from_runtime() { + let kotlin_input = "Kotlin version 1.4.21-release-411 (JRE 14.0.1+7)"; + assert_eq!( + format_kotlin_version(kotlin_input), + Some("v1.4.21".to_string()) + ); + } + + #[test] + fn test_format_kotlin_version_from_compiler() { + let kotlin_input = "info: kotlinc-jvm 1.4.21 (JRE 14.0.1+7)"; + assert_eq!( + format_kotlin_version(kotlin_input), + Some("v1.4.21".to_string()) + ); + } +} diff --git a/src/modules/mod.rs b/src/modules/mod.rs index ba7196d8..4884fcfd 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -26,6 +26,7 @@ mod hostname; mod java; mod jobs; mod julia; +mod kotlin; mod kubernetes; mod line_break; mod lua; @@ -94,6 +95,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option> { "java" => java::module(context), "jobs" => jobs::module(context), "julia" => julia::module(context), + "kotlin" => kotlin::module(context), "kubernetes" => kubernetes::module(context), "line_break" => line_break::module(context), "lua" => lua::module(context), @@ -169,6 +171,7 @@ pub fn description(module: &str) -> &'static str { "java" => "The currently installed version of Java", "jobs" => "The current number of jobs running", "julia" => "The currently installed version of Julia", + "kotlin" => "The currently installed version of Kotlin", "kubernetes" => "The current Kubernetes context name and, if set, the namespace", "line_break" => "Separates the prompt into two lines", "lua" => "The currently installed version of Lua", diff --git a/src/utils.rs b/src/utils.rs index 5dd9b212..b866a2bd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -89,6 +89,14 @@ Elixir 1.10 (compiled with Erlang/OTP 22)\n", stdout: String::from("julia version 1.4.0\n"), stderr: String::default(), }), + "kotlin -version" => Some(CommandOutput { + stdout: String::from("Kotlin version 1.4.21-release-411 (JRE 14.0.1+7)\n"), + stderr: String::default(), + }), + "kotlinc -version" => Some(CommandOutput { + stdout: String::from("info: kotlinc-jvm 1.4.21 (JRE 14.0.1+7)\n"), + stderr: String::default(), + }), "lua -v" => Some(CommandOutput{ stdout: String::from("Lua 5.4.0 Copyright (C) 1994-2020 Lua.org, PUC-Rio\n"), stderr: String::default(),