~repos /gromer

#golang#htmx#ssr

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.


0212dc91 Peter John

3 years ago
improve template
template/parser.go CHANGED
@@ -7,27 +7,35 @@ import (
7
7
 
8
8
  type Module struct {
9
9
  Pos lexer.Position
10
- Nodes []*Xml `parser:"{ @@ }"`
10
+ Nodes []*Xml `@@`
11
11
  }
12
12
 
13
- type KeyValue struct {
13
+ type Attribute struct {
14
14
  Pos lexer.Position
15
15
  Key string `parser:"@\":\"? @Ident ( @\"-\" @Ident )*"`
16
16
  Value *Literal `parser:"\"=\" @@"`
17
17
  }
18
18
 
19
+ type KV struct {
20
+ Pos lexer.Position
21
+ Key string `@Ident`
22
+ Value string `":" @"!"? @Ident ( @"." @Ident )*`
23
+ }
24
+
19
25
  type Literal struct {
26
+ Pos lexer.Position
20
- Str string `parser:"@String"`
27
+ Str string `@String`
21
- Ref string `parser:"| @\"{\" @Ident ( @\".\" @Ident )* @\"}\""`
28
+ Ref string `| "{" @Ident ( @"." @Ident )* "}"`
29
+ KV []*KV `| "{""{" @@* "}""}"`
22
30
  }
23
31
 
24
32
  type Xml struct {
25
33
  Pos lexer.Position
26
- Name string `parser:"\"<\"@Ident"`
34
+ Name string `parser:"\"<\"@Ident"`
27
- Parameters []*KeyValue `parser:"[ @@ { @@ } ] \">\""`
35
+ Attributes []*Attribute `parser:"[ @@ { @@ } ] \">\""`
28
- Children []*Xml `parser:"{ @@ }"`
36
+ Children []*Xml `parser:"{ @@ }"`
29
- Value *Literal `parser:"{ @@ }"` // Todo make this match with @String or Literal
37
+ Value *Literal `parser:"{ @@ }"` // Todo make this match with @String or Literal
30
- Close string `parser:"\"<\"\"/\"@Ident\">\""`
38
+ Close string `parser:"\"<\"\"/\"@Ident\">\""`
31
39
  }
32
40
 
33
41
  var xmlParser = participle.MustBuild(&Module{})
template/template.go CHANGED
@@ -5,12 +5,19 @@ import (
5
5
  "reflect"
6
6
  "runtime"
7
7
  "strings"
8
+
9
+ "github.com/alecthomas/repr"
8
10
  )
9
11
 
12
+ type ComponentFunc struct {
13
+ Func interface{}
14
+ Args []string
15
+ }
16
+
10
- type Component func(map[string]interface{}) string
17
+ type Html func(string) string
11
18
 
12
19
  var htmlTags = []string{"ul", "li", "span", "div"}
13
- var compMap = map[string]interface{}{}
20
+ var compMap = map[string]ComponentFunc{}
14
21
  var funcMap = map[string]interface{}{}
15
22
 
16
23
  func getFunctionName(temp interface{}) string {
@@ -18,9 +25,12 @@ func getFunctionName(temp interface{}) string {
18
25
  return strs[len(strs)-1]
19
26
  }
20
27
 
21
- func RegisterComponent(f interface{}) {
28
+ func RegisterComponent(f interface{}, args ...string) {
22
29
  name := getFunctionName(f)
23
- compMap[name] = f
30
+ compMap[name] = ComponentFunc{
31
+ Func: f,
32
+ Args: args,
33
+ }
24
34
  }
25
35
 
26
36
  func RegisterFunc(f interface{}) {
@@ -28,7 +38,7 @@ func RegisterFunc(f interface{}) {
28
38
  funcMap[name] = f
29
39
  }
30
40
 
31
- func getAttribute(k string, kvs []*KeyValue) string {
41
+ func getAttribute(k string, kvs []*Attribute) string {
32
42
  for _, param := range kvs {
33
43
  if param.Key == k {
34
44
  return strings.ReplaceAll(param.Value.Str, `"`, "")
@@ -37,37 +47,64 @@ func getAttribute(k string, kvs []*KeyValue) string {
37
47
  return ""
38
48
  }
39
49
 
50
+ func subsRef(ctx map[string]interface{}, ref string) interface{} {
51
+ if f, ok := funcMap[ref]; ok {
52
+ return f.(func() string)()
53
+ } else {
54
+ parts := strings.Split(strings.ReplaceAll(ref, "!", ""), ".")
55
+ if len(parts) == 2 {
56
+ if v, ok := ctx[parts[0]]; ok {
57
+ i := reflect.ValueOf(v).Elem().FieldByName(parts[1]).Interface()
58
+ switch iv := i.(type) {
59
+ case bool:
60
+ if strings.Contains(ref, "!") {
61
+ return !iv
62
+ } else {
63
+ return iv
64
+ }
65
+ case string:
66
+ return iv
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return nil
72
+ }
73
+
40
74
  func Render(x *Xml, ctx map[string]interface{}) string {
41
75
  space, _ := ctx["_space"].(string)
42
76
  s := space + "<" + x.Name
43
- if len(x.Parameters) > 0 {
77
+ if len(x.Attributes) > 0 {
44
78
  s += " "
45
79
  }
46
- for i, param := range x.Parameters {
80
+ for i, param := range x.Attributes {
81
+ if len(param.Value.KV) != 0 {
82
+ values := []string{}
83
+ for _, kv := range param.Value.KV {
84
+ if subsRef(ctx, kv.Value) == true {
85
+ values = append(values, kv.Key)
86
+ }
87
+ }
88
+ s += param.Key + `="` + strings.Join(values, "") + `"`
89
+ repr.Println(s)
90
+ } else if param.Value.Ref != "" {
91
+ s += param.Key + `="` + subsRef(ctx, param.Value.Ref).(string) + `"`
92
+ } else {
47
- s += param.Key + "=" + param.Value.Str
93
+ s += param.Key + "=" + param.Value.Str
94
+ }
48
- if i < len(x.Parameters)-1 {
95
+ if i < len(x.Attributes)-1 {
49
96
  s += " "
50
97
  }
51
98
  }
52
99
  s += ">\n"
53
100
  if x.Value != nil {
54
101
  if x.Value.Ref != "" {
55
- key := strings.ReplaceAll(strings.ReplaceAll(x.Value.Ref, "{", ""), "}", "")
102
+ s += space + " " + subsRef(ctx, x.Value.Ref).(string) + "\n"
56
- if f, ok := funcMap[key]; ok {
57
- s += f.(func() string)()
58
- } else {
59
- parts := strings.Split(key, ".")
60
- if len(parts) == 2 {
61
- if v, ok := ctx[parts[0]]; ok {
62
- s += reflect.ValueOf(v).Elem().FieldByName(parts[1]).Interface().(string)
63
- }
64
- }
65
- }
66
103
  }
67
104
  }
68
105
  if x.Name == "For" {
69
- ctxKey := getAttribute("key", x.Parameters)
106
+ ctxKey := getAttribute("key", x.Attributes)
70
- ctxName := getAttribute("name", x.Parameters)
107
+ ctxName := getAttribute("itemKey", x.Attributes)
71
108
  data := ctx[ctxKey]
72
109
  switch reflect.TypeOf(data).Kind() {
73
110
  case reflect.Slice:
@@ -75,14 +112,24 @@ func Render(x *Xml, ctx map[string]interface{}) string {
75
112
  for i := 0; i < v.Len(); i++ {
76
113
  ctx["_space"] = space + " "
77
114
  ctx[ctxName] = v.Index(i).Interface()
78
- s += Render(x.Children[0], ctx)
115
+ s += Render(x.Children[0], ctx) + "\n"
79
116
  }
80
117
  }
81
118
  } else {
82
119
  if comp, ok := compMap[x.Name]; ok {
120
+ ctx["_space"] = space + " "
121
+ h := HtmlFunc(ctx)
122
+ args := []reflect.Value{reflect.ValueOf(h)}
123
+ for _, k := range comp.Args {
124
+ if v, ok := ctx[k]; ok {
125
+ args = append(args, reflect.ValueOf(v))
126
+ } else {
83
- ctxKey := getAttribute("key", x.Parameters)
127
+ v := getAttribute(k, x.Attributes)
128
+ args = append(args, reflect.ValueOf(v))
129
+ }
130
+ }
84
- result := reflect.ValueOf(comp).Call([]reflect.Value{reflect.ValueOf(ctx[ctxKey])})
131
+ result := reflect.ValueOf(comp.Func).Call(args)
85
- s += result[0].Interface().(string)
132
+ s += result[0].Interface().(string) + "\n"
86
133
  } else {
87
134
  found := false
88
135
  for _, t := range htmlTags {
@@ -103,18 +150,20 @@ func Render(x *Xml, ctx map[string]interface{}) string {
103
150
  return s
104
151
  }
105
152
 
106
- func Html(ctx map[string]interface{}, tpl string) string {
153
+ func HtmlFunc(ctx map[string]interface{}) Html {
154
+ return func(tpl string) string {
107
- tree := &Module{}
155
+ tree := &Module{}
108
- err := xmlParser.ParseBytes("filename", []byte(tpl), tree)
156
+ err := xmlParser.ParseBytes("filename", []byte(tpl), tree)
109
- if err != nil {
157
+ if err != nil {
110
- panic(err)
158
+ panic(err)
111
- }
159
+ }
112
- o := ""
160
+ o := ""
113
- for _, n := range tree.Nodes {
161
+ for _, n := range tree.Nodes {
114
- v := Render(n, ctx)
162
+ v := Render(n, ctx)
115
- o += v
163
+ o += v
164
+ }
165
+ return o
116
166
  }
117
- return o
118
167
  }
119
168
 
120
169
  // <script>
template/template_test.go CHANGED
@@ -7,16 +7,14 @@ import (
7
7
  )
8
8
 
9
9
  type TodoData struct {
10
- ID string
10
+ ID string
11
- Text string
11
+ Text string
12
+ Completed bool
12
13
  }
13
14
 
14
- func Todo(data *TodoData) string {
15
+ func Todo(html Html, todo *TodoData) string {
15
- ctx := map[string]interface{}{
16
- "todo": data,
17
- }
18
- return Html(ctx, `
16
+ return html(`
19
- <li id={todo.ID} :class="{ 'completed': todo.Completed }">
17
+ <li id={todo.ID} class={{ completed: todo.Completed }}>
20
18
  <div class="view">
21
19
  <span>{todo.Text}</span>
22
20
  </div>
@@ -30,17 +28,17 @@ func WebsiteName() string {
30
28
 
31
29
  func TestHtml(t *testing.T) {
32
30
  r := require.New(t)
33
- RegisterComponent(Todo)
31
+ RegisterComponent(Todo, "todo")
34
32
  RegisterFunc(WebsiteName)
35
33
  ctx := map[string]interface{}{
36
34
  "_space": "",
37
35
  "todos": []*TodoData{
38
- {ID: "b1a7359c-ebb4-11ec-8ea0-0242ac120002", Text: "My first todo"},
36
+ {ID: "b1a7359c-ebb4-11ec-8ea0-0242ac120002", Text: "My first todo", Completed: true},
39
37
  },
40
38
  }
41
- actual := Html(ctx, `
39
+ actual := HtmlFunc(ctx)(`
42
40
  <ul id="todo-list" class="relative">
43
- <For key="todos" name="todo">
41
+ <For key="todos" itemKey="todo">
44
42
  <Todo key="todo"></Todo>
45
43
  </For>
46
44
  <span>{WebsiteName}</span>