Repository: JanDeDobbeleer/oh-my-posh
Pull request #1765 (2022-02-16 16:37:29)

feat: cloud foundry segment
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [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
A segment for Cloud Foundry CLI - https://github.com/cloudfoundry/cli
In files mode it is based on presence of "manifest.yml" file, which is the default config file, used by cloud foundry commands like "cf push" (https://docs.cloudfoundry.org/devguide/deploy-apps/manifest.html).
The additional file "mta.yaml" is a SAP-specific, it is for Multi Target Application and can be used instead of manifest.yml in SAP specific scenarios for building and deploying. 

Best Regards,
Jacek


Patch:
diff --git a/docs/docs/segment-cf.md b/docs/docs/segment-cf.md
new file mode 100644
index 00000000..af0ffd85
--- /dev/null
+++ b/docs/docs/segment-cf.md
@@ -0,0 +1,57 @@
+---
+id: cf
+title: Cloud Foundry
+sidebar_label: Cloud Foundry
+---
+
+## What
+
+Display the active [Cloud Foundry CLI][cloud-foundry] version.
+
+## Sample Configuration
+
+```json
+{
+  "background": "#a7cae1",
+  "foreground": "#100e23",
+  "powerline_symbol": "\ue0b0",
+  "properties": {
+    "template": " \uf40a cf {{ .Full }} "
+  },
+  "style": "powerline",
+  "type": "cf"
+}
+```
+
+## Properties
+
+- home_enabled: `boolean` - display the segment in the HOME folder or not - defaults to `false`
+- fetch_version: `boolean` - display the UI5 tooling version - defaults to `true`
+- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true`
+- missing_command_text: `string` - text to display when the java command is missing - defaults to empty
+- display_mode: `string` - determines when the segment is displayed
+  - `always`: the segment is always displayed
+  - `files`: the segment is displayed when `manifest.yml` or `mta.yaml` file is present (default)
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+{{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }}
+```
+
+:::
+
+## Template Properties
+
+- `.Full`: `string` - the full version
+- `.Major`: `string` - major number
+- `.Minor`: `string` - minor number
+- `.Patch`: `string` - patch number
+- `.Prerelease`: `string` - prerelease info text
+- `.BuildMetadata`: `string` - build metadata
+- `.Error`: `string` - when fetching the version string errors
+
+[templates]: /docs/config-templates
+[cloud-foundry]: https://github.com/cloudfoundry/cli
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 5b248084..945a972f 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -28,7 +28,7 @@ module.exports = {
         "config-templates",
         "config-transient",
         "config-tooltips",
-        "config-fonts"
+        "config-fonts",
       ],
     },
     {
@@ -44,6 +44,7 @@ module.exports = {
         "brewfather",
         "command",
         "crystal",
+        "cf",
         "dart",
         "dotnet",
         "executiontime",
diff --git a/src/engine/segment.go b/src/engine/segment.go
index f7ab8f11..a7e636d6 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -151,6 +151,8 @@ const (
 	HASKELL SegmentType = "haskell"
 	// UI5 Tooling segment
 	UI5TOOLING SegmentType = "ui5tooling"
+	// Cloud Foundry segment
+	CF SegmentType = "cf"
 )
 
 func (segment *Segment) shouldIncludeFolder() bool {
@@ -269,6 +271,7 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error
 		IPIFY:         &segments.IPify{},
 		HASKELL:       &segments.Haskell{},
 		UI5TOOLING:    &segments.UI5Tooling{},
+		CF:            &segments.Cf{},
 	}
 	if segment.Properties == nil {
 		segment.Properties = make(properties.Map)
diff --git a/src/segments/cf.go b/src/segments/cf.go
new file mode 100644
index 00000000..28ac3912
--- /dev/null
+++ b/src/segments/cf.go
@@ -0,0 +1,35 @@
+package segments
+
+import (
+	"oh-my-posh/environment"
+	"oh-my-posh/properties"
+)
+
+type Cf struct {
+	language
+}
+
+func (c *Cf) Template() string {
+	return languageTemplate
+}
+
+func (c *Cf) Init(props properties.Properties, env environment.Environment) {
+	c.language = language{
+		env:        env,
+		props:      props,
+		extensions: []string{"manifest.yml", "mta.yaml"},
+		commands: []*cmd{
+			{
+				executable: "cf",
+				args:       []string{"version"},
+				regex:      `(?:(?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))))`,
+			},
+		},
+		displayMode:        props.GetString(DisplayMode, DisplayModeFiles),
+		versionURLTemplate: "https://github.com/cloudfoundry/cli/releases/tag/v{{ .Full }}",
+	}
+}
+
+func (c *Cf) Enabled() bool {
+	return c.language.Enabled()
+}
diff --git a/themes/schema.json b/themes/schema.json
index b89c436a..4824cf36 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -1934,6 +1934,32 @@
               }
             }
           }
+        },
+        {
+          "if": {
+            "properties": {
+              "type": { "const": "cf" }
+            }
+          },
+          "then": {
+            "title": "Clound Foundry CLI segment",
+            "description": "https://ohmyposh.dev/docs/cf",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "fetch_version": {
+                    "$ref": "#/definitions/fetch_version"
+                  },
+                  "display_mode": {
+                    "$ref": "#/definitions/display_mode"
+                  },
+                  "missing_command_text": {
+                    "$ref": "#/definitions/missing_command_text"
+                  }
+                }
+              }
+            }
+          }
         }
       ]
     }


