gromer
git clone https://git.pyrossh.dev/gromer
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
6f1b2b1
— Peter John
2021-09-24T23:23:32+05:30
publish wapp
Generated files not shown: go.sum
cmd/main.go
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io/ioutil"
|
|
7
|
+
"log"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"strings"
|
|
11
|
+
|
|
12
|
+
"github.com/gobuffalo/velvet"
|
|
13
|
+
"golang.org/x/mod/modfile"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
type Route struct {
|
|
17
|
+
Method string
|
|
18
|
+
Path string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func getMethod(src string) string {
|
|
22
|
+
if strings.HasSuffix(src, "get.go") {
|
|
23
|
+
return "GET"
|
|
24
|
+
} else if strings.HasSuffix(src, "post.go") {
|
|
25
|
+
return "POST"
|
|
26
|
+
} else if strings.HasSuffix(src, "put.go") {
|
|
27
|
+
return "PUT"
|
|
28
|
+
} else if strings.HasSuffix(src, "patch.go") {
|
|
29
|
+
return "PATCH"
|
|
30
|
+
} else if strings.HasSuffix(src, "delete.go") {
|
|
31
|
+
return "DELETE"
|
|
32
|
+
} else if strings.HasSuffix(src, "head.go") {
|
|
33
|
+
return "HEAD"
|
|
34
|
+
} else if strings.HasSuffix(src, "options.go") {
|
|
35
|
+
return "OPTIONS"
|
|
36
|
+
} else if strings.HasSuffix(src, "connect.go") {
|
|
37
|
+
return "CONNECT"
|
|
38
|
+
} else if strings.HasSuffix(src, "trace.go") {
|
|
39
|
+
return "TRACE"
|
|
40
|
+
} else {
|
|
41
|
+
panic(fmt.Sprintf("Uknown route found %s", src))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func getRoute(method, src string) string {
|
|
46
|
+
muxRoute := bytes.NewBuffer(nil)
|
|
47
|
+
baseRoute := strings.Replace(src, "/"+strings.ToLower(method)+".go", "", 1)
|
|
48
|
+
foundStart := false
|
|
49
|
+
for _, v := range baseRoute {
|
|
50
|
+
if string(v) == "_" && !foundStart {
|
|
51
|
+
foundStart = true
|
|
52
|
+
muxRoute.WriteString("{")
|
|
53
|
+
} else if string(v) == "_" && foundStart {
|
|
54
|
+
foundStart = false
|
|
55
|
+
muxRoute.WriteString("}")
|
|
56
|
+
} else {
|
|
57
|
+
muxRoute.WriteString(string(v))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return muxRoute.String()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func main() {
|
|
64
|
+
data, err := ioutil.ReadFile("go.mod")
|
|
65
|
+
if err != nil {
|
|
66
|
+
log.Fatalf("go.mod file not found %w", err)
|
|
67
|
+
}
|
|
68
|
+
modTree, err := modfile.Parse("go.mod", data, nil)
|
|
69
|
+
if err != nil {
|
|
70
|
+
log.Fatalf("could not parse go.mod %w", err)
|
|
71
|
+
}
|
|
72
|
+
moduleName := modTree.Module.Mod.Path
|
|
73
|
+
routes := []*Route{}
|
|
74
|
+
err = filepath.Walk("pages",
|
|
75
|
+
func(filesrc string, info os.FileInfo, err error) error {
|
|
76
|
+
if err != nil {
|
|
77
|
+
return err
|
|
78
|
+
}
|
|
79
|
+
if !info.IsDir() {
|
|
80
|
+
route := strings.Replace(filesrc, "pages", "", 1)
|
|
81
|
+
method := getMethod(route)
|
|
82
|
+
path := getRoute(method, route)
|
|
83
|
+
if path == "" { // for index page
|
|
84
|
+
path = "/"
|
|
85
|
+
}
|
|
86
|
+
routes = append(routes, &Route{Method: method, Path: path})
|
|
87
|
+
}
|
|
88
|
+
return nil
|
|
89
|
+
})
|
|
90
|
+
if err != nil {
|
|
91
|
+
log.Fatal(err)
|
|
92
|
+
}
|
|
93
|
+
for _, r := range routes {
|
|
94
|
+
println(r.Method, r.Path)
|
|
95
|
+
}
|
|
96
|
+
ctx := velvet.NewContext()
|
|
97
|
+
ctx.Set("moduleName", moduleName)
|
|
98
|
+
ctx.Set("routes", routes)
|
|
99
|
+
s, err := velvet.Render(`// GENERATED BY WAPP DO NOT EDIT THIS FILE
|
|
100
|
+
package main
|
|
101
|
+
|
|
102
|
+
import (
|
|
103
|
+
"embed"
|
|
104
|
+
"log"
|
|
105
|
+
"net/http"
|
|
106
|
+
"os"
|
|
107
|
+
"time"
|
|
108
|
+
|
|
109
|
+
"github.com/apex/gateway/v2"
|
|
110
|
+
"github.com/gorilla/mux"
|
|
111
|
+
. "{{ moduleName }}/context"
|
|
112
|
+
"{{ moduleName }}/pages"
|
|
113
|
+
"{{ moduleName }}/pages/about"
|
|
114
|
+
"{{ moduleName }}/pages/api/todos/_id_"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
//go:embed assets/*
|
|
118
|
+
var assetsFS embed.FS
|
|
119
|
+
|
|
120
|
+
func main() {
|
|
121
|
+
isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
|
|
122
|
+
r := mux.NewRouter()
|
|
123
|
+
r.PathPrefix("/assets/").Handler(http.FileServer(http.FS(assetsFS)))
|
|
124
|
+
|
|
125
|
+
{{#each routes as |route| }}r.HandleFunc("{{ route.Path }}", Wrap(pages.GET)).Methods("{{ route.Method }}")
|
|
126
|
+
{{/each}}
|
|
127
|
+
if !isLambda {
|
|
128
|
+
println("http server listening on http://localhost:3000")
|
|
129
|
+
srv := &http.Server{
|
|
130
|
+
Handler: r,
|
|
131
|
+
Addr: "127.0.0.1:3000",
|
|
132
|
+
WriteTimeout: 30 * time.Second,
|
|
133
|
+
ReadTimeout: 30 * time.Second,
|
|
134
|
+
}
|
|
135
|
+
log.Fatal(srv.ListenAndServe())
|
|
136
|
+
} else {
|
|
137
|
+
log.Print("running in lambda mode")
|
|
138
|
+
log.Fatal(gateway.ListenAndServe(":3000", r))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
`, ctx)
|
|
143
|
+
if err != nil {
|
|
144
|
+
panic(err)
|
|
145
|
+
}
|
|
146
|
+
println(s)
|
|
147
|
+
}
|
go.mod
CHANGED
|
@@ -3,10 +3,23 @@ module github.com/pyros2097/wapp
|
|
|
3
3
|
go 1.16
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
|
+
github.com/PuerkitoBio/goquery v1.7.1 // indirect
|
|
6
7
|
github.com/apex/gateway/v2 v2.0.0
|
|
8
|
+
github.com/aymerick/raymond v2.0.2+incompatible // indirect
|
|
9
|
+
github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f // indirect
|
|
7
10
|
github.com/gorilla/mux v1.8.0
|
|
8
11
|
github.com/kr/pretty v0.1.0 // indirect
|
|
12
|
+
github.com/markbates/inflect v1.0.4 // indirect
|
|
13
|
+
github.com/microcosm-cc/bluemonday v1.0.15 // indirect
|
|
14
|
+
github.com/sergi/go-diff v1.2.0 // indirect
|
|
15
|
+
github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 // indirect
|
|
16
|
+
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect
|
|
17
|
+
github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b // indirect
|
|
18
|
+
github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c // indirect
|
|
19
|
+
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
|
|
20
|
+
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
|
|
9
21
|
github.com/stretchr/testify v1.6.1
|
|
22
|
+
golang.org/x/mod v0.5.1 // indirect
|
|
10
|
-
|
|
23
|
+
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf // indirect
|
|
11
24
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
|
12
25
|
)
|
html.go
CHANGED
|
@@ -389,54 +389,3 @@ func MergeAttributes(parent *Element, uis ...interface{}) *Element {
|
|
|
389
389
|
}
|
|
390
390
|
return parent
|
|
391
391
|
}
|
|
392
|
-
|
|
393
|
-
// func UseData(name, data string) *Element {
|
|
394
|
-
// return Script(Text(fmt.Sprintf(`
|
|
395
|
-
// document.addEventListener('alpine:init', () => {
|
|
396
|
-
// Alpine.data('%s', () => {
|
|
397
|
-
// return %s;
|
|
398
|
-
// });
|
|
399
|
-
// });
|
|
400
|
-
// `, name, data)))
|
|
401
|
-
// }
|
|
402
|
-
|
|
403
|
-
// func HTML(html string, data map[string]interface{}) string {
|
|
404
|
-
// return fmt.Sprintf("")
|
|
405
|
-
// }
|
|
406
|
-
|
|
407
|
-
// func Counter2(start int) string {
|
|
408
|
-
// data := map[string]interface{}{
|
|
409
|
-
// "count": 1,
|
|
410
|
-
// "names": []string{"123", "123"},
|
|
411
|
-
// "increment": func() string {
|
|
412
|
-
// return "this.count += 1;"
|
|
413
|
-
// },
|
|
414
|
-
// "decrement": func() string {
|
|
415
|
-
// return "this.count -= 1;"
|
|
416
|
-
// },
|
|
417
|
-
// }
|
|
418
|
-
// return HTML(`
|
|
419
|
-
// <div class="flex flex-col justify-center items-center text-3xl text-gray-700">
|
|
420
|
-
// <div class="flex flex-row justify-center items-center">
|
|
421
|
-
// <div class="flex flex-row justify-center items-center underline">
|
|
422
|
-
// Counter
|
|
423
|
-
// </div>
|
|
424
|
-
// </div>
|
|
425
|
-
// <div class="flex flex-row justify-center items-center" x-data="counter">
|
|
426
|
-
// <button @click="decrement" class="btn m-20">-</button>
|
|
427
|
-
// <div class="flex flex-row justify-center items-center m-20">{{ count }}</div>
|
|
428
|
-
// <button @click="increment" class="btn m-20">+</button>
|
|
429
|
-
// </div>
|
|
430
|
-
// <div>
|
|
431
|
-
// {{#if true }}
|
|
432
|
-
// render this
|
|
433
|
-
// {{/if}}
|
|
434
|
-
// </div>
|
|
435
|
-
// <div>
|
|
436
|
-
// {{#each names}}
|
|
437
|
-
// <li>{{ @index }} - {{ @value }}</li>
|
|
438
|
-
// {{/each}}
|
|
439
|
-
// </div>
|
|
440
|
-
// </div>
|
|
441
|
-
// `, data)
|
|
442
|
-
// }
|
readme.md
CHANGED
|
@@ -7,72 +7,14 @@
|
|
|
7
7
|
# wapp
|
|
8
8
|
|
|
9
9
|
**wapp** is a framework to build web apps in golang.
|
|
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.
|
|
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
12
|
# Install
|
|
13
13
|
|
|
14
|
-
**wapp** requirements:
|
|
15
|
-
|
|
16
|
-
- [Go 1.16](https://golang.org/doc/go1.16)
|
|
17
|
-
|
|
18
14
|
```sh
|
|
19
15
|
go mod init
|
|
20
16
|
go get -u -v github.com/pyros2097/wapp
|
|
21
17
|
```
|
|
18
|
+
# Example
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
https://github.com/pyros2097/wapp-example
|
|
24
|
-
|
|
25
|
-
**Example**
|
|
26
|
-
|
|
27
|
-
```go
|
|
28
|
-
package main
|
|
29
|
-
|
|
30
|
-
import (
|
|
31
|
-
. "github.com/pyros2097/wapp"
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
func Header() *Element {
|
|
35
|
-
return Row(Css("w-full mb-20 font-bold text-xl text-gray-700 p-4"),
|
|
36
|
-
Div(Css("text-blue-700"),
|
|
37
|
-
A(Href("https://wapp.pyros2097.dev"), Text("wapp.pyros2097.dev")),
|
|
38
|
-
),
|
|
39
|
-
Div(Css("flex flex-row flex-1 justify-end items-end p-2"),
|
|
40
|
-
Div(Css("border-b-2 border-white text-lg text-blue-700 mr-4"), Text("Examples: ")),
|
|
41
|
-
Div(Css("link mr-4"), A(Href("/"), Text("Home"))),
|
|
42
|
-
Div(Css("link mr-4"), A(Href("/clock"), Text("Clock"))),
|
|
43
|
-
Div(Css("link mr-4"), A(Href("/about"), Text("About"))),
|
|
44
|
-
Div(Css("link mr-4"), A(Href("/container"), Text("Container"))),
|
|
45
|
-
Div(Css("link mr-4"), A(Href("/panic"), Text("Panic"))),
|
|
46
|
-
),
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
func Index(w http.ResponseWriter, r *http.Request) *Element {
|
|
51
|
-
return Page(
|
|
52
|
-
Col(
|
|
53
|
-
Header(),
|
|
54
|
-
H1(Text("Hello this is a h1")),
|
|
55
|
-
H2(Text("Hello this is a h2")),
|
|
56
|
-
H2(XData("{ message: 'I ❤️ Alpine' }"), XText("message"), Text("")),
|
|
57
|
-
Col(Css("text-3xl text-gray-700"),
|
|
58
|
-
Row(
|
|
59
|
-
Row(Css("underline"),
|
|
60
|
-
Text("Counter"),
|
|
61
|
-
),
|
|
62
|
-
),
|
|
63
|
-
Row(
|
|
64
|
-
Button(Css("btn m-20"),
|
|
65
|
-
Text("-"),
|
|
66
|
-
),
|
|
67
|
-
Row(Css("m-20"),
|
|
68
|
-
Text(strconv.Itoa(1)),
|
|
69
|
-
),
|
|
70
|
-
Button(Css("btn m-20"),
|
|
71
|
-
Text("+"),
|
|
72
|
-
),
|
|
73
|
-
),
|
|
74
|
-
),
|
|
75
|
-
),
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
```
|