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


5050bbb1 Peter John

3 years ago
remove handlebars
_example/components/page.go CHANGED
@@ -1,9 +1,7 @@
1
1
  package components
2
2
 
3
3
  import (
4
- "html/template"
5
-
6
- . "github.com/pyros2097/gromer/handlebars"
4
+ . "github.com/pyros2097/gromer/gsx"
7
5
  )
8
6
 
9
7
  var _ = Css(`
@@ -219,31 +217,26 @@ var _ = Css(`
219
217
  }
220
218
  `)
221
219
 
222
- type PageProps struct {
223
- Title string `json:"title"`
224
- Children template.HTML `json:"children"`
225
- }
226
-
227
- func Page(props PageProps) *Template {
220
+ func Page(h Html, title string) string {
228
- return Html(`
221
+ return h.Render(`
229
222
  <!DOCTYPE html>
230
223
  <html lang="en">
231
224
  <head>
232
225
  <meta charset="UTF-8" />
233
226
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
234
227
  <meta content="utf-8" http-equiv="encoding" />
235
- <title>{{ props.Title }}</title>
228
+ <title>{title}</title>
236
- <meta name="description" content="{{ props.Title }}" />
229
+ <meta name="description" content="{title}" />
237
230
  <meta name="author" content="pyrossh" />
238
231
  <meta name="keywords" content="pyros.sh, pyrossh, gromer" />
239
232
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover" />
240
- <link rel="icon" href="{{ GetAssetUrl "images/icon.png" }}" />
233
+ <link rel="icon" href="{GetAssetUrl "images/icon.png"}" />
241
- <link rel="stylesheet" href="{{ GetStylesUrl }}" />
234
+ <link rel="stylesheet" href="{GetStylesUrl}" />
242
- <script src="{{ GetAlpineJsUrl }}"></script>
235
+ <script src="{GetAlpineJsUrl}"></script>
243
- <script src="{{ GetHtmxJsUrl }}" defer=""></script>
236
+ <script src="{GetHtmxJsUrl}" defer=""></script>
244
237
  </head>
245
238
  <body>
246
- {{ props.Children }}
239
+ {children}
247
240
  </body>
248
241
  </html>
249
242
  `)
_example/components/todo.go CHANGED
@@ -2,33 +2,30 @@ package components
2
2
 
3
3
  import (
4
4
  "github.com/pyros2097/gromer/_example/services/todos"
5
- . "github.com/pyros2097/gromer/handlebars"
5
+ . "github.com/pyros2097/gromer/gsx"
6
6
  )
7
7
 
8
8
  var _ = Css(`
9
9
  `)
10
10
 
