From dd5c62791cc96f89588af5827e014a56790ceb4c Mon Sep 17 00:00:00 2001 From: Beni Ben zikry Date: Sun, 14 Mar 2021 21:37:00 +0200 Subject: [PATCH] feat(scala): Add scala module (#2409) * Add support for scala ( w/o sbt ) * Add scala description * Change scala color to bold red * update scala extensions in README * failed lint * cr changes ( regex, simplify parsing, imports ) * Scala readme punctuation Co-authored-by: David Knaack Co-authored-by: David Knaack --- docs/config/README.md | 44 +++++++++ docs/presets/README.md | 3 + src/configs/mod.rs | 1 + src/configs/scala.rs | 28 ++++++ src/configs/starship_root.rs | 1 + src/module.rs | 1 + src/modules/mod.rs | 3 + src/modules/scala.rs | 170 +++++++++++++++++++++++++++++++++++ src/utils.rs | 4 + 9 files changed, 255 insertions(+) create mode 100644 src/configs/scala.rs create mode 100644 src/modules/scala.rs diff --git a/docs/config/README.md b/docs/config/README.md index 926e26dc0..af580016a 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -216,6 +216,7 @@ $purescript\ $python\ $ruby\ $rust\ +$scala\ $swift\ $terraform\ $vagrant\ @@ -2272,6 +2273,49 @@ The module will be shown if any of the following conditions are met: format = "via [⚙️ $version](red bold)" ``` + +## Scala + +The `scala` module shows the currently installed version of Scala. +By default the module will be shown if any of the following conditions are met: + +- The current directory contains a `build.sbt`, `.scalaenv` or `.sbtenv` file +- The current directory contains a file with the `.scala` or `.sbt` extension +- The current directory contains a directory named `.metals` + +### Options + + +| Option | Default | Description | +| ------------------- | -------------------------------------------| ----------------------------------------------- | +| `format` | `"via [${symbol}(${version} )]($style)"` | The format for the module. | +| `detect_extensions` | `["sbt", "scala"]` | Which extensions should trigger this module. | +| `detect_files` | `[".scalaenv", ".sbtenv", "build.sbt"]` | Which filenames should trigger this module. | +| `detect_folders` | `[".metals"]` | Which folders should trigger this modules. | +| `symbol` | `"🆂 "` | A format string representing the symbol of Scala. | +| `style` | `"red dimmed"` | The style for the module. | +| `disabled` | `false` | Disables the `scala` module. | + +### Variables + +| Variable | Example | Description | +| -------- | -----------| ------------------------------------ | +| version | `2.13.5` | The version of `scala` | +| 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 + +[scala] +symbol = "🌟 " +``` + + ## Shell The `shell` module shows an indicator for currently used shell. diff --git a/docs/presets/README.md b/docs/presets/README.md index f8939242c..040cc207b 100644 --- a/docs/presets/README.md +++ b/docs/presets/README.md @@ -86,6 +86,9 @@ symbol = " " [rust] symbol = " " +[scala] +symbol = " " + [swift] symbol = "ﯣ " ``` diff --git a/src/configs/mod.rs b/src/configs/mod.rs index 1052a6736..4ad15699c 100644 --- a/src/configs/mod.rs +++ b/src/configs/mod.rs @@ -42,6 +42,7 @@ pub mod purescript; pub mod python; pub mod ruby; pub mod rust; +pub mod scala; pub mod shell; pub mod shlvl; pub mod singularity; diff --git a/src/configs/scala.rs b/src/configs/scala.rs new file mode 100644 index 000000000..3c5fc670e --- /dev/null +++ b/src/configs/scala.rs @@ -0,0 +1,28 @@ +use crate::config::{ModuleConfig, RootModuleConfig}; + +use starship_module_config_derive::ModuleConfig; + +#[derive(Clone, ModuleConfig)] +pub struct ScalaConfig<'a> { + pub disabled: bool, + pub format: &'a str, + pub style: &'a str, + pub symbol: &'a str, + pub detect_extensions: Vec<&'a str>, + pub detect_files: Vec<&'a str>, + pub detect_folders: Vec<&'a str>, +} + +impl<'a> RootModuleConfig<'a> for ScalaConfig<'a> { + fn new() -> Self { + ScalaConfig { + format: "via [$symbol($version )]($style)", + disabled: false, + style: "red bold", + symbol: "🆂 ", + detect_extensions: vec!["sbt", "scala"], + detect_files: vec![".scalaenv", ".sbtenv", "build.sbt"], + detect_folders: vec![".metals"], + } + } +} diff --git a/src/configs/starship_root.rs b/src/configs/starship_root.rs index a73f0229a..9064e5afe 100644 --- a/src/configs/starship_root.rs +++ b/src/configs/starship_root.rs @@ -50,6 +50,7 @@ pub const PROMPT_ORDER: &[&str] = &[ "python", "ruby", "rust", + "scala", "swift", "terraform", "vagrant", diff --git a/src/module.rs b/src/module.rs index 7a6961beb..c5ca928e7 100644 --- a/src/module.rs +++ b/src/module.rs @@ -34,6 +34,7 @@ pub const ALL_MODULES: &[&str] = &[ "hg_branch", "hostname", "java", + "scala", "jobs", "julia", "kotlin", diff --git a/src/modules/mod.rs b/src/modules/mod.rs index 207d4e66b..35dd4f37c 100644 --- a/src/modules/mod.rs +++ b/src/modules/mod.rs @@ -43,6 +43,7 @@ mod purescript; mod python; mod ruby; mod rust; +mod scala; mod shell; mod shlvl; mod singularity; @@ -114,6 +115,7 @@ pub fn handle<'a>(module: &str, context: &'a Context) -> Option> { "python" => python::module(context), "ruby" => ruby::module(context), "rust" => rust::module(context), + "scala" => scala::module(context), "shell" => shell::module(context), "shlvl" => shlvl::module(context), "singularity" => singularity::module(context), @@ -192,6 +194,7 @@ pub fn description(module: &str) -> &'static str { "python" => "The currently installed version of Python", "ruby" => "The currently installed version of Ruby", "rust" => "The currently installed version of Rust", + "scala" => "The currently installed version of Scala", "swift" => "The currently installed version of Swift", "shell" => "The currently used shell indicator", "shlvl" => "The current value of SHLVL", diff --git a/src/modules/scala.rs b/src/modules/scala.rs new file mode 100644 index 000000000..f02f44cc6 --- /dev/null +++ b/src/modules/scala.rs @@ -0,0 +1,170 @@ +use crate::configs::scala::ScalaConfig; +use crate::formatter::StringFormatter; + +use super::{Context, Module, RootModuleConfig}; + +use regex::Regex; + +const SCALA_VERSION_PATTERN: &str = "version[\\s](?P[^\\s]+)"; + +pub fn module<'a>(context: &'a Context) -> Option> { + let mut module = context.new_module("scala"); + let config: ScalaConfig = ScalaConfig::try_load(module.config); + + let is_scala_project = context + .try_begin_scan()? + .set_files(&config.detect_files) + .set_extensions(&config.detect_extensions) + .set_folders(&config.detect_folders) + .is_match(); + + if !is_scala_project { + return None; + } + + 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" => { + let scala_version = get_scala_version(context)?; + Some(Ok(scala_version)) + } + _ => None, + }) + .parse(None) + }); + + module.set_segments(match parsed { + Ok(segments) => segments, + Err(error) => { + log::warn!("Error in module `scala`:\n{}", error); + return None; + } + }); + + Some(module) +} + +fn get_scala_version(context: &Context) -> Option { + let output = context.exec_cmd("scalac", &["-version"])?; + let scala_version = if output.stdout.is_empty() { + output.stderr + } else { + output.stdout + }; + + parse_scala_version(&scala_version) +} + +fn parse_scala_version(scala_version: &str) -> Option { + let re = Regex::new(SCALA_VERSION_PATTERN).ok()?; + let captures = re.captures(scala_version)?; + 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::{self, File}; + use std::io; + + #[test] + fn test_parse_scala_version() { + let scala_2_13 = + "Scala compiler version 2.13.5 -- Copyright 2002-2020, LAMP/EPFL and Lightbend, Inc."; + assert_eq!(parse_scala_version(scala_2_13), Some("v2.13.5".to_string())); + } + + #[test] + fn test_parse_dotty_version() { + let dotty_version = "Scala compiler version 3.0.0-RC1 -- Copyright 2002-2021, LAMP/EPFL"; + assert_eq!( + parse_scala_version(dotty_version), + Some("v3.0.0-RC1".to_string()) + ); + } + + #[test] + fn folder_without_scala_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let actual = ModuleRenderer::new("scala").path(dir.path()).collect(); + let expected = None; + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_scala_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("Test.scala"))?.sync_all()?; + let actual = ModuleRenderer::new("scala").path(dir.path()).collect(); + let expected = Some(format!("via {}", Color::Red.bold().paint("🆂 v2.13.5 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_scala_file_no_scala_installed() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("Test.scala"))?.sync_all()?; + let actual = ModuleRenderer::new("scala") + .cmd("scalac -version", None) + .path(dir.path()) + .collect(); + let expected = Some(format!("via {}", Color::Red.bold().paint("🆂 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_sbt_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("build.sbt"))?.sync_all()?; + let actual = ModuleRenderer::new("scala").path(dir.path()).collect(); + let expected = Some(format!("via {}", Color::Red.bold().paint("🆂 v2.13.5 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_scala_env_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join(".scalaenv"))?.sync_all()?; + let actual = ModuleRenderer::new("scala").path(dir.path()).collect(); + let expected = Some(format!("via {}", Color::Red.bold().paint("🆂 v2.13.5 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_sbt_env_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join(".sbtenv"))?.sync_all()?; + let actual = ModuleRenderer::new("scala").path(dir.path()).collect(); + let expected = Some(format!("via {}", Color::Red.bold().paint("🆂 v2.13.5 "))); + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_metals_dir() -> io::Result<()> { + let dir = tempfile::tempdir()?; + fs::create_dir_all(dir.path().join(".metals"))?; + let actual = ModuleRenderer::new("scala").path(dir.path()).collect(); + let expected = Some(format!("via {}", Color::Red.bold().paint("🆂 v2.13.5 "))); + assert_eq!(expected, actual); + dir.close() + } +} diff --git a/src/utils.rs b/src/utils.rs index 352efb84f..3c02ece94 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -86,6 +86,10 @@ Elixir 1.10 (compiled with Erlang/OTP 22)\n", stdout: String::from("OpenJDK 64-Bit Server VM (13.0.2+8) for bsd-amd64 JRE (13.0.2+8), built on Feb 6 2020 02:07:52 by \"brew\" with clang 4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.17)"), stderr: String::default(), }), + "scalac -version" => Some(CommandOutput { + stdout: String::from("Scala compiler version 2.13.5 -- Copyright 2002-2020, LAMP/EPFL and Lightbend, Inc."), + stderr: String::default(), + }), "julia --version" => Some(CommandOutput { stdout: String::from("julia version 1.4.0\n"), stderr: String::default(),