github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/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 = "// Generated with go run github.com/joomcode/cue/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.Fprint(w, copyright) 172 fmt.Fprintln(w) 173 fmt.Fprintln(w, genLine) 174 fmt.Fprintln(w) 175 fmt.Fprintf(w, "package %s\n", pkgName()) 176 fmt.Fprintln(w) 177 fmt.Fprintf(w, "import %q", p.PkgPath) 178 fmt.Fprintln(w) 179 } 180 181 for _, d := range f.Decls { 182 switch x := d.(type) { 183 case *ast.FuncDecl: 184 e.reportFun(w, x) 185 case *ast.GenDecl: 186 e.reportDecl(w, x) 187 } 188 } 189 } 190 } 191 flushFile() 192 } 193 194 func (e *extracter) reportFun(w io.Writer, x *ast.FuncDecl) { 195 if filter(x.Name.Name) { 196 return 197 } 198 pkgName := e.pkg.Name 199 override := "" 200 params := []ast.Expr{} 201 if x.Type.Params != nil { 202 for _, f := range x.Type.Params.List { 203 tx := f.Type 204 if star, isStar := tx.(*ast.StarExpr); isStar { 205 if i, ok := star.X.(*ast.Ident); ok && ast.IsExported(i.Name) { 206 f.Type = &ast.SelectorExpr{X: ast.NewIdent(pkgName), Sel: i} 207 if isStar { 208 f.Type = &ast.StarExpr{X: f.Type} 209 } 210 } 211 } 212 for _, n := range f.Names { 213 params = append(params, n) 214 if n.Name == pkgName { 215 override = pkgName + x.Name.Name 216 } 217 } 218 } 219 } 220 var fn ast.Expr = &ast.SelectorExpr{ 221 X: ast.NewIdent(pkgName), 222 Sel: x.Name, 223 } 224 if override != "" { 225 fn = ast.NewIdent(override) 226 } 227 x.Body = &ast.BlockStmt{List: []ast.Stmt{ 228 &ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{ 229 Fun: fn, 230 Args: params, 231 }}}, 232 }} 233 if name := x.Name.Name; *stripstr && strings.HasSuffix(name, "String") { 234 newName := name[:len(name)-len("String")] 235 x.Name = ast.NewIdent(newName) 236 if x.Doc != nil { 237 for _, c := range x.Doc.List { 238 c.Text = strings.Replace(c.Text, name, newName, -1) 239 } 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.pkg.Fset, x) 265 fmt.Println() 266 return 267 } 268 } 269 fmt.Fprintln(w) 270 printer.Fprint(w, e.pkg.Fset, x.Doc) 271 printer.Fprint(w, e.pkg.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 278 func (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.pkg.Fset, e.pkg.Types, 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.pkg.Fset, x) 326 fmt.Fprintln(w) 327 }