muxt

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2024 License: MIT Imports: 13 Imported by: 0

README

Muxt facilitates improved locality of behavior by letting you register HTTP routes in your templates Go Reference Go

This is especially helpful when you are writing HTMX.

Given main.go and index.gohtml, muxt will generate template_routes.go.

index.gohtml
<!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}}
main.go

In your Go code focus on your domain. Let muxt generate handlers and wire them up with the HTTP mux (multiplexer).

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))
}
template_routes.go

This file is generated by running go generate in the same directory as main.go

// Code generated by muxt. DO NOT EDIT.

package main

import (
	"context"
	"net/http"
	"bytes"
	"html/template"
)

type RoutesReceiver interface {
	SubmitFormEditRow(request *http.Request, fruit string) EditRowPage
	GetFormEditRow(fruit string) EditRowPage
	List(_ context.Context) []Row
}

func Routes(mux *http.ServeMux, receiver RoutesReceiver) {
	mux.HandleFunc("PATCH /fruits/{fruit}", func(response http.ResponseWriter, request *http.Request) {
		fruit := request.PathValue("fruit")
		data := receiver.SubmitFormEditRow(request, fruit)
		execute(response, request, templates.Lookup("PATCH /fruits/{fruit} SubmitFormEditRow(request, fruit)"), http.StatusOK, data)
	})
	mux.HandleFunc("GET /fruits/{fruit}/edit", func(response http.ResponseWriter, request *http.Request) {
		fruit := request.PathValue("fruit")
		data := receiver.GetFormEditRow(fruit)
		execute(response, request, templates.Lookup("GET /fruits/{fruit}/edit GetFormEditRow(fruit)"), http.StatusOK, data)
	})
	mux.HandleFunc("GET /help", func(response http.ResponseWriter, request *http.Request) {
		execute(response, request, templates.Lookup("GET /help"), http.StatusOK, request)
	})
	mux.HandleFunc("GET /{$}", func(response http.ResponseWriter, request *http.Request) {
		ctx := request.Context()
		data := receiver.List(ctx)
		execute(response, request, templates.Lookup("GET /{$} List(ctx)"), http.StatusOK, data)
	})
}
func execute(response http.ResponseWriter, request *http.Request, t *template.Template, code int, data any) {
	buf := bytes.NewBuffer(nil)
	if err := t.Execute(buf, data); err != nil {
		http.Error(response, err.Error(), http.StatusOK)
		return
	}
	response.WriteHeader(code)
	_, _ = buf.WriteTo(response)
}

Documentation

Index

Constants

View Source
const (
	DefaultTemplatesVariableName = "templates"
	DefaultRoutesFunctionName    = "routes"
	DefaultOutputFileName        = "template_routes.go"
)
View Source
const (
	TemplateNameScopeIdentifierHTTPRequest  = "request"
	TemplateNameScopeIdentifierHTTPResponse = "response"
	TemplateNameScopeIdentifierContext      = "ctx"
	TemplateNameScopeIdentifierTemplate     = "template"
	TemplateNameScopeIdentifierLogger       = "logger"
)

Variables

This section is empty.

Functions

func Generate added in v0.4.0

func Generate(templateNames []TemplateName, packageName, templatesVariableName, routesFunctionName, receiverTypeIdent string, _ *token.FileSet, receiverPackage, templatesPackage []*ast.File, log *log.Logger) (string, error)

Types

type TemplateName added in v0.5.0

type TemplateName struct {
	// contains filtered or unexported fields
}

func NewTemplateName added in v0.5.0

func NewTemplateName(in string) (TemplateName, error, bool)

func TemplateNames added in v0.5.0

func TemplateNames(ts *template.Template) ([]TemplateName, error)

func (TemplateName) Pattern added in v0.5.0

func (def TemplateName) Pattern() string

func (TemplateName) String added in v0.5.0

func (def TemplateName) String() string

Directories

Path Synopsis
cmd
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL