github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/macros/routes/routes.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/parser" 7 "go/token" 8 "io/ioutil" 9 "log" 10 "os" 11 "regexp" 12 "strings" 13 ) 14 15 func main() { 16 groups := parse("main.go") 17 fmt.Println(len(groups)) 18 19 out := "// -- DONT EDIT - autogenerated from main.go \n\n" 20 for name, rts := range groups { 21 out += fmt.Sprintf(`export const %s = { 22 %s 23 } 24 25 `, name, rts.render()) 26 } 27 err := ioutil.WriteFile("frontend/directeurs/src/logic/api.ts", []byte(out), os.ModePerm) 28 if err != nil { 29 log.Fatal(err) 30 } 31 err = ioutil.WriteFile("frontend/bv/src/shared/logic/api.ts", []byte(out), os.ModePerm) 32 if err != nil { 33 log.Fatal(err) 34 } 35 } 36 37 type namedPath struct { 38 method string 39 path string 40 } 41 42 type routes map[string][]namedPath 43 44 func (r routes) render() string { 45 var out []string 46 for handler, paths := range r { 47 if len(paths) == 1 { 48 out = append(out, fmt.Sprintf("\t/** %s */ %s : %s, ", paths[0].method, handler, paths[0].path)) 49 } else if len(paths) > 1 { 50 for i, path := range paths { 51 out = append(out, fmt.Sprintf("\t/** %s */ %s%d : %s, ", path.method, handler, i+1, path.path)) 52 } 53 } 54 } 55 return strings.Join(out, "\n") 56 } 57 58 func isHttpMethod(name string) bool { 59 switch name { 60 case "GET", "PUT", "POST", "DELETE": 61 return true 62 default: 63 return false 64 } 65 } 66 67 func parse(filename string) map[string]routes { 68 t := token.NewFileSet() 69 f, err := parser.ParseFile(t, filename, nil, parser.ParseComments) 70 if err != nil { 71 log.Fatal(err) 72 } 73 74 out := map[string]routes{} 75 for _, decl := range f.Decls { 76 funcStm, ok := decl.(*ast.FuncDecl) 77 if !ok || funcStm.Body == nil { 78 continue 79 } 80 groupName := funcStm.Name.Name 81 if !strings.HasPrefix(groupName, "setupRoutes") { 82 continue 83 } 84 groupName = strings.TrimPrefix(groupName, "setupRoutes") 85 86 for _, stm := range funcStm.Body.List { 87 call, ok := stm.(*ast.ExprStmt) 88 if !ok { 89 continue 90 } 91 callExpr, ok := call.X.(*ast.CallExpr) 92 if !ok { 93 continue 94 } 95 selector, ok := callExpr.Fun.(*ast.SelectorExpr) 96 if !ok { 97 continue 98 } 99 methodName := selector.Sel.Name 100 if !isHttpMethod(methodName) || len(callExpr.Args) < 2 { 101 continue 102 } 103 path, handler := parseArgPath(callExpr.Args[0]), parseArgHandler(callExpr.Args[1]) 104 if path == "" || handler == "" { 105 continue 106 } 107 path = replacePlaceholders(path) 108 rts := out[groupName] 109 if rts == nil { 110 rts = make(routes) 111 } 112 rts[handler] = append(rts[handler], namedPath{method: methodName, path: path}) 113 out[groupName] = rts 114 } 115 } 116 return out 117 } 118 119 func parseArgPath(arg ast.Expr) string { 120 switch arg := arg.(type) { 121 case *ast.CallExpr: 122 path := arg.Args[0] 123 if lit, ok := path.(*ast.BasicLit); ok { 124 return lit.Value 125 } 126 case *ast.BasicLit: 127 return arg.Value 128 } 129 return "" 130 } 131 132 func parseArgHandler(arg ast.Expr) string { 133 if method, ok := arg.(*ast.SelectorExpr); ok { 134 return method.Sel.Name 135 } 136 return "" 137 } 138 139 var rePlaceholder = regexp.MustCompile(`:([^/"']+)`) 140 141 const templateFuncReplace = `(%s) => %s%s` // path , .replace(placeholder, args[0]) ... 142 143 func replacePlaceholders(endpoint string) string { 144 pls := rePlaceholder.FindAllString(endpoint, -1) 145 if len(pls) > 0 { 146 var args, calls string 147 for _, pl := range pls { 148 argname := pl[1:] 149 if argname == "default" { // js keywords 150 argname += "_" 151 } 152 args += argname + ":string," 153 calls += fmt.Sprintf(".replace('%s', %s)", pl, argname) 154 } 155 return fmt.Sprintf(templateFuncReplace, args, endpoint, calls) 156 } 157 return endpoint 158 }