Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,16 @@ func lookup(name string, context ...interface{}) (interface{}, bool) {
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.
// struct that matches the name.
// Support struct field tags, like `mustache:"foo_bar"`
case reflect.Ptr:
reflectValue = reflectValue.Elem()
fallthrough
case reflect.Struct:
field := reflectValue.FieldByName(name)
if field.IsValid() {
return field.Interface(), truth(field)
result, isTrue := lookupStructByName(reflectValue, name)
if result != nil {
return result, isTrue
}
method := reflectValue.MethodByName(name)
if method.IsValid() && method.Type().NumIn() == 1 {
out := method.Call(nil)[0]
return out.Interface(), truth(out)
}

}
// If by this point no value was matched, we'll move up a step in the
// chain and try to match a value there.
Expand All @@ -63,6 +59,42 @@ func lookup(name string, context ...interface{}) (interface{}, bool) {
return nil, false
}

const kLookupTagKey = "mustache"

// lookupStructByName will return the field or the method result with the given name.
// Returns nil, false if no such field or method.
func lookupStructByName(reflectValue reflect.Value, name string) (interface{}, bool) {
field := reflectValue.FieldByName(name)
if field.IsValid() {
return field.Interface(), truth(field)
}
method := reflectValue.MethodByName(name)
if method.IsValid() && method.Type().NumIn() == 1 {
out := method.Call(nil)[0]
return out.Interface(), truth(out)
}

for i := 0; i < reflectValue.NumField(); i++ {
tag := reflectValue.Type().Field(i).Tag
val, ok := tag.Lookup(kLookupTagKey)
if !ok {
continue
}
// NOTE: Is there any option we need to add?
// Just treat all tag value as the symbol name for now.
if val != name {
continue
}
Comment on lines +83 to +87

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question! To start, I think it would be sufficient to lookup the entire tag value as you do now. In the future, we could allow modifying the tag name as an option to mustache.New().

One use case I can see is reusing the json tag. As that tag may have other values following the comma (e.g omitempty), we may want to split the value and use the first element.

I'd be more than happy to accept it as is though.

field := reflectValue.Field(i)
if !field.IsValid() {
continue
}
return field.Interface(), truth(field)
}

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.
Expand Down
14 changes: 14 additions & 0 deletions lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ func TestSimpleLookup(t *testing.T) {
"map": map[string]interface{}{
"in": "I'm nested!",
},
"ptr": &struct {
Foo *struct{ Bar string }
}{
Foo: &struct{ Bar string }{
Bar: "bar",
},
},
"tag": &struct {
Foo string `mustache:"foo"`
}{
Foo: "foo",
},
},
assertions: []struct {
name string
Expand All @@ -34,6 +46,8 @@ func TestSimpleLookup(t *testing.T) {
{"string", "abc", true},
{"boolean", true, true},
{"map.in", "I'm nested!", true},
{"ptr.Foo.Bar", "bar", true},
{"tag.foo", "foo", true},
},
},
{
Expand Down