123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- // 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))
- }
- }
|