Test patch:
diff --git a/src/segments/cf_test.go b/src/segments/cf_test.go
new file mode 100644
index 00000000..1392d885
--- /dev/null
+++ b/src/segments/cf_test.go
@@ -0,0 +1,100 @@
+package segments
+
+import (
+	"fmt"
+	"oh-my-posh/environment"
+	"oh-my-posh/mock"
+	"oh-my-posh/properties"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCFSegment(t *testing.T) {
+	cases := []struct {
+		Case            string
+		Template        string
+		ExpectedString  string
+		ExpectedEnabled bool
+		CfYamlFile      string
+		Version         string
+		DisplayMode     string
+	}{
+		{
+			Case:            "1) cf 2.12.1 - file manifest.yml",
+			ExpectedString:  "2.12.1",
+			ExpectedEnabled: true,
+			CfYamlFile:      "manifest.yml",
+			Version:         `cf.exe version 2.12.1+645c3ce6a.2021-08-16`,
+			DisplayMode:     DisplayModeFiles,
+		},
+		{
+			Case:            "2) cf 11.0.0-rc1 - file mta.yaml",
+			Template:        "{{ .Major }}",
+			ExpectedString:  "11",
+			ExpectedEnabled: true,
+			CfYamlFile:      "mta.yaml",
+			Version:         `cf version 11.0.0-rc1`,
+			DisplayMode:     DisplayModeFiles,
+		},
+		{
+			Case:            "3) cf 11.0.0-rc1 - no file",
+			Template:        "{{ .Major }}",
+			ExpectedString:  "",
+			ExpectedEnabled: false,
+			Version:         `cf version 11.0.0-rc1`,
+			DisplayMode:     DisplayModeFiles,
+		},
+		{
+			Case:            "4) cf 11.1.0-rc1 - mode always",
+			Template:        "{{ .Major }}.{{ .Minor }}",
+			ExpectedString:  "11.1",
+			ExpectedEnabled: true,
+			Version:         `cf.exe version 11.1.0-rc1`,
+			DisplayMode:     DisplayModeAlways,
+		},
+	}
+
+	for _, tc := range cases {
+		var env = new(mock.MockedEnvironment)
+		env.On("HasCommand", "cf").Return(true)
+		env.On("RunCommand", "cf", []string{"version"}).Return(tc.Version, nil)
+		env.On("Pwd").Return("/usr/home/dev/my-app")
+		env.On("Home").Return("/usr/home")
+
+		env.On("TemplateCache").Return(&environment.TemplateCache{
+			Env: make(map[string]string),
+		})
+
+		cf := &Cf{}
+
+		props := properties.Map{
+			DisplayMode: tc.DisplayMode,
+		}
+
+		if tc.Template == "" {
+			tc.Template = cf.Template()
+		}
+
+		cf.Init(props, env)
+
+		for _, f := range cf.language.extensions {
+			match, err := filepath.Match(f, tc.CfYamlFile)
+
+			if err != nil {
+				t.Fail()
+			}
+
+			if match {
+				env.On("HasFiles", f).Return(true)
+			} else {
+				env.On("HasFiles", f).Return(false)
+			}
+		}
+
+		failMsg := fmt.Sprintf("Failed in case: %s", tc.Case)
+		assert.Equal(t, tc.ExpectedEnabled, cf.Enabled(), failMsg)
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, cf), failMsg)
+	}
+}