-
Notifications
You must be signed in to change notification settings - Fork 0
/
generator.go
177 lines (150 loc) · 4.69 KB
/
generator.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
package codegen
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/types"
"strings"
"text/template"
"github.com/shipengqi/log"
"golang.org/x/tools/go/packages"
)
var errCodeDocPrefix = `# Error Codes
Error Codes Documentation generated by "{{.}}jaguar codegen --doc{{.}}", DO NOT EDIT.
## Features
If the result contains a {{.}}code{{.}} field, then the API call has failed. Example:
{{.}}{{.}}{{.}}json
{
"code": 100101,
"message": "Database error"
}
{{.}}{{.}}{{.}}
In the above example, {{.}}code{{.}} indicates error code, {{.}}message{{.}} indicates the message of the error.
The HTTP status code is 500 (Internal Server Error).
## Error Code List
| Identifier | Code | HTTP Code | Description |
| ---------- | ---- | --------- | ----------- |
`
// Package defines options for package.
type Package struct {
name string
defs map[*ast.Ident]types.Object
files []*File
}
// Generator holds the state of the analysis. Primarily used to buffer
// the output for format.Source.
type Generator struct {
buf bytes.Buffer // Accumulated output.
pkg *Package // Package we are scanning.
trimPrefix string
}
func NewGenerator(prefix string) *Generator {
return &Generator{
trimPrefix: prefix,
}
}
// Printf like fmt.Printf, but add the string to g.buf.
func (g *Generator) Printf(format string, args ...interface{}) {
_, _ = fmt.Fprintf(&g.buf, format, args...)
}
// parsePackage analyzes the single package constructed from the patterns and tags.
func (g *Generator) parsePackage(patterns []string, tags []string) error {
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles |
packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes |
packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo,
// TODO: Need to think about constants in test files. Maybe write type_string_test.go
// in a separate pass? For later.
Tests: false,
BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))},
}
pkgs, err := packages.Load(cfg, patterns...)
if err != nil {
return err
}
if len(pkgs) != 1 {
return fmt.Errorf("%d packages found", len(pkgs))
}
g.addPackage(pkgs[0])
return nil
}
// addPackage adds a type checked Package and its syntax files to the generator.
func (g *Generator) addPackage(pkg *packages.Package) {
g.pkg = &Package{
name: pkg.Name,
defs: pkg.TypesInfo.Defs,
files: make([]*File, len(pkg.Syntax)),
}
for i, file := range pkg.Syntax {
g.pkg.files[i] = &File{
file: file,
pkg: g.pkg,
trimPrefix: g.trimPrefix,
}
}
}
// generate produces the register calls for the named type.
func (g *Generator) generate(typeName string) error {
values := make([]Value, 0, 100)
for _, file := range g.pkg.files {
// Set the state for this run of the walker.
file.typeName = typeName
file.values = nil
if file.file != nil {
ast.Inspect(file.file, file.genDecl)
values = append(values, file.values...)
}
}
if len(values) == 0 {
return fmt.Errorf("no values defined for type %s", typeName)
}
// Generate code that will fail if the constants change value.
g.Printf("\t// init register error codes defines in this source code to `github.com/shipengqi/errors`\n")
g.Printf("func init() {\n")
for _, v := range values {
code, description := v.ParseComment()
g.Printf("\tregister(%s, %s, \"%s\")\n", v.originalName, code, description)
}
g.Printf("}\n")
return nil
}
// generateDocs produces error code Markdown document for the named type.
func (g *Generator) generateDocs(typeName string) error {
values := make([]Value, 0, 100)
for _, file := range g.pkg.files {
// Set the state for this run of the walker.
file.typeName = typeName
file.values = nil
if file.file != nil {
ast.Inspect(file.file, file.genDecl)
values = append(values, file.values...)
}
}
if len(values) == 0 {
return fmt.Errorf("no values defined for type %s", typeName)
}
tmpl, _ := template.New("doc").Parse(errCodeDocPrefix)
var buf bytes.Buffer
_ = tmpl.Execute(&buf, "`")
// Generate code that will fail if the constants change value.
g.Printf(buf.String())
for _, v := range values {
code, description := v.ParseComment()
g.Printf("| %s | %d | %s | %s |\n", v.originalName, v.value, code, description)
}
g.Printf("\n")
return nil
}
// format returns the gofmt-ed contents of the Generator's buffer.
func (g *Generator) format() []byte {
src, err := format.Source(g.buf.Bytes())
if err != nil {
// Should never happen, but can arise when developing this code.
// The user can compile the output to see the error.
log.Printf("warning: internal error: invalid Go generated: %s", err)
log.Printf("warning: compile the package to analyze the error")
return g.buf.Bytes()
}
return src
}