engine.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // Copyright 2017 Frédéric Guillot. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. package template // import "miniflux.app/template"
  5. import (
  6. "bytes"
  7. "embed"
  8. "html/template"
  9. "strings"
  10. "time"
  11. "miniflux.app/errors"
  12. "miniflux.app/locale"
  13. "miniflux.app/logger"
  14. "github.com/gorilla/mux"
  15. )
  16. //go:embed templates/common/*.html
  17. var commonTemplateFiles embed.FS
  18. //go:embed templates/views/*.html
  19. var viewTemplateFiles embed.FS
  20. // Engine handles the templating system.
  21. type Engine struct {
  22. templates map[string]*template.Template
  23. funcMap *funcMap
  24. }
  25. // NewEngine returns a new template engine.
  26. func NewEngine(router *mux.Router) *Engine {
  27. return &Engine{
  28. templates: make(map[string]*template.Template),
  29. funcMap: &funcMap{router},
  30. }
  31. }
  32. // ParseTemplates parses template files embed into the application.
  33. func (e *Engine) ParseTemplates() error {
  34. var commonTemplateContents strings.Builder
  35. dirEntries, err := commonTemplateFiles.ReadDir("templates/common")
  36. if err != nil {
  37. return err
  38. }
  39. for _, dirEntry := range dirEntries {
  40. fileData, err := commonTemplateFiles.ReadFile("templates/common/" + dirEntry.Name())
  41. if err != nil {
  42. return err
  43. }
  44. commonTemplateContents.Write(fileData)
  45. }
  46. dirEntries, err = viewTemplateFiles.ReadDir("templates/views")
  47. if err != nil {
  48. return err
  49. }
  50. for _, dirEntry := range dirEntries {
  51. templateName := dirEntry.Name()
  52. fileData, err := viewTemplateFiles.ReadFile("templates/views/" + dirEntry.Name())
  53. if err != nil {
  54. return err
  55. }
  56. var templateContents strings.Builder
  57. templateContents.WriteString(commonTemplateContents.String())
  58. templateContents.Write(fileData)
  59. logger.Debug("[Template] Parsing: %s", templateName)
  60. e.templates[templateName] = template.Must(template.New("main").Funcs(e.funcMap.Map()).Parse(templateContents.String()))
  61. }
  62. return nil
  63. }
  64. // Render process a template.
  65. func (e *Engine) Render(name, language string, data interface{}) []byte {
  66. tpl, ok := e.templates[name]
  67. if !ok {
  68. logger.Fatal("[Template] The template %s does not exists", name)
  69. }
  70. printer := locale.NewPrinter(language)
  71. // Functions that need to be declared at runtime.
  72. tpl.Funcs(template.FuncMap{
  73. "elapsed": func(timezone string, t time.Time) string {
  74. return elapsedTime(printer, timezone, t)
  75. },
  76. "t": func(key interface{}, args ...interface{}) string {
  77. switch k := key.(type) {
  78. case string:
  79. return printer.Printf(k, args...)
  80. case errors.LocalizedError:
  81. return k.Localize(printer)
  82. case *errors.LocalizedError:
  83. return k.Localize(printer)
  84. case error:
  85. return k.Error()
  86. default:
  87. return ""
  88. }
  89. },
  90. "plural": func(key string, n int, args ...interface{}) string {
  91. return printer.Plural(key, n, args...)
  92. },
  93. })
  94. var b bytes.Buffer
  95. err := tpl.ExecuteTemplate(&b, "base", data)
  96. if err != nil {
  97. logger.Fatal("[Template] Unable to render template: %v", err)
  98. }
  99. return b.Bytes()
  100. }