Repository: JanDeDobbeleer/oh-my-posh
Pull request #2661 (2022-08-14 16:13:57)

feat: add Google Cloud (gcp) 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

Adds a Google Cloud (gcp) segment which display several values from your gcloud configuration, namely:
- project
- account
- region



Patch:
diff --git a/src/engine/segment.go b/src/engine/segment.go
index 6ceebd0b..a7b514d5 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -3,14 +3,15 @@ package engine
 import (
 	"errors"
 	"fmt"
+	"runtime/debug"
+	"strings"
+	"time"
+
 	"oh-my-posh/environment"
 	"oh-my-posh/properties"
 	"oh-my-posh/segments"
 	"oh-my-posh/shell"
 	"oh-my-posh/template"
-	"runtime/debug"
-	"strings"
-	"time"
 )
 
 // Segment represent a single segment and it's configuration
@@ -108,6 +109,8 @@ const (
 	FLUTTER SegmentType = "flutter"
 	// FOSSIL writes the fossil status
 	FOSSIL SegmentType = "fossil"
+	// GCP writes the active GCP context
+	GCP SegmentType = "gcp"
 	// GIT represents the git status and information
 	GIT SegmentType = "git"
 	// GOLANG writes which go version is currently active
@@ -281,6 +284,7 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error
 		EXIT:          &segments.Exit{},
 		FLUTTER:       &segments.Flutter{},
 		FOSSIL:        &segments.Fossil{},
+		GCP:           &segments.Gcp{},
 		GIT:           &segments.Git{},
 		GOLANG:        &segments.Golang{},
 		HASKELL:       &segments.Haskell{},
diff --git a/src/segments/gcp.go b/src/segments/gcp.go
new file mode 100644
index 00000000..4081f34f
--- /dev/null
+++ b/src/segments/gcp.go
@@ -0,0 +1,76 @@
+package segments
+
+import (
+	"errors"
+	"oh-my-posh/environment"
+	"oh-my-posh/properties"
+	"path"
+	"path/filepath"
+
+	"gopkg.in/ini.v1"
+)
+
+type Gcp struct {
+	props properties.Properties
+	env   environment.Environment
+
+	Account string
+	Project string
+	Region  string
+	Error   string
+}
+
+func (g *Gcp) Template() string {
+	return " {{ if .Error }}{{ .Error }}{{ else }}{{ .Project }}{{ end }} "
+}
+
+func (g *Gcp) Init(props properties.Properties, env environment.Environment) {
+	g.props = props
+	g.env = env
+}
+
+func (g *Gcp) Enabled() bool {
+	cfgDir := g.getConfigDirectory()
+	configFile, err := g.getActiveConfig(cfgDir)
+	if err != nil {
+		g.Error = err.Error()
+		return true
+	}
+
+	cfgpath := path.Join(cfgDir, "configurations", "config_"+configFile)
+
+	cfg, err := ini.Load(cfgpath)
+	if err != nil {
+		g.Error = "GCLOUD CONFIG ERROR"
+		return true
+	}
+
+	g.Project = cfg.Section("core").Key("project").String()
+	g.Account = cfg.Section("core").Key("account").String()
+	g.Region = cfg.Section("compute").Key("region").String()
+
+	return true
+}
+
+func (g *Gcp) getActiveConfig(cfgDir string) (string, error) {
+	ap := path.Join(cfgDir, "active_config")
+	absolutePath, err := filepath.Abs(ap)
+	if err != nil {
+		return "", err
+	}
+
+	fileContent := g.env.FileContent(absolutePath)
+	if len(fileContent) == 0 {
+		return "", errors.New("NO ACTIVE CONFIG FOUND")
+	}
+	return fileContent, nil
+}
+
+func (g *Gcp) getConfigDirectory() string {
+	cfgDir := g.env.Getenv("CLOUDSDK_CONFIG")
+	if len(cfgDir) == 0 {
+		cfgDir = path.Join(g.env.Home(), ".config", "gcloud")
+	}
+
+	return cfgDir
+}
diff --git a/themes/schema.json b/themes/schema.json
index 76e15734..3b69077a 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -236,6 +236,7 @@
             "executiontime",
             "flutter",
             "fossil",
+            "gcp",
             "git",
             "go",
             "haskell",
@@ -2726,6 +2727,19 @@
               }
             }
           }
+        },
+        {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "gcp"
+              }
+            }
+          },
+          "then": {
+            "title": "GCP Segment",
+            "description": "https://ohmyposh.dev/docs/segments/gcp"
+          }
         }
       ]
     }
diff --git a/website/docs/segments/gcp.mdx b/website/docs/segments/gcp.mdx
new file mode 100644
index 00000000..6be8deb5
--- /dev/null
+++ b/website/docs/segments/gcp.mdx
@@ -0,0 +1,41 @@
+---
+id: gcp
+title: GCP Context
+sidebar_label: GCP
+---
+
+## What
+
+Display the currently active GCP project, region and account
+
+## Sample Configuration
+
+```json
+{
+  "type": "gcp",
+  "style": "powerline",
+  "powerline_symbol": "\uE0B0",
+  "foreground": "#ffffff",
+  "background": "#47888d",
+  "template": " \uE7B2 {{.Project} :: {{.Account}} "
+}
+```
+
+## Template ([info][templates])
+
+:::note default template
+
+``` template
+{{ if .Error }}{{ .Error }}{{ else }}{{ .Project }}{{ end }}
+```
+
+:::
+
+### Properties
+
+- `.Project`: `string` - the currently active project
+- `.Account`: `string` - the currently active account
+- `.Region`: `string` - default region for the active context
+- `.Error`: `string` - contains any error messages generated when trying to load the GCP config
+
+[templates]: /docs/configuration/templates
diff --git a/website/sidebars.js b/website/sidebars.js
index 1e5397a7..ae9f0183 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -69,6 +69,7 @@ module.exports = {
         "segments/exit",
         "segments/flutter",
         "segments/fossil",
+        "segments/gcp",
         "segments/git",
         "segments/poshgit",
         "segments/golang",


Test patch:
diff --git a/src/segments/gcp_test.go b/src/segments/gcp_test.go
new file mode 100644
index 00000000..46d33b56
--- /dev/null
+++ b/src/segments/gcp_test.go
@@ -0,0 +1,62 @@
+package segments
+
+import (
+	"path"
+	"path/filepath"
+	"testing"
+
+	"oh-my-posh/mock"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGcpSegment(t *testing.T) {
+	standardTemplate := "{{ if .Error }}{{ .Error }}{{ else }}{{ .Project }}{{ end }}"
+	allTemplate := "{{.Project}} :: {{.Region}} :: {{.Account}}"
+
+	cases := []struct {
+		Case            string
+		Template        string
+		ConfigPath      string
+		ActiveConfig    string
+		ExpectedEnabled bool
+		ExpectedString  string
+	}{
+		{
+			Case:            "all information",
+			Template:        allTemplate,
+			ConfigPath:      "../test/",
+			ActiveConfig:    "gcptest",
+			ExpectedEnabled: true,
+			ExpectedString:  "test-test-test :: europe-test1 :: test@example.com",
+		},
+		{
+			Case:            "non-existent config file",
+			Template:        standardTemplate,
+			ConfigPath:      "../invalid/",
+			ActiveConfig:    "nofile",
+			ExpectedEnabled: true,
+			ExpectedString:  "GCLOUD CONFIG ERROR",
+		},
+		{
+			Case:            "invalid active config file",
+			Template:        standardTemplate,
+			ConfigPath:      "../invalid/",
+			ActiveConfig:    "",
+			ExpectedEnabled: true,
+			ExpectedString:  "NO ACTIVE CONFIG FOUND",
+		},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		env.On("Getenv", "CLOUDSDK_CONFIG").Return(tc.ConfigPath)
+		fcPath, _ := filepath.Abs(path.Join(tc.ConfigPath, "active_config"))
+		env.On("FileContent", fcPath).Return(tc.ActiveConfig)
+		g := &Gcp{
+			env: env,
+		}
+		assert.Equal(t, tc.ExpectedEnabled, g.Enabled(), tc.Case)
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, g), tc.Case)
+	}
+}
diff --git a/src/test/configurations/config_gcptest b/src/test/configurations/config_gcptest
new file mode 100644
index 00000000..e54e6df9
--- /dev/null
+++ b/src/test/configurations/config_gcptest
@@ -0,0 +1,6 @@
+[core]
+account = test@example.com
+project = test-test-test
+
+[compute]
+region = europe-test1