2022-01-26 06:54:36 -08:00
|
|
|
package template
|
2021-02-07 01:54:36 -08:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-04-11 06:24:03 -07:00
|
|
|
"errors"
|
2023-04-22 03:10:12 -07:00
|
|
|
"reflect"
|
2021-05-26 12:12:58 -07:00
|
|
|
"strings"
|
2021-02-07 01:54:36 -08:00
|
|
|
"text/template"
|
2022-12-28 08:30:48 -08:00
|
|
|
|
2023-01-05 12:57:38 -08:00
|
|
|
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
|
|
|
"github.com/jandedobbeleer/oh-my-posh/src/regex"
|
2021-02-07 01:54:36 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Errors to show when the template handling fails
|
2022-01-26 06:54:36 -08:00
|
|
|
InvalidTemplate = "invalid template text"
|
|
|
|
IncorrectTemplate = "unable to create text based on template"
|
2023-04-22 03:10:12 -07:00
|
|
|
|
|
|
|
globalRef = ".$"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
knownVariables = []string{
|
|
|
|
"Root",
|
|
|
|
"PWD",
|
|
|
|
"Folder",
|
|
|
|
"Shell",
|
|
|
|
"ShellVersion",
|
|
|
|
"UserName",
|
|
|
|
"HostName",
|
|
|
|
"Env",
|
|
|
|
"Data",
|
|
|
|
"Code",
|
|
|
|
"OS",
|
|
|
|
"WSL",
|
|
|
|
"Segments",
|
|
|
|
"Templates",
|
|
|
|
"PromptCount",
|
|
|
|
"Var",
|
|
|
|
}
|
2021-02-07 01:54:36 -08:00
|
|
|
)
|
|
|
|
|
2022-01-26 06:54:36 -08:00
|
|
|
type Text struct {
|
2022-07-08 02:47:08 -07:00
|
|
|
Template string
|
|
|
|
Context interface{}
|
2022-11-09 11:27:54 -08:00
|
|
|
Env platform.Environment
|
2022-07-08 02:47:08 -07:00
|
|
|
TemplatesResult string
|
2021-02-07 01:54:36 -08:00
|
|
|
}
|
|
|
|
|
2022-01-12 14:39:34 -08:00
|
|
|
type Data interface{}
|
|
|
|
|
|
|
|
type Context struct {
|
2022-11-09 11:27:54 -08:00
|
|
|
*platform.TemplateCache
|
2022-01-18 00:48:47 -08:00
|
|
|
|
|
|
|
// Simple container to hold ANY object
|
|
|
|
Data
|
2022-07-08 02:47:08 -07:00
|
|
|
Templates string
|
2022-01-18 00:48:47 -08:00
|
|
|
}
|
|
|
|
|
2022-01-26 06:54:36 -08:00
|
|
|
func (c *Context) init(t *Text) {
|
2022-01-12 14:39:34 -08:00
|
|
|
c.Data = t.Context
|
2022-07-08 02:47:08 -07:00
|
|
|
c.Templates = t.TemplatesResult
|
2022-01-23 12:37:51 -08:00
|
|
|
if cache := t.Env.TemplateCache(); cache != nil {
|
2022-05-05 23:41:58 -07:00
|
|
|
c.TemplateCache = cache
|
2022-01-12 14:39:34 -08:00
|
|
|
return
|
2021-06-15 12:23:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-26 06:54:36 -08:00
|
|
|
func (t *Text) Render() (string, error) {
|
2022-11-15 23:37:25 -08:00
|
|
|
if !strings.Contains(t.Template, "{{") || !strings.Contains(t.Template, "}}") {
|
|
|
|
return t.Template, nil
|
|
|
|
}
|
2022-01-12 14:39:34 -08:00
|
|
|
t.cleanTemplate()
|
2022-07-12 02:10:21 -07:00
|
|
|
tmpl, err := template.New(t.Template).Funcs(funcMap()).Parse(t.Template)
|
2021-02-07 01:54:36 -08:00
|
|
|
if err != nil {
|
2023-01-16 11:58:43 -08:00
|
|
|
t.Env.Error(err)
|
2022-01-26 06:54:36 -08:00
|
|
|
return "", errors.New(InvalidTemplate)
|
2021-02-07 01:54:36 -08:00
|
|
|
}
|
2022-01-12 14:39:34 -08:00
|
|
|
context := &Context{}
|
|
|
|
context.init(t)
|
2021-02-07 01:54:36 -08:00
|
|
|
buffer := new(bytes.Buffer)
|
|
|
|
defer buffer.Reset()
|
2022-01-12 14:39:34 -08:00
|
|
|
err = tmpl.Execute(buffer, context)
|
2021-02-07 01:54:36 -08:00
|
|
|
if err != nil {
|
2023-01-16 11:58:43 -08:00
|
|
|
t.Env.Error(err)
|
2022-12-18 03:32:05 -08:00
|
|
|
msg := regex.FindNamedRegexMatch(`at (?P<MSG><.*)$`, err.Error())
|
|
|
|
if len(msg) == 0 {
|
|
|
|
return "", errors.New(IncorrectTemplate)
|
|
|
|
}
|
|
|
|
return "", errors.New(msg["MSG"])
|
2021-02-07 01:54:36 -08:00
|
|
|
}
|
2021-05-26 12:12:58 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-01-26 06:54:36 -08:00
|
|
|
func (t *Text) cleanTemplate() {
|
2023-04-22 03:10:12 -07:00
|
|
|
isKnownVariable := func(variable string) bool {
|
2022-10-12 11:02:58 -07:00
|
|
|
variable = strings.TrimPrefix(variable, ".")
|
|
|
|
splitted := strings.Split(variable, ".")
|
|
|
|
if len(splitted) == 0 {
|
2022-10-19 11:53:48 -07:00
|
|
|
return true
|
2022-10-12 11:02:58 -07:00
|
|
|
}
|
|
|
|
variable = splitted[0]
|
2022-10-19 11:53:48 -07:00
|
|
|
// check if alphanumeric
|
|
|
|
if !regex.MatchString(`^[a-zA-Z0-9]+$`, variable) {
|
|
|
|
return true
|
|
|
|
}
|
2022-10-12 11:02:58 -07:00
|
|
|
for _, b := range knownVariables {
|
|
|
|
if variable == b {
|
|
|
|
return true
|
|
|
|
}
|
2021-05-27 22:55:57 -07:00
|
|
|
}
|
2022-10-12 11:02:58 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-04-22 03:10:12 -07:00
|
|
|
fields := make(fields)
|
|
|
|
fields.init(t.Context)
|
|
|
|
|
2022-12-18 04:04:42 -08:00
|
|
|
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
|
|
|
|
}
|
2022-10-13 11:12:06 -07:00
|
|
|
switch char {
|
|
|
|
case '.':
|
|
|
|
var lastChar rune
|
|
|
|
if len(result) > 0 {
|
|
|
|
lastChar = rune(result[len(result)-1])
|
|
|
|
}
|
|
|
|
// only replace if we're in a valid property start
|
|
|
|
// with a space, { or ( character
|
|
|
|
switch lastChar {
|
|
|
|
case ' ', '{', '(':
|
|
|
|
property += string(char)
|
|
|
|
inProperty = true
|
2022-10-12 11:02:58 -07:00
|
|
|
default:
|
|
|
|
result += string(char)
|
|
|
|
}
|
2022-10-13 11:12:06 -07:00
|
|
|
case ' ', '}', ')': // space or }
|
|
|
|
if !inProperty {
|
|
|
|
result += string(char)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// end of a variable, needs to be appended
|
2023-04-22 03:10:12 -07:00
|
|
|
if !isKnownVariable(property) {
|
2022-10-13 11:12:06 -07:00
|
|
|
result += ".Data" + property
|
2023-08-03 10:36:35 -07:00
|
|
|
} else if strings.HasPrefix(property, ".Segments") && !strings.HasSuffix(property, ".Contains") {
|
|
|
|
// as we can't provide a clean way to access the list
|
|
|
|
// of segments, we need to replace the property with
|
|
|
|
// the list of segments so they can be accessed directly
|
2023-08-08 00:09:05 -07:00
|
|
|
property = strings.Replace(property, ".Segments", ".Segments.SimpleMap", 1)
|
2023-08-03 10:36:35 -07:00
|
|
|
result += property
|
2022-10-13 11:12:06 -07:00
|
|
|
} else {
|
2023-04-22 03:10:12 -07:00
|
|
|
// 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)
|
2022-10-13 11:12:06 -07:00
|
|
|
result += property
|
|
|
|
}
|
|
|
|
property = ""
|
|
|
|
result += string(char)
|
|
|
|
inProperty = false
|
|
|
|
default:
|
|
|
|
if inProperty {
|
|
|
|
property += string(char)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
result += string(char)
|
2022-10-12 11:02:58 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-15 21:50:41 -08:00
|
|
|
// return the result and remaining unresolved property
|
|
|
|
t.Template = result + property
|
2021-02-07 01:54:36 -08:00
|
|
|
}
|
2023-04-22 03:10:12 -07:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2023-07-23 00:29:54 -07:00
|
|
|
case reflect.Ptr:
|
|
|
|
f.init(reflect.ValueOf(data).Elem().Interface())
|
2023-04-22 03:10:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f fields) hasField(field string) bool {
|
|
|
|
field = strings.TrimPrefix(field, ".")
|
|
|
|
_, ok := f[field]
|
|
|
|
return ok
|
|
|
|
}
|