~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.
3b377aa9
—
Peter John 3 years ago
make it simpler
- api_explorer.go +4 -1
- cmd/gromer/main.go +22 -0
- example/components/header.go +51 -51
- example/components/init.go +0 -10
- example/components/page.go +8 -3
- example/main.go +7 -0
- example/pages/about/get.go +1 -1
- example/pages/get.go +3 -1
- http.go +36 -17
api_explorer.go
CHANGED
|
@@ -780,5 +780,8 @@ func ApiExplorer(ctx context.Context) (HtmlContent, int, error) {
|
|
|
780
780
|
</script>
|
|
781
781
|
</body>
|
|
782
782
|
</html>
|
|
783
|
+
`).Props(
|
|
784
|
+
"routes", apiRoutes,
|
|
783
|
-
|
|
785
|
+
"apiData", template.HTML(string(apiData)),
|
|
786
|
+
).Render()
|
|
784
787
|
}
|
cmd/gromer/main.go
CHANGED
|
@@ -141,10 +141,26 @@ func main() {
|
|
|
141
141
|
hasRouteMap[v.PkgPath] = true
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
componentNames := []string{}
|
|
145
|
+
err = filepath.Walk("components",
|
|
146
|
+
func(filesrc string, info os.FileInfo, err error) error {
|
|
147
|
+
if err != nil {
|
|
148
|
+
return err
|
|
149
|
+
}
|
|
150
|
+
if !info.IsDir() {
|
|
151
|
+
filename := strings.ReplaceAll(filepath.Base(filesrc), ".go", "")
|
|
152
|
+
componentNames = append(componentNames, strings.Title((filename)))
|
|
153
|
+
}
|
|
154
|
+
return nil
|
|
155
|
+
})
|
|
156
|
+
if err != nil {
|
|
157
|
+
log.Fatal(err)
|
|
158
|
+
}
|
|
144
159
|
ctx := velvet.NewContext()
|
|
145
160
|
ctx.Set("moduleName", moduleName)
|
|
146
161
|
ctx.Set("routes", gromer.RouteDefs)
|
|
147
162
|
ctx.Set("routeImports", routeImports)
|
|
163
|
+
ctx.Set("componentNames", componentNames)
|
|
148
164
|
ctx.Set("tick", "`")
|
|
149
165
|
s, err := velvet.Render(`// Code generated by gromer. DO NOT EDIT.
|
|
150
166
|
package main
|
|
@@ -158,10 +174,16 @@ import (
|
|
|
158
174
|
"gocloud.dev/server"
|
|
159
175
|
|
|
160
176
|
"{{ moduleName }}/assets"
|
|
177
|
+
"{{ moduleName }}/components"
|
|
161
178
|
{{#each routeImports as |route| }}"{{ moduleName }}/pages{{ route.PkgPath }}"
|
|
162
179
|
{{/each}}
|
|
163
180
|
)
|
|
164
181
|
|
|
182
|
+
func init() {
|
|
183
|
+
{{#each componentNames as |name| }}gromer.RegisterComponent(components.{{ name }})
|
|
184
|
+
{{/each}}
|
|
185
|
+
}
|
|
186
|
+
|
|
165
187
|
func main() {
|
|
166
188
|
port := os.Getenv("PORT")
|
|
167
189
|
r := mux.NewRouter()
|
example/components/header.go
CHANGED
|
@@ -4,57 +4,57 @@ import (
|
|
|
4
4
|
. "github.com/pyros2097/gromer"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
-
func Header()
|
|
7
|
+
func Header() *HandlersTemplate {
|
|
8
|
-
return
|
|
8
|
+
return Html(`
|
|
9
9
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
10
|
-
|
|
10
|
+
<div class="navbar-brand">
|
|
11
|
-
|
|
11
|
+
<a class="navbar-item" href="https://bulma.io">
|
|
12
|
-
|
|
12
|
+
<img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
|
|
13
|
-
|
|
13
|
+
</a>
|
|
14
|
-
|
|
14
|
+
|
|
15
|
-
|
|
15
|
+
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
|
16
|
-
|
|
16
|
+
<span aria-hidden="true"></span>
|
|
17
|
-
|
|
17
|
+
<span aria-hidden="true"></span>
|
|
18
|
-
|
|
18
|
+
<span aria-hidden="true"></span>
|
|
19
|
-
|
|
19
|
+
</a>
|
|
20
|
-
|
|
20
|
+
</div>
|
|
21
|
-
|
|
21
|
+
|
|
22
|
-
|
|
22
|
+
<div id="navbarBasicExample" class="navbar-menu">
|
|
23
|
-
|
|
23
|
+
<div class="navbar-start">
|
|
24
|
-
|
|
24
|
+
<a class="navbar-item" href="/">
|
|
25
|
-
|
|
25
|
+
Home
|
|
26
|
-
|
|
26
|
+
</a>
|
|
27
|
-
|
|
27
|
+
|
|
28
|
-
|
|
28
|
+
<a class="navbar-item" href="/about">
|
|
29
|
-
|
|
29
|
+
About
|
|
30
|
-
|
|
30
|
+
</a>
|
|
31
|
-
|
|
31
|
+
|
|
32
|
-
|
|
32
|
+
<a class="navbar-item" href="/clock">
|
|
33
|
-
|
|
33
|
+
Clock
|
|
34
|
-
|
|
34
|
+
</a>
|
|
35
|
-
|
|
35
|
+
|
|
36
|
-
|
|
36
|
+
<a class="navbar-item" href="/counter">
|
|
37
|
-
|
|
37
|
+
Counter
|
|
38
|
-
|
|
38
|
+
</a>
|
|
39
|
-
|
|
39
|
+
|
|
40
|
-
|
|
40
|
+
<a class="navbar-item" href="/api">
|
|
41
|
-
|
|
41
|
+
API
|
|
42
|
-
|
|
42
|
+
</a>
|
|
43
|
-
|
|
43
|
+
</div>
|
|
44
|
-
|
|
44
|
+
|
|
45
|
-
|
|
45
|
+
<div class="navbar-end">
|
|
46
|
-
|
|
46
|
+
<div class="navbar-item">
|
|
47
|
-
|
|
47
|
+
<div class="buttons">
|
|
48
|
-
|
|
48
|
+
<a class="button is-primary">
|
|
49
|
-
|
|
49
|
+
<strong>Sign up</strong>
|
|
50
|
-
|
|
50
|
+
</a>
|
|
51
|
-
|
|
51
|
+
<a class="button is-light">
|
|
52
|
-
|
|
52
|
+
Log in
|
|
53
|
-
|
|
53
|
+
</a>
|
|
54
|
-
|
|
54
|
+
</div>
|
|
55
|
-
|
|
55
|
+
</div>
|
|
56
|
-
|
|
56
|
+
</div>
|
|
57
|
-
|
|
57
|
+
</div>
|
|
58
|
-
</nav>
|
|
58
|
+
</nav>
|
|
59
59
|
`)
|
|
60
60
|
}
|
example/components/init.go
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
package components
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
. "github.com/pyros2097/gromer"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
func init() {
|
|
8
|
-
RegisterComponent(Page)
|
|
9
|
-
RegisterComponent(Header)
|
|
10
|
-
}
|
example/components/page.go
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
package components
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"html/template"
|
|
5
|
+
|
|
4
6
|
. "github.com/pyros2097/gromer"
|
|
5
7
|
)
|
|
6
8
|
|
|
7
|
-
func Page(
|
|
9
|
+
func Page(title string, children template.HTML) *HandlersTemplate {
|
|
8
|
-
return
|
|
10
|
+
return Html(`
|
|
9
11
|
<!DOCTYPE html>
|
|
10
12
|
<html lang="en">
|
|
11
13
|
<head>
|
|
@@ -26,5 +28,8 @@ func Page() string {
|
|
|
26
28
|
{{ children }}
|
|
27
29
|
</body>
|
|
28
30
|
</html>
|
|
31
|
+
`).Props(
|
|
32
|
+
"title", title,
|
|
33
|
+
"children", children,
|
|
29
|
-
|
|
34
|
+
)
|
|
30
35
|
}
|
example/main.go
CHANGED
|
@@ -10,6 +10,7 @@ import (
|
|
|
10
10
|
"gocloud.dev/server"
|
|
11
11
|
|
|
12
12
|
"github.com/pyros2097/gromer/example/assets"
|
|
13
|
+
"github.com/pyros2097/gromer/example/components"
|
|
13
14
|
"github.com/pyros2097/gromer/example/pages"
|
|
14
15
|
"github.com/pyros2097/gromer/example/pages/about"
|
|
15
16
|
"github.com/pyros2097/gromer/example/pages/api/recover"
|
|
@@ -18,6 +19,12 @@ import (
|
|
|
18
19
|
|
|
19
20
|
)
|
|
20
21
|
|
|
22
|
+
func init() {
|
|
23
|
+
gromer.RegisterComponent(components.Header)
|
|
24
|
+
gromer.RegisterComponent(components.Page)
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
func main() {
|
|
22
29
|
port := os.Getenv("PORT")
|
|
23
30
|
r := mux.NewRouter()
|
example/pages/about/get.go
CHANGED
|
@@ -16,5 +16,5 @@ func GET(c context.Context) (HtmlContent, int, error) {
|
|
|
16
16
|
<h1>About Me</h1>
|
|
17
17
|
</div>
|
|
18
18
|
{{/Page}}
|
|
19
|
-
`
|
|
19
|
+
`).Render()
|
|
20
20
|
}
|
example/pages/get.go
CHANGED
|
@@ -74,5 +74,7 @@ func GET(ctx context.Context, params GetParams) (HtmlContent, int, error) {
|
|
|
74
74
|
</nav>
|
|
75
75
|
</main>
|
|
76
76
|
{{/Page}}
|
|
77
|
+
`).
|
|
77
|
-
|
|
78
|
+
Prop("todos", todos).
|
|
79
|
+
Render()
|
|
78
80
|
}
|
http.go
CHANGED
|
@@ -40,23 +40,36 @@ type RouteDefinition struct {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
type HtmlContent string
|
|
43
|
+
type HandlersTemplate struct {
|
|
44
|
+
text string
|
|
45
|
+
ctx *velvet.Context
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func Html(tpl string) *HandlersTemplate {
|
|
49
|
+
return &HandlersTemplate{text: tpl, ctx: velvet.NewContext()}
|
|
50
|
+
}
|
|
43
51
|
|
|
44
|
-
func
|
|
52
|
+
func (t *HandlersTemplate) Prop(key string, v any) *HandlersTemplate {
|
|
45
|
-
ctx := velvet.NewContext()
|
|
46
|
-
for k, v := range params {
|
|
47
|
-
|
|
53
|
+
t.ctx.Set(key, v)
|
|
54
|
+
return t
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func (t *HandlersTemplate) Props(args ...any) *HandlersTemplate {
|
|
58
|
+
for i := 0; i < len(args); i += 2 {
|
|
59
|
+
key := fmt.Sprintf("%s", args[i])
|
|
60
|
+
t.ctx.Set(key, args[i+1])
|
|
48
61
|
}
|
|
62
|
+
return t
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func (t *HandlersTemplate) Render(args ...any) (HtmlContent, int, error) {
|
|
49
|
-
s, err := velvet.Render(
|
|
66
|
+
s, err := velvet.Render(t.text, t.ctx)
|
|
50
67
|
if err != nil {
|
|
51
|
-
return HtmlContent(""), 500, err
|
|
68
|
+
return HtmlContent("Server Erorr"), 500, err
|
|
52
69
|
}
|
|
53
70
|
return HtmlContent(s), 200, nil
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
func Component(tpl string) string {
|
|
57
|
-
return tpl
|
|
58
|
-
}
|
|
59
|
-
|
|
60
73
|
func HtmlErr(status int, err error) (HtmlContent, int, error) {
|
|
61
74
|
return HtmlContent("ErrorPage/AccessDeniedPage/NotFoundPage based on status code"), status, err
|
|
62
75
|
}
|
|
@@ -66,19 +79,25 @@ func GetFunctionName(temp interface{}) string {
|
|
|
66
79
|
return strs[len(strs)-1]
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
func RegisterComponent(fn interface{}) {
|
|
82
|
+
func RegisterComponent(fn interface{}, props ...string) {
|
|
70
83
|
name := GetFunctionName(fn)
|
|
84
|
+
fnType := reflect.TypeOf(fn)
|
|
71
|
-
|
|
85
|
+
fnValue := reflect.ValueOf(fn)
|
|
72
86
|
velvet.Helpers.Add(name, func(title string, c velvet.HelperContext) (template.HTML, error) {
|
|
73
87
|
s, err := c.Block()
|
|
74
88
|
if err != nil {
|
|
75
89
|
return "", err
|
|
76
90
|
}
|
|
77
|
-
|
|
91
|
+
args := []reflect.Value{}
|
|
92
|
+
if fnType.NumIn() > 0 {
|
|
93
|
+
args = append(args, reflect.ValueOf(title))
|
|
78
|
-
|
|
94
|
+
if s != "" {
|
|
79
|
-
|
|
95
|
+
args = append(args, reflect.ValueOf(template.HTML(s)))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
res := fnValue.Call(args)
|
|
80
|
-
|
|
99
|
+
tpl := res[0].Interface().(*HandlersTemplate)
|
|
81
|
-
comp, err := velvet.Render(
|
|
100
|
+
comp, err := velvet.Render(tpl.text, tpl.ctx)
|
|
82
101
|
if err != nil {
|
|
83
102
|
return "", err
|
|
84
103
|
}
|