summaryrefslogtreecommitdiff
path: root/cmd/generate
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/generate')
-rw-r--r--cmd/generate/main.go170
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
+}