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