engine.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package template // import "miniflux.app/v2/internal/template"
  4. import (
  5. "bytes"
  6. "embed"
  7. "html/template"
  8. "log/slog"
  9. "strings"
  10. "time"
  11. "miniflux.app/v2/internal/errors"
  12. "miniflux.app/v2/internal/locale"
  13. "github.com/gorilla/mux"
  14. )
  15. //go:embed templates/common/*.html
  16. var commonTemplateFiles embed.FS
  17. //go:embed templates/views/*.html
  18. var viewTemplateFiles embed.FS
  19. //go:embed templates/standalone/*.html
  20. var standaloneTemplateFiles embed.FS
  21. // Engine handles the templating system.
  22. type Engine struct {
  23. templates map[string]*template.Template
  24. funcMap *funcMap
  25. }
  26. // NewEngine returns a new template engine.
  27. func NewEngine(router *mux.Router) *Engine {
  28. return &Engine{
  29. templates: make(map[string]*template.Template),
  30. funcMap: &funcMap{router},
  31. }
  32. }
  33. // ParseTemplates parses template files embed into the application.
  34. func (e *Engine) ParseTemplates() error {
  35. var commonTemplateContents strings.Builder
  36. dirEntries, err := commonTemplateFiles.ReadDir("templates/common")
  37. if err != nil {
  38. return err
  39. }
  40. for _, dirEntry := range dirEntries {
  41. fileData, err := commonTemplateFiles.ReadFile("templates/common/" + dirEntry.Name())
  42. if err != nil {
  43. return err
  44. }
  45. commonTemplateContents.Write(fileData)
  46. }
  47. dirEntries, err = viewTemplateFiles.ReadDir("templates/views")
  48. if err != nil {
  49. return err
  50. }
  51. for _, dirEntry := range dirEntries {
  52. templateName := dirEntry.Name()
  53. fileData, err := viewTemplateFiles.ReadFile("templates/views/" + dirEntry.Name())
  54. if err != nil {
  55. return err
  56. }
  57. var templateContents strings.Builder
  58. templateContents.WriteString(commonTemplateContents.String())
  59. templateContents.Write(fileData)
  60. slog.Debug("Parsing template",
  61. slog.String("template_name", templateName),
  62. )
  63. e.templates[templateName] = template.Must(template.New("main").Funcs(e.funcMap.Map()).Parse(templateContents.String()))
  64. }
  65. dirEntries, err = standaloneTemplateFiles.ReadDir("templates/standalone")
  66. if err != nil {
  67. return err
  68. }
  69. for _, dirEntry := range dirEntries {
  70. templateName := dirEntry.Name()
  71. fileData, err := standaloneTemplateFiles.ReadFile("templates/standalone/" + dirEntry.Name())
  72. if err != nil {
  73. return err
  74. }
  75. slog.Debug("Parsing template",
  76. slog.String("template_name", templateName),
  77. )
  78. e.templates[templateName] = template.Must(template.New("base").Funcs(e.funcMap.Map()).Parse(string(fileData)))
  79. }
  80. return nil
  81. }
  82. // Render process a template.
  83. func (e *Engine) Render(name string, data map[string]interface{}) []byte {
  84. tpl, ok := e.templates[name]
  85. if !ok {
  86. panic("This template does not exists: " + name)
  87. }
  88. printer := locale.NewPrinter(data["language"].(string))
  89. // Functions that need to be declared at runtime.
  90. tpl.Funcs(template.FuncMap{
  91. "elapsed": func(timezone string, t time.Time) string {
  92. return elapsedTime(printer, timezone, t)
  93. },
  94. "t": func(key interface{}, args ...interface{}) string {
  95. switch k := key.(type) {
  96. case string:
  97. return printer.Printf(k, args...)
  98. case errors.LocalizedError:
  99. return k.Localize(printer)
  100. case *errors.LocalizedError:
  101. return k.Localize(printer)
  102. case error:
  103. return k.Error()
  104. default:
  105. return ""
  106. }
  107. },
  108. "plural": func(key string, n int, args ...interface{}) string {
  109. return printer.Plural(key, n, args...)
  110. },
  111. })
  112. var b bytes.Buffer
  113. err := tpl.ExecuteTemplate(&b, "base", data)
  114. if err != nil {
  115. panic(err)
  116. }
  117. return b.Bytes()
  118. }