11
- type TodoProps struct {
12
- Todo *todos.Todo `json:"todo"`
13
- }
14
-
15
- func Todo(props TodoProps) *Template {
11
+ func Todo(h Html, todo *todos.Todo) string {
16
- return Html(`
12
+ return h.Render(`
17
- <li id="todo-{{ props.Todo.ID }}" {{#if props.Todo.Completed }} class="completed" {{/if}}>
13
+ <li id="todo-{todo.ID}" class={{ completed: todo.Completed }}>
18
14
  <div class="view">
19
- <form hx-target="#todo-{{ props.Todo.ID }}" hx-swap="outerHTML">
15
+ <form hx-target="#todo-{todo.ID}" hx-swap="outerHTML">
20
16
  <input type="hidden" name="intent" value="complete" />
21
- <input type="hidden" name="id" value="{{ props.Todo.ID }}" />
17
+ <input type="hidden" name="id" value="{todo.ID}" />
22
- <input hx-post="/" class="checkbox" type="checkbox" {{#if props.Todo.Completed }} checked="" {{/if}} />
18
+ <input hx-post="/" class="checkbox" type="checkbox" checked={{ completed: todo.Completed }} />
23
19
  </form>
24
- <label>{{ props.Todo.Text }}</label>
20
+ <label>{todo.Text}</label>
25
- <!-- <label hx-get="/todos/edit/{{ props.Todo.ID }}" hx-target="#todo-{{ props.Todo.ID }}" hx-swap="outerHTML">{{ props.Todo.Text }}</label> -->
26
- <form hx-post="/" hx-target="#todo-{{ props.Todo.ID }}" hx-swap="delete">
21
+ <form hx-post="/" hx-target="#todo-{todo.ID}" hx-swap="delete">
27
22
  <input type="hidden" name="intent" value="delete" />
28
- <input type="hidden" name="id" value="{{ props.Todo.ID }}" />
23
+ <input type="hidden" name="id" value="{todo.ID}" />
29
24
  <button class="destroy"></button>
30
25
  </form>
31
26
  </div>
32
27
  </li>
33
28
  `)
34
29
  }
30
+
31
+ // <!-- <label hx-get="/todos/edit/{todo.ID}" hx-target="#todo-{todo.ID}" hx-swap="outerHTML">{{ props.Todo.Text }}</label> -->
_example/containers/TodoCount.go CHANGED
@@ -3,9 +3,8 @@ package containers
3
3
  import (
4
4
  "context"
5
5
 
6
- . "github.com/pyros2097/gromer"
7
6
  "github.com/pyros2097/gromer/_example/services/todos"
8
- . "github.com/pyros2097/gromer/handlebars"
7
+ . "github.com/pyros2097/gromer/gsx"
9
8
  )
10
9
 
11
10
  var _ = Css(`
@@ -19,23 +18,18 @@ var _ = Css(`
19
18
  }
20
19
  `)
21
20
 
22
- type TodoCountProps struct {
23
- Page int `json:"page"`
24
- Filter string `json:"filter"`
25
- }
26
-
27
- func TodoCount(ctx context.Context, props TodoCountProps) (*Template, error) {
21
+ func TodoCount(h Html, ctx context.Context, filter string) (string, error) {
28
- index := Default(props.Page, 1)
29
22
  todos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
30
- Filter: props.Filter,
23
+ Filter: filter,
31
- Limit: index,
24
+ Limit: 1000,
32
25
  })
33
26
  if err != nil {
34
- return nil, err
27
+ return "", err
35
28
  }
29
+ h["count"] = len(todos)
36
- return Html(`
30
+ return h.Render(`
37
31
  <span class="todo-count" id="todo-count" hx-swap-oob="true">
38
- <strong>{{ count }}</strong> item left
32
+ <strong>{count}</strong> item left
39
33
  </span>
40
- `).Prop("count", len(todos)), nil
34
+ `), nil
41
35
  }
_example/containers/TodoList.go CHANGED
@@ -5,7 +5,7 @@ import (
5
5
 
6
6
  . "github.com/pyros2097/gromer"
7
7
  "github.com/pyros2097/gromer/_example/services/todos"
8
- . "github.com/pyros2097/gromer/handlebars"
8
+ . "github.com/pyros2097/gromer/gsx"
9
9
  )
10
10
 
11
11
  var _ = Css(`
@@ -135,20 +135,21 @@ type TodoListProps struct {
135
135
  Filter string `json:"filter"`
136
136
  }
137
137
 
138
- func TodoList(ctx context.Context, props TodoListProps) (*Template, error) {
138
+ func TodoList(h Html, ctx context.Context, props TodoListProps) (string, error) {
139
139
  index := Default(props.Page, 1)
140
140
  todos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
141
141
  Filter: props.Filter,
142
142
  Limit: index,
143
143
  })
144
144
  if err != nil {
145
- return nil, err
145
+ return "", err
146
146
  }
147
+ h["todos"] = todos
147
- return Html(`
148
+ return h.Render(`
148
149
  <ul id="todo-list" class="relative">
149
- {{#each todos as |todo|}}
150
+ <For key="todos" itemKey="todo">
150
- {{#Todo todo=todo}}{{/Todo}}
151
+ <Todo key="todo"></Todo>
151
- {{/each}}
152
+ </For>
152
153
  </ul>
153
- `).Prop("todos", todos), nil
154
+ `), nil
154
155
  }
_example/main.go CHANGED
@@ -13,15 +13,16 @@ import (
13
13
  "github.com/pyros2097/gromer/_example/pages/404"
14
14
  "github.com/pyros2097/gromer/_example/pages"
15
15
  "github.com/pyros2097/gromer/_example/pages/about"
16
+ "github.com/pyros2097/gromer/gsx"
16
17
 
17
18
  )
18
19
 
19
20
  func init() {
20
- gromer.RegisterComponent(components.Page)
21
+ gsx.RegisterComponent(components.Page)
21
- gromer.RegisterComponent(components.Todo)
22
+ gsx.RegisterComponent(components.Todo)
22
23
 
23
- gromer.RegisterContainer(containers.TodoCount)
24
+ gsx.RegisterComponent(containers.TodoCount)
24
- gromer.RegisterContainer(containers.TodoList)
25
+ gsx.RegisterComponent(containers.TodoList)
25
26
  gromer.RegisterAssets(assets.FS)
26
27
  }
27
28
 
@@ -38,7 +39,7 @@ func main() {
38
39
  gromer.StylesRoute(staticRouter, "/styles.css")
39
40
 
40
41
  pageRouter := baseRouter.NewRoute().Subrouter()
41
- gromer.ApiExplorerRoute(pageRouter, "/explorer")
42
+ // gromer.ApiExplorerRoute(pageRouter, "/explorer")
42
43
  gromer.Handle(pageRouter, "GET", "/", pages.GET)
43
44
  gromer.Handle(pageRouter, "POST", "/", pages.POST)
44
45
  gromer.Handle(pageRouter, "GET", "/about", about.GET)
_example/pages/404/get.go CHANGED
@@ -3,11 +3,11 @@ package not_found_404
3
3
  import (
4
4
  "context"
5
5
 
6
- . "github.com/pyros2097/gromer/handlebars"
6
+ . "github.com/pyros2097/gromer/gsx"
7
7
  )
8
8
 
9
- func GET(c context.Context) (HtmlContent, int, error) {
9
+ func GET(h Html, c context.Context) (string, int, error) {
10
- return Html(`
10
+ return h.Render(`
11
11
  {{#Page title="Page Not Found"}}
12
12
  {{#Header}}{{/Header}}
13
13
  <main class="box center">
@@ -17,5 +17,5 @@ func GET(c context.Context) (HtmlContent, int, error) {
17
17
  </h1>
18
18
  </main>
19
19
  {{/Page}}
20
- `).RenderWithStatus(404)
20
+ `), 404, nil
21
21
  }
_example/pages/about/get.go CHANGED
@@ -3,11 +3,11 @@ package about
3
3
  import (
4
4
  "context"
5
5
 
6
- . "github.com/pyros2097/gromer/handlebars"
6
+ . "github.com/pyros2097/gromer/gsx"
7
7
  )
8
8
 
9
- func GET(c context.Context) (HtmlContent, int, error) {
9
+ func GET(h Html, c context.Context) (string, int, error) {
10
- return Html(`
10
+ return h.Render(`
11
11
  {{#Page title="About me"}}
12
12
  <div class="flex flex-col justify-center items-center">
13
13
  {{#Header}}
@@ -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
- `).Render()
19
+ `), 200, nil
20
20
  }
_example/pages/get.go CHANGED
@@ -4,7 +4,7 @@ import (
4
4
  "context"
5
5
 
6
6
  _ "github.com/pyros2097/gromer/_example/components"
7
- . "github.com/pyros2097/gromer/handlebars"
7
+ . "github.com/pyros2097/gromer/gsx"
8
8
  )
9
9
 
10
10
  var _ = Css(`
@@ -61,24 +61,26 @@ type GetParams struct {
61
61
  Filter string `json:"filter"`
62
62
  }
63
63
 
64
- func GET(ctx context.Context, params GetParams) (HtmlContent, int, error) {
64
+ func GET(h Html, ctx context.Context, params GetParams) (string, int, error) {
65
+ h["page"] = params.Page
66
+ h["filter"] = params.Filter
65
- return Html(`
67
+ return h.Render(`
66
- {{#Page title="gromer example"}}
68
+ <Page title="gromer example">
67
69
  <section class="todoapp">
68
70
  <header class="header">
69
71
  <h1>todos</h1>
70
72
  <form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
71
- <input type="hidden" name="intent" value="create" />
73
+ <input type="hidden" name="intent" value="create"></input>
72
- <input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off">
74
+ <input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off"></input>
73
75
  </form>
74
76
  </header>
75
77
  <section class="main">
76
78
  <input class="toggle-all" id="toggle-all" type="checkbox">
77
79
  <label for="toggle-all">Mark all as complete</label>
78
- {{#TodoList id="todo-list" page=page filter=filter }}{{/TodoList}}
80
+ <TodoList id="todo-list" page={page} filter={filter}></TodoList>
79
81
  </section>
80
82
  <footer class="footer">
81
- {{#TodoCount page=page filter=filter}}{{/TodoCount}}
83
+ <TodoCount filter={filter}></TodoCount>
82
84
  <ul class="filters">
83
85
  <li>
84
86
  <a href="?filter=all">All</a>
@@ -96,9 +98,6 @@ func GET(ctx context.Context, params GetParams) (HtmlContent, int, error) {
96
98
  </form>
97
99
  </footer>
98
100
  </section>
99
- {{/Page}}
101
+ </Page>
100
- `).
101
- Prop("page", params.Page).
102
- Prop("filter", params.Filter).
103
- Render()
102
+ `), 200, nil
104
103
  }
_example/pages/post.go CHANGED
@@ -6,7 +6,7 @@ import (
6
6
 
7
7
  _ "github.com/pyros2097/gromer/_example/components"
8
8
  "github.com/pyros2097/gromer/_example/services/todos"
9
- . "github.com/pyros2097/gromer/handlebars"
9
+ . "github.com/pyros2097/gromer/gsx"
10
10
  )
11
11
 
12
12
  type PostParams struct {
@@ -15,61 +15,59 @@ type PostParams struct {
15
15
  Text string `json:"text"`
16
16
  }
17
17
 
18
- func POST(ctx context.Context, params PostParams) (HtmlContent, int, error) {
18
+ func POST(h Html, ctx context.Context, params PostParams) (string, int, error) {
19
19
  if params.Intent == "clear_completed" {
20
20
  allTodos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
21
21
  Filter: "all",
22
22
  Limit: 1000,
23
23
  })
24
24
  if err != nil {
25
- return HtmlErr(500, err)
25
+ return "", 500, err
26
26
  }
27
27
  for _, t := range allTodos {
28
28
  if t.Completed {
29
29
  _, err := todos.DeleteTodo(ctx, t.ID)
30
30
  if err != nil {
31
- return HtmlErr(500, err)
31
+ return "", 500, err
32
32
  }
33
33
  }
34
34
  }
35
- return Html(`
35
+ return h.Render(`
36
- {{#TodoList id="todo-list" page=1 filter="all"}}{{/TodoList}}
36
+ <TodoList id="todo-list" filter="all" page="1"></TodoList>
37
- {{#TodoCount filter="all" page=1}}{{/TodoCount}}
37
+ <TodoCount filter="all" page="1"></TodoCount>
38
- `).Render()
38
+ `), 200, nil
39
39
  } else if params.Intent == "create" {
40
40
  todo, err := todos.CreateTodo(ctx, params.Text)
41
41
  if err != nil {
42
- return HtmlErr(500, err)
42
+ return "", 500, err
43
43
  }
44
+ h["todo"] = todo
44
- return Html(`
45
+ return h.Render(`
45
- {{#Todo todo=todo}}{{/Todo}}
46
+ <Todo todo=todo></Todo>
46
- {{#TodoCount filter="all" page=1}}{{/TodoCount}}
47
+ <TodoCount filter="all" page=1></TodoCount>
47
- `).
48
- Prop("todo", todo).
49
- Render()
48
+ `), 200, nil
50
49
  } else if params.Intent == "delete" {
51
50
  _, err := todos.DeleteTodo(ctx, params.ID)
52
51
  if err != nil {
53
- return HtmlErr(500, err)
52
+ return "", 500, err
54
53
  }
55
- return HtmlEmpty()
54
+ return "", 200, nil
56
55
  } else if params.Intent == "complete" {
57
56
  todo, err := todos.GetTodo(ctx, params.ID)
58
57
  if err != nil {
59
- return HtmlErr(500, err)
58
+ return "", 500, err
60
59
  }
61
60
  _, err = todos.UpdateTodo(ctx, params.ID, todos.UpdateTodoParams{
62
61
  Text: todo.Text,
63
62
  Completed: !todo.Completed,
64
63
  })
65
64
  if err != nil {
66
- return HtmlErr(500, err)
65
+ return "", 500, err
67
66
  }
67
+ h["todo"] = todo
68
- return Html(`
68
+ return h.Render(`
69
69
  {{#Todo todo=todo}}{{/Todo}}
70
- `).
71
- Prop("todo", todo).
72
- Render()
70
+ `), 200, nil
73
71
  }
74
- return HtmlErr(404, fmt.Errorf("Intent not specified: %s", params.Intent))
72
+ return "", 404, fmt.Errorf("Intent not specified: %s", params.Intent)
75
73
  }
api_explorer.go DELETED
@@ -1,275 +0,0 @@
1
- package gromer
2
-
3
- import (
4
- "encoding/json"
5
- "html/template"
6
- "net/http"
7
- "strings"
8
-
9
- "github.com/carlmjohnson/versioninfo"
10
- "github.com/gorilla/mux"
11
- "github.com/pyros2097/gromer/assets"
12
- . "github.com/pyros2097/gromer/handlebars"
13
- )
14
-
15
- func ApiExplorerRoute(router *mux.Router, path string) {
16
- router.Path(path).Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17
- cmcss, _ := assets.FS.ReadFile("css/codemirror@5.63.1.css")
18
- stylescss, _ := assets.FS.ReadFile("css/styles.css")
19
- cmjs, _ := assets.FS.ReadFile("js/codemirror@5.63.1.min.js")
20
- cmjsjs, _ := assets.FS.ReadFile("js/codemirror-javascript@5.63.1.js")
21
- apiRoutes := []RouteDefinition{}
22
- for _, v := range RouteDefs {
23
- if strings.Contains(v.Path, "/api/") {
24
- apiRoutes = append(apiRoutes, v)
25
- }
26
- }
27
- apiData, err := json.Marshal(apiRoutes)
28
- if err != nil {
29
- RespondError(w, 500, err)
30
- }
31
- status, err := Html(`
32
- <!DOCTYPE html>
33
- <html lang="en">
34
- <head>
35
- <meta charset="UTF-8">
36
- <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
37
- <meta http-equiv="encoding" content="utf-8">
38
- <title> API Explorer </title>
39
- <meta name="description" content="API Explorer">
40
- <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover">
41
- <style>
42
- {{ css }}
43
- </style>
44
- <script>
45
- {{ js }}
46
- </script>
47
- </head>
48
- <body>
49
- <div class="flex flex-col">
50
- <div class="flex w-full p-2 bg-gray-50 border-b border-gray-200 items-center justify-start">
51
- <div class="flex mr-4 text-gray-700 text-2xl font-bold"> API Explorer</div>
52
- <div class="text-xl">
53
- <select id="api-select" class="form-select block">
54
- {{#each routes as |route|}}
55
- <option value="{{ @index }}">
56
- <div> {{ route.Method }} {{ route.Path }} </div>
57
- </option>
58
- {{/each}}
59
- </select>
60
- </div>
61
- <div class="flex ml-3 mr-3">
62
- <button id="run" class="bg-gray-200 border border-gray-400 hover:bg-gray-200 focus:outline-none rounded-md text-gray-700 text-md font-bold pt-2 pb-2 pl-6 pr-6"> RUN </button>
63
- </div>
64
- <div class="flex flex-1 justify-end">
65
- (commit {{ commit }})
66
- </div>
67
- </div>
68
- <div class="flex">
69
- <div class="flex flex-row" style="width: 50%;;">
70
- <div class="pr-8 border-r border-gray-300" style="background: #f7f7f7;;"></div>
71
- <div class="w-full">
72
- <div class="text-gray-700 text-sm font-bold uppercase pl-2 pt-2 pb-2 bg-gray-50 border-b border-gray-200"> Headers </div>
73
- <table id="headersTable">
74
- <tr>
75
- <td>
76
- <input class="w-full p-1" value="Authorization">
77
- </td>
78
- <td>
79
- <input class="w-full p-1">
80
- </td>
81
- </tr>
82
- </table>
83
- <div class="text-gray-700 text-sm font-bold uppercase pl-2 pt-2 pb-2 bg-gray-50 border-b border-gray-200"> Path Params </div>
84
- <table id="pathParamsTable">
85
- <tr>
86
- <td class="text-gray-700" style="width: 50%;;">
87
- <div class="p-1"> 123 </div>
88
- </td>
89
- <td style="width: 50%;;">
90
- <input class="w-full p-1">
91
- </td>
92
- </tr>
93
- </table>
94
- <div class="text-gray-700 text-sm font-bold uppercase pl-2 pt-2 pb-2 bg-gray-50 border-b border-gray-200"> Query Params </div>
95
- <table id="queryParamsTable">
96
- <tr>
97
- <td class="text-gray-700" style="width: 50%;;">
98
- <div class="p-1"> 123 </div>
99
- </td>
100
- <td style="width: 50%;;">
101
- <input class="w-full p-1">
102
- </td>
103
- </tr>
104
- </table>
105
- <div class="text-gray-700 text-sm font-bold uppercase pl-2 pt-2 pb-2 bg-gray-50 border-b border-gray-200"> Body </div>
106
- <div id="left" class="border-b border-gray-200 text-md"></div>
107
- </div>
108
- </div>
109
- <div class="flex flex-row" style="width: 50%;;">
110
- <div id="right" class="w-full border-l border-l-gray-200 text-md"></div>
111
- <div class="pr-8 border-l border-gray-300" style="background: #f7f7f7;;"></div>
112
- </div>
113
- </div>
114
- </div>
115
- <script>
116
- window.apiDefs = {{ apiData }}
117
- </script>
118
- <script>
119
- window.codeLeft = CodeMirror(document.getElementById('left'), {
120
- value: '{}',
121
- mode: 'javascript'
122
- })
123
- </script>
124
- <script>
125
- window.codeRight = CodeMirror(document.getElementById('right'), {
126
- value: '',
127
- mode: 'javascript',
128
- lineNumbers: true,
129
- readOnly: true,
130
- lineWrapping: true
131
- })
132
- </script>
133
- <script>
134
- const getCurrentApiCall = () => {
135
- const index = document.getElementById("api-select").value;
136
- return window.apiDefs[index];
137
- }
138
- const updatePathParams = (apiCall) => {
139
- const table = document.getElementById("pathParamsTable");
140
- if (apiCall.pathParams.length === 0) {
141
- table.innerHTML = " <div style='background-color: rgb(245, 245, 245); padding: 0.25rem; text-align: center; color: gray;'>NONE</div>";
142
- } else {
143
- table.innerHTML = "";
144
- }
145
- for (const param of apiCall.pathParams.reverse()) {
146
- const row = table.insertRow(0);
147
- const cell1 = row.insertCell(0);
148
- const cell2 = row.insertCell(1);
149
- cell1.style = "width: 30%; border-left: 0px;";
150
- cell1.class = "text-gray-700";
151
- cell2.style = "width: 70%;";
152
- cell1.innerHTML = " <div class='p-1'> " + param + " </div>";
153
- cell2.innerHTML = " <input id='path-param-" + param + " class='w-full p-1'>";
154
- }
155
- }
156
- const updateParams = (apiCall) => {
157
- const table = document.getElementById("queryParamsTable");
158
- if (!apiCall.params) {
159
- table.innerHTML = " <div style='background-color: rgb(245, 245, 245); padding: 0.25rem; text-align: center; color: gray;'> NONE </div>";
160
- } else {
161
- table.innerHTML = "";
162
- }
163
- if (apiCall.method === "GET" || apiCall.method === "DELETE") {
164
- for (const key of Object.keys(apiCall.params)) {
165
- const row = table.insertRow(0);
166
- const cell1 = row.insertCell(0);
167
- const cell2 = row.insertCell(1);
168
- cell1.style = "width: 30%; border-left: 0px;";
169
- cell1.class = "text-gray-700";
170
- cell2.style = "width: 70%;";
171
- cell1.innerHTML = " <div class='p-1'> " + key + " </div>";
172
- cell2.innerHTML = " <input id='query-param-" + key + "' class='w-full p-1'>";
173
- }
174
- }
175
- }
176
- const updateBody = (apiCall) => {
177
- if (apiCall.method !== "GET" && apiCall.method !== "DELETE") {
178
- window.codeLeft.setValue(JSON.stringify(apiCall.params, 2, 2));
179
- } else {
180
- window.codeLeft.setValue("");
181
- }
182
- }
183
- const init = () => {
184
- updatePathParams(window.apiDefs[0]);
185
- updateParams(window.apiDefs[0]);
186
- updateBody(window.apiDefs[0]);
187
- const headersJson = localStorage.getItem("headers");
188
- if (headersJson) {
189
- const table = document.getElementById("headersTable");
190
- const headers = JSON.parse(headersJson);
191
- table.innerHTML = "";
192
- for (const key of Object.keys(headers)) {
193
- const value = headers[key];
194
- const row = table.insertRow(0);
195
- const cell1 = row.insertCell(0);
196
- const cell2 = row.insertCell(1);
197
- cell1.style = "width: 30%; border-left: 0px;";
198
- cell2.style = "width: 70%;";
199
- cell1.innerHTML = "<input value = '" + key + "' class='w-full p-1'>";
200
- cell2.innerHTML = "<input value = '" + value + "' class='w-full p-1'>";
201
- }
202
- }
203
- }
204
- window.onload = () => {
205
- init();
206
- }
207
- document.getElementById("api-select").onchange = () => {
208
- const apiCall = getCurrentApiCall();
209
- updatePathParams(apiCall);
210
- updateParams(apiCall);
211
- updateBody(apiCall);
212
- }
213
- const run = document.getElementById("run");
214
- run.onclick = async () => {
215
- run.innerHTML = "<svg class='spinner' viewBox='0 0 50 50'><circle class='path' cx='25' cy='25' r='20' fill='none' stroke-width='5'></circle></svg>";
216
- const table = document.getElementById("headersTable");
217
- const headers = {};
218
- for (const row of table.rows) {
219
- const key = row.cells[0].children[0].value;
220
- const value = row.cells[1].children[0].value;
221
- headers[key] = value;
222
- }
223
- const apiCall = getCurrentApiCall();
224
- let path = apiCall.path;
225
- const bodyParams = {};
226
- if (apiCall.method !== "GET" && apiCall.method != "DELETE") {
227
- bodyParams["body"] = window.codeLeft.getValue();
228
- } else {
229
- for (const param of apiCall.pathParams) {
230
- const value = document.getElementById('path-param-' + param).value;
231
- path = path.replace('{' + param + '}', value);
232
- }
233
- const paramsKeys = Object.keys(apiCall.params);
234
- if (paramsKeys.length > 0) {
235
- path += "?";
236
- paramsKeys.forEach((key, i) => {
237
- const value = document.getElementById('query-param-' + key).value;
238
- path += key + "=" + value;
239
- if (i !== paramsKeys.length - 1) {
240
- path += "&";
241
- }
242
- });
243
- }
244
- }
245
- localStorage.setItem("headers", JSON.stringify(headers));
246
- try {
247
- const res = await fetch(path, {
248
- method: apiCall.method,
249
- headers,
250
- ...bodyParams
251
- });
252
- const json = await res.json();
253
- window.codeRight.setValue(JSON.stringify(json, 2, 2));
254
- } catch (err) {
255
- window.codeRight.setValue(JSON.stringify({
256
- error: err.message
257
- }, 2, 2));
258
- }
259
- run.innerHTML = "RUN";
260
- }
261
- </script>
262
- </body>
263
- </html>
264
- `).Props(
265
- "commit", versioninfo.Revision[0:7],
266
- "routes", apiRoutes,
267
- "apiData", template.HTML(string(apiData)),
268
- "css", template.HTML(string(cmcss)+"\n\n"+string(stylescss)),
269
- "js", template.HTML(string(cmjs)+"\n\n"+string(cmjsjs)),
270
- ).RenderWriter(w)
271
- if err != nil {
272
- RespondError(w, status, err)
273
- }
274
- })
275
- }
gsx/template.go CHANGED
@@ -50,6 +50,17 @@ func RegisterFunc(f interface{}) {
50
50
  funcMap[name] = f
51
51
  }
52
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
+
53
64
  func getAttribute(k string, kvs []*Attribute) string {
54
65
  for _, param := range kvs {
55
66
  if param.Key == k {
handlebars/README.md DELETED
@@ -1,132 +0,0 @@
1
- # Templating
2
-
3
- Gromer uses a handlebars like templating language for components and pages. This is a modified version of this package [velvet](https://github.com/gobuffalo/velvet)
4
- If you know handlebars, you basically know how to use it.
5
-
6
- You can install this plugin [VSCode Go inline html plugin](https://marketplace.visualstudio.com/items?itemName=pyros2097.vscode-go-inline-html) for syntax highlighting the templates.
7
-
8
- Let's assume you have a template (a string of some kind):
9
-
10
- ```handlebars
11
- <!-- some input -->
12
- <h1>{{name}}</h1>
13
- <ul>
14
- {{#each names}}
15
- <li>{{@value}}</li>
16
- {{/each}}
17
- </ul>
18
- ```
19
-
20
- Given that string, you can render the template like such:
21
-
22
- ```html
23
- <h1>Mark</h1>
24
- <ul>
25
- <li>John</li>
26
- <li>Paul</li>
27
- <li>George</li>
28
- <li>Ringo</li>
29
- </ul>
30
- ```
31
-
32
- ### If Statements
33
-
34
- ```handlebars
35
- {{#if true}}
36
- render this
37
- {{/if}}
38
- ```
39
-
40
- #### Else Statements
41
-
42
- ```handlebars
43
- {{#if false}}
44
- won't render this
45
- {{else}}
46
- render this
47
- {{/if}}
48
- ```
49
-
50
- #### Unless Statements
51
-
52
- ```handlebars
53
- {{#unless true}}
54
- won't render this
55
- {{/unless}}
56
- ```
57
-
58
- ### Each Statements
59
-
60
- #### Arrays
61
-
62
- When looping through `arrays` or `slices`, the block being looped through will be access to the "global" context, as well as have four new variables available within that block:
63
-
64
- - `@first` [`bool`] - is this the first pass through the iteration?
65
- - `@last` [`bool`] - is this the last pass through the iteration?
66
- - `@index` [`int`] - the counter of where in the loop you are, starting with `0`.
67
- - `@value` - the current element in the array or slice that is being iterated over.
68
-
69
- ```handlebars
70
- <ul>
71
- {{#each names}}
72
- <li>{{@index}} - {{@value}}</li>
73
- {{/each}}
74
- </ul>
75
- ```
76
-
77
- By using "block parameters" you can change the "key" of the element being accessed from `@value` to a key of your choosing.
78
-
79
- ```handlebars
80
- <ul>
81
- {{#each names as |name|}}
82
- <li>{{name}}</li>
83
- {{/each}}
84
- </ul>
85
- ```
86
-
87
- To change both the key and the index name you can pass two "block parameters"; the first being the new name for the index and the second being the name for the element.
88
-
89
- ```handlebars
90
- <ul>
91
- {{#each names as |index, name|}}
92
- <li>{{ index }} - {{ name }}</li>
93
- {{/each}}
94
- </ul>
95
- ```
96
-
97
- #### Maps
98
-
99
- Looping through `maps` using the `each` helper is also supported, and follows very similar guidelines to looping through `arrays`.
100
-
101
- - `@first` [`bool`] - is this the first pass through the iteration?
102
- - `@last` [`bool`] - is this the last pass through the iteration?
103
- - `@key` - the key of the pair being accessed.
104
- - `@value` - the value of the pair being accessed.
105
-
106
- ```handlebars
107
- <ul>
108
- {{#each users}}
109
- <li>{{@key}} - {{@value}}</li>
110
- {{/each}}
111
- </ul>
112
- ```
113
-
114
- By using "block parameters" you can change the "key" of the element being accessed from `@value` to a key of your choosing.
115
-
116
- ```handlebars
117
- <ul>
118
- {{#each users as |user|}}
119
- <li>{{@key}} - {{user}}</li>
120
- {{/each}}
121
- </ul>
122
- ```
123
-
124
- To change both the key and the value name you can pass two "block parameters"; the first being the new name for the key and the second being the name for the value.
125
-
126
- ```handlebars
127
- <ul>
128
- {{#each users as |key, user|}}
129
- <li>{{ key }} - {{ user }}</li>
130
- {{/each}}
131
- </ul>
132
- ```
handlebars/context.go DELETED
@@ -1,79 +0,0 @@
1
- package handlebars
2
-
3
- // Context holds all of the data for the template that is being rendered.
4
- type Context struct {
5
- data map[string]interface{}
6
- options map[string]interface{}
7
- outer *Context
8
- }
9
-
10
- func (c *Context) export() map[string]interface{} {
11
- m := map[string]interface{}{}
12
- if c.outer != nil {
13
- for k, v := range c.outer.export() {
14
- m[k] = v
15
- }
16
- }
17
- for k, v := range c.data {
18
- m[k] = v
19
- }
20
- if c.options != nil {
21
- for k, v := range c.options {
22
- m[k] = v
23
- }
24
- }
25
-
26
- return m
27
- }
28
-
29
- // New context containing the current context. Values set on the new context
30
- // will not be set onto the original context, however, the original context's
31
- // values will be available to the new context.
32
- func (c *Context) New() *Context {
33
- cc := NewContext()
34
- cc.outer = c
35
- return cc
36
- }
37
-
38
- // Set a value onto the context
39
- func (c *Context) Set(key string, value interface{}) {
40
- c.data[key] = value
41
- }
42
-
43
- // Get a value from the context, or it's parent's context if one exists.
44
- func (c *Context) Get(key string) interface{} {
45
- if v, ok := c.data[key]; ok {
46
- return v
47
- }
48
- if c.outer != nil {
49
- return c.outer.Get(key)
50
- }
51
- return nil
52
- }
53
-
54
- // Has checks the existence of the key in the context.
55
- func (c *Context) Has(key string) bool {
56
- return c.Get(key) != nil
57
- }
58
-
59
- // Options are the values passed into a helper.
60
- func (c *Context) Options() map[string]interface{} {
61
- return c.options
62
- }
63
-
64
- // NewContext returns a fully formed context ready to go
65
- func NewContext() *Context {
66
- return &Context{
67
- data: map[string]interface{}{},
68
- options: map[string]interface{}{},
69
- outer: nil,
70
- }
71
- }
72
-
73
- // NewContextWith returns a fully formed context using the data
74
- // provided.
75
- func NewContextWith(data map[string]interface{}) *Context {
76
- c := NewContext()
77
- c.data = data
78
- return c
79
- }
handlebars/context_test.go DELETED
@@ -1,47 +0,0 @@
1
- package handlebars
2
-
3
- import (
4
- "testing"
5
-
6
- "github.com/stretchr/testify/require"
7
- )
8
-
9
- func Test_Context_Set(t *testing.T) {
10
- r := require.New(t)
11
- c := NewContext()
12
- r.Nil(c.Get("foo"))
13
- c.Set("foo", "bar")
14
- r.NotNil(c.Get("foo"))
15
- }
16
-
17
- func Test_Context_Get(t *testing.T) {
18
- r := require.New(t)
19
- c := NewContext()
20
- r.Nil(c.Get("foo"))
21
- c.Set("foo", "bar")
22
- r.Equal("bar", c.Get("foo"))
23
- }
24
-
25
- func Test_NewSubContext_Set(t *testing.T) {
26
- r := require.New(t)
27
-
28
- c := NewContext()
29
- r.Nil(c.Get("foo"))
30
-
31
- sc := c.New()
32
- r.Nil(sc.Get("foo"))
33
- sc.Set("foo", "bar")
34
- r.Equal("bar", sc.Get("foo"))
35
-
36
- r.Nil(c.Get("foo"))
37
- }
38
-
39
- func Test_NewSubContext_Get(t *testing.T) {
40
- r := require.New(t)
41
-
42
- c := NewContext()
43
- c.Set("foo", "bar")
44
-
45
- sc := c.New()
46
- r.Equal("bar", sc.Get("foo"))
47
- }
handlebars/eval.go DELETED
@@ -1,385 +0,0 @@
1
- package handlebars
2
-
3
- import (
4
- "bytes"
5
- "fmt"
6
- "html/template"
7
- "reflect"
8
- "strconv"
9
- "strings"
10
-
11
- "github.com/aymerick/raymond/ast"
12
- "github.com/pkg/errors"
13
- )
14
-
15
- // HTMLer generates HTML source
16
- type HTMLer interface {
17
- HTML() template.HTML
18
- }
19
-
20
- type interfacer interface {
21
- Interface() interface{}
22
- }
23
-
24
- type blockParams struct {
25
- current []string
26
- stack [][]string
27
- }
28
-
29
- func NewBlockParams() *blockParams {
30
- return &blockParams{
31
- current: []string{},
32
- stack: [][]string{},
33
- }
34
- }
35
-
36
- func (bp *blockParams) push(params []string) {
37
- bp.current = params
38
- bp.stack = append(bp.stack, params)
39
- }
40
-
41
- func (bp *blockParams) pop() []string {
42
- l := len(bp.stack)
43
- if l == 0 {
44
- return bp.current
45
- }
46
- p := bp.stack[l-1]
47
- bp.stack = bp.stack[0:(l - 1)]
48
- l = len(bp.stack)
49
- if l == 0 {
50
- bp.current = []string{}
51
- } else {
52
- bp.current = bp.stack[l-1]
53
- }
54
- return p
55
- }
56
-
57
- var helperContextKind = "HelperContext"
58
-
59
- type evalVisitor struct {
60
- template *Template
61
- context *Context
62
- curBlock *ast.BlockStatement
63
- blockParams *blockParams
64
- }
65
-
66
- func newEvalVisitor(t *Template, c *Context) *evalVisitor {
67
- return &evalVisitor{
68
- template: t,
69
- context: c,
70
- blockParams: NewBlockParams(),
71
- }
72
- }
73
-
74
- func (ev *evalVisitor) VisitProgram(p *ast.Program) interface{} {
75
- // fmt.Println("VisitProgram")
76
- defer ev.blockParams.pop()
77
- out := &bytes.Buffer{}
78
- ev.blockParams.push(p.BlockParams)
79
- for _, b := range p.Body {
80
- ev.context = ev.context.New()
81
- var value interface{}
82
- value = b.Accept(ev)
83
- switch vp := value.(type) {
84
- case error:
85
- return vp
86
- case template.HTML:
87
- out.Write([]byte(vp))
88
- case HTMLer:
89
- out.Write([]byte(vp.HTML()))
90
- case string:
91
- out.WriteString(template.HTMLEscapeString(vp))
92
- case []string:
93
- out.WriteString(template.HTMLEscapeString(strings.Join(vp, " ")))
94
- case int:
95
- out.WriteString(strconv.Itoa(vp))
96
- case fmt.Stringer:
97
- out.WriteString(template.HTMLEscapeString(vp.String()))
98
- case interfacer:
99
- out.WriteString(template.HTMLEscaper(vp.Interface()))
100
- case nil:
101
- default:
102
- return errors.WithStack(errors.Errorf("unsupport eval return format %T: %+v", value, value))
103
- }
104
-
105
- }
106
- return out.String()
107
- }
108
- func (ev *evalVisitor) VisitMustache(m *ast.MustacheStatement) interface{} {
109
- // fmt.Println("VisitMustache")
110
- expr := m.Expression.Accept(ev)
111
- return expr
112
- }
113
- func (ev *evalVisitor) VisitBlock(node *ast.BlockStatement) interface{} {
114
- // fmt.Println("VisitBlock")
115
- defer func() {
116
- ev.curBlock = nil
117
- }()
118
- ev.curBlock = node
119
- expr := node.Expression.Accept(ev)
120
- return expr
121
- }
122
-
123
- func (ev *evalVisitor) VisitPartial(*ast.PartialStatement) interface{} {
124
- // fmt.Println("VisitPartial")
125
- return ""
126
- }
127
-
128
- func (ev *evalVisitor) VisitContent(c *ast.ContentStatement) interface{} {
129
- // fmt.Println("VisitContent")
130
- return template.HTML(c.Original)
131
- }
132
-
133
- func (ev *evalVisitor) VisitComment(*ast.CommentStatement) interface{} {
134
- return ""
135
- }
136
-
137
- func (ev *evalVisitor) VisitExpression(e *ast.Expression) interface{} {
138
- // fmt.Println("VisitExpression")
139
- if e.Hash != nil {
140
- e.Hash.Accept(ev)
141
- }
142
- h := ev.helperName(e.HelperName())
143
- if h != "" {
144
- if helper, ok := GlobalHelpers.helpers[h]; ok {
145
- return ev.evalHelper(e, helper)
146
- }
147
- if ev.context.Has(h) {
148
- x := ev.context.Get(h)
149
- if x != nil && h == "partial" {
150
- return ev.evalHelper(e, x)
151
- }
152
- return x
153
- }
154
- return errors.WithStack(errors.Errorf("could not find value for %s [line %d:%d]", h, e.Line, e.Pos))
155
- }
156
- parts := strings.Split(e.Canonical(), ".")
157
- if len(parts) > 1 && ev.context.Has(parts[0]) {
158
- rv := reflect.ValueOf(ev.context.Get(parts[0]))
159
- if rv.Kind() == reflect.Ptr {
160
- rv = rv.Elem()
161
- }
162
- m := rv.MethodByName(parts[1])
163
- if m.IsValid() {
164
- return ev.evalHelper(e, m.Interface())
165
- }
166
- }
167
- if fp := e.FieldPath(); fp != nil {
168
- return ev.VisitPath(fp)
169
- }
170
- if e.Path != nil {
171
- return e.Path.Accept(ev)
172
- }
173
- return nil
174
- }
175
-
176
- func (ev *evalVisitor) VisitSubExpression(*ast.SubExpression) interface{} {
177
- // fmt.Println("VisitSubExpression")
178
- return nil
179
- }
180
-
181
- func (ev *evalVisitor) VisitPath(node *ast.PathExpression) interface{} {
182
- // fmt.Println("VisitPath")
183
- // fmt.Printf("### node -> %+v\n", node)
184
- // fmt.Printf("### node -> %T\n", node)
185
- // fmt.Printf("### node.IsDataRoot() -> %+v\n", node.IsDataRoot())
186
- // fmt.Printf("### node.Loc() -> %+v\n", node.Location())
187
- // fmt.Printf("### node.String() -> %+v\n", node.String())
188
- // fmt.Printf("### node.Type() -> %+v\n", node.Type())
189
- // fmt.Printf("### node.Data -> %+v\n", node.Data)
190
- // fmt.Printf("### node.Depth -> %+v\n", node.Depth)
191
- // fmt.Printf("### node.Original -> %+v\n", node.Original)
192
- // fmt.Printf("### node.Parts -> %+v\n", node.Parts)
193
- // fmt.Printf("### node.Scoped -> %+v\n", node.Scoped)
194
- var v interface{}
195
- var h string
196
- if node.Data || len(node.Parts) == 0 {
197
- h = ev.helperName(node.Original)
198
- } else {
199
- h = ev.helperName(node.Parts[0])
200
- }
201
- if ev.context.Get(h) != nil {
202
- v = ev.context.Get(h)
203
- }
204
- if v == nil {
205
- return ""
206
- // return errors.WithStack(errors.Errorf("could not find value for %s [line %d:%d]", h, node.Line, node.Pos))
207
- }
208
-
209
- for i := 1; i < len(node.Parts); i++ {
210
- rv := reflect.ValueOf(v)
211
- if rv.Kind() == reflect.Ptr {
212
- rv = rv.Elem()
213
- }
214
- p := node.Parts[i]
215
- m := rv.MethodByName(p)
216
- if m.IsValid() {
217
-
218
- args := []reflect.Value{}
219
- rt := m.Type()
220
- if rt.NumIn() > 0 {
221
- last := rt.In(rt.NumIn() - 1)
222
- if last.Name() == helperContextKind {
223
- hargs := HelperContext{
224
- Context: ev.context,
225
- Args: []interface{}{},
226
- evalVisitor: ev,
227
- }
228
- args = append(args, reflect.ValueOf(hargs))
229
- } else if last.Kind() == reflect.Map {
230
- args = append(args, reflect.ValueOf(ev.context.Options()))
231
- }
232
- if len(args) > rt.NumIn() {
233
- err := errors.Errorf("Incorrect number of arguments being passed to %s (%d for %d)", p, len(args), rt.NumIn())
234
- return errors.WithStack(err)
235
- }
236
- }
237
- vv := m.Call(args)
238
-
239
- if len(vv) >= 1 {
240
- v = vv[0].Interface()
241
- }
242
- continue
243
- }
244
- switch rv.Kind() {
245
- case reflect.Map:
246
- pv := reflect.ValueOf(p)
247
- keys := rv.MapKeys()
248
- for i := 0; i < len(keys); i++ {
249
- k := keys[i]
250
- if k.Interface() == pv.Interface() {
251
- return rv.MapIndex(k).Interface()
252
- }
253
- }
254
- return errors.WithStack(errors.Errorf("could not find value for %s [line %d:%d]", node.Original, node.Line, node.Pos))
255
- default:
256
- f := rv.FieldByName(p)
257
- v = f.Interface()
258
- }
259
- }
260
- return v
261
- }
262
-
263
- func (ev *evalVisitor) VisitString(node *ast.StringLiteral) interface{} {
264
- // fmt.Println("VisitString")
265
- return node.Value
266
- }
267
-
268
- func (ev *evalVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} {
269
- // fmt.Println("VisitBoolean")
270
- return node.Value
271
- }
272
-
273
- func (ev *evalVisitor) VisitNumber(node *ast.NumberLiteral) interface{} {
274
- // fmt.Println("VisitNumber")
275
- return node.Number()
276
- }
277
-
278
- func (ev *evalVisitor) VisitHash(node *ast.Hash) interface{} {
279
- // fmt.Println("VisitHash")
280
- ctx := ev.context.New()
281
- for _, h := range node.Pairs {
282
- val := h.Accept(ev).(map[string]interface{})
283
- for k, v := range val {
284
- ctx.Set(k, v)
285
- ctx.Options()[k] = v
286
- }
287
- }
288
- ev.context = ctx
289
- return nil
290
- }
291
-
292
- func (ev *evalVisitor) VisitHashPair(node *ast.HashPair) interface{} {
293
- // fmt.Println("VisitHashPair")
294
- return map[string]interface{}{
295
- node.Key: node.Val.Accept(ev),
296
- }
297
- }
298
-
299
- func (ev *evalVisitor) evalHelper(node *ast.Expression, helper interface{}) (ret interface{}) {
300
- // fmt.Println("evalHelper")
301
- defer func() {
302
- if r := recover(); r != nil {
303
- switch rp := r.(type) {
304
- case error:
305
- ret = errors.WithStack(rp)
306
- case string:
307
- ret = errors.WithStack(errors.New(rp))
308
- }
309
- }
310
- }()
311
-
312
- hargs := HelperContext{
313
- Context: ev.context,
314
- Args: []interface{}{},
315
- evalVisitor: ev,
316
- }
317
-
318
- rv := reflect.ValueOf(helper)
319
- if rv.Kind() == reflect.Ptr {
320
- rv = rv.Elem()
321
- }
322
- rt := rv.Type()
323
-
324
- args := []reflect.Value{}
325
-
326
- if rt.NumIn() > 0 {
327
- for _, p := range node.Params {
328
- v := p.Accept(ev)
329
- vv := reflect.ValueOf(v)
330
- hargs.Args = append(hargs.Args, v)
331
- args = append(args, vv)
332
- }
333
-
334
- last := rt.In(rt.NumIn() - 1)
335
- if last.Name() == helperContextKind {
336
- args = append(args, reflect.ValueOf(hargs))
337
- } else if last.Kind() == reflect.Map {
338
- if node.Canonical() == "partial" {
339
- args = append(args, reflect.ValueOf(ev.context.export()))
340
- } else {
341
- args = append(args, reflect.ValueOf(ev.context.Options()))
342
- }
343
- }
344
- if len(args) > rt.NumIn() {
345
- err := errors.Errorf("Incorrect number of arguments being passed to %s (%d for %d)", node.Canonical(), len(args), rt.NumIn())
346
- return errors.WithStack(err)
347
- }
348
- }
349
- vv := rv.Call(args)
350
-
351
- if len(vv) >= 1 {
352
- v := vv[0].Interface()
353
- if len(vv) >= 2 {
354
- if !vv[1].IsNil() {
355
- return errors.WithStack(vv[1].Interface().(error))
356
- }
357
- }
358
- return v
359
- }
360
-
361
- return ""
362
- }
363
-
364
- func (ev *evalVisitor) helperName(h string) string {
365
- if h != "" {
366
- bp := ev.blockParams.current
367
- if len(bp) == 1 {
368
- if t := ev.context.Get("@value"); t != nil {
369
- ev.context.Set(bp[0], t)
370
- }
371
- }
372
- if len(bp) >= 2 {
373
- if t := ev.context.Get("@value"); t != nil {
374
- ev.context.Set(bp[1], t)
375
- }
376
- for _, k := range []string{"@index", "@key"} {
377
- if t := ev.context.Get(k); t != nil {
378
- ev.context.Set(bp[0], t)
379
- }
380
- }
381
- }
382
- return h
383
- }
384
- return ""
385
- }
handlebars/eval_test.go DELETED
@@ -1,73 +0,0 @@
1
- package handlebars
2
-
3
- import (
4
- "strings"
5
- "testing"
6
-
7
- "github.com/stretchr/testify/require"
8
- )
9
-
10
- func Test_blockParams(t *testing.T) {
11
- r := require.New(t)
12
- bp := NewBlockParams()
13
- r.Equal([]string{}, bp.current)
14
- r.Len(bp.stack, 0)
15
-
16
- bp.push([]string{"mark"})
17
- r.Equal([]string{"mark"}, bp.current)
18
- r.Len(bp.stack, 1)
19
-
20
- bp.push([]string{"bates"})
21
- r.Equal([]string{"bates"}, bp.current)
22
- r.Len(bp.stack, 2)
23
- r.Equal([][]string{
24
- []string{"mark"},
25
- []string{"bates"},
26
- }, bp.stack)
27
-
28
- b := bp.pop()
29
- r.Equal([]string{"bates"}, b)
30
- r.Equal([]string{"mark"}, bp.current)
31
- r.Len(bp.stack, 1)
32
-
33
- b = bp.pop()
34
- r.Equal([]string{"mark"}, b)
35
- r.Len(bp.stack, 0)
36
- r.Equal([]string{}, bp.current)
37
- }
38
-
39
- func newBlockParams() {
40
- panic("unimplemented")
41
- }
42
-
43
- func Test_Eval_Map_Call_Key(t *testing.T) {
44
- r := require.New(t)
45
- ctx := NewContext()
46
- data := map[string]string{
47
- "a": "A",
48
- "b": "B",
49
- }
50
- ctx.Set("letters", data)
51
- s, _, err := Html(`
52
- {{letters.a}}|{{letters.b}}
53
- `).Props(
54
- "a", "A",
55
- "b", "B",
56
- ).Render()
57
- r.NoError(err)
58
- r.Equal("A|B", strings.TrimSpace(string(s)))
59
- }
60
-
61
- func Test_Eval_Calls_on_Pointers(t *testing.T) {
62
- r := require.New(t)
63
- type user struct {
64
- Name string
65
- }
66
- u := &user{Name: "Mark"}
67
- ctx := NewContext()
68
- ctx.Set("user", u)
69
-
70
- s, err := Render("{{user.Name}}", ctx)
71
- r.NoError(err)
72
- r.Equal("Mark", s)
73
- }
handlebars/helpers.go DELETED
@@ -1,312 +0,0 @@
1
- package handlebars
2
-
3
- import (
4
- "bytes"
5
- "encoding/json"
6
- "fmt"
7
- "html/template"
8
- "reflect"
9
- "strconv"
10
- "strings"
11
- "sync"
12
-
13
- "github.com/pkg/errors"
14
- )
15
-
16
- // GlobalHelpers contains all of the default helpers for handlebars.
17
- // These will be available to all templates. You should add
18
- // any custom global helpers to this list.
19
- var GlobalHelpers = HelperMap{
20
- moot: &sync.Mutex{},
21
- helpers: map[string]interface{}{},
22
- }
23
-
24
- func init() {
25
- GlobalHelpers.Add("if", ifHelper)
26
- GlobalHelpers.Add("unless", unlessHelper)
27
- GlobalHelpers.Add("each", eachHelper)
28
- GlobalHelpers.Add("eq", equalHelper)
29
- GlobalHelpers.Add("equal", equalHelper)
30
- GlobalHelpers.Add("neq", notEqualHelper)
31
- GlobalHelpers.Add("notequal", notEqualHelper)
32
- GlobalHelpers.Add("json", toJSONHelper)
33
- GlobalHelpers.Add("js_escape", template.JSEscapeString)
34
- GlobalHelpers.Add("html_escape", template.HTMLEscapeString)
35
- GlobalHelpers.Add("upcase", strings.ToUpper)
36
- GlobalHelpers.Add("downcase", strings.ToLower)
37
- GlobalHelpers.Add("len", lenHelper)
38
- GlobalHelpers.Add("debug", debugHelper)
39
- GlobalHelpers.Add("inspect", inspectHelper)
40
- }
41
-
42
- // HelperContext is an optional context that can be passed
43
- // as the last argument to helper functions.
44
- type HelperContext struct {
45
- Context *Context
46
- Args []interface{}
47
- evalVisitor *evalVisitor
48
- }
49
-
50
- // Block executes the block of template associated with
51
- // the helper, think the block inside of an "if" or "each"
52
- // statement.
53
- func (h HelperContext) Block() (string, error) {
54
- return h.BlockWith(h.Context)
55
- }
56
-
57
- // BlockWith executes the block of template associated with
58
- // the helper, think the block inside of an "if" or "each"
59
- // statement. It takes a new context with which to evaluate
60
- // the block.
61
- func (h HelperContext) BlockWith(ctx *Context) (string, error) {
62
- nev := newEvalVisitor(h.evalVisitor.template, ctx)
63
- nev.blockParams = h.evalVisitor.blockParams
64
- dd := nev.VisitProgram(h.evalVisitor.curBlock.Program)
65
- switch tp := dd.(type) {
66
- case string:
67
- return tp, nil
68
- case error:
69
- return "", errors.WithStack(tp)
70
- case nil:
71
- return "", nil
72
- default:
73
- return "", errors.WithStack(errors.Errorf("unknown return value %T %+v", dd, dd))
74
- }
75
- }
76
-
77
- // ElseBlock executes the "inverse" block of template associated with
78
- // the helper, think the "else" block of an "if" or "each"
79
- // statement.
80
- func (h HelperContext) ElseBlock() (string, error) {
81
- return h.ElseBlockWith(h.Context)
82
- }
83
-
84
- // ElseBlockWith executes the "inverse" block of template associated with
85
- // the helper, think the "else" block of an "if" or "each"
86
- // statement. It takes a new context with which to evaluate
87
- // the block.
88
- func (h HelperContext) ElseBlockWith(ctx *Context) (string, error) {
89
- if h.evalVisitor.curBlock.Inverse == nil {
90
- return "", nil
91
- }
92
- nev := newEvalVisitor(h.evalVisitor.template, ctx)
93
- nev.blockParams = h.evalVisitor.blockParams
94
- dd := nev.VisitProgram(h.evalVisitor.curBlock.Inverse)
95
- switch tp := dd.(type) {
96
- case string:
97
- return tp, nil
98
- case error:
99
- return "", errors.WithStack(tp)
100
- case nil:
101
- return "", nil
102
- default:
103
- return "", errors.WithStack(errors.Errorf("unknown return value %T %+v", dd, dd))
104
- }
105
- }
106
-
107
- // Get is a convenience method that calls the underlying Context.
108
- func (h HelperContext) Get(key string) interface{} {
109
- return h.Context.Get(key)
110
- }
111
-
112
- // toJSONHelper converts an interface into a string.
113
- func toJSONHelper(v interface{}) (template.HTML, error) {
114
- b, err := json.Marshal(v)
115
- if err != nil {
116
- return "", errors.WithStack(err)
117
- }
118
- return template.HTML(b), nil
119
- }
120
-
121
- func lenHelper(v interface{}) string {
122
- rv := reflect.ValueOf(v)
123
- if rv.Kind() == reflect.Ptr {
124
- rv = rv.Elem()
125
- }
126
- return strconv.Itoa(rv.Len())
127
- }
128
-
129
- // Debug by verbosely printing out using 'pre' tags.
130
- func debugHelper(v interface{}) template.HTML {
131
- return template.HTML(fmt.Sprintf("<pre>%+v</pre>", v))
132
- }
133
-
134
- func inspectHelper(v interface{}) string {
135
- return fmt.Sprintf("%+v", v)
136
- }
137
-
138
- func eachHelper(collection interface{}, help HelperContext) (template.HTML, error) {
139
- out := bytes.Buffer{}
140
- val := reflect.ValueOf(collection)
141
- if val.Kind() == reflect.Ptr {
142
- val = val.Elem()
143
- }
144
- if val.Kind() == reflect.Struct || val.Len() == 0 {
145
- s, err := help.ElseBlock()
146
- return template.HTML(s), err
147
- }
148
- switch val.Kind() {
149
- case reflect.Array, reflect.Slice:
150
- for i := 0; i < val.Len(); i++ {
151
- v := val.Index(i).Interface()
152
- ctx := help.Context.New()
153
- ctx.Set("@first", i == 0)
154
- ctx.Set("@last", i == val.Len()-1)
155
- ctx.Set("@index", i)
156
- ctx.Set("@value", v)
157
- s, err := help.BlockWith(ctx)
158
- if err != nil {
159
- return "", errors.WithStack(err)
160
- }
161
- out.WriteString(s)
162
- }
163
- case reflect.Map:
164
- keys := val.MapKeys()
165
- for i := 0; i < len(keys); i++ {
166
- key := keys[i].Interface()
167
- v := val.MapIndex(keys[i]).Interface()
168
- ctx := help.Context.New()
169
- ctx.Set("@first", i == 0)
170
- ctx.Set("@last", i == len(keys)-1)
171
- ctx.Set("@key", key)
172
- ctx.Set("@value", v)
173
- s, err := help.BlockWith(ctx)
174
- if err != nil {
175
- return "", errors.WithStack(err)
176
- }
177
- out.WriteString(s)
178
- }
179
- }
180
- return template.HTML(out.String()), nil
181
- }
182
-
183
- func equalHelper(a, b interface{}, help HelperContext) (template.HTML, error) {
184
- if a == b {
185
- s, err := help.Block()
186
- if err != nil {
187
- return "", err
188
- }
189
- return template.HTML(s), nil
190
- }
191
- s, err := help.ElseBlock()
192
- if err != nil {
193
- return "", err
194
- }
195
- return template.HTML(s), nil
196
- }
197
-
198
- func notEqualHelper(a, b interface{}, help HelperContext) (template.HTML, error) {
199
- if a != b {
200
- s, err := help.Block()
201
- if err != nil {
202
- return "", err
203
- }
204
- return template.HTML(s), nil
205
- }
206
- s, err := help.ElseBlock()
207
- if err != nil {
208
- return "", err
209
- }
210
- return template.HTML(s), nil
211
- }
212
-
213
- func ifHelper(conditional interface{}, help HelperContext) (template.HTML, error) {
214
- if IsTrue(conditional) {
215
- s, err := help.Block()
216
- return template.HTML(s), err
217
- }
218
- s, err := help.ElseBlock()
219
- return template.HTML(s), err
220
- }
221
-
222
- // IsTrue returns true if obj is a truthy value.
223
- func IsTrue(obj interface{}) bool {
224
- thruth, ok := isTrueValue(reflect.ValueOf(obj))
225
- if !ok {
226
- return false
227
- }
228
- return thruth
229
- }
230
-
231
- // isTrueValue reports whether the value is 'true', in the sense of not the zero of its type,
232
- // and whether the value has a meaningful truth value
233
- //
234
- // NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
235
- func isTrueValue(val reflect.Value) (truth, ok bool) {
236
- if !val.IsValid() {
237
- // Something like var x interface{}, never set. It's a form of nil.
238
- return false, true
239
- }
240
- switch val.Kind() {
241
- case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
242
- truth = val.Len() > 0
243
- case reflect.Bool:
244
- truth = val.Bool()
245
- case reflect.Complex64, reflect.Complex128:
246
- truth = val.Complex() != 0
247
- case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
248
- truth = !val.IsNil()
249
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
250
- truth = val.Int() != 0
251
- case reflect.Float32, reflect.Float64:
252
- truth = val.Float() != 0
253
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
254
- truth = val.Uint() != 0
255
- case reflect.Struct:
256
- truth = true // Struct values are always true.
257
- default:
258
- return
259
- }
260
- return truth, true
261
- }
262
-
263
- func unlessHelper(conditional bool, help HelperContext) (template.HTML, error) {
264
- return ifHelper(!conditional, help)
265
- }
266
-
267
- // HelperMap holds onto helpers and validates they are properly formed.
268
- type HelperMap struct {
269
- moot *sync.Mutex
270
- helpers map[string]interface{}
271
- }
272
-
273
- // Add a new helper to the map. New Helpers will be validated to ensure they
274
- // meet the requirements for a helper:
275
- /*
276
- func(...) (string) {}
277
- func(...) (string, error) {}
278
- func(...) (template.HTML) {}
279
- func(...) (template.HTML, error) {}
280
- */
281
- func (h *HelperMap) Add(key string, helper interface{}) error {
282
- h.moot.Lock()
283
- defer h.moot.Unlock()
284
- err := h.validateHelper(key, helper)
285
- if err != nil {
286
- return errors.WithStack(err)
287
- }
288
- h.helpers[key] = helper
289
- return nil
290
- }
291
-
292
- func (h *HelperMap) validateHelper(key string, helper interface{}) error {
293
- ht := reflect.ValueOf(helper).Type()
294
-
295
- if ht.NumOut() < 1 {
296
- return errors.WithStack(errors.Errorf("%s must return at least one value ([string|template.HTML], [error])", key))
297
- }
298
- so := ht.Out(0).Kind().String()
299
- if ht.NumOut() > 1 {
300
- et := ht.Out(1)
301
- ev := reflect.ValueOf(et)
302
- ek := fmt.Sprintf("%s", ev.Interface())
303
- if (so != "string" && so != "template.HTML") || (ek != "error") {
304
- return errors.WithStack(errors.Errorf("%s must return ([string|template.HTML], [error]), not (%s, %s)", key, so, et.Kind()))
305
- }
306
- } else {
307
- if so != "string" && so != "template.HTML" {
308
- return errors.WithStack(errors.Errorf("%s must return ([string|template.HTML], [error]), not (%s)", key, so))
309
- }
310
- }
311
- return nil
312
- }
handlebars/helpers_test.go DELETED
@@ -1,466 +0,0 @@
1
- package handlebars
2
-
3
- import (
4
- "fmt"
5
- "html/template"
6
- "strings"
7
- "testing"
8
-
9
- "github.com/stretchr/testify/require"
10
- )
11
-
12
- func Test_CustomGlobalHelper(t *testing.T) {
13
- r := require.New(t)
14
- err := GlobalHelpers.Add("say", func(name string) (string, error) {
15
- return fmt.Sprintf("say: %s", name), nil
16
- })
17
- r.NoError(err)
18
-
19
- input := `{{say "mark"}}`
20
- ctx := NewContext()
21
- s, err := Render(input, ctx)
22
- r.NoError(err)
23
- r.Equal("say: mark", s)
24
- }
25
-
26
- func Test_CustomGlobalBlockHelper(t *testing.T) {
27
- r := require.New(t)
28
- GlobalHelpers.Add("say", func(name string, help HelperContext) (template.HTML, error) {
29
- ctx := help.Context
30
- ctx.Set("name", strings.ToUpper(name))
31
- s, err := help.BlockWith(ctx)
32
- return template.HTML(s), err
33
- })
34
-
35
- input := `
36
- {{#say "mark"}}
37
- <h1>{{name}}</h1>
38
- {{/say}}
39
- `
40
- ctx := NewContext()
41
- s, err := Render(input, ctx)
42
- r.NoError(err)
43
- r.Contains(s, "<h1>MARK</h1>")
44
- }
45
-
46
- func Test_Helper_Hash_Options(t *testing.T) {
47
- r := require.New(t)
48
- GlobalHelpers.Add("say", func(help HelperContext) string {
49
- return help.Context.Get("name").(string)
50
- })
51
-
52
- input := `{{say name="mark"}}`
53
- ctx := NewContext()
54
- s, err := Render(input, ctx)
55
- r.NoError(err)
56
- r.Equal("mark", s)
57
- }
58
-
59
- func Test_Helper_Hash_Options_Many(t *testing.T) {
60
- r := require.New(t)
61
- GlobalHelpers.Add("say", func(help HelperContext) string {
62
- return help.Context.Get("first").(string) + help.Context.Get("last").(string)
63
- })
64
-
65
- input := `{{say first=first_name last=last_name}}`
66
- ctx := NewContext()
67
- ctx.Set("first_name", "Mark")
68
- ctx.Set("last_name", "Bates")
69
- s, err := Render(input, ctx)
70
- r.NoError(err)
71
- r.Equal("MarkBates", s)
72
- }
73
-
74
- func Test_Helper_Santize_Output(t *testing.T) {
75
- r := require.New(t)
76
-
77
- GlobalHelpers.Add("safe", func(help HelperContext) template.HTML {
78
- return template.HTML("<p>safe</p>")
79
- })
80
- GlobalHelpers.Add("unsafe", func(help HelperContext) string {
81
- return "<b>unsafe</b>"
82
- })
83
-
84
- input := `{{safe}}|{{unsafe}}`
85
- s, err := Render(input, NewContext())
86
- r.NoError(err)
87
- r.Equal("<p>safe</p>|&lt;b&gt;unsafe&lt;/b&gt;", s)
88
- }
89
-
90
- func Test_JSON_Helper(t *testing.T) {
91
- r := require.New(t)
92
-
93
- input := `{{json names}}`
94
- ctx := NewContext()
95
- ctx.Set("names", []string{"mark", "bates"})
96
- s, err := Render(input, ctx)
97
- r.NoError(err)
98
- r.Equal(`["mark","bates"]`, s)
99
- }
100
-
101
- func Test_If_Helper(t *testing.T) {
102
- r := require.New(t)
103
- ctx := NewContext()
104
- input := `{{#if true}}hi{{/if}}`
105
-
106
- s, err := Render(input, ctx)
107
- r.NoError(err)
108
- r.Equal("hi", s)
109
- }
110
-
111
- func Test_If_Helper_false(t *testing.T) {
112
- r := require.New(t)
113
- ctx := NewContext()
114
- input := `{{#if false}}hi{{/if}}`
115
-
116
- s, err := Render(input, ctx)
117
- r.NoError(err)
118
- r.Equal("", s)
119
- }
120
-
121
- func Test_If_Helper_NoArgs(t *testing.T) {
122
- r := require.New(t)
123
- ctx := NewContext()
124
- input := `{{#if }}hi{{/if}}`
125
-
126
- _, err := Render(input, ctx)
127
- r.Error(err)
128
- }
129
-
130
- func Test_If_Helper_Else(t *testing.T) {
131
- r := require.New(t)
132
- ctx := NewContext()
133
- input := `
134
- {{#if false}}
135
- hi
136
- {{ else }}
137
- bye
138
- {{/if}}`
139
-
140
- s, err := Render(input, ctx)
141
- r.NoError(err)
142
- r.Contains(s, "bye")
143
- }
144
-
145
- func Test_Unless_Helper(t *testing.T) {
146
- r := require.New(t)
147
- ctx := NewContext()
148
- input := `{{#unless false}}hi{{/unless}}`
149
-
150
- s, err := Render(input, ctx)
151
- r.NoError(err)
152
- r.Equal("hi", s)
153
- }
154
-
155
- func Test_EqualHelper_True(t *testing.T) {
156
- r := require.New(t)
157
- input := `
158
- {{#eq 1 1}}
159
- it was true
160
- {{else}}
161
- it was false
162
- {{/eq}}
163
- `
164
- s, err := Render(input, NewContext())
165
- r.NoError(err)
166
- r.Contains(s, "it was true")
167
- }
168
-
169
- func Test_EqualHelper_False(t *testing.T) {
170
- r := require.New(t)
171
- input := `
172
- {{#eq 1 2}}
173
- it was true
174
- {{else}}
175
- it was false
176
- {{/eq}}
177
- `
178
- s, err := Render(input, NewContext())
179
- r.NoError(err)
180
- r.Contains(s, "it was false")
181
- }
182
-
183
- func Test_EqualHelper_DifferentTypes(t *testing.T) {
184
- r := require.New(t)
185
- input := `
186
- {{#eq 1 "1"}}
187
- it was true
188
- {{else}}
189
- it was false
190
- {{/eq}}
191
- `
192
- s, err := Render(input, NewContext())
193
- r.NoError(err)
194
- r.Contains(s, "it was false")
195
- }
196
-
197
- func Test_NotEqualHelper_True(t *testing.T) {
198
- r := require.New(t)
199
- input := `
200
- {{#neq 1 1}}
201
- it was true
202
- {{else}}
203
- it was false
204
- {{/neq}}
205
- `
206
- s, err := Render(input, NewContext())
207
- r.NoError(err)
208
- r.Contains(s, "it was false")
209
- }
210
-
211
- func Test_NotEqualHelper_False(t *testing.T) {
212
- r := require.New(t)
213
- input := `
214
- {{#neq 1 2}}
215
- it was true
216
- {{else}}
217
- it was false
218
- {{/neq}}
219
- `
220
- s, err := Render(input, NewContext())
221
- r.NoError(err)
222
- r.Contains(s, "it was true")
223
- }
224
-
225
- func Test_NotEqualHelper_DifferentTypes(t *testing.T) {
226
- r := require.New(t)
227
- input := `
228
- {{#neq 1 "1"}}
229
- it was true
230
- {{else}}
231
- it was false
232
- {{/neq}}
233
- `
234
- s, err := Render(input, NewContext())
235
- r.NoError(err)
236
- r.Contains(s, "it was true")
237
- }
238
-
239
- func Test_Each_Helper_NoArgs(t *testing.T) {
240
- r := require.New(t)
241
- ctx := NewContext()
242
- input := `{{#each }}{{@value}}{{/each}}`
243
-
244
- _, err := Render(input, ctx)
245
- r.Error(err)
246
- }
247
-
248
- func Test_Each_Helper(t *testing.T) {
249
- r := require.New(t)
250
- ctx := NewContext()
251
- ctx.Set("names", []string{"mark", "bates"})
252
- input := `{{#each names }}<p>{{@value}}</p>{{/each}}`
253
-
254
- s, err := Render(input, ctx)
255
- r.NoError(err)
256
- r.Equal("<p>mark</p><p>bates</p>", s)
257
- }
258
-
259
- func Test_Each_Helper_Index(t *testing.T) {
260
- r := require.New(t)
261
- ctx := NewContext()
262
- ctx.Set("names", []string{"mark", "bates"})
263
- input := `{{#each names }}<p>{{@index}}</p>{{/each}}`
264
-
265
- s, err := Render(input, ctx)
266
- r.NoError(err)
267
- r.Equal("<p>0</p><p>1</p>", s)
268
- }
269
-
270
- func Test_Each_Helper_As(t *testing.T) {
271
- r := require.New(t)
272
- ctx := NewContext()
273
- ctx.Set("names", []string{"mark", "bates"})
274
- input := `{{#each names as |ind name| }}<p>{{ind}}-{{name}}</p>{{/each}}`
275
-
276
- s, err := Render(input, ctx)
277
- r.NoError(err)
278
- r.Equal("<p>0-mark</p><p>1-bates</p>", s)
279
- }
280
-
281
- func Test_Each_Helper_As_Nested(t *testing.T) {
282
- r := require.New(t)
283
- ctx := NewContext()
284
- users := []struct {
285
- Name string
286
- Initials []string
287
- }{
288
- {Name: "Mark", Initials: []string{"M", "F", "B"}},
289
- {Name: "Rachel", Initials: []string{"R", "A", "B"}},
290
- }
291
- ctx.Set("users", users)
292
- input := `
293
- {{#each users as |user|}}
294
- <h1>{{user.Name}}</h1>
295
- {{#each user.Initials as |i|}}
296
- {{user.Name}}: {{i}}
297
- {{/each}}
298
- {{/each}}
299
- `
300
-
301
- s, err := Render(input, ctx)
302
- r.NoError(err)
303
- r.Contains(s, "<h1>Mark</h1>")
304
- r.Contains(s, "Mark: M")
305
- r.Contains(s, "Mark: F")
306
- r.Contains(s, "Mark: B")
307
- r.Contains(s, "<h1>Rachel</h1>")
308
- r.Contains(s, "Rachel: R")
309
- r.Contains(s, "Rachel: A")
310
- r.Contains(s, "Rachel: B")
311
- }
312
-
313
- func Test_Each_Helper_SlicePtr(t *testing.T) {
314
- r := require.New(t)
315
- type user struct {
316
- Name string
317
- }
318
- type users []user
319
-
320
- us := &users{
321
- {Name: "Mark"},
322
- {Name: "Rachel"},
323
- }
324
-
325
- ctx := NewContext()
326
- ctx.Set("users", us)
327
-
328
- input := `
329
- {{#each users as |user|}}
330
- {{user.Name}}
331
- {{/each}}
332
- `
333
- s, err := Render(input, ctx)
334
- r.NoError(err)
335
- r.Contains(s, "Mark")
336
- r.Contains(s, "Rachel")
337
- }
338
-
339
- func Test_Each_Helper_Map(t *testing.T) {
340
- r := require.New(t)
341
- ctx := NewContext()
342
- data := map[string]string{
343
- "a": "A",
344
- "b": "B",
345
- }
346
- ctx.Set("letters", data)
347
- input := `
348
- {{#each letters}}
349
- {{@key}}:{{@value}}
350
- {{/each}}
351
- `
352
-
353
- s, err := Render(input, ctx)
354
- r.NoError(err)
355
- for k, v := range data {
356
- r.Contains(s, fmt.Sprintf("%s:%s", k, v))
357
- }
358
- }
359
-
360
- func Test_Each_Helper_Map_As(t *testing.T) {
361
- r := require.New(t)
362
- ctx := NewContext()
363
- data := map[string]string{
364
- "a": "A",
365
- "b": "B",
366
- }
367
- ctx.Set("letters", data)
368
- input := `
369
- {{#each letters as |k v|}}
370
- {{k}}:{{v}}
371
- {{/each}}
372
- `
373
-
374
- s, err := Render(input, ctx)
375
- r.NoError(err)
376
- for k, v := range data {
377
- r.Contains(s, fmt.Sprintf("%s:%s", k, v))
378
- }
379
- }
380
-
381
- func Test_Each_Helper_Else(t *testing.T) {
382
- r := require.New(t)
383
- ctx := NewContext()
384
- data := map[string]string{}
385
- ctx.Set("letters", data)
386
- input := `
387
- {{#each letters as |k v|}}
388
- {{k}}:{{v}}
389
- {{else}}
390
- no letters
391
- {{/each}}
392
- `
393
-
394
- s, err := Render(input, ctx)
395
- r.NoError(err)
396
- r.Contains(s, "no letters")
397
- }
398
-
399
- func Test_Each_Helper_Else_Collection(t *testing.T) {
400
- r := require.New(t)
401
- ctx := NewContext()
402
- data := map[string][]string{}
403
- ctx.Set("collection", data)
404
-
405
- input := `
406
- {{#each collection.emptykey as |k v|}}
407
- {{k}}:{{v}}
408
- {{else}}
409
- no letters
410
- {{/each}}
411
- `
412
-
413
- s, err := Render(input, ctx)
414
- r.NoError(err)
415
- r.Contains(s, "no letters")
416
- }
417
-
418
- func Test_Each_Helper_Else_CollectionMap(t *testing.T) {
419
- r := require.New(t)
420
- ctx := NewContext()
421
- data := map[string]map[string]string{
422
- "emptykey": map[string]string{},
423
- }
424
-
425
- ctx.Set("collection", data)
426
-
427
- input := `
428
- {{#each collection.emptykey.something as |k v|}}
429
- {{k}}:{{v}}
430
- {{else}}
431
- no letters
432
- {{/each}}
433
- `
434
-
435
- s, err := Render(input, ctx)
436
- r.NoError(err)
437
- r.Contains(s, "no letters")
438
- }
439
-
440
- func Test_HelperMap_Add(t *testing.T) {
441
- r := require.New(t)
442
- err := GlobalHelpers.Add("foo", func(help HelperContext) (string, error) {
443
- return "", nil
444
- })
445
- r.NoError(err)
446
- }
447
-
448
- func Test_HelperMap_Add_Invalid_NoReturn(t *testing.T) {
449
- r := require.New(t)
450
- err := GlobalHelpers.Add("foo", func(help HelperContext) {})
451
- r.Error(err)
452
- r.Contains(err.Error(), "must return at least one")
453
- }
454
-
455
- func Test_HelperMap_Add_Invalid_ReturnTypes(t *testing.T) {
456
- r := require.New(t)
457
- err := GlobalHelpers.Add("foo", func(help HelperContext) (string, string) {
458
- return "", ""
459
- })
460
- r.Error(err)
461
- r.Contains(err.Error(), "foo must return ([string|template.HTML], [error]), not (string, string)")
462
-
463
- err = GlobalHelpers.Add("foo", func(help HelperContext) int { return 1 })
464
- r.Error(err)
465
- r.Contains(err.Error(), "foo must return ([string|template.HTML], [error]), not (int)")
466
- }
handlebars/template.go DELETED
@@ -1,125 +0,0 @@
1
- package handlebars
2
-
3
- import (
4
- "fmt"
5
- "io"
6
- "net/http"
7
-
8
- "github.com/aymerick/raymond/ast"
9
- "github.com/aymerick/raymond/parser"
10
- "github.com/pkg/errors"
11
- )
12
-
13
- var stylesCss = CssContent("")
14
-
15
- type CssContent string
16
- type HtmlContent string
17
-
18
- // Template represents an input and helpers to be used
19
- // to evaluate and render the input.
20
- type Template struct {
21
- Input string
22
- Context *Context
23
- program *ast.Program
24
- }
25
-
26
- // NewTemplate from the input string.
27
- func NewTemplate(input string) (*Template, error) {
28
- t := &Template{
29
- Input: input,
30
- Context: NewContext(),
31
- }
32
- err := t.Parse()
33
- if err != nil {
34
- return t, errors.WithStack(err)
35
- }
36
- return t, nil
37
- }
38
-
39
- // Parse the template this can be called many times
40
- // as a successful result is cached and is used on subsequent
41
- // uses.
42
- func (t *Template) Parse() error {
43
- if t.program != nil {
44
- return nil
45
- }
46
- program, err := parser.Parse(t.Input)
47
- if err != nil {
48
- return errors.WithStack(err)
49
- }
50
- t.program = program
51
- return nil
52
- }
53
-
54
- // Exec the template using the content and return the results
55
- func (t *Template) RenderWithStatus(status int) (HtmlContent, int, error) {
56
- err := t.Parse()
57
- if err != nil {
58
- return HtmlContent("Server Erorr"), 500, errors.WithStack(err)
59
- }
60
- v := newEvalVisitor(t, t.Context)
61
- r := t.program.Accept(v)
62
- switch rp := r.(type) {
63
- case string:
64
- return HtmlContent(rp), status, nil
65
- case error:
66
- return HtmlContent("Server Erorr"), 500, rp
67
- case nil:
68
- return HtmlContent(""), 200, nil
69
- default:
70
- return HtmlContent("Server Erorr"), 500, errors.WithStack(errors.Errorf("unsupport eval return format %T: %+v", r, r))
71
- }
72
- }
73
-
74
- func (t *Template) Render() (HtmlContent, int, error) {
75
- return t.RenderWithStatus(200)
76
- }
77
-
78
- func (t *Template) RenderWriter(w io.Writer) (int, error) {
79
- s, status, err := t.Render()
80
- if err != nil {
81
- return status, err
82
- }
83
- if v, ok := w.(http.ResponseWriter); ok {
84
- v.WriteHeader(status)
85
- }
86
- w.Write([]byte(s))
87
- return 200, nil
88
- }
89
-
90
- func (t *Template) Prop(key string, v any) *Template {
91
- t.Context.Set(key, v)
92
- return t
93
- }
94
-
95
- func (t *Template) Props(args ...any) *Template {
96
- for i := 0; i < len(args); i += 2 {
97
- key := fmt.Sprintf("%s", args[i])
98
- t.Context.Set(key, args[i+1])
99
- }
100
- return t
101
- }
102
-
103
- func Html(tpl string) *Template {
104
- return &Template{
105
- Input: tpl,
106
- Context: NewContext(),
107
- }
108
- }
109
-
110
- func HtmlErr(status int, err error) (HtmlContent, int, error) {
111
- return HtmlContent("ErrorPage/AccessDeniedPage/NotFoundPage based on status code"), status, err
112
- }
113
-
114
- func HtmlEmpty() (HtmlContent, int, error) {
115
- return Html("").Render()
116
- }
117
-
118
- func Css(v string) CssContent {
119
- stylesCss += CssContent(v)
120
- return CssContent(v)
121
- }
122
-
123
- func GetStyles() CssContent {
124
- return stylesCss
125
- }
handlebars/template_test.go DELETED
@@ -1,89 +0,0 @@
1
- package handlebars
2
-
3
- import (
4
- "testing"
5
-
6
- "github.com/stretchr/testify/require"
7
- )
8
-
9
- func Test_Render(t *testing.T) {
10
- r := require.New(t)
11
-
12
- ctx := NewContext()
13
- ctx.Set("name", "Tim")
14
- s, err := Render("{{name}}", ctx)
15
- r.NoError(err)
16
- r.Equal("Tim", s)
17
- }
18
-
19
- func Test_Render_with_Content(t *testing.T) {
20
- r := require.New(t)
21
-
22
- ctx := NewContext()
23
- ctx.Set("name", "Tim")
24
- s, err := Render("<p>{{name}}</p>", ctx)
25
- r.NoError(err)
26
- r.Equal("<p>Tim</p>", s)
27
- }
28
-
29
- func Test_Render_Unknown_Value(t *testing.T) {
30
- r := require.New(t)
31
-
32
- ctx := NewContext()
33
- _, err := Render("<p>{{name}}</p>", ctx)
34
- r.Error(err)
35
- r.Equal("could not find value for name [line 1:3]", err.Error())
36
- }
37
-
38
- func Test_Render_with_String(t *testing.T) {
39
- r := require.New(t)
40
-
41
- ctx := NewContext()
42
- s, err := Render(`<p>{{"Tim"}}</p>`, ctx)
43
- r.NoError(err)
44
- r.Equal("<p>Tim</p>", s)
45
- }
46
-
47
- func Test_Render_with_Math(t *testing.T) {
48
- r := require.New(t)
49
-
50
- ctx := NewContext()
51
- _, err := Render(`<p>{{2 + 1}}</p>`, ctx)
52
- r.Error(err)
53
- }
54
-
55
- func Test_Render_with_Comments(t *testing.T) {
56
- r := require.New(t)
57
- ctx := NewContext()
58
- s, err := Render(`<p><!-- comment --></p>`, ctx)
59
- r.NoError(err)
60
- r.Equal("<p><!-- comment --></p>", s)
61
- }
62
-
63
- func Test_Render_with_Func(t *testing.T) {
64
- r := require.New(t)
65
- ctx := NewContext()
66
- ctx.Set("user", user{First: "Mark", Last: "Bates"})
67
- s, err := Render("{{user.FullName}}", ctx)
68
- r.NoError(err)
69
- r.Equal("Mark Bates", s)
70
- }
71
-
72
- func Test_Render_Array(t *testing.T) {
73
- r := require.New(t)
74
-
75
- ctx := NewContext()
76
- ctx.Set("names", []string{"mark", "bates"})
77
- s, err := Render("{{names}}", ctx)
78
- r.NoError(err)
79
- r.Equal("mark bates", s)
80
- }
81
-
82
- type user struct {
83
- First string
84
- Last string
85
- }
86
-
87
- func (u user) FullName() string {
88
- return u.First + " " + u.Last
89
- }
http.go CHANGED
@@ -6,7 +6,6 @@ import (
6
6
  "embed"
7
7
  "encoding/json"
8
8
  "fmt"
9
- "html/template"
10
9
  "net"
11
10
  "net/http"
12
11
  "net/url"
@@ -20,10 +19,11 @@ import (
20
19
  "sync"
21
20
  "time"
22
21
 
22
+ "github.com/alecthomas/repr"
23
23
  "github.com/go-playground/validator/v10"
24
24
  "github.com/gorilla/mux"
25
25
  "github.com/pyros2097/gromer/assets"
26
- "github.com/pyros2097/gromer/handlebars"
26
+ "github.com/pyros2097/gromer/gsx"
27
27
  "github.com/rs/zerolog"
28
28
  "github.com/rs/zerolog/log"
29
29
  "github.com/rs/zerolog/pkgerrors"
@@ -49,10 +49,10 @@ func init() {
49
49
  PartsExclude: []string{zerolog.TimestampFieldName},
50
50
  })
51
51
  }
52
- handlebars.GlobalHelpers.Add("GetStylesUrl", GetStylesUrl)
52
+ gsx.RegisterFunc(GetStylesUrl)
53
- handlebars.GlobalHelpers.Add("GetAssetUrl", GetAssetUrl)
53
+ gsx.RegisterFunc(GetAssetUrl)
54
- handlebars.GlobalHelpers.Add("GetAlpineJsUrl", GetAlpineJsUrl)
54
+ gsx.RegisterFunc(GetAlpineJsUrl)
55
- handlebars.GlobalHelpers.Add("GetHtmxJsUrl", GetHtmxJsUrl)
55
+ gsx.RegisterFunc(GetHtmxJsUrl)
56
56
  }
57
57
 
58
58
  var RouteDefs []RouteDefinition
@@ -76,104 +76,104 @@ func RegisterAssets(fs embed.FS) {
76
76
  appAssets = fs
77
77
  }
78
78
 
79
- func RegisterComponent(fn any, props ...string) {
80
- name := getFunctionName(fn)
81
- fnType := reflect.TypeOf(fn)
82
- fnValue := reflect.ValueOf(fn)
83
- handlebars.GlobalHelpers.Add(name, func(help handlebars.HelperContext) (template.HTML, error) {
84
- args := []reflect.Value{}
85
- var props any
86
- if fnType.NumIn() > 0 {
87
- structType := fnType.In(0)
88
- instance := reflect.New(structType)
89
- if structType.Kind() != reflect.Struct {
90
- log.Fatal().Msgf("component '%s' props should be a struct", name)
91
- }
92
- rv := instance.Elem()
93
- for i := 0; i < structType.NumField(); i++ {
94
- if f := rv.Field(i); f.CanSet() {
95
- jsonName := structType.Field(i).Tag.Get("json")
96
- defaultValue := structType.Field(i).Tag.Get("default")
97
- if jsonName == "children" {
98
- s, err := help.Block()
99
- if err != nil {
100
- return "", err
101
- }
102
- f.Set(reflect.ValueOf(template.HTML(s)))
103
- } else {
104
- v := help.Context.Get(jsonName)
105
- if v == nil {
106
- f.Set(reflect.ValueOf(defaultValue))
107
- } else {
108
- f.Set(reflect.ValueOf(v))
109
- }
110
- }
111
- }
112
- }
113
- args = append(args, rv)
114
- props = rv.Interface()
115
- }
116
- res := fnValue.Call(args)
117
- tpl := res[0].Interface().(*handlebars.Template)
118
- tpl.Context.Set("props", props)
119
- s, _, err := tpl.Render()
120
- if err != nil {
121
- return "", err
122
- }
123
- return template.HTML(s), nil
124
- })
125
- }
126
-
127
- func RegisterContainer(fn any, props ...string) {
128
- name := getFunctionName(fn)
129
- fnType := reflect.TypeOf(fn)
130
- fnValue := reflect.ValueOf(fn)
131
- handlebars.GlobalHelpers.Add(name, func(help handlebars.HelperContext) (template.HTML, error) {
132
- args := []reflect.Value{reflect.ValueOf(context.TODO())}
133
- var props any
134
- if fnType.NumIn() > 1 {
135
- structType := fnType.In(1)
136
- instance := reflect.New(structType)
137
- if structType.Kind() != reflect.Struct {
138
- log.Fatal().Msgf("component '%s' props should be a struct", name)
139
- }
140
- rv := instance.Elem()
141
- for i := 0; i < structType.NumField(); i++ {
142
- if f := rv.Field(i); f.CanSet() {
143
- jsonName := structType.Field(i).Tag.Get("json")
144
- defaultValue := structType.Field(i).Tag.Get("default")
145
- if jsonName == "children" {
146
- s, err := help.Block()
147
- if err != nil {
148
- return "", err
149
- }
150
- f.Set(reflect.ValueOf(template.HTML(s)))
151
- } else {
152
- v := help.Context.Get(jsonName)
153
- if v == nil {
154
- f.Set(reflect.ValueOf(defaultValue))
155
- } else {
156
- f.Set(reflect.ValueOf(v))
157
- }
158
- }
159
- }
160
- }
161
- args = append(args, rv)
162
- props = rv.Interface()
163
- }
164
- res := fnValue.Call(args)
165
- tpl := res[0].Interface().(*handlebars.Template)
166
- // if res[1].Interface() != nil {
167
- // show error in component
168
- // }
169
- tpl.Context.Set("props", props)
170
- s, _, err := tpl.Render()
171
- if err != nil {
172
- return "", err
173
- }
174
- return template.HTML(s), nil
175
- })
176
- }
79
+ // func RegisterComponent(fn any, props ...string) {
80
+ // name := getFunctionName(fn)
81
+ // fnType := reflect.TypeOf(fn)
82
+ // fnValue := reflect.ValueOf(fn)
83
+ // handlebars.GlobalHelpers.Add(name, func(help handlebars.HelperContext) (template.HTML, error) {
84
+ // args := []reflect.Value{}
85
+ // var props any
86
+ // if fnType.NumIn() > 0 {
87
+ // structType := fnType.In(0)
88
+ // instance := reflect.New(structType)
89
+ // if structType.Kind() != reflect.Struct {
90
+ // log.Fatal().Msgf("component '%s' props should be a struct", name)
91
+ // }
92
+ // rv := instance.Elem()
93
+ // for i := 0; i < structType.NumField(); i++ {
94
+ // if f := rv.Field(i); f.CanSet() {
95
+ // jsonName := structType.Field(i).Tag.Get("json")
96
+ // defaultValue := structType.Field(i).Tag.Get("default")
97
+ // if jsonName == "children" {
98
+ // s, err := help.Block()
99
+ // if err != nil {
100
+ // return "", err
101
+ // }
102
+ // f.Set(reflect.ValueOf(template.HTML(s)))
103
+ // } else {
104
+ // v := help.Context.Get(jsonName)
105
+ // if v == nil {
106
+ // f.Set(reflect.ValueOf(defaultValue))
107
+ // } else {
108
+ // f.Set(reflect.ValueOf(v))
109
+ // }
110
+ // }
111
+ // }
112
+ // }
113
+ // args = append(args, rv)
114
+ // props = rv.Interface()
115
+ // }
116
+ // res := fnValue.Call(args)
117
+ // tpl := res[0].Interface().(*handlebars.Template)
118
+ // tpl.Context.Set("props", props)
119
+ // s, _, err := tpl.Render()
120
+ // if err != nil {
121
+ // return "", err
122
+ // }
123
+ // return template.HTML(s), nil
124
+ // })
125
+ // }
126
+
127
+ // func RegisterContainer(fn any, props ...string) {
128
+ // name := getFunctionName(fn)
129
+ // fnType := reflect.TypeOf(fn)
130
+ // fnValue := reflect.ValueOf(fn)
131
+ // // shandlebars.GlobalHelpers.Add(name, func(help handlebars.HelperContext) (template.HTML, error) {
132
+ // args := []reflect.Value{reflect.ValueOf(context.TODO())}
133
+ // var props any
134
+ // if fnType.NumIn() > 1 {
135
+ // structType := fnType.In(1)
136
+ // instance := reflect.New(structType)
137
+ // if structType.Kind() != reflect.Struct {
138
+ // log.Fatal().Msgf("component '%s' props should be a struct", name)
139
+ // }
140
+ // rv := instance.Elem()
141
+ // for i := 0; i < structType.NumField(); i++ {
142
+ // if f := rv.Field(i); f.CanSet() {
143
+ // jsonName := structType.Field(i).Tag.Get("json")
144
+ // defaultValue := structType.Field(i).Tag.Get("default")
145
+ // if jsonName == "children" {
146
+ // s, err := help.Block()
147
+ // if err != nil {
148
+ // return "", err
149
+ // }
150
+ // f.Set(reflect.ValueOf(template.HTML(s)))
151
+ // } else {
152
+ // v := help.Context.Get(jsonName)
153
+ // if v == nil {
154
+ // f.Set(reflect.ValueOf(defaultValue))
155
+ // } else {
156
+ // f.Set(reflect.ValueOf(v))
157
+ // }
158
+ // }
159
+ // }
160
+ // }
161
+ // args = append(args, rv)
162
+ // props = rv.Interface()
163
+ // }
164
+ // res := fnValue.Call(args)
165
+ // tpl := res[0].Interface().(*handlebars.Template)
166
+ // // if res[1].Interface() != nil {
167
+ // // show error in component
168
+ // // }
169
+ // tpl.Context.Set("props", props)
170
+ // s, _, err := tpl.Render()
171
+ // if err != nil {
172
+ // return "", err
173
+ // }
174
+ // return template.HTML(s), nil
175
+ // })
176
+ // }
177
177
 
178
178
  func RespondError(w http.ResponseWriter, status int, err error) {
179
179
  w.Header().Set("Content-Type", "application/json")
@@ -208,7 +208,7 @@ func addRouteDef(method, route string, h interface{}) {
208
208
  pathParams := GetRouteParams(route)
209
209
  var body any = nil
210
210
  funcType := reflect.TypeOf(h)
211
- if funcType.NumIn() > len(pathParams)+1 {
211
+ if funcType.NumIn() > len(pathParams)+2 {
212
212
  structType := funcType.In(funcType.NumIn() - 1)
213
213
  instance := reflect.New(structType)
214
214
  if structType.Kind() != reflect.Struct {
@@ -226,13 +226,15 @@ func addRouteDef(method, route string, h interface{}) {
226
226
 
227
227
  func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) {
228
228
  params := GetRouteParams(route)
229
+ htmlTemplate := gsx.Html(map[string]interface{}{})
229
- args := []reflect.Value{reflect.ValueOf(ctx)}
230
+ args := []reflect.Value{reflect.ValueOf(htmlTemplate), reflect.ValueOf(ctx)}
230
231
  funcType := reflect.TypeOf(h)
231
232
  icount := funcType.NumIn()
232
233
  vars := mux.Vars(r)
233
234
  for _, k := range params {
234
235
  args = append(args, reflect.ValueOf(vars[k]))
235
236
  }
237
+ repr.Println(len(args), icount)
236
238
  if len(args) != icount {
237
239
  structType := funcType.In(icount - 1)
238
240
  instance := reflect.New(structType)
@@ -310,20 +312,20 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
310
312
  RespondError(w, responseStatus, responseError.(error))
311
313
  return
312
314
  }
313
- if v, ok := response.(handlebars.HtmlContent); ok {
315
+ if v, ok := response.(string); ok {
314
316
  w.Header().Set("Content-Type", "text/html")
315
317
  // This has to be at end always
316
318
  w.WriteHeader(responseStatus)
317
319
  w.Write([]byte(v))
318
320
  return
319
321
  }
320
- if v, ok := response.(handlebars.CssContent); ok {
322
+ // if v, ok := response.(handlebars.CssContent); ok {
321
- w.Header().Set("Content-Type", "text/css")
323
+ // w.Header().Set("Content-Type", "text/css")
322
- // This has to be at end always
324
+ // // This has to be at end always
323
- w.WriteHeader(responseStatus)
325
+ // w.WriteHeader(responseStatus)
324
- w.Write([]byte(v))
326
+ // w.Write([]byte(v))
325
- return
327
+ // return
326
- }
328
+ // }
327
329
  w.Header().Set("Content-Type", "application/json")
328
330
  // This has to be at end always
329
331
  w.WriteHeader(responseStatus)
@@ -455,7 +457,7 @@ func StatusHandler(h interface{}) http.Handler {
455
457
 
456
458
  // This has to be at end always after headers are set
457
459
  w.WriteHeader(responseStatus)
458
- w.Write([]byte(response.(handlebars.HtmlContent)))
460
+ w.Write([]byte(response.(string)))
459
461
  })).(http.Handler)
460
462
  }
461
463
 
@@ -471,7 +473,7 @@ func StylesRoute(router *mux.Router, path string) {
471
473
  router.Path(path).Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
472
474
  w.Header().Set("Content-Type", "text/css")
473
475
  w.WriteHeader(200)
474
- w.Write([]byte(handlebars.GetStyles()))
476
+ w.Write([]byte(gsx.GetStyles()))
475
477
  })
476
478
  }
477
479
 
@@ -523,7 +525,7 @@ func GetAlpineJsUrl() string {
523
525
 
524
526
  func GetStylesUrl() string {
525
527
  sum := getSum("styles.css", func() [16]byte {
526
- return md5.Sum([]byte(handlebars.GetStyles()))
528
+ return md5.Sum([]byte(gsx.GetStyles()))
527
529
  })
528
530
  return fmt.Sprintf("/styles.css?hash=%s", sum)
529
531
  }
readme.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Version](https://badge.fury.io/gh/pyros2097%2Fgromer.svg)](https://github.com/pyros2097/gromer)
4
4
 
5
5
  **gromer** is a framework and cli to build web apps in golang.
6
- It uses a declarative syntax using inline handlebar templates for components and pages.
6
+ It uses a declarative syntax using inline templates for components and pages.
7
7
  It also generates http handlers for your routes which follow a particular folder structure. Similar to other frameworks like nextjs, sveltekit.
8
8
  These handlers are also normal functions and can be imported in other packages directly. ((inspired by [Encore](https://encore.dev/)).
9
9
  More information on the templating syntax is given [here](https://github.com/pyrossh/gromer/blob/master/handlebars/README.md),
@@ -56,8 +56,8 @@ func GET(ctx context.Context, params GetParams) (HtmlContent, int, error) {
56
56
  return HtmlErr(status, err)
57
57
  }
58
58
  return Html(`
59
- {{#Page title="gromer example"}}
59
+ <Page title="gromer example">
60
- {{#Header}}{{/Header}}
60
+ <Header></Header>
61
61
  <section class="todoapp">
62
62
  <section class="main">
63
63
  <ul class="todo-list" id="todo-list">
@@ -68,7 +68,7 @@ func GET(ctx context.Context, params GetParams) (HtmlContent, int, error) {
68
68
  </section>
69
69
  {{/if}}
70
70
  </section>
71
- {{/Page}}
71
+ </Page>
72
72
  `).
73
73
  Prop("todos", todos).
74
74
  Render()