-
Notifications
You must be signed in to change notification settings - Fork 0
/
builder.go
161 lines (126 loc) · 3.95 KB
/
builder.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
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
161
package html
import (
"io"
"strings"
)
// Builder collects tags and attributes into a strings.Builder efficiently.
type Builder struct {
*strings.Builder
last *Element
}
// New generates a fully initialized Builder
func New() *Builder {
var builder strings.Builder
return &Builder{
Builder: &builder,
}
}
// Element adds a new HTML element to the builder
func (builder *Builder) Element(name string, container bool) *Element {
result := &Element{
builder: builder,
name: name,
container: container,
}
// If there's already another element on the stack then
// we probably have some cleanup to do for it first.
if builder.last != nil {
// If the last element is NOT a container, then it should
// be closed and popped from the stack.
if !builder.last.container {
builder.Close()
} else {
// If it IS a container, then it should at least
// get an EndBracket
builder.last.EndBracket()
}
// Point the result at the rest of the stack
result.parent = builder.last
}
// Save this current element on the stack.
builder.last = result
return result.Start()
}
// Empty creates a new "empty" or non-container element that WILL NOT have an end tag
func (builder *Builder) Empty(name string) *Element {
return builder.Element(name, false)
}
// Container creates a new "container" element that WILL have an end tag.
func (builder *Builder) Container(name string) *Element {
return builder.Element(name, true)
}
// Space adds a single space character to the buffer
func (builder *Builder) Space() *Builder {
builder.Grow(1)
builder.WriteString(" ")
return builder
}
// SubTree generates a new Builder that shares this Builder's string buffer.
// This is useful when sending a Builder to another function, so that the
// other function can maintain it's own stack of elements -- and potentially
// call .CloseAll() -- without affecting this current builder.
func (builder *Builder) SubTree() *Builder {
// If we're beginning a sub-tree, then guarantee that the most recent tag has at
// least been ended properly.
builder.EndBracket()
return &Builder{
Builder: builder.Builder,
}
}
// EndBracket adds an end bracket to the last tag on the stack
func (builder *Builder) EndBracket() *Builder {
if builder.last == nil {
return builder
}
// Write the closing HTML to the buffer
builder.last.EndBracket()
return builder
}
// Close completes the last tag on the stack, then pops it off of the stack
func (builder *Builder) Close() *Builder {
if builder.last == nil {
return builder
}
// Write the closing HTML to the buffer
builder.last.Close()
return builder
}
// CloseAll calls .Cload() until the stack is empty.
func (builder *Builder) CloseAll() *Builder {
for builder.last != nil {
builder.Close()
}
return builder
}
// ReadString reads the existing contents of the buffer but DOES NOT
// close any existing tags. This is useful for reading the header
// of an HTML document before calling a subroutine that will fill in
// its body.
func (builder *Builder) ReadString() string {
// if there was any extra space remaining in the old buffer,
// then we'll use that in the new buffer, too
extraSpace := builder.Builder.Cap() - builder.Builder.Len()
result := builder.Builder.String()
builder.Builder.Reset()
if extraSpace > 0 {
builder.Builder.Grow(extraSpace)
}
// Return the contents of the original buffer so far.
return result
}
// String returns the assembled HTML as a string. It overrides
// the default behavior of the strings.Builder by also calling
// CloseAll() on all unclosed tags in the stack before generating HTML.
func (builder *Builder) String() string {
builder.CloseAll()
return builder.Builder.String()
}
// Bytes returns the assembled HTML as a slice of bytes.
func (builder *Builder) Bytes() []byte {
builder.CloseAll()
return []byte(builder.Builder.String())
}
// Reader returns the string as an io.Reader.
func (builder *Builder) Reader() io.Reader {
return strings.NewReader(builder.String())
}