/
generate.go
133 lines (112 loc) · 4.01 KB
/
generate.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
/*
Copyright 2020 Wim Henderickx.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package generate generates method sets for Go types.
package generate
import (
"bytes"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"github.com/dave/jennifer/jen"
"github.com/pkg/errors"
"golang.org/x/tools/go/packages"
"github.com/netw-device-driver/ndd-tools/internal/match"
"github.com/netw-device-driver/ndd-tools/internal/method"
)
const (
// HeaderGenerated is added to all files generated by ndd-gen.
// See https://github.com/golang/go/issues/13560#issuecomment-288457920.
HeaderGenerated = "Code generated by ndd-gen. DO NOT EDIT."
HeaderIgnoreAutoGenerated = "+build !ignore_autogenerated"
)
type options struct {
Matches match.Object
ImportAliases map[string]string
Headers []string
}
// A WriteOption configures method generation behaviour.
type WriteOption func(o *options)
// WithHeaders specifies strings to be written as comments to the generated
// file, above the package definition. Single line strings use // comments,
// while multiline strings use /**/ comments.
func WithHeaders(h ...string) WriteOption {
return func(o *options) {
o.Headers = append(o.Headers, h...)
}
}
// WithMatcher specifies an Object matcher that is used to filter the Objects
// within the package down to the set that need the generated methods.
func WithMatcher(m match.Object) WriteOption {
return func(o *options) {
o.Matches = m
}
}
// WithImportAliases configures a map of import paths to aliases that will be
// used when generating code. For example if a generated method requires
// "example.org/foo/bar" it may refer to that package as "foobar" by supplying
// map[string]string{"example.org/foo/bar": "foobar"}
func WithImportAliases(ia map[string]string) WriteOption {
return func(o *options) {
o.ImportAliases = ia
}
}
// WriteMethods writes the supplied methods for each object in the supplied
// package to the supplied file. Use WithMatcher to limit the objects for which
// methods will be written. Methods will not be generated if a method with the
// same name is already defined for the object outside of the supplied filename.
// Files will not be written if they would contain no methods.
func WriteMethods(p *packages.Package, ms method.Set, file string, wo ...WriteOption) error {
opts := &options{Matches: func(o types.Object) bool { return true }}
for _, fn := range wo {
fn(opts)
}
f := jen.NewFile(p.Name)
for path, alias := range opts.ImportAliases {
f.ImportAlias(path, alias)
}
f.HeaderComment(HeaderIgnoreAutoGenerated)
for _, hc := range opts.Headers {
if hc != "" {
f.HeaderComment(hc)
}
}
f.HeaderComment(HeaderGenerated)
for _, n := range p.Types.Scope().Names() {
o := p.Types.Scope().Lookup(n)
if !opts.Matches(o) {
continue
}
ms.Write(f, o, method.DefinedOutside(p.Fset, file))
}
b := &bytes.Buffer{}
if err := f.Render(b); err != nil {
return errors.Wrap(err, "cannot render Go file")
}
if ProducedNothing(b.Bytes()) {
return nil
}
// gosec would prefer this to be written as 0600, but we're comfortable with
// it being world readable.
return errors.Wrap(ioutil.WriteFile(file, b.Bytes(), 0644), "cannot write Go file") // nolint:gosec
}
// ProducedNothing returns true if the supplied data is either not a valid Go
// source file, or a valid Go file that contains no top level objects or
// declarations.
func ProducedNothing(data []byte) bool {
f, err := parser.ParseFile(token.NewFileSet(), "f.go", data, 0)
if err != nil {
return true
}
return len(f.Decls)+len(f.Scope.Objects) == 0
}