Repository: JanDeDobbeleer/oh-my-posh
Pull request #1796 (2022-02-22 18:59:04)

feat: cloud foundry target 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

closes #1785 

@wozjac this changes `.APIURL` with `.URL`

<!---

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.

-->

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/docs/docs/segment-cftarget.md b/docs/docs/segment-cftarget.md
new file mode 100644
index 00000000..38fcf350
--- /dev/null
+++ b/docs/docs/segment-cftarget.md
@@ -0,0 +1,44 @@
+---
+id: cftarget
+title: Cloud Foundry Target
+sidebar_label: Cloud Foundry Target
+---
+
+## What
+
+Display the details of the logged [Cloud Foundry endpoint][cf-target] (`cf target` details).
+
+## Sample Configuration
+
+```json
+{
+  "background": "#a7cae1",
+  "foreground": "#100e23",
+  "powerline_symbol": "\ue0b0",
+  "properties": {
+    "template": " \uf40a {{ .Org }}/{{ .Space }} "
+  },
+  "style": "powerline",
+  "type": "cftarget"
+}
+```
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+{{if .Org }}{{ .Org }}{{ end }}{{ if .Space }}/{{ .Space }}{{ end }}
+```
+
+:::
+
+## Template Properties
+
+- `.Org`: `string` - Cloud Foundry organization
+- `.Space`: `string` - Cloud Foundry space
+- `.URL`: `string` - Cloud Foundry API URL
+- `.User`: `string` - logged in user
+
+[templates]: /docs/config-templates
+[cf-target]: https://cli.cloudfoundry.org/en-US/v8/target.html
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 8da4355e..5d1901b1 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -47,6 +47,7 @@ module.exports = {
         "command",
         "crystal",
         "cf",
+        "cftarget",
         "dart",
         "dotnet",
         "executiontime",
diff --git a/src/engine/segment.go b/src/engine/segment.go
index 87dc1154..e9019e18 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -156,6 +156,8 @@ const (
 	UI5TOOLING SegmentType = "ui5tooling"
 	// Cloud Foundry segment
 	CF SegmentType = "cf"
+	// Cloud Foundry logged in target
+	CFTARGET SegmentType = "cftarget"
 )
 
 func (segment *Segment) shouldIncludeFolder() bool {
@@ -281,6 +283,7 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error
 		HASKELL:       &segments.Haskell{},
 		UI5TOOLING:    &segments.UI5Tooling{},
 		CF:            &segments.Cf{},
+		CFTARGET:      &segments.CfTarget{},
 	}
 	if segment.Properties == nil {
 		segment.Properties = make(properties.Map)
diff --git a/src/segments/cf_target.go b/src/segments/cf_target.go
new file mode 100644
index 00000000..c93b5462
--- /dev/null
+++ b/src/segments/cf_target.go
@@ -0,0 +1,83 @@
+package segments
+
+import (
+	"oh-my-posh/environment"
+	"oh-my-posh/properties"
+	"regexp"
+)
+
+type CfTarget struct {
+	props properties.Properties
+	env   environment.Environment
+
+	CfTargetDetails
+}
+
+type CfTargetDetails struct {
+	URL   string
+	User  string
+	Org   string
+	Space string
+}
+
+func (c *CfTarget) Template() string {
+	return "{{if .Org }}{{ .Org }}{{ end }}{{if .Space }}/{{ .Space }}{{ end }}"
+}
+
+func (c *CfTarget) Init(props properties.Properties, env environment.Environment) {
+	c.props = props
+	c.env = env
+}
+
+func (c *CfTarget) Enabled() bool {
+	return c.setCFTargetStatus()
+}
+
+func (c *CfTarget) getCFTargetCommandOutput() string {
+	if !c.env.HasCommand("cf") {
+		return ""
+	}
+
+	output, err := c.env.RunCommand("cf", "target")
+
+	if err != nil {
+		return ""
+	}
+
+	return output
+}
+
+func (c *CfTarget) setCFTargetStatus() bool {
+	output := c.getCFTargetCommandOutput()
+
+	if output == "" {
+		return false
+	}
+
+	regex := regexp.MustCompile(`API endpoint:\s*(?P<api_url>http[s].*)|user:\s*(?P<user>.*)|org:\s*(?P<org>.*)|space:\s*(?P<space>(.*))`)
+	match := regex.FindAllStringSubmatch(output, -1)
+	result := make(map[string]string)
+
+	for i, name := range regex.SubexpNames() {
+		if i == 0 || len(name) == 0 {
+			continue
+		}
+
+		for j, val := range match[i-1] {
+			if j == 0 {
+				continue
+			}
+
+			if val != "" {
+				result[name] = val
+			}
+		}
+	}
+
+	c.URL = result["api_url"]
+	c.Org = result["org"]
+	c.Space = result["space"]
+	c.User = result["user"]
+
+	return true
+}
diff --git a/themes/schema.json b/themes/schema.json
index e0c012f8..ff17194b 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -1972,6 +1972,17 @@
               }
             }
           }
+        },
+        {
+          "if": {
+            "properties": {
+              "type": { "const": "cftarget" }
+            }
+          },
+          "then": {
+            "title": "Clound Foundry Target segment",
+            "description": "https://ohmyposh.dev/docs/cftarget"
+          }
         }
       ]
     }


Test patch:
diff --git a/src/segments/cf_target_test.go b/src/segments/cf_target_test.go
new file mode 100644
index 00000000..cf9d25fa
--- /dev/null
+++ b/src/segments/cf_target_test.go
@@ -0,0 +1,66 @@
+package segments
+
+import (
+	"fmt"
+	"oh-my-posh/mock"
+	"oh-my-posh/properties"
+	"os/exec"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCFTargetSegment(t *testing.T) {
+	cases := []struct {
+		Case            string
+		Template        string
+		ExpectedString  string
+		ExpectedEnabled bool
+		TargetOutput    string
+		CommandError    error
+	}{
+		{
+			Case:            "1) not logged in to CF account",
+			ExpectedString:  "",
+			ExpectedEnabled: false,
+			TargetOutput:    `Not logged in`,
+			CommandError:    &exec.ExitError{},
+		},
+		{
+			Case:            "2) logged in, default template",
+			ExpectedString:  "12345678trial/dev",
+			ExpectedEnabled: true,
+			TargetOutput:    "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev",
+			CommandError:    nil,
+		},
+		{
+			Case:            "3) logged in, full template",
+			Template:        "{{.URL}} {{.User}} {{.Org}} {{.Space}}",
+			ExpectedString:  "https://api.cf.eu10.hana.ondemand.com user@some.com 12345678trial dev",
+			ExpectedEnabled: true,
+			TargetOutput:    "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev",
+			CommandError:    nil,
+		},
+	}
+
+	for _, tc := range cases {
+		var env = new(mock.MockedEnvironment)
+		env.On("HasCommand", "cf").Return(true)
+		env.On("RunCommand", "cf", []string{"target"}).Return(tc.TargetOutput, tc.CommandError)
+		env.On("Pwd", nil).Return("/usr/home/dev/my-app")
+		env.On("Home", nil).Return("/usr/home")
+
+		cfTarget := &CfTarget{}
+		props := properties.Map{}
+
+		if tc.Template == "" {
+			tc.Template = cfTarget.Template()
+		}
+
+		cfTarget.Init(props, env)
+
+		failMsg := fmt.Sprintf("Failed in case: %s", tc.Case)
+		assert.Equal(t, tc.ExpectedEnabled, cfTarget.Enabled(), failMsg)
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, cfTarget), failMsg)
+	}
+}