~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.


ac320ee3 Peter John

3 years ago
use gsx
Files changed (6) hide show
  1. gsx/gsx.go +270 -0
  2. gsx/gsx_test.go +92 -0
  3. gsx/parser.go +0 -41
  4. gsx/template.go +0 -196
  5. gsx/template_test.go +0 -70
  6. lib/main.go +0 -133
gsx/gsx.go ADDED
@@ -0,0 +1,270 @@
1
+ package gsx
2
+
3
+ import (
4
+ "bytes"
5
+ "io"
6
+ "reflect"
7
+ "regexp"
8
+ "runtime"
9
+ "strings"
10
+
11
+ "github.com/alecthomas/repr"
12
+ "golang.org/x/net/html"
13
+ "golang.org/x/net/html/atom"
14
+ )
15
+
16
+ var (
17
+ contextNode = &html.Node{
18
+ Type: html.ElementNode,
19
+ Data: "div",
20
+ DataAtom: atom.Lookup([]byte("div")),
21
+ }
22
+ htmlTags = []string{"a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bb", "bdo", "big", "blockquote", "body", "br /", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datagrid", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "embed", "eventsource", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1 to <h6>", "head", "header", "hgroup", "hr /", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "keygen", "label", "legend", "li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"}
23
+ compMap = map[string]ComponentFunc{}
24
+ funcMap = map[string]interface{}{}
25
+ styles = ""
26
+ )
27
+
28
+ type (
29
+ ComponentFunc struct {
30
+ Func interface{}
31
+ Args []string
32
+ }
33
+ Html map[string]interface{}
34
+ Node struct {
35
+ *html.Node
36
+ }
37
+ )
38
+
39
+ func (h Html) Render(tpl string) Node {
40
+ newTpl := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(tpl, "\n", ""), "\r", ""), "\t", "")
41
+ doc, err := html.ParseFragment(bytes.NewBuffer([]byte(newTpl)), contextNode)
42
+ if err != nil {
43
+ panic(err)
44
+ }
45
+ populate(h, doc[0])
46
+ return Node{doc[0]}
47
+ }
48
+
49
+ func (n Node) Write(w io.Writer) error {
50
+ return html.Render(w, n.Node)
51
+ }
52
+
53
+ func (n Node) String() string {
54
+ b := bytes.NewBuffer(nil)
55
+ html.Render(b, n.Node)
56
+ return b.String()
57
+ }
58
+
59
+ func RegisterComponent(name string, f interface{}, args ...string) {
60
+ compMap[name] = ComponentFunc{
61
+ Func: f,
62
+ Args: args,
63
+ }
64
+ }
65
+
66
+ func RegisterFunc(f interface{}) {
67
+ name := getFunctionName(f)
68
+ funcMap[name] = f
69
+ }
70
+
71
+ func Css(v string) string {
72
+ styles += v
73
+ return v
74
+ }
75
+
76
+ func GetStyles() string {
77
+ return styles
78
+ }
79
+
80
+ func getFunctionName(temp interface{}) string {
81
+ strs := strings.Split((runtime.FuncForPC(reflect.ValueOf(temp).Pointer()).Name()), ".")
82
+ return strs[len(strs)-1]
83
+ }
84
+
85
+ func getAttribute(k string, kvs []html.Attribute) string {
86
+ for _, v := range kvs {
87
+ if v.Key == k {
88
+ return v.Val
89
+ }
90
+ }
91
+ return ""
92
+ }
93
+
94
+ func convert(ref string, i interface{}) interface{} {
95
+ switch iv := i.(type) {
96
+ case bool:
97
+ if strings.Contains(ref, "!") {
98
+ return !iv
99
+ } else {
100
+ return iv
101
+ }
102
+ case string:
103
+ return iv
104
+ }
105
+ return nil
106
+ }
107
+
108
+ func subsRef(ctx map[string]interface{}, ref string) interface{} {
109
+ if f, ok := funcMap[ref]; ok {
110
+ return f.(func() string)()
111
+ } else {
112
+ parts := strings.Split(strings.ReplaceAll(ref, "!", ""), ".")
113
+ if len(parts) == 2 {
114
+ if v, ok := ctx[parts[0]]; ok {
115
+ i := reflect.ValueOf(v).Elem().FieldByName(parts[1]).Interface()
116
+ return convert(ref, i)
117
+ }
118
+ }
119
+ return convert(ref, ctx[ref])
120
+ }
121
+ }
122
+
123
+ func populateChildren(n, replaceNode1 *html.Node) {
124
+ if n.Data == "{children}" { // first
125
+ replaceNode1.NextSibling = &html.Node{}
126
+ *replaceNode1.NextSibling = *n.NextSibling
127
+ n.Parent.FirstChild = replaceNode1
128
+ return
129
+ }
130
+ if n.NextSibling != nil {
131
+ if n.NextSibling.Data == "{children}" {
132
+ if n.NextSibling.NextSibling != nil { // middle
133
+ replaceNode1.NextSibling = &html.Node{}
134
+ *replaceNode1.NextSibling = *n.NextSibling.NextSibling
135
+ n.NextSibling = replaceNode1
136
+ }
137
+ if n.NextSibling.PrevSibling != nil { // last
138
+ replaceNode1.PrevSibling = &html.Node{}
139
+ *replaceNode1.PrevSibling = *n.NextSibling.PrevSibling
140
+ n.NextSibling = replaceNode1
141
+ }
142
+ } else {
143
+ populateChildren(n.NextSibling, replaceNode1)
144
+ }
145
+ }
146
+ if n.FirstChild != nil {
147
+ populateChildren(n.FirstChild, replaceNode1)
148
+ }
149
+ }
150
+
151
+ func populate(ctx Html, n *html.Node) {
152
+ if n.Type == html.TextNode {
153
+ // if n.Data != "" {
154
+ // }
155
+ } else if n.Type == html.ElementNode {
156
+ repr.Println("dd", n.Data)
157
+ for i, at := range n.Attr {
158
+ // if len(param.Value.KV) != 0 {
159
+ // values := []string{}
160
+ // for _, kv := range param.Value.KV {
161
+ // if subsRef(ctx, kv.Value) == true {
162
+ // values = append(values, kv.Key)
163
+ // }
164
+ // }
165
+ if at.Val != "" && strings.Contains(at.Val, "{") {
166
+ if at.Key == "class" {
167
+ repr.Println(at)
168
+ } else {
169
+ re := regexp.MustCompile(`{(.*?)}`)
170
+ found := re.FindString(at.Val)
171
+ if found != "" {
172
+ varName := strings.ReplaceAll(strings.ReplaceAll(found, "{", ""), "}", "")
173
+ varValue := subsRef(ctx, varName).(string)
174
+ n.Attr[i] = html.Attribute{
175
+ Namespace: at.Namespace,
176
+ Key: at.Key,
177
+ Val: strings.ReplaceAll(at.Val, found, varValue),
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ if comp, ok := compMap[n.Data]; ok {
184
+ h := Html(ctx)
185
+ args := []reflect.Value{reflect.ValueOf(h)}
186
+ for _, arg := range comp.Args {
187
+ if v, ok := ctx[arg]; ok {
188
+ args = append(args, reflect.ValueOf(v))
189
+ } else {
190
+ v := getAttribute(arg, n.Attr)
191
+ args = append(args, reflect.ValueOf(v))
192
+ }
193
+ }
194
+ result := reflect.ValueOf(comp.Func).Call(args)
195
+ compNode := result[0].Interface().(Node)
196
+ // html.Render(os.Stdout, componentNode)
197
+ // println("")
198
+ // html.Render(os.Stdout, n)
199
+ // println("")
200
+ if n.FirstChild != nil {
201
+ newChild := &html.Node{}
202
+ *newChild = *n.FirstChild
203
+ newChild.Parent = nil
204
+ n.RemoveChild(n.FirstChild)
205
+ populateChildren(compNode.FirstChild, newChild)
206
+ n.AppendChild(compNode.Node)
207
+ }
208
+ }
209
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
210
+ populate(ctx, c)
211
+ }
212
+ }
213
+ }
214
+
215
+ // func render(x *Xml, ctx map[string]interface{}) string {
216
+ // if x.Value != nil {
217
+ // if x.Value.Ref != "" {
218
+ // s += space + " " + subsRef(ctx, x.Value.Ref).(string) + "\n"
219
+ // } else if x.Value.Str != "" {
220
+ // s += space + " " + strings.ReplaceAll(x.Value.Str, `"`, "") + "\n"
221
+ // }
222
+ // }
223
+ // if x.Name == "For" {
224
+ // ctxKey := getAttribute("key", x.Attributes)
225
+ // ctxName := getAttribute("itemKey", x.Attributes)
226
+ // data := ctx[ctxKey]
227
+ // switch reflect.TypeOf(data).Kind() {
228
+ // case reflect.Slice:
229
+ // v := reflect.ValueOf(data)
230
+ // for i := 0; i < v.Len(); i++ {
231
+ // ctx["_space"] = space + " "
232
+ // ctx[ctxName] = v.Index(i).Interface()
233
+ // s += render(x.Children[0], ctx) + "\n"
234
+ // }
235
+ // }
236
+ // } else {
237
+ // if comp, ok := compMap[x.Name]; ok {
238
+ // } else {
239
+ // found := false
240
+ // for _, t := range htmlTags {
241
+ // if t == x.Name {
242
+ // found = true
243
+ // }
244
+ // }
245
+ // if !found {
246
+ // panic(fmt.Errorf("Comp not found %s", x.Name))
247
+ // }
248
+ // for _, c := range x.Children {
249
+ // ctx["_space"] = space + " "
250
+ // s += render(c, ctx) + "\n"
251
+ // }
252
+ // }
253
+ // }
254
+ // s += space + "</" + x.Name + ">"
255
+ // return s
256
+ // }
257
+
258
+ // <script>
259
+ // document.addEventListener('alpine:init', () => {
260
+ // Alpine.store('todos', {
261
+ // list: [],
262
+ // count: 0,
263
+ // })
264
+ // })
265
+ // </script>
266
+
267
+ // patch: {
268
+ // { "op": "add", "path": "/todos/list", "value": { "id": "123", "text": "123" } },
269
+ // }
270
+ //
gsx/gsx_test.go ADDED
@@ -0,0 +1,92 @@
1
+ package gsx
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/stretchr/testify/require"
7
+ )
8
+
9
+ type TodoData struct {
10
+ ID string
11
+ Text string
12
+ Completed bool
13
+ }
14
+
15
+ func Todo(h Html, todo *TodoData) Node {
16
+ return h.Render(`
17
+ <li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
18
+ <div class="view">
19
+ <span>{todo.Text}</span>
20
+ </div>
21
+ {children}
22
+ <div class="count">
23
+ <span>{todo.Completed}</span>
24
+ </div>
25
+ </li>
26
+ `)
27
+ }
28
+
29
+ func WebsiteName() string {
30
+ return "My Website"
31
+ }
32
+
33
+ func TestHtml(t *testing.T) {
34
+ r := require.New(t)
35
+ RegisterComponent("todo", Todo, "todo")
36
+ RegisterFunc(WebsiteName)
37
+ h := Html(map[string]interface{}{
38
+ "todos": []*TodoData{
39
+ {ID: "b1a7359c-ebb4-11ec-8ea0-0242ac120002", Text: "My first todo", Completed: true},
40
+ },
41
+ })
42
+ h["todo"] = &TodoData{ID: "b1a7359c-ebb4-11ec-8ea0-0242ac120002", Text: "My first todo", Completed: true}
43
+ // <template x-for="todo in todos">
44
+ actual := h.Render(`
45
+ <div>
46
+ <div>
47
+ 123
48
+ <Todo key="todo">
49
+ <div class="container">
50
+ <h2>Title</h2>
51
+ <h3>Sub title</h3>
52
+ </div>
53
+ </Todo>
54
+ </div>
55
+ <div>
56
+ Test
57
+ <button>click</button>
58
+ </div>
59
+ </div>
60
+ `).String()
61
+ expected := "<div><div>123<todo key=\"todo\"><li id=\"todo-b1a7359c-ebb4-11ec-8ea0-0242ac120002\" class=\"{ completed: todo.Completed }\"><div class=\"view\"><span>{todo.Text}</span></div><div class=\"container\"><h2>Title</h2><h3>Sub title</h3></div><div class=\"count\"><span>{todo.Completed}</span></div></li></todo></div><div>Test<button>click</button></div></div>"
62
+ // actual := Html(ctx).Render(`
63
+ // <ul id="todo-list" class="relative">
64
+ // <For key="todos" itemKey="todo">
65
+ // <Todo key="todo">
66
+ // <div>"Todo123"</div>
67
+ // </Todo>
68
+ // </For>
69
+ // <span>{WebsiteName}</span>
70
+ // </ul>
71
+ // `)
72
+ // expected := `<ul id="todo-list" class="relative">
73
+ // <For key="todos" itemKey="todo">
74
+ // <Todo key="todo">
75
+ // <li id="b1a7359c-ebb4-11ec-8ea0-0242ac120002" class="completed">
76
+ // <div>
77
+ // Todo123
78
+ // </div>
79
+ // <div class="view">
80
+ // <span>
81
+ // My first todo
82
+ // </span>
83
+ // </div>
84
+ // </li>
85
+ // </Todo>
86
+ // </For>
87
+ // <span>
88
+ // My Website
89
+ // </span>
90
+ // </ul>`
91
+ r.Equal(expected, actual)
92
+ }
gsx/parser.go DELETED
@@ -1,41 +0,0 @@
1
- package gsx
2
-
3
- import (
4
- "github.com/alecthomas/participle/v2"
5
- "github.com/alecthomas/participle/v2/lexer"
6
- )
7
-
8
- type Module struct {
9
- Pos lexer.Position
10
- Nodes []*Xml `@@`
11
- }
12
-
13
- type Attribute struct {
14
- Pos lexer.Position
15
- Key string `@":"? @Ident ( @"-" @Ident )*`
16
- Value *Literal `"=" @@`
17
- }
18
-
19
- type KV struct {
20
- Pos lexer.Position
21
- Key string `@Ident`
22
- Value string `":" @"!"? @Ident ( @"." @Ident )*`
23
- }
24
-
25
- type Literal struct {
26
- Pos lexer.Position
27
- Str string `@String`
28
- Ref string `| "{" @Ident ( @"." @Ident )* "}"`
29
- KV []*KV `| "{""{" @@* "}""}"`
30
- }
31
-
32
- type Xml struct {
33
- Pos lexer.Position
34
- Name string `"<" @Ident`
35
- Attributes []*Attribute `[ @@ { @@ } ]">"`
36
- Children []*Xml `{ @@ }`
37
- Value *Literal `{ @@ }` // Todo make this match with @String or Literal
38
- Close string `("<""/"@Ident">")?`
39
- }
40
-
41
- var xmlParser = participle.MustBuild(&Module{})
gsx/template.go DELETED
@@ -1,196 +0,0 @@
1
- package gsx
2
-
3
- import (
4
- "fmt"
5
- "reflect"
6
- "runtime"
7
- "strings"
8
- )
9
-
10
- type ComponentFunc struct {
11
- Func interface{}
12
- Args []string
13
- }
14
-
15
- type Html map[string]interface{}
16
-
17
- func (h Html) Render(tpl string) string {
18
- tree := &Module{}
19
- err := xmlParser.ParseBytes(tpl[0:10], []byte(tpl), tree)
20
- if err != nil {
21
- panic(err)
22
- }
23
- o := ""
24
- for _, n := range tree.Nodes {
25
- v := render(n, h)
26
- o += v
27
- }
28
- return o
29
- }
30
-
31
- var htmlTags = []string{"a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bb", "bdo", "big", "blockquote", "body", "br /", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datagrid", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "embed", "eventsource", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1 to <h6>", "head", "header", "hgroup", "hr /", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "keygen", "label", "legend", "li", "link", "map", "mark", "menu", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"}
32
- var compMap = map[string]ComponentFunc{}
33
- var funcMap = map[string]interface{}{}
34
-
35
- func getFunctionName(temp interface{}) string {
36
- strs := strings.Split((runtime.FuncForPC(reflect.ValueOf(temp).Pointer()).Name()), ".")
37
- return strs[len(strs)-1]
38
- }
39
-
40
- func RegisterComponent(f interface{}, args ...string) {
41
- name := getFunctionName(f)
42
- compMap[name] = ComponentFunc{
43
- Func: f,
44
- Args: args,
45
- }
46
- }
47
-
48
- func RegisterFunc(f interface{}) {
49
- name := getFunctionName(f)
50
- funcMap[name] = f
51
- }
52
-
53
- var styles = ""
54
-
55
- func Css(v string) string {
56
- styles += v
57
- return v
58
- }
59
-
60
- func GetStyles() string {
61
- return styles
62
- }
63
-
64
- func getAttribute(k string, kvs []*Attribute) string {
65
- for _, param := range kvs {
66
- if param.Key == k {
67
- return strings.ReplaceAll(param.Value.Str, `"`, "")
68
- }
69
- }
70
- return ""
71
- }
72
-
73
- func convert(ref string, i interface{}) interface{} {
74
- switch iv := i.(type) {
75
- case bool:
76
- if strings.Contains(ref, "!") {
77
- return !iv
78
- } else {
79
- return iv
80
- }
81
- case string:
82
- return iv
83
- }
84
- return nil
85
- }
86
-
87
- func subsRef(ctx map[string]interface{}, ref string) interface{} {
88
- if f, ok := funcMap[ref]; ok {
89
- return f.(func() string)()
90
- } else {
91
- parts := strings.Split(strings.ReplaceAll(ref, "!", ""), ".")
92
- if len(parts) == 2 {
93
- if v, ok := ctx[parts[0]]; ok {
94
- i := reflect.ValueOf(v).Elem().FieldByName(parts[1]).Interface()
95
- return convert(ref, i)
96
- }
97
- }
98
- return convert(ref, ctx[ref])
99
- }
100
- }
101
-
102
- func render(x *Xml, ctx map[string]interface{}) string {
103
- space, _ := ctx["_space"].(string)
104
- s := space + "<" + x.Name
105
- if len(x.Attributes) > 0 {
106
- s += " "
107
- }
108
- for i, param := range x.Attributes {
109
- if len(param.Value.KV) != 0 {
110
- values := []string{}
111
- for _, kv := range param.Value.KV {
112
- if subsRef(ctx, kv.Value) == true {
113
- values = append(values, kv.Key)
114
- }
115
- }
116
- s += param.Key + `="` + strings.Join(values, "") + `"`
117
- } else if param.Value.Ref != "" {
118
- s += param.Key + `="` + subsRef(ctx, param.Value.Ref).(string) + `"`
119
- } else {
120
- s += param.Key + "=" + param.Value.Str
121
- }
122
- if i < len(x.Attributes)-1 {
123
- s += " "
124
- }
125
- }
126
- s += ">\n"
127
- if x.Value != nil {
128
- if x.Value.Ref != "" {
129
- s += space + " " + subsRef(ctx, x.Value.Ref).(string) + "\n"
130
- } else if x.Value.Str != "" {
131
- s += space + " " + strings.ReplaceAll(x.Value.Str, `"`, "") + "\n"
132
- }
133
- }
134
- if x.Name == "For" {
135
- ctxKey := getAttribute("key", x.Attributes)
136
- ctxName := getAttribute("itemKey", x.Attributes)
137
- data := ctx[ctxKey]
138
- switch reflect.TypeOf(data).Kind() {
139
- case reflect.Slice:
140
- v := reflect.ValueOf(data)
141
- for i := 0; i < v.Len(); i++ {
142
- ctx["_space"] = space + " "
143
- ctx[ctxName] = v.Index(i).Interface()
144
- s += render(x.Children[0], ctx) + "\n"
145
- }
146
- }
147
- } else {
148
- if comp, ok := compMap[x.Name]; ok {
149
- ctx["_space"] = space + " "
150
- h := Html(ctx)
151
- args := []reflect.Value{reflect.ValueOf(h)}
152
- for _, k := range comp.Args {
153
- if v, ok := ctx[k]; ok {
154
- args = append(args, reflect.ValueOf(v))
155
- } else {
156
- v := getAttribute(k, x.Attributes)
157
- args = append(args, reflect.ValueOf(v))
158
- }
159
- }
160
- if len(x.Children) > 0 {
161
- h["children"] = render(x.Children[0], h)
162
- }
163
- result := reflect.ValueOf(comp.Func).Call(args)
164
- s += result[0].Interface().(string) + "\n"
165
- } else {
166
- found := false
167
- for _, t := range htmlTags {
168
- if t == x.Name {
169
- found = true
170
- }
171
- }
172
- if !found {
173
- panic(fmt.Errorf("Comp not found %s", x.Name))
174
- }
175
- for _, c := range x.Children {
176
- ctx["_space"] = space + " "
177
- s += render(c, ctx) + "\n"
178
- }
179
- }
180
- }
181
- s += space + "</" + x.Name + ">"
182
- return s
183
- }
184
-
185
- // <script>
186
- // document.addEventListener('alpine:init', () => {
187
- // Alpine.store('todos', {
188
- // list: [],
189
- // count: 0,
190
- // })
191
- // })
192
- // </script>
193
-
194
- // patch: {
195
- // { "op": "add", "path": "/todos/list", "value": { "id": "123", "text": "123" } },
196
- // }
gsx/template_test.go DELETED
@@ -1,70 +0,0 @@
1
- package gsx
2
-
3
- import (
4
- "testing"
5
-
6
- "github.com/stretchr/testify/require"
7
- )
8
-
9
- type TodoData struct {
10
- ID string
11
- Text string
12
- Completed bool
13
- }
14
-
15
- func Todo(h Html, todo *TodoData) string {
16
- return h.Render(`
17
- <li id={todo.ID} class={{ completed: todo.Completed }}>
18
- <div class="view">
19
- <span>{todo.Text}</span>
20
- </div>
21
- {children}
22
- </li>
23
- `)
24
- }
25
-
26
- func WebsiteName() string {
27
- return "My Website"
28
- }
29
-
30
- func TestHtml(t *testing.T) {
31
- r := require.New(t)
32
- RegisterComponent(Todo, "todo")
33
- RegisterFunc(WebsiteName)
34
- ctx := map[string]interface{}{
35
- "_space": "",
36
- "todos": []*TodoData{
37
- {ID: "b1a7359c-ebb4-11ec-8ea0-0242ac120002", Text: "My first todo", Completed: true},
38
- },
39
- }
40
- actual := Html(ctx).Render(`
41
- <ul id="todo-list" class="relative">
42
- <For key="todos" itemKey="todo">
43
- <Todo key="todo">
44
- <div>"Todo123"</div>
45
- </Todo>
46
- </For>
47
- <span>{WebsiteName}</span>
48
- </ul>
49
- `)
50
- expected := `<ul id="todo-list" class="relative">
51
- <For key="todos" itemKey="todo">
52
- <Todo key="todo">
53
- <li id="b1a7359c-ebb4-11ec-8ea0-0242ac120002" class="completed">
54
- <div>
55
- Todo123
56
- </div>
57
- <div class="view">
58
- <span>
59
- My first todo
60
- </span>
61
- </div>
62
- </li>
63
- </Todo>
64
- </For>
65
- <span>
66
- My Website
67
- </span>
68
- </ul>`
69
- r.Equal(expected, actual)
70
- }
lib/main.go DELETED
@@ -1,133 +0,0 @@
1
- package main
2
-
3
- import (
4
- "bytes"
5
- "os"
6
- "strings"
7
-
8
- "golang.org/x/net/html"
9
- "golang.org/x/net/html/atom"
10
- )
11
-
12
- type HtmlTag struct {
13
- Tag string
14
- Text string
15
- Attr []html.Attribute
16
- Children []*HtmlTag
17
- }
18
-
19
- var contextNode = &html.Node{
20
- Type: html.ElementNode,
21
- Data: "div",
22
- DataAtom: atom.Lookup([]byte("div")),
23
- }
24
-
25
- func Html(tpl string) *html.Node {
26
- newTpl := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(tpl, "\n", ""), "\r", ""), "\t", "")
27
- doc, err := html.ParseFragment(bytes.NewBuffer([]byte(newTpl)), contextNode)
28
- if err != nil {
29
- panic(err)
30
- }
31
- return doc[0]
32
- }
33
-
34
- func Todo() *html.Node {
35
- return Html(`
36
- <li id="{todo.ID}" class="{ completed: todo.Completed }">
37
- <div class="view">
38
- <span>{todo.Text}</span>
39
- </div>
40
- {children}
41
- <div class="count">
42
- <span>{todo.Count}</span>
43
- </div>
44
- </li>
45
- `)
46
- }
47
-
48
- func walkChildren(n, replaceNode1 *html.Node) {
49
- if n.Data == "{children}" { // first
50
- replaceNode1.NextSibling = &html.Node{}
51
- *replaceNode1.NextSibling = *n.NextSibling
52
- n.Parent.FirstChild = replaceNode1
53
- return
54
- }
55
- if n.NextSibling != nil {
56
- if n.NextSibling.Data == "{children}" {
57
- if n.NextSibling.NextSibling != nil { // middle
58
- replaceNode1.NextSibling = &html.Node{}
59
- *replaceNode1.NextSibling = *n.NextSibling.NextSibling
60
- n.NextSibling = replaceNode1
61
- }
62
- if n.NextSibling.PrevSibling != nil { // last
63
- replaceNode1.PrevSibling = &html.Node{}
64
- *replaceNode1.PrevSibling = *n.NextSibling.PrevSibling
65
- n.NextSibling = replaceNode1
66
- }
67
- } else {
68
- walkChildren(n.NextSibling, replaceNode1)
69
- }
70
- }
71
- if n.FirstChild != nil {
72
- walkChildren(n.FirstChild, replaceNode1)
73
- }
74
- }
75
-
76
- func main() {
77
- // ctx := map[string]interface{}{}
78
- doc := Html(`
79
- <div>
80
- <div>
81
- 123
82
- <Todo id={todo.ID} class="{ completed: todo.Completed }">
83
- <div class="container">
84
- <h2>Title</h2>
85
- <h3>Sub title</h3>
86
- </div>
87
- </Todo>
88
- </div>
89
- <div>
90
- Test
91
- <button>click</button>
92
- </div>
93
- </div>
94
- `)
95
- top := &HtmlTag{}
96
- var f func(parent *HtmlTag, n *html.Node)
97
- f = func(parent *HtmlTag, n *html.Node) {
98
- if n.Type == html.TextNode {
99
- data := strings.TrimSpace(n.Data)
100
- if data != "" {
101
- // data = "{}"
102
- }
103
- } else if n.Type == html.ElementNode {
104
- // repr.Println(n.Data)
105
- if n.Data == "todo" {
106
- componentNode := Todo()
107
- html.Render(os.Stdout, componentNode)
108
- println("")
109
- html.Render(os.Stdout, n)
110
- println("")
111
- if n.FirstChild != nil {
112
- newChild := &html.Node{}
113
- *newChild = *n.FirstChild
114
- newChild.Parent = nil
115
- n.RemoveChild(n.FirstChild)
116
- walkChildren(componentNode.FirstChild, newChild)
117
- n.AppendChild(componentNode)
118
- }
119
- }
120
- // newParent := &HtmlTag{
121
- // Tag: n.Data,
122
- // Attr: n.Attr,
123
- // }
124
- // parent.Children = append(parent.Children, newParent)
125
- for c := n.FirstChild; c != nil; c = c.NextSibling {
126
- f(nil, c)
127
- }
128
- }
129
- }
130
- f(top, doc)
131
- // repr.Println(top)
132
- html.Render(os.Stdout, doc)
133
- }