1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package gocode
16
17 import (
18 "bytes"
19 "fmt"
20 "go/ast"
21 "go/format"
22 "go/types"
23 "text/template"
24
25 "golang.org/x/tools/go/packages"
26
27 "cuelang.org/go/cue"
28 "cuelang.org/go/cue/errors"
29 "cuelang.org/go/internal/value"
30 )
31
32
33 type Config struct {
34
35
36 Prefix string
37
38
39
40
41 ValidateName string
42
43
44
45 CompleteName string
46
47
48
49 RuntimeVar string
50 }
51
52 const defaultPrefix = "cuegen"
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 func Generate(pkgPath string, inst cue.InstanceOrValue, c *Config) (b []byte, err error) {
97
98
99 if c == nil {
100 c = &Config{}
101 }
102
103 g := &generator{
104 Config: *c,
105 typeMap: map[string]types.Type{},
106 }
107
108 val := inst.Value()
109 pkgName := inst.Value().BuildInstance().PkgName
110 if pkgPath != "" {
111 loadCfg := &packages.Config{
112 Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedDeps,
113 }
114 pkgs, err := packages.Load(loadCfg, pkgPath)
115 if err != nil {
116 return nil, fmt.Errorf("generating failed: %v", err)
117 }
118
119 if len(pkgs) != 1 {
120 return nil, fmt.Errorf(
121 "generate only allowed for one package at a time, found %d",
122 len(pkgs))
123 }
124
125 g.pkg = pkgs[0]
126 if len(g.pkg.Errors) > 0 {
127 for _, err := range g.pkg.Errors {
128 g.addErr(err)
129 }
130 return nil, g.err
131 }
132
133 pkgName = g.pkg.Name
134
135 for _, obj := range g.pkg.TypesInfo.Defs {
136 if obj == nil || obj.Pkg() != g.pkg.Types || obj.Parent() == nil {
137 continue
138 }
139 g.typeMap[obj.Name()] = obj.Type()
140 }
141 }
142
143
144
145 g.exec(headerCode, map[string]string{
146 "pkgName": pkgName,
147 })
148
149 iter, err := val.Fields(cue.Definitions(true))
150 g.addErr(err)
151
152 for iter.Next() {
153 g.decl(iter.Label(), iter.Value())
154 }
155
156 r := value.ConvertToRuntime(val.Context())
157 b, err = r.Marshal(&val)
158 g.addErr(err)
159
160 g.exec(loadCode, map[string]string{
161 "runtime": g.RuntimeVar,
162 "prefix": strValue(g.Prefix, defaultPrefix),
163 "data": string(b),
164 })
165
166 if g.err != nil {
167 return nil, g.err
168 }
169
170 b, err = format.Source(g.w.Bytes())
171 if err != nil {
172
173 return g.w.Bytes(), err
174 }
175
176 return b, err
177 }
178
179 type generator struct {
180 Config
181 pkg *packages.Package
182 typeMap map[string]types.Type
183
184 w bytes.Buffer
185 err errors.Error
186 }
187
188 func (g *generator) addErr(err error) {
189 if err != nil {
190 g.err = errors.Append(g.err, errors.Promote(err, "generate failed"))
191 }
192 }
193
194 func (g *generator) exec(t *template.Template, data interface{}) {
195 g.addErr(t.Execute(&g.w, data))
196 }
197
198 func (g *generator) decl(name string, v cue.Value) {
199 attr := v.Attribute("go")
200
201 if !ast.IsExported(name) && attr.Err() != nil {
202 return
203 }
204
205 goName := name
206 switch s, _ := attr.String(0); s {
207 case "":
208 case "-":
209 return
210 default:
211 goName = s
212 }
213
214 goTypeName := goName
215 goType := ""
216 if str, ok, _ := attr.Lookup(1, "type"); ok {
217 goType = str
218 goTypeName = str
219 }
220
221 isFunc, _ := attr.Flag(1, "func")
222 if goTypeName != goName {
223 isFunc = true
224 }
225
226 zero := "nil"
227
228 typ, ok := g.typeMap[goTypeName]
229 if !ok && !mappedGoTypes(goTypeName) {
230 return
231 }
232 if goType == "" {
233 goType = goTypeName
234 if typ != nil {
235 switch typ.Underlying().(type) {
236 case *types.Struct, *types.Array:
237 goType = "*" + goTypeName
238 zero = fmt.Sprintf("&%s{}", goTypeName)
239 case *types.Pointer:
240 zero = fmt.Sprintf("%s(nil)", goTypeName)
241 isFunc = true
242 }
243 }
244 }
245
246 g.exec(stubCode, map[string]interface{}{
247 "prefix": strValue(g.Prefix, defaultPrefix),
248 "cueName": name,
249 "goType": goType,
250 "zero": zero,
251
252
253 "func": isFunc,
254 "validate": lookupName(attr, "validate", strValue(g.ValidateName, "Validate")),
255 "complete": lookupName(attr, "complete", g.CompleteName),
256 })
257 }
258
259 func lookupName(attr cue.Attribute, option, config string) string {
260 name, ok, _ := attr.Lookup(1, option)
261 if !ok {
262 name = config
263 }
264 if name == "-" {
265 return ""
266 }
267 return name
268 }
269
270 func strValue(have, fallback string) string {
271 if have == "" {
272 return fallback
273 }
274 return have
275 }
276
277 func mappedGoTypes(s string) bool {
278 switch s {
279 case "bool", "float32", "float64",
280 "int", "int8", "int16", "int32", "int64", "string",
281 "uint", "uint8", "uint16", "uint32", "uint64":
282 return true
283 }
284 return false
285 }
286
View as plain text