Repository: JanDeDobbeleer/oh-my-posh
Pull request #3220 (2022-12-18 13:49:03)

template errors
### 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

- feat(template): print actual error
- docs(templates): add context for float64 numbers in segment cache
- fix(template): remove the need for escaping a dot literal
- feat(template): custom compare functions

<!---

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



Patch:
diff --git a/src/platform/shell.go b/src/platform/shell.go
index a5894329..93cc5984 100644
--- a/src/platform/shell.go
+++ b/src/platform/shell.go
@@ -689,7 +689,7 @@ func (env *Shell) Close() {
 
 func (env *Shell) LoadTemplateCache() {
 	defer env.Trace(time.Now(), "LoadTemplateCache")
-	val, OK := env.fileCache.Get(TEMPLATECACHE)
+	val, OK := env.fileCache.Get("template_cache_91508")
 	if !OK {
 		return
 	}
diff --git a/src/template/compare.go b/src/template/compare.go
new file mode 100644
index 00000000..820ae835
--- /dev/null
+++ b/src/template/compare.go
@@ -0,0 +1,35 @@
+package template
+
+func interFaceToInt(e interface{}) int {
+	if val, OK := e.(int); OK {
+		return val
+	}
+	if val, OK := e.(float64); OK {
+		return int(val)
+	}
+	return 0
+}
+
+func interfaceToFloat64(e interface{}) float64 {
+	if val, OK := e.(float64); OK {
+		return val
+	}
+	if val, OK := e.(int); OK {
+		return float64(val)
+	}
+	return 0
+}
+
+func gt(e1, e2 interface{}) bool {
+	if val, OK := e1.(int); OK {
+		return val > interFaceToInt(e2)
+	}
+	if val, OK := e1.(float64); OK {
+		return val > interfaceToFloat64(e2)
+	}
+	return false
+}
+
+func lt(e1, e2 interface{}) bool {
+	return !gt(e1, e2)
+}
diff --git a/src/template/func_map.go b/src/template/func_map.go
index 7b9ab496..44d02c59 100644
--- a/src/template/func_map.go
+++ b/src/template/func_map.go
@@ -14,6 +14,8 @@ func funcMap() template.FuncMap {
 		"glob":         glob,
 		"matchP":       matchP,
 		"replaceP":     replaceP,
+		"gt":           gt,
+		"lt":           lt,
 	}
 	for key, fun := range sprig.TxtFuncMap() {
 		if _, ok := funcMap[key]; !ok {
diff --git a/src/template/text.go b/src/template/text.go
index f88df0a3..f8d4e54f 100644
--- a/src/template/text.go
+++ b/src/template/text.go
@@ -58,7 +58,11 @@ func (t *Text) Render() (string, error) {
 	err = tmpl.Execute(buffer, context)
 	if err != nil {
 		t.Env.Error("Render", err)
-		return "", errors.New(IncorrectTemplate)
+		msg := regex.FindNamedRegexMatch(`at (?P<MSG><.*)$`, err.Error())
+		if len(msg) == 0 {
+			return "", errors.New(IncorrectTemplate)
+		}
+		return "", errors.New(msg["MSG"])
 	}
 	text := buffer.String()
 	// issue with missingkey=zero ignored for map[string]interface{}
@@ -104,10 +108,23 @@ func (t *Text) cleanTemplate() {
 		return false
 	}
 
-	var result string
-	var property string
-	var inProperty bool
-	for _, char := range t.Template {
+	var result, property string
+	var inProperty, inTemplate bool
+	for i, char := range t.Template {
+		// define start or end of template
+		if !inTemplate && char == '{' {
+			if i-1 >= 0 && rune(t.Template[i-1]) == '{' {
+				inTemplate = true
+			}
+		} else if inTemplate && char == '}' {
+			if i-1 >= 0 && rune(t.Template[i-1]) == '}' {
+				inTemplate = false
+			}
+		}
+		if !inTemplate {
+			result += string(char)
+			continue
+		}
 		switch char {
 		case '.':
 			var lastChar rune
diff --git a/website/docs/configuration/templates.mdx b/website/docs/configuration/templates.mdx
index 57c90a85..4a517075 100644
--- a/website/docs/configuration/templates.mdx
+++ b/website/docs/configuration/templates.mdx
@@ -90,16 +90,6 @@ end
 
 ## Template logic
 
-:::tip
-Oh My Posh replaces all unknown `.Var` variables in the template with the right references. If you need a literal `.Var`, use `\.Var`
-in the template.
-
-```json
-"template": " \\.NET \uE77F "
-```
-
-:::
-
 <!-- markdownlint-disable MD013 -->
 
 | Template                                                             | Description                                                                                                                                                                                                                                                                                                     |


Test patch:
diff --git a/src/segments/az_test.go b/src/segments/az_test.go
index c8ec6972..f4119e90 100644
--- a/src/segments/az_test.go
+++ b/src/segments/az_test.go
@@ -4,7 +4,6 @@ import (
 	"oh-my-posh/mock"
 	"oh-my-posh/platform"
 	"oh-my-posh/properties"
-	"oh-my-posh/template"
 	"os"
 	"path/filepath"
 	"testing"
@@ -50,7 +49,7 @@ func TestAzSegment(t *testing.T) {
 		{
 			Case:            "Faulty template",
 			ExpectedEnabled: true,
-			ExpectedString:  template.IncorrectTemplate,
+			ExpectedString:  "<.Data.Burp>: can't evaluate field Burp in type template.Data",
 			Template:        "{{ .Burp }}",
 			HasPowerShell:   true,
 		},
diff --git a/src/segments/nightscout_test.go b/src/segments/nightscout_test.go
index 3f8099f7..4f3be7fd 100644
--- a/src/segments/nightscout_test.go
+++ b/src/segments/nightscout_test.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 	"oh-my-posh/mock"
 	"oh-my-posh/properties"
-	"oh-my-posh/template"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -125,7 +124,7 @@ func TestNSSegment(t *testing.T) {
 			JSONResponse: `
 			[{"sgv":50,"direction":"DoubleDown"}]`,
 			Template:        "\ue2a1 {{.Sgv}}{{.Burp}}",
-			ExpectedString:  template.IncorrectTemplate,
+			ExpectedString:  "<.Data.Burp>: can't evaluate field Burp in type template.Data",
 			ExpectedEnabled: true,
 			CacheTimeout:    10,
 		},
diff --git a/src/segments/strava_test.go b/src/segments/strava_test.go
index f6e15571..bf299680 100644
--- a/src/segments/strava_test.go
+++ b/src/segments/strava_test.go
@@ -4,7 +4,6 @@ import (
 	"errors"
 	"oh-my-posh/mock"
 	"oh-my-posh/properties"
-	"oh-my-posh/template"
 	"testing"
 	"time"
 
@@ -85,7 +84,7 @@ func TestStravaSegment(t *testing.T) {
 				},
 			},
 			Template:        "{{.Ago}}{{.Burp}}",
-			ExpectedString:  template.IncorrectTemplate,
+			ExpectedString:  "<.Data.Burp>: can't evaluate field Burp in type template.Data",
 			ExpectedEnabled: true,
 		},
 	}
diff --git a/src/template/compare_test.go b/src/template/compare_test.go
new file mode 100644
index 00000000..124d33f2
--- /dev/null
+++ b/src/template/compare_test.go
@@ -0,0 +1,50 @@
+package template
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGt(t *testing.T) {
+	cases := []struct {
+		Case     string
+		Expected bool
+		E1       interface{}
+		E2       interface{}
+	}{
+		{Case: "Float vs int", Expected: false, E1: float64(3), E2: 4},
+		{Case: "Int vs float", Expected: false, E1: 3, E2: float64(4)},
+		{Case: "Int vs Int", Expected: false, E1: 3, E2: 4},
+		{Case: "Float vs Float", Expected: false, E1: float64(3), E2: float64(4)},
+		{Case: "Float vs String", Expected: true, E1: float64(3), E2: "test"},
+		{Case: "Int vs String", Expected: true, E1: 3, E2: "test"},
+		{Case: "String vs String", Expected: false, E1: "test", E2: "test"},
+	}
+
+	for _, tc := range cases {
+		got := gt(tc.E1, tc.E2)
+		assert.Equal(t, tc.Expected, got, tc.Case)
+	}
+}
+
+func TestLt(t *testing.T) {
+	cases := []struct {
+		Case     string
+		Expected bool
+		E1       interface{}
+		E2       interface{}
+	}{
+		{Case: "Float vs int", Expected: true, E1: float64(3), E2: 4},
+		{Case: "Int vs float", Expected: true, E1: 3, E2: float64(4)},
+		{Case: "Int vs Int", Expected: true, E1: 3, E2: 4},
+		{Case: "Float vs Float", Expected: true, E1: float64(3), E2: float64(4)},
+		{Case: "Float vs String", Expected: false, E1: float64(3), E2: "test"},
+		{Case: "String vs String", Expected: true, E1: "test", E2: "test"},
+	}
+
+	for _, tc := range cases {
+		got := lt(tc.E1, tc.E2)
+		assert.Equal(t, tc.Expected, got, tc.Case)
+	}
+}
diff --git a/src/template/text_test.go b/src/template/text_test.go
index 69b13ec7..ddfa2ede 100644
--- a/src/template/text_test.go
+++ b/src/template/text_test.go
@@ -20,6 +20,12 @@ func TestRenderTemplate(t *testing.T) {
 		ShouldError bool
 		Context     interface{}
 	}{
+		{
+			Case:     "dot literal",
+			Expected: "Hello .NET \uE77F",
+			Template: "{{ .Text }} .NET \uE77F",
+			Context:  struct{ Text string }{Text: "Hello"},
+		},
 		{
 			Case:     "color override with dots",
 			Expected: "😺💬<#FF8000> Meow! What should I do next? ...</>",