Repository: JanDeDobbeleer/oh-my-posh
Pull request #1870 (2022-03-06 22:03:46)

feat: cds segment
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [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

A segment for cds command from [SAP Cloud Application Programming Model - SAP CAP](https://cap.cloud.sap/docs/tools/#command-line-interface-cli). 
In the project root directory you can have .cdsrc.json (or .cdsrc-private.json) file, but it is not mandatory, so package.json dependency is also checked. *.cds files are located in `db', `srv` subdirectories (usually scaffolded with `cds init`).

Best Regards
Jacek 



Patch:
diff --git a/docs/docs/segment-cds.md b/docs/docs/segment-cds.md
new file mode 100644
index 00000000..fc1db967
--- /dev/null
+++ b/docs/docs/segment-cds.md
@@ -0,0 +1,59 @@
+---
+id: cds
+title: CDS (SAP CAP)
+sidebar_label: CDS
+---
+
+## What
+
+Display the active [CDS CLI][sap-cap-cds] version.
+
+## Sample Configuration
+
+```json
+{
+  "background": "#a7cae1",
+  "foreground": "#100e23",
+  "powerline_symbol": "\ue0b0",
+  "properties": {
+    "template": " \ue311 cds {{ .Full }} "
+  },
+  "style": "powerline",
+  "type": "cds"
+}
+```
+
+## Properties
+
+- home_enabled: `boolean` - display the segment in the HOME folder or not - defaults to `false`
+- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true`
+- missing_command_text: `string` - text to display when the cds command is missing - defaults to empty
+- display_mode: `string` - determines when the segment is displayed
+  - `always`: the segment is always displayed
+  - `files`: the segment is displayed when `.cdsrc.json`, `.cdsrc-private` or `*.cds` file is present
+  - `context`: (default) the segment is displayed when conditions from `files` mode are fulfilled or
+    `package.json` file is present and `@sap/cds` is in `dependencies` section
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+{{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }}
+```
+
+:::
+
+## Template Properties
+
+- `.Full`: `string` - the full version
+- `.Major`: `string` - major number
+- `.Minor`: `string` - minor number
+- `.Patch`: `string` - patch number
+- `.Prerelease`: `string` - prerelease info text
+- `.BuildMetadata`: `string` - build metadata
+- `.Error`: `string` - when fetching the version string errors
+- `.HasDependency`: `bool` - a flag if `@sap/cds` was found in `package.json`
+
+[templates]: /docs/config-templates
+[sap-cap-cds]: https://cap.cloud.sap/docs/tools/#command-line-interface-cli
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 0f431578..0f9c8c8b 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -44,6 +44,7 @@ module.exports = {
         "azfunc",
         "battery",
         "brewfather",
+        "cds",
         "command",
         "crystal",
         "cf",
diff --git a/src/engine/segment.go b/src/engine/segment.go
index de35a834..2903cf88 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -162,6 +162,8 @@ const (
 	KOTLIN SegmentType = "kotlin"
 	// SWIFT writes the active swift version
 	SWIFT SegmentType = "swift"
+	// cds (SAP CAP) version
+	CDS SegmentType = "cds"
 )
 
 func (segment *Segment) shouldIncludeFolder() bool {
@@ -290,6 +292,7 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error
 		CFTARGET:      &segments.CfTarget{},
 		KOTLIN:        &segments.Kotlin{},
 		SWIFT:         &segments.Swift{},
+		CDS:           &segments.Cds{},
 	}
 	if segment.Properties == nil {
 		segment.Properties = make(properties.Map)
diff --git a/src/segments/cds.go b/src/segments/cds.go
new file mode 100644
index 00000000..f229364d
--- /dev/null
+++ b/src/segments/cds.go
@@ -0,0 +1,68 @@
+package segments
+
+import (
+	"encoding/json"
+	"oh-my-posh/environment"
+	"oh-my-posh/properties"
+)
+
+type Cds struct {
+	language
+	HasDependency bool
+}
+
+func (c *Cds) Template() string {
+	return languageTemplate
+}
+
+func (c *Cds) Init(props properties.Properties, env environment.Environment) {
+	c.language = language{
+		env:        env,
+		props:      props,
+		extensions: []string{".cdsrc.json", ".cdsrc-private.json", "*.cds"},
+		commands: []*cmd{
+			{
+				executable: "cds",
+				args:       []string{"--version"},
+				regex:      `@sap/cds: (?:(?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))))`,
+			},
+		},
+		loadContext: c.loadContext,
+		inContext:   c.inContext,
+		displayMode: props.GetString(DisplayMode, DisplayModeContext),
+	}
+}
+
+func (c *Cds) Enabled() bool {
+	return c.language.Enabled()
+}
+
+func (c *Cds) loadContext() {
+	if !c.language.env.HasFiles("package.json") {
+		return
+	}
+
+	content := c.language.env.FileContent("package.json")
+	objmap := map[string]json.RawMessage{}
+
+	if err := json.Unmarshal([]byte(content), &objmap); err != nil {
+		return
+	}
+
+	dependencies := map[string]json.RawMessage{}
+
+	if err := json.Unmarshal(objmap["dependencies"], &dependencies); err != nil {
+		return
+	}
+
+	for d := range dependencies {
+		if d == "@sap/cds" {
+			c.HasDependency = true
+			break
+		}
+	}
+}
+
+func (c *Cds) inContext() bool {
+	return c.HasDependency
+}
diff --git a/themes/schema.json b/themes/schema.json
index 457a93ce..634c9ab9 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -2043,6 +2043,32 @@
               }
             }
           }
+        },
+        {
+          "if": {
+            "properties": {
+              "type": { "const": "cds" }
+            }
+          },
+          "then": {
+            "title": "CDS (SAP CAP) segment",
+            "description": "https://ohmyposh.dev/docs/cds",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "fetch_version": {
+                    "$ref": "#/definitions/fetch_version"
+                  },
+                  "display_mode": {
+                    "$ref": "#/definitions/display_mode"
+                  },
+                  "missing_command_text": {
+                    "$ref": "#/definitions/missing_command_text"
+                  }
+                }
+              }
+            }
+          }
         }
       ]
     }


Test patch:
diff --git a/src/segments/cds_test.go b/src/segments/cds_test.go
new file mode 100644
index 00000000..c6c93da1
--- /dev/null
+++ b/src/segments/cds_test.go
@@ -0,0 +1,146 @@
+package segments
+
+import (
+	"fmt"
+	"oh-my-posh/environment"
+	"oh-my-posh/mock"
+	"oh-my-posh/properties"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCdsSegment(t *testing.T) {
+	cases := []struct {
+		Case            string
+		ExpectedString  string
+		ExpectedEnabled bool
+		File            string
+		Template        string
+		Version         string
+		PackageJSON     string
+		DisplayMode     string
+	}{
+		{
+			Case:            "1) cds 5.5.0 - file .cdsrc.json present",
+			ExpectedString:  "5.5.0",
+			ExpectedEnabled: true,
+			File:            ".cdsrc.json",
+			Version:         "@sap/cds: 5.5.0\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			DisplayMode:     DisplayModeFiles,
+		},
+		{
+			Case:            "2) cds 5.5.1 - file some.cds",
+			ExpectedString:  "5.5.1",
+			ExpectedEnabled: true,
+			File:            "some.cds",
+			Version:         "@sap/cds: 5.5.1\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			DisplayMode:     DisplayModeFiles,
+		},
+		{
+			Case:            "3) cds 5.5.2 - no files",
+			ExpectedString:  "",
+			ExpectedEnabled: false,
+			DisplayMode:     DisplayModeFiles,
+		},
+		{
+			Case:            "4) cds 5.5.3 - package.json dependency",
+			ExpectedString:  "5.5.3",
+			ExpectedEnabled: true,
+			Version:         "@sap/cds: 5.5.3\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			PackageJSON:     "{ \"name\": \"my-app\",\"dependencies\": { \"@sap/cds\": \"^5\" } }",
+			DisplayMode:     DisplayModeContext,
+		},
+		{
+			Case:            "4) cds 5.5.4 - package.json dependency, major + minor",
+			ExpectedString:  "5.5",
+			ExpectedEnabled: true,
+			Template:        "{{ .Major }}.{{ .Minor }}",
+			Version:         "@sap/cds: 5.5.4\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			PackageJSON:     "{ \"name\": \"my-app\",\"dependencies\": { \"@sap/cds\": \"^5\" } }",
+			DisplayMode:     DisplayModeContext,
+		},
+		{
+			Case:            "5) cds 5.5.5 - package.json present, no dependency, no files",
+			ExpectedString:  "",
+			ExpectedEnabled: false,
+			Version:         "@sap/cds: 5.5.5\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			PackageJSON:     "{ \"name\": \"my-app\",\"dependencies\": { \"@sap/some\": \"^5\" } }",
+			DisplayMode:     DisplayModeContext,
+		},
+		{
+			Case:            "6) cds 5.5.9 - display always",
+			ExpectedString:  "5.5.9",
+			ExpectedEnabled: true,
+			Version:         "@sap/cds: 5.5.9\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			PackageJSON:     "{ \"name\": \"my-app\",\"dependencies\": { \"@sap/cds\": \"^5\" } }",
+			DisplayMode:     DisplayModeAlways,
+		},
+		{
+			Case:            "7) cds 5.5.9 - package.json, no dependencies section",
+			ExpectedString:  "",
+			ExpectedEnabled: false,
+			Version:         "@sap/cds: 5.5.9\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			PackageJSON:     "{ \"name\": \"my-app\" }",
+			DisplayMode:     DisplayModeContext,
+		},
+		{
+			Case:            "8) cds 5.5.0 - file .cdsrc-private.json present",
+			ExpectedString:  "5.5.0",
+			ExpectedEnabled: true,
+			File:            ".cdsrc-private.json",
+			Version:         "@sap/cds: 5.5.0\n@sap/cds-compiler: 2.7.0\n@sap/cds-dk: 4.5.3",
+			DisplayMode:     DisplayModeFiles,
+		},
+	}
+
+	for _, tc := range cases {
+		var env = new(mock.MockedEnvironment)
+		env.On("HasCommand", "cds").Return(true)
+		env.On("RunCommand", "cds", []string{"--version"}).Return(tc.Version, nil)
+		env.On("Pwd").Return("/usr/home/dev/my-app")
+		env.On("Home").Return("/usr/home")
+
+		if tc.PackageJSON != "" {
+			env.On("HasFiles", "package.json").Return(true)
+			env.On("FileContent", "package.json").Return(tc.PackageJSON)
+		} else {
+			env.On("HasFiles", "package.json").Return(false)
+		}
+
+		cds := &Cds{}
+
+		props := properties.Map{
+			"display_mode": tc.DisplayMode,
+		}
+
+		env.On("TemplateCache").Return(&environment.TemplateCache{
+			Env: make(map[string]string),
+		})
+
+		if tc.Template == "" {
+			tc.Template = cds.Template()
+		}
+
+		if tc.DisplayMode == "" {
+			tc.DisplayMode = DisplayModeContext
+		}
+
+		cds.Init(props, env)
+
+		for _, f := range cds.language.extensions {
+			match, err := filepath.Match(f, tc.File)
+
+			if err != nil {
+				t.Fail()
+			}
+
+			env.On("HasFiles", f).Return(match)
+		}
+
+		failMsg := fmt.Sprintf("Failed in case: %s", tc.Case)
+		assert.Equal(t, tc.ExpectedEnabled, cds.Enabled(), failMsg)
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, cds), failMsg)
+	}
+}