123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package raymond
- import (
- "fmt"
- "io/ioutil"
- "reflect"
- "runtime"
- "sync"
- "github.com/aymerick/raymond/ast"
- "github.com/aymerick/raymond/parser"
- )
- // Template represents a handlebars template.
- type Template struct {
- source string
- program *ast.Program
- helpers map[string]reflect.Value
- partials map[string]*partial
- mutex sync.RWMutex // protects helpers and partials
- }
- // newTemplate instanciate a new template without parsing it
- func newTemplate(source string) *Template {
- return &Template{
- source: source,
- helpers: make(map[string]reflect.Value),
- partials: make(map[string]*partial),
- }
- }
- // Parse instanciates a template by parsing given source.
- func Parse(source string) (*Template, error) {
- tpl := newTemplate(source)
- // parse template
- if err := tpl.parse(); err != nil {
- return nil, err
- }
- return tpl, nil
- }
- // MustParse instanciates a template by parsing given source. It panics on error.
- func MustParse(source string) *Template {
- result, err := Parse(source)
- if err != nil {
- panic(err)
- }
- return result
- }
- // ParseFile reads given file and returns parsed template.
- func ParseFile(filePath string) (*Template, error) {
- b, err := ioutil.ReadFile(filePath)
- if err != nil {
- return nil, err
- }
- return Parse(string(b))
- }
- // parse parses the template
- //
- // It can be called several times, the parsing will be done only once.
- func (tpl *Template) parse() error {
- if tpl.program == nil {
- var err error
- tpl.program, err = parser.Parse(tpl.source)
- if err != nil {
- return err
- }
- }
- return nil
- }
- // Clone returns a copy of that template.
- func (tpl *Template) Clone() *Template {
- result := newTemplate(tpl.source)
- result.program = tpl.program
- tpl.mutex.RLock()
- defer tpl.mutex.RUnlock()
- for name, helper := range tpl.helpers {
- result.RegisterHelper(name, helper.Interface())
- }
- for name, partial := range tpl.partials {
- result.addPartial(name, partial.source, partial.tpl)
- }
- return result
- }
- func (tpl *Template) findHelper(name string) reflect.Value {
- tpl.mutex.RLock()
- defer tpl.mutex.RUnlock()
- return tpl.helpers[name]
- }
- // RegisterHelper registers a helper for that template.
- func (tpl *Template) RegisterHelper(name string, helper interface{}) {
- tpl.mutex.Lock()
- defer tpl.mutex.Unlock()
- if tpl.helpers[name] != zero {
- panic(fmt.Sprintf("Helper %s already registered", name))
- }
- val := reflect.ValueOf(helper)
- ensureValidHelper(name, val)
- tpl.helpers[name] = val
- }
- // RegisterHelpers registers several helpers for that template.
- func (tpl *Template) RegisterHelpers(helpers map[string]interface{}) {
- for name, helper := range helpers {
- tpl.RegisterHelper(name, helper)
- }
- }
- func (tpl *Template) addPartial(name string, source string, template *Template) {
- tpl.mutex.Lock()
- defer tpl.mutex.Unlock()
- if tpl.partials[name] != nil {
- panic(fmt.Sprintf("Partial %s already registered", name))
- }
- tpl.partials[name] = newPartial(name, source, template)
- }
- func (tpl *Template) findPartial(name string) *partial {
- tpl.mutex.RLock()
- defer tpl.mutex.RUnlock()
- return tpl.partials[name]
- }
- // RegisterPartial registers a partial for that template.
- func (tpl *Template) RegisterPartial(name string, source string) {
- tpl.addPartial(name, source, nil)
- }
- // RegisterPartials registers several partials for that template.
- func (tpl *Template) RegisterPartials(partials map[string]string) {
- for name, partial := range partials {
- tpl.RegisterPartial(name, partial)
- }
- }
- // RegisterPartialFile reads given file and registers its content as a partial with given name.
- func (tpl *Template) RegisterPartialFile(filePath string, name string) error {
- b, err := ioutil.ReadFile(filePath)
- if err != nil {
- return err
- }
- tpl.RegisterPartial(name, string(b))
- return nil
- }
- // RegisterPartialFiles reads several files and registers them as partials, the filename base is used as the partial name.
- func (tpl *Template) RegisterPartialFiles(filePaths ...string) error {
- if len(filePaths) == 0 {
- return nil
- }
- for _, filePath := range filePaths {
- name := fileBase(filePath)
- if err := tpl.RegisterPartialFile(filePath, name); err != nil {
- return err
- }
- }
- return nil
- }
- // RegisterPartialTemplate registers an already parsed partial for that template.
- func (tpl *Template) RegisterPartialTemplate(name string, template *Template) {
- tpl.addPartial(name, "", template)
- }
- // Exec evaluates template with given context.
- func (tpl *Template) Exec(ctx interface{}) (result string, err error) {
- return tpl.ExecWith(ctx, nil)
- }
- // MustExec evaluates template with given context. It panics on error.
- func (tpl *Template) MustExec(ctx interface{}) string {
- result, err := tpl.Exec(ctx)
- if err != nil {
- panic(err)
- }
- return result
- }
- // ExecWith evaluates template with given context and private data frame.
- func (tpl *Template) ExecWith(ctx interface{}, privData *DataFrame) (result string, err error) {
- defer errRecover(&err)
- // parses template if necessary
- err = tpl.parse()
- if err != nil {
- return
- }
- // setup visitor
- v := newEvalVisitor(tpl, ctx, privData)
- // visit AST
- result, _ = tpl.program.Accept(v).(string)
- // named return values
- return
- }
- // errRecover recovers evaluation panic
- func errRecover(errp *error) {
- e := recover()
- if e != nil {
- switch err := e.(type) {
- case runtime.Error:
- panic(e)
- case error:
- *errp = err
- default:
- panic(e)
- }
- }
- }
- // PrintAST returns string representation of parsed template.
- func (tpl *Template) PrintAST() string {
- if err := tpl.parse(); err != nil {
- return fmt.Sprintf("PARSER ERROR: %s", err)
- }
- return ast.Print(tpl.program)
- }
|