~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.
43cb0718
—
Peter John 3 years ago
fix favicon
- _example/assets/favicon.ico +0 -0
- _example/main.go +3 -5
- assets/assets.go +1 -1
- cmd/gromer/main.go +4 -5
- http.go +23 -50
- readme.md +38 -38
_example/assets/favicon.ico
ADDED
|
Binary file
|
_example/main.go
CHANGED
|
@@ -4,6 +4,7 @@ package main
|
|
|
4
4
|
import (
|
|
5
5
|
"github.com/gorilla/mux"
|
|
6
6
|
"github.com/pyros2097/gromer"
|
|
7
|
+
"github.com/pyros2097/gromer/assets"
|
|
7
8
|
"github.com/pyros2097/gromer/gsx"
|
|
8
9
|
"github.com/rs/zerolog/log"
|
|
9
10
|
"gocloud.dev/server"
|
|
@@ -20,11 +21,8 @@ import (
|
|
|
20
21
|
func init() {
|
|
21
22
|
gsx.RegisterComponent(components.Todo, "todo")
|
|
22
23
|
gsx.RegisterComponent(components.Checkbox, "value")
|
|
23
|
-
|
|
24
24
|
gsx.RegisterComponent(containers.TodoCount, "filter")
|
|
25
25
|
gsx.RegisterComponent(containers.TodoList, "page", "filter")
|
|
26
|
-
|
|
27
|
-
gromer.RegisterAssets(assets.FS)
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
func main() {
|
|
@@ -34,8 +32,8 @@ func main() {
|
|
|
34
32
|
|
|
35
33
|
staticRouter := baseRouter.NewRoute().Subrouter()
|
|
36
34
|
staticRouter.Use(gromer.CacheMiddleware)
|
|
37
|
-
gromer.
|
|
35
|
+
gromer.StaticRoute(staticRouter, "/gromer/", gromer_assets.FS)
|
|
38
|
-
gromer.StaticRoute(staticRouter, "/assets/")
|
|
36
|
+
gromer.StaticRoute(staticRouter, "/assets/", assets.FS)
|
|
39
37
|
gromer.StylesRoute(staticRouter, "/styles.css")
|
|
40
38
|
|
|
41
39
|
pageRouter := baseRouter.NewRoute().Subrouter()
|
assets/assets.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package gromer_assets
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"embed"
|
cmd/gromer/main.go
CHANGED
|
@@ -182,6 +182,7 @@ package main
|
|
|
182
182
|
import (
|
|
183
183
|
"github.com/gorilla/mux"
|
|
184
184
|
"github.com/pyros2097/gromer"
|
|
185
|
+
"github.com/pyros2097/gromer/assets"
|
|
185
186
|
"github.com/pyros2097/gromer/gsx"
|
|
186
187
|
"github.com/rs/zerolog/log"
|
|
187
188
|
"gocloud.dev/server"
|
|
@@ -196,10 +197,8 @@ import (
|
|
|
196
197
|
|
|
197
198
|
func init() {
|
|
198
199
|
{{#each componentNames as |name| }}gsx.RegisterComponent(components.{{ name }})
|
|
200
|
+
{{/each}}{{#each containerNames as |name| }}gsx.RegisterComponent(containers.{{ name }})
|
|
199
201
|
{{/each}}
|
|
200
|
-
{{#each containerNames as |name| }}gsx.RegisterComponent(containers.{{ name }})
|
|
201
|
-
{{/each}}
|
|
202
|
-
gromer.RegisterAssets(assets.FS)
|
|
203
202
|
}
|
|
204
203
|
|
|
205
204
|
func main() {
|
|
@@ -209,8 +208,8 @@ func main() {
|
|
|
209
208
|
{{/if}}
|
|
210
209
|
staticRouter := baseRouter.NewRoute().Subrouter()
|
|
211
210
|
staticRouter.Use(gromer.CacheMiddleware)
|
|
212
|
-
gromer.
|
|
211
|
+
gromer.StaticRoute(staticRouter, "/gromer/", gromer_assets.FS)
|
|
213
|
-
gromer.StaticRoute(staticRouter, "/assets/")
|
|
212
|
+
gromer.StaticRoute(staticRouter, "/assets/", assets.FS)
|
|
214
213
|
gromer.StylesRoute(staticRouter, "/styles.css")
|
|
215
214
|
|
|
216
215
|
pageRouter := baseRouter.NewRoute().Subrouter()
|
http.go
CHANGED
|
@@ -22,7 +22,6 @@ import (
|
|
|
22
22
|
"github.com/go-playground/validator/v10"
|
|
23
23
|
"github.com/google/uuid"
|
|
24
24
|
"github.com/gorilla/mux"
|
|
25
|
-
"github.com/pyros2097/gromer/assets"
|
|
26
25
|
"github.com/pyros2097/gromer/gsx"
|
|
27
26
|
"github.com/rs/zerolog"
|
|
28
27
|
"github.com/rs/zerolog/log"
|
|
@@ -30,8 +29,21 @@ import (
|
|
|
30
29
|
"xojoc.pw/useragent"
|
|
31
30
|
)
|
|
32
31
|
|
|
32
|
+
var (
|
|
33
|
-
|
|
33
|
+
info *debug.BuildInfo
|
|
34
|
-
|
|
34
|
+
IsCloundRun bool
|
|
35
|
+
routeDefs []RouteDefinition
|
|
36
|
+
pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
type RouteDefinition struct {
|
|
40
|
+
Pkg string `json:"pkg"`
|
|
41
|
+
PkgPath string `json:"pkgPath"`
|
|
42
|
+
Method string `json:"method"`
|
|
43
|
+
Path string `json:"path"`
|
|
44
|
+
PathParams []string `json:"pathParams"`
|
|
45
|
+
Params interface{} `json:"params"`
|
|
46
|
+
}
|
|
35
47
|
|
|
36
48
|
func init() {
|
|
37
49
|
IsCloundRun = os.Getenv("K_REVISION") != ""
|
|
@@ -51,20 +63,6 @@ func init() {
|
|
|
51
63
|
}
|
|
52
64
|
gsx.RegisterFunc(GetStylesUrl)
|
|
53
65
|
gsx.RegisterFunc(GetAssetUrl)
|
|
54
|
-
gsx.RegisterFunc(GetAlpineJsUrl)
|
|
55
|
-
gsx.RegisterFunc(GetHtmxJsUrl)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
var RouteDefs []RouteDefinition
|
|
59
|
-
var appAssets embed.FS
|
|
60
|
-
|
|
61
|
-
type RouteDefinition struct {
|
|
62
|
-
Pkg string `json:"pkg"`
|
|
63
|
-
PkgPath string `json:"pkgPath"`
|
|
64
|
-
Method string `json:"method"`
|
|
65
|
-
Path string `json:"path"`
|
|
66
|
-
PathParams []string `json:"pathParams"`
|
|
67
|
-
Params interface{} `json:"params"`
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
func getFunctionName(temp interface{}) string {
|
|
@@ -72,10 +70,6 @@ func getFunctionName(temp interface{}) string {
|
|
|
72
70
|
return strs[len(strs)-1]
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
func RegisterAssets(fs embed.FS) {
|
|
76
|
-
appAssets = fs
|
|
77
|
-
}
|
|
78
|
-
|
|
79
73
|
func RespondError(w http.ResponseWriter, status int, err error) {
|
|
80
74
|
w.Header().Set("Content-Type", "application/json")
|
|
81
75
|
w.WriteHeader(status) // always write status last
|
|
@@ -94,8 +88,6 @@ func RespondError(w http.ResponseWriter, status int, err error) {
|
|
|
94
88
|
w.Write(data)
|
|
95
89
|
}
|
|
96
90
|
|
|
97
|
-
var pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
|
|
98
|
-
|
|
99
91
|
func GetRouteParams(route string) []string {
|
|
100
92
|
params := []string{}
|
|
101
93
|
found := pathParamsRegex.FindAllString(route, -1)
|
|
@@ -117,7 +109,7 @@ func addRouteDef(method, route string, h interface{}) {
|
|
|
117
109
|
}
|
|
118
110
|
body = instance.Interface()
|
|
119
111
|
}
|
|
120
|
-
|
|
112
|
+
routeDefs = append(routeDefs, RouteDefinition{
|
|
121
113
|
Method: method,
|
|
122
114
|
Path: route,
|
|
123
115
|
PathParams: pathParams,
|
|
@@ -129,10 +121,10 @@ func PerformRequest(route string, h interface{}, ctx context.Context, w http.Res
|
|
|
129
121
|
params := GetRouteParams(route)
|
|
130
122
|
renderContext := gsx.NewContext(ctx, r.Header.Get("HX-Request") == "true")
|
|
131
123
|
renderContext.Set("requestId", uuid.NewString())
|
|
132
|
-
renderContext.Link("rel", GetAssetUrl("images/icon.png"), "", "")
|
|
133
124
|
renderContext.Link("stylesheet", GetStylesUrl(), "", "")
|
|
134
|
-
renderContext.
|
|
125
|
+
renderContext.Link("icon", "/assets/favicon.ico", "image/x-icon", "image")
|
|
135
|
-
renderContext.Script(
|
|
126
|
+
renderContext.Script("/gromer/js/htmx@1.7.0.js", false)
|
|
127
|
+
renderContext.Script("/gromer/js/alpinejs@3.9.6.js", true)
|
|
136
128
|
args := []reflect.Value{reflect.ValueOf(renderContext)}
|
|
137
129
|
funcType := reflect.TypeOf(h)
|
|
138
130
|
icount := funcType.NumIn()
|
|
@@ -225,13 +217,6 @@ func PerformRequest(route string, h interface{}, ctx context.Context, w http.Res
|
|
|
225
217
|
v.Write(renderContext, w)
|
|
226
218
|
return
|
|
227
219
|
}
|
|
228
|
-
// if v, ok := response.(handlebars.CssContent); ok {
|
|
229
|
-
// w.Header().Set("Content-Type", "text/css")
|
|
230
|
-
// // This has to be at end always
|
|
231
|
-
// w.WriteHeader(responseStatus)
|
|
232
|
-
// w.Write([]byte(v))
|
|
233
|
-
// return
|
|
234
|
-
// }
|
|
235
220
|
w.Header().Set("Content-Type", "application/json")
|
|
236
221
|
// This has to be at end always
|
|
237
222
|
w.WriteHeader(responseStatus)
|
|
@@ -369,12 +354,8 @@ func StatusHandler(h interface{}) http.Handler {
|
|
|
369
354
|
})).(http.Handler)
|
|
370
355
|
}
|
|
371
356
|
|
|
372
|
-
func StaticRoute(router *mux.Router, path string) {
|
|
357
|
+
func StaticRoute(router *mux.Router, path string, fs embed.FS) {
|
|
373
|
-
router.PathPrefix(path).Methods("GET").Handler(http.StripPrefix(path, http.FileServer(http.FS(
|
|
358
|
+
router.PathPrefix(path).Methods("GET").Handler(http.StripPrefix(path, http.FileServer(http.FS(fs))))
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
func GromerRoute(router *mux.Router, path string) {
|
|
377
|
-
router.PathPrefix(path).Methods("GET").Handler(http.StripPrefix(path, http.FileServer(http.FS(assets.FS))))
|
|
378
359
|
}
|
|
379
360
|
|
|
380
361
|
func StylesRoute(router *mux.Router, path string) {
|
|
@@ -412,9 +393,9 @@ func getSum(k string, cb func() [16]byte) string {
|
|
|
412
393
|
return sum
|
|
413
394
|
}
|
|
414
395
|
|
|
415
|
-
func GetAssetUrl(path string) string {
|
|
396
|
+
func GetAssetUrl(fs embed.FS, path string) string {
|
|
416
397
|
sum := getSum(path, func() [16]byte {
|
|
417
|
-
data, err :=
|
|
398
|
+
data, err := fs.ReadFile(path)
|
|
418
399
|
if err != nil {
|
|
419
400
|
panic(err)
|
|
420
401
|
}
|
|
@@ -423,14 +404,6 @@ func GetAssetUrl(path string) string {
|
|
|
423
404
|
return fmt.Sprintf("/assets/%s?hash=%s", path, sum)
|
|
424
405
|
}
|
|
425
406
|
|
|
426
|
-
func GetHtmxJsUrl() string {
|
|
427
|
-
return "/gromer/js/htmx@1.7.0.js"
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
func GetAlpineJsUrl() string {
|
|
431
|
-
return "/gromer/js/alpinejs@3.9.6.js"
|
|
432
|
-
}
|
|
433
|
-
|
|
434
407
|
func GetStylesUrl() string {
|
|
435
408
|
sum := getSum("styles.css", func() [16]byte {
|
|
436
409
|
return md5.Sum([]byte(gsx.GetStyles()))
|
readme.md
CHANGED
|
@@ -116,50 +116,50 @@ And then run the gromer cli command annd it will generate the route handlers in
|
|
|
116
116
|
package main
|
|
117
117
|
|
|
118
118
|
import (
|
|
119
|
-
|
|
119
|
+
"github.com/gorilla/mux"
|
|
120
|
-
|
|
120
|
+
"github.com/pyros2097/gromer"
|
|
121
|
+
"github.com/pyros2097/gromer/assets"
|
|
122
|
+
"github.com/pyros2097/gromer/gsx"
|
|
121
|
-
|
|
123
|
+
"github.com/rs/zerolog/log"
|
|
122
|
-
|
|
124
|
+
"gocloud.dev/server"
|
|
123
|
-
|
|
125
|
+
|
|
124
|
-
|
|
126
|
+
"github.com/pyros2097/gromer/_example/assets"
|
|
125
|
-
|
|
127
|
+
"github.com/pyros2097/gromer/_example/components"
|
|
126
|
-
|
|
128
|
+
"github.com/pyros2097/gromer/_example/containers"
|
|
127
|
-
|
|
129
|
+
"github.com/pyros2097/gromer/_example/routes/404"
|
|
128
|
-
|
|
130
|
+
"github.com/pyros2097/gromer/_example/routes"
|
|
129
|
-
|
|
131
|
+
"github.com/pyros2097/gromer/_example/routes/about"
|
|
130
|
-
|
|
132
|
+
|
|
131
|
-
|
|
132
133
|
)
|
|
133
134
|
|
|
134
135
|
func init() {
|
|
135
|
-
|
|
136
|
+
gsx.RegisterComponent(components.Todo, "todo")
|
|
136
|
-
|
|
137
|
+
gsx.RegisterComponent(components.Checkbox, "value")
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
gsx.RegisterComponent(containers.TodoCount, "filter")
|
|
139
|
-
|
|
139
|
+
gsx.RegisterComponent(containers.TodoList, "page", "filter")
|
|
140
|
-
gromer.RegisterAssets(assets.FS)
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
func main() {
|
|
144
|
-
|
|
143
|
+
baseRouter := mux.NewRouter()
|
|
145
|
-
|
|
144
|
+
baseRouter.Use(gromer.LogMiddleware)
|
|
146
|
-
|
|
145
|
+
baseRouter.NotFoundHandler = gromer.StatusHandler(not_found_404.GET)
|
|
147
|
-
|
|
146
|
+
|
|
148
|
-
|
|
147
|
+
staticRouter := baseRouter.NewRoute().Subrouter()
|
|
149
|
-
|
|
148
|
+
staticRouter.Use(gromer.CacheMiddleware)
|
|
150
|
-
|
|
149
|
+
gromer.StaticRoute(staticRouter, "/gromer/", gromer_assets.FS)
|
|
151
|
-
|
|
150
|
+
gromer.StaticRoute(staticRouter, "/assets/", assets.FS)
|
|
152
|
-
|
|
151
|
+
gromer.StylesRoute(staticRouter, "/styles.css")
|
|
153
|
-
|
|
152
|
+
|
|
154
|
-
|
|
153
|
+
pageRouter := baseRouter.NewRoute().Subrouter()
|
|
155
|
-
|
|
154
|
+
gromer.Handle(pageRouter, "GET", "/", routes.GET)
|
|
156
|
-
|
|
155
|
+
gromer.Handle(pageRouter, "POST", "/", routes.POST)
|
|
157
|
-
|
|
156
|
+
gromer.Handle(pageRouter, "GET", "/about", about.GET)
|
|
157
|
+
|
|
158
|
-
|
|
158
|
+
log.Info().Msg("http server listening on http://localhost:3000")
|
|
159
|
-
|
|
159
|
+
srv := server.New(baseRouter, nil)
|
|
160
|
-
|
|
160
|
+
if err := srv.ListenAndServe(":3000"); err != nil {
|
|
161
|
-
|
|
161
|
+
log.Fatal().Stack().Err(err).Msg("failed to listen")
|
|
162
|
-
|
|
162
|
+
}
|
|
163
163
|
}
|
|
164
164
|
```
|
|
165
165
|
|