template.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. // Package fasttemplate implements simple and fast template library.
  2. //
  3. // Fasttemplate is faster than text/template, strings.Replace
  4. // and strings.Replacer.
  5. //
  6. // Fasttemplate ideally fits for fast and simple placeholders' substitutions.
  7. package fasttemplate
  8. import (
  9. "bytes"
  10. "fmt"
  11. "io"
  12. "sync"
  13. )
  14. // Template implements simple template engine, which can be used for fast
  15. // tags' (aka placeholders) substitution.
  16. type Template struct {
  17. texts [][]byte
  18. tags []string
  19. bytesBufferPool sync.Pool
  20. }
  21. // NewTemplate parses the given template using the given startTag and endTag
  22. // as tag start and tag end.
  23. //
  24. // The returned template can be executed by concurrently running goroutines
  25. // using Execute* methods.
  26. func NewTemplate(template, startTag, endTag string) (*Template, error) {
  27. var t Template
  28. if len(startTag) == 0 {
  29. panic("startTag cannot be empty")
  30. }
  31. if len(endTag) == 0 {
  32. panic("endTag cannot be empty")
  33. }
  34. s := []byte(template)
  35. a := []byte(startTag)
  36. b := []byte(endTag)
  37. for {
  38. n := bytes.Index(s, a)
  39. if n < 0 {
  40. t.texts = append(t.texts, s)
  41. break
  42. }
  43. t.texts = append(t.texts, s[:n])
  44. s = s[n+len(a):]
  45. n = bytes.Index(s, b)
  46. if n < 0 {
  47. return nil, fmt.Errorf("Cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
  48. }
  49. t.tags = append(t.tags, string(s[:n]))
  50. s = s[n+len(b):]
  51. }
  52. t.bytesBufferPool.New = newBytesBuffer
  53. return &t, nil
  54. }
  55. func newBytesBuffer() interface{} {
  56. return &bytes.Buffer{}
  57. }
  58. // TagFunc can be used as a substitution value in the map passed to Execute*.
  59. // Execute* functions pass tag (placeholder) name in 'tag' argument.
  60. //
  61. // TagFunc must be safe to call from concurrently running goroutines.
  62. //
  63. // TagFunc must write contents to w and return the number of bytes written.
  64. type TagFunc func(w io.Writer, tag string) (int, error)
  65. // ExecuteFunc calls f on each template tag (placeholder) occurence.
  66. //
  67. // Returns the number of bytes written to w.
  68. func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) {
  69. var nn int64
  70. n := len(t.texts) - 1
  71. for i := 0; i < n; i++ {
  72. ni, err := w.Write(t.texts[i])
  73. if err != nil {
  74. return nn, err
  75. }
  76. nn += int64(ni)
  77. if ni, err = f(w, t.tags[i]); err != nil {
  78. return nn, err
  79. }
  80. nn += int64(ni)
  81. }
  82. ni, err := w.Write(t.texts[n])
  83. if err != nil {
  84. return nn, err
  85. }
  86. nn += int64(ni)
  87. return nn, nil
  88. }
  89. // Execute substitutes template tags (placeholders) with the corresponding
  90. // values from the map m and writes the result to the given writer w.
  91. //
  92. // Substitution map m may contain values with the following types:
  93. // * []byte - the fastest value type
  94. // * string - convenient value type
  95. // * TagFunc - flexible value type
  96. //
  97. // Returns the number of bytes written to w.
  98. func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) {
  99. return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
  100. }
  101. // ExecuteFuncString call f on each template tag (placeholder) occurence
  102. // and substitutes it with the data written to TagFunc's w.
  103. //
  104. // Returns the resulting string.
  105. func (t *Template) ExecuteFuncString(f TagFunc) string {
  106. w := t.bytesBufferPool.Get().(*bytes.Buffer)
  107. if _, err := t.ExecuteFunc(w, f); err != nil {
  108. panic(fmt.Sprintf("unexpected error: %s", err))
  109. }
  110. s := string(w.Bytes())
  111. w.Reset()
  112. t.bytesBufferPool.Put(w)
  113. return s
  114. }
  115. // ExecuteString substitutes template tags (placeholders) with the corresponding
  116. // values from the map m and returns the result.
  117. //
  118. // Substitution map m may contain values with the following types:
  119. // * []byte - the fastest value type
  120. // * string - convenient value type
  121. // * TagFunc - flexible value type
  122. //
  123. func (t *Template) ExecuteString(m map[string]interface{}) string {
  124. return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
  125. }
  126. func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {
  127. v := m[tag]
  128. if v == nil {
  129. return 0, nil
  130. }
  131. switch value := v.(type) {
  132. case []byte:
  133. return w.Write(value)
  134. case string:
  135. return w.Write([]byte(value))
  136. case TagFunc:
  137. return value(w, tag)
  138. default:
  139. panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
  140. }
  141. }