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*(?Phttp[s].*)|user:\s*(?P.*)|org:\s*(?P.*)|space:\s*(?P(.*))`) + 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/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) + } +} 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" + } } ] }