-
Notifications
You must be signed in to change notification settings - Fork 5
/
lookup.go
127 lines (123 loc) · 4.28 KB
/
lookup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright (c) 2014 Alex Kalyvitis
// Portions Copyright (c) 2009 Michael Hoisie
package mustache
import (
"reflect"
"strings"
)
// The lookup function searches for a property that matches name within the
// context chain. We first start from the first item in the context chain which
// is the most likely to have the value we're looking for. If not found, we'll
// move up the chain and repeat.
func lookup(name string, context ...interface{}) (interface{}, bool) {
// If the dot notation was used we split the word in two and perform two
// consecutive lookups. If the first one fails we return no value and a
// negative truth. Taken from github.com/hoisie/mustache.
if name != "." && strings.Contains(name, ".") {
parts := strings.SplitN(name, ".", 2)
if value, ok := lookup(parts[0], context...); ok {
return lookup(parts[1], value)
}
return nil, false
}
// Iterate over the context chain and try to match the name to a value.
for _, c := range context {
// Reflect on the value and type of the current context.
reflectValue := reflect.ValueOf(c)
reflectType := reflect.TypeOf(c)
// If the name is ".", we should return the whole context as-is.
if name == "." {
return c, truth(reflectValue)
}
switch reflectValue.Kind() {
// If the current context is a map, we'll look for a key in that map
// that matches the name.
case reflect.Map:
// Try to match a map key to the name. For example:
//
// m := map[string]string{"foo": "bar"}
// mustache.Render("{{foo}}", m)
item := reflectValue.MapIndex(reflect.ValueOf(name))
if item.IsValid() {
return item.Interface(), truth(item)
}
// If the current context is a struct, we'll look for a property in that
// struct that matches the name. In the near future I'd like to add
// support for matching struct names to tags so we can use lower_case
// names in our templates which makes it more mustache like.
case reflect.Struct:
// First we'll try to match a field. For example:
//
// type Foo struct { Bar string }
// ctx := &Foo{"baz"}
// mustache.Render("{{Bar}}", ctx)
field := reflectValue.FieldByName(name)
if field.IsValid() {
return field.Interface(), truth(field)
}
// If no field was matched, we'll try to match a method. This is
// useful for methods that return a value. For example:
//
// type Foo struct { bar string }
// func (f *Foo) Bar() string { return f.bar }
// ctx := &Foo{"baz"}
// mustache.Render("{{Bar}}", ctx)
//
method := reflectValue.MethodByName(name)
if method.IsValid() && method.Type().NumIn() == 1 {
out := method.Call(nil)[0]
return out.Interface(), truth(out)
}
// If no method was matched, we'll try to match a tag. This is
// useful for matching fields that have a different name than the
// one we want to use in our templates. For example:
//
// type Foo struct {
// Bar string `template:"baz"`
// }
// ctx := &Foo{"qux"}
// mustache.Render("{{baz}}", ctx)
//
for i := 0; i < reflectValue.NumField(); i++ {
field := reflectValue.Field(i)
tag := reflectType.Field(i).Tag.Get("template")
if tag == name {
return field.Interface(), truth(field)
}
}
}
// If by this point no value was matched, we'll move up a step in the
// chain and try to match a value there.
}
// We've exhausted the whole context chain and found nothing. Return a nil
// value and a negative truth.
return nil, false
}
// The truth function will tell us if r is a truthy value or not. This is
// important for sections as they will render their content based on the output
// of this function.
//
// Zero values are considered falsy. For example an empty string, the integer 0
// and so on are all considered falsy.
func truth(r reflect.Value) bool {
out:
switch r.Kind() {
case reflect.Array, reflect.Slice:
return r.Len() > 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return r.Int() > 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return r.Uint() > 0
case reflect.Float32, reflect.Float64:
return r.Float() > 0
case reflect.String:
return r.String() != ""
case reflect.Bool:
return r.Bool()
case reflect.Ptr, reflect.Interface:
r = r.Elem()
goto out
default:
return r.Interface() != nil
}
}