diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go
index 636bc4c3347..3ea54eafaaf 100644
--- a/src/pkg/exp/template/exec.go
+++ b/src/pkg/exp/template/exec.go
@@ -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) {
diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go
index d9e2cda0694..534690a6a3e 100644
--- a/src/pkg/exp/template/exec_test.go
+++ b/src/pkg/exp/template/exec_test.go
@@ -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>", tVal, true},
+ "<script>alert("XSS");</script>", nil, true},
{"html pipeline", `{{printf "" | html}}`,
- "<script>alert("XSS");</script>", tVal, true},
+ "<script>alert("XSS");</script>", 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},
diff --git a/src/pkg/exp/template/funcs.go b/src/pkg/exp/template/funcs.go
index 93f8816eb55..44770c7044e 100644
--- a/src/pkg/exp/template/funcs.go
+++ b/src/pkg/exp/template/funcs.go
@@ -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(""") // shorter than """
diff --git a/src/pkg/exp/template/set.go b/src/pkg/exp/template/set.go
index bda4600192d..7100e7e3ec8 100644
--- a/src/pkg/exp/template/set.go
+++ b/src/pkg/exp/template/set.go
@@ -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) {