Repository: JanDeDobbeleer/oh-my-posh
Pull request #3649 (2023-03-28 18:15:16)

feat(cftarget): add display_mode
### 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 1335b1f</samp>

Added a `display_mode` property to the `CfTarget` segment to control its visibility based on the existence of a `manifest.yml` file. Updated the segment logic, tests, schema, and documentation accordingly.

### How

<!--
copilot:walkthrough
-->
### <samp>🤖 Generated by Copilot at 1335b1f</samp>

*  Add and document `display_mode` property to `CfTarget` segment ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-ec3dac06620cce64305d2c8de93c5942f80faacbd794e5d356e85d8641170ca4L4-R5), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-ec3dac06620cce64305d2c8de93c5942f80faacbd794e5d356e85d8641170ca4L34-R93), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-892c540c9bfebcb3bb8a0be1714201cd17eb6f0d40367c09f80d56e883b7335eL2866-R2875), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-a222c519961aadf82dd9103d302f58c1fe039c5a4d44c962ed9054cedf35b9c1R24-R29))
  - Check `display_mode` and `manifest.yml` file presence in `Enabled` function ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-ec3dac06620cce64305d2c8de93c5942f80faacbd794e5d356e85d8641170ca4L34-R93))
  - Return error if `cf` command output is empty or fails in `getCFTargetCommandOutput` function ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-ec3dac06620cce64305d2c8de93c5942f80faacbd794e5d356e85d8641170ca4L34-R93))
  - Use `strings.Split` instead of `regexp` to parse `cf` command output in `setCFTargetStatus` function ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-ec3dac06620cce64305d2c8de93c5942f80faacbd794e5d356e85d8641170ca4L34-R93))
  - Add `display_mode` to schema of `CfTarget` segment in `themes/schema.json` ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-892c540c9bfebcb3bb8a0be1714201cd17eb6f0d40367c09f80d56e883b7335eL2866-R2875))
  - Document `display_mode` property in `website/docs/segments/cftarget.mdx` ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-a222c519961aadf82dd9103d302f58c1fe039c5a4d44c962ed9054cedf35b9c1R24-R29))
* Update test cases and setup for `CfTarget` segment ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79R4), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79R10), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79L16-R63), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79L53-R81), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79L64-R90))
  - Add `errors` and `platform` packages to imports in `src/segments/cf_target_test.go` ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79R4), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79R10))
  - Modify test cases to include `DisplayMode`, `FileInfo`, and `CommandError` fields and remove `ExpectedEnabled` field ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79L16-R63))
  - Mock `HasParentFilePath` function and set `DisplayMode` property in test setup ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79L53-R81))
  - Use length of `ExpectedString` field to check `Enabled` function and remove `CommandError` field from fail message in test assertions ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3649/files?diff=unified&w=0#diff-4f6e602c1ef9cc4b72058e94f0300bfdd91cf58556a39aba5ba148f2abb68a79L64-R90))



Patch:
diff --git a/src/segments/cf_target.go b/src/segments/cf_target.go
index 9ef8e0e8..1c1d252f 100644
--- a/src/segments/cf_target.go
+++ b/src/segments/cf_target.go
@@ -1,7 +1,8 @@
 package segments
 
 import (
-	"regexp"
+	"errors"
+	"strings"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/platform"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
@@ -31,54 +32,63 @@ func (c *CfTarget) Init(props properties.Properties, env platform.Environment) {
 }
 
 func (c *CfTarget) Enabled() bool {
-	return c.setCFTargetStatus()
-}
-
-func (c *CfTarget) getCFTargetCommandOutput() string {
 	if !c.env.HasCommand("cf") {
-		return ""
+		return false
 	}
 
-	output, err := c.env.RunCommand("cf", "target")
+	displayMode := c.props.GetString(DisplayMode, DisplayModeAlways)
+	if displayMode != DisplayModeFiles {
+		return c.setCFTargetStatus()
+	}
 
-	if err != nil {
-		return ""
+	manifest, err := c.env.HasParentFilePath("manifest.yml")
+	if err != nil || manifest.IsDir {
+		return false
 	}
 
-	return output
+	return c.setCFTargetStatus()
 }
 
 func (c *CfTarget) setCFTargetStatus() bool {
-	output := c.getCFTargetCommandOutput()
+	output, err := c.getCFTargetCommandOutput()
 
-	if output == "" {
+	if err != nil {
 		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 {
+	lines := strings.Split(output, "\n")
+	for _, line := range lines {
+		splitted := strings.SplitN(line, ":", 2)
+		if len(splitted) < 2 {
 			continue
 		}
+		key := splitted[0]
+		value := strings.TrimSpace(splitted[1])
+		switch key {
+		case "API endpoint":
+			c.URL = value
+		case "user":
+			c.User = value
+		case "org":
+			c.Org = value
+		case "space":
+			c.Space = value
+		}
+	}
 
-		for j, val := range match[i-1] {
-			if j == 0 {
-				continue
-			}
+	return true
+}
 
-			if val != "" {
-				result[name] = val
-			}
-		}
+func (c *CfTarget) getCFTargetCommandOutput() (string, error) {
+	output, err := c.env.RunCommand("cf", "target")
+
+	if err != nil {
+		return "", err
 	}
 
-	c.URL = result["api_url"]
-	c.Org = result["org"]
-	c.Space = result["space"]
-	c.User = result["user"]
+	if len(output) == 0 {
+		return "", errors.New("cf command output is empty")
+	}
 
-	return true
+	return output, nil
 }
diff --git a/themes/schema.json b/themes/schema.json
index 5ae6c43a..517901fe 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -2863,7 +2863,16 @@
           },
           "then": {
             "title": "Clound Foundry Target segment",
-            "description": "https://ohmyposh.dev/docs/segments/cftarget"
+            "description": "https://ohmyposh.dev/docs/segments/cftarget",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "display_mode": {
+                    "$ref": "#/definitions/display_mode"
+                  }
+                }
+              }
+            }
           }
         },
         {
diff --git a/website/docs/segments/cftarget.mdx b/website/docs/segments/cftarget.mdx
index a73d8879..9e21188a 100644
--- a/website/docs/segments/cftarget.mdx
+++ b/website/docs/segments/cftarget.mdx
@@ -21,6 +21,12 @@ Display the details of the logged [Cloud Foundry endpoint][cf-target] (`cf targe
 }
 ```
 
+## 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 `manifest.yml` file is present </li></ul> |
+
 ## Template ([info][templates])
 
 :::note default template


Test patch:
diff --git a/src/segments/cf_target_test.go b/src/segments/cf_target_test.go
index 39047cba..81d435ae 100644
--- a/src/segments/cf_target_test.go
+++ b/src/segments/cf_target_test.go
@@ -1,11 +1,13 @@
 package segments
 
 import (
+	"errors"
 	"fmt"
 	"os/exec"
 	"testing"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/mock"
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
 
 	"github.com/stretchr/testify/assert"
@@ -13,34 +15,51 @@ import (
 
 func TestCFTargetSegment(t *testing.T) {
 	cases := []struct {
-		Case            string
-		Template        string
-		ExpectedString  string
-		ExpectedEnabled bool
-		TargetOutput    string
-		CommandError    error
+		Case           string
+		Template       string
+		ExpectedString string
+		DisplayMode    string
+		FileInfo       *platform.FileInfo
+		TargetOutput   string
+		CommandError   error
 	}{
 		{
-			Case:            "1) not logged in to CF account",
-			ExpectedString:  "",
-			ExpectedEnabled: false,
-			TargetOutput:    `Not logged in`,
-			CommandError:    &exec.ExitError{},
+			Case:         "not logged in to CF account",
+			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:           "logged in, default template",
+			ExpectedString: "12345678trial/dev",
+			TargetOutput:   "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev",
 		},
 		{
-			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,
+			Case: "no output from command",
+		},
+		{
+			Case:           "logged in, full template",
+			Template:       "{{.URL}} {{.User}} {{.Org}} {{.Space}}",
+			ExpectedString: "https://api.cf.eu10.hana.ondemand.com user@some.com 12345678trial dev",
+			TargetOutput:   "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev",
+		},
+		{
+			Case:         "files and no manifest file",
+			DisplayMode:  DisplayModeFiles,
+			TargetOutput: "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev",
+		},
+		{
+			Case:           "files and a manifest file",
+			ExpectedString: "12345678trial/dev",
+			DisplayMode:    DisplayModeFiles,
+			FileInfo:       &platform.FileInfo{},
+			TargetOutput:   "API endpoint: https://api.cf.eu10.hana.ondemand.com\nAPI version: 3.109.0\nuser: user@some.com\norg: 12345678trial\nspace: dev",
+		},
+		{
+			Case:        "files and a manifest directory",
+			DisplayMode: DisplayModeFiles,
+			FileInfo: &platform.FileInfo{
+				IsDir: true,
+			},
 		},
 	}
 
@@ -50,9 +69,16 @@ func TestCFTargetSegment(t *testing.T) {
 		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")
+		var err error
+		if tc.FileInfo == nil {
+			err = errors.New("no such file or directory")
+		}
+		env.On("HasParentFilePath", "manifest.yml").Return(tc.FileInfo, err)
 
 		cfTarget := &CfTarget{}
-		props := properties.Map{}
+		props := properties.Map{
+			DisplayMode: tc.DisplayMode,
+		}
 
 		if tc.Template == "" {
 			tc.Template = cfTarget.Template()
@@ -61,7 +87,7 @@ func TestCFTargetSegment(t *testing.T) {
 		cfTarget.Init(props, env)
 
 		failMsg := fmt.Sprintf("Failed in case: %s", tc.Case)
-		assert.Equal(t, tc.ExpectedEnabled, cfTarget.Enabled(), failMsg)
+		assert.Equal(t, len(tc.ExpectedString) > 0, cfTarget.Enabled(), failMsg)
 		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, cfTarget), failMsg)
 	}
 }