]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: add templates to sets; boolean logic.
authorRob Pike <r@golang.org>
Tue, 5 Jul 2011 07:05:15 +0000 (17:05 +1000)
committerRob Pike <r@golang.org>
Tue, 5 Jul 2011 07:05:15 +0000 (17:05 +1000)
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/4670045

src/pkg/exp/template/exec.go
src/pkg/exp/template/exec_test.go
src/pkg/exp/template/funcs.go
src/pkg/exp/template/set.go

index 636bc4c334793fcab0b1c853a7440d1da58dd77f..3ea54eafaaf74264a0886be799b7e0ef08907f64 100644 (file)
@@ -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) {
index d9e2cda069465fb494b907c5bd74881188b94f8c..534690a6a3ee306cd4c5a27b6dc4887f329db40d 100644 (file)
@@ -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},
index 93f8816eb554ede0fc20eaef74a22935a3105954..44770c7044e007c687738b9db37ed1ac14798660 100644 (file)
@@ -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;"
index bda4600192d604c7a76747823fa3916167b7a111..7100e7e3ec8b4cd76849f4b2cacea817ac2215ba 100644 (file)
@@ -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) {