~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
4bd0b2cb
—
Peter John 3 years ago
sort routes
- cmd/gromer/main.go +26 -15
- example/main.go +11 -11
- example/pages/about/get.go +0 -2
- example/pages/api/todos/_todoId_/delete.go +0 -2
- http.go +26 -6
cmd/gromer/main.go
CHANGED
|
@@ -8,6 +8,7 @@ import (
|
|
|
8
8
|
"log"
|
|
9
9
|
"os"
|
|
10
10
|
"path/filepath"
|
|
11
|
+
"sort"
|
|
11
12
|
"strings"
|
|
12
13
|
"unicode"
|
|
13
14
|
|
|
@@ -50,10 +51,6 @@ func getRoute(method, src string) string {
|
|
|
50
51
|
return strings.ReplaceAll(src, "/"+strings.ToLower(method)+".go", "")
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
func getPackage(src string) string {
|
|
54
|
-
return src
|
|
55
|
-
}
|
|
56
|
-
|
|
57
54
|
func rewritePath(route string) string {
|
|
58
55
|
muxRoute := bytes.NewBuffer(nil)
|
|
59
56
|
foundStart := false
|
|
@@ -87,6 +84,10 @@ func lowerFirst(s string) string {
|
|
|
87
84
|
return ""
|
|
88
85
|
}
|
|
89
86
|
|
|
87
|
+
func getPackage(src string) string {
|
|
88
|
+
return src
|
|
89
|
+
}
|
|
90
|
+
|
|
90
91
|
func main() {
|
|
91
92
|
moduleName := ""
|
|
92
93
|
pkgFlag := flag.String("pkg", "", "specify a package name")
|
|
@@ -104,7 +105,6 @@ func main() {
|
|
|
104
105
|
} else {
|
|
105
106
|
moduleName = *pkgFlag
|
|
106
107
|
}
|
|
107
|
-
allPkgs := map[string]string{}
|
|
108
108
|
err := filepath.Walk("pages",
|
|
109
109
|
func(filesrc string, info os.FileInfo, err error) error {
|
|
110
110
|
if err != nil {
|
|
@@ -115,15 +115,15 @@ func main() {
|
|
|
115
115
|
method := getMethod(route)
|
|
116
116
|
path := getRoute(method, route)
|
|
117
117
|
pkg := getPackage(path)
|
|
118
|
-
allPkgs[pkg] = ""
|
|
119
118
|
if path == "" { // for index page
|
|
120
119
|
path = "/"
|
|
121
120
|
pkg = "pages"
|
|
122
121
|
}
|
|
123
122
|
gromer.RouteDefs = append(gromer.RouteDefs, gromer.RouteDefinition{
|
|
123
|
+
Pkg: rewritePkg(pkg),
|
|
124
|
+
PkgPath: getRoute(method, route),
|
|
124
|
-
Method:
|
|
125
|
+
Method: method,
|
|
125
|
-
Path:
|
|
126
|
+
Path: rewritePath(path),
|
|
126
|
-
Pkg: rewritePkg(pkg),
|
|
127
127
|
})
|
|
128
128
|
}
|
|
129
129
|
return nil
|
|
@@ -131,8 +131,11 @@ func main() {
|
|
|
131
131
|
if err != nil {
|
|
132
132
|
log.Fatal(err)
|
|
133
133
|
}
|
|
134
|
+
sort.Slice(gromer.RouteDefs, func(i, j int) bool {
|
|
135
|
+
return gromer.RouteDefs[i].Path < gromer.RouteDefs[j].Path
|
|
136
|
+
})
|
|
134
137
|
for _, r := range gromer.RouteDefs {
|
|
135
|
-
fmt.Printf("%-6s %s\n", r.Method, r.Path)
|
|
138
|
+
fmt.Printf("%-6s %s %-6s\n", r.Method, r.Path, r.PkgPath)
|
|
136
139
|
}
|
|
137
140
|
err = velvet.Helpers.Add("title", func(v string) string {
|
|
138
141
|
return strings.Title(strings.ToLower(v))
|
|
@@ -140,10 +143,18 @@ func main() {
|
|
|
140
143
|
if err != nil {
|
|
141
144
|
log.Fatal(err)
|
|
142
145
|
}
|
|
146
|
+
hasRouteMap := map[string]bool{}
|
|
147
|
+
routeImports := []gromer.RouteDefinition{}
|
|
148
|
+
for _, v := range gromer.RouteDefs {
|
|
149
|
+
if _, ok := hasRouteMap[v.PkgPath]; !ok {
|
|
150
|
+
routeImports = append(routeImports, v)
|
|
151
|
+
hasRouteMap[v.PkgPath] = true
|
|
152
|
+
}
|
|
153
|
+
}
|
|
143
154
|
ctx := velvet.NewContext()
|
|
144
155
|
ctx.Set("moduleName", moduleName)
|
|
145
|
-
ctx.Set("allPkgs", allPkgs)
|
|
146
156
|
ctx.Set("routes", gromer.RouteDefs)
|
|
157
|
+
ctx.Set("routeImports", routeImports)
|
|
147
158
|
ctx.Set("tick", "`")
|
|
148
159
|
s, err := velvet.Render(`// Code generated by gromer. DO NOT EDIT.
|
|
149
160
|
package main
|
|
@@ -157,7 +168,7 @@ import (
|
|
|
157
168
|
"gocloud.dev/server"
|
|
158
169
|
|
|
159
170
|
"{{ moduleName }}/assets"
|
|
160
|
-
{{#each
|
|
171
|
+
{{#each routeImports as |route| }}"{{ moduleName }}/pages{{ route.PkgPath }}"
|
|
161
172
|
{{/each}}
|
|
162
173
|
)
|
|
163
174
|
|
|
@@ -168,10 +179,10 @@ func main() {
|
|
|
168
179
|
r.Use(gromer.LogMiddleware)
|
|
169
180
|
r.NotFoundHandler = gromer.NotFoundHandler
|
|
170
181
|
gromer.Static(r, "/assets/", assets.FS)
|
|
171
|
-
gromer.Handle(r, "GET", "/api", gromer.ApiExplorer
|
|
182
|
+
gromer.Handle(r, "GET", "/api", gromer.ApiExplorer)
|
|
172
|
-
{{#each routes as |route| }}gromer.Handle(r, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }}
|
|
183
|
+
{{#each routes as |route| }}gromer.Handle(r, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }})
|
|
173
184
|
{{/each}}
|
|
174
|
-
|
|
185
|
+
log.Info().Msg("http server listening on http://localhost:"+port)
|
|
175
186
|
srv := server.New(r, nil)
|
|
176
187
|
if err := srv.ListenAndServe(":"+port); err != nil {
|
|
177
188
|
log.Fatal().Stack().Err(err).Msg("failed to listen")
|
example/main.go
CHANGED
|
@@ -10,10 +10,10 @@ import (
|
|
|
10
10
|
"gocloud.dev/server"
|
|
11
11
|
|
|
12
12
|
"github.com/pyros2097/gromer/example/assets"
|
|
13
|
-
"github.com/pyros2097/gromer/example/pages/api/todos"
|
|
14
13
|
"github.com/pyros2097/gromer/example/pages"
|
|
15
14
|
"github.com/pyros2097/gromer/example/pages/about"
|
|
16
15
|
"github.com/pyros2097/gromer/example/pages/api/recover"
|
|
16
|
+
"github.com/pyros2097/gromer/example/pages/api/todos"
|
|
17
17
|
"github.com/pyros2097/gromer/example/pages/api/todos/_todoId_"
|
|
18
18
|
|
|
19
19
|
)
|
|
@@ -25,17 +25,17 @@ func main() {
|
|
|
25
25
|
r.Use(gromer.LogMiddleware)
|
|
26
26
|
r.NotFoundHandler = gromer.NotFoundHandler
|
|
27
27
|
gromer.Static(r, "/assets/", assets.FS)
|
|
28
|
-
gromer.Handle(r, "GET", "/api", gromer.ApiExplorer
|
|
28
|
+
gromer.Handle(r, "GET", "/api", gromer.ApiExplorer)
|
|
29
|
+
gromer.Handle(r, "GET", "/", pages.GET)
|
|
29
|
-
gromer.Handle(r, "GET", "/about", about.GET
|
|
30
|
+
gromer.Handle(r, "GET", "/about", about.GET)
|
|
30
|
-
gromer.Handle(r, "GET", "/api/recover", recover.GET
|
|
31
|
+
gromer.Handle(r, "GET", "/api/recover", recover.GET)
|
|
31
|
-
gromer.Handle(r, "DELETE", "/api/todos/{todoId}", todos_todoId_.DELETE, todos_todoId_.DeleteParams{})
|
|
32
|
-
gromer.Handle(r, "GET", "/api/todos/{todoId}", todos_todoId_.GET, todos_todoId_.GetParams{})
|
|
33
|
-
gromer.Handle(r, "PUT", "/api/todos/{todoId}", todos_todoId_.PUT, todos_todoId_.PutParams{})
|
|
34
|
-
gromer.Handle(r, "GET", "/api/todos", todos.GET
|
|
32
|
+
gromer.Handle(r, "GET", "/api/todos", todos.GET)
|
|
35
|
-
gromer.Handle(r, "POST", "/api/todos", todos.POST
|
|
33
|
+
gromer.Handle(r, "POST", "/api/todos", todos.POST)
|
|
34
|
+
gromer.Handle(r, "DELETE", "/api/todos/{todoId}", todos_todoId_.DELETE)
|
|
36
|
-
gromer.Handle(r, "GET", "/",
|
|
35
|
+
gromer.Handle(r, "GET", "/api/todos/{todoId}", todos_todoId_.GET)
|
|
36
|
+
gromer.Handle(r, "PUT", "/api/todos/{todoId}", todos_todoId_.PUT)
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
log.Info().Msg("http server listening on http://localhost:"+port)
|
|
39
39
|
srv := server.New(r, nil)
|
|
40
40
|
if err := srv.ListenAndServe(":"+port); err != nil {
|
|
41
41
|
log.Fatal().Stack().Err(err).Msg("failed to listen")
|
example/pages/about/get.go
CHANGED
|
@@ -6,8 +6,6 @@ import (
|
|
|
6
6
|
. "github.com/pyros2097/gromer"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
type GetParams struct{}
|
|
10
|
-
|
|
11
9
|
func GET(c context.Context) (HtmlContent, int, error) {
|
|
12
10
|
return Html(`
|
|
13
11
|
{{#Page "gromer example"}}
|
example/pages/api/todos/_todoId_/delete.go
CHANGED
|
@@ -6,8 +6,6 @@ import (
|
|
|
6
6
|
"github.com/pyros2097/gromer/example/db"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
type DeleteParams struct{}
|
|
10
|
-
|
|
11
9
|
func DELETE(ctx context.Context, id string) (string, int, error) {
|
|
12
10
|
_, status, err := GET(ctx, id, GetParams{})
|
|
13
11
|
if err != nil {
|
http.go
CHANGED
|
@@ -31,10 +31,11 @@ var IsCloundRun bool
|
|
|
31
31
|
var RouteDefs []RouteDefinition
|
|
32
32
|
|
|
33
33
|
type RouteDefinition struct {
|
|
34
|
+
Pkg string `json:"pkg"`
|
|
35
|
+
PkgPath string `json:"pkgPath"`
|
|
34
|
-
Method
|
|
36
|
+
Method string `json:"method"`
|
|
35
|
-
Pkg string `json:"pkg"`
|
|
36
|
-
Path
|
|
37
|
+
Path string `json:"path"`
|
|
37
|
-
Params
|
|
38
|
+
Params interface{} `json:"params"`
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
type HtmlContent string
|
|
@@ -121,6 +122,25 @@ func GetRouteParams(route string) []string {
|
|
|
121
122
|
return params
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
func addRouteDef(method, route string, h interface{}) {
|
|
126
|
+
pathParams := GetRouteParams(route)
|
|
127
|
+
var body any = nil
|
|
128
|
+
funcType := reflect.TypeOf(h)
|
|
129
|
+
if funcType.NumIn() > len(pathParams)+1 {
|
|
130
|
+
structType := funcType.In(funcType.NumIn() - 1)
|
|
131
|
+
instance := reflect.New(structType)
|
|
132
|
+
if structType.Kind() != reflect.Struct {
|
|
133
|
+
log.Fatal().Msgf("router '%s' '%s' func final param should be a struct", method, route)
|
|
134
|
+
}
|
|
135
|
+
body = instance.Interface()
|
|
136
|
+
}
|
|
137
|
+
RouteDefs = append(RouteDefs, RouteDefinition{
|
|
138
|
+
Method: method,
|
|
139
|
+
Path: route,
|
|
140
|
+
Params: body,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
124
144
|
func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) {
|
|
125
145
|
params := GetRouteParams(route)
|
|
126
146
|
args := []reflect.Value{reflect.ValueOf(ctx)}
|
|
@@ -302,8 +322,8 @@ func Static(router *mux.Router, path string, fs embed.FS) {
|
|
|
302
322
|
router.PathPrefix(path).Handler(http.StripPrefix(path, WrapCache(http.FileServer(http.FS(fs)))))
|
|
303
323
|
}
|
|
304
324
|
|
|
305
|
-
func Handle(router *mux.Router, method, route string, h interface{}
|
|
325
|
+
func Handle(router *mux.Router, method, route string, h interface{}) {
|
|
306
|
-
|
|
326
|
+
addRouteDef(method, route, h)
|
|
307
327
|
router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
|
|
308
328
|
ctx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
|
|
309
329
|
PerformRequest(route, h, ctx, w, r)
|