-
Notifications
You must be signed in to change notification settings - Fork 77
/
artifact.go
322 lines (263 loc) · 10 KB
/
artifact.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
package pgs
import (
"bytes"
"errors"
"io"
"os"
"path/filepath"
"strings"
"google.golang.org/protobuf/proto"
plugin_go "google.golang.org/protobuf/types/pluginpb"
)
// An Artifact describes the output for a Module. Typically this is the creation
// of a file either directly against the file system or via protoc.
type Artifact interface {
artifact()
}
// A Template to use for rendering artifacts. Either text/template or
// html/template Template types satisfy this interface.
type Template interface {
Execute(w io.Writer, data interface{}) error
}
// GeneratorArtifact describes an Artifact that uses protoc for code generation.
// GeneratorArtifacts must be valid UTF8. To create binary files, use one of
// the "custom" Artifact types.
type GeneratorArtifact interface {
Artifact
// ProtoFile converts the GeneratorArtifact to a CodeGeneratorResponse_File,
// which is handed to protoc to actually write the file to disk. An error is
// returned if Artifact cannot be converted.
ProtoFile() (*plugin_go.CodeGeneratorResponse_File, error)
}
// TemplateArtifact contains the shared logic used by Artifacts that render
// their contents using a Template.
type TemplateArtifact struct {
// The Template to use for rendering. Either text/template or html/template
// Template types are supported.
Template Template
// Data is arbitrary data passed into the Template's Execute method.
Data interface{}
}
func (ta TemplateArtifact) render() (string, error) {
buf := &bytes.Buffer{}
if err := ta.Template.Execute(buf, ta.Data); err != nil {
return "", err
}
return buf.String(), nil
}
// A GeneratorFile Artifact describes a file to be generated using protoc.
type GeneratorFile struct {
GeneratorArtifact
// Name of the file to generate, relative to the protoc-plugin's generation
// output directory.
Name string
// Contents are the body of the file.
Contents string
// Overwrite specifies whether or not this file should replace another file
// with the same name if a prior Plugin or Module has created one.
Overwrite bool
}
// ProtoFile satisfies the GeneratorArtifact interface. An error is returned if
// the name field is not a path relative to and within the protoc-plugin's
// generation output directory.
func (f GeneratorFile) ProtoFile() (*plugin_go.CodeGeneratorResponse_File, error) {
name, err := cleanGeneratorFileName(f.Name)
if err != nil {
return nil, err
}
return &plugin_go.CodeGeneratorResponse_File{
Name: proto.String(name),
Content: proto.String(f.Contents),
}, nil
}
// A GeneratorTemplateFile describes a file to be generated using protoc from
// a Template.
type GeneratorTemplateFile struct {
GeneratorArtifact
TemplateArtifact
// Name of the file to generate, relative to the protoc-plugin's generation
// output directory.
Name string
// Overwrite specifies whether or not this file should replace another file
// with the same name if a prior Plugin or Module has created one.
Overwrite bool
}
// ProtoFile satisfies the GeneratorArtifact interface. An error is returned if
// the name field is not a path relative to and within the protoc-plugin's
// generation output directory or if there is an error executing the Template.
func (f GeneratorTemplateFile) ProtoFile() (*plugin_go.CodeGeneratorResponse_File, error) {
name, err := cleanGeneratorFileName(f.Name)
if err != nil {
return nil, err
}
content, err := f.render()
if err != nil {
return nil, err
}
return &plugin_go.CodeGeneratorResponse_File{
Name: proto.String(name),
Content: proto.String(content),
}, nil
}
// A GeneratorAppend Artifact appends content to the end of the specified protoc
// generated file. This Artifact can only be used if another Module generates a
// file with the same name.
type GeneratorAppend struct {
GeneratorArtifact
// Filename of the file to append to, relative to the protoc-plugin's generation
// output directory.
FileName string
// Contents to be appended to the file
Contents string
}
// ProtoFile satisfies the GeneratorArtifact interface. An error is returned if
// the name field is not a path relative to and within the protoc-plugin's
// generation output directory.
func (f GeneratorAppend) ProtoFile() (*plugin_go.CodeGeneratorResponse_File, error) {
if _, err := cleanGeneratorFileName(f.FileName); err != nil {
return nil, err
}
return &plugin_go.CodeGeneratorResponse_File{
Content: proto.String(f.Contents),
}, nil
}
// A GeneratorTemplateAppend appends content to a protoc-generated file from a
// Template. See GeneratorAppend for limitations.
type GeneratorTemplateAppend struct {
GeneratorArtifact
TemplateArtifact
// Filename of the file to append to, relative to the protoc-plugin's generation
// output directory.
FileName string
}
// ProtoFile satisfies the GeneratorArtifact interface. An error is returned if
// the name field is not a path relative to and within the protoc-plugin's
// generation output directory or if there is an error executing the Template.
func (f GeneratorTemplateAppend) ProtoFile() (*plugin_go.CodeGeneratorResponse_File, error) {
if _, err := cleanGeneratorFileName(f.FileName); err != nil {
return nil, err
}
content, err := f.render()
if err != nil {
return nil, err
}
return &plugin_go.CodeGeneratorResponse_File{
Content: proto.String(content),
}, nil
}
// A GeneratorInjection Artifact inserts content into a protoc-generated file
// at the specified insertion point. The target file does not need to be
// generated by this protoc-plugin but must be generated by a prior plugin
// executed by protoc.
type GeneratorInjection struct {
GeneratorArtifact
// Filename of the file to inject into, relative to the protoc-plugin's
// generation output directory.
FileName string
// The name of the insertion point to inject into
InsertionPoint string
// Contents to be inject into the file
Contents string
}
// ProtoFile satisfies the GeneratorArtifact interface. An error is returned if
// the name field is not a path relative to and within the protoc-plugin's
// generation output directory.
func (f GeneratorInjection) ProtoFile() (*plugin_go.CodeGeneratorResponse_File, error) {
name, err := cleanGeneratorFileName(f.FileName)
if err != nil {
return nil, err
}
return &plugin_go.CodeGeneratorResponse_File{
Name: proto.String(name),
InsertionPoint: proto.String(f.InsertionPoint),
Content: proto.String(f.Contents),
}, nil
}
// A GeneratorTemplateInjection Artifact inserts content rendered from a
// Template into protoc-generated file at the specified insertion point. The
// target file does not need to be generated by this protoc-plugin but must be
// generated by a prior plugin executed by protoc.
type GeneratorTemplateInjection struct {
GeneratorArtifact
TemplateArtifact
// Filename of the file to inject into, relative to the protoc-plugin's
// generation output directory.
FileName string
// The name of the insertion point to inject into
InsertionPoint string
}
// ProtoFile satisfies the GeneratorArtifact interface. An error is returned if
// the name field is not a path relative to and within the protoc-plugin's
// generation output directory or if there is an error executing the Template.
func (f GeneratorTemplateInjection) ProtoFile() (*plugin_go.CodeGeneratorResponse_File, error) {
name, err := cleanGeneratorFileName(f.FileName)
if err != nil {
return nil, err
}
content, err := f.render()
if err != nil {
return nil, err
}
return &plugin_go.CodeGeneratorResponse_File{
Name: proto.String(name),
InsertionPoint: proto.String(f.InsertionPoint),
Content: proto.String(content),
}, nil
}
// CustomFile Artifacts are files generated directly against the file system,
// and do not use protoc for the generation. CustomFiles should be used over
// GeneratorFiles when custom permissions need to be set (such as executable
// scripts or read-only configs) or when the file needs to be created outside
// of the protoc-plugin's generation output directory.
type CustomFile struct {
Artifact
// Name of the file to generate. If relative, the file is created relative to
// the directory in which protoc is executed. If absolute, the file is
// created as specified.
Name string
// Contents are the body of the file.
Contents string
// Perms are the file permission to generate the file with. Note that the
// umask of the process will be applied against these permissions.
Perms os.FileMode
// Overwrite indicates if an existing file on disk should be overwritten by
// this file.
Overwrite bool
}
// CustomTemplateFile Artifacts are files generated from a Template directly
// against the file system, and do not use protoc for the generation.
// CustomFiles should be used over GeneratorFiles when custom permissions need
// to be set (such as executable scripts or read-only configs) or when the file
// needs to be created outside of the protoc-plugin's generation output
// directory.
type CustomTemplateFile struct {
Artifact
TemplateArtifact
// Name of the file to generate. If relative, the file is created relative to
// the directory in which protoc is executed. If absolute, the file is
// created as specified.
Name string
// Perms are the file permission to generate the file with. Note that the
// umask of the process will be applied against these permissions.
Perms os.FileMode
// Overwrite indicates if an existing file on disk should be overwritten by
// this file.
Overwrite bool
}
func cleanGeneratorFileName(name string) (string, error) {
if filepath.IsAbs(name) {
return "", errors.New("generator file names must be relative paths")
}
if name = filepath.ToSlash(filepath.Clean(name)); name == "." || strings.HasPrefix(name, "..") {
return "", errors.New("generator file names must be not contain . or .. within them")
}
return name, nil
}
// GeneratorError Artifacts are strings describing errors that happened in the
// code generation, but have not been fatal. They'll be used to populate the
// CodeGeneratorResponse's `error` field. Since that field is a string, multiple
// GeneratorError Artifacts will be concatenated.
type GeneratorError struct {
Artifact
Message string
}