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  }