diff options
Diffstat (limited to 'cmd/generate')
-rw-r--r-- | cmd/generate/main.go | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/cmd/generate/main.go b/cmd/generate/main.go new file mode 100644 index 0000000..9f1f839 --- /dev/null +++ b/cmd/generate/main.go @@ -0,0 +1,170 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "net/http" + "strconv" + "strings" +) + +func main() { + if err := run(); err != nil { + panic(err) + } +} + +func run() error { + var fset token.FileSet + f, err := parser.ParseFile(&fset, "examples/basic.go", nil, parser.ParseComments|parser.SkipObjectResolution) + if err != nil { + return err + } + for _, decl := range f.Decls { + f, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + hhRoute := f.Doc.List[len(f.Doc.List)-1].Text + var routeSpec string + if routeSpec, ok = strings.CutPrefix(hhRoute, "//hh:route "); !ok { + continue + } + rs, err := parseRouteSpec(routeSpec) + if err != nil { + return err + } + var wrapper bytes.Buffer + wrapper.WriteString("func httpWrapper_") + wrapper.WriteString(f.Name.String()) + wrapper.WriteString("[S any](s S, r *http.Request, w http.ResponseWriter) {") + i := 0 + for _, p := range f.Type.Params.List { + for _, n := range p.Names { + wrapper.WriteString("\n\tvar") + wrapper.WriteString(strconv.Itoa(i)) + wrapper.WriteString(" := ") + wrapper.WriteString(n.Name) + i++ + } + } + wrapper.WriteString("\t") + wrapper.WriteString(f.Name.Name) + wrapper.WriteString("(") + i = 0 + for _, p := range f.Type.Params.List { + for range p.Names { + if i > 0 { + wrapper.WriteString(", ") + } + wrapper.WriteString("var") + wrapper.WriteString(strconv.Itoa(i)) + i++ + } + } + wrapper.WriteString(")\n") + wrapper.WriteString("}\n") + + fmt.Printf("`%v`\n`%v`\n`%v`\n", routeSpec, f.Name.Name, rs) + } + return nil +} + +type routeSpec struct { + method string + path string + parameters []routeSpecParam +} + +type routeSpecParam struct { + name string + extractor string +} + +func parseRouteSpec(s string) (routeSpec, error) { + s = strings.TrimRight(s, " \n") + var rs routeSpec + for _, method := range []string{ + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodConnect, + http.MethodOptions, + http.MethodTrace, + } { + if rest, ok := strings.CutPrefix(s, method+" "); ok { + s = rest + rs.method = method + break + } + } + + for { + if commaPos := strings.IndexByte(s, ','); commaPos != -1 { + rs.path = s[:commaPos] + s = s[commaPos+1:] + } else { + rs.path = s + return rs, nil + } + + s = strings.TrimLeft(s, " ") + if s == "" { + break + } + + end := -1 + for i := 0; i < len(s); i++ { + if 'a' <= s[i] && s[i] <= 'z' || + 'A' <= s[i] && s[i] <= 'Z' || + s[i] == '_' { + continue + } + if s[i] == ':' { + end = i + break + } + return rs, errors.New("Expected ':' to mark end of parameter name, got " + s[i:i+1]) + } + if end == -1 { + return rs, errors.New("Expected ':' to mark end of parameter name, got end of line") + } + var p routeSpecParam + p.name = s[:end] + s = s[end+1:] + + s = strings.TrimLeft(s, " ") + + expr, err := parser.ParseExpr(s) + if el, ok := err.(scanner.ErrorList); err == nil || ok && el[0].Msg == "expected 'EOF', found ','" { + switch expr := expr.(type) { + case *ast.Ident: + switch expr.Name { + case "query": + p.extractor = ":" + expr.Name + default: + return rs, errors.New("Unexpected extractor " + expr.Name) + } + case *ast.CallExpr: + p.extractor = s[expr.Pos()-1 : expr.End()-1] + default: + return rs, errors.New("Unexpected extractor" + s[expr.Pos()-1:expr.End()-1]) + } + } else { + return rs, err + } + s = s[expr.End()-1:] + + rs.parameters = append(rs.parameters, p) + } + + return rs, nil +} |