Repository: JanDeDobbeleer/oh-my-posh
Pull request #3926 (2023-06-01 09:32:57)

feat(Sitecore): upgraded Sitecore segment to support Sitecore CLI context switching 
### 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 c14f4a8</samp>

Refactored and improved the `sitecore` segment to use a new logic based on the user.json file and added a new option to display the default icon. Updated the tests, schema, and documentation accordingly.

### How

<!--
copilot:walkthrough
-->
### <samp>🤖 Generated by Copilot at c14f4a8</samp>

*  Simplify import statements and remove unused packages in `sitecore.go` and `sitecore_test.go` ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-e4e61b9f252fb291a7129580c917d7dfe5178f2d12c9bdb8c3a28a0fdcfe802dL5-R5), [link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-7ba0e717460746e6e69e829d8abb6927bc2834beb817f3da6948f25f2285b80dL4-R8))
*  Modify `Sitecore` and `SitecoreConfig` structs to use different fields and types for Sitecore environment configuration ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-e4e61b9f252fb291a7129580c917d7dfe5178f2d12c9bdb8c3a28a0fdcfe802dL12-R62))
*  Replace `shouldDisplay` method with `Enabled` method that implements new logic for checking and parsing sitecore.json and user.json files and setting `EndpointName` and `CmHost` fields ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-e4e61b9f252fb291a7129580c917d7dfe5178f2d12c9bdb8c3a28a0fdcfe802dL45-R96))
*  Rewrite `TestSitecoreSegment` function to use different test cases and mock data for the new logic of the segment ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-7ba0e717460746e6e69e829d8abb6927bc2834beb817f3da6948f25f2285b80dL17-R161))
*  Add `sitecore` segment name to the list of valid segment names in `themes/schema.json` ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-892c540c9bfebcb3bb8a0be1714201cd17eb6f0d40367c09f80d56e883b7335eR295))
*  Add `display_default` property to the schema of the `sitecore` segment in `themes/schema.json` with description, type, and default value ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-892c540c9bfebcb3bb8a0be1714201cd17eb6f0d40367c09f80d56e883b7335eL1941-R1954))
*  Update sample configuration and properties sections of the `sitecore` segment documentation in `website/docs/segments/sitecore.mdx` to match the new logic and output of the segment ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-3af710d4f9882cc603875c32893b1e701198ef8218f9be38d2a4ffb3a1348819L9-R27))
*  Simplify template section of the `sitecore` segment documentation in `website/docs/segments/sitecore.mdx` to use `EndpointName` and `CmHost` fields and remove advanced xmcloud template and `.Cloud` and `.Environment` properties ([link](https://github.com/JanDeDobbeleer/oh-my-posh/pull/3926/files?diff=unified&w=0#diff-3af710d4f9882cc603875c32893b1e701198ef8218f9be38d2a4ffb3a1348819L36-R44))

<!---

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


dorokhin-bohdan: Hello @JanDeDobbeleer @BasLijten 
First of all thanks for spending your time on review PR.

The Sitecore CLI 5.2+ provides a new ability to change the default environment. More information about the new command [here](https://doc.sitecore.com/xp/en/developers/103/developer-tools/the-environment-command.html).
The segment provided by @BasLijten works only with the default environment. 
The new segment implementation extends it and provides the ability to work with context-switching commands.

According to the new implementation authors of themes can show not only the host but also the environment name. 
Possible theme and example:
![image](https://github.com/JanDeDobbeleer/oh-my-posh/assets/24988081/0a4884d1-00ab-47f8-8a67-44391df9ba6d)
BasLijten: Hi, @dorokhin-bohdan , first of all: cool that you added extra functionality, good to know that I am not the only person in the world that wanted to use this segment. I wasn't aware that this was added in a new version of the CLI: I might have completely missed it, or it wasn't available (yet) at the time of writing, as I was planning on making a context switcher, great the Sitecore introduced this. So I can find myself completely in the functionality, that you added. I do have a few comments/questions. It's based on reading the code and not actual testing, as I am not in the possibility to do this at this very moment.

- my _assumption_ is that it is backwards compatible: when the defaultEndpoint entry is missing, it always defaults back to "default"
- I'd love to keep the IsCloud boolean, to determine if you are working locally, xmcloud, or something else. If you have a suggestion on how to make this work, i'd love to hear that from you, based on complexity I could agree ;)

JanDeDobbeleer: @BasLijten just need you blessing/feedback
BasLijten: > @BasLijten
> 
> > my assumption is that it is backwards compatible: when the defaultEndpoint entry is missing, it always defaults back to "default"
> 
> This segment will work for previous versions of CLI (without context switching command) and return "default" name in that case. For the new version you can easily disable showing the default endpoint in the segment by passing "false" value to the "display_default" properties. In that case segment will be displayed if your current env name is not default.
> 
> > I'd love to keep the IsCloud boolean, to determine if you are working locally, xmcloud, or something else. If you have a suggestion on how to make this work, i'd love to hear that from you, based on complexity I could agree ;)
> 
> Did you consider an option to pass this logic to the theme's template? Template example: {{ if contains "sitecorecloud.io" .CmHost }}\uf65e{{ else }}\uf98a{{ end }} ![image](https://user-images.githubusercontent.com/24988081/243625676-d1a02ef3-ad92-46b4-be15-3d89567464ff.png)
> 
> The main concern regarding to .IsCloud property is a hardcoded part of the hostname in the segment code. In case of changing xmcloud hostname, the segment should be updated and users will wait for a fix/update. 

I agree on the hardcoded part. Was planning to remove that in a future update to a property. Although I am not to fond of moving a lot of logic into the template, I can totally live with it. Thanks for the effort in making this a (much) better segment! @JanDeDobbeleer I'm not sure if you have the same saying in the Belgium, but I'll give it a try: gaan met die banaan!


BasLijten: > @BasLijten just need you blessing/feedback

I dont have any remarks anymore
JanDeDobbeleer: > gaan met die banaan!

@BasLijten can do 😂

Patch:
diff --git a/src/segments/sitecore.go b/src/segments/sitecore.go
index 7dd7d59f..3a9ca63d 100644
--- a/src/segments/sitecore.go
+++ b/src/segments/sitecore.go
@@ -2,77 +2,96 @@ package segments
 
 import (
 	"encoding/json"
-	"path/filepath"
-	"strings"
+	"path"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/platform"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
 )
 
+const (
+	sitecoreFileName   = "sitecore.json"
+	sitecoreFolderName = ".sitecore"
+	userFileName       = "user.json"
+	defaultEnpointName = "default"
+)
+
 type Sitecore struct {
 	props properties.Properties
 	env   platform.Environment
 
-	Environment string
-	Cloud       bool
+	EndpointName string
+	CmHost       string
 }
 
-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"`
+type EndpointConfig struct {
+	Host string `json:"host"`
 }
 
-func (s *Sitecore) Enabled() bool {
-	return s.shouldDisplay()
+type UserConfig struct {
+	DefaultEndpoint string                    `json:"defaultEndpoint"`
+	Endpoints       map[string]EndpointConfig `json:"endpoints"`
 }
 
-func (s *Sitecore) Template() string {
-	return " {{ if .Cloud }}\uf65e{{ else }}\uf98a{{ end }} {{ .Environment }} "
-}
+func (s *Sitecore) Enabled() bool {
+	if !s.env.HasFiles(sitecoreFileName) || !s.env.HasFiles(path.Join(sitecoreFolderName, userFileName)) {
+		return false
+	}
 
-func (s *Sitecore) Init(props properties.Properties, env platform.Environment) {
-	s.props = props
-	s.env = env
-}
+	var userConfig, err = getUserConfig(s)
 
-func (s *Sitecore) shouldDisplay() bool {
-	sitecoreDir, err := s.env.HasParentFilePath(".sitecore")
 	if err != nil {
 		return false
 	}
 
-	if !sitecoreDir.IsDir {
+	s.EndpointName = userConfig.getDefaultEndpoint()
+
+	displayDefault := s.props.GetBool(properties.DisplayDefault, true)
+
+	if !displayDefault && s.EndpointName == defaultEnpointName {
 		return false
 	}
 
-	if !s.env.HasFilesInDir(sitecoreDir.Path, "user.json") {
-		return false
+	if endpoint := userConfig.getEndpoint(s.EndpointName); endpoint != nil && len(endpoint.Host) > 0 {
+		s.CmHost = endpoint.Host
 	}
 
-	sitecoreConfigFile := filepath.Join(sitecoreDir.Path, "user.json")
+	return true
+}
 
-	if len(sitecoreConfigFile) == 0 {
-		return false
+func (s *Sitecore) Template() string {
+	return "{{ .EndpointName }} {{ if .CmHost }}({{ .CmHost }}){{ end }}"
+}
+
+func (s *Sitecore) Init(props properties.Properties, env platform.Environment) {
+	s.props = props
+	s.env = env
+}
+
+func getUserConfig(s *Sitecore) (*UserConfig, error) {
+	userJSON := s.env.FileContent(path.Join(sitecoreFolderName, userFileName))
+	var userConfig UserConfig
+
+	if err := json.Unmarshal([]byte(userJSON), &userConfig); err != nil {
+		return nil, err
 	}
 
-	content := s.env.FileContent(sitecoreConfigFile)
+	return &userConfig, nil
+}
 
-	var config SitecoreConfig
-	if err := json.Unmarshal([]byte(content), &config); err != nil {
-		return false
+func (u *UserConfig) getDefaultEndpoint() string {
+	if len(u.DefaultEndpoint) > 0 {
+		return u.DefaultEndpoint
 	}
 
-	// sitecore xm cloud always has sitecorecloud.io as domain
-	s.Cloud = strings.Contains(config.Endpoints.Default.Host, "sitecorecloud.io")
+	return defaultEnpointName
+}
 
-	s.Environment = config.Endpoints.Default.Host
+func (u *UserConfig) getEndpoint(name string) *EndpointConfig {
+	endpoint, exists := u.Endpoints[name]
 
-	return true
+	if exists {
+		return &endpoint
+	}
+
+	return nil
 }
diff --git a/themes/schema.json b/themes/schema.json
index 99b1af8d..c65face0 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -292,6 +292,7 @@
             "r",
             "sapling",
             "session",
+            "sitecore",
             "spotify",
             "shell",
             "sysinfo",
@@ -1944,7 +1945,19 @@
           },
           "then": {
             "title": "Sitecore Segment",
-            "description": "https://ohmyposh.dev/docs/segments/sitecore"
+            "description": "https://ohmyposh.dev/docs/segments/sitecore",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "display_default": {
+                    "type": "boolean",
+                    "title": "Display Default",
+                    "description": "Display the segment or not when the Sitecore environment name matches `default`",
+                    "default": true
+                  }
+                }
+              }
+            }
           }
         },
         {
diff --git a/website/docs/segments/sitecore.mdx b/website/docs/segments/sitecore.mdx
index 6ecdc95d..7d399ada 100644
--- a/website/docs/segments/sitecore.mdx
+++ b/website/docs/segments/sitecore.mdx
@@ -6,52 +6,39 @@ sidebar_label: Sitecore
 
 ## What
 
-Show the current active Sitecore environment
+Display current Sitecore environment. Will not be active when sitecore.json and user.json don't exist.
 
 ## 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 }} ",
-  }}
-/>
+```json
+{
+  "type": "sitecore",
+  "style": "plain",
+  "foreground": "#000000",
+  "background": "#FFFFFF",
+  "template": "Env: {{ .EndpointName }}{{ if .CmHost }} CM: {{ .CmHost }}{{ end }}"
+}
+```
 
 ## Properties
 
-There are no properties that can be set
+| Name              | Type      | Description                                                                                        |
+| ----------------- | --------- | -------------------------------------------------------------------------------------------------- |
+| `display_default` | `boolean` | display the segment or not when the Sitecore environment name matches `default` - defaults to true |
 
 ## 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 }}
+{{ .EndpointName }} {{ if .CmHost }}({{ .CmHost }}){{ 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 |
+## Properties
 
-[templates]: /docs/configuration/templates
+| Name           | Type     | Description                              |
+| -------------- | -------- | ---------------------------------------- |
+| `EndpointName` | `string` | name of the current Sitecore environment |
+| `CmHost`       | `string` | host of the current Sitecore environment |


Test patch:
diff --git a/src/segments/sitecore_test.go b/src/segments/sitecore_test.go
index f6917161..9dda7f46 100644
--- a/src/segments/sitecore_test.go
+++ b/src/segments/sitecore_test.go
@@ -1,93 +1,162 @@
 package segments
 
 import (
-	"errors"
-	"os"
-	"path/filepath"
+	"path"
 	"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"
 )
 
 func TestSitecoreSegment(t *testing.T) {
 	cases := []struct {
-		Case            string
-		ExpectedEnabled bool
-		ExpectedString  string
-		Template        string
-		IsCloud         bool
+		Case               string
+		ExpectedString     string
+		ExpectedEnabled    bool
+		SitecoreFileExists bool
+		UserFileExists     bool
+		UserFileContent    string
+		DisplayDefault     bool
 	}{
+		{Case: "Disabled, no sitecore.json file and user.json file", ExpectedString: "", ExpectedEnabled: false, SitecoreFileExists: false, UserFileExists: false},
+		{Case: "Disabled, only sitecore.json file exists", ExpectedString: "", ExpectedEnabled: false, SitecoreFileExists: true, UserFileExists: false},
+		{Case: "Disabled, only user.json file exists", ExpectedString: "", ExpectedEnabled: false, SitecoreFileExists: false, UserFileExists: true},
 		{
-			Case:            "no config files found",
-			ExpectedEnabled: false,
+			Case:               "Disabled, user.json is empty",
+			ExpectedString:     "",
+			ExpectedEnabled:    false,
+			SitecoreFileExists: true,
+			UserFileExists:     true,
+			UserFileContent:    "",
 		},
 		{
-			Case:            "Sitecore local config",
-			ExpectedEnabled: true,
-			ExpectedString:  "https://xmcloudcm.local false",
-			Template:        "{{ .Environment }} {{ .Cloud }}",
-			IsCloud:         false,
+			Case:               "Disabled, user.json contains non-json text",
+			ExpectedString:     "",
+			ExpectedEnabled:    false,
+			SitecoreFileExists: true,
+			UserFileExists:     true,
+			UserFileContent:    testUserJSONNotJSONFormat,
 		},
 		{
-			Case:            "Sitecore cloud config",
-			ExpectedEnabled: true,
-			ExpectedString:  "https://xmc-sitecore<someID>-projectName-environmentName.sitecorecloud.io true",
-			Template:        "{{ .Environment }} {{ .Cloud }}",
-			IsCloud:         true,
+			Case:               "Disabled with default endpoint",
+			ExpectedString:     "default",
+			ExpectedEnabled:    false,
+			SitecoreFileExists: true,
+			UserFileExists:     true,
+			UserFileContent:    testUserJSONOnlyDefaultEnv,
+			DisplayDefault:     false,
 		},
 		{
-			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,
+			Case:               "Enabled, user.json initial state",
+			ExpectedString:     "default",
+			ExpectedEnabled:    true,
+			SitecoreFileExists: true,
+			UserFileExists:     true,
+			UserFileContent:    testUserJSONDefaultEmpty,
+			DisplayDefault:     true,
+		},
+		{
+			Case:               "Enabled, user.json with custom default endpoint and without endpoints",
+			ExpectedString:     "MySuperEnv",
+			ExpectedEnabled:    true,
+			SitecoreFileExists: true,
+			UserFileExists:     true,
+			UserFileContent:    testUserJSONCustomDefaultEnvWithoutEndpoints,
+		},
+		{
+			Case:               "Enabled, user.json with custom default endpoint and configured endpoints",
+			ExpectedString:     "myEnv (https://host.com)",
+			ExpectedEnabled:    true,
+			SitecoreFileExists: true,
+			UserFileExists:     true,
+			UserFileContent:    testUserJSONCustomDefaultEnv,
+		},
+		{
+			Case:               "Enabled, user.json with custom default endpoint and empty host",
+			ExpectedString:     "envWithEmptyHost",
+			ExpectedEnabled:    true,
+			SitecoreFileExists: true,
+			UserFileExists:     true,
+			UserFileContent:    testUserJSONCustomDefaultEnvAndEmptyHost,
 		},
 	}
 
 	for _, tc := range cases {
 		env := new(mock.MockedEnvironment)
-		env.On("Home").Return(poshHome)
-		var sitecoreConfigFile string
+		env.On("HasFiles", "sitecore.json").Return(tc.SitecoreFileExists)
+		env.On("HasFiles", path.Join(".sitecore", "user.json")).Return(tc.UserFileExists)
+		env.On("FileContent", path.Join(".sitecore", "user.json")).Return(tc.UserFileContent)
 
-		if tc.IsCloud {
-			content, _ := os.ReadFile("../test/sitecoreUser1.json")
-			sitecoreConfigFile = string(content)
+		props := properties.Map{
+			properties.DisplayDefault: tc.DisplayDefault,
 		}
 
-		if !tc.IsCloud {
-			content, _ := os.ReadFile("../test/sitecoreUser2.json")
-			sitecoreConfigFile = string(content)
-		}
+		sitecore := &Sitecore{}
+		sitecore.Init(props, env)
+		assert.Equal(t, tc.ExpectedEnabled, sitecore.Enabled(), tc.Case)
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, sitecore.Template(), sitecore), tc.Case)
+	}
+}
+
+var testUserJSONDefaultEmpty = `
+{
+	"endpoints": {}
+}`
 
-		var projectDir *platform.FileInfo
-		var err error
+var testUserJSONCustomDefaultEnvWithoutEndpoints = `
+{
+	"endpoints": {},
+	"defaultEndpoint": "MySuperEnv"
+}`
 
-		if !tc.ExpectedEnabled {
-			err = errors.New("no config directory")
-			projectDir = nil
+var testUserJSONCustomDefaultEnv = `
+{
+	"endpoints": {
+		"myEnv": {
+			"host": "https://host.com"
 		}
+	},
+	"defaultEndpoint": "myEnv"
+}`
 
-		if tc.ExpectedEnabled {
-			err = nil
-			projectDir = &platform.FileInfo{
-				ParentFolder: "SitecoreProjectRoot",
-				Path:         filepath.Join("SitecoreProjectRoot", ".sitecore"),
-				IsDir:        true,
-			}
+var testUserJSONCustomDefaultEnvAndEmptyHost = `
+{
+	"endpoints": {
+		"myEnv": {
+			"host": ""
 		}
+	},
+	"defaultEndpoint": "envWithEmptyHost"
+}`
 
-		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)
+var testUserJSONNotJSONFormat = `
+---
+ doe: "a deer, a female deer"
+ ray: "a drop of golden sun"
+ pi: 3.14159
+ xmas: true
+ french-hens: 3
+ calling-birds:
+   - huey
+   - dewey
+   - louie
+   - fred
+ xmas-fifth-day:
+   calling-birds: four
+   french-hens: 3
+   golden-rings: 5
+   partridges:
+     count: 1
+     location: "a pear tree"
+   turtle-doves: two`
 
-		sitecore := &Sitecore{
-			env: env,
+var testUserJSONOnlyDefaultEnv = `
+{
+	"endpoints": {
+		"default": {
+			"host": "https://host.com"
 		}
-
-		assert.Equal(t, tc.ExpectedEnabled, sitecore.Enabled(), tc.Case)
-		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, sitecore), tc.Case)
 	}
-}
+}`