|
|
@@ -16,6 +16,10 @@ import (
|
|
|
// Template implements simple template engine, which can be used for fast
|
|
|
// tags' (aka placeholders) substitution.
|
|
|
type Template struct {
|
|
|
+ template string
|
|
|
+ startTag string
|
|
|
+ endTag string
|
|
|
+
|
|
|
texts [][]byte
|
|
|
tags []string
|
|
|
bytesBufferPool sync.Pool
|
|
|
@@ -44,6 +48,40 @@ func New(template, startTag, endTag string) *Template {
|
|
|
// using Execute* methods.
|
|
|
func NewTemplate(template, startTag, endTag string) (*Template, error) {
|
|
|
var t Template
|
|
|
+ t.bytesBufferPool.New = newBytesBuffer
|
|
|
+ err := t.Reset(template, startTag, endTag)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return &t, nil
|
|
|
+}
|
|
|
+
|
|
|
+func newBytesBuffer() interface{} {
|
|
|
+ return &bytes.Buffer{}
|
|
|
+}
|
|
|
+
|
|
|
+// TagFunc can be used as a substitution value in the map passed to Execute*.
|
|
|
+// Execute* functions pass tag (placeholder) name in 'tag' argument.
|
|
|
+//
|
|
|
+// TagFunc must be safe to call from concurrently running goroutines.
|
|
|
+//
|
|
|
+// TagFunc must write contents to w and return the number of bytes written.
|
|
|
+type TagFunc func(w io.Writer, tag string) (int, error)
|
|
|
+
|
|
|
+// Reset resets the template t to new one defined by
|
|
|
+// template, startTag and endTag.
|
|
|
+//
|
|
|
+// Reset allows Template object re-use.
|
|
|
+//
|
|
|
+// Reset may be called only if no other goroutines call t methods at the moment.
|
|
|
+func (t *Template) Reset(template, startTag, endTag string) error {
|
|
|
+ // Keep these vars in t, so GC won't collect them and won't break
|
|
|
+ // vars derived via unsafe*
|
|
|
+ t.template = template
|
|
|
+ t.startTag = startTag
|
|
|
+ t.endTag = endTag
|
|
|
+ t.texts = t.texts[:0]
|
|
|
+ t.tags = t.tags[:0]
|
|
|
|
|
|
if len(startTag) == 0 {
|
|
|
panic("startTag cannot be empty")
|
|
|
@@ -52,9 +90,21 @@ func NewTemplate(template, startTag, endTag string) (*Template, error) {
|
|
|
panic("endTag cannot be empty")
|
|
|
}
|
|
|
|
|
|
- s := []byte(template)
|
|
|
- a := []byte(startTag)
|
|
|
- b := []byte(endTag)
|
|
|
+ s := unsafeString2Bytes(template)
|
|
|
+ a := unsafeString2Bytes(startTag)
|
|
|
+ b := unsafeString2Bytes(endTag)
|
|
|
+
|
|
|
+ tagsCount := bytes.Count(s, a)
|
|
|
+ if tagsCount == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if tagsCount+1 > cap(t.texts) {
|
|
|
+ t.texts = make([][]byte, 0, tagsCount+1)
|
|
|
+ }
|
|
|
+ if tagsCount > cap(t.tags) {
|
|
|
+ t.tags = make([]string, 0, tagsCount)
|
|
|
+ }
|
|
|
|
|
|
for {
|
|
|
n := bytes.Index(s, a)
|
|
|
@@ -67,29 +117,16 @@ func NewTemplate(template, startTag, endTag string) (*Template, error) {
|
|
|
s = s[n+len(a):]
|
|
|
n = bytes.Index(s, b)
|
|
|
if n < 0 {
|
|
|
- return nil, fmt.Errorf("Cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
|
|
|
+ return fmt.Errorf("Cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
|
|
|
}
|
|
|
|
|
|
- t.tags = append(t.tags, string(s[:n]))
|
|
|
+ t.tags = append(t.tags, unsafeBytes2String(s[:n]))
|
|
|
s = s[n+len(b):]
|
|
|
}
|
|
|
|
|
|
- t.bytesBufferPool.New = newBytesBuffer
|
|
|
- return &t, nil
|
|
|
-}
|
|
|
-
|
|
|
-func newBytesBuffer() interface{} {
|
|
|
- return &bytes.Buffer{}
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-// TagFunc can be used as a substitution value in the map passed to Execute*.
|
|
|
-// Execute* functions pass tag (placeholder) name in 'tag' argument.
|
|
|
-//
|
|
|
-// TagFunc must be safe to call from concurrently running goroutines.
|
|
|
-//
|
|
|
-// TagFunc must write contents to w and return the number of bytes written.
|
|
|
-type TagFunc func(w io.Writer, tag string) (int, error)
|
|
|
-
|
|
|
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
|
|
|
//
|
|
|
// Returns the number of bytes written to w.
|
|
|
@@ -97,6 +134,11 @@ func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) {
|
|
|
var nn int64
|
|
|
|
|
|
n := len(t.texts) - 1
|
|
|
+ if n == -1 {
|
|
|
+ ni, err := w.Write(unsafeString2Bytes(t.template))
|
|
|
+ return int64(ni), err
|
|
|
+ }
|
|
|
+
|
|
|
for i := 0; i < n; i++ {
|
|
|
ni, err := w.Write(t.texts[i])
|
|
|
if err != nil {
|