318 lines
8.9 KiB
Go
318 lines
8.9 KiB
Go
// Package fasttemplate implements simple and fast template library.
|
|
//
|
|
// Fasttemplate is faster than text/template, strings.Replace
|
|
// and strings.Replacer.
|
|
//
|
|
// Fasttemplate ideally fits for fast and simple placeholders' substitutions.
|
|
package fasttemplate
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"github.com/valyala/bytebufferpool"
|
|
"io"
|
|
)
|
|
|
|
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.ExecuteFunc for frozen templates.
|
|
func ExecuteFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error) {
|
|
s := unsafeString2Bytes(template)
|
|
a := unsafeString2Bytes(startTag)
|
|
b := unsafeString2Bytes(endTag)
|
|
|
|
var nn int64
|
|
var ni int
|
|
var err error
|
|
for {
|
|
n := bytes.Index(s, a)
|
|
if n < 0 {
|
|
break
|
|
}
|
|
ni, err = w.Write(s[:n])
|
|
nn += int64(ni)
|
|
if err != nil {
|
|
return nn, err
|
|
}
|
|
|
|
s = s[n+len(a):]
|
|
n = bytes.Index(s, b)
|
|
if n < 0 {
|
|
// cannot find end tag - just write it to the output.
|
|
ni, _ = w.Write(a)
|
|
nn += int64(ni)
|
|
break
|
|
}
|
|
|
|
ni, err = f(w, unsafeBytes2String(s[:n]))
|
|
nn += int64(ni)
|
|
s = s[n+len(b):]
|
|
}
|
|
ni, err = w.Write(s)
|
|
nn += int64(ni)
|
|
|
|
return nn, err
|
|
}
|
|
|
|
// Execute substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and writes the result to the given writer w.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.Execute for frozen templates.
|
|
func Execute(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {
|
|
return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
|
|
// and substitutes it with the data written to TagFunc's w.
|
|
//
|
|
// Returns the resulting string.
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.ExecuteFuncString for frozen templates.
|
|
func ExecuteFuncString(template, startTag, endTag string, f TagFunc) string {
|
|
tagsCount := bytes.Count(unsafeString2Bytes(template), unsafeString2Bytes(startTag))
|
|
if tagsCount == 0 {
|
|
return template
|
|
}
|
|
|
|
bb := byteBufferPool.Get()
|
|
if _, err := ExecuteFunc(template, startTag, endTag, bb, f); err != nil {
|
|
panic(fmt.Sprintf("unexpected error: %s", err))
|
|
}
|
|
s := string(bb.B)
|
|
bb.Reset()
|
|
byteBufferPool.Put(bb)
|
|
return s
|
|
}
|
|
|
|
var byteBufferPool bytebufferpool.Pool
|
|
|
|
// ExecuteString substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and returns the result.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// This function is optimized for constantly changing templates.
|
|
// Use Template.ExecuteString for frozen templates.
|
|
func ExecuteString(template, startTag, endTag string, m map[string]interface{}) string {
|
|
return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
// 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
|
|
byteBufferPool bytebufferpool.Pool
|
|
}
|
|
|
|
// New parses the given template using the given startTag and endTag
|
|
// as tag start and tag end.
|
|
//
|
|
// The returned template can be executed by concurrently running goroutines
|
|
// using Execute* methods.
|
|
//
|
|
// New panics if the given template cannot be parsed. Use NewTemplate instead
|
|
// if template may contain errors.
|
|
func New(template, startTag, endTag string) *Template {
|
|
t, err := NewTemplate(template, startTag, endTag)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return t
|
|
}
|
|
|
|
// NewTemplate parses the given template using the given startTag and endTag
|
|
// as tag start and tag end.
|
|
//
|
|
// The returned template can be executed by concurrently running goroutines
|
|
// using Execute* methods.
|
|
func NewTemplate(template, startTag, endTag string) (*Template, error) {
|
|
var t Template
|
|
err := t.Reset(template, startTag, endTag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &t, 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)
|
|
|
|
// 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")
|
|
}
|
|
if len(endTag) == 0 {
|
|
panic("endTag cannot be empty")
|
|
}
|
|
|
|
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)
|
|
if n < 0 {
|
|
t.texts = append(t.texts, s)
|
|
break
|
|
}
|
|
t.texts = append(t.texts, s[:n])
|
|
|
|
s = s[n+len(a):]
|
|
n = bytes.Index(s, b)
|
|
if n < 0 {
|
|
return fmt.Errorf("Cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
|
|
}
|
|
|
|
t.tags = append(t.tags, unsafeBytes2String(s[:n]))
|
|
s = s[n+len(b):]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExecuteFunc calls f on each template tag (placeholder) occurrence.
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
//
|
|
// This function is optimized for frozen templates.
|
|
// Use ExecuteFunc for constantly changing templates.
|
|
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])
|
|
nn += int64(ni)
|
|
if err != nil {
|
|
return nn, err
|
|
}
|
|
|
|
ni, err = f(w, t.tags[i])
|
|
nn += int64(ni)
|
|
if err != nil {
|
|
return nn, err
|
|
}
|
|
}
|
|
ni, err := w.Write(t.texts[n])
|
|
nn += int64(ni)
|
|
return nn, err
|
|
}
|
|
|
|
// Execute substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and writes the result to the given writer w.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// Returns the number of bytes written to w.
|
|
func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) {
|
|
return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
// ExecuteFuncString calls f on each template tag (placeholder) occurrence
|
|
// and substitutes it with the data written to TagFunc's w.
|
|
//
|
|
// Returns the resulting string.
|
|
//
|
|
// This function is optimized for frozen templates.
|
|
// Use ExecuteFuncString for constantly changing templates.
|
|
func (t *Template) ExecuteFuncString(f TagFunc) string {
|
|
bb := t.byteBufferPool.Get()
|
|
if _, err := t.ExecuteFunc(bb, f); err != nil {
|
|
panic(fmt.Sprintf("unexpected error: %s", err))
|
|
}
|
|
s := string(bb.Bytes())
|
|
bb.Reset()
|
|
t.byteBufferPool.Put(bb)
|
|
return s
|
|
}
|
|
|
|
// ExecuteString substitutes template tags (placeholders) with the corresponding
|
|
// values from the map m and returns the result.
|
|
//
|
|
// Substitution map m may contain values with the following types:
|
|
// * []byte - the fastest value type
|
|
// * string - convenient value type
|
|
// * TagFunc - flexible value type
|
|
//
|
|
// This function is optimized for frozen templates.
|
|
// Use ExecuteString for constantly changing templates.
|
|
func (t *Template) ExecuteString(m map[string]interface{}) string {
|
|
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
|
|
}
|
|
|
|
func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {
|
|
v := m[tag]
|
|
if v == nil {
|
|
return 0, nil
|
|
}
|
|
switch value := v.(type) {
|
|
case []byte:
|
|
return w.Write(value)
|
|
case string:
|
|
return w.Write([]byte(value))
|
|
case TagFunc:
|
|
return value(w, tag)
|
|
default:
|
|
panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
|
|
}
|
|
}
|