~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.
e6828463
—
Peter John 4 years ago
improv
- example/components/counter.go +7 -13
- example/components/page.go +1 -1
- example/context/context.go +142 -14
- example/main.go +3 -32
- example/makefile +1 -1
- example/models/todo.go +7 -0
- example/pages/about.go +16 -17
- example/pages/api/todos/_id/delete.go +11 -0
- example/pages/api/todos/_id/get.go +11 -0
- example/pages/api/todos/_id/put.go +11 -0
- example/pages/api/todos/get.go +11 -0
- example/pages/api/todos/post.go +11 -0
- example/pages/index.go +39 -15
example/components/counter.go
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package components
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"fmt"
|
|
5
4
|
"strconv"
|
|
6
5
|
|
|
7
6
|
. "github.com/pyros2097/wapp"
|
|
@@ -19,17 +18,12 @@ import (
|
|
|
19
18
|
// },
|
|
20
19
|
// }
|
|
21
20
|
|
|
22
|
-
func Counter(ctx
|
|
21
|
+
func Counter(ctx ReqContext, start int) *Element {
|
|
23
|
-
UseData(ctx, "counter",
|
|
22
|
+
UseData(ctx, "counter", M{
|
|
24
|
-
count: %d,
|
|
25
|
-
increment() {
|
|
26
|
-
|
|
23
|
+
"count": start,
|
|
24
|
+
"increment": func() string { return "this.count += 1;" },
|
|
25
|
+
"decrement": func() string { return "this.count -= 1;" },
|
|
27
|
-
|
|
26
|
+
})
|
|
28
|
-
decrement() {
|
|
29
|
-
this.count -= 1;
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
`, start))
|
|
33
27
|
return Col(Css("text-3xl text-gray-700"),
|
|
34
28
|
Row(
|
|
35
29
|
Row(Css("underline"),
|
|
@@ -40,7 +34,7 @@ func Counter(ctx *ReqContext, start int) *Element {
|
|
|
40
34
|
Button(Css("btn m-20"), OnClick("decrement"),
|
|
41
35
|
Text("-"),
|
|
42
36
|
),
|
|
43
|
-
Row(Css("m-20"), XText("count"),
|
|
37
|
+
Row(Css("m-20 text-8xl"), XText("count"),
|
|
44
38
|
Text(strconv.Itoa(start)),
|
|
45
39
|
),
|
|
46
40
|
Button(Css("btn m-20"), OnClick("increment"),
|
example/components/page.go
CHANGED
|
@@ -6,7 +6,7 @@ import (
|
|
|
6
6
|
. "github.com/pyros2097/wapp/example/context"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
func Page(ctx
|
|
9
|
+
func Page(ctx ReqContext, elem *Element) *Element {
|
|
10
10
|
return Html(
|
|
11
11
|
Head(
|
|
12
12
|
Title("123"),
|
example/context/context.go
CHANGED
|
@@ -3,29 +3,157 @@ package context
|
|
|
3
3
|
import (
|
|
4
4
|
"bytes"
|
|
5
5
|
c "context"
|
|
6
|
+
"encoding/json"
|
|
6
|
-
"
|
|
7
|
+
"io/ioutil"
|
|
7
8
|
"net/http"
|
|
9
|
+
"reflect"
|
|
10
|
+
|
|
11
|
+
"github.com/gobuffalo/velvet"
|
|
12
|
+
"github.com/gorilla/mux"
|
|
13
|
+
. "github.com/pyros2097/wapp"
|
|
14
|
+
"golang.org/x/net/html"
|
|
8
15
|
)
|
|
9
16
|
|
|
10
17
|
type ReqContext struct {
|
|
11
18
|
c.Context
|
|
12
|
-
|
|
19
|
+
Path string
|
|
20
|
+
PathParams map[string]string
|
|
21
|
+
QueryParams map[string]string
|
|
22
|
+
Body []byte
|
|
13
|
-
JS
|
|
23
|
+
JS *bytes.Buffer
|
|
14
|
-
CSS
|
|
24
|
+
CSS *bytes.Buffer
|
|
25
|
+
UserID string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func NewReqContext(r *http.Request) (ReqContext, error) {
|
|
29
|
+
pathParams := mux.Vars(r)
|
|
30
|
+
var body []byte
|
|
31
|
+
var err error
|
|
32
|
+
if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
|
|
33
|
+
body, err = ioutil.ReadAll(r.Body)
|
|
34
|
+
if err != nil {
|
|
35
|
+
return ReqContext{}, err
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return ReqContext{
|
|
39
|
+
Path: r.URL.Path,
|
|
40
|
+
QueryParams: map[string]string{},
|
|
41
|
+
PathParams: pathParams,
|
|
42
|
+
Body: body,
|
|
43
|
+
JS: bytes.NewBuffer(nil),
|
|
44
|
+
CSS: bytes.NewBuffer(nil),
|
|
45
|
+
UserID: "123",
|
|
46
|
+
}, nil
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Handler func(c ReqContext) (interface{}, int, error)
|
|
50
|
+
|
|
51
|
+
func Wrap(h Handler) func(http.ResponseWriter, *http.Request) {
|
|
52
|
+
return func(w http.ResponseWriter, r *http.Request) {
|
|
53
|
+
ctx, err := NewReqContext(r)
|
|
54
|
+
if err != nil {
|
|
55
|
+
w.Header().Set("Content-Type", "application/json")
|
|
56
|
+
data, _ := json.Marshal(M{
|
|
57
|
+
"error": err.Error(),
|
|
58
|
+
})
|
|
59
|
+
w.Write(data)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
value, status, err := h(ctx)
|
|
63
|
+
w.WriteHeader(status)
|
|
64
|
+
if err != nil {
|
|
65
|
+
w.Header().Set("Content-Type", "application/json")
|
|
66
|
+
data, _ := json.Marshal(M{
|
|
67
|
+
"error": err.Error(),
|
|
68
|
+
})
|
|
69
|
+
w.Write(data)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if v, ok := value.(*Element); ok {
|
|
73
|
+
w.Header().Set("Content-Type", "text/html")
|
|
74
|
+
v.WriteHtml(w)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
w.Header().Set("Content-Type", "application/json")
|
|
78
|
+
data, _ := json.Marshal(value)
|
|
79
|
+
w.Write(data)
|
|
80
|
+
}
|
|
15
81
|
}
|
|
16
82
|
|
|
83
|
+
func IsFunc(v interface{}) bool {
|
|
17
|
-
|
|
84
|
+
return reflect.TypeOf(v).Kind() == reflect.Func
|
|
18
|
-
return &ReqContext{r.Context(), "123", bytes.NewBuffer(nil), bytes.NewBuffer(nil)}
|
|
19
85
|
}
|
|
20
86
|
|
|
21
|
-
func UseData(ctx
|
|
87
|
+
func UseData(ctx ReqContext, name string, data map[string]interface{}) {
|
|
88
|
+
v := velvet.NewContext()
|
|
22
|
-
|
|
89
|
+
v.Set("name", name)
|
|
90
|
+
generatedMap := M{}
|
|
91
|
+
for k, v := range data {
|
|
92
|
+
if IsFunc(v) {
|
|
93
|
+
f := v.(func() string)
|
|
94
|
+
generatedMap[k] = "() {" + f() + "}"
|
|
95
|
+
} else {
|
|
96
|
+
generatedMap[k+":"] = v
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
v.Set("data", generatedMap)
|
|
100
|
+
s, err := velvet.Render(`
|
|
23
|
-
Alpine.data('
|
|
101
|
+
Alpine.data('{{ name }}', () => {
|
|
24
|
-
return
|
|
102
|
+
return {
|
|
103
|
+
{{#each data}}
|
|
104
|
+
{{ @key }}{{ @value }},
|
|
105
|
+
{{/each}}
|
|
106
|
+
};
|
|
25
107
|
});
|
|
108
|
+
`, v)
|
|
26
|
-
|
|
109
|
+
if err != nil {
|
|
110
|
+
panic(err)
|
|
111
|
+
}
|
|
112
|
+
ctx.JS.WriteString(s)
|
|
27
113
|
}
|
|
28
114
|
|
|
115
|
+
type Component func(ReqContext) string
|
|
116
|
+
|
|
117
|
+
var components = map[string]Component{}
|
|
118
|
+
|
|
119
|
+
func RegisterComponent(k string, v Component) {
|
|
120
|
+
components[k] = v
|
|
121
|
+
}
|
|
122
|
+
|
|
29
|
-
|
|
123
|
+
func Html2(ctx ReqContext, input string, data map[string]interface{}) string {
|
|
124
|
+
vctx := velvet.NewContext()
|
|
125
|
+
for k, v := range data {
|
|
126
|
+
vctx.Set(k, v)
|
|
127
|
+
}
|
|
128
|
+
doc, err := html.Parse(bytes.NewBufferString(input))
|
|
129
|
+
if err != nil {
|
|
130
|
+
panic(err)
|
|
131
|
+
}
|
|
132
|
+
var f func(*html.Node)
|
|
133
|
+
f = func(n *html.Node) {
|
|
134
|
+
if c, ok := components[n.Data]; ok {
|
|
135
|
+
txt := c(ctx)
|
|
136
|
+
println(n.Data, txt)
|
|
30
|
-
//
|
|
137
|
+
// newNode, err := html.Parse(bytes.NewBufferString(txt))
|
|
138
|
+
// if err != nil {
|
|
139
|
+
// panic(err)
|
|
31
|
-
// }
|
|
140
|
+
// }
|
|
141
|
+
println(n.NextSibling.Type)
|
|
142
|
+
// n.RemoveChild(n.NextSibling)
|
|
143
|
+
// n.RemoveChild(n.PrevSibling)
|
|
144
|
+
// n.AppendChild(newNode)
|
|
145
|
+
}
|
|
146
|
+
if n.Type == html.ElementNode && n.Data == "h1" {
|
|
147
|
+
println("h1")
|
|
148
|
+
}
|
|
149
|
+
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
150
|
+
f(c)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
f(doc)
|
|
154
|
+
s, err := velvet.Render(input, vctx)
|
|
155
|
+
if err != nil {
|
|
156
|
+
panic(err)
|
|
157
|
+
}
|
|
158
|
+
return s
|
|
159
|
+
}
|
example/main.go
CHANGED
|
@@ -2,7 +2,6 @@ package main
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"embed"
|
|
5
|
-
"encoding/json"
|
|
6
5
|
"log"
|
|
7
6
|
"net/http"
|
|
8
7
|
"os"
|
|
@@ -10,47 +9,19 @@ import (
|
|
|
10
9
|
|
|
11
10
|
"github.com/apex/gateway/v2"
|
|
12
11
|
"github.com/gorilla/mux"
|
|
13
|
-
. "github.com/pyros2097/wapp"
|
|
14
|
-
|
|
15
|
-
"github.com/pyros2097/wapp/example/context"
|
|
12
|
+
. "github.com/pyros2097/wapp/example/context"
|
|
16
13
|
"github.com/pyros2097/wapp/example/pages"
|
|
17
14
|
)
|
|
18
15
|
|
|
19
16
|
//go:embed assets/*
|
|
20
17
|
var assetsFS embed.FS
|
|
21
18
|
|
|
22
|
-
type Handler func(c *context.ReqContext) (interface{}, int, error)
|
|
23
|
-
|
|
24
|
-
func wrap(h Handler) func(http.ResponseWriter, *http.Request) {
|
|
25
|
-
return func(w http.ResponseWriter, r *http.Request) {
|
|
26
|
-
ctx := context.NewReqContext(w, r)
|
|
27
|
-
value, status, err := h(ctx)
|
|
28
|
-
w.WriteHeader(status)
|
|
29
|
-
if err != nil {
|
|
30
|
-
w.Header().Set("Content-Type", "application/json")
|
|
31
|
-
data, _ := json.Marshal(M{
|
|
32
|
-
"error": err.Error(),
|
|
33
|
-
})
|
|
34
|
-
w.Write(data)
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
if v, ok := value.(*Element); ok {
|
|
38
|
-
w.Header().Set("Content-Type", "text/html")
|
|
39
|
-
v.WriteHtml(w)
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
w.Header().Set("Content-Type", "application/json")
|
|
43
|
-
data, _ := json.Marshal(value)
|
|
44
|
-
w.Write(data)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
19
|
func main() {
|
|
49
20
|
isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
|
|
50
21
|
r := mux.NewRouter()
|
|
51
22
|
r.PathPrefix("/assets/").Handler(http.FileServer(http.FS(assetsFS)))
|
|
52
|
-
r.HandleFunc("/",
|
|
23
|
+
r.HandleFunc("/", Wrap(pages.Index)).Methods("GET")
|
|
53
|
-
|
|
24
|
+
r.HandleFunc("/about", Wrap(pages.About)).Methods("GET")
|
|
54
25
|
if !isLambda {
|
|
55
26
|
println("http server listening on http://localhost:3000")
|
|
56
27
|
srv := &http.Server{
|
example/makefile
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
run:
|
|
2
|
-
go run
|
|
2
|
+
go run main.go
|
|
3
3
|
|
|
4
4
|
local:
|
|
5
5
|
sam local start-api
|
example/models/todo.go
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
package models
|
|
2
|
+
|
|
3
|
+
type Todo struct {
|
|
4
|
+
ID string `json:"id"`
|
|
5
|
+
Text string `json:"text"`
|
|
6
|
+
Completed bool `json:"completed"`
|
|
7
|
+
}
|
example/pages/about.go
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
package pages
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import (
|
|
4
|
-
//
|
|
4
|
+
. "github.com/pyros2097/wapp"
|
|
5
|
+
. "github.com/pyros2097/wapp/example/components"
|
|
6
|
+
"github.com/pyros2097/wapp/example/context"
|
|
7
|
+
)
|
|
5
8
|
|
|
6
|
-
// . "github.com/pyros2097/wapp"
|
|
7
|
-
// . "github.com/pyros2097/wapp/example/components"
|
|
8
|
-
// )
|
|
9
|
-
|
|
10
|
-
//
|
|
9
|
+
//wapp:page method=GET path=/about
|
|
11
|
-
|
|
10
|
+
func About(c context.ReqContext) (interface{}, int, error) {
|
|
12
|
-
|
|
11
|
+
return Page(c,
|
|
13
|
-
|
|
12
|
+
Col(
|
|
14
|
-
|
|
13
|
+
Header(),
|
|
15
|
-
|
|
14
|
+
Row(Css("text-5xl text-gray-700"),
|
|
16
|
-
|
|
15
|
+
Text("About Me"),
|
|
17
|
-
// ),
|
|
18
|
-
|
|
16
|
+
),
|
|
19
|
-
|
|
17
|
+
),
|
|
20
|
-
|
|
18
|
+
), 200, nil
|
|
19
|
+
}
|
example/pages/api/todos/_id/delete.go
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package todo
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
. "github.com/pyros2097/wapp/example/models"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func DELETE(c context.Context, id string, params map[string]interface{}) (interface{}, error) {
|
|
10
|
+
return Todo{}, nil
|
|
11
|
+
}
|
example/pages/api/todos/_id/get.go
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package todo
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
. "github.com/pyros2097/wapp/example/models"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func GET(c context.Context, id string, params map[string]interface{}) (interface{}, error) {
|
|
10
|
+
return Todo{}, nil
|
|
11
|
+
}
|
example/pages/api/todos/_id/put.go
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package todo
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
. "github.com/pyros2097/wapp/example/models"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func PUT(c context.Context, id string, params map[string]interface{}) (interface{}, error) {
|
|
10
|
+
return Todo{}, nil
|
|
11
|
+
}
|
example/pages/api/todos/get.go
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package pages
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
. "github.com/pyros2097/wapp/example/models"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func GET(c context.Context) (interface{}, error) {
|
|
10
|
+
return []Todo{}, nil
|
|
11
|
+
}
|
example/pages/api/todos/post.go
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package pages
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
|
|
6
|
+
. "github.com/pyros2097/wapp/example/models"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func POST(c context.Context) (interface{}, error) {
|
|
10
|
+
return Todo{}, nil
|
|
11
|
+
}
|
example/pages/index.go
CHANGED
|
@@ -3,11 +3,35 @@ package pages
|
|
|
3
3
|
import (
|
|
4
4
|
. "github.com/pyros2097/wapp"
|
|
5
5
|
. "github.com/pyros2097/wapp/example/components"
|
|
6
|
-
"github.com/pyros2097/wapp/example/context"
|
|
6
|
+
. "github.com/pyros2097/wapp/example/context"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
+
func init() {
|
|
10
|
+
RegisterComponent("page", func(c ReqContext) string {
|
|
11
|
+
return Html2(c, `
|
|
12
|
+
<html>
|
|
13
|
+
<body>
|
|
9
|
-
/
|
|
14
|
+
<slot></slot>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
17
|
+
`, M{})
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
10
|
-
func Index(c
|
|
21
|
+
func Index(c ReqContext) (interface{}, int, error) {
|
|
22
|
+
ss := Html2(c, `
|
|
23
|
+
<page x-data="pageData">
|
|
24
|
+
<div class="flex flex-col items-center justify-center">
|
|
25
|
+
<header></header>
|
|
26
|
+
<h1>Hello {{ userID }}</h1>
|
|
27
|
+
<h2>Hello this is a h1</h1>
|
|
28
|
+
<h2>Hello this is a h2</h1>
|
|
29
|
+
<h3 x-text="message"></h3>
|
|
30
|
+
<counter start={4}></counter>
|
|
31
|
+
</div>
|
|
32
|
+
</page>
|
|
33
|
+
`, M{"userID": c.UserID})
|
|
34
|
+
println("ss", ss)
|
|
11
35
|
return Page(c,
|
|
12
36
|
Col(
|
|
13
37
|
Header(),
|
|
@@ -25,16 +49,16 @@ func Index(c *context.ReqContext) (interface{}, int, error) {
|
|
|
25
49
|
// "userID": c.UserID,
|
|
26
50
|
// "message": "I ❤️ Alpine",
|
|
27
51
|
// }
|
|
28
|
-
//
|
|
52
|
+
// return Html(`
|
|
29
|
-
//
|
|
53
|
+
// <page x-data="pageData">
|
|
30
|
-
//
|
|
54
|
+
// <div class="flex flex-col items-center justify-center">
|
|
31
|
-
//
|
|
55
|
+
// <header></header>
|
|
32
|
-
//
|
|
56
|
+
// <h1>Hello <template x-text="userID"></template></h1>
|
|
33
|
-
//
|
|
57
|
+
// <h2>Hello this is a h1</h1>
|
|
34
|
-
//
|
|
58
|
+
// <h2>Hello this is a h2</h1>
|
|
35
|
-
//
|
|
59
|
+
// <h3 x-text="message"></h3>
|
|
36
|
-
//
|
|
60
|
+
// <counter start={4}></counter>
|
|
37
|
-
//
|
|
61
|
+
// </div>
|
|
38
|
-
//
|
|
62
|
+
// </page>
|
|
39
|
-
//
|
|
63
|
+
// `, data), 200, nil
|
|
40
64
|
// }
|