-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontainer.go
More file actions
160 lines (143 loc) · 3.65 KB
/
container.go
File metadata and controls
160 lines (143 loc) · 3.65 KB
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package hx
import (
"fmt"
"io"
"strings"
"time"
"github.com/shayanderson/hx/attr"
)
// Cache holds container caching configuration
type Cache struct {
Disable bool // disable caching for this container
KeyPrefix string // prefix to add to cache keys
TTL time.Duration // time-to-live duration for cached items, 0 means no expiration
}
// Renderer represents a container that can render itself to an io.Writer
type Renderer interface {
// Render writes the rendered output to the provided io.Writer
Render(io.Writer) error
}
// Container represents a container that can hold nodes
type Container struct {
Cache // caching configuration
Nodes []any
}
// Append adds nodes to the container
func (c *Container) Append(v ...any) *Container {
c.Nodes = append(c.Nodes, v...)
return c
}
// Render writes the rendered output of the container to the provided io.Writer
func (c Container) Render(w io.Writer) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("hx.Container.Render: %v", r)
}
}()
var s string
k := makeCacheKey(c.Cache.KeyPrefix, c.Nodes)
if v, ok := cacheLoad(c.Cache, k); ok {
s = *v
} else {
var b strings.Builder
for _, v := range c.Nodes {
b.WriteString(makeString(v, true))
}
s = b.String()
cachePut(c.Cache, k, &s)
}
_, err = w.Write([]byte(s))
return err
}
// Doc represents an HTML document container
type Doc struct {
Cache // caching configuration
Attrs attr.Attrs // document attributes
Body Body // document body
Head Head // document head
}
// Append adds nodes to the document body
func (d *Doc) Append(v ...any) *Doc {
d.Body = append(d.Body, v...)
return d
}
// Render writes the rendered output of the document to the provided io.Writer
func (d Doc) Render(w io.Writer) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("hx.Doc.Render: %v", r)
}
}()
var s string
k := makeCacheKey(d.Cache.KeyPrefix, d.Attrs, d.Head, d.Body)
if v, ok := cacheLoad(d.Cache, k); ok {
s = *v
} else {
r := []any{}
for _, a := range d.Attrs {
r = append(r, a)
}
r = append(r, d.Head, d.Body)
s = Element{
Nodes: r,
Tag: Tag{
Name: "html",
prepend: "<!DOCTYPE html>",
},
}.String()
cachePut(d.Cache, k, &s)
}
_, err = w.Write([]byte(s))
return err
}
// Group represents a group of renderers
type Group []Renderer
// Append adds a renderer to the group
func (g *Group) Append(r Renderer) Group {
*g = append(*g, r)
return *g
}
// Render writes the rendered output of the group to the provided io.Writer
func (g *Group) Render(w io.Writer) error {
for _, r := range *g {
if r == nil {
continue
}
if err := r.Render(w); err != nil {
return err
}
}
return nil
}
// cacheLoad attempts to load a cached value for the given key
// returns the cached value and true if found, otherwise nil and false
// respects the container's caching configuration and global cache settings
func cacheLoad(c Cache, key string) (*string, bool) {
cfg := defaultConfig.get()
if cfg.CacheDisable || c.Disable {
return nil, false
}
if v, ok := cfg.Cacher.Get(key); ok {
return &v.Value, true
}
return nil, false
}
// cachePut stores a value in the cache with the given key
// respects the container's caching configuration and global cache settings
func cachePut(c Cache, key string, value *string) {
cfg := defaultConfig.get()
if cfg.CacheDisable || c.Disable {
return
}
var ttl time.Duration
if c.TTL > 0 { // specific ttl is priority
ttl = c.TTL
} else { // default ttl is fallback
ttl = cfg.CacheTTL
}
defaultConfig.get().Cacher.Set(&CacheItem{
Key: key,
TTL: ttl,
Value: *value,
})
}