diff options
Diffstat (limited to 'tpl/internal/go_templates/texttemplate/hugo_template.go')
-rw-r--r-- | tpl/internal/go_templates/texttemplate/hugo_template.go | 158 |
1 files changed, 141 insertions, 17 deletions
diff --git a/tpl/internal/go_templates/texttemplate/hugo_template.go b/tpl/internal/go_templates/texttemplate/hugo_template.go index e3252e7aa..c2738ec53 100644 --- a/tpl/internal/go_templates/texttemplate/hugo_template.go +++ b/tpl/internal/go_templates/texttemplate/hugo_template.go @@ -27,15 +27,31 @@ package is auto generated. */ -// TODO1 name +// Preparer prepares the template before execution. type Preparer interface { Prepare() (*Template, error) } -type TemplateExecutor struct { +// ExecHelper allows some custom eval hooks. +type ExecHelper interface { + GetFunc(name string) (reflect.Value, bool) + GetMapValue(receiver, key reflect.Value) (reflect.Value, bool) } -func (t *TemplateExecutor) Execute(p Preparer, wr io.Writer, data interface{}) error { +// Executer executes a given template. +type Executer interface { + Execute(p Preparer, wr io.Writer, data interface{}) error +} + +type executer struct { + helper ExecHelper +} + +func NewExecuter(helper ExecHelper) Executer { + return &executer{helper: helper} +} + +func (t *executer) Execute(p Preparer, wr io.Writer, data interface{}) error { tmpl, err := p.Prepare() if err != nil { return err @@ -47,9 +63,10 @@ func (t *TemplateExecutor) Execute(p Preparer, wr io.Writer, data interface{}) e } state := &state{ - tmpl: tmpl, - wr: wr, - vars: []variable{{"$", value}}, + helper: t.helper, + tmpl: tmpl, + wr: wr, + vars: []variable{{"$", value}}, } return tmpl.executeWithState(state, value) @@ -62,24 +79,131 @@ func (t *Template) Prepare() (*Template, error) { return t, nil } +func (t *Template) executeWithState(state *state, value reflect.Value) (err error) { + defer errRecover(&err) + if t.Tree == nil || t.Root == nil { + state.errorf("%q is an incomplete or empty template", t.Name()) + } + state.walk(value, t.Root) + return +} + // Below are modifed structs etc. // state represents the state of an execution. It's not part of the // template so that multiple executions of the same template // can execute in parallel. type state struct { - tmpl *Template - wr io.Writer - node parse.Node // current node, for errors - vars []variable // push-down stack of variable values. - depth int // the height of the stack of executing templates. + tmpl *Template + helper ExecHelper + wr io.Writer + node parse.Node // current node, for errors + vars []variable // push-down stack of variable values. + depth int // the height of the stack of executing templates. } -func (t *Template) executeWithState(state *state, value reflect.Value) (err error) { - defer errRecover(&err) - if t.Tree == nil || t.Root == nil { - state.errorf("%q is an incomplete or empty template", t.Name()) +func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value { + s.at(node) + name := node.Ident + + var function reflect.Value + var ok bool + if s.helper != nil { + function, ok = s.helper.GetFunc(name) } - state.walk(value, t.Root) - return + + if !ok { + function, ok = findFunction(name, s.tmpl) + } + + if !ok { + s.errorf("%q is not a defined function", name) + } + return s.evalCall(dot, function, cmd, name, args, final) +} + +// evalField evaluates an expression like (.Field) or (.Field arg1 arg2). +// The 'final' argument represents the return value from the preceding +// value of the pipeline, if any. +func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value { + if !receiver.IsValid() { + if s.tmpl.option.missingKey == mapError { // Treat invalid value as missing map key. + s.errorf("nil data; no entry for key %q", fieldName) + } + return zero + } + typ := receiver.Type() + receiver, isNil := indirect(receiver) + if receiver.Kind() == reflect.Interface && isNil { + // Calling a method on a nil interface can't work. The + // MethodByName method call below would panic. + s.errorf("nil pointer evaluating %s.%s", typ, fieldName) + return zero + } + + // Unless it's an interface, need to get to a value of type *T to guarantee + // we see all methods of T and *T. + ptr := receiver + if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() { + ptr = ptr.Addr() + } + if method := ptr.MethodByName(fieldName); method.IsValid() { + return s.evalCall(dot, method, node, fieldName, args, final) + } + hasArgs := len(args) > 1 || final != missingVal + // It's not a method; must be a field of a struct or an element of a map. + switch receiver.Kind() { + case reflect.Struct: + tField, ok := receiver.Type().FieldByName(fieldName) + if ok { + field := receiver.FieldByIndex(tField.Index) + if tField.PkgPath != "" { // field is unexported + s.errorf("%s is an unexported field of struct type %s", fieldName, typ) + } + // If it's a function, we must call it. + if hasArgs { + s.errorf("%s has arguments but cannot be invoked as function", fieldName) + } + return field + } + case reflect.Map: + // If it's a map, attempt to use the field name as a key. + nameVal := reflect.ValueOf(fieldName) + if nameVal.Type().AssignableTo(receiver.Type().Key()) { + if hasArgs { + s.errorf("%s is not a method but has arguments", fieldName) + } + var result reflect.Value + if s.helper != nil { + result, _ = s.helper.GetMapValue(receiver, nameVal) + } else { + result = receiver.MapIndex(nameVal) + } + if !result.IsValid() { + switch s.tmpl.option.missingKey { + case mapInvalid: + // Just use the invalid value. + case mapZeroValue: + result = reflect.Zero(receiver.Type().Elem()) + case mapError: + s.errorf("map has no entry for key %q", fieldName) + } + } + return result + } + case reflect.Ptr: + etyp := receiver.Type().Elem() + if etyp.Kind() == reflect.Struct { + if _, ok := etyp.FieldByName(fieldName); !ok { + // If there's no such field, say "can't evaluate" + // instead of "nil pointer evaluating". + break + } + } + if isNil { + s.errorf("nil pointer evaluating %s.%s", typ, fieldName) + } + } + s.errorf("can't evaluate field %s in type %s", fieldName, typ) + panic("not reached") } |