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 }