You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

130 lines
3.3 KiB

  1. package templates
  2. import (
  3. "errors"
  4. "io"
  5. "net/http"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "sync"
  10. "github.com/PuerkitoBio/ghost"
  11. )
  12. var (
  13. ErrTemplateNotExist = errors.New("template does not exist")
  14. ErrDirNotExist = errors.New("directory does not exist")
  15. compilers = make(map[string]TemplateCompiler)
  16. // The mutex guards the templaters map
  17. mu sync.RWMutex
  18. templaters = make(map[string]Templater)
  19. )
  20. // Defines the interface that the template compiler must return. The Go native
  21. // templates implement this interface.
  22. type Templater interface {
  23. Execute(wr io.Writer, data interface{}) error
  24. }
  25. // The interface that a template engine must implement to be used by Ghost.
  26. type TemplateCompiler interface {
  27. Compile(fileName string) (Templater, error)
  28. }
  29. // TODO : How to manage Go nested templates?
  30. // TODO : Support Go's port of the mustache template?
  31. // Register a template compiler for the specified extension. Extensions are case-sensitive.
  32. // The extension must start with a dot (it is compared to the result of path.Ext() on a
  33. // given file name).
  34. //
  35. // Registering is not thread-safe. Compilers should be registered before the http server
  36. // is started.
  37. // Compiling templates, on the other hand, is thread-safe.
  38. func Register(ext string, c TemplateCompiler) {
  39. if c == nil {
  40. panic("ghost: Register TemplateCompiler is nil")
  41. }
  42. if _, dup := compilers[ext]; dup {
  43. panic("ghost: Register called twice for extension " + ext)
  44. }
  45. compilers[ext] = c
  46. }
  47. // Compile all templates that have a matching compiler (based on their extension) in the
  48. // specified directory.
  49. func CompileDir(dir string) error {
  50. mu.Lock()
  51. defer mu.Unlock()
  52. return filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
  53. if fi == nil {
  54. return ErrDirNotExist
  55. }
  56. if !fi.IsDir() {
  57. err = compileTemplate(path, dir)
  58. if err != nil {
  59. ghost.LogFn("ghost.templates : error compiling template %s : %s", path, err)
  60. return err
  61. }
  62. }
  63. return nil
  64. })
  65. }
  66. // Compile a single template file, using the specified base directory. The base
  67. // directory is used to set the name of the template (the part of the path relative to this
  68. // base directory is used as the name of the template).
  69. func Compile(path, base string) error {
  70. mu.Lock()
  71. defer mu.Unlock()
  72. return compileTemplate(path, base)
  73. }
  74. // Compile the specified template file if there is a matching compiler.
  75. func compileTemplate(p, base string) error {
  76. ext := path.Ext(p)
  77. c, ok := compilers[ext]
  78. // Ignore file if no template compiler exist for this extension
  79. if ok {
  80. t, err := c.Compile(p)
  81. if err != nil {
  82. return err
  83. }
  84. key, err := filepath.Rel(base, p)
  85. if err != nil {
  86. return err
  87. }
  88. ghost.LogFn("ghost.templates : storing template for file %s", key)
  89. templaters[key] = t
  90. }
  91. return nil
  92. }
  93. // Execute the template.
  94. func Execute(tplName string, w io.Writer, data interface{}) error {
  95. mu.RLock()
  96. t, ok := templaters[tplName]
  97. mu.RUnlock()
  98. if !ok {
  99. return ErrTemplateNotExist
  100. }
  101. return t.Execute(w, data)
  102. }
  103. // Render is the same as Execute, except that it takes a http.ResponseWriter
  104. // instead of a generic io.Writer, and sets the Content-Type to text/html.
  105. func Render(tplName string, w http.ResponseWriter, data interface{}) (err error) {
  106. w.Header().Set("Content-Type", "text/html")
  107. defer func() {
  108. if err != nil {
  109. w.Header().Del("Content-Type")
  110. }
  111. }()
  112. return Execute(tplName, w, data)
  113. }