blob: 7c2cbdff343d68b33fe22e27335e789c2a074507 [file] [log] [blame]
Marcel van Lohuizenb5dc1922018-12-11 11:49:57 +01001// Copyright 2018 The CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// qgo builds CUE builtin packages from Go packages.
16package main
17
18import (
19 "bytes"
20 "flag"
21 "fmt"
22 "go/ast"
23 "go/constant"
24 "go/format"
25 "go/parser"
26 "go/printer"
27 "go/token"
28 "go/types"
29 "io"
30 "io/ioutil"
31 "log"
32 "os"
33 "path/filepath"
34 "regexp"
35 "strings"
36
37 "golang.org/x/tools/go/loader"
38)
39
40const help = `
41Commands:
42extract Extract one-line signature of exported types of
43 the given package.
44
45 Functions that have have more than one return
46 argument or unknown types are skipped.
47`
48
49// Even though all of the code is generated, the documentation is copied as is.
50// So for proper measure, include both the CUE and Go licenses.
51const copyright = `// Copyright 2018 The CUE Authors
52//
53// Licensed under the Apache License, Version 2.0 (the "License");
54// you may not use this file except in compliance with the License.
55// You may obtain a copy of the License at
56//
57// http://www.apache.org/licenses/LICENSE-2.0
58//
59// Unless required by applicable law or agreed to in writing, software
60// distributed under the License is distributed on an "AS IS" BASIS,
61// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
62// See the License for the specific language governing permissions and
63// limitations under the License.
64
65// Copyright 2018 The Go Authors. All rights reserved.
66// Use of this source code is governed by a BSD-style
67// license that can be found in the LICENSE file.
68`
69
70var genLine string
71
72var (
73 exclude = flag.String("exclude", "", "comma-separated list of regexps of entries to exclude")
74 stripstr = flag.Bool("stripstr", false, "Remove String suffix from functions")
75)
76
77func init() {
78 log.SetFlags(log.Lshortfile)
79}
80
81func main() {
82 flag.Parse()
83
84 genLine = "//go:generate " + strings.Join(os.Args, " ")
85
86 args := flag.Args()
87 if len(args) == 0 {
88 fmt.Println(strings.TrimSpace(help))
89 return
90 }
91
92 command := args[0]
93 args = args[1:]
94
95 switch command {
96 case "extract":
97 extract(args)
98 }
99}
100
101var exclusions []*regexp.Regexp
102
103func initExclusions() {
104 for _, re := range strings.Split(*exclude, ",") {
105 if re != "" {
106 exclusions = append(exclusions, regexp.MustCompile(re))
107 }
108 }
109}
110
111func filter(name string) bool {
112 if !ast.IsExported(name) {
113 return true
114 }
115 for _, ex := range exclusions {
116 if ex.MatchString(name) {
117 return true
118 }
119 }
120 return false
121}
122
123func pkgName() string {
124 pkg, err := os.Getwd()
125 if err != nil {
126 log.Fatal(err)
127 }
128 return filepath.Base(pkg)
129}
130
131type extracter struct {
132 prog *loader.Program
133 pkg *loader.PackageInfo
134}
135
136func extract(args []string) {
137
138 cfg := loader.Config{
139 ParserMode: parser.ParseComments,
140 }
141 cfg.FromArgs(args, false)
142
143 e := extracter{}
144 var err error
145 e.prog, err = cfg.Load()
146 if err != nil {
147 log.Fatal(err)
148 }
149
150 lastPkg := ""
151 var w *bytes.Buffer
152 initExclusions()
153
154 flushFile := func() {
155 if w != nil && w.Len() > 0 {
156 b, err := format.Source(w.Bytes())
157 if err != nil {
158 log.Fatal(err)
159 }
160 err = ioutil.WriteFile(lastPkg+".go", b, 0644)
161 if err != nil {
162 log.Fatal(err)
163 }
164 }
165 w = &bytes.Buffer{}
166 }
167
168 for _, p := range e.prog.InitialPackages() {
169 e.pkg = p
170 for _, f := range p.Files {
171 if lastPkg != p.Pkg.Name() {
172 flushFile()
173 lastPkg = p.Pkg.Name()
174 fmt.Fprintln(w, copyright)
175 fmt.Fprintln(w, genLine)
176 fmt.Fprintln(w)
177 fmt.Fprintf(w, "package %s\n", pkgName())
178 fmt.Fprintln(w)
179 fmt.Fprintf(w, "import %q", p.Pkg.Path())
180 fmt.Fprintln(w)
181 }
182
183 for _, d := range f.Decls {
184 switch x := d.(type) {
185 case *ast.FuncDecl:
186 e.reportFun(w, x)
187 case *ast.GenDecl:
188 e.reportDecl(w, x)
189 }
190 }
191 }
192 }
193 flushFile()
194}
195
196func (e *extracter) reportFun(w io.Writer, x *ast.FuncDecl) {
197 if filter(x.Name.Name) {
198 return
199 }
200 pkgName := e.pkg.Pkg.Name()
201 override := ""
202 params := []ast.Expr{}
203 if x.Type.Params != nil {
204 for _, f := range x.Type.Params.List {
205 tx := f.Type
206 if star, isStar := tx.(*ast.StarExpr); isStar {
207 if i, ok := star.X.(*ast.Ident); ok && ast.IsExported(i.Name) {
208 f.Type = &ast.SelectorExpr{X: ast.NewIdent(pkgName), Sel: i}
209 if isStar {
210 f.Type = &ast.StarExpr{X: f.Type}
211 }
212 }
213 }
214 for _, n := range f.Names {
215 params = append(params, n)
216 if n.Name == pkgName {
217 override = pkgName + x.Name.Name
218 }
219 }
220 }
221 }
222 var fn ast.Expr = &ast.SelectorExpr{
223 X: ast.NewIdent(pkgName),
224 Sel: x.Name,
225 }
226 if override != "" {
227 fn = ast.NewIdent(override)
228 }
229 x.Body = &ast.BlockStmt{List: []ast.Stmt{
230 &ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{
231 Fun: fn,
232 Args: params,
233 }}},
234 }}
235 if name := x.Name.Name; *stripstr && strings.HasSuffix(name, "String") {
236 newName := name[:len(name)-len("String")]
237 x.Name = ast.NewIdent(newName)
238 for _, c := range x.Doc.List {
239 c.Text = strings.Replace(c.Text, name, newName, -1)
240 }
241 }
242 types := []ast.Expr{}
243 if x.Recv == nil && x.Type != nil && x.Type.Results != nil && !strings.HasPrefix(x.Name.Name, "New") {
244 for _, f := range x.Type.Results.List {
245 if len(f.Names) == 0 {
246 types = append(types, f.Type)
247 } else {
248 for range f.Names {
249 types = append(types, f.Type)
250 }
251 }
252 }
253 }
254 if len(types) != 1 {
255 switch len(types) {
256 case 2:
257 if i, ok := types[1].(*ast.Ident); ok && i.Name == "error" {
258 break
259 }
260 fallthrough
261 default:
262 fmt.Printf("Skipping ")
263 x.Doc = nil
264 printer.Fprint(os.Stdout, e.prog.Fset, x)
265 fmt.Println()
266 return
267 }
268 }
269 fmt.Fprintln(w)
270 printer.Fprint(w, e.prog.Fset, x.Doc)
271 printer.Fprint(w, e.prog.Fset, x)
272 fmt.Fprint(w, "\n")
273 if override != "" {
274 fmt.Fprintf(w, "var %s = %s.%s\n\n", override, pkgName, x.Name.Name)
275 }
276}
277
278func (e *extracter) reportDecl(w io.Writer, x *ast.GenDecl) {
279 if x.Tok != token.CONST {
280 return
281 }
282 k := 0
283 for _, s := range x.Specs {
284 if v, ok := s.(*ast.ValueSpec); ok && !filter(v.Names[0].Name) {
285 if v.Values == nil {
286 v.Values = make([]ast.Expr, len(v.Names))
287 }
288 for i, expr := range v.Names {
289 // This check can be removed if we set constants to floats.
290 if _, ok := v.Values[i].(*ast.BasicLit); ok {
291 continue
292 }
293 tv, _ := types.Eval(e.prog.Fset, e.pkg.Pkg, v.Pos(), v.Names[0].Name)
294 tok := token.ILLEGAL
295 switch tv.Value.Kind() {
296 case constant.Bool:
297 v.Values[i] = ast.NewIdent(tv.Value.ExactString())
298 continue
299 case constant.String:
300 tok = token.STRING
301 case constant.Int:
302 tok = token.INT
303 case constant.Float:
304 tok = token.FLOAT
305 default:
306 fmt.Printf("Skipping %s\n", v.Names)
307 continue
308 }
309 v.Values[i] = &ast.BasicLit{
310 ValuePos: expr.Pos(),
311 Kind: tok,
312 Value: tv.Value.ExactString(),
313 }
314 }
315 v.Type = nil
316 x.Specs[k] = v
317 k++
318 }
319 }
320 x.Specs = x.Specs[:k]
321 if len(x.Specs) == 0 {
322 return
323 }
324 fmt.Fprintln(w)
325 printer.Fprint(w, e.prog.Fset, x)
326 fmt.Fprintln(w)
327}