From 351bf9d0b382adcc3e073c1a293fd815bb623f37 Mon Sep 17 00:00:00 2001 From: marcybell Date: Thu, 1 Jun 2023 22:18:38 +0300 Subject: [PATCH] feat(golang): adding `mod_version` variable (#5177) --- .github/config-schema.json | 5 ++ docs/config/README.md | 41 +++++++++----- src/configs/go.rs | 2 + src/modules/golang.rs | 109 +++++++++++++++++++++++++++++++++++-- 4 files changed, 138 insertions(+), 19 deletions(-) diff --git a/.github/config-schema.json b/.github/config-schema.json index 6bc08e47..20e4fa3b 100644 --- a/.github/config-schema.json +++ b/.github/config-schema.json @@ -649,6 +649,7 @@ ], "disabled": false, "format": "via [$symbol($version )]($style)", + "not_capable_style": "bold red", "style": "bold cyan", "symbol": "🐹 ", "version_format": "v${raw}" @@ -3372,6 +3373,10 @@ "default": false, "type": "boolean" }, + "not_capable_style": { + "default": "bold red", + "type": "string" + }, "detect_extensions": { "default": [ "go" diff --git a/docs/config/README.md b/docs/config/README.md index b80f0fed..d75c805d 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -1972,24 +1972,26 @@ By default the module will be shown if any of the following conditions are met: ### Options -| Option | Default | Description | -| ------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| `format` | `'via [$symbol($version )]($style)'` | The format for the module. | -| `version_format` | `'v${raw}'` | The version format. Available vars are `raw`, `major`, `minor`, & `patch` | -| `symbol` | `'🐹 '` | A format string representing the symbol of Go. | -| `detect_extensions` | `['go']` | Which extensions should trigger this module. | -| `detect_files` | `['go.mod', 'go.sum', 'go.work', 'glide.yaml', 'Gopkg.yml', 'Gopkg.lock', '.go-version']` | Which filenames should trigger this module. | -| `detect_folders` | `['Godeps']` | Which folders should trigger this module. | -| `style` | `'bold cyan'` | The style for the module. | -| `disabled` | `false` | Disables the `golang` module. | +| Option | Default | Description | +| ------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | +| `format` | `'via [$symbol($version )]($style)'` | The format for the module. | +| `version_format` | `'v${raw}'` | The version format. Available vars are `raw`, `major`, `minor`, & `patch` | +| `symbol` | `'🐹 '` | A format string representing the symbol of Go. | +| `detect_extensions` | `['go']` | Which extensions should trigger this module. | +| `detect_files` | `['go.mod', 'go.sum', 'go.work', 'glide.yaml', 'Gopkg.yml', 'Gopkg.lock', '.go-version']` | Which filenames should trigger this module. | +| `detect_folders` | `['Godeps']` | Which folders should trigger this module. | +| `style` | `'bold cyan'` | The style for the module. | +| `not_capable_style` | `'bold red'` | The style for the module when the go directive in the go.mod file does not match the installed Go version. | +| `disabled` | `false` | Disables the `golang` module. | ### Variables -| Variable | Example | Description | -| -------- | --------- | ------------------------------------ | -| version | `v1.12.1` | The version of `go` | -| symbol | | Mirrors the value of option `symbol` | -| style\* | | Mirrors the value of option `style` | +| Variable | Example | Description | +| ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| version | `v1.12.1` | The version of `go` | +| mod_version | `1.16` | `go` version requirement as set in the go directive of `go.mod`. Will only show if the version requirement does not match the `go` version. | +| 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 @@ -2002,6 +2004,15 @@ By default the module will be shown if any of the following conditions are met: format = 'via [🏎💨 $version](bold cyan) ' ``` +### Using `mod_version` + +```toml +# ~/.config/starship.toml + +[golang] +format = 'via [$symbol($version )($mod_version )]($style)' +``` + ## Guix-shell The `guix_shell` module shows the [guix-shell](https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-shell.html) environment. diff --git a/src/configs/go.rs b/src/configs/go.rs index 2b4adebb..8aaa83dc 100644 --- a/src/configs/go.rs +++ b/src/configs/go.rs @@ -13,6 +13,7 @@ pub struct GoConfig<'a> { pub symbol: &'a str, pub style: &'a str, pub disabled: bool, + pub not_capable_style: &'a str, pub detect_extensions: Vec<&'a str>, pub detect_files: Vec<&'a str>, pub detect_folders: Vec<&'a str>, @@ -26,6 +27,7 @@ impl<'a> Default for GoConfig<'a> { symbol: "🐹 ", style: "bold cyan", disabled: false, + not_capable_style: "bold red", detect_extensions: vec!["go"], detect_files: vec![ "go.mod", diff --git a/src/modules/golang.rs b/src/modules/golang.rs index 52c65a67..ee0f5095 100644 --- a/src/modules/golang.rs +++ b/src/modules/golang.rs @@ -4,6 +4,12 @@ use crate::configs::go::GoConfig; use crate::formatter::StringFormatter; use crate::formatter::VersionFormatter; +use once_cell::sync::Lazy; +use regex::Regex; +use semver::Version; +use semver::VersionReq; +use std::ops::Deref; + /// Creates a module with the current Go version pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("golang"); @@ -19,6 +25,10 @@ pub fn module<'a>(context: &'a Context) -> Option> { return None; } + let golang_version = + Lazy::new(|| parse_go_version(&context.exec_cmd("go", &["version"])?.stdout)); + let mod_version = Lazy::new(|| get_go_mod_version(context)); + let parsed = StringFormatter::new(config.format).and_then(|formatter| { formatter .map_meta(|var, _| match var { @@ -26,21 +36,36 @@ pub fn module<'a>(context: &'a Context) -> Option> { _ => None, }) .map_style(|variable| match variable { - "style" => Some(Ok(config.style)), + "style" => { + let in_mod_range = + check_go_version(golang_version.as_deref(), mod_version.as_deref()); + + if in_mod_range { + Some(Ok(config.style)) + } else { + Some(Ok(config.not_capable_style)) + } + } _ => None, }) .map(|variable| match variable { "version" => { - let golang_version = - parse_go_version(&context.exec_cmd("go", &["version"])?.stdout)?; + let go_ver = golang_version.deref().as_ref()?; VersionFormatter::format_module_version( module.get_name(), - &golang_version, + go_ver, config.version_format, ) .map(Ok) } + "mod_version" => { + let in_mod_range = + check_go_version(golang_version.as_deref(), mod_version.as_deref()); + let mod_ver = mod_version.as_deref()?.to_string(); + + (!in_mod_range).then_some(Ok(mod_ver)) + } _ => None, }) .parse(None, Some(context)) @@ -74,6 +99,32 @@ fn parse_go_version(go_stdout: &str) -> Option { Some(version.to_string()) } +fn get_go_mod_version(context: &Context) -> Option { + let mod_str = context.read_file_from_pwd("go.mod")?; + let re = Regex::new(r"(?:go\s)(\d+(\.\d+)+)").unwrap(); + + if let Some(cap) = re.captures(&mod_str) { + let mod_ver = cap.get(1)?.as_str(); + Some(mod_ver.to_string()) + } else { + None + } +} + +fn check_go_version(go_version: Option<&str>, mod_version: Option<&str>) -> bool { + let (Some(go_version), Some(mod_version)) = (go_version, mod_version) else { + return true; + }; + let Ok(r) = VersionReq::parse(mod_version) else { + return true; + }; + let Ok(v) = Version::parse(go_version) else { + return true; + }; + + r.matches(&v) +} + #[cfg(test)] mod tests { use super::*; @@ -81,6 +132,7 @@ mod tests { use nu_ansi_term::Color; use std::fs::{self, File}; use std::io; + use std::io::Write; #[test] fn folder_without_go_files() -> io::Result<()> { @@ -203,4 +255,53 @@ mod tests { let input = "go version go1.12 darwin/amd64"; assert_eq!(parse_go_version(input), Some("1.12".to_string())); } + + #[test] + fn show_mod_version_if_not_matching_go_version() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let mut file = File::create(dir.path().join("go.mod"))?; + file.write_all( + b"package test\n\n + go 1.16", + )?; + file.sync_all()?; + + let actual = ModuleRenderer::new("golang") + .path(dir.path()) + .config(toml::toml! { + [golang] + format = "via [$symbol($version )($mod_version )]($style)" + }) + .collect(); + let expected = Some(format!( + "via {}", + Color::Red.bold().paint("🐹 v1.12.1 1.16 ") + )); + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn hide_mod_version_when_it_matches_go_version() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let mut file = File::create(dir.path().join("go.mod"))?; + file.write_all( + b"package test\n\n + go 1.12", + )?; + file.sync_all()?; + + let actual = ModuleRenderer::new("golang") + .path(dir.path()) + .config(toml::toml! { + [golang] + format = "via [$symbol($version )($mod_version )]($style)" + }) + .collect(); + let expected = Some(format!("via {}", Color::Cyan.bold().paint("🐹 v1.12.1 "))); + + assert_eq!(expected, actual); + dir.close() + } }