Skip to content

Commit

Permalink
fix bugs discovered while making an example
Browse files Browse the repository at this point in the history
  • Loading branch information
crhntr committed Aug 18, 2024
1 parent 339bceb commit b960ecd
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 888 deletions.
34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,35 @@

This is especially helpful when you are writing HTMX.

## Example
Given the following files

The "define" blocks in the following template register handlers with the server mux.
### main.go

The http method, http host, and path semantics match those of in the HTTP package.
```go
package main

This library extends this to add custom data handler invocations see "PATCH /fruits/{fruit}". It is configured to call EditRow on template parse time provided receiver.
import (
"html/template"
"log"
"net/http"
)

When no handler method is specified in the "declare" string (as is the case with "GET /fruits/{fruit}/edit" in the example), the template receives the *http.Request.
//go:embed *.gohtml
var templateSource embed.FS

var templates = template.Must(template.ParseFS(templateSource, "*"))

type Backend struct {}

func main() {
mux := http.NewServeMux()
muxt := Routes(mux, templates)


}
```

### index.gohtml
```html
<!DOCTYPE html>
<html lang="en">
Expand All @@ -36,7 +55,6 @@ When no handler method is specified in the "declare" string (as is the case with
</thead>
<tbody>

{{- range . -}}
{{- block "fruit row" . -}}
<tr>
<td>{{ .Fruit }}</td>
Expand All @@ -45,9 +63,11 @@ When no handler method is specified in the "declare" string (as is the case with
</td>
</tr>
{{- end -}}

{{- define "GET /{} List(ctx)" -}}
{{template "index.gohtml" .}}
{{- end -}}


{{- define "GET /fruits/{fruit}/edit" -}}
<tr>
<td>{{ .PathValue "fruit" }}</td>
Expand Down
5 changes: 4 additions & 1 deletion cmd/muxt/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Generate struct {
templatesVariable string
outputFilename string
routesFunction string
receiverIdent string
}

func (g Generate) ImportReceiverMethods(tp, method string) (*ast.FuncType, []*ast.ImportSpec, bool) {
Expand All @@ -43,9 +44,11 @@ func generateCommand(args []string, workingDirectory string, getEnv func(string)
flagSet.StringVar(&config.templatesVariable, "templates-variable", muxt.DefaultTemplatesVariableName, "templates variable name")
flagSet.StringVar(&config.outputFilename, "output-file", "template_routes.go", "file name of generated output")
flagSet.StringVar(&config.routesFunction, "routes-func", muxt.DefaultRoutesFunctionName, "file name of generated output")
flagSet.StringVar(&config.receiverIdent, "receiver", "", "static receiver type identifier")
if err := flagSet.Parse(args); err != nil {
return err
}
_ = os.Remove(filepath.Join(workingDirectory, config.outputFilename))
list, err := packages.Load(&packages.Config{
Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedEmbedPatterns | packages.NeedEmbedFiles,
Dir: workingDirectory,
Expand All @@ -72,7 +75,7 @@ func generateCommand(args []string, workingDirectory string, getEnv func(string)
return err
}
out := log.New(stdout, "", 0)
s, err := muxt.Generate(patterns, config.goPackage, config.templatesVariable, config.routesFunction, "", config.Package.Fset, config.Package.Syntax, config.Package.Syntax, out)
s, err := muxt.Generate(patterns, config.goPackage, config.templatesVariable, config.routesFunction, config.receiverIdent, config.Package.Fset, config.Package.Syntax, config.Package.Syntax, out)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/muxt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func command(wd string, args []string, getEnv func(string) string, stdout, stder

func handleError(err error) int {
if err != nil {
_, _ = os.Stderr.WriteString(err.Error())
_, _ = os.Stderr.WriteString(err.Error() + "\n")
return 1
}
return 0
Expand Down
76 changes: 76 additions & 0 deletions example/index.gohtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
{{block "head" "example"}}
<head>
<meta charset='UTF-8'/>
<title>{{.}}</title>
<script src='https://unpkg.com/htmx.org@2.0.1' integrity='sha384-QWGpdj554B4ETpJJC9z+ZHJcA/i59TyjxEPXiiUgN2WmTyV5OEZWCD6gQhgkdpB/' crossorigin='anonymous'></script>
<script src='https://unpkg.com/htmx-ext-response-targets@2.0.0/response-targets.js'></script>

<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css'>
</head>
{{end}}
<body hx-ext='response-targets'>
<main class='container'>
<table>
<thead>
<tr>
<th>Fruit</th>
<th>Count</th>
</tr>
</thead>
<tbody hx-target="closest tr" hx-swap="outerHTML">

{{- define "fruit row" -}}
<tr>
<td>{{ .Name }}</td>
<td id="count" hx-get='/fruits/{{.Name}}/edit'>{{ .Value }}</td>
</tr>
{{- end -}}

{{range .}}
{{template "fruit row" .}}
{{end}}

{{- define "GET /{$} List(ctx)" -}}
{{template "index.gohtml" .}}
{{- end -}}

{{- define "GET /fruits/{fruit}/edit GetFormEditRow(fruit)" -}}
<tr>
<td>{{ .Row.Name }}</td>
<td>
<form hx-patch='/fruits/{{.Row.Name}}'>
<input aria-label='Count' type='number' name='count' value='{{ .Row.Value }}' step='1' min='0'>
<input type='submit' value='Update'>
</form>
<p id='error'>{{.Error}}</p>
</td>
</tr>
{{- end -}}

{{- define "PATCH /fruits/{fruit} SubmitFormEditRow(request, fruit)" }}
{{- if .Error -}}
{{template "GET /fruits/{fruit}/edit GetFormEditRow(fruit)" .}}
{{- else -}}
{{template "fruit row" .Row}}
{{- end -}}
{{ end -}}

</tbody>
</table>
</main>
</body>
</html>

{{define "GET /help"}}
<!DOCTYPE html>
<html lang='us-en'>
{{template "head" "Help"}}
<body>
<main class='container'>
Hello, help!
</main>
</body>
</html>
{{end}}
70 changes: 70 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"context"
"embed"
"fmt"
"html/template"
"log"
"net/http"
"strconv"
)

//go:embed *.gohtml
var templateSource embed.FS

var templates = template.Must(template.ParseFS(templateSource, "*"))

type Backend struct {
data []Row
}

type EditRowPage struct {
Row Row
Error error
}

func (b *Backend) SubmitFormEditRow(request *http.Request, fruit string) EditRowPage {
count, err := strconv.Atoi(request.FormValue("count"))
if err != nil {
return EditRowPage{Error: err, Row: Row{Name: fruit}}
}
for i := range b.data {
if b.data[i].Name == fruit {
b.data[i].Value = count
return EditRowPage{Error: nil, Row: b.data[i]}
}
}
return EditRowPage{Error: fmt.Errorf("fruit not found")}
}

func (b *Backend) GetFormEditRow(fruit string) EditRowPage {
for i := range b.data {
if b.data[i].Name == fruit {
return EditRowPage{Error: nil, Row: b.data[i]}
}
}
return EditRowPage{Error: fmt.Errorf("fruit not found")}
}

type Row struct {
Name string
Value int
}

func (b *Backend) List(_ context.Context) []Row { return b.data }

//go:generate muxt generate --receiver Backend

func main() {
backend := &Backend{
data: []Row{
{Name: "Peach", Value: 10},
{Name: "Plum", Value: 20},
{Name: "Pineapple", Value: 2},
},
}
mux := http.NewServeMux()
Routes(mux, backend)
log.Fatal(http.ListenAndServe(":8080", mux))
}
46 changes: 46 additions & 0 deletions example/template_routes.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 17 additions & 9 deletions generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ func Generate(patterns []Pattern, packageName, templatesVariableName, routesFunc
Type: method,
})
}
handlerFunc, handerSignatureImports, err := pattern.funcLit(templatesVariableName, method)
handlerFunc, methodImports, err := pattern.funcLit(templatesVariableName, method)
if err != nil {
return "", err
}
imports = source.SortImports(append(imports, handerSignatureImports...))
imports = source.SortImports(append(imports, methodImports...))
routes.Body.List = append(routes.Body.List, pattern.callHandleFunc(handlerFunc))
log.Printf("%s has route for %s", routesFunctionName, pattern.String())
}
Expand Down Expand Up @@ -163,12 +163,12 @@ func (def Pattern) funcLit(templatesVariableIdent string, method *ast.FuncType)
}
}

const dataVarIdent = "data"
if method != nil && len(method.Results.List) > 1 {
dataVar := ast.NewIdent(dataVarIdent)
errVar := ast.NewIdent("err")

lit.Body.List = append(lit.Body.List,
&ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent(dataVar.Name), ast.NewIdent(errVar.Name)}, Tok: token.DEFINE, Rhs: []ast.Expr{call}},
&ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent(dataVarIdent), ast.NewIdent(errVar.Name)}, Tok: token.DEFINE, Rhs: []ast.Expr{call}},
&ast.IfStmt{
Cond: &ast.BinaryExpr{X: ast.NewIdent(errVar.Name), Op: token.NEQ, Y: ast.NewIdent("nil")},
Body: &ast.BlockStmt{
Expand All @@ -194,11 +194,10 @@ func (def Pattern) funcLit(templatesVariableIdent string, method *ast.FuncType)
},
},
)
} else {
lit.Body.List = append(lit.Body.List, &ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent(dataVarIdent)}, Tok: token.DEFINE, Rhs: []ast.Expr{call}})
}

data := ast.NewIdent(dataVarIdent)
lit.Body.List = append(lit.Body.List, &ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent(data.Name)}, Tok: token.DEFINE, Rhs: []ast.Expr{call}})
lit.Body.List = append(lit.Body.List, def.executeCall(ast.NewIdent(templatesVariableIdent), httpStatusCode(httpStatusCode200Ident), data))
lit.Body.List = append(lit.Body.List, def.executeCall(ast.NewIdent(templatesVariableIdent), httpStatusCode(httpStatusCode200Ident), ast.NewIdent(dataVarIdent)))
return lit, imports, nil
}

Expand Down Expand Up @@ -422,7 +421,16 @@ func (def Pattern) httpRequestReceiverTemplateHandlerFunc(templatesVariableName
}

func (def Pattern) matchReceiver(funcDecl *ast.FuncDecl, receiverTypeIdent string) bool {
return funcDecl.Name.Name == def.fun.Name && funcDecl.Recv != nil && len(funcDecl.Recv.List) == 1 && funcDecl.Recv.List[0].Type.(*ast.Ident).Name == receiverTypeIdent
if funcDecl == nil || funcDecl.Name == nil || funcDecl.Name.Name != def.fun.Name || funcDecl.Recv == nil && len(funcDecl.Recv.List) < 1 {
return false
}
exp := funcDecl.Recv.List[0].Type
if star, ok := exp.(*ast.StarExpr); ok {
exp = star.X
}
ident, ok := exp.(*ast.Ident)
return ok && ident.Name == receiverTypeIdent

}

func executeFuncDecl() *ast.FuncDecl {
Expand Down
Loading

0 comments on commit b960ecd

Please sign in to comment.