/
html_renderer.go
135 lines (111 loc) Β· 3.39 KB
/
html_renderer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package chttp
import (
"html/template"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/gocopper/copper/clogger"
"github.com/gocopper/copper/cerrors"
)
type (
// HTMLDir is a directory that can be embedded or found on the host system. It should contain sub-directories
// and files to support the WriteHTML function in ReaderWriter.
HTMLDir fs.FS
// StaticDir represents a directory that holds static resources (JS, CSS, images, etc.)
StaticDir fs.FS
// HTMLRenderer provides functionality in rendering templatized HTML along with HTML components
HTMLRenderer struct {
htmlDir HTMLDir
staticDir StaticDir
renderFuncs []HTMLRenderFunc
}
// HTMLRenderFunc can be used to register new template functions
HTMLRenderFunc struct {
// Name for the function that can be invoked in a template
Name string
// Func should return a function that takes in any number of params and returns either a single return value,
// or two return values of which the second has type error.
Func func(r *http.Request) interface{}
}
// NewHTMLRendererParams holds the params needed to create HTMLRenderer
NewHTMLRendererParams struct {
HTMLDir HTMLDir
StaticDir StaticDir
RenderFuncs []HTMLRenderFunc
Config Config
Logger clogger.Logger
}
)
// NewHTMLRenderer creates a new HTMLRenderer with HTML templates stored in dir and registers the provided HTML
// components
func NewHTMLRenderer(p NewHTMLRendererParams) (*HTMLRenderer, error) {
hr := HTMLRenderer{
htmlDir: p.HTMLDir,
staticDir: p.StaticDir,
renderFuncs: p.RenderFuncs,
}
if p.Config.UseLocalHTML {
wd, err := os.Getwd()
if err != nil {
return nil, cerrors.New(err, "failed to get current working directory", nil)
}
hr.htmlDir = os.DirFS(filepath.Join(wd, "web"))
}
return &hr, nil
}
func (r *HTMLRenderer) funcMap(req *http.Request) template.FuncMap {
var funcMap = template.FuncMap{
"partial": r.partial(req),
}
for i := range r.renderFuncs {
funcMap[r.renderFuncs[i].Name] = r.renderFuncs[i].Func(req)
}
return funcMap
}
func (r *HTMLRenderer) render(req *http.Request, layout, page string, data interface{}) (template.HTML, error) {
var dest strings.Builder
tmpl, err := template.New(layout).
Funcs(r.funcMap(req)).
ParseFS(r.htmlDir,
path.Join("src", "layouts", layout),
path.Join("src", "pages", page),
)
if err != nil {
return "", cerrors.New(err, "failed to parse templates in html dir", map[string]interface{}{
"layout": layout,
"page": page,
})
}
err = tmpl.Execute(&dest, data)
if err != nil {
return "", cerrors.New(err, "failed to execute template", nil)
}
//nolint:gosec
return template.HTML(dest.String()), nil
}
func (r *HTMLRenderer) partial(req *http.Request) func(name string, data interface{}) (template.HTML, error) {
return func(name string, data interface{}) (template.HTML, error) {
var dest strings.Builder
tmpl, err := template.New(name+".html").
Funcs(r.funcMap(req)).
ParseFS(r.htmlDir,
path.Join("src", "partials", "*.html"),
)
if err != nil {
return "", cerrors.New(err, "failed to parse partial template", map[string]interface{}{
"name": name,
})
}
err = tmpl.Execute(&dest, data)
if err != nil {
return "", cerrors.New(err, "failed to execute partial template", map[string]interface{}{
"name": name,
})
}
//nolint:gosec
return template.HTML(dest.String()), nil
}
}