123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005 |
- package raymond
- import (
- "bytes"
- "fmt"
- "reflect"
- "strconv"
- "strings"
- "github.com/aymerick/raymond/ast"
- )
- var (
- // @note borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
- errorType = reflect.TypeOf((*error)(nil)).Elem()
- fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
- zero reflect.Value
- )
- // evalVisitor evaluates a handlebars template with context
- type evalVisitor struct {
- tpl *Template
- // contexts stack
- ctx []reflect.Value
- // current data frame (chained with parent)
- dataFrame *DataFrame
- // block parameters stack
- blockParams []map[string]interface{}
- // block statements stack
- blocks []*ast.BlockStatement
- // expressions stack
- exprs []*ast.Expression
- // memoize expressions that were function calls
- exprFunc map[*ast.Expression]bool
- // used for info on panic
- curNode ast.Node
- }
- // NewEvalVisitor instanciate a new evaluation visitor with given context and initial private data frame
- //
- // If privData is nil, then a default data frame is created
- func newEvalVisitor(tpl *Template, ctx interface{}, privData *DataFrame) *evalVisitor {
- frame := privData
- if frame == nil {
- frame = NewDataFrame()
- }
- return &evalVisitor{
- tpl: tpl,
- ctx: []reflect.Value{reflect.ValueOf(ctx)},
- dataFrame: frame,
- exprFunc: make(map[*ast.Expression]bool),
- }
- }
- // at sets current node
- func (v *evalVisitor) at(node ast.Node) {
- v.curNode = node
- }
- //
- // Contexts stack
- //
- // pushCtx pushes new context to the stack
- func (v *evalVisitor) pushCtx(ctx reflect.Value) {
- v.ctx = append(v.ctx, ctx)
- }
- // popCtx pops last context from stack
- func (v *evalVisitor) popCtx() reflect.Value {
- if len(v.ctx) == 0 {
- return zero
- }
- var result reflect.Value
- result, v.ctx = v.ctx[len(v.ctx)-1], v.ctx[:len(v.ctx)-1]
- return result
- }
- // rootCtx returns root context
- func (v *evalVisitor) rootCtx() reflect.Value {
- return v.ctx[0]
- }
- // curCtx returns current context
- func (v *evalVisitor) curCtx() reflect.Value {
- return v.ancestorCtx(0)
- }
- // ancestorCtx returns ancestor context
- func (v *evalVisitor) ancestorCtx(depth int) reflect.Value {
- index := len(v.ctx) - 1 - depth
- if index < 0 {
- return zero
- }
- return v.ctx[index]
- }
- //
- // Private data frame
- //
- // setDataFrame sets new data frame
- func (v *evalVisitor) setDataFrame(frame *DataFrame) {
- v.dataFrame = frame
- }
- // popDataFrame sets back parent data frame
- func (v *evalVisitor) popDataFrame() {
- v.dataFrame = v.dataFrame.parent
- }
- //
- // Block Parameters stack
- //
- // pushBlockParams pushes new block params to the stack
- func (v *evalVisitor) pushBlockParams(params map[string]interface{}) {
- v.blockParams = append(v.blockParams, params)
- }
- // popBlockParams pops last block params from stack
- func (v *evalVisitor) popBlockParams() map[string]interface{} {
- var result map[string]interface{}
- if len(v.blockParams) == 0 {
- return result
- }
- result, v.blockParams = v.blockParams[len(v.blockParams)-1], v.blockParams[:len(v.blockParams)-1]
- return result
- }
- // blockParam iterates on stack to find given block parameter, and returns its value or nil if not founc
- func (v *evalVisitor) blockParam(name string) interface{} {
- for i := len(v.blockParams) - 1; i >= 0; i-- {
- for k, v := range v.blockParams[i] {
- if name == k {
- return v
- }
- }
- }
- return nil
- }
- //
- // Blocks stack
- //
- // pushBlock pushes new block statement to stack
- func (v *evalVisitor) pushBlock(block *ast.BlockStatement) {
- v.blocks = append(v.blocks, block)
- }
- // popBlock pops last block statement from stack
- func (v *evalVisitor) popBlock() *ast.BlockStatement {
- if len(v.blocks) == 0 {
- return nil
- }
- var result *ast.BlockStatement
- result, v.blocks = v.blocks[len(v.blocks)-1], v.blocks[:len(v.blocks)-1]
- return result
- }
- // curBlock returns current block statement
- func (v *evalVisitor) curBlock() *ast.BlockStatement {
- if len(v.blocks) == 0 {
- return nil
- }
- return v.blocks[len(v.blocks)-1]
- }
- //
- // Expressions stack
- //
- // pushExpr pushes new expression to stack
- func (v *evalVisitor) pushExpr(expression *ast.Expression) {
- v.exprs = append(v.exprs, expression)
- }
- // popExpr pops last expression from stack
- func (v *evalVisitor) popExpr() *ast.Expression {
- if len(v.exprs) == 0 {
- return nil
- }
- var result *ast.Expression
- result, v.exprs = v.exprs[len(v.exprs)-1], v.exprs[:len(v.exprs)-1]
- return result
- }
- // curExpr returns current expression
- func (v *evalVisitor) curExpr() *ast.Expression {
- if len(v.exprs) == 0 {
- return nil
- }
- return v.exprs[len(v.exprs)-1]
- }
- //
- // Error functions
- //
- // errPanic panics
- func (v *evalVisitor) errPanic(err error) {
- panic(fmt.Errorf("Evaluation error: %s\nCurrent node:\n\t%s", err, v.curNode))
- }
- // errorf panics with a custom message
- func (v *evalVisitor) errorf(format string, args ...interface{}) {
- v.errPanic(fmt.Errorf(format, args...))
- }
- //
- // Evaluation
- //
- // evalProgram eEvaluates program with given context and returns string result
- func (v *evalVisitor) evalProgram(program *ast.Program, ctx interface{}, data *DataFrame, key interface{}) string {
- blockParams := make(map[string]interface{})
- // compute block params
- if len(program.BlockParams) > 0 {
- blockParams[program.BlockParams[0]] = ctx
- }
- if (len(program.BlockParams) > 1) && (key != nil) {
- blockParams[program.BlockParams[1]] = key
- }
- // push contexts
- if len(blockParams) > 0 {
- v.pushBlockParams(blockParams)
- }
- ctxVal := reflect.ValueOf(ctx)
- if ctxVal.IsValid() {
- v.pushCtx(ctxVal)
- }
- if data != nil {
- v.setDataFrame(data)
- }
- // evaluate program
- result, _ := program.Accept(v).(string)
- // pop contexts
- if data != nil {
- v.popDataFrame()
- }
- if ctxVal.IsValid() {
- v.popCtx()
- }
- if len(blockParams) > 0 {
- v.popBlockParams()
- }
- return result
- }
- // evalPath evaluates all path parts with given context
- func (v *evalVisitor) evalPath(ctx reflect.Value, parts []string, exprRoot bool) (reflect.Value, bool) {
- partResolved := false
- for i := 0; i < len(parts); i++ {
- part := parts[i]
- // "[foo bar]"" => "foo bar"
- if (len(part) >= 2) && (part[0] == '[') && (part[len(part)-1] == ']') {
- part = part[1 : len(part)-1]
- }
- ctx = v.evalField(ctx, part, exprRoot)
- if !ctx.IsValid() {
- break
- }
- // we resolved at least one part of path
- partResolved = true
- }
- return ctx, partResolved
- }
- // evalField evaluates field with given context
- func (v *evalVisitor) evalField(ctx reflect.Value, fieldName string, exprRoot bool) reflect.Value {
- result := zero
- ctx, _ = indirect(ctx)
- if !ctx.IsValid() {
- return result
- }
- // check if this is a method call
- result, isMeth := v.evalMethod(ctx, fieldName, exprRoot)
- if !isMeth {
- switch ctx.Kind() {
- case reflect.Struct:
- // example: firstName => FirstName
- expFieldName := strings.Title(fieldName)
- // check if struct have this field and that it is exported
- if tField, ok := ctx.Type().FieldByName(expFieldName); ok && (tField.PkgPath == "") {
- // struct field
- result = ctx.FieldByIndex(tField.Index)
- break
- }
- // attempts to find template variable name as a struct tag
- result = v.evalStructTag(ctx, fieldName)
- case reflect.Map:
- nameVal := reflect.ValueOf(fieldName)
- if nameVal.Type().AssignableTo(ctx.Type().Key()) {
- // map key
- result = ctx.MapIndex(nameVal)
- }
- case reflect.Array, reflect.Slice:
- if i, err := strconv.Atoi(fieldName); (err == nil) && (i < ctx.Len()) {
- result = ctx.Index(i)
- }
- }
- }
- // check if result is a function
- result, _ = indirect(result)
- if result.Kind() == reflect.Func {
- result = v.evalFieldFunc(fieldName, result, exprRoot)
- }
- return result
- }
- // evalFieldFunc tries to evaluate given method name, and a boolean to indicate if this was a method call
- func (v *evalVisitor) evalMethod(ctx reflect.Value, name string, exprRoot bool) (reflect.Value, bool) {
- if ctx.Kind() != reflect.Interface && ctx.CanAddr() {
- ctx = ctx.Addr()
- }
- method := ctx.MethodByName(name)
- if !method.IsValid() {
- // example: subject() => Subject()
- method = ctx.MethodByName(strings.Title(name))
- }
- if !method.IsValid() {
- return zero, false
- }
- return v.evalFieldFunc(name, method, exprRoot), true
- }
- // evalFieldFunc evaluates given function
- func (v *evalVisitor) evalFieldFunc(name string, funcVal reflect.Value, exprRoot bool) reflect.Value {
- ensureValidHelper(name, funcVal)
- var options *Options
- if exprRoot {
- // create function arg with all params/hash
- expr := v.curExpr()
- options = v.helperOptions(expr)
- // ok, that expression was a function call
- v.exprFunc[expr] = true
- } else {
- // we are not at root of expression, so we are a parameter... and we don't like
- // infinite loops caused by trying to parse ourself forever
- options = newEmptyOptions(v)
- }
- return v.callFunc(name, funcVal, options)
- }
- // evalStructTag checks for the existence of a struct tag containing the
- // name of the variable in the template. This allows for a template variable to
- // be separated from the field in the struct.
- func (v *evalVisitor) evalStructTag(ctx reflect.Value, name string) reflect.Value {
- val := reflect.ValueOf(ctx.Interface())
- for i := 0; i < val.NumField(); i++ {
- field := val.Type().Field(i)
- tag := field.Tag.Get("handlebars")
- if tag == name {
- return val.Field(i)
- }
- }
- return zero
- }
- // findBlockParam returns node's block parameter
- func (v *evalVisitor) findBlockParam(node *ast.PathExpression) (string, interface{}) {
- if len(node.Parts) > 0 {
- name := node.Parts[0]
- if value := v.blockParam(name); value != nil {
- return name, value
- }
- }
- return "", nil
- }
- // evalPathExpression evaluates a path expression
- func (v *evalVisitor) evalPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
- var result interface{}
- if name, value := v.findBlockParam(node); value != nil {
- // block parameter value
- // We push a new context so we can evaluate the path expression (note: this may be a bad idea).
- //
- // Example:
- // {{#foo as |bar|}}
- // {{bar.baz}}
- // {{/foo}}
- //
- // With data:
- // {"foo": {"baz": "bat"}}
- newCtx := map[string]interface{}{name: value}
- v.pushCtx(reflect.ValueOf(newCtx))
- result = v.evalCtxPathExpression(node, exprRoot)
- v.popCtx()
- } else {
- ctxTried := false
- if node.IsDataRoot() {
- // context path
- result = v.evalCtxPathExpression(node, exprRoot)
- ctxTried = true
- }
- if (result == nil) && node.Data {
- // if it is @root, then we tried to evaluate with root context but nothing was found
- // so let's try with private data
- // private data
- result = v.evalDataPathExpression(node, exprRoot)
- }
- if (result == nil) && !ctxTried {
- // context path
- result = v.evalCtxPathExpression(node, exprRoot)
- }
- }
- return result
- }
- // evalDataPathExpression evaluates a private data path expression
- func (v *evalVisitor) evalDataPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
- // find data frame
- frame := v.dataFrame
- for i := node.Depth; i > 0; i-- {
- if frame.parent == nil {
- return nil
- }
- frame = frame.parent
- }
- // resolve data
- // @note Can be changed to v.evalCtx() as context can't be an array
- result, _ := v.evalCtxPath(reflect.ValueOf(frame.data), node.Parts, exprRoot)
- return result
- }
- // evalCtxPathExpression evaluates a context path expression
- func (v *evalVisitor) evalCtxPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
- v.at(node)
- if node.IsDataRoot() {
- // `@root` - remove the first part
- parts := node.Parts[1:len(node.Parts)]
- result, _ := v.evalCtxPath(v.rootCtx(), parts, exprRoot)
- return result
- }
- return v.evalDepthPath(node.Depth, node.Parts, exprRoot)
- }
- // evalDepthPath iterates on contexts, starting at given depth, until there is one that resolve given path parts
- func (v *evalVisitor) evalDepthPath(depth int, parts []string, exprRoot bool) interface{} {
- var result interface{}
- partResolved := false
- ctx := v.ancestorCtx(depth)
- for (result == nil) && ctx.IsValid() && (depth <= len(v.ctx) && !partResolved) {
- // try with context
- result, partResolved = v.evalCtxPath(ctx, parts, exprRoot)
- // As soon as we find the first part of a path, we must not try to resolve with parent context if result is finally `nil`
- // Reference: "Dotted Names - Context Precedence" mustache test
- if !partResolved && (result == nil) {
- // try with previous context
- depth++
- ctx = v.ancestorCtx(depth)
- }
- }
- return result
- }
- // evalCtxPath evaluates path with given context
- func (v *evalVisitor) evalCtxPath(ctx reflect.Value, parts []string, exprRoot bool) (interface{}, bool) {
- var result interface{}
- partResolved := false
- switch ctx.Kind() {
- case reflect.Array, reflect.Slice:
- // Array context
- var results []interface{}
- for i := 0; i < ctx.Len(); i++ {
- value, _ := v.evalPath(ctx.Index(i), parts, exprRoot)
- if value.IsValid() {
- results = append(results, value.Interface())
- }
- }
- result = results
- default:
- // NOT array context
- var value reflect.Value
- value, partResolved = v.evalPath(ctx, parts, exprRoot)
- if value.IsValid() {
- result = value.Interface()
- }
- }
- return result, partResolved
- }
- //
- // Helpers
- //
- // isHelperCall returns true if given expression is a helper call
- func (v *evalVisitor) isHelperCall(node *ast.Expression) bool {
- if helperName := node.HelperName(); helperName != "" {
- return v.findHelper(helperName) != zero
- }
- return false
- }
- // findHelper finds given helper
- func (v *evalVisitor) findHelper(name string) reflect.Value {
- // check template helpers
- if h := v.tpl.findHelper(name); h != zero {
- return h
- }
- // check global helpers
- return findHelper(name)
- }
- // callFunc calls function with given options
- func (v *evalVisitor) callFunc(name string, funcVal reflect.Value, options *Options) reflect.Value {
- params := options.Params()
- funcType := funcVal.Type()
- // @todo Is there a better way to do that ?
- strType := reflect.TypeOf("")
- boolType := reflect.TypeOf(true)
- // check parameters number
- addOptions := false
- numIn := funcType.NumIn()
- if numIn == len(params)+1 {
- lastArgType := funcType.In(numIn - 1)
- if reflect.TypeOf(options).AssignableTo(lastArgType) {
- addOptions = true
- }
- }
- if !addOptions && (len(params) != numIn) {
- v.errorf("Helper '%s' called with wrong number of arguments, needed %d but got %d", name, numIn, len(params))
- }
- // check and collect arguments
- args := make([]reflect.Value, numIn)
- for i, param := range params {
- arg := reflect.ValueOf(param)
- argType := funcType.In(i)
- if !arg.IsValid() {
- if canBeNil(argType) {
- arg = reflect.Zero(argType)
- } else if argType.Kind() == reflect.String {
- arg = reflect.ValueOf("")
- } else {
- // @todo Maybe we can panic on that
- return reflect.Zero(strType)
- }
- }
- if !arg.Type().AssignableTo(argType) {
- if strType.AssignableTo(argType) {
- // convert parameter to string
- arg = reflect.ValueOf(strValue(arg))
- } else if boolType.AssignableTo(argType) {
- // convert parameter to bool
- val, _ := isTrueValue(arg)
- arg = reflect.ValueOf(val)
- } else {
- v.errorf("Helper %s called with argument %d with type %s but it should be %s", name, i, arg.Type(), argType)
- }
- }
- args[i] = arg
- }
- if addOptions {
- args[numIn-1] = reflect.ValueOf(options)
- }
- result := funcVal.Call(args)
- return result[0]
- }
- // callHelper invoqs helper function for given expression node
- func (v *evalVisitor) callHelper(name string, helper reflect.Value, node *ast.Expression) interface{} {
- result := v.callFunc(name, helper, v.helperOptions(node))
- if !result.IsValid() {
- return nil
- }
- // @todo We maybe want to ensure here that helper returned a string or a SafeString
- return result.Interface()
- }
- // helperOptions computes helper options argument from an expression
- func (v *evalVisitor) helperOptions(node *ast.Expression) *Options {
- var params []interface{}
- var hash map[string]interface{}
- for _, paramNode := range node.Params {
- param := paramNode.Accept(v)
- params = append(params, param)
- }
- if node.Hash != nil {
- hash, _ = node.Hash.Accept(v).(map[string]interface{})
- }
- return newOptions(v, params, hash)
- }
- //
- // Partials
- //
- // findPartial finds given partial
- func (v *evalVisitor) findPartial(name string) *partial {
- // check template partials
- if p := v.tpl.findPartial(name); p != nil {
- return p
- }
- // check global partials
- return findPartial(name)
- }
- // partialContext computes partial context
- func (v *evalVisitor) partialContext(node *ast.PartialStatement) reflect.Value {
- if nb := len(node.Params); nb > 1 {
- v.errorf("Unsupported number of partial arguments: %d", nb)
- }
- if (len(node.Params) > 0) && (node.Hash != nil) {
- v.errorf("Passing both context and named parameters to a partial is not allowed")
- }
- if len(node.Params) == 1 {
- return reflect.ValueOf(node.Params[0].Accept(v))
- }
- if node.Hash != nil {
- hash, _ := node.Hash.Accept(v).(map[string]interface{})
- return reflect.ValueOf(hash)
- }
- return zero
- }
- // evalPartial evaluates a partial
- func (v *evalVisitor) evalPartial(p *partial, node *ast.PartialStatement) string {
- // get partial template
- partialTpl, err := p.template()
- if err != nil {
- v.errPanic(err)
- }
- // push partial context
- ctx := v.partialContext(node)
- if ctx.IsValid() {
- v.pushCtx(ctx)
- }
- // evaluate partial template
- result, _ := partialTpl.program.Accept(v).(string)
- // ident partial
- result = indentLines(result, node.Indent)
- if ctx.IsValid() {
- v.popCtx()
- }
- return result
- }
- // indentLines indents all lines of given string
- func indentLines(str string, indent string) string {
- if indent == "" {
- return str
- }
- var indented []string
- lines := strings.Split(str, "\n")
- for i, line := range lines {
- if (i == (len(lines) - 1)) && (line == "") {
- // input string ends with a new line
- indented = append(indented, line)
- } else {
- indented = append(indented, indent+line)
- }
- }
- return strings.Join(indented, "\n")
- }
- //
- // Functions
- //
- // wasFuncCall returns true if given expression was a function call
- func (v *evalVisitor) wasFuncCall(node *ast.Expression) bool {
- // check if expression was tagged as a function call
- return v.exprFunc[node]
- }
- //
- // Visitor interface
- //
- // Statements
- // VisitProgram implements corresponding Visitor interface method
- func (v *evalVisitor) VisitProgram(node *ast.Program) interface{} {
- v.at(node)
- buf := new(bytes.Buffer)
- for _, n := range node.Body {
- if str := Str(n.Accept(v)); str != "" {
- if _, err := buf.Write([]byte(str)); err != nil {
- v.errPanic(err)
- }
- }
- }
- return buf.String()
- }
- // VisitMustache implements corresponding Visitor interface method
- func (v *evalVisitor) VisitMustache(node *ast.MustacheStatement) interface{} {
- v.at(node)
- // evaluate expression
- expr := node.Expression.Accept(v)
- // check if this is a safe string
- isSafe := isSafeString(expr)
- // get string value
- str := Str(expr)
- if !isSafe && !node.Unescaped {
- // escape html
- str = Escape(str)
- }
- return str
- }
- // VisitBlock implements corresponding Visitor interface method
- func (v *evalVisitor) VisitBlock(node *ast.BlockStatement) interface{} {
- v.at(node)
- v.pushBlock(node)
- var result interface{}
- // evaluate expression
- expr := node.Expression.Accept(v)
- if v.isHelperCall(node.Expression) || v.wasFuncCall(node.Expression) {
- // it is the responsibility of the helper/function to evaluate block
- result = expr
- } else {
- val := reflect.ValueOf(expr)
- truth, _ := isTrueValue(val)
- if truth {
- if node.Program != nil {
- switch val.Kind() {
- case reflect.Array, reflect.Slice:
- concat := ""
- // Array context
- for i := 0; i < val.Len(); i++ {
- // Computes new private data frame
- frame := v.dataFrame.newIterDataFrame(val.Len(), i, nil)
- // Evaluate program
- concat += v.evalProgram(node.Program, val.Index(i).Interface(), frame, i)
- }
- result = concat
- default:
- // NOT array
- result = v.evalProgram(node.Program, expr, nil, nil)
- }
- }
- } else if node.Inverse != nil {
- result, _ = node.Inverse.Accept(v).(string)
- }
- }
- v.popBlock()
- return result
- }
- // VisitPartial implements corresponding Visitor interface method
- func (v *evalVisitor) VisitPartial(node *ast.PartialStatement) interface{} {
- v.at(node)
- // partialName: helperName | sexpr
- name, ok := ast.HelperNameStr(node.Name)
- if !ok {
- if subExpr, ok := node.Name.(*ast.SubExpression); ok {
- name, _ = subExpr.Accept(v).(string)
- }
- }
- if name == "" {
- v.errorf("Unexpected partial name: %q", node.Name)
- }
- partial := v.findPartial(name)
- if partial == nil {
- v.errorf("Partial not found: %s", name)
- }
- return v.evalPartial(partial, node)
- }
- // VisitContent implements corresponding Visitor interface method
- func (v *evalVisitor) VisitContent(node *ast.ContentStatement) interface{} {
- v.at(node)
- // write content as is
- return node.Value
- }
- // VisitComment implements corresponding Visitor interface method
- func (v *evalVisitor) VisitComment(node *ast.CommentStatement) interface{} {
- v.at(node)
- // ignore comments
- return ""
- }
- // Expressions
- // VisitExpression implements corresponding Visitor interface method
- func (v *evalVisitor) VisitExpression(node *ast.Expression) interface{} {
- v.at(node)
- var result interface{}
- done := false
- v.pushExpr(node)
- // helper call
- if helperName := node.HelperName(); helperName != "" {
- if helper := v.findHelper(helperName); helper != zero {
- result = v.callHelper(helperName, helper, node)
- done = true
- }
- }
- if !done {
- // literal
- if literal, ok := node.LiteralStr(); ok {
- if val := v.evalField(v.curCtx(), literal, true); val.IsValid() {
- result = val.Interface()
- done = true
- }
- }
- }
- if !done {
- // field path
- if path := node.FieldPath(); path != nil {
- // @todo Find a cleaner way ! Don't break the pattern !
- // this is an exception to visitor pattern, because we need to pass the info
- // that this path is at root of current expression
- if val := v.evalPathExpression(path, true); val != nil {
- result = val
- }
- }
- }
- v.popExpr()
- return result
- }
- // VisitSubExpression implements corresponding Visitor interface method
- func (v *evalVisitor) VisitSubExpression(node *ast.SubExpression) interface{} {
- v.at(node)
- return node.Expression.Accept(v)
- }
- // VisitPath implements corresponding Visitor interface method
- func (v *evalVisitor) VisitPath(node *ast.PathExpression) interface{} {
- return v.evalPathExpression(node, false)
- }
- // Literals
- // VisitString implements corresponding Visitor interface method
- func (v *evalVisitor) VisitString(node *ast.StringLiteral) interface{} {
- v.at(node)
- return node.Value
- }
- // VisitBoolean implements corresponding Visitor interface method
- func (v *evalVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} {
- v.at(node)
- return node.Value
- }
- // VisitNumber implements corresponding Visitor interface method
- func (v *evalVisitor) VisitNumber(node *ast.NumberLiteral) interface{} {
- v.at(node)
- return node.Number()
- }
- // Miscellaneous
- // VisitHash implements corresponding Visitor interface method
- func (v *evalVisitor) VisitHash(node *ast.Hash) interface{} {
- v.at(node)
- result := make(map[string]interface{})
- for _, pair := range node.Pairs {
- if value := pair.Accept(v); value != nil {
- result[pair.Key] = value
- }
- }
- return result
- }
- // VisitHashPair implements corresponding Visitor interface method
- func (v *evalVisitor) VisitHashPair(node *ast.HashPair) interface{} {
- v.at(node)
- return node.Val.Accept(v)
- }
|