]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: fixes and updates.
authorRob Pike <r@golang.org>
Wed, 6 Jul 2011 04:46:41 +0000 (14:46 +1000)
committerRob Pike <r@golang.org>
Wed, 6 Jul 2011 04:46:41 +0000 (14:46 +1000)
- fix line numbers - forgot to update state.line during execution
- add a comment convention {{/* comment */}}
- set.Template returns the named template in the set
- set.Execute executes the named template in the set
- use a local methodByName so this package can be used with earlier release of reflect.
- use initial cap to detect exported names

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/4668054

src/pkg/exp/template/exec.go
src/pkg/exp/template/lex.go
src/pkg/exp/template/lex_test.go
src/pkg/exp/template/parse.go
src/pkg/exp/template/set.go

index 3ea54eafaaf74264a0886be799b7e0ef08907f64..befb2ded9acb142b0263be90ff60057ae441dc8e 100644 (file)
@@ -10,6 +10,8 @@ import (
        "os"
        "reflect"
        "strings"
+       "unicode"
+       "utf8"
 )
 
 // state represents the state of an execution. It's not part of the
@@ -69,16 +71,20 @@ func (s *state) walk(data reflect.Value, n node) {
                        s.walk(data, node)
                }
        case *ifNode:
+               s.line = n.line
                s.walkIfOrWith(nodeIf, data, n.pipeline, n.list, n.elseList)
        case *rangeNode:
+               s.line = n.line
                s.walkRange(data, n)
        case *textNode:
                if _, err := s.wr.Write(n.text); err != nil {
                        s.error(err)
                }
        case *templateNode:
+               s.line = n.line
                s.walkTemplate(data, n)
        case *withNode:
+               s.line = n.line
                s.walkIfOrWith(nodeWith, data, n.pipeline, n.list, n.elseList)
        default:
                s.errorf("unknown node: %s", n)
@@ -230,6 +236,12 @@ func (s *state) evalFieldNode(data reflect.Value, field *fieldNode, args []node,
        return s.evalFieldOrCall(data, field.ident[n-1], args, final)
 }
 
+// Is this an exported - upper case - name?
+func isExported(name string) bool {
+       rune, _ := utf8.DecodeRuneInString(name)
+       return unicode.IsUpper(rune)
+}
+
 func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
        for data.Kind() == reflect.Ptr {
                data = reflect.Indirect(data)
@@ -240,7 +252,7 @@ func (s *state) evalField(data reflect.Value, fieldName string) reflect.Value {
                field := data.FieldByName(fieldName)
                // TODO: look higher up the tree if we can't find it here. Also unexported fields
                // might succeed higher up, as map keys.
-               if field.IsValid() && field.Type().PkgPath() == "" { // valid and exported
+               if field.IsValid() && isExported(fieldName) { // valid and exported
                        return field
                }
                s.errorf("%s has no field %s", data.Type(), fieldName)
@@ -260,7 +272,7 @@ func (s *state) evalFieldOrCall(data reflect.Value, fieldName string, args []nod
                ptr, data = data, reflect.Indirect(data)
        }
        // Is it a method? We use the pointer because it has value methods too.
-       if method, ok := ptr.Type().MethodByName(fieldName); ok {
+       if method, ok := methodByName(ptr.Type(), fieldName); ok {
                return s.evalCall(ptr, method.Func, fieldName, true, args, final)
        }
        if len(args) > 1 || final.IsValid() {
@@ -275,6 +287,16 @@ func (s *state) evalFieldOrCall(data reflect.Value, fieldName string, args []nod
        panic("not reached")
 }
 
+// TODO: delete when reflect's own MethodByName is released.
+func methodByName(typ reflect.Type, name string) (reflect.Method, bool) {
+       for i := 0; i < typ.NumMethod(); i++ {
+               if typ.Method(i).Name == name {
+                       return typ.Method(i), true
+               }
+       }
+       return reflect.Method{}, false
+}
+
 var (
        osErrorType = reflect.TypeOf(new(os.Error)).Elem()
 )
index 435762c03e4e871472fb341306b7d8514e5abbe4..7230f5b02522bd68498716e8c2a6a500e4727739 100644 (file)
@@ -209,8 +209,12 @@ func lex(name, input string) *lexer {
 
 // state functions
 
-const leftMeta = "{{"
-const rightMeta = "}}"
+const (
+       leftMeta     = "{{"
+       rightMeta    = "}}"
+       leftComment  = "{{/*"
+       rightComment = "*/}}"
+)
 
 // lexText scans until a metacharacter
 func lexText(l *lexer) stateFn {
@@ -235,11 +239,25 @@ func lexText(l *lexer) stateFn {
 
 // leftMeta scans the left "metacharacter", which is known to be present.
 func lexLeftMeta(l *lexer) stateFn {
+       if strings.HasPrefix(l.input[l.pos:], leftComment) {
+               return lexComment
+       }
        l.pos += len(leftMeta)
        l.emit(itemLeftMeta)
        return lexInsideAction
 }
 
+// lexComment scans a comment. The left comment marker is known to be present.
+func lexComment(l *lexer) stateFn {
+       i := strings.Index(l.input[l.pos:], rightComment)
+       if i < 0 {
+               return l.errorf("unclosed comment")
+       }
+       l.pos += i + len(rightComment)
+       l.ignore()
+       return lexText
+}
+
 // rightMeta scans the right "metacharacter", which is known to be present.
 func lexRightMeta(l *lexer) stateFn {
        l.pos += len(rightMeta)
index 67c4bb0600c859542ab99a792251ebe234c4d47a..ba0568ef3cea07f0640822819558e7ce12b4f4c3 100644 (file)
@@ -31,6 +31,11 @@ var lexTests = []lexTest{
        {"empty", "", []item{tEOF}},
        {"spaces", " \t\n", []item{{itemText, " \t\n"}, tEOF}},
        {"text", `now is the time`, []item{{itemText, "now is the time"}, tEOF}},
+       {"text with comment", "hello-{{/* this is a comment */}}-world", []item{
+               {itemText, "hello-"},
+               {itemText, "-world"},
+               tEOF,
+       }},
        {"empty action", `{{}}`, []item{tLeft, tRight, tEOF}},
        {"for", `{{for }}`, []item{tLeft, tFor, tRight, tEOF}},
        {"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}},
index 23f3665d628e0816b9aab36eaf26c9fd88c06177..2ef95fd45722e5bf93de2af78416f78b802ec311 100644 (file)
@@ -648,6 +648,7 @@ func (t *Template) pipeline(context string) (pipe []*commandNode) {
 }
 
 func (t *Template) parseControl(context string) (lineNum int, pipe []*commandNode, list, elseList *listNode) {
+       lineNum = t.lex.lineNumber()
        pipe = t.pipeline(context)
        var next node
        list, next = t.itemList(false)
index 8b381358618e7d8d772887bfdfe8b50352ff0d78..58bbb0c1296d014dfd296e5daeac67ed5d608da2 100644 (file)
@@ -6,6 +6,7 @@ package template
 
 import (
        "fmt"
+       "io"
        "os"
        "reflect"
        "runtime"
@@ -49,6 +50,23 @@ func (s *Set) Add(templates ...*Template) *Set {
        return s
 }
 
+// Template returns the template with the given name in the set,
+// or nil if there is no such template.
+func (s *Set) Template(name string) *Template {
+       return s.tmpl[name]
+}
+
+// Execute looks for the named template in the set and then applies that
+// template to the specified data object, writing the output to wr.  Nested
+// template invocations will be resolved from the set.
+func (s *Set) Execute(name string, wr io.Writer, data interface{}) os.Error {
+       tmpl := s.tmpl[name]
+       if tmpl == nil {
+               return fmt.Errorf("template: no template %q in set", name)
+       }
+       return tmpl.ExecuteInSet(wr, data, s)
+}
+
 // recover is the handler that turns panics into returns from the top
 // level of Parse.
 func (s *Set) recover(errp *os.Error) {