summaryrefslogtreecommitdiff
path: root/cmd/generate/main.go
diff options
context:
space:
mode:
authorMathias Magnusson <mathias@magnusson.space>2025-04-10 19:06:03 +0200
committerMathias Magnusson <mathias@magnusson.space>2025-04-10 19:06:03 +0200
commitb9bf8a23c75db82e1aff8295a97dcfdf789735f3 (patch)
treeb6c57cb6747e9a1d56f243dd07f64cd15f1a9541 /cmd/generate/main.go
parent2778c52e4da52fd33f2df7fc9024252c2470b172 (diff)
downloadhh-b9bf8a23c75db82e1aff8295a97dcfdf789735f3.tar.gz
target is a module, not file; support path params; add function to mount all routes
Diffstat (limited to 'cmd/generate/main.go')
-rw-r--r--cmd/generate/main.go182
1 files changed, 107 insertions, 75 deletions
diff --git a/cmd/generate/main.go b/cmd/generate/main.go
index bdb46f2..06cf8fd 100644
--- a/cmd/generate/main.go
+++ b/cmd/generate/main.go
@@ -1,9 +1,11 @@
package main
import (
+ "bytes"
_ "embed"
"errors"
"go/ast"
+ "go/format"
"go/parser"
"go/token"
"os"
@@ -25,7 +27,7 @@ func slice(fileContents string, fset *token.FileSet, start token.Pos, end token.
return fileContents[fset.Position(start).Offset:fset.Position(end).Offset]
}
-type File struct {
+type Package struct {
PackageName string
Functions []Function
}
@@ -51,99 +53,129 @@ func run() error {
"quote": func(s string) string {
return `"` + strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\n", `\n`).Replace(s) + `"`
},
- "error": func() struct{} {
- panic("error")
+ "error": func(msg string) struct{} {
+ panic("error in template: " + msg)
},
}).Parse(fileTemplateString)
if err != nil {
return err
}
- filename := "examples/basic.go"
- fileBytes, err := os.ReadFile(filename)
+ dirEntries, err := os.ReadDir(".")
if err != nil {
return err
}
- fileContents := string(fileBytes)
- var fset token.FileSet
- f, err := parser.ParseFile(&fset, "examples/basic.go", fileBytes, parser.ParseComments|parser.SkipObjectResolution)
- if err != nil {
- return err
- }
- parsedFile := File{PackageName: f.Name.Name}
- for _, decl := range f.Decls {
- f, ok := decl.(*ast.FuncDecl)
- if !ok {
+
+ parsedPackage := Package{}
+ for _, ent := range dirEntries {
+ if !strings.HasSuffix(ent.Name(), ".go") {
continue
}
- if f.Doc == nil {
- continue
+
+ fileBytes, err := os.ReadFile(ent.Name())
+ if err != nil {
+ return err
}
- hhRoute := f.Doc.List[len(f.Doc.List)-1].Text
- var pattern string
- if pattern, ok = strings.CutPrefix(hhRoute, "//hh:route "); !ok {
- continue
+ fileContents := string(fileBytes)
+ var fset token.FileSet
+ f, err := parser.ParseFile(&fset, ent.Name(), fileBytes, parser.ParseComments|parser.SkipObjectResolution)
+ if err != nil {
+ return err
}
- parsedRequestType, ok := f.Type.Params.List[1].Type.(*ast.StructType)
- if !ok {
- return errors.New("Parsed request type must be a struct")
+ if strings.HasSuffix(f.Name.String(), "_test") {
+ continue
}
- parsedFunction := Function{
- Name: f.Name.Name,
- Pattern: pattern,
- RequestTypeDef: slice(fileContents, &fset, parsedRequestType.Pos(), parsedRequestType.End()),
+ if parsedPackage.PackageName != "" && parsedPackage.PackageName != f.Name.String() {
+ return errors.New("Found two different package names in directory: " + parsedPackage.PackageName + " and " + f.Name.String())
}
- for _, field := range parsedRequestType.Fields.List {
- for _, nameIdent := range field.Names {
- typ := field.Type
- parsedField := RequestTypeField{
- Name: nameIdent.Name,
- Extractor: "",
- Optional: false,
- TypeDef: slice(fileContents, &fset, typ.Pos(), typ.End()),
- }
- if parsedField.TypeDef == "*http.Request" {
- parsedFunction.RequestTypeFields = append(parsedFunction.RequestTypeFields, parsedField)
- continue
- }
- var tag string
- if field.Tag != nil {
- tag = reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("hh")
- }
- if tag == "" {
- return errors.New("Don't know what to do with '" + parsedField.Name + "'. You must add a `hh:\"...\"` tag to specify")
- }
- tags := strings.Split(tag, ",")
- if tags[0] == "optional" {
- parsedField.Optional = true
- tags = tags[1:]
- }
- if len(tags) == 0 {
- return errors.New("Must specify extractor for '" + parsedField.Name + "' in `" + tag + "`")
- }
- parsedField.Extractor = tags[0]
- tags = tags[1:]
- switch parsedField.Extractor {
- case "form":
- parsedFunction.DoParseForm = true
- case "cookie":
- default:
- return errors.New("Unknown extractor '" + tags[0] + "' on field " + nameIdent.Name)
- }
- if len(tags) >= 1 {
- parsedField.NameInReq = tags[0]
+ parsedPackage.PackageName = f.Name.String()
+
+ for _, decl := range f.Decls {
+ f, ok := decl.(*ast.FuncDecl)
+ if !ok {
+ continue
+ }
+ if f.Doc == nil {
+ continue
+ }
+ hhRoute := f.Doc.List[len(f.Doc.List)-1].Text
+ var pattern string
+ if pattern, ok = strings.CutPrefix(hhRoute, "//hh:route "); !ok {
+ continue
+ }
+ parsedRequestType, ok := f.Type.Params.List[1].Type.(*ast.StructType)
+ if !ok {
+ return errors.New("Parsed request type must be a struct")
+ }
+ parsedFunction := Function{
+ Name: f.Name.Name,
+ Pattern: pattern,
+ RequestTypeDef: slice(fileContents, &fset, parsedRequestType.Pos(), parsedRequestType.End()),
+ }
+ for _, field := range parsedRequestType.Fields.List {
+ for _, nameIdent := range field.Names {
+ typ := field.Type
+ parsedField := RequestTypeField{
+ Name: nameIdent.Name,
+ Extractor: "",
+ Optional: false,
+ TypeDef: slice(fileContents, &fset, typ.Pos(), typ.End()),
+ }
+ if parsedField.TypeDef == "*http.Request" {
+ parsedFunction.RequestTypeFields = append(parsedFunction.RequestTypeFields, parsedField)
+ continue
+ }
+ var tag string
+ if field.Tag != nil {
+ tag = reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]).Get("hh")
+ }
+ if tag == "" {
+ return errors.New("Don't know what to do with '" + parsedField.Name + "'. You must add a `hh:\"...\"` tag to specify")
+ }
+ tags := strings.Split(tag, ",")
+ if tags[0] == "optional" {
+ parsedField.Optional = true
+ tags = tags[1:]
+ }
+ if len(tags) == 0 {
+ return errors.New("Must specify extractor for '" + parsedField.Name + "' in `" + tag + "`")
+ }
+ parsedField.Extractor = tags[0]
tags = tags[1:]
- } else {
- parsedField.NameInReq = parsedField.Name
- }
- if len(tags) > 0 {
- return errors.New("Unexpected rest of tag '" + tags[0] + "' in tag `" + tag + "` on field " + nameIdent.Name)
+ switch parsedField.Extractor {
+ case "form":
+ parsedFunction.DoParseForm = true
+ case "cookie", "path":
+ default:
+ return errors.New("Unknown extractor '" + tags[0] + "' on field " + nameIdent.Name)
+ }
+ if len(tags) >= 1 {
+ parsedField.NameInReq = tags[0]
+ tags = tags[1:]
+ } else {
+ parsedField.NameInReq = parsedField.Name
+ }
+ if len(tags) > 0 {
+ return errors.New("Unexpected rest of tag '" + tags[0] + "' in tag `" + tag + "` on field " + nameIdent.Name)
+ }
+ parsedFunction.RequestTypeFields = append(parsedFunction.RequestTypeFields, parsedField)
}
- parsedFunction.RequestTypeFields = append(parsedFunction.RequestTypeFields, parsedField)
}
+ parsedPackage.Functions = append(parsedPackage.Functions, parsedFunction)
}
- parsedFile.Functions = append(parsedFile.Functions, parsedFunction)
}
- fileTemplate.Execute(os.Stdout, parsedFile)
+
+ var unformatted bytes.Buffer
+ if err := fileTemplate.Execute(&unformatted, parsedPackage); err != nil {
+ return err
+ }
+ formatted, err := format.Source(unformatted.Bytes())
+ if err != nil {
+ return err
+ }
+ if err := os.WriteFile("hh.gen.go", formatted, 0o660); err != nil {
+ return err
+ }
+
return nil
}