engine.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  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/locale"
  12. "github.com/gorilla/mux"
  13. )
  14. //go:embed templates/common/*.html
  15. var commonTemplateFiles embed.FS
  16. //go:embed templates/views/*.html
  17. var viewTemplateFiles embed.FS
  18. //go:embed templates/standalone/*.html
  19. var standaloneTemplateFiles 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. slog.Debug("Parsing template",
  60. slog.String("template_name", templateName),
  61. )
  62. e.templates[templateName] = template.Must(template.New("main").Funcs(e.funcMap.Map()).Parse(templateContents.String()))
  63. }
  64. dirEntries, err = standaloneTemplateFiles.ReadDir("templates/standalone")
  65. if err != nil {
  66. return err
  67. }
  68. for _, dirEntry := range dirEntries {
  69. templateName := dirEntry.Name()
  70. fileData, err := standaloneTemplateFiles.ReadFile("templates/standalone/" + dirEntry.Name())
  71. if err != nil {
  72. return err
  73. }
  74. slog.Debug("Parsing template",
  75. slog.String("template_name", templateName),
  76. )
  77. e.templates[templateName] = template.Must(template.New("base").Funcs(e.funcMap.Map()).Parse(string(fileData)))
  78. }
  79. return nil
  80. }
  81. // Render process a template.
  82. func (e *Engine) Render(name string, data map[string]interface{}) []byte {
  83. tpl, ok := e.templates[name]
  84. if !ok {
  85. panic("This template does not exists: " + name)
  86. }
  87. printer := locale.NewPrinter(data["language"].(string))
  88. // Functions that need to be declared at runtime.
  89. tpl.Funcs(template.FuncMap{
  90. "elapsed": func(timezone string, t time.Time) string {
  91. return elapsedTime(printer, timezone, t)
  92. },
  93. "t": func(key interface{}, args ...interface{}) string {
  94. switch k := key.(type) {
  95. case string:
  96. return printer.Printf(k, args...)
  97. case error:
  98. return k.Error()
  99. default:
  100. return ""
  101. }
  102. },
  103. "plural": func(key string, n int, args ...interface{}) string {
  104. return printer.Plural(key, n, args...)
  105. },
  106. })
  107. var b bytes.Buffer
  108. err := tpl.ExecuteTemplate(&b, "base", data)
  109. if err != nil {
  110. panic(err)
  111. }
  112. return b.Bytes()
  113. }