-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuilder.go
More file actions
127 lines (105 loc) · 3.37 KB
/
Copy pathbuilder.go
File metadata and controls
127 lines (105 loc) · 3.37 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
package leaf
import (
"html/template"
"io"
)
// Builder provides a mutable interface for a template container.
//
// It also implements some additional template loading strategies (like expressing dependencies between templates).
//
// In production, the Builder should be compiled into an immutable container when the program starts, so that errors are
// caught early.
type Builder struct {
loader Loader
definitions map[string]Definition
}
// NewBuilder accepts a loader and returns a new Builder.
func NewBuilder(loader Loader) *Builder {
return &Builder{
loader: loader,
definitions: make(map[string]Definition),
}
}
func (b *Builder) Get(name string) (*template.Template, error) {
definition, ok := b.definitions[name]
if !ok {
return nil, ErrTemplateNotFound
}
return definition.Resolve()
}
func (b *Builder) Execute(name string, wr io.Writer, data interface{}) error {
tpl, err := b.Get(name)
if err != nil {
return err
}
return tpl.Execute(wr, data)
}
// Define defines a new template based on dependencies.
func (b *Builder) Define(name string, templates ...string) {
b.definitions[name] = &loaderDefinition{
name: name,
templates: templates,
loader: b.loader,
}
}
// Set allows setting an already created template in the builder.
func (b *Builder) Set(name string, tpl *template.Template) {
b.definitions[name] = &templateDefinition{
tpl: tpl,
}
}
// Compile parses and loads all templates and creates an immutable container from them.
//
// This method should be called early so that template issues do not occur runtime.
func (b *Builder) Compile() (Container, error) {
templates := make(map[string]*template.Template, len(b.definitions))
for name, definition := range b.definitions {
t, err := definition.Resolve()
if err != nil {
return nil, err
}
templates[name] = t
}
return &container{templates}, nil
}
// Definition is the building block for the container builder.
//
// Definitions are used internally to resolve templates.
// Template resolution occurs either during "compilation" time (see Compile method of Builder)
// or every time a template is fetched from the Builder. This is necessary, because the Builder is mutable,
// so between two subsequent fetcher calls a template definition for the same name might change.
//
// This behaviour is also useful for template development: templates can be reloaded (eg. when they are files)
// without restarting the application. (Note that this is highly unrecommended for production usage)
type Definition interface {
// Resolve tries to resolve a template from the definition.
Resolve() (*template.Template, error)
}
// templateDefinition always resolves to the same template reference.
type templateDefinition struct {
tpl *template.Template
}
func (d *templateDefinition) Resolve() (*template.Template, error) {
return d.tpl, nil
}
// loaderDefinition loads a template along with it's dependencies using a user configured template loader.
type loaderDefinition struct {
name string
base string
templates []string
loader Loader
}
func (d *loaderDefinition) Resolve() (*template.Template, error) {
tpl := template.New(d.name)
for _, dependency := range d.templates {
templateContent, err := d.loader.Load(dependency)
if err != nil {
return nil, err
}
tpl, err = tpl.Parse(string(templateContent))
if err != nil {
return nil, err
}
}
return tpl, nil
}