-
Notifications
You must be signed in to change notification settings - Fork 0
/
template.go
190 lines (160 loc) · 5.44 KB
/
template.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package godin
import (
"bytes"
"fmt"
"go/format"
"io/ioutil"
"os"
"path"
"path/filepath"
"text/template"
log "github.com/sirupsen/logrus"
"github.com/Masterminds/sprig"
"github.com/pkg/errors"
)
type Template interface {
Configuration() *TemplateConfiguration
Render(projectContext, protobufContext interface{}, moduleConfig interface{}, templateRootPath, outputRootPath string) error
}
// TemplateConfiguration specifies the base configuration for each template.
type TemplateConfiguration struct {
Name string
SourceFile string
TargetFile string
GoSource bool
Skip bool
}
// SourceExists checks - given the path to the templates - if the template source file exists
func (cfg *TemplateConfiguration) SourceExists(templateDir string) bool {
if _, err := os.Stat(filepath.Join(templateDir, cfg.SourceFile)); os.IsNotExist(err) {
return false
}
return true
}
// TargetExists checks - given the path to the output folder - if the template target file exists
func (cfg *TemplateConfiguration) TargetExists(outputRoot string) bool {
if _, err := os.Stat(filepath.Join(outputRoot, cfg.TargetFile)); os.IsNotExist(err) {
return false
}
return true
}
// EnsureTargetPath ensures that the template's target path exists and is writeable by the current user.
// It will create missing folders.
func (cfg *TemplateConfiguration) EnsureTargetPath(outputDir string) error {
// Check if the target folder exists
targetPath := filepath.Join(outputDir, cfg.TargetFile)
targetFolder := filepath.Dir(targetPath)
if _, err := os.Stat(targetFolder); os.IsNotExist(err) {
if err := os.MkdirAll(targetFolder, 0755); err != nil {
return err
}
}
// ensure permission for existing
testFile := filepath.Join(targetFolder, "tester")
if _, err := os.Create(testFile); os.IsPermission(err) {
if err := os.Chmod(targetFolder, 0755); err != nil {
err = errors.Wrap(err, "target path not writeable and chmod failed")
return err
}
}
if err := os.Remove(testFile); err != nil {
return errors.Wrap(err, "failed to remove permission tester")
}
return nil
}
// BaseTemplate defines some useful default behaviour for module templates
type BaseTemplate struct {
Config *TemplateConfiguration
}
// Configuration returns the TemplateConfiguration
func (tpl *BaseTemplate) Configuration() *TemplateConfiguration {
return tpl.Config
}
func (tpl *BaseTemplate) Render(projectContext, protobufContext interface{}, moduleConfig interface{}, templateRootPath, outputRootPath string) error {
logger := log.WithFields(log.Fields{
"template": tpl.Config.SourceFile,
"target": tpl.Config.TargetFile,
})
if tpl.Config.Skip {
logger.Info("template disabled")
return nil
}
logger.Debug("template enabled")
if !tpl.Config.SourceExists(templateRootPath) {
err := fmt.Errorf("template not found: %s", tpl.Config.SourceFile)
return err
}
logger.Debug("template found")
render := NewTemplateRenderer(*tpl.Config, templateRootPath)
output, err := render.Render(tpl.prepareContext(projectContext, protobufContext, moduleConfig))
if err != nil {
logger.WithError(err).Error("failed to render template")
fmt.Println(err)
}
if err := tpl.Config.EnsureTargetPath(outputRootPath); err != nil {
return err
}
// write targetFile
targetPath := path.Join(outputRootPath, tpl.Config.TargetFile)
writer := NewFileWriter(targetPath, output)
if err := writer.Write(true); err != nil {
return fmt.Errorf("failed to write template '%s': %s", tpl.Config.SourceFile, err)
}
logger.Info("rendered template into target")
return nil
}
// prepareContext aggregates the protobuf context (global context) with the module and template configuration.
// TODO: add project config to global template context
func (tpl *BaseTemplate) prepareContext(projectContext, protobufContext interface{}, moduleConfig interface{}) interface{} {
return struct {
Project interface{}
Protobuf interface{}
Template *TemplateConfiguration
Module interface{}
}{
Project: projectContext,
Protobuf: protobufContext,
Template: tpl.Config,
Module: moduleConfig,
}
}
type TemplateRenderer struct {
templateRootPath string
template TemplateConfiguration
}
func NewTemplateRenderer(config TemplateConfiguration, templateSource string) *TemplateRenderer {
return &TemplateRenderer{
template: config,
templateRootPath: templateSource,
}
}
// Render the template given the template configuration and return the rendered buffer.
// If a template is configured to be a Go-source file, the rendered output will be formatted using go/format before returning.
//
// The given templateContext will be passed on template execution and is thus available from within the template.
// In addition to the templateContext, the 'github.com/Masterminds/sprig' template functions are injected to further increase
// template productivity.
func (r *TemplateRenderer) Render(templateContext interface{}) (rendered []byte, err error) {
templatePath := filepath.Join(r.templateRootPath, r.template.SourceFile)
buf, err := ioutil.ReadFile(templatePath)
if err != nil {
return nil, err
}
tpl := template.New(r.template.Name).Funcs(sprig.TxtFuncMap())
tpl, err = tpl.Parse(string(buf))
if err != nil {
return nil, err
}
out := bytes.Buffer{}
if err := tpl.Execute(&out, templateContext); err != nil {
return nil, err
}
if r.template.GoSource {
formatted, err := format.Source(out.Bytes())
if err != nil {
return nil, err
}
out = *bytes.NewBuffer(formatted)
}
return out.Bytes(), nil
}