feat: allow segment to override global properties

This commit is contained in:
Jan De Dobbeleer 2023-04-22 12:10:12 +02:00 committed by Jan De Dobbeleer
parent b2e1b041e3
commit 287f183244
3 changed files with 101 additions and 21 deletions

View file

@ -3,6 +3,7 @@ package template
import ( import (
"bytes" "bytes"
"errors" "errors"
"reflect"
"strings" "strings"
"text/template" "text/template"
@ -14,6 +15,29 @@ const (
// Errors to show when the template handling fails // Errors to show when the template handling fails
InvalidTemplate = "invalid template text" InvalidTemplate = "invalid template text"
IncorrectTemplate = "unable to create text based on template" IncorrectTemplate = "unable to create text based on template"
globalRef = ".$"
)
var (
knownVariables = []string{
"Root",
"PWD",
"Folder",
"Shell",
"ShellVersion",
"UserName",
"HostName",
"Env",
"Data",
"Code",
"OS",
"WSL",
"Segments",
"Templates",
"PromptCount",
"Var",
}
) )
type Text struct { type Text struct {
@ -73,26 +97,7 @@ func (t *Text) Render() (string, error) {
} }
func (t *Text) cleanTemplate() { func (t *Text) cleanTemplate() {
knownVariables := []string{ isKnownVariable := func(variable string) bool {
"Root",
"PWD",
"Folder",
"Shell",
"ShellVersion",
"UserName",
"HostName",
"Env",
"Data",
"Code",
"OS",
"WSL",
"Segments",
"Templates",
"PromptCount",
"Var",
}
knownVariable := func(variable string) bool {
variable = strings.TrimPrefix(variable, ".") variable = strings.TrimPrefix(variable, ".")
splitted := strings.Split(variable, ".") splitted := strings.Split(variable, ".")
if len(splitted) == 0 { if len(splitted) == 0 {
@ -111,6 +116,9 @@ func (t *Text) cleanTemplate() {
return false return false
} }
fields := make(fields)
fields.init(t.Context)
var result, property string var result, property string
var inProperty, inTemplate bool var inProperty, inTemplate bool
for i, char := range t.Template { for i, char := range t.Template {
@ -149,9 +157,17 @@ func (t *Text) cleanTemplate() {
continue continue
} }
// end of a variable, needs to be appended // end of a variable, needs to be appended
if !knownVariable(property) { if !isKnownVariable(property) {
result += ".Data" + property result += ".Data" + property
} else { } else {
// check if we have the same property in Data
// and replace it with the Data property so it
// can take precedence
if fields.hasField(property) {
property = ".Data" + property
}
// remove the global reference so we can use it directly
property = strings.TrimPrefix(property, globalRef)
result += property result += property
} }
property = "" property = ""
@ -169,3 +185,34 @@ func (t *Text) cleanTemplate() {
// return the result and remaining unresolved property // return the result and remaining unresolved property
t.Template = result + property t.Template = result + property
} }
type fields map[string]bool
func (f *fields) init(data interface{}) {
if data == nil {
return
}
val := reflect.TypeOf(data)
switch val.Kind() { //nolint:exhaustive
case reflect.Struct:
fieldsNum := val.NumField()
for i := 0; i < fieldsNum; i++ {
(*f)[val.Field(i).Name] = true
}
case reflect.Map:
m, ok := data.(map[string]interface{})
if !ok {
return
}
for key := range m {
(*f)[key] = true
}
}
}
func (f fields) hasField(field string) bool {
field = strings.TrimPrefix(field, ".")
_, ok := f[field]
return ok
}

View file

@ -209,11 +209,40 @@ func TestRenderTemplateEnvVar(t *testing.T) {
{Case: "no env var", Expected: "hello world", Template: "{{.Text}} world", Context: struct{ Text string }{Text: "hello"}}, {Case: "no env var", Expected: "hello world", Template: "{{.Text}} world", Context: struct{ Text string }{Text: "hello"}},
{Case: "map", Expected: "hello world", Template: "{{.Text}} world", Context: map[string]interface{}{"Text": "hello"}}, {Case: "map", Expected: "hello world", Template: "{{.Text}} world", Context: map[string]interface{}{"Text": "hello"}},
{Case: "empty map", Expected: " world", Template: "{{.Text}} world", Context: map[string]string{}}, {Case: "empty map", Expected: " world", Template: "{{.Text}} world", Context: map[string]string{}},
{
Case: "Struct with duplicate property",
Expected: "posh",
Template: "{{ .OS }}",
Context: struct{ OS string }{OS: "posh"},
Env: map[string]string{"HELLO": "hello"},
},
{
Case: "Struct with duplicate property, but global override",
Expected: "darwin",
Template: "{{ .$.OS }}",
Context: struct{ OS string }{OS: "posh"},
Env: map[string]string{"HELLO": "hello"},
},
{
Case: "Map with duplicate property",
Expected: "posh",
Template: "{{ .OS }}",
Context: map[string]interface{}{"OS": "posh"},
Env: map[string]string{"HELLO": "hello"},
},
{
Case: "Non-supported map",
Expected: "darwin",
Template: "{{ .OS }}",
Context: map[int]interface{}{},
Env: map[string]string{"HELLO": "hello"},
},
} }
for _, tc := range cases { for _, tc := range cases {
env := &mock.MockedEnvironment{} env := &mock.MockedEnvironment{}
env.On("TemplateCache").Return(&platform.TemplateCache{ env.On("TemplateCache").Return(&platform.TemplateCache{
Env: tc.Env, Env: tc.Env,
OS: "darwin",
}) })
env.On("Error", mock2.Anything) env.On("Error", mock2.Anything)
tmpl := &Text{ tmpl := &Text{

View file

@ -13,6 +13,10 @@ offers a few standard properties to work with.
## Global properties ## Global properties
These properties can be used anywhere, in any segment. If a segment contains a property with the same name,
the segment property value will be used instead. In case you want to use the global property, you can prefix
it with `.$` to reference it directly.
| Name | Type | Description | | Name | Type | Description |
| --------------- | --------- | ----------------------------------------------------------------- | | --------------- | --------- | ----------------------------------------------------------------- |
| `.Root` | `boolean` | is the current user root/admin or not | | `.Root` | `boolean` | is the current user root/admin or not |