~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.
move Template to handlebars
- api_explorer.go +2 -0
- example/components/header.go +2 -2
- example/components/page.go +2 -2
- example/components/todo.go +2 -2
- example/main.go +1 -0
- example/pages/about/get.go +3 -3
- example/pages/get.go +1 -0
- handlebars/template.go +29 -33
- http.go +5 -41
api_explorer.go
CHANGED
|
@@ -5,6 +5,8 @@ import (
|
|
|
5
5
|
"encoding/json"
|
|
6
6
|
"html/template"
|
|
7
7
|
"strings"
|
|
8
|
+
|
|
9
|
+
. "github.com/pyros2097/gromer/handlebars"
|
|
8
10
|
)
|
|
9
11
|
|
|
10
12
|
func ApiExplorer(ctx context.Context) (HtmlContent, int, error) {
|
example/components/header.go
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
package components
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
. "github.com/pyros2097/gromer"
|
|
4
|
+
. "github.com/pyros2097/gromer/handlebars"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
-
func Header() *
|
|
7
|
+
func Header() *Template {
|
|
8
8
|
return Html(`
|
|
9
9
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
|
10
10
|
<div class="navbar-brand">
|
example/components/page.go
CHANGED
|
@@ -3,7 +3,7 @@ package components
|
|
|
3
3
|
import (
|
|
4
4
|
"html/template"
|
|
5
5
|
|
|
6
|
-
. "github.com/pyros2097/gromer"
|
|
6
|
+
. "github.com/pyros2097/gromer/handlebars"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
9
|
type PageProps struct {
|
|
@@ -11,7 +11,7 @@ type PageProps struct {
|
|
|
11
11
|
Children template.HTML `json:"children"`
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
func Page(props PageProps) *
|
|
14
|
+
func Page(props PageProps) *Template {
|
|
15
15
|
return Html(`
|
|
16
16
|
<!DOCTYPE html>
|
|
17
17
|
<html lang="en">
|
example/components/todo.go
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
package components
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
. "github.com/pyros2097/gromer"
|
|
5
4
|
"github.com/pyros2097/gromer/example/db"
|
|
5
|
+
. "github.com/pyros2097/gromer/handlebars"
|
|
6
6
|
)
|
|
7
7
|
|
|
8
8
|
type TodoProps struct {
|
|
9
9
|
Todo *db.Todo `json:"todo"`
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
func Todo(props TodoProps) *
|
|
12
|
+
func Todo(props TodoProps) *Template {
|
|
13
13
|
return Html(`
|
|
14
14
|
<tr>
|
|
15
15
|
<td>{{ props.Todo.ID }}</td>
|
example/main.go
CHANGED
|
@@ -23,6 +23,7 @@ func init() {
|
|
|
23
23
|
gromer.RegisterComponent(components.Header)
|
|
24
24
|
gromer.RegisterComponent(components.Page)
|
|
25
25
|
gromer.RegisterComponent(components.Todo)
|
|
26
|
+
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
func main() {
|
example/pages/about/get.go
CHANGED
|
@@ -3,14 +3,14 @@ package about
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
5
|
|
|
6
|
-
. "github.com/pyros2097/gromer"
|
|
6
|
+
. "github.com/pyros2097/gromer/handlebars"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
9
|
func GET(c context.Context) (HtmlContent, int, error) {
|
|
10
10
|
return Html(`
|
|
11
|
-
{{#Page "About me"}}
|
|
11
|
+
{{#Page title="About me"}}
|
|
12
12
|
<div class="flex flex-col justify-center items-center">
|
|
13
|
-
{{#Header
|
|
13
|
+
{{#Header}}
|
|
14
14
|
A new link is here
|
|
15
15
|
{{/Header}}
|
|
16
16
|
<h1>About Me</h1>
|
example/pages/get.go
CHANGED
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
. "github.com/pyros2097/gromer"
|
|
7
7
|
_ "github.com/pyros2097/gromer/example/components"
|
|
8
8
|
"github.com/pyros2097/gromer/example/pages/api/todos"
|
|
9
|
+
. "github.com/pyros2097/gromer/handlebars"
|
|
9
10
|
)
|
|
10
11
|
|
|
11
12
|
type GetParams struct {
|
handlebars/template.go
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
package handlebars
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"
|
|
4
|
+
"fmt"
|
|
5
5
|
|
|
6
6
|
"github.com/aymerick/raymond/ast"
|
|
7
7
|
"github.com/aymerick/raymond/parser"
|
|
8
8
|
"github.com/pkg/errors"
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
type HtmlContent string
|
|
12
|
+
|
|
11
13
|
// Template represents an input and helpers to be used
|
|
12
14
|
// to evaluate and render the input.
|
|
13
15
|
type Template struct {
|
|
14
16
|
Input string
|
|
17
|
+
Context *Context
|
|
15
18
|
program *ast.Program
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
// NewTemplate from the input string.
|
|
19
22
|
func NewTemplate(input string) (*Template, error) {
|
|
20
23
|
t := &Template{
|
|
21
|
-
Input:
|
|
24
|
+
Input: input,
|
|
25
|
+
Context: NewContext(),
|
|
22
26
|
}
|
|
23
27
|
err := t.Parse()
|
|
24
28
|
if err != nil {
|
|
@@ -43,53 +47,45 @@ func (t *Template) Parse() error {
|
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
// Exec the template using the content and return the results
|
|
46
|
-
func (t *Template)
|
|
50
|
+
func (t *Template) Render() (HtmlContent, int, error) {
|
|
47
51
|
err := t.Parse()
|
|
48
52
|
if err != nil {
|
|
49
|
-
return "", errors.WithStack(err)
|
|
53
|
+
return HtmlContent("Server Erorr"), 500, errors.WithStack(err)
|
|
50
54
|
}
|
|
51
|
-
v := newEvalVisitor(t,
|
|
55
|
+
v := newEvalVisitor(t, t.Context)
|
|
52
56
|
r := t.program.Accept(v)
|
|
53
57
|
switch rp := r.(type) {
|
|
54
58
|
case string:
|
|
55
|
-
return rp, nil
|
|
59
|
+
return HtmlContent(rp), 200, nil
|
|
56
60
|
case error:
|
|
57
|
-
return "", rp
|
|
61
|
+
return HtmlContent("Server Erorr"), 500, rp
|
|
58
62
|
case nil:
|
|
59
|
-
return "", nil
|
|
63
|
+
return HtmlContent(""), 200, nil
|
|
60
64
|
default:
|
|
61
|
-
return "", errors.WithStack(errors.Errorf("unsupport eval return format %T: %+v", r, r))
|
|
65
|
+
return HtmlContent("Server Erorr"), 500, errors.WithStack(errors.Errorf("unsupport eval return format %T: %+v", r, r))
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
|
|
69
|
+
func (t *Template) Prop(key string, v any) *Template {
|
|
66
|
-
|
|
70
|
+
t.Context.Set(key, v)
|
|
71
|
+
return t
|
|
72
|
+
}
|
|
67
73
|
|
|
68
|
-
// Parse an input string and return a Template.
|
|
69
|
-
func
|
|
74
|
+
func (t *Template) Props(args ...any) *Template {
|
|
70
|
-
moot.Lock()
|
|
71
|
-
defer moot.Unlock()
|
|
72
|
-
|
|
75
|
+
for i := 0; i < len(args); i += 2 {
|
|
76
|
+
key := fmt.Sprintf("%s", args[i])
|
|
73
|
-
|
|
77
|
+
t.Context.Set(key, args[i+1])
|
|
74
78
|
}
|
|
75
|
-
|
|
79
|
+
return t
|
|
80
|
+
}
|
|
76
81
|
|
|
82
|
+
func Html(tpl string) *Template {
|
|
77
|
-
|
|
83
|
+
return &Template{
|
|
78
|
-
|
|
84
|
+
Input: tpl,
|
|
85
|
+
Context: NewContext(),
|
|
79
86
|
}
|
|
80
|
-
|
|
81
|
-
if err != nil {
|
|
82
|
-
return t, errors.WithStack(err)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return t, nil
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
// Render a string using the given the context.
|
|
89
|
-
func
|
|
89
|
+
func HtmlErr(status int, err error) (HtmlContent, int, error) {
|
|
90
|
-
t, err := Parse(input)
|
|
91
|
-
if err != nil {
|
|
92
|
-
|
|
90
|
+
return HtmlContent("ErrorPage/AccessDeniedPage/NotFoundPage based on status code"), status, err
|
|
93
|
-
}
|
|
94
|
-
return t.Exec(ctx)
|
|
95
91
|
}
|
http.go
CHANGED
|
@@ -39,41 +39,6 @@ type RouteDefinition struct {
|
|
|
39
39
|
Params interface{} `json:"params"`
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
type HtmlContent string
|
|
43
|
-
type HandlersTemplate struct {
|
|
44
|
-
text string
|
|
45
|
-
ctx *handlebars.Context
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
func Html(tpl string) *HandlersTemplate {
|
|
49
|
-
return &HandlersTemplate{text: tpl, ctx: handlebars.NewContext()}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
func (t *HandlersTemplate) Prop(key string, v any) *HandlersTemplate {
|
|
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])
|
|
61
|
-
}
|
|
62
|
-
return t
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
func (t *HandlersTemplate) Render(args ...any) (HtmlContent, int, error) {
|
|
66
|
-
s, err := handlebars.Render(t.text, t.ctx)
|
|
67
|
-
if err != nil {
|
|
68
|
-
return HtmlContent("Server Erorr"), 500, err
|
|
69
|
-
}
|
|
70
|
-
return HtmlContent(s), 200, nil
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
func HtmlErr(status int, err error) (HtmlContent, int, error) {
|
|
74
|
-
return HtmlContent("ErrorPage/AccessDeniedPage/NotFoundPage based on status code"), status, err
|
|
75
|
-
}
|
|
76
|
-
|
|
77
42
|
func GetFunctionName(temp interface{}) string {
|
|
78
43
|
strs := strings.Split((runtime.FuncForPC(reflect.ValueOf(temp).Pointer()).Name()), ".")
|
|
79
44
|
return strs[len(strs)-1]
|
|
@@ -96,7 +61,6 @@ func RegisterComponent(fn any, props ...string) {
|
|
|
96
61
|
for i := 0; i < structType.NumField(); i++ {
|
|
97
62
|
if f := rv.Field(i); f.CanSet() {
|
|
98
63
|
jsonName := structType.Field(i).Tag.Get("json")
|
|
99
|
-
fmt.Printf("jsonName %s %+v\n", jsonName, help.Context.Get(jsonName))
|
|
100
64
|
if jsonName == "children" {
|
|
101
65
|
s, err := help.Block()
|
|
102
66
|
if err != nil {
|
|
@@ -112,13 +76,13 @@ func RegisterComponent(fn any, props ...string) {
|
|
|
112
76
|
props = rv.Interface()
|
|
113
77
|
}
|
|
114
78
|
res := fnValue.Call(args)
|
|
115
|
-
tpl := res[0].Interface().(*
|
|
79
|
+
tpl := res[0].Interface().(*handlebars.Template)
|
|
116
|
-
tpl.
|
|
80
|
+
tpl.Context.Set("props", props)
|
|
117
|
-
|
|
81
|
+
s, _, err := tpl.Render()
|
|
118
82
|
if err != nil {
|
|
119
83
|
return "", err
|
|
120
84
|
}
|
|
121
|
-
return template.HTML(
|
|
85
|
+
return template.HTML(s), nil
|
|
122
86
|
})
|
|
123
87
|
}
|
|
124
88
|
|
|
@@ -254,7 +218,7 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
|
|
|
254
218
|
RespondError(w, responseStatus, responseError.(error))
|
|
255
219
|
return
|
|
256
220
|
}
|
|
257
|
-
if v, ok := response.(HtmlContent); ok {
|
|
221
|
+
if v, ok := response.(handlebars.HtmlContent); ok {
|
|
258
222
|
w.Header().Set("Content-Type", "text/html")
|
|
259
223
|
// This has to be at end always
|
|
260
224
|
w.WriteHeader(responseStatus)
|