diff --git a/lookup.go b/lookup.go index 763e8e8..c3a5da6 100644 --- a/lookup.go +++ b/lookup.go @@ -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. @@ -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 + } + 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. diff --git a/lookup_test.go b/lookup_test.go index eb58628..3241d84 100644 --- a/lookup_test.go +++ b/lookup_test.go @@ -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 @@ -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}, }, }, {