| // Copyright 2018 The CUE Authors |
| // |
| // 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. |
| |
| // qgo builds CUE builtin packages from Go packages. |
| package main |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "go/ast" |
| "go/constant" |
| "go/format" |
| "go/printer" |
| "go/token" |
| "go/types" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strings" |
| |
| "golang.org/x/tools/go/packages" |
| ) |
| |
| const help = ` |
| Commands: |
| extract Extract one-line signature of exported types of |
| the given package. |
| |
| Functions that have have more than one return |
| argument or unknown types are skipped. |
| ` |
| |
| // Even though all of the code is generated, the documentation is copied as is. |
| // So for proper measure, include both the CUE and Go licenses. |
| const copyright = `// Copyright 2020 The CUE Authors |
| // |
| // 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. |
| |
| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| ` |
| |
| var genLine string |
| |
| var ( |
| exclude = flag.String("exclude", "", "comma-separated list of regexps of entries to exclude") |
| stripstr = flag.Bool("stripstr", false, "Remove String suffix from functions") |
| ) |
| |
| func init() { |
| log.SetFlags(log.Lshortfile) |
| } |
| |
| func main() { |
| flag.Parse() |
| |
| genLine = "//go:generate " + strings.Join(os.Args, " ") |
| |
| args := flag.Args() |
| if len(args) == 0 { |
| fmt.Println(strings.TrimSpace(help)) |
| return |
| } |
| |
| command := args[0] |
| args = args[1:] |
| |
| switch command { |
| case "extract": |
| extract(args) |
| } |
| } |
| |
| var exclusions []*regexp.Regexp |
| |
| func initExclusions() { |
| for _, re := range strings.Split(*exclude, ",") { |
| if re != "" { |
| exclusions = append(exclusions, regexp.MustCompile(re)) |
| } |
| } |
| } |
| |
| func filter(name string) bool { |
| if !ast.IsExported(name) { |
| return true |
| } |
| for _, ex := range exclusions { |
| if ex.MatchString(name) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func pkgName() string { |
| pkg, err := os.Getwd() |
| if err != nil { |
| log.Fatal(err) |
| } |
| return filepath.Base(pkg) |
| } |
| |
| type extracter struct { |
| pkg *packages.Package |
| } |
| |
| func extract(args []string) { |
| cfg := &packages.Config{ |
| Mode: packages.LoadFiles | |
| packages.LoadAllSyntax | |
| packages.LoadTypes, |
| } |
| pkgs, err := packages.Load(cfg, args...) |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| e := extracter{} |
| |
| lastPkg := "" |
| var w *bytes.Buffer |
| initExclusions() |
| |
| flushFile := func() { |
| if w != nil && w.Len() > 0 { |
| b, err := format.Source(w.Bytes()) |
| if err != nil { |
| log.Fatal(err) |
| } |
| err = ioutil.WriteFile(lastPkg+".go", b, 0644) |
| if err != nil { |
| log.Fatal(err) |
| } |
| } |
| w = &bytes.Buffer{} |
| } |
| |
| for _, p := range pkgs { |
| e.pkg = p |
| for _, f := range p.Syntax { |
| if lastPkg != p.Name { |
| flushFile() |
| lastPkg = p.Name |
| fmt.Fprintln(w, copyright) |
| fmt.Fprintln(w, genLine) |
| fmt.Fprintln(w) |
| fmt.Fprintf(w, "package %s\n", pkgName()) |
| fmt.Fprintln(w) |
| fmt.Fprintf(w, "import %q", p.PkgPath) |
| fmt.Fprintln(w) |
| } |
| |
| for _, d := range f.Decls { |
| switch x := d.(type) { |
| case *ast.FuncDecl: |
| e.reportFun(w, x) |
| case *ast.GenDecl: |
| e.reportDecl(w, x) |
| } |
| } |
| } |
| } |
| flushFile() |
| } |
| |
| func (e *extracter) reportFun(w io.Writer, x *ast.FuncDecl) { |
| if filter(x.Name.Name) { |
| return |
| } |
| pkgName := e.pkg.Name |
| override := "" |
| params := []ast.Expr{} |
| if x.Type.Params != nil { |
| for _, f := range x.Type.Params.List { |
| tx := f.Type |
| if star, isStar := tx.(*ast.StarExpr); isStar { |
| if i, ok := star.X.(*ast.Ident); ok && ast.IsExported(i.Name) { |
| f.Type = &ast.SelectorExpr{X: ast.NewIdent(pkgName), Sel: i} |
| if isStar { |
| f.Type = &ast.StarExpr{X: f.Type} |
| } |
| } |
| } |
| for _, n := range f.Names { |
| params = append(params, n) |
| if n.Name == pkgName { |
| override = pkgName + x.Name.Name |
| } |
| } |
| } |
| } |
| var fn ast.Expr = &ast.SelectorExpr{ |
| X: ast.NewIdent(pkgName), |
| Sel: x.Name, |
| } |
| if override != "" { |
| fn = ast.NewIdent(override) |
| } |
| x.Body = &ast.BlockStmt{List: []ast.Stmt{ |
| &ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ |
| Fun: fn, |
| Args: params, |
| }}}, |
| }} |
| if name := x.Name.Name; *stripstr && strings.HasSuffix(name, "String") { |
| newName := name[:len(name)-len("String")] |
| x.Name = ast.NewIdent(newName) |
| if x.Doc != nil { |
| for _, c := range x.Doc.List { |
| c.Text = strings.Replace(c.Text, name, newName, -1) |
| } |
| } |
| } |
| types := []ast.Expr{} |
| if x.Recv == nil && x.Type != nil && x.Type.Results != nil && !strings.HasPrefix(x.Name.Name, "New") { |
| for _, f := range x.Type.Results.List { |
| if len(f.Names) == 0 { |
| types = append(types, f.Type) |
| } else { |
| for range f.Names { |
| types = append(types, f.Type) |
| } |
| } |
| } |
| } |
| if len(types) != 1 { |
| switch len(types) { |
| case 2: |
| if i, ok := types[1].(*ast.Ident); ok && i.Name == "error" { |
| break |
| } |
| fallthrough |
| default: |
| fmt.Printf("Skipping ") |
| x.Doc = nil |
| printer.Fprint(os.Stdout, e.pkg.Fset, x) |
| fmt.Println() |
| return |
| } |
| } |
| fmt.Fprintln(w) |
| printer.Fprint(w, e.pkg.Fset, x.Doc) |
| printer.Fprint(w, e.pkg.Fset, x) |
| fmt.Fprint(w, "\n") |
| if override != "" { |
| fmt.Fprintf(w, "var %s = %s.%s\n\n", override, pkgName, x.Name.Name) |
| } |
| } |
| |
| func (e *extracter) reportDecl(w io.Writer, x *ast.GenDecl) { |
| if x.Tok != token.CONST { |
| return |
| } |
| k := 0 |
| for _, s := range x.Specs { |
| if v, ok := s.(*ast.ValueSpec); ok && !filter(v.Names[0].Name) { |
| if v.Values == nil { |
| v.Values = make([]ast.Expr, len(v.Names)) |
| } |
| for i, expr := range v.Names { |
| // This check can be removed if we set constants to floats. |
| if _, ok := v.Values[i].(*ast.BasicLit); ok { |
| continue |
| } |
| tv, _ := types.Eval(e.pkg.Fset, e.pkg.Types, v.Pos(), v.Names[0].Name) |
| tok := token.ILLEGAL |
| switch tv.Value.Kind() { |
| case constant.Bool: |
| v.Values[i] = ast.NewIdent(tv.Value.ExactString()) |
| continue |
| case constant.String: |
| tok = token.STRING |
| case constant.Int: |
| tok = token.INT |
| case constant.Float: |
| tok = token.FLOAT |
| default: |
| fmt.Printf("Skipping %s\n", v.Names) |
| continue |
| } |
| v.Values[i] = &ast.BasicLit{ |
| ValuePos: expr.Pos(), |
| Kind: tok, |
| Value: tv.Value.ExactString(), |
| } |
| } |
| v.Type = nil |
| x.Specs[k] = v |
| k++ |
| } |
| } |
| x.Specs = x.Specs[:k] |
| if len(x.Specs) == 0 { |
| return |
| } |
| fmt.Fprintln(w) |
| printer.Fprint(w, e.pkg.Fset, x) |
| fmt.Fprintln(w) |
| } |