feat: env vars in templates

resolves #743
This commit is contained in:
Jan De Dobbeleer 2021-05-26 21:12:58 +02:00 committed by Jan De Dobbeleer
parent 0075ac229c
commit 79fa990205
10 changed files with 126 additions and 11 deletions

View file

@ -21,8 +21,6 @@ const (
FullPath ConsoleTitleStyle = "path" FullPath ConsoleTitleStyle = "path"
// Template allows a more powerful custom string // Template allows a more powerful custom string
Template ConsoleTitleStyle = "template" Template ConsoleTitleStyle = "template"
templateEnvRegex = `\.Env\.(?P<ENV>[^ \.}]*)`
) )
func (t *consoleTitle) getConsoleTitle() string { func (t *consoleTitle) getConsoleTitle() string {
@ -54,17 +52,10 @@ func (t *consoleTitle) getTemplateText() string {
context["Host"] = host context["Host"] = host
} }
// load environment variables into the map
envVars := map[string]string{}
matches := findAllNamedRegexMatch(templateEnvRegex, t.config.ConsoleTitleTemplate)
for _, match := range matches {
envVars[match["ENV"]] = t.env.getenv(match["ENV"])
}
context["Env"] = envVars
template := &textTemplate{ template := &textTemplate{
Template: t.config.ConsoleTitleTemplate, Template: t.config.ConsoleTitleTemplate,
Context: context, Context: context,
Env: t.env,
} }
text, err := template.render() text, err := template.render()
if err != nil { if err != nil {

View file

@ -276,6 +276,7 @@ func getConsoleBackgroundColor(env environmentInfo, backgroundColorTemplate stri
template := &textTemplate{ template := &textTemplate{
Template: backgroundColorTemplate, Template: backgroundColorTemplate,
Context: context, Context: context,
Env: env,
} }
text, err := template.render() text, err := template.render()
if err != nil { if err != nil {

View file

@ -23,6 +23,7 @@ type Segment struct {
writer SegmentWriter writer SegmentWriter
stringValue string stringValue string
active bool active bool
env environmentInfo
} }
// SegmentTiming holds the timing context for a segment // SegmentTiming holds the timing context for a segment
@ -182,6 +183,7 @@ func (segment *Segment) getColor(templates []string, defaultColor string) string
} }
txtTemplate := &textTemplate{ txtTemplate := &textTemplate{
Context: segment.writer, Context: segment.writer,
Env: segment.env,
} }
for _, template := range templates { for _, template := range templates {
txtTemplate.Template = template txtTemplate.Template = template
@ -211,6 +213,7 @@ func (segment *Segment) background() string {
} }
func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error { func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
segment.env = env
functions := map[SegmentType]SegmentWriter{ functions := map[SegmentType]SegmentWriter{
Session: &session{}, Session: &session{},
Path: &path{}, Path: &path{},

View file

@ -83,6 +83,7 @@ func (a *aws) string() string {
template := &textTemplate{ template := &textTemplate{
Template: segmentTemplate, Template: segmentTemplate,
Context: a, Context: a,
Env: a.env,
} }
text, err := template.render() text, err := template.render()
if err != nil { if err != nil {

View file

@ -129,6 +129,7 @@ func (b *batt) string() string {
template := &textTemplate{ template := &textTemplate{
Template: segmentTemplate, Template: segmentTemplate,
Context: b, Context: b,
Env: b.env,
} }
text, err := template.render() text, err := template.render()
if err != nil { if err != nil {

View file

@ -16,6 +16,7 @@ func (k *kubectl) string() string {
template := &textTemplate{ template := &textTemplate{
Template: segmentTemplate, Template: segmentTemplate,
Context: k, Context: k,
Env: k.env,
} }
text, err := template.render() text, err := template.render()
if err != nil { if err != nil {

View file

@ -46,6 +46,7 @@ func (s *session) enabled() bool {
template := &textTemplate{ template := &textTemplate{
Template: segmentTemplate, Template: segmentTemplate,
Context: s, Context: s,
Env: s.env,
} }
var err error var err error
s.templateText, err = template.render() s.templateText, err = template.render()

View file

@ -24,6 +24,7 @@ func (t *tempus) enabled() bool {
template := &textTemplate{ template := &textTemplate{
Template: segmentTemplate, Template: segmentTemplate,
Context: t, Context: t,
Env: t.env,
} }
var err error var err error
t.templateText, err = template.render() t.templateText, err = template.render()

View file

@ -3,6 +3,8 @@ package main
import ( import (
"bytes" "bytes"
"errors" "errors"
"reflect"
"strings"
"text/template" "text/template"
"github.com/Masterminds/sprig" "github.com/Masterminds/sprig"
@ -12,11 +14,14 @@ 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"
templateEnvRegex = `\.Env\.(?P<ENV>[^ \.}]*)`
) )
type textTemplate struct { type textTemplate struct {
Template string Template string
Context interface{} Context interface{}
Env environmentInfo
} }
func (t *textTemplate) render() (string, error) { func (t *textTemplate) render() (string, error) {
@ -24,11 +29,66 @@ func (t *textTemplate) render() (string, error) {
if err != nil { if err != nil {
return "", errors.New(invalidTemplate) return "", errors.New(invalidTemplate)
} }
if strings.Contains(t.Template, ".Env") {
t.loadEnvVars()
}
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
defer buffer.Reset() defer buffer.Reset()
err = tmpl.Execute(buffer, t.Context) err = tmpl.Execute(buffer, t.Context)
if err != nil { if err != nil {
return "", errors.New(incorrectTemplate) return "", errors.New(incorrectTemplate)
} }
return buffer.String(), nil text := buffer.String()
// issue with missingkey=zero ignored for map[string]interface{}
// https://github.com/golang/go/issues/24963
text = strings.ReplaceAll(text, "<no value>", "")
return text, nil
}
func (t *textTemplate) loadEnvVars() {
context := make(map[string]interface{})
switch v := t.Context.(type) {
case map[string]interface{}:
context = v
default:
// we currently only support structs
if !t.isStruct() {
break
}
context = t.structToMap()
}
envVars := map[string]string{}
matches := findAllNamedRegexMatch(templateEnvRegex, t.Template)
for _, match := range matches {
envVars[match["ENV"]] = t.Env.getenv(match["ENV"])
}
context["Env"] = envVars
t.Context = context
}
func (t *textTemplate) isStruct() bool {
v := reflect.TypeOf(t.Context)
if v == nil {
return false
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() == reflect.Invalid {
return false
}
return v.Kind() == reflect.Struct
}
func (t *textTemplate) structToMap() map[string]interface{} {
context := make(map[string]interface{})
v := reflect.ValueOf(t.Context)
strct := v.Type()
for i := 0; i < strct.NumField(); i++ {
sf := strct.Field(i)
name := sf.Name
value := v.Field(i).Interface()
context[name] = value
}
return context
} }

View file

@ -68,10 +68,65 @@ func TestRenderTemplate(t *testing.T) {
}, },
} }
env := &MockedEnvironment{}
for _, tc := range cases { for _, tc := range cases {
template := &textTemplate{ template := &textTemplate{
Template: tc.Template, Template: tc.Template,
Context: tc.Context, Context: tc.Context,
Env: env,
}
text, err := template.render()
if tc.ShouldError {
assert.Error(t, err)
continue
}
assert.Equal(t, tc.Expected, text, tc.Case)
}
}
func TestRenderTemplateEnvVar(t *testing.T) {
cases := []struct {
Case string
Expected string
Template string
ShouldError bool
Env map[string]string
Context interface{}
}{
{
Case: "map with env var",
Expected: "hello world",
Template: "{{.Env.HELLO}} {{.World}}",
Context: map[string]interface{}{"World": "world"},
Env: map[string]string{"HELLO": "hello"},
},
{
Case: "nil struct with env var",
Expected: "hello world",
Template: "{{.Env.HELLO }} world{{ .Text}}",
Context: nil,
Env: map[string]string{"HELLO": "hello"},
},
{
Case: "struct with env var",
Expected: "hello world posh",
Template: "{{.Env.HELLO}} world {{ .Text }}",
Context: struct{ Text string }{Text: "posh"},
Env: map[string]string{"HELLO": "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: "empty map", Expected: " world", Template: "{{.Text}} world", Context: map[string]string{}},
}
for _, tc := range cases {
env := &MockedEnvironment{}
for name, value := range tc.Env {
env.On("getenv", name).Return(value)
}
template := &textTemplate{
Template: tc.Template,
Context: tc.Context,
Env: env,
} }
text, err := template.render() text, err := template.render()
if tc.ShouldError { if tc.ShouldError {