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