Commit eea54435 authored by Rob Pike's avatar Rob Pike

exp/template: add templates to sets; boolean logic.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4670045
parent b7db4fef
......@@ -89,7 +89,23 @@ func (s *state) walk(data reflect.Value, n node) {
// are identical in behavior except that 'with' sets dot.
func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe []*commandNode, list, elseList *listNode) {
val := s.evalPipeline(data, pipe)
truth := false
truth, ok := isTrue(val)
if !ok {
s.errorf("if/with can't use value of type %T", val.Interface())
}
if truth {
if typ == nodeWith {
data = val
}
s.walk(data, list)
} else if elseList != nil {
s.walk(data, elseList)
}
}
// isTrue returns whether the value is 'true', in the sense of not the zero of its type,
// and whether the value has a meaningful truth value.
func isTrue(val reflect.Value) (truth, ok bool) {
switch val.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
truth = val.Len() > 0
......@@ -106,16 +122,9 @@ func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe []*commandNo
case reflect.Chan, reflect.Func, reflect.Ptr:
truth = !val.IsNil()
default:
s.errorf("if/with can't use value of type %T", val.Interface())
}
if truth {
if typ == nodeWith {
data = val
}
s.walk(data, list)
} else if elseList != nil {
s.walk(data, elseList)
return
}
return truth, true
}
func (s *state) walkRange(data reflect.Value, r *rangeNode) {
......
......@@ -140,7 +140,7 @@ var execTests = []execTest{
{"if slice", "{{if .SI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
{"if emptymap", "{{if .MSIEmpty}}NON-EMPTY{{else}}EMPTY{{end}}", "EMPTY", tVal, true},
{"if map", "{{if .MSI}}NON-EMPTY{{else}}EMPTY{{end}}", "NON-EMPTY", tVal, true},
// Function calls.
// Printf.
{"printf", `{{printf "hello, printf"}}`, "hello, printf", tVal, true},
{"printf int", `{{printf "%04x" 127}}`, "007f", tVal, true},
{"printf float", `{{printf "%g" 3.5}}`, "3.5", tVal, true},
......@@ -150,10 +150,17 @@ var execTests = []execTest{
{"printf field", `{{printf "%s" .U.V}}`, "v", tVal, true},
{"printf method", `{{printf "%s" .Method0}}`, "resultOfMethod0", tVal, true},
{"printf lots", `{{printf "%d %s %g %s" 127 "hello" 7-3i .Method0}}`, "127 hello (7-3i) resultOfMethod0", tVal, true},
// HTML.
{"html", `{{html "<script>alert(\"XSS\");</script>"}}`,
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", tVal, true},
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
{"html pipeline", `{{printf "<script>alert(\"XSS\");</script>" | html}}`,
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", tVal, true},
"&lt;script&gt;alert(&#34;XSS&#34;);&lt;/script&gt;", nil, true},
// Booleans
{"not", "{{not true}} {{not false}}", "false true", nil, true},
{"and", "{{and 0 0}} {{and 1 0}} {{and 0 1}} {{and 1 1}}", "false false false true", nil, true},
{"or", "{{or 0 0}} {{or 1 0}} {{or 0 1}} {{or 1 1}}", "false true true true", nil, true},
{"boolean if", "{{if and true 1 `hi`}}TRUE{{else}}FALSE{{end}}", "TRUE", tVal, true},
{"boolean if not", "{{if and true 1 `hi` | not}}TRUE{{else}}FALSE{{end}}", "FALSE", nil, true},
// With.
{"with true", "{{with true}}{{.}}{{end}}", "true", tVal, true},
{"with false", "{{with false}}{{.}}{{else}}FALSE{{end}}", "FALSE", tVal, true},
......
......@@ -20,6 +20,9 @@ type FuncMap map[string]interface{}
var funcs = map[string]reflect.Value{
"printf": reflect.ValueOf(fmt.Sprintf),
"html": reflect.ValueOf(HTMLEscaper),
"and": reflect.ValueOf(and),
"or": reflect.ValueOf(or),
"not": reflect.ValueOf(not),
}
// addFuncs adds to values the functions in funcs, converting them to reflect.Values.
......@@ -66,7 +69,33 @@ func findFunction(name string, tmpl *Template, set *Set) (reflect.Value, bool) {
return reflect.Value{}, false
}
// HTML escaping
// Boolean logic.
// and returns the Boolean AND of its arguments.
func and(arg0 interface{}, args ...interface{}) (truth bool) {
truth, _ = isTrue(reflect.ValueOf(arg0))
for i := 0; truth && i < len(args); i++ {
truth, _ = isTrue(reflect.ValueOf(args[i]))
}
return
}
// or returns the Boolean OR of its arguments.
func or(arg0 interface{}, args ...interface{}) (truth bool) {
truth, _ = isTrue(reflect.ValueOf(arg0))
for i := 0; !truth && i < len(args); i++ {
truth, _ = isTrue(reflect.ValueOf(args[i]))
}
return
}
// not returns the Boolean negation of its argument.
func not(arg interface{}) (truth bool) {
truth, _ = isTrue(reflect.ValueOf(arg))
return !truth
}
// HTML escaping.
var (
escQuot = []byte("&#34;") // shorter than "&quot;"
......
......@@ -5,6 +5,7 @@
package template
import (
"fmt"
"os"
"reflect"
"runtime"
......@@ -35,6 +36,19 @@ func (s *Set) Funcs(funcMap FuncMap) *Set {
return s
}
// Add adds the argument templates to the set. It panics if the call
// attempts to reuse a name defined in the template.
// The return value is the set, so calls can be chained.
func (s *Set) Add(templates ...*Template) *Set {
for _, t := range templates {
if _, ok := s.tmpl[t.name]; ok {
panic(fmt.Errorf("template: %q already defined in set", t.name))
}
s.tmpl[t.name] = t
}
return s
}
// recover is the handler that turns panics into returns from the top
// level of Parse.
func (s *Set) recover(errp *os.Error) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment