Repository: JanDeDobbeleer/oh-my-posh
Pull request #4221 (2023-09-03 14:11:30)

feat(helm): Add helm segment
### Prerequisites

- [X] I have read and understood the [contributing guide][CONTRIBUTING.md].
- [X] The commit message follows the [conventional commits][cc] guidelines.
- [X] Tests for the changes have been added (for bug fixes / features).
- [X] Docs have been added/updated (for bug fixes / features).

### Description

<!--
copilot:summary
-->
### <samp>🤖 Generated by Copilot at 4fbb2b3</samp>

This pull request adds a new segment type `helm` to the oh-my-posh engine and theme configuration, along with tests and documentation. The `helm` segment displays information about the helm version and the current chart directory. The pull request modifies the `src/engine/segment.go`, `themes/schema.json`, and `website/sidebars.js` files, and adds new files `src/segments/helm.go`, `src/segments/helm_test.go`, and `website/docs/segments/helm.mdx`.

### How

<!--
copilot:walkthrough
-->
### <samp>🤖 Generated by Copilot at 4fbb2b3</samp>

*  Add a new `helm` segment type to display the current helm chart and version ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-cd158856c1b1aa6b2acece8041eb07352e4247b700e0fc62798a6be35d5f1c7dR153-R154), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-cd158856c1b1aa6b2acece8041eb07352e4247b700e0fc62798a6be35d5f1c7dR285), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-892c540c9bfebcb3bb8a0be1714201cd17eb6f0d40367c09f80d56e883b7335eR275), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-892c540c9bfebcb3bb8a0be1714201cd17eb6f0d40367c09f80d56e883b7335eR2874-R2895), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-4caaf3187919c98f42856c140e32964b0829fc19165f3c989a6cd4ea088a7164R82))
*  Implement the `Helm` struct and its methods in the `segments` package ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-df1a97cbd278b2de3196d27854a9fe46b8ed1312ad6ce675615eeebb73f9b5bfR1-R57))
*  Test the `Helm` segment using a mocked environment and different display modes ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-83c2d5483b7b8b45a357a88965a7bd9c2d2f1e2913296cef011f1582f94a44c6R1-R85))
*  Document the `helm` segment and its properties in the website ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/4221/files?diff=unified&w=0#diff-fbf266bf8ee43801cb5840ee127d3c5ea58f19b9ab4d5d6fd659aa90430479d0R1-R36))

<!---

Tips:

If you're not comfortable with working with Git,
we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/src/engine/segment.go b/src/engine/segment.go
index 43cb83f5..26f76ef2 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -150,6 +150,8 @@ const (
 	GOLANG SegmentType = "go"
 	// HASKELL segment
 	HASKELL SegmentType = "haskell"
+	// HELM segment
+	HELM SegmentType = "helm"
 	// IPIFY segment
 	IPIFY SegmentType = "ipify"
 	// ITERM inserts the Shell Integration prompt mark on iTerm zsh/bash/fish
@@ -280,6 +282,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
 	GITVERSION:    func() SegmentWriter { return &segments.GitVersion{} },
 	GOLANG:        func() SegmentWriter { return &segments.Golang{} },
 	HASKELL:       func() SegmentWriter { return &segments.Haskell{} },
+	HELM:          func() SegmentWriter { return &segments.Helm{} },
 	IPIFY:         func() SegmentWriter { return &segments.IPify{} },
 	ITERM:         func() SegmentWriter { return &segments.ITerm{} },
 	JAVA:          func() SegmentWriter { return &segments.Java{} },
diff --git a/src/segments/helm.go b/src/segments/helm.go
new file mode 100644
index 00000000..9df1e745
--- /dev/null
+++ b/src/segments/helm.go
@@ -0,0 +1,55 @@
+package segments
+
+import (
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+)
+
+type Helm struct {
+	props properties.Properties
+	env   platform.Environment
+
+	Version string
+}
+
+func (h *Helm) Enabled() bool {
+	displayMode := h.props.GetString(DisplayMode, DisplayModeAlways)
+	if displayMode != DisplayModeFiles {
+		return h.getVersion()
+	}
+
+	inChart := false
+	files := []string{"Chart.yml", "Chart.yaml"}
+	for _, file := range files {
+		if _, err := h.env.HasParentFilePath(file); err == nil {
+			inChart = true
+			break
+		}
+	}
+
+	return inChart && h.getVersion()
+}
+
+func (h *Helm) Template() string {
+	return " Helm {{.Version}}"
+}
+
+func (h *Helm) Init(props properties.Properties, env platform.Environment) {
+	h.props = props
+	h.env = env
+}
+
+func (h *Helm) getVersion() bool {
+	cmd := "helm"
+	if !h.env.HasCommand(cmd) {
+		return false
+	}
+
+	result, err := h.env.RunCommand(cmd, "version", "--short", "--template={{.Version}}")
+	if err != nil {
+		return false
+	}
+
+	h.Version = result[1:]
+	return true
+}
diff --git a/themes/schema.json b/themes/schema.json
index d8f41a5d..3b70bbb3 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -272,6 +272,7 @@
             "gitversion",
             "go",
             "haskell",
+            "helm",
             "ipify",
             "iterm",
             "julia",
@@ -2866,6 +2867,28 @@
             }
           }
         },
+        {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "helm"
+              }
+            }
+          },
+          "then": {
+            "title": "Helm segment",
+            "description": "https://ohmyposh.dev/docs/segments/helm",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "display_mode": {
+                    "$ref": "#/definitions/display_mode"
+                  }
+                }
+              }
+            }
+          }
+        },
         {
           "if": {
             "properties": {
diff --git a/website/docs/segments/helm.mdx b/website/docs/segments/helm.mdx
new file mode 100644
index 00000000..3edff120
--- /dev/null
+++ b/website/docs/segments/helm.mdx
@@ -0,0 +1,46 @@
+---
+id: helm
+title: Helm
+sidebar_label: Helm
+---
+
+## What
+
+Display the version of helm
+
+## Sample Configuration
+
+import Config from '@site/src/components/Config.js';
+
+<Config data={{
+  "background": "#a7cae1",
+  "foreground": "#100e23",
+  "powerline_symbol": "\ue0b0",
+  "template": " Helm {{ .Version }}",
+  "style": "powerline",
+  "type": "helm"
+}}/>
+
+## Properties
+
+| Name           | Type     | Description                                                                                                                                                                      |
+| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `display_mode` | `string` | <ul><li>`always`: the segment is always displayed (**default**)</li><li>`files`: the segment is only displayed when a `Chart.yaml` (or `Chart.yml`) file is present </li></ul>   |
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+ Helm {{ .Version }}
+```
+
+:::
+
+### Properties
+
+| Name         | Type     | Description                |
+| ------------ | -------- | -------------------------- |
+| `.Version`   | `string` | Helm cli version           |
+
+[templates]: /docs/configuration/templates
diff --git a/website/sidebars.js b/website/sidebars.js
index c0d2d845..22bfabad 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -79,6 +79,7 @@ module.exports = {
         "segments/gitversion",
         "segments/golang",
         "segments/haskell",
+        "segments/helm",
         "segments/ipify",
         "segments/iterm",
         "segments/java",


Test patch:
diff --git a/src/segments/helm_test.go b/src/segments/helm_test.go
new file mode 100644
index 00000000..83e5d4a3
--- /dev/null
+++ b/src/segments/helm_test.go
@@ -0,0 +1,89 @@
+package segments
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	testify_mock "github.com/stretchr/testify/mock"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/mock"
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+)
+
+func TestHelmSegment(t *testing.T) {
+	cases := []struct {
+		Case            string
+		HelmExists      bool
+		ExpectedEnabled bool
+		ExpectedString  string
+		Template        string
+		DisplayMode     string
+		ChartFile       string
+	}{
+		{
+			Case:            "Helm not installed",
+			HelmExists:      false,
+			ExpectedEnabled: false,
+		},
+		{
+			Case:            "DisplayMode always inside chart",
+			HelmExists:      true,
+			ExpectedEnabled: true,
+			ExpectedString:  "Helm 3.12.3",
+			DisplayMode:     "always",
+		},
+		{
+			Case:            "DisplayMode always outside chart",
+			HelmExists:      true,
+			ExpectedEnabled: true,
+			ExpectedString:  "Helm 3.12.3",
+			DisplayMode:     "always",
+		},
+		{
+			Case:            "DisplayMode files inside chart. Chart file Chart.yml",
+			HelmExists:      true,
+			ExpectedEnabled: true,
+			ExpectedString:  "Helm 3.12.3",
+			DisplayMode:     "files",
+			ChartFile:       "Chart.yml",
+		},
+		{
+			Case:            "DisplayMode always inside chart. Chart file Chart.yaml",
+			HelmExists:      true,
+			ExpectedEnabled: true,
+			ExpectedString:  "Helm 3.12.3",
+			DisplayMode:     "files",
+			ChartFile:       "Chart.yaml",
+		},
+		{
+			Case:            "DisplayMode always outside chart",
+			HelmExists:      true,
+			ExpectedEnabled: false,
+			DisplayMode:     "files",
+		},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		env.On("HasCommand", "helm").Return(tc.HelmExists)
+		env.On("RunCommand", "helm", []string{"version", "--short", "--template={{.Version}}"}).Return("v3.12.3", nil)
+
+		env.On("HasParentFilePath", tc.ChartFile).Return(&platform.FileInfo{}, nil)
+		env.On("HasParentFilePath", testify_mock.Anything).Return(&platform.FileInfo{}, errors.New("no such file or directory"))
+
+		props := properties.Map{
+			DisplayMode: tc.DisplayMode,
+		}
+
+		h := &Helm{
+			env:   env,
+			props: props,
+		}
+		assert.Equal(t, tc.ExpectedEnabled, h.Enabled(), tc.Case)
+		if tc.ExpectedEnabled {
+			assert.Equal(t, tc.ExpectedString, renderTemplate(env, h.Template(), h), tc.Case)
+		}
+	}
+}