github.com/solo-io/cue@v0.4.7/internal/cmd/qgo/qgo.go (about)

     1  // 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.
    16  package main
    17  
    18  import (
    19  	"bytes"
    20  	"flag"
    21  	"fmt"
    22  	"go/ast"
    23  	"go/constant"
    24  	"go/format"
    25  	"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  
    36  	"golang.org/x/tools/go/packages"
    37  )
    38  
    39  const help = `
    40  Commands:
    41  extract		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.
    50  const copyright = `// Copyright 2020 The CUE Authors
    51  //
    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  
    69  var genLine string
    70  
    71  var (
    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  
    76  func init() {
    77  	log.SetFlags(log.Lshortfile)
    78  }
    79  
    80  func main() {
    81  	flag.Parse()
    82  
    83  	genLine = "//go:generate go run cuelang.org/go/internal/cmd/qgo " + strings.Join(os.Args[1:], " ")
    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  
   100  var exclusions []*regexp.Regexp
   101  
   102  func initExclusions() {
   103  	for _, re := range strings.Split(*exclude, ",") {
   104  		if re != "" {
   105  			exclusions = append(exclusions, regexp.MustCompile(re))
   106  		}
   107  	}
   108  }
   109  
   110  func 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  
   122  func pkgName() string {
   123  	pkg, err := os.Getwd()
   124  	if err != nil {
   125  		log.Fatal(err)
   126  	}
   127  	return filepath.Base(pkg)
   128  }
   129  
   130  type extracter struct {
   131  	pkg *packages.Package
   132  }
   133  
   134  func extract(args []string) {
   135  	cfg := &packages.Config{
   136  		Mode: packages.LoadFiles |
   137  			packages.LoadAllSyntax |
   138  			packages.LoadTypes,
   139  	}
   140  	pkgs, err := packages.Load(cfg, args...)
   141  	if err != nil {
   142  		log.Fatal(err)
   143  	}
   144  
   145  	e := extracter{}
   146  
   147  	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  
   165  	for _, p := range pkgs {
   166  		e.pkg = p
   167  		for _, f := range p.Syntax {
   168  			if lastPkg != p.Name {
   169  				flushFile()
   170  				lastPkg = p.Name
   171  				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)
   176  				fmt.Fprintf(w, "import %q", p.PkgPath)
   177  				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  
   193  func (e *extracter) reportFun(w io.Writer, x *ast.FuncDecl) {
   194  	if filter(x.Name.Name) {
   195  		return
   196  	}
   197  	pkgName := e.pkg.Name
   198  	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)
   235  		if x.Doc != nil {
   236  			for _, c := range x.Doc.List {
   237  				c.Text = strings.Replace(c.Text, name, newName, -1)
   238  			}
   239  		}
   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
   263  			printer.Fprint(os.Stdout, e.pkg.Fset, x)
   264  			fmt.Println()
   265  			return
   266  		}
   267  	}
   268  	fmt.Fprintln(w)
   269  	printer.Fprint(w, e.pkg.Fset, x.Doc)
   270  	printer.Fprint(w, e.pkg.Fset, x)
   271  	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  
   277  func (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  				}
   292  				tv, _ := types.Eval(e.pkg.Fset, e.pkg.Types, v.Pos(), v.Names[0].Name)
   293  				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)
   324  	printer.Fprint(w, e.pkg.Fset, x)
   325  	fmt.Fprintln(w)
   326  }