~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.
76c43570
—
Peter John 4 years ago
rename package and fix bugs
- api_explorer.go +3 -3
- cmd/{wapp → gromer}/main.go +31 -23
- css.go +3 -2
- go.mod +4 -1
- go.sum +12 -0
- hooks.go +1 -1
- hooks_test.go +1 -1
- html.go +1 -118
- html_test.go +1 -1
- http.go +133 -0
- readme.md +10 -15
api_explorer.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package gromer
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
@@ -51,8 +51,8 @@ func ApiExplorer(apiDefs []ApiDefinition) func(c context.Context) (HtmlPage, int
|
|
|
51
51
|
Head(
|
|
52
52
|
Title("Example"),
|
|
53
53
|
Meta("description", "Example"),
|
|
54
|
-
Meta("author", "
|
|
54
|
+
Meta("author", "pyros.sh"),
|
|
55
|
-
Meta("keywords", "
|
|
55
|
+
Meta("keywords", "pyros.sh, gromer"),
|
|
56
56
|
Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
|
|
57
57
|
Link("icon", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEU0OkArMjhobHEoPUPFEBIuO0L+AAC2FBZ2JyuNICOfGx7xAwTjCAlCNTvVDA1aLzQ3COjMAAAAVUlEQVQI12NgwAaCDSA0888GCItjn0szWGBJTVoGSCjWs8TleQCQYV95evdxkFT8Kpe0PLDi5WfKd4LUsN5zS1sKFolt8bwAZrCaGqNYJAgFDEpQAAAzmxafI4vZWwAAAABJRU5ErkJggg=="),
|
|
58
58
|
Link("stylesheet", "https://cdn.jsdelivr.net/npm/codemirror@5.63.1/lib/codemirror.css"),
|
cmd/{wapp → gromer}/main.go
RENAMED
|
@@ -2,6 +2,7 @@ package main
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
|
5
|
+
"flag"
|
|
5
6
|
"fmt"
|
|
6
7
|
"io/ioutil"
|
|
7
8
|
"log"
|
|
@@ -10,7 +11,7 @@ import (
|
|
|
10
11
|
"strings"
|
|
11
12
|
|
|
12
13
|
"github.com/gobuffalo/velvet"
|
|
13
|
-
"github.com/pyros2097/
|
|
14
|
+
"github.com/pyros2097/gromer"
|
|
14
15
|
"golang.org/x/mod/modfile"
|
|
15
16
|
)
|
|
16
17
|
|
|
@@ -78,7 +79,7 @@ func rewritePkg(pkg string) string {
|
|
|
78
79
|
return lastItem
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
func getApiFunc(method, route string, params []string)
|
|
82
|
+
func getApiFunc(method, route string, params []string) gromer.ApiDefinition {
|
|
82
83
|
muxRoute := bytes.NewBuffer(nil)
|
|
83
84
|
foundStart := false
|
|
84
85
|
for _, v := range route {
|
|
@@ -92,7 +93,7 @@ func getApiFunc(method, route string, params []string) wapp.ApiDefinition {
|
|
|
92
93
|
muxRoute.WriteString(string(v))
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
|
-
return
|
|
96
|
+
return gromer.ApiDefinition{
|
|
96
97
|
Method: method,
|
|
97
98
|
PathParams: params,
|
|
98
99
|
Path: muxRoute.String(),
|
|
@@ -154,19 +155,26 @@ func getApiFunc(method, route string, params []string) wapp.ApiDefinition {
|
|
|
154
155
|
// }
|
|
155
156
|
|
|
156
157
|
func main() {
|
|
158
|
+
moduleName := ""
|
|
159
|
+
pkgFlag := flag.String("pkg", "", "specify a package name")
|
|
160
|
+
flag.Parse()
|
|
161
|
+
if pkgFlag == nil || *pkgFlag == "" {
|
|
157
|
-
|
|
162
|
+
data, err := ioutil.ReadFile("go.mod")
|
|
158
|
-
|
|
163
|
+
if err != nil {
|
|
159
|
-
|
|
164
|
+
log.Fatalf("go.mod file not found %s", err.Error())
|
|
160
|
-
|
|
165
|
+
}
|
|
161
|
-
|
|
166
|
+
modTree, err := modfile.Parse("go.mod", data, nil)
|
|
162
|
-
|
|
167
|
+
if err != nil {
|
|
163
|
-
|
|
168
|
+
log.Fatalf("could not parse go.mod %s", err.Error())
|
|
169
|
+
}
|
|
170
|
+
moduleName = modTree.Module.Mod.Path
|
|
171
|
+
} else {
|
|
172
|
+
moduleName = *pkgFlag
|
|
164
173
|
}
|
|
165
|
-
moduleName := modTree.Module.Mod.Path
|
|
166
174
|
routes := []*Route{}
|
|
167
|
-
apiDefs := []
|
|
175
|
+
apiDefs := []gromer.ApiDefinition{}
|
|
168
176
|
allPkgs := map[string]string{}
|
|
169
|
-
err = filepath.Walk("pages",
|
|
177
|
+
err := filepath.Walk("pages",
|
|
170
178
|
func(filesrc string, info os.FileInfo, err error) error {
|
|
171
179
|
if err != nil {
|
|
172
180
|
return err
|
|
@@ -182,7 +190,7 @@ func main() {
|
|
|
182
190
|
pkg = "pages"
|
|
183
191
|
}
|
|
184
192
|
routePath := rewritePath(path)
|
|
185
|
-
params :=
|
|
193
|
+
params := gromer.GetRouteParams(routePath)
|
|
186
194
|
routes = append(routes, &Route{
|
|
187
195
|
Method: method,
|
|
188
196
|
Path: routePath,
|
|
@@ -206,7 +214,7 @@ func main() {
|
|
|
206
214
|
ctx.Set("routes", routes)
|
|
207
215
|
ctx.Set("apiDefs", apiDefs)
|
|
208
216
|
ctx.Set("tick", "`")
|
|
209
|
-
s, err := velvet.Render(`// Code generated by
|
|
217
|
+
s, err := velvet.Render(`// Code generated by gromer. DO NOT EDIT.
|
|
210
218
|
package main
|
|
211
219
|
|
|
212
220
|
import (
|
|
@@ -217,7 +225,7 @@ import (
|
|
|
217
225
|
|
|
218
226
|
"github.com/apex/gateway/v2"
|
|
219
227
|
"github.com/gorilla/mux"
|
|
220
|
-
"github.com/pyros2097/
|
|
228
|
+
"github.com/pyros2097/gromer"
|
|
221
229
|
"github.com/rs/zerolog/log"
|
|
222
230
|
"gocloud.dev/server"
|
|
223
231
|
|
|
@@ -234,7 +242,7 @@ func main() {
|
|
|
234
242
|
r := mux.NewRouter()
|
|
235
243
|
r.NotFoundHandler = http.HandlerFunc(notFound)
|
|
236
244
|
r.PathPrefix("/assets/").Handler(wrapCache(http.FileServer(http.FS(assetsFS))))
|
|
237
|
-
handle(r, "GET", "/api",
|
|
245
|
+
handle(r, "GET", "/api", gromer.ApiExplorer(apiDefinitions()))
|
|
238
246
|
{{#each routes as |route| }}handle(r, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }})
|
|
239
247
|
{{/each}}
|
|
240
248
|
if !isLambda {
|
|
@@ -259,14 +267,14 @@ func wrapCache(h http.Handler) http.Handler {
|
|
|
259
267
|
}
|
|
260
268
|
|
|
261
269
|
func notFound(w http.ResponseWriter, r *http.Request) {
|
|
262
|
-
|
|
270
|
+
gromer.LogReq(404, r)
|
|
263
271
|
}
|
|
264
272
|
|
|
265
273
|
func handle(router *mux.Router, method, route string, h interface{}) {
|
|
266
274
|
router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
|
|
267
275
|
var status int
|
|
268
276
|
defer func() {
|
|
269
|
-
|
|
277
|
+
gromer.LogReq(status, r)
|
|
270
278
|
}()
|
|
271
279
|
ctx, err := context.WithContext(c.WithValue(
|
|
272
280
|
c.WithValue(
|
|
@@ -274,18 +282,18 @@ func handle(router *mux.Router, method, route string, h interface{}) {
|
|
|
274
282
|
"url", r.URL),
|
|
275
283
|
"header", r.Header))
|
|
276
284
|
if err != nil {
|
|
277
|
-
|
|
285
|
+
gromer.RespondError(w, 500, err)
|
|
278
286
|
return
|
|
279
287
|
}
|
|
280
|
-
status, err =
|
|
288
|
+
status, err = gromer.PerformRequest(route, h, ctx, w, r)
|
|
281
289
|
if err != nil {
|
|
282
290
|
log.Error().Stack().Err(err).Msg("")
|
|
283
291
|
}
|
|
284
292
|
}).Methods(method)
|
|
285
293
|
}
|
|
286
294
|
|
|
287
|
-
func apiDefinitions() []
|
|
295
|
+
func apiDefinitions() []gromer.ApiDefinition {
|
|
288
|
-
return []
|
|
296
|
+
return []gromer.ApiDefinition{
|
|
289
297
|
{{#each apiDefs as |api| }}
|
|
290
298
|
{
|
|
291
299
|
Method: "{{api.Method}}",
|
css.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package gromer
|
|
2
2
|
|
|
3
3
|
type M map[string]interface{}
|
|
4
4
|
type MS map[string]string
|
|
@@ -467,8 +467,9 @@ func mapApply(obj KeyValues) {
|
|
|
467
467
|
twClassLookup[className] = vstring + ": " + vv + ";"
|
|
468
468
|
}
|
|
469
469
|
if varr, ok := v.(Arr); ok {
|
|
470
|
+
twClassLookup[className] = ""
|
|
470
471
|
for _, kk := range varr {
|
|
471
|
-
twClassLookup[className] = kk.(string) + ": " + vv + ";"
|
|
472
|
+
twClassLookup[className] += kk.(string) + ": " + vv + ";"
|
|
472
473
|
}
|
|
473
474
|
}
|
|
474
475
|
}
|
go.mod
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
module github.com/pyros2097/
|
|
1
|
+
module github.com/pyros2097/gromer
|
|
2
2
|
|
|
3
3
|
go 1.16
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
6
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 // indirect
|
|
7
|
+
github.com/apex/gateway/v2 v2.0.0 // indirect
|
|
7
8
|
github.com/aymerick/raymond v2.0.2+incompatible // indirect
|
|
8
9
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect
|
|
9
10
|
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible
|
|
@@ -19,6 +20,7 @@ require (
|
|
|
19
20
|
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f
|
|
20
21
|
github.com/gogo/protobuf v1.1.1 // indirect
|
|
21
22
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 // indirect
|
|
23
|
+
github.com/google/uuid v1.3.0 // indirect
|
|
22
24
|
github.com/googleapis/gax-go v2.0.0+incompatible // indirect
|
|
23
25
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
|
|
24
26
|
github.com/gorilla/context v1.1.1 // indirect
|
|
@@ -28,6 +30,7 @@ require (
|
|
|
28
30
|
github.com/jonboulle/clockwork v0.1.0 // indirect
|
|
29
31
|
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
|
30
32
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
|
|
33
|
+
github.com/lib/pq v1.10.4 // indirect
|
|
31
34
|
github.com/markbates/inflect v1.0.4 // indirect
|
|
32
35
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
|
33
36
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
go.sum
CHANGED
|
@@ -97,7 +97,11 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
|
|
|
97
97
|
github.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1KT/jNjDw7Z5qxAEtIiERJ2sXjnII=
|
|
98
98
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
99
99
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
100
|
+
github.com/apex/gateway/v2 v2.0.0 h1:tJwKiB7ObbXuF3yoqTf/CfmaZRhHB+GfilTNSCf1Wnc=
|
|
101
|
+
github.com/apex/gateway/v2 v2.0.0/go.mod h1:y+uuK0JxdvTHZeVns501/7qklBhnDHtGU0hfUQ6QIfI=
|
|
100
102
|
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
|
103
|
+
github.com/aws/aws-lambda-go v1.17.0 h1:Ogihmi8BnpmCNktKAGpNwSiILNNING1MiosnKUfU8m0=
|
|
104
|
+
github.com/aws/aws-lambda-go v1.17.0/go.mod h1:FEwgPLE6+8wcGBTe5cJN3JWurd1Ztm9zN4jsXsjzKKw=
|
|
101
105
|
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
|
102
106
|
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
|
103
107
|
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
|
|
@@ -138,6 +142,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
|
|
138
142
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
139
143
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
140
144
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
|
145
|
+
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
141
146
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
142
147
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
143
148
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
@@ -263,6 +268,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
|
|
263
268
|
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
|
264
269
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
265
270
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
271
|
+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
266
272
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
267
273
|
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
|
268
274
|
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
|
@@ -307,6 +313,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
|
307
313
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
308
314
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
|
309
315
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
316
|
+
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
|
317
|
+
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
310
318
|
github.com/markbates/inflect v1.0.4 h1:5fh1gzTFhfae06u3hzHYO9xe3l3v3nW5Pwt3naLTP5g=
|
|
311
319
|
github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
|
|
312
320
|
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
|
@@ -340,6 +348,7 @@ github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
|
|
|
340
348
|
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
|
|
341
349
|
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
|
342
350
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
351
|
+
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
343
352
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
|
344
353
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
345
354
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 h1:86e54L0i3pH3dAIA8OxBbfLrVyhoGpnNk1iJCigAWYs=
|
|
@@ -375,9 +384,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|
|
375
384
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
376
385
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
377
386
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
387
|
+
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
|
|
378
388
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
|
379
389
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
|
380
390
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
|
391
|
+
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
|
381
392
|
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
|
382
393
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
383
394
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
@@ -804,6 +815,7 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
|
804
815
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
805
816
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
806
817
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
818
|
+
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
807
819
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
|
808
820
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
809
821
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
hooks.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package gromer
|
|
2
2
|
|
|
3
3
|
type Context struct {
|
|
4
4
|
index int
|
hooks_test.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package gromer
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"testing"
|
html.go
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
|
-
package
|
|
1
|
+
package gromer
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
|
5
|
-
"encoding/json"
|
|
6
5
|
"fmt"
|
|
7
6
|
"io"
|
|
8
|
-
"net/http"
|
|
9
|
-
"os"
|
|
10
|
-
"reflect"
|
|
11
|
-
"regexp"
|
|
12
7
|
"strconv"
|
|
13
8
|
"strings"
|
|
14
|
-
|
|
15
|
-
"github.com/fatih/color"
|
|
16
|
-
"github.com/gorilla/mux"
|
|
17
|
-
"github.com/rs/zerolog"
|
|
18
|
-
"github.com/rs/zerolog/log"
|
|
19
9
|
)
|
|
20
10
|
|
|
21
|
-
func init() {
|
|
22
|
-
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
|
23
|
-
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
11
|
func writeIndent(w io.Writer, indent int) {
|
|
27
12
|
for i := 0; i < indent*2; i++ {
|
|
28
13
|
w.Write([]byte(" "))
|
|
@@ -413,105 +398,3 @@ func XData(v string) Attribute {
|
|
|
413
398
|
func XText(v string) Attribute {
|
|
414
399
|
return Attribute{"x-text", v}
|
|
415
400
|
}
|
|
416
|
-
|
|
417
|
-
func RespondError(w http.ResponseWriter, status int, err error) {
|
|
418
|
-
w.WriteHeader(status)
|
|
419
|
-
w.Header().Set("Content-Type", "application/json")
|
|
420
|
-
data, _ := json.Marshal(map[string]string{
|
|
421
|
-
"error": err.Error(),
|
|
422
|
-
})
|
|
423
|
-
w.Write(data)
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
var pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
|
|
427
|
-
|
|
428
|
-
func GetRouteParams(route string) []string {
|
|
429
|
-
params := []string{}
|
|
430
|
-
found := pathParamsRegex.FindAllString(route, -1)
|
|
431
|
-
for _, v := range found {
|
|
432
|
-
params = append(params, strings.Replace(strings.Replace(v, "}", "", 1), "{", "", 1))
|
|
433
|
-
}
|
|
434
|
-
return params
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
438
|
-
params := GetRouteParams(route)
|
|
439
|
-
args := []reflect.Value{reflect.ValueOf(ctx)}
|
|
440
|
-
funcType := reflect.TypeOf(h)
|
|
441
|
-
icount := funcType.NumIn()
|
|
442
|
-
vars := mux.Vars(r)
|
|
443
|
-
for _, k := range params {
|
|
444
|
-
args = append(args, reflect.ValueOf(vars[k]))
|
|
445
|
-
}
|
|
446
|
-
if len(args) != icount {
|
|
447
|
-
structType := funcType.In(icount - 1)
|
|
448
|
-
instance := reflect.New(structType)
|
|
449
|
-
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
|
|
450
|
-
err := json.NewDecoder(r.Body).Decode(instance.Interface())
|
|
451
|
-
if err != nil {
|
|
452
|
-
RespondError(w, 500, err)
|
|
453
|
-
return 500, err
|
|
454
|
-
}
|
|
455
|
-
} else if r.Method == "GET" {
|
|
456
|
-
rv := instance.Elem()
|
|
457
|
-
for i := 0; i < structType.NumField(); i++ {
|
|
458
|
-
if f := rv.Field(i); f.CanSet() {
|
|
459
|
-
jsonName := structType.Field(i).Tag.Get("json")
|
|
460
|
-
jsonValue := r.URL.Query().Get(jsonName)
|
|
461
|
-
if f.Kind() == reflect.String {
|
|
462
|
-
f.SetString(jsonValue)
|
|
463
|
-
} else if f.Kind() == reflect.Int64 {
|
|
464
|
-
v, err := strconv.ParseInt(jsonValue, 10, 64)
|
|
465
|
-
if err != nil {
|
|
466
|
-
RespondError(w, 500, err)
|
|
467
|
-
return 500, err
|
|
468
|
-
}
|
|
469
|
-
f.SetInt(v)
|
|
470
|
-
} else {
|
|
471
|
-
panic("Uknown query param: " + jsonValue)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
args = append(args, instance.Elem())
|
|
477
|
-
}
|
|
478
|
-
values := reflect.ValueOf(h).Call(args)
|
|
479
|
-
response := values[0].Interface()
|
|
480
|
-
responseStatus := values[1].Interface().(int)
|
|
481
|
-
responseError := values[2].Interface()
|
|
482
|
-
if responseError != nil {
|
|
483
|
-
RespondError(w, responseStatus, responseError.(error))
|
|
484
|
-
return responseStatus, responseError.(error)
|
|
485
|
-
}
|
|
486
|
-
if v, ok := response.(HtmlPage); ok {
|
|
487
|
-
w.Header().Set("Content-Type", "text/html")
|
|
488
|
-
// This has to be at end always
|
|
489
|
-
w.WriteHeader(responseStatus)
|
|
490
|
-
v.WriteHtml(w)
|
|
491
|
-
return 200, nil
|
|
492
|
-
}
|
|
493
|
-
w.Header().Set("Content-Type", "application/json")
|
|
494
|
-
// This has to be at end always
|
|
495
|
-
w.WriteHeader(responseStatus)
|
|
496
|
-
data, _ := json.Marshal(response)
|
|
497
|
-
w.Write(data)
|
|
498
|
-
return 200, nil
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
func LogReq(status int, r *http.Request) {
|
|
502
|
-
a := color.FgGreen
|
|
503
|
-
if status >= 500 {
|
|
504
|
-
a = color.FgRed
|
|
505
|
-
} else if status >= 400 {
|
|
506
|
-
a = color.FgYellow
|
|
507
|
-
}
|
|
508
|
-
m := color.FgCyan
|
|
509
|
-
if r.Method == "POST" {
|
|
510
|
-
m = color.FgYellow
|
|
511
|
-
} else if r.Method == "PUT" {
|
|
512
|
-
m = color.FgMagenta
|
|
513
|
-
} else if r.Method == "DELETE" {
|
|
514
|
-
m = color.FgRed
|
|
515
|
-
}
|
|
516
|
-
log.Info().Msgf("%3s %s %s", color.New(a).Sprint(status), color.New(m).Sprintf("%-4s", r.Method), color.WhiteString(r.URL.Path))
|
|
517
|
-
}
|
html_test.go
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package gromer
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
http.go
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
package gromer
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"net/http"
|
|
6
|
+
"os"
|
|
7
|
+
"reflect"
|
|
8
|
+
"regexp"
|
|
9
|
+
"strconv"
|
|
10
|
+
"strings"
|
|
11
|
+
|
|
12
|
+
"github.com/fatih/color"
|
|
13
|
+
"github.com/gorilla/mux"
|
|
14
|
+
"github.com/rs/zerolog"
|
|
15
|
+
"github.com/rs/zerolog/log"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
func init() {
|
|
19
|
+
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
|
20
|
+
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func RespondError(w http.ResponseWriter, status int, err error) {
|
|
24
|
+
w.WriteHeader(status)
|
|
25
|
+
w.Header().Set("Content-Type", "application/json")
|
|
26
|
+
data, _ := json.Marshal(map[string]string{
|
|
27
|
+
"error": err.Error(),
|
|
28
|
+
})
|
|
29
|
+
w.Write(data)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
|
|
33
|
+
|
|
34
|
+
func GetRouteParams(route string) []string {
|
|
35
|
+
params := []string{}
|
|
36
|
+
found := pathParamsRegex.FindAllString(route, -1)
|
|
37
|
+
for _, v := range found {
|
|
38
|
+
params = append(params, strings.Replace(strings.Replace(v, "}", "", 1), "{", "", 1))
|
|
39
|
+
}
|
|
40
|
+
return params
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) (int, error) {
|
|
44
|
+
params := GetRouteParams(route)
|
|
45
|
+
args := []reflect.Value{reflect.ValueOf(ctx)}
|
|
46
|
+
funcType := reflect.TypeOf(h)
|
|
47
|
+
icount := funcType.NumIn()
|
|
48
|
+
vars := mux.Vars(r)
|
|
49
|
+
for _, k := range params {
|
|
50
|
+
args = append(args, reflect.ValueOf(vars[k]))
|
|
51
|
+
}
|
|
52
|
+
if len(args) != icount {
|
|
53
|
+
structType := funcType.In(icount - 1)
|
|
54
|
+
instance := reflect.New(structType)
|
|
55
|
+
if structType.Kind() != reflect.Struct {
|
|
56
|
+
panic(route + " func final param should be a struct")
|
|
57
|
+
}
|
|
58
|
+
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
|
|
59
|
+
err := json.NewDecoder(r.Body).Decode(instance.Interface())
|
|
60
|
+
if err != nil {
|
|
61
|
+
RespondError(w, 500, err)
|
|
62
|
+
return 500, err
|
|
63
|
+
}
|
|
64
|
+
} else if r.Method == "GET" {
|
|
65
|
+
rv := instance.Elem()
|
|
66
|
+
for i := 0; i < structType.NumField(); i++ {
|
|
67
|
+
if f := rv.Field(i); f.CanSet() {
|
|
68
|
+
jsonName := structType.Field(i).Tag.Get("json")
|
|
69
|
+
jsonValue := r.URL.Query().Get(jsonName)
|
|
70
|
+
if f.Kind() == reflect.String {
|
|
71
|
+
f.SetString(jsonValue)
|
|
72
|
+
} else if f.Kind() == reflect.Int64 {
|
|
73
|
+
v, err := strconv.ParseInt(jsonValue, 10, 64)
|
|
74
|
+
if err != nil {
|
|
75
|
+
RespondError(w, 500, err)
|
|
76
|
+
return 500, err
|
|
77
|
+
}
|
|
78
|
+
f.SetInt(v)
|
|
79
|
+
} else if f.Kind() == reflect.Int32 {
|
|
80
|
+
v, err := strconv.ParseInt(jsonValue, 10, 32)
|
|
81
|
+
if err != nil {
|
|
82
|
+
RespondError(w, 500, err)
|
|
83
|
+
return 500, err
|
|
84
|
+
}
|
|
85
|
+
f.SetInt(v)
|
|
86
|
+
} else {
|
|
87
|
+
panic("Uknown query param: " + jsonValue)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
args = append(args, instance.Elem())
|
|
93
|
+
}
|
|
94
|
+
values := reflect.ValueOf(h).Call(args)
|
|
95
|
+
response := values[0].Interface()
|
|
96
|
+
responseStatus := values[1].Interface().(int)
|
|
97
|
+
responseError := values[2].Interface()
|
|
98
|
+
if responseError != nil {
|
|
99
|
+
RespondError(w, responseStatus, responseError.(error))
|
|
100
|
+
return responseStatus, responseError.(error)
|
|
101
|
+
}
|
|
102
|
+
if v, ok := response.(HtmlPage); ok {
|
|
103
|
+
w.Header().Set("Content-Type", "text/html")
|
|
104
|
+
// This has to be at end always
|
|
105
|
+
w.WriteHeader(responseStatus)
|
|
106
|
+
v.WriteHtml(w)
|
|
107
|
+
return 200, nil
|
|
108
|
+
}
|
|
109
|
+
w.Header().Set("Content-Type", "application/json")
|
|
110
|
+
// This has to be at end always
|
|
111
|
+
w.WriteHeader(responseStatus)
|
|
112
|
+
data, _ := json.Marshal(response)
|
|
113
|
+
w.Write(data)
|
|
114
|
+
return 200, nil
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func LogReq(status int, r *http.Request) {
|
|
118
|
+
a := color.FgGreen
|
|
119
|
+
if status >= 500 {
|
|
120
|
+
a = color.FgRed
|
|
121
|
+
} else if status >= 400 {
|
|
122
|
+
a = color.FgYellow
|
|
123
|
+
}
|
|
124
|
+
m := color.FgCyan
|
|
125
|
+
if r.Method == "POST" {
|
|
126
|
+
m = color.FgYellow
|
|
127
|
+
} else if r.Method == "PUT" {
|
|
128
|
+
m = color.FgMagenta
|
|
129
|
+
} else if r.Method == "DELETE" {
|
|
130
|
+
m = color.FgRed
|
|
131
|
+
}
|
|
132
|
+
log.Info().Msgf("%3s %s %s", color.New(a).Sprint(status), color.New(m).Sprintf("%-4s", r.Method), color.WhiteString(r.URL.Path))
|
|
133
|
+
}
|
readme.md
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://goreportcard.com/report/github.com/pyros2097/
|
|
2
|
+
<a href="https://goreportcard.com/report/github.com/pyros2097/gromer"><img src="https://goreportcard.com/badge/github.com/pyros2097/gromer" alt="Go Report Card"></a>
|
|
3
|
-
<a href="https://GitHub.com/pyros2097/
|
|
3
|
+
<a href="https://GitHub.com/pyros2097/gromer/releases/"><img src="https://img.shields.io/github/release/pyros2097/gromer.svg" alt="GitHub release"></a>
|
|
4
|
-
<a href="https://pkg.go.dev/github.com/pyros2097/
|
|
4
|
+
<a href="https://pkg.go.dev/github.com/pyros2097/gromer"><img src="https://img.shields.io/badge/dev-reference-007d9c?logo=go&logoColor=white&style=flat" alt="pkg.go.dev docs"></a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# gromer
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**gromer** is a framework to build web apps in golang.
|
|
10
10
|
It uses a declarative syntax using funcs that allows creating and dealing with HTML elements only by using Go, and without writing any HTML markup. It is highly opioninated and integrates uses tailwind css and alpinejs.
|
|
11
11
|
|
|
12
|
-
# Install
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
go mod init
|
|
16
|
-
go get -u -v github.com/pyros2097/wapp
|
|
17
|
-
```
|
|
18
|
-
|
|
19
12
|
# Install Cli
|
|
20
13
|
|
|
21
14
|
```sh
|
|
22
15
|
go mod init
|
|
23
|
-
go get -u -v github.com/pyros2097/wapp/cmd/
|
|
16
|
+
go get -u -v github.com/pyros2097/wapp/cmd/gromer
|
|
24
17
|
```
|
|
25
18
|
|
|
26
|
-
#
|
|
19
|
+
# Using
|
|
20
|
+
You need to follow this directory structure similar to nextjs for the api route handlers to be generated
|
|
21
|
+
Please look at the example for now,
|
|
27
22
|
|
|
28
|
-
https://github.com/pyros2097/
|
|
23
|
+
[Example](https://github.com/pyros2097/gromer/tree/master/example)
|