Repository: JanDeDobbeleer/oh-my-posh
Pull request #3789 (2023-05-03 19:46:38)

feat: new Sitecore context 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

<!--
copilot:summary
-->
### <samp>🤖 Generated by Copilot at b459b39</samp>

This pull request adds a new Sitecore segment for oh-my-posh that displays the current Sitecore environment. It implements the segment writer, unit tests, schema definition, and documentation for the segment. It also updates the segment type enum, the segment writers map, and the website sidebar.

### How

<!--
copilot:walkthrough
-->
### <samp>🤖 Generated by Copilot at b459b39</samp>

*  Add a new Sitecore segment to the engine package that shows the current active Sitecore environment ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-cd158856c1b1aa6b2acece8041eb07352e4247b700e0fc62798a6be35d5f1c7dR214-R215), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-cd158856c1b1aa6b2acece8041eb07352e4247b700e0fc62798a6be35d5f1c7dR310), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-e4e61b9f252fb291a7129580c917d7dfe5178f2d12c9bdb8c3a28a0fdcfe802dR1-R96))
*  Implement the `Sitecore` struct that reads and parses the Sitecore configuration file, determines the environment name and icon, and renders the segment template ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-e4e61b9f252fb291a7129580c917d7dfe5178f2d12c9bdb8c3a28a0fdcfe802dR1-R96))
*  Add unit tests for the `Sitecore` segment writer using different test cases and mock environments in the `sitecore_test.go` file ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-7ba0e717460746e6e69e829d8abb6927bc2834beb817f3da6948f25f2285b80dR1-R98))
*  Add sample Sitecore configuration files for cloud and local environments in the `test` folder ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-a10ba0ebeaa3f838d54d75fd27ad4cd663cd4d9d397760f37e83822161d263abR1-R10), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-43a45154d722fb1c0abe825cb3b3e23a6a2ca974455629051e98f0b982bf47c6R1-R10))
*  Add a schema definition for the Sitecore segment to the `themes/schema.json` file that specifies the title, description, properties, and default values for the segment ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-892c540c9bfebcb3bb8a0be1714201cd17eb6f0d40367c09f80d56e883b7335eR1934-R1964))
*  Add documentation for the Sitecore segment in the `sitecore.mdx` file that includes the segment description, sample configuration, properties, and template information ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-3af710d4f9882cc603875c32893b1e701198ef8218f9be38d2a4ffb3a1348819R1-R62))
*  Add the Sitecore segment to the sidebar navigation for the website documentation in the `sidebars.js` file ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3789/files?diff=unified&w=0#diff-4caaf3187919c98f42856c140e32964b0829fc19165f3c989a6cd4ea088a7164R111))

<!---

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.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary


BasLijten: Is there any action required from my side? For some reason I do see a conflicting change on a file that I even didn't touch ;)
JanDeDobbeleer: @BasLijten I will have a look over the weekend. The conflict is because I force pushed main 😅
BasLijten: > @BasLijten I will have a look over the weekend. The conflict is because I force pushed main 😅

great! I'll wait patiently
JanDeDobbeleer: @BasLijten changed the property names and fixed the conflicts. If you're OK with this, I'll merge.
BasLijten: > @BasLijten changed the property names and fixed the conflicts. If you're OK with this, I'll merge.

Thanks! I am ok with it. Thanks for spending time on the segment and my inability to correctly work with git ;)
BasLijten: accidentally closed it, grr

Patch:
diff --git a/src/engine/segment.go b/src/engine/segment.go
index 0e0fba2c..68fcdb49 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -211,6 +211,8 @@ const (
 	SESSION SegmentType = "session"
 	// SHELL writes which shell we're currently in
 	SHELL SegmentType = "shell"
+	// SITECORE displays the current context for the Sitecore CLI
+	SITECORE SegmentType = "sitecore"
 	// SPOTIFY writes the SPOTIFY status for Mac
 	SPOTIFY SegmentType = "spotify"
 	// STRAVA is a sports activity tracker
@@ -307,6 +309,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
 	SAPLING:       func() SegmentWriter { return &segments.Sapling{} },
 	SESSION:       func() SegmentWriter { return &segments.Session{} },
 	SHELL:         func() SegmentWriter { return &segments.Shell{} },
+	SITECORE:      func() SegmentWriter { return &segments.Sitecore{} },
 	SPOTIFY:       func() SegmentWriter { return &segments.Spotify{} },
 	STRAVA:        func() SegmentWriter { return &segments.Strava{} },
 	SVN:           func() SegmentWriter { return &segments.Svn{} },
diff --git a/src/segments/sitecore.go b/src/segments/sitecore.go
new file mode 100644
index 00000000..7dd7d59f
--- /dev/null
+++ b/src/segments/sitecore.go
@@ -0,0 +1,78 @@
+package segments
+
+import (
+	"encoding/json"
+	"path/filepath"
+	"strings"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+)
+
+type Sitecore struct {
+	props properties.Properties
+	env   platform.Environment
+
+	Environment string
+	Cloud       bool
+}
+
+type SitecoreConfig struct {
+	Endpoints struct {
+		Default struct {
+			Ref        string `json:"ref"`
+			AllowWrite bool   `json:"allowWrite"`
+			Host       string `json:"host"`
+			Variables  struct {
+			} `json:"variables"`
+		} `json:"default"`
+	} `json:"endpoints"`
+}
+
+func (s *Sitecore) Enabled() bool {
+	return s.shouldDisplay()
+}
+
+func (s *Sitecore) Template() string {
+	return " {{ if .Cloud }}\uf65e{{ else }}\uf98a{{ end }} {{ .Environment }} "
+}
+
+func (s *Sitecore) Init(props properties.Properties, env platform.Environment) {
+	s.props = props
+	s.env = env
+}
+
+func (s *Sitecore) shouldDisplay() bool {
+	sitecoreDir, err := s.env.HasParentFilePath(".sitecore")
+	if err != nil {
+		return false
+	}
+
+	if !sitecoreDir.IsDir {
+		return false
+	}
+
+	if !s.env.HasFilesInDir(sitecoreDir.Path, "user.json") {
+		return false
+	}
+
+	sitecoreConfigFile := filepath.Join(sitecoreDir.Path, "user.json")
+
+	if len(sitecoreConfigFile) == 0 {
+		return false
+	}
+
+	content := s.env.FileContent(sitecoreConfigFile)
+
+	var config SitecoreConfig
+	if err := json.Unmarshal([]byte(content), &config); err != nil {
+		return false
+	}
+
+	// sitecore xm cloud always has sitecorecloud.io as domain
+	s.Cloud = strings.Contains(config.Endpoints.Default.Host, "sitecorecloud.io")
+
+	s.Environment = config.Endpoints.Default.Host
+
+	return true
+}
diff --git a/themes/schema.json b/themes/schema.json
index e8a55372..a7885c29 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -1927,6 +1927,19 @@
             }
           }
         },
+        {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "sitecore"
+              }
+            }
+          },
+          "then": {
+            "title": "Sitecore Segment",
+            "description": "https://ohmyposh.dev/docs/segments/sitecore"
+          }
+        },
         {
           "if": {
             "properties": {
diff --git a/website/docs/segments/sitecore.mdx b/website/docs/segments/sitecore.mdx
new file mode 100644
index 00000000..6ecdc95d
--- /dev/null
+++ b/website/docs/segments/sitecore.mdx
@@ -0,0 +1,57 @@
+---
+id: sitecore
+title: Sitecore
+sidebar_label: Sitecore
+---
+
+## What
+
+Show the current active Sitecore environment
+
+## Sample Configuration
+
+import Config from "@site/src/components/Config.js";
+
+<Config
+  data={{
+    type: "sitecore",
+    style: "powerline",
+    powerline_symbol: "\uE0B0",
+    foreground: "#ffffff",
+    background: "#0077c2",
+    template:
+      " {{ if .Cloud }}\uf65e{{ else }}\uf98a{{ end }} {{ .Environment }} ",
+  }}
+/>
+
+## Properties
+
+There are no properties that can be set
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+{{ if .Cloud }}\uf65e{{ else }}\uf98a{{ end }} {{ .Environment }}
+```
+
+:::
+
+:::note advanced xmcloud template
+When using xmcloud, the hostname can become very long. You can shorten `.Environment` by using the following template:
+
+```template
+ {{ if .Cloud }}\uf65e{{ else }}\uf98a{{ end }} {{ if .Cloud }}{{ $splittedHostName := split \".\" .Environment }}{{ $myList := split \"-\" $splittedHostName._0 }} {{ $myList._1 }} - {{ $myList._2 }} - {{ $myList._3 }}{{ else }} {{ .Environment }} {{ end }}
+```
+
+:::
+
+### Properties
+
+| Name           | Type     | Description                                                                                                   |
+| -------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
+| `.Environment` | `string` | The hostname of the environment                                                                               |
+| `.Cloud`       | `bool`   | Used to determine if the environment is a cloud environment. Is true when hostname contains "sitecorecloud.io |
+
+[templates]: /docs/configuration/templates
diff --git a/website/sidebars.js b/website/sidebars.js
index 4110875f..b5ba94a4 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -108,6 +108,7 @@ module.exports = {
         "segments/sapling",
         "segments/session",
         "segments/shell",
+        "segments/sitecore",
         "segments/spotify",
         "segments/strava",
         "segments/svn",


Test patch:
diff --git a/src/segments/sitecore_test.go b/src/segments/sitecore_test.go
new file mode 100644
index 00000000..f6917161
--- /dev/null
+++ b/src/segments/sitecore_test.go
@@ -0,0 +1,93 @@
+package segments
+
+import (
+	"errors"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/mock"
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSitecoreSegment(t *testing.T) {
+	cases := []struct {
+		Case            string
+		ExpectedEnabled bool
+		ExpectedString  string
+		Template        string
+		IsCloud         bool
+	}{
+		{
+			Case:            "no config files found",
+			ExpectedEnabled: false,
+		},
+		{
+			Case:            "Sitecore local config",
+			ExpectedEnabled: true,
+			ExpectedString:  "https://xmcloudcm.local false",
+			Template:        "{{ .Environment }} {{ .Cloud }}",
+			IsCloud:         false,
+		},
+		{
+			Case:            "Sitecore cloud config",
+			ExpectedEnabled: true,
+			ExpectedString:  "https://xmc-sitecore<someID>-projectName-environmentName.sitecorecloud.io true",
+			Template:        "{{ .Environment }} {{ .Cloud }}",
+			IsCloud:         true,
+		},
+		{
+			Case:            "Sitecore cloud config - advanced template",
+			ExpectedEnabled: true,
+			ExpectedString:  "sitecore<someID> - projectName - environmentName",
+			Template:        "{{ if .Cloud }} {{ $splittedHostName := split \".\" .Environment }} {{ $myList := split \"-\" $splittedHostName._0 }} {{ $myList._1 }} - {{ $myList._2 }} - {{ $myList._3 }} {{ end }}", //nolint:lll
+			IsCloud:         true,
+		},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		env.On("Home").Return(poshHome)
+		var sitecoreConfigFile string
+
+		if tc.IsCloud {
+			content, _ := os.ReadFile("../test/sitecoreUser1.json")
+			sitecoreConfigFile = string(content)
+		}
+
+		if !tc.IsCloud {
+			content, _ := os.ReadFile("../test/sitecoreUser2.json")
+			sitecoreConfigFile = string(content)
+		}
+
+		var projectDir *platform.FileInfo
+		var err error
+
+		if !tc.ExpectedEnabled {
+			err = errors.New("no config directory")
+			projectDir = nil
+		}
+
+		if tc.ExpectedEnabled {
+			err = nil
+			projectDir = &platform.FileInfo{
+				ParentFolder: "SitecoreProjectRoot",
+				Path:         filepath.Join("SitecoreProjectRoot", ".sitecore"),
+				IsDir:        true,
+			}
+		}
+
+		env.On("HasParentFilePath", ".sitecore").Return(projectDir, err)
+		env.On("HasFilesInDir", filepath.Join("SitecoreProjectRoot", ".sitecore"), "user.json").Return(true)
+		env.On("FileContent", filepath.Join("SitecoreProjectRoot", ".sitecore", "user.json")).Return(sitecoreConfigFile)
+
+		sitecore := &Sitecore{
+			env: env,
+		}
+
+		assert.Equal(t, tc.ExpectedEnabled, sitecore.Enabled(), tc.Case)
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, sitecore), tc.Case)
+	}
+}
diff --git a/src/test/sitecoreUser1.json b/src/test/sitecoreUser1.json
new file mode 100644
index 00000000..802948ad
--- /dev/null
+++ b/src/test/sitecoreUser1.json
@@ -0,0 +1,10 @@
+{
+  "endpoints": {
+    "default": {
+      "ref": "xmcloud",
+      "allowWrite": true,
+      "host": "https://xmc-sitecore<someID>-projectName-environmentName.sitecorecloud.io",
+      "variables": {}
+    }
+  }
+}
diff --git a/src/test/sitecoreUser2.json b/src/test/sitecoreUser2.json
new file mode 100644
index 00000000..7bcdf551
--- /dev/null
+++ b/src/test/sitecoreUser2.json
@@ -0,0 +1,10 @@
+{
+  "endpoints": {
+    "default": {
+      "ref": "local",
+      "allowWrite": true,
+      "host": "https://xmcloudcm.local",
+      "variables": {}
+    }
+  }
+}