func BenchmarkSimpleTemplate_Soy(b *testing.B) { var tofu, err = NewBundle(). AddTemplateString("", ` {namespace small} /** * @param foo * @param bar * @param baz */ {template .test} some {$foo}, some {$bar}, more {$baz} {/template}`). CompileToTofu() if err != nil { panic(err) } b.ResetTimer() var buf = new(bytes.Buffer) for i := 0; i < b.N; i++ { buf.Reset() err = tofu.Render(buf, "small.test", data.Map{ "foo": data.String("foostring"), "bar": data.Int(42), "baz": data.Bool(true), }) if err != nil { b.Error(err) } } }
func BenchmarkSimpleTemplate_Go(b *testing.B) { var tmpl = template.Must(template.New("").Parse(` {{define "small.test"}} some {{.foo}}, some {{.bar}}, more {{.baz}} {{end}}`)) b.ResetTimer() var buf = new(bytes.Buffer) for i := 0; i < b.N; i++ { buf.Reset() var err = tmpl.ExecuteTemplate(buf, "small.test", data.Map{ "foo": data.String("foostring"), "bar": data.Int(42), "baz": data.Bool(true), }) if err != nil { b.Error(err) } } }
func TestIsnonnull(t *testing.T) { var tests = []struct { input data.Value expected bool }{ {data.Null{}, false}, {data.Undefined{}, false}, {data.Bool(false), true}, {data.Int(0), true}, {data.Float(0), true}, {data.String(""), true}, {data.List{}, true}, {data.Map{}, true}, } for _, test := range tests { var actual = funcIsNonnull([]data.Value{test.input}).(data.Bool) if bool(actual) != test.expected { t.Errorf("isNonnull(%v) => %v, expected %v", test.input, actual, test.expected) } } }
func directiveTruncate(value data.Value, args []data.Value) data.Value { if !isInt(args[0]) { panic(fmt.Errorf("First parameter of '|truncate' is not an integer: %v", args[0])) } var maxLen = int(args[0].(data.Int)) var str = value.String() if len(str) <= maxLen { return value } var ellipsis = data.Bool(true) if len(args) == 2 { var ok bool ellipsis, ok = args[1].(data.Bool) if !ok { panic(fmt.Errorf("Second parameter of '|truncate' is not a bool: %v", args[1])) } } if ellipsis { if maxLen > 3 { maxLen -= 3 } else { ellipsis = false } } for !utf8.RuneStart(str[maxLen]) { maxLen-- } str = str[:maxLen] if ellipsis { str += "..." } return data.String(str) }
// walk recursively goes through each node and executes the indicated logic and // writes the output func (s *state) walk(node ast.Node) { s.val = data.Undefined{} s.at(node) switch node := node.(type) { case *ast.SoyFileNode: for _, node := range node.Body { s.walk(node) } case *ast.TemplateNode: if node.Autoescape != ast.AutoescapeUnspecified { s.autoescape = node.Autoescape } s.walk(node.Body) case *ast.ListNode: for _, node := range node.Nodes { s.walk(node) } // Output nodes ---------- case *ast.PrintNode: s.evalPrint(node) case *ast.RawTextNode: if _, err := s.wr.Write(node.Text); err != nil { s.errorf("%s", err) } case *ast.MsgNode: s.walk(node.Body) case *ast.CssNode: var prefix = "" if node.Expr != nil { prefix = s.eval(node.Expr).String() + "-" } if _, err := io.WriteString(s.wr, prefix+node.Suffix); err != nil { s.errorf("%s", err) } case *ast.DebuggerNode: // nothing to do case *ast.LogNode: Logger.Print(string(s.renderBlock(node.Body))) // Control flow ---------- case *ast.IfNode: for _, cond := range node.Conds { if cond.Cond == nil || s.eval(cond.Cond).Truthy() { s.walk(cond.Body) break } } case *ast.ForNode: var list, ok = s.eval(node.List).(data.List) if !ok { s.errorf("In for loop %q, %q does not resolve to a list.", node.String(), node.List.String()) } if len(list) == 0 { if node.IfEmpty != nil { s.walk(node.IfEmpty) } break } s.context.push() for i, item := range list { s.context.set(node.Var, item) s.context.set(node.Var+"__index", data.Int(i)) s.context.set(node.Var+"__lastIndex", data.Int(len(list)-1)) s.walk(node.Body) } s.context.pop() case *ast.SwitchNode: var switchValue = s.eval(node.Value) for _, caseNode := range node.Cases { for _, caseValueNode := range caseNode.Values { if switchValue.Equals(s.eval(caseValueNode)) { s.walk(caseNode.Body) return } } if len(caseNode.Values) == 0 { // default/last case s.walk(caseNode.Body) return } } case *ast.CallNode: s.evalCall(node) case *ast.LetValueNode: s.context.set(node.Name, s.eval(node.Expr)) case *ast.LetContentNode: s.context.set(node.Name, data.String(s.renderBlock(node.Body))) // Values ---------- case *ast.NullNode: s.val = data.Null{} case *ast.StringNode: s.val = data.String(node.Value) case *ast.IntNode: s.val = data.Int(node.Value) case *ast.FloatNode: s.val = data.Float(node.Value) case *ast.BoolNode: s.val = data.Bool(node.True) case *ast.GlobalNode: s.val = node.Value case *ast.ListLiteralNode: var items = make(data.List, len(node.Items)) for i, item := range node.Items { items[i] = s.eval(item) } s.val = data.List(items) case *ast.MapLiteralNode: var items = make(data.Map, len(node.Items)) for k, v := range node.Items { items[k] = s.eval(v) } s.val = data.Map(items) case *ast.FunctionNode: s.val = s.evalFunc(node) case *ast.DataRefNode: s.val = s.evalDataRef(node) // Arithmetic operators ---------- case *ast.NegateNode: switch arg := s.evaldef(node.Arg).(type) { case data.Int: s.val = data.Int(-arg) case data.Float: s.val = data.Float(-arg) default: s.errorf("can not negate non-number: %q", arg.String()) } case *ast.AddNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) switch { case isInt(arg1) && isInt(arg2): s.val = data.Int(arg1.(data.Int) + arg2.(data.Int)) case isString(arg1) || isString(arg2): s.val = data.String(arg1.String() + arg2.String()) default: s.val = data.Float(toFloat(arg1) + toFloat(arg2)) } case *ast.SubNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) switch { case isInt(arg1) && isInt(arg2): s.val = data.Int(arg1.(data.Int) - arg2.(data.Int)) default: s.val = data.Float(toFloat(arg1) - toFloat(arg2)) } case *ast.DivNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) s.val = data.Float(toFloat(arg1) / toFloat(arg2)) case *ast.MulNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) switch { case isInt(arg1) && isInt(arg2): s.val = data.Int(arg1.(data.Int) * arg2.(data.Int)) default: s.val = data.Float(toFloat(arg1) * toFloat(arg2)) } case *ast.ModNode: var arg1, arg2 = s.eval2def(node.Arg1, node.Arg2) s.val = data.Int(arg1.(data.Int) % arg2.(data.Int)) // Arithmetic comparisons ---------- case *ast.EqNode: s.val = data.Bool(s.eval(node.Arg1).Equals(s.eval(node.Arg2))) case *ast.NotEqNode: s.val = data.Bool(!s.eval(node.Arg1).Equals(s.eval(node.Arg2))) case *ast.LtNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) < toFloat(s.evaldef(node.Arg2))) case *ast.LteNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) <= toFloat(s.evaldef(node.Arg2))) case *ast.GtNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) > toFloat(s.evaldef(node.Arg2))) case *ast.GteNode: s.val = data.Bool(toFloat(s.evaldef(node.Arg1)) >= toFloat(s.evaldef(node.Arg2))) // Boolean operators ---------- case *ast.NotNode: s.val = data.Bool(!s.eval(node.Arg).Truthy()) case *ast.AndNode: s.val = data.Bool(s.eval(node.Arg1).Truthy() && s.eval(node.Arg2).Truthy()) case *ast.OrNode: s.val = data.Bool(s.eval(node.Arg1).Truthy() || s.eval(node.Arg2).Truthy()) case *ast.ElvisNode: var arg1 = s.eval(node.Arg1) if arg1 != (data.Null{}) && arg1 != (data.Undefined{}) { s.val = arg1 } else { s.val = s.eval(node.Arg2) } case *ast.TernNode: var arg1 = s.eval(node.Arg1) if arg1.Truthy() { s.val = s.eval(node.Arg2) } else { s.val = s.eval(node.Arg3) } default: s.errorf("unknown node: %T", node) } }
func funcIsNonnull(v []data.Value) data.Value { return data.Bool(!(v[0] == data.Null{} || v[0] == data.Undefined{})) }
func funcIsLast(s *state, key string) data.Value { return data.Bool( s.context.lookup(key+"__index").(data.Int) == s.context.lookup(key+"__lastIndex").(data.Int)) }
func funcHasData(v []data.Value) data.Value { return data.Bool(true) }
func funcStrContains(v []data.Value) data.Value { return data.Bool(strings.Contains(string(v[0].(data.String)), string(v[1].(data.String)))) }