func RenderSoy(c *gin.Context) { var testdata = []data.Map{ {"names": data.List{}}, {"names": data.List{data.String("Rob")}}, {"names": data.List{data.String("Rob"), data.String("Joe")}}, } if err := tofu.Render(c.Writer, "soy.examples.simple.helloNames", testdata[2]); err != nil { c.Error(err) return } }
func directiveInsertWordBreaks(value data.Value, args []data.Value) data.Value { var ( input = template.HTMLEscapeString(value.String()) maxChars = int(args[0].(data.Int)) chars = 0 output *bytes.Buffer // create the buffer lazily ) for i, ch := range input { switch { case ch == ' ': chars = 0 case chars >= maxChars: if output == nil { output = bytes.NewBufferString(input[:i]) } output.WriteString("<wbr>") chars = 1 default: chars++ } if output != nil { output.WriteRune(ch) } } if output == nil { return value } return data.String(output.String()) }
func directiveJson(value data.Value, _ []data.Value) data.Value { j, err := json.Marshal(value) if err != nil { panic(fmt.Errorf("Error JSON encoding value: %v", err)) } return data.String(j) }
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 funcKeys(v []data.Value) data.Value { var keys data.List for k, _ := range v[0].(data.Map) { keys = append(keys, data.String(k)) } return keys }
func BenchmarkExecuteSimple_Soy(b *testing.B) { var tofu, err = NewBundle(). AddTemplateString("", mustReadFile("testdata/simple.soy")). CompileToTofu() if err != nil { panic(err) } b.ResetTimer() var buf = new(bytes.Buffer) var testdata = []data.Map{ {"names": data.List{}}, {"names": data.List{data.String("Rob")}}, {"names": data.List{data.String("Rob"), data.String("Joe")}}, } for i := 0; i < b.N; i++ { for _, data := range testdata { buf.Reset() err = tofu.Render(buf, "soy.examples.simple.helloNames", data) 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 handler(res http.ResponseWriter, req *http.Request) { var tofu, err = soy.NewBundle(). AddTemplateFile(os.Args[1]). CompileToTofu() if err != nil { http.Error(res, err.Error(), 500) return } var m = make(data.Map) for k, v := range req.URL.Query() { m[k] = data.String(v[0]) } var buf bytes.Buffer err = tofu.Render(&buf, "soyweb.soyweb", m) if err != nil { http.Error(res, err.Error(), 500) return } io.Copy(res, &buf) }
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) }
{"log", "{log}Hello {$name}{/log}", tFile( &ast.LogNode{0, tList( newText(0, "Hello "), &ast.PrintNode{0, &ast.DataRefNode{0, "name", nil}, nil}, )}, )}, {"log+comment", "{log}Hello {$name} // comment\n{/log}", tFile( &ast.LogNode{0, tList( newText(0, "Hello "), &ast.PrintNode{0, &ast.DataRefNode{0, "name", nil}, nil}, )}, )}, {"debugger", "{debugger}", tFile(&ast.DebuggerNode{0})}, {"global", "{GLOBAL_STR}{app.GLOBAL}", tFile( &ast.PrintNode{0, &ast.GlobalNode{0, "GLOBAL_STR", data.String("a")}, nil}, &ast.PrintNode{0, &ast.GlobalNode{0, "app.GLOBAL", data.String("b")}, nil}, )}, {"expression1", "{not false and (isFirst($foo) or (-$x - 5) > 3.1)}", tFile(&ast.PrintNode{0, &ast.AndNode{bin( &ast.NotNode{0, &ast.BoolNode{0, false}}, &ast.OrNode{bin( &ast.FunctionNode{0, "isFirst", []ast.Node{&ast.DataRefNode{0, "foo", nil}}}, &ast.GtNode{bin( &ast.SubNode{bin( &ast.NegateNode{0, &ast.DataRefNode{0, "x", nil}}, &ast.IntNode{0, 5})}, &ast.FloatNode{0, 3.1})})})}, nil})}, {"expression2", `{null or ('foo' == 'f'+true ? -3 <= 5 : not $foo ?: bar(5))}`, tFile(&ast.PrintNode{0, &ast.OrNode{bin( &ast.NullNode{0},
func directiveChangeNewlineToBr(value data.Value, _ []data.Value) data.Value { return data.String(newlinePattern.ReplaceAllString( template.HTMLEscapeString(value.String()), "<br>")) }
func directiveEscapeJsString(value data.Value, _ []data.Value) data.Value { return data.String(template.JSEscapeString(value.String())) }
func directiveEscapeUri(value data.Value, _ []data.Value) data.Value { return data.String(url.QueryEscape(value.String())) }
// 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 TestInjectedData(t *testing.T) { ij["foo"] = data.String("abc") runExecTests(t, []execTest{ exprtest("ij", `{$ij.foo}`, `abc`), }) }
func directiveEscapeHtml(value data.Value, _ []data.Value) data.Value { return data.String(template.HTMLEscapeString(value.String())) }