template.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package raymond
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "reflect"
  6. "runtime"
  7. "sync"
  8. "github.com/aymerick/raymond/ast"
  9. "github.com/aymerick/raymond/parser"
  10. )
  11. // Template represents a handlebars template.
  12. type Template struct {
  13. source string
  14. program *ast.Program
  15. helpers map[string]reflect.Value
  16. partials map[string]*partial
  17. mutex sync.RWMutex // protects helpers and partials
  18. }
  19. // newTemplate instanciate a new template without parsing it
  20. func newTemplate(source string) *Template {
  21. return &Template{
  22. source: source,
  23. helpers: make(map[string]reflect.Value),
  24. partials: make(map[string]*partial),
  25. }
  26. }
  27. // Parse instanciates a template by parsing given source.
  28. func Parse(source string) (*Template, error) {
  29. tpl := newTemplate(source)
  30. // parse template
  31. if err := tpl.parse(); err != nil {
  32. return nil, err
  33. }
  34. return tpl, nil
  35. }
  36. // MustParse instanciates a template by parsing given source. It panics on error.
  37. func MustParse(source string) *Template {
  38. result, err := Parse(source)
  39. if err != nil {
  40. panic(err)
  41. }
  42. return result
  43. }
  44. // ParseFile reads given file and returns parsed template.
  45. func ParseFile(filePath string) (*Template, error) {
  46. b, err := ioutil.ReadFile(filePath)
  47. if err != nil {
  48. return nil, err
  49. }
  50. return Parse(string(b))
  51. }
  52. // parse parses the template
  53. //
  54. // It can be called several times, the parsing will be done only once.
  55. func (tpl *Template) parse() error {
  56. if tpl.program == nil {
  57. var err error
  58. tpl.program, err = parser.Parse(tpl.source)
  59. if err != nil {
  60. return err
  61. }
  62. }
  63. return nil
  64. }
  65. // Clone returns a copy of that template.
  66. func (tpl *Template) Clone() *Template {
  67. result := newTemplate(tpl.source)
  68. result.program = tpl.program
  69. tpl.mutex.RLock()
  70. defer tpl.mutex.RUnlock()
  71. for name, helper := range tpl.helpers {
  72. result.RegisterHelper(name, helper.Interface())
  73. }
  74. for name, partial := range tpl.partials {
  75. result.addPartial(name, partial.source, partial.tpl)
  76. }
  77. return result
  78. }
  79. func (tpl *Template) findHelper(name string) reflect.Value {
  80. tpl.mutex.RLock()
  81. defer tpl.mutex.RUnlock()
  82. return tpl.helpers[name]
  83. }
  84. // RegisterHelper registers a helper for that template.
  85. func (tpl *Template) RegisterHelper(name string, helper interface{}) {
  86. tpl.mutex.Lock()
  87. defer tpl.mutex.Unlock()
  88. if tpl.helpers[name] != zero {
  89. panic(fmt.Sprintf("Helper %s already registered", name))
  90. }
  91. val := reflect.ValueOf(helper)
  92. ensureValidHelper(name, val)
  93. tpl.helpers[name] = val
  94. }
  95. // RegisterHelpers registers several helpers for that template.
  96. func (tpl *Template) RegisterHelpers(helpers map[string]interface{}) {
  97. for name, helper := range helpers {
  98. tpl.RegisterHelper(name, helper)
  99. }
  100. }
  101. func (tpl *Template) addPartial(name string, source string, template *Template) {
  102. tpl.mutex.Lock()
  103. defer tpl.mutex.Unlock()
  104. if tpl.partials[name] != nil {
  105. panic(fmt.Sprintf("Partial %s already registered", name))
  106. }
  107. tpl.partials[name] = newPartial(name, source, template)
  108. }
  109. func (tpl *Template) findPartial(name string) *partial {
  110. tpl.mutex.RLock()
  111. defer tpl.mutex.RUnlock()
  112. return tpl.partials[name]
  113. }
  114. // RegisterPartial registers a partial for that template.
  115. func (tpl *Template) RegisterPartial(name string, source string) {
  116. tpl.addPartial(name, source, nil)
  117. }
  118. // RegisterPartials registers several partials for that template.
  119. func (tpl *Template) RegisterPartials(partials map[string]string) {
  120. for name, partial := range partials {
  121. tpl.RegisterPartial(name, partial)
  122. }
  123. }
  124. // RegisterPartialFile reads given file and registers its content as a partial with given name.
  125. func (tpl *Template) RegisterPartialFile(filePath string, name string) error {
  126. b, err := ioutil.ReadFile(filePath)
  127. if err != nil {
  128. return err
  129. }
  130. tpl.RegisterPartial(name, string(b))
  131. return nil
  132. }
  133. // RegisterPartialFiles reads several files and registers them as partials, the filename base is used as the partial name.
  134. func (tpl *Template) RegisterPartialFiles(filePaths ...string) error {
  135. if len(filePaths) == 0 {
  136. return nil
  137. }
  138. for _, filePath := range filePaths {
  139. name := fileBase(filePath)
  140. if err := tpl.RegisterPartialFile(filePath, name); err != nil {
  141. return err
  142. }
  143. }
  144. return nil
  145. }
  146. // RegisterPartialTemplate registers an already parsed partial for that template.
  147. func (tpl *Template) RegisterPartialTemplate(name string, template *Template) {
  148. tpl.addPartial(name, "", template)
  149. }
  150. // Exec evaluates template with given context.
  151. func (tpl *Template) Exec(ctx interface{}) (result string, err error) {
  152. return tpl.ExecWith(ctx, nil)
  153. }
  154. // MustExec evaluates template with given context. It panics on error.
  155. func (tpl *Template) MustExec(ctx interface{}) string {
  156. result, err := tpl.Exec(ctx)
  157. if err != nil {
  158. panic(err)
  159. }
  160. return result
  161. }
  162. // ExecWith evaluates template with given context and private data frame.
  163. func (tpl *Template) ExecWith(ctx interface{}, privData *DataFrame) (result string, err error) {
  164. defer errRecover(&err)
  165. // parses template if necessary
  166. err = tpl.parse()
  167. if err != nil {
  168. return
  169. }
  170. // setup visitor
  171. v := newEvalVisitor(tpl, ctx, privData)
  172. // visit AST
  173. result, _ = tpl.program.Accept(v).(string)
  174. // named return values
  175. return
  176. }
  177. // errRecover recovers evaluation panic
  178. func errRecover(errp *error) {
  179. e := recover()
  180. if e != nil {
  181. switch err := e.(type) {
  182. case runtime.Error:
  183. panic(e)
  184. case error:
  185. *errp = err
  186. default:
  187. panic(e)
  188. }
  189. }
  190. }
  191. // PrintAST returns string representation of parsed template.
  192. func (tpl *Template) PrintAST() string {
  193. if err := tpl.parse(); err != nil {
  194. return fmt.Sprintf("PARSER ERROR: %s", err)
  195. }
  196. return ast.Print(tpl.program)
  197. }