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


13e9ba96 Peter John

3 years ago
something working
_example/components/page.go CHANGED
@@ -217,9 +217,9 @@ var _ = Css(`
217
217
  }
218
218
  `)
219
219
 
220
- func Page(h Html, title string) string {
220
+ func Page(ctx Context, title string) *Node {
221
+ ctx.Set("title", title)
221
- return h.Render(`
222
+ return ctx.Render(`
222
- <!DOCTYPE html>
223
223
  <html lang="en">
224
224
  <head>
225
225
  <meta charset="UTF-8" />
@@ -228,12 +228,8 @@ func Page(h Html, title string) string {
228
228
  <title>{title}</title>
229
229
  <meta name="description" content="{title}" />
230
230
  <meta name="author" content="pyrossh" />
231
- <meta name="keywords" content="pyros.sh, pyrossh, gromer" />
231
+ <meta name="keywords" content="pyros.sh, pyrossh, gromer" />
232
232
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover" />
233
- <link rel="icon" href="{GetAssetUrl "images/icon.png"}" />
234
- <link rel="stylesheet" href="{GetStylesUrl}" />
235
- <script src="{GetAlpineJsUrl}"></script>
236
- <script src="{GetHtmxJsUrl}" defer=""></script>
237
233
  </head>
238
234
  <body>
239
235
  {children}
_example/components/todo.go CHANGED
@@ -8,14 +8,14 @@ import (
8
8
  var _ = Css(`
9
9
  `)
10
10
 
11
- func Todo(h Html, todo *todos.Todo) string {
11
+ func Todo(ctx Context, todo *todos.Todo) *Node {
12
- return h.Render(`
12
+ return ctx.Render(`
13
- <li id="todo-{todo.ID}" class={{ completed: todo.Completed }}>
13
+ <li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
14
14
  <div class="view">
15
15
  <form hx-target="#todo-{todo.ID}" hx-swap="outerHTML">
16
16
  <input type="hidden" name="intent" value="complete" />
17
17
  <input type="hidden" name="id" value="{todo.ID}" />
18
- <input hx-post="/" class="checkbox" type="checkbox" checked={{ completed: todo.Completed }} />
18
+ <input hx-post="/" class="checkbox" type="checkbox" checked="{ completed: todo.Completed }" />
19
19
  </form>
20
20
  <label>{todo.Text}</label>
21
21
  <form hx-post="/" hx-target="#todo-{todo.ID}" hx-swap="delete">
_example/containers/TodoCount.go CHANGED
@@ -1,8 +1,6 @@
1
1
  package containers
2
2
 
3
3
  import (
4
- "context"
5
-
6
4
  "github.com/pyros2097/gromer/_example/services/todos"
7
5
  . "github.com/pyros2097/gromer/gsx"
8
6
  )
@@ -18,16 +16,16 @@ var _ = Css(`
18
16
  }
19
17
  `)
20
18
 
21
- func TodoCount(h Html, ctx context.Context, filter string) (string, error) {
19
+ func TodoCount(ctx Context, filter string) (*Node, error) {
22
20
  todos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
23
21
  Filter: filter,
24
22
  Limit: 1000,
25
23
  })
26
24
  if err != nil {
27
- return "", err
25
+ return nil, err
28
26
  }
29
- h["count"] = len(todos)
27
+ ctx.Set("count", len(todos))
30
- return h.Render(`
28
+ return ctx.Render(`
31
29
  <span class="todo-count" id="todo-count" hx-swap-oob="true">
32
30
  <strong>{count}</strong> item left
33
31
  </span>
_example/containers/TodoList.go CHANGED
@@ -1,8 +1,6 @@
1
1
  package containers
2
2
 
3
3
  import (
4
- "context"
5
-
6
4
  . "github.com/pyros2097/gromer"
7
5
  "github.com/pyros2097/gromer/_example/services/todos"
8
6
  . "github.com/pyros2097/gromer/gsx"
@@ -129,27 +127,19 @@ var _ = Css(`
129
127
 
130
128
  `)
131
129
 
132
- type TodoListProps struct {
133
- ID string `json:"id"`
134
- Page int `json:"page"`
135
- Filter string `json:"filter"`
136
- }
137
-
138
- func TodoList(h Html, ctx context.Context, props TodoListProps) (string, error) {
130
+ func TodoList(ctx Context, page int, filter string) (*Node, error) {
139
- index := Default(props.Page, 1)
131
+ index := Default(page, 1)
140
132
  todos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
141
- Filter: props.Filter,
133
+ Filter: filter,
142
134
  Limit: index,
143
135
  })
144
136
  if err != nil {
145
- return "", err
137
+ return nil, err
146
- }
138
+ }
147
- h["todos"] = todos
139
+ ctx.Set("todos", todos)
148
- return h.Render(`
140
+ return ctx.Render(`
149
- <ul id="todo-list" class="relative">
141
+ <ul id="todo-list" class="relative" x-for="todo in todos">
150
- <For key="todos" itemKey="todo">
151
- <Todo key="todo"></Todo>
152
- </For>
142
+ <Todo />
153
143
  </ul>
154
144
  `), nil
155
145
  }
_example/main.go CHANGED
@@ -10,19 +10,19 @@ import (
10
10
  "github.com/pyros2097/gromer/_example/assets"
11
11
  "github.com/pyros2097/gromer/_example/components"
12
12
  "github.com/pyros2097/gromer/_example/containers"
13
- "github.com/pyros2097/gromer/_example/pages/404"
13
+ "github.com/pyros2097/gromer/_example/routes/404"
14
- "github.com/pyros2097/gromer/_example/pages"
14
+ "github.com/pyros2097/gromer/_example/routes"
15
- "github.com/pyros2097/gromer/_example/pages/about"
15
+ "github.com/pyros2097/gromer/_example/routes/about"
16
16
  "github.com/pyros2097/gromer/gsx"
17
17
 
18
18
  )
19
19
 
20
20
  func init() {
21
- gsx.RegisterComponent(components.Page)
21
+ gsx.RegisterComponent(components.Page, "title")
22
- gsx.RegisterComponent(components.Todo)
22
+ gsx.RegisterComponent(components.Todo, "todo")
23
23
 
24
- gsx.RegisterComponent(containers.TodoCount)
24
+ gsx.RegisterComponent(containers.TodoCount, "filter")
25
- gsx.RegisterComponent(containers.TodoList)
25
+ gsx.RegisterComponent(containers.TodoList, "page", "filter")
26
26
  gromer.RegisterAssets(assets.FS)
27
27
  }
28
28
 
@@ -40,8 +40,8 @@ func main() {
40
40
 
41
41
  pageRouter := baseRouter.NewRoute().Subrouter()
42
42
  // gromer.ApiExplorerRoute(pageRouter, "/explorer")
43
- gromer.Handle(pageRouter, "GET", "/", pages.GET)
43
+ gromer.Handle(pageRouter, "GET", "/", routes.GET)
44
- gromer.Handle(pageRouter, "POST", "/", pages.POST)
44
+ gromer.Handle(pageRouter, "POST", "/", routes.POST)
45
45
  gromer.Handle(pageRouter, "GET", "/about", about.GET)
46
46
 
47
47
 
_example/pages/about/get.go DELETED
@@ -1,20 +0,0 @@
1
- package about
2
-
3
- import (
4
- "context"
5
-
6
- . "github.com/pyros2097/gromer/gsx"
7
- )
8
-
9
- func GET(h Html, c context.Context) (string, int, error) {
10
- return h.Render(`
11
- {{#Page title="About me"}}
12
- <div class="flex flex-col justify-center items-center">
13
- {{#Header}}
14
- A new link is here
15
- {{/Header}}
16
- <h1>About Me</h1>
17
- </div>
18
- {{/Page}}
19
- `), 200, nil
20
- }
_example/pages/get.go DELETED
@@ -1,103 +0,0 @@
1
- package pages
2
-
3
- import (
4
- "context"
5
-
6
- _ "github.com/pyros2097/gromer/_example/components"
7
- . "github.com/pyros2097/gromer/gsx"
8
- )
9
-
10
- var _ = Css(`
11
- .todoapp {
12
- background: #fff;
13
- margin: 130px 0 40px 0;
14
- position: relative;
15
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
16
- }
17
-
18
- .todoapp input::-webkit-input-placeholder {
19
- font-style: italic;
20
- font-weight: 300;
21
- color: #e6e6e6;
22
- }
23
-
24
- .todoapp input::-moz-placeholder {
25
- font-style: italic;
26
- font-weight: 300;
27
- color: #e6e6e6;
28
- }
29
-
30
- .todoapp input::input-placeholder {
31
- font-style: italic;
32
- font-weight: 300;
33
- color: #e6e6e6;
34
- }
35
-
36
- .todoapp h1 {
37
- position: absolute;
38
- top: -155px;
39
- width: 100%;
40
- font-size: 100px;
41
- font-weight: 100;
42
- text-align: center;
43
- color: rgba(175, 47, 47, 0.15);
44
- -webkit-text-rendering: optimizeLegibility;
45
- -moz-text-rendering: optimizeLegibility;
46
- text-rendering: optimizeLegibility;
47
- }
48
- `)
49
-
50
- // var (
51
- // Container = Css(`
52
- // background: #fff;
53
- // margin: 130px 0 40px 0;
54
- // position: relative;
55
- // box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
56
- // `)
57
- // )
58
-
59
- type GetParams struct {
60
- Page int `json:"page"`
61
- Filter string `json:"filter"`
62
- }
63
-
64
- func GET(h Html, ctx context.Context, params GetParams) (string, int, error) {
65
- h["page"] = params.Page
66
- h["filter"] = params.Filter
67
- return h.Render(`
68
- <Page title="gromer example">
69
- <section class="todoapp">
70
- <header class="header">
71
- <h1>todos</h1>
72
- <form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
73
- <input type="hidden" name="intent" value="create"></input>
74
- <input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off"></input>
75
- </form>
76
- </header>
77
- <section class="main">
78
- <input class="toggle-all" id="toggle-all" type="checkbox">
79
- <label for="toggle-all">Mark all as complete</label>
80
- <TodoList id="todo-list" page={page} filter={filter}></TodoList>
81
- </section>
82
- <footer class="footer">
83
- <TodoCount filter={filter}></TodoCount>
84
- <ul class="filters">
85
- <li>
86
- <a href="?filter=all">All</a>
87
- </li>
88
- <li>
89
- <a href="?filter=active">Active</a>
90
- </li>
91
- <li>
92
- <a href="?filter=completed">Completed</a>
93
- </li>
94
- </ul>
95
- <form hx-target="#todo-list" hx-post="/">
96
- <input type="hidden" name="intent" value="clear_completed" />
97
- <button type="submit" class="clear-completed" >Clear completed</button>
98
- </form>
99
- </footer>
100
- </section>
101
- </Page>
102
- `), 200, nil
103
- }
_example/{pages → routes}/404/get.go RENAMED
@@ -6,16 +6,15 @@ import (
6
6
  . "github.com/pyros2097/gromer/gsx"
7
7
  )
8
8
 
9
- func GET(h Html, c context.Context) (string, int, error) {
9
+ func GET(h Context, c context.Context) (*Node, int, error) {
10
10
  return h.Render(`
11
- {{#Page title="Page Not Found"}}
11
+ <Page title="Page Not Found">
12
- {{#Header}}{{/Header}}
13
12
  <main class="box center">
14
13
  <h1>Page Not Found</h1>
15
14
  <h2 class="mt-6">
16
15
  <a class="is-underlined" href="/">Go Back</a>
17
- </h1>
16
+ </h2>
18
17
  </main>
19
- {{/Page}}
18
+ </Page>
20
19
  `), 404, nil
21
20
  }
_example/routes/about/get.go ADDED
@@ -0,0 +1,18 @@
1
+ package about
2
+
3
+ import (
4
+ "context"
5
+
6
+ . "github.com/pyros2097/gromer/gsx"
7
+ )
8
+
9
+ func GET(h Context, c context.Context) (*Node, int, error) {
10
+ return h.Render(`
11
+ <Page title="About me">
12
+ <div class="flex flex-col justify-center items-center">
13
+ A new link is here
14
+ P<h1>About Me</h1>
15
+ </div>
16
+ </Page>
17
+ `), 200, nil
18
+ }
_example/routes/get.go ADDED
@@ -0,0 +1,119 @@
1
+ package routes
2
+
3
+ import (
4
+ "context"
5
+
6
+ _ "github.com/pyros2097/gromer/_example/components"
7
+ . "github.com/pyros2097/gromer/gsx"
8
+ )
9
+
10
+ var _ = Css(`
11
+ .todoapp {
12
+ background: #fff;
13
+ margin: 130px 0 40px 0;
14
+ position: relative;
15
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
16
+ }
17
+
18
+ .todoapp input::-webkit-input-placeholder {
19
+ font-style: italic;
20
+ font-weight: 300;
21
+ color: #e6e6e6;
22
+ }
23
+
24
+ .todoapp input::-moz-placeholder {
25
+ font-style: italic;
26
+ font-weight: 300;
27
+ color: #e6e6e6;
28
+ }
29
+
30
+ .todoapp input::input-placeholder {
31
+ font-style: italic;
32
+ font-weight: 300;
33
+ color: #e6e6e6;
34
+ }
35
+
36
+ .todoapp h1 {
37
+ position: absolute;
38
+ top: -155px;
39
+ width: 100%;
40
+ font-size: 100px;
41
+ font-weight: 100;
42
+ text-align: center;
43
+ color: rgba(175, 47, 47, 0.15);
44
+ -webkit-text-rendering: optimizeLegibility;
45
+ -moz-text-rendering: optimizeLegibility;
46
+ text-rendering: optimizeLegibility;
47
+ }
48
+ `)
49
+
50
+ // var (
51
+ // Container = Css(`
52
+ // background: #fff;
53
+ // margin: 130px 0 40px 0;
54
+ // position: relative;
55
+ // box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
56
+ // `)
57
+ // )
58
+
59
+ type GetParams struct {
60
+ Page int `json:"page"`
61
+ Filter string `json:"filter"`
62
+ }
63
+
64
+ // func IndexLoader() {
65
+ // Data: M{},
66
+ // Meta: []*Meta{},
67
+ // Links: []*Link{},
68
+ // }
69
+
70
+ func IndexAction(h Context, ctx context.Context, params PostParams) {
71
+ }
72
+
73
+ func IndexPage() {
74
+ }
75
+
76
+ // <meta name="description" content="{title}" />
77
+ // <meta name="author" content="pyrossh" />
78
+ // <meta name="keywords" content="pyros.sh, pyrossh, gromer" />
79
+ // <meta />
80
+
81
+ func GET(h Context, params GetParams) (*Node, int, error) {
82
+ h.Meta("title", "Todos")
83
+ h.Set("page", params.Page)
84
+ h.Set("filter", params.Filter)
85
+ return h.Render(`
86
+ <section class="todoapp">
87
+ <header class="header">
88
+ <h1>todos</h1>
89
+ <form hx-post="/" hx-target="#todo-list" hx-swap="afterbegin" _="on htmx:afterOnLoad set #text.value to ''">
90
+ <input type="hidden" name="intent" value="create" />
91
+ <input class="new-todo" id="text" name="text" placeholder="What needs to be done?" autofocus="false" autocomplete="off" /s>
92
+ </form>
93
+ </header>
94
+ <section class="main">
95
+ <input class="toggle-all" id="toggle-all" type="checkbox" />
96
+ <label for="toggle-all">Mark all as complete</label>
97
+ <TodoList id="todo-list" page="{page}" filter="{filter}" />
98
+ </section>
99
+ <footer class="footer">
100
+ <TodoCount filter="{filter}" />
101
+ <ul class="filters">
102
+ <li>
103
+ <a href="?filter=all">All</a>
104
+ </li>
105
+ <li>
106
+ <a href="?filter=active">Active</a>
107
+ </li>
108
+ <li>
109
+ <a href="?filter=completed">Completed</a>
110
+ </li>
111
+ </ul>
112
+ <form hx-target="#todo-list" hx-post="/">
113
+ <input type="hidden" name="intent" value="clear_completed" />
114
+ <button type="submit" class="clear-completed" >Clear completed</button>
115
+ </form>
116
+ </footer>
117
+ </section>
118
+ `), 200, nil
119
+ }
_example/{pages → routes}/post.go RENAMED
@@ -1,7 +1,6 @@
1
- package pages
1
+ package routes
2
2
 
3
3
  import (
4
- "context"
5
4
  "fmt"
6
5
 
7
6
  _ "github.com/pyros2097/gromer/_example/components"
@@ -15,59 +14,59 @@ type PostParams struct {
15
14
  Text string `json:"text"`
16
15
  }
17
16
 
18
- func POST(h Html, ctx context.Context, params PostParams) (string, int, error) {
17
+ func POST(ctx Context, params PostParams) (*Node, int, error) {
19
18
  if params.Intent == "clear_completed" {
20
19
  allTodos, err := todos.GetAllTodo(ctx, todos.GetAllTodoParams{
21
20
  Filter: "all",
22
21
  Limit: 1000,
23
22
  })
24
23
  if err != nil {
25
- return "", 500, err
24
+ return nil, 500, err
26
25
  }
27
26
  for _, t := range allTodos {
28
27
  if t.Completed {
29
28
  _, err := todos.DeleteTodo(ctx, t.ID)
30
29
  if err != nil {
31
- return "", 500, err
30
+ return nil, 500, err
32
31
  }
33
32
  }
34
33
  }
35
- return h.Render(`
34
+ return ctx.Render(`
36
35
  <TodoList id="todo-list" filter="all" page="1"></TodoList>
37
36
  <TodoCount filter="all" page="1"></TodoCount>
38
37
  `), 200, nil
39
38
  } else if params.Intent == "create" {
40
39
  todo, err := todos.CreateTodo(ctx, params.Text)
41
40
  if err != nil {
42
- return "", 500, err
41
+ return nil, 500, err
43
42
  }
44
- h["todo"] = todo
43
+ ctx.Set("todo", todo)
45
- return h.Render(`
44
+ return ctx.Render(`
46
45
  <Todo todo=todo></Todo>
47
- <TodoCount filter="all" page=1></TodoCount>
46
+ <TodoCount filter="all" page="1"></TodoCount>
48
47
  `), 200, nil
49
48
  } else if params.Intent == "delete" {
50
49
  _, err := todos.DeleteTodo(ctx, params.ID)
51
50
  if err != nil {
52
- return "", 500, err
51
+ return nil, 500, err
53
52
  }
54
- return "", 200, nil
53
+ return nil, 200, nil
55
54
  } else if params.Intent == "complete" {
56
55
  todo, err := todos.GetTodo(ctx, params.ID)
57
56
  if err != nil {
58
- return "", 500, err
57
+ return nil, 500, err
59
58
  }
60
59
  _, err = todos.UpdateTodo(ctx, params.ID, todos.UpdateTodoParams{
61
60
  Text: todo.Text,
62
61
  Completed: !todo.Completed,
63
62
  })
64
63
  if err != nil {
65
- return "", 500, err
64
+ return nil, 500, err
66
65
  }
67
- h["todo"] = todo
66
+ ctx.Set("todo", todo)
68
- return h.Render(`
67
+ return ctx.Render(`
69
68
  {{#Todo todo=todo}}{{/Todo}}
70
69
  `), 200, nil
71
70
  }
72
- return "", 404, fmt.Errorf("Intent not specified: %s", params.Intent)
71
+ return nil, 404, fmt.Errorf("Intent not specified: %s", params.Intent)
73
72
  }
cmd/gromer/main.go CHANGED
@@ -12,9 +12,8 @@ import (
12
12
  "strings"
13
13
  "unicode"
14
14
 
15
+ "github.com/gobuffalo/velvet"
15
16
  "github.com/pyros2097/gromer"
16
- "github.com/pyros2097/gromer/handlebars"
17
- . "github.com/pyros2097/gromer/handlebars"
18
17
  "golang.org/x/mod/modfile"
19
18
  )
20
19
 
@@ -141,7 +140,7 @@ func main() {
141
140
  pageRoutes = append(pageRoutes, r)
142
141
  }
143
142
  }
144
- err = handlebars.GlobalHelpers.Add("title", func(v string) string {
143
+ err = velvet.Helpers.Add("title", func(v string) string {
145
144
  return strings.Title(strings.ToLower(v))
146
145
  })
147
146
  if err != nil {
@@ -177,7 +176,16 @@ func main() {
177
176
  log.Fatal(err)
178
177
  }
179
178
  }
179
+ ctx := velvet.NewContext()
180
+ ctx.Set("moduleName", moduleName)
181
+ ctx.Set("pageRoutes", pageRoutes)
182
+ ctx.Set("apiRoutes", apiRoutes)
183
+ ctx.Set("routeImports", routeImports)
184
+ ctx.Set("componentNames", componentNames)
185
+ ctx.Set("containerNames", containerNames)
186
+ ctx.Set("notFoundPkg", notFoundPkg)
187
+ ctx.Set("tick", "`")
180
- s, _, err := Html(`// Code generated by gromer. DO NOT EDIT.
188
+ s, err := velvet.Render(`// Code generated by gromer. DO NOT EDIT.
181
189
  package main
182
190
 
183
191
  import (
@@ -231,16 +239,7 @@ func main() {
231
239
  log.Fatal().Stack().Err(err).Msg("failed to listen")
232
240
  }
233
241
  }
234
- `).Props(
242
+ `, ctx)
235
- "moduleName", moduleName,
236
- "pageRoutes", pageRoutes,
237
- "apiRoutes", apiRoutes,
238
- "routeImports", routeImports,
239
- "componentNames", componentNames,
240
- "containerNames", containerNames,
241
- "notFoundPkg", notFoundPkg,
242
- "tick", "`",
243
- ).Render()
244
243
  if err != nil {
245
244
  panic(err)
246
245
  }
go.mod CHANGED
@@ -3,40 +3,56 @@ module github.com/pyros2097/gromer
3
3
  go 1.18
4
4
 
5
5
  require (
6
- github.com/aymerick/raymond v2.0.2+incompatible
7
- github.com/carlmjohnson/versioninfo v0.22.1
6
+ github.com/alecthomas/repr v0.1.0
8
7
  github.com/go-playground/validator/v10 v10.9.0
8
+ github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f
9
9
  github.com/gorilla/mux v1.8.0
10
10
  github.com/imdario/mergo v0.3.12
11
- github.com/pkg/errors v0.9.1
12
11
  github.com/rs/zerolog v1.26.1
13
12
  github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734
14
13
  github.com/stretchr/testify v1.7.0
15
14
  gocloud.dev v0.24.0
16
15
  golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57
16
+ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
17
17
  xojoc.pw/useragent v0.0.0-20200116211053-1ec61d55e8fe
18
18
  )
19
19
 
20
20
  require (
21
21
  cloud.google.com/go v0.94.0 // indirect
22
22
  cloud.google.com/go/firestore v1.5.0 // indirect
23
- github.com/alecthomas/participle/v2 v2.0.0-alpha9 // indirect
24
- github.com/alecthomas/repr v0.1.0 // indirect
23
+ github.com/aymerick/douceur v0.2.0 // indirect
24
+ github.com/aymerick/raymond v2.0.2+incompatible // indirect
25
25
  github.com/blang/semver v3.5.1+incompatible // indirect
26
26
  github.com/davecgh/go-spew v1.1.1 // indirect
27
27
  github.com/go-playground/locales v0.14.0 // indirect
28
28
  github.com/go-playground/universal-translator v0.18.0 // indirect
29
+ github.com/gobuffalo/envy v1.6.5 // indirect
29
30
  github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
30
31
  github.com/golang/protobuf v1.5.2 // indirect
31
32
  github.com/google/go-cmp v0.5.6 // indirect
32
33
  github.com/google/uuid v1.3.0 // indirect
33
34
  github.com/google/wire v0.5.0 // indirect
34
35
  github.com/googleapis/gax-go/v2 v2.1.0 // indirect
36
+ github.com/gorilla/css v1.0.0 // indirect
37
+ github.com/joho/godotenv v1.3.0 // indirect
35
38
  github.com/leodido/go-urn v1.2.1 // indirect
39
+ github.com/markbates/inflect v1.0.4 // indirect
40
+ github.com/microcosm-cc/bluemonday v1.0.18 // indirect
41
+ github.com/pkg/errors v0.9.1 // indirect
36
42
  github.com/pmezard/go-difflib v1.0.0 // indirect
43
+ github.com/russross/blackfriday v1.5.2 // indirect
44
+ github.com/sergi/go-diff v1.2.0 // indirect
45
+ github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 // indirect
46
+ github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 // indirect
47
+ github.com/shurcooL/go-goon v1.0.0 // indirect
48
+ github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect
49
+ github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b // indirect
50
+ github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c // indirect
51
+ github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
52
+ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
53
+ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
37
54
  go.opencensus.io v0.23.0 // indirect
38
55
  golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e // indirect
39
- golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
40
56
  golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
41
57
  golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
42
58
  golang.org/x/text v0.3.7 // indirect
go.sum CHANGED
@@ -99,9 +99,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
99
99
  github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
100
100
  github.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1KT/jNjDw7Z5qxAEtIiERJ2sXjnII=
101
101
  github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
102
- github.com/alecthomas/participle/v2 v2.0.0-alpha9 h1:TnflwDbtf5/aG6JMbmdiA+YB3bLg0sc6yRtmAfedfN4=
103
- github.com/alecthomas/participle/v2 v2.0.0-alpha9/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA=
104
- github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
105
102
  github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
106
103
  github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
107
104
  github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -130,13 +127,13 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 h1:1at4e5P+lvHNl2nUktdM2/v+rpICg
130
127
  github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM=
131
128
  github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc=
132
129
  github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
130
+ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
131
+ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
133
132
  github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
134
133
  github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
135
134
  github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
136
135
  github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
137
136
  github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
138
- github.com/carlmjohnson/versioninfo v0.22.1 h1:NVwTmCUpSoxBxy8+z10CbyBazeRZ4R2n6QgrNi3Wd6M=
139
- github.com/carlmjohnson/versioninfo v0.22.1/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
140
137
  github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
141
138
  github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
142
139
  github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -188,6 +185,10 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
188
185
  github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
189
186
  github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
190
187
  github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
188
+ github.com/gobuffalo/envy v1.6.5 h1:X3is06x7v0nW2xiy2yFbbIjwHz57CD6z6MkvqULTCm8=
189
+ github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
190
+ github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f h1:ddIdPdlkAgKMB0mbkft2LT3oxN1h3MN1fopCFrOgkhY=
191
+ github.com/gobuffalo/velvet v0.0.0-20170320144106-d97471bf5d8f/go.mod h1:m9x1vDSQYrGiEhEiu0c4XuE0SZzw31Ms8ULjGdhaA54=
191
192
  github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
192
193
  github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
193
194
  github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
@@ -283,6 +284,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
283
284
  github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
284
285
  github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
285
286
  github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
287
+ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
288
+ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
286
289
  github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
287
290
  github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
288
291
  github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -297,6 +300,7 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht
297
300
  github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
298
301
  github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
299
302
  github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
303
+ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
300
304
  github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
301
305
  github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
302
306
  github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -316,9 +320,13 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
316
320
  github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
317
321
  github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
318
322
  github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
323
+ github.com/markbates/inflect v1.0.4 h1:5fh1gzTFhfae06u3hzHYO9xe3l3v3nW5Pwt3naLTP5g=
324
+ github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
319
325
  github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
320
326
  github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
321
327
  github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
328
+ github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo=
329
+ github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
322
330
  github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
323
331
  github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
324
332
  github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@@ -339,11 +347,34 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
339
347
  github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
340
348
  github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
341
349
  github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
350
+ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
351
+ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
342
352
  github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734 h1:Cpx2WLIv6fuPvaJAHNhYOgYzk/8RcJXu/8+mOrxf2KM=
343
353
  github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734/go.mod h1:hqVOMAwu+ekffC3Tvq5N1ljnXRrFKcaSjbCmQ8JgYaI=
354
+ github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
355
+ github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
356
+ github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629 h1:86e54L0i3pH3dAIA8OxBbfLrVyhoGpnNk1iJCigAWYs=
357
+ github.com/shurcooL/github_flavored_markdown v0.0.0-20210228213109-c3a9aa474629/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
358
+ github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ=
359
+ github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
360
+ github.com/shurcooL/go-goon v1.0.0 h1:BCQPvxGkHHJ4WpBO4m/9FXbITVIsvAm/T66cCcCGI7E=
361
+ github.com/shurcooL/go-goon v1.0.0/go.mod h1:2wTHMsGo7qnpmqA8ADYZtP4I1DD94JpXGQ3Dxq2YQ5w=
362
+ github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 h1:KaKXZldeYH73dpQL+Nr38j1r5BgpAYQjYvENOUpIZDQ=
363
+ github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
364
+ github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b h1:rBIwpb5ggtqf0uZZY5BPs1sL7njUMM7I8qD2jiou70E=
365
+ github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
366
+ github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c h1:p3w+lTqXulfa3aDeycxmcLJDNxyUB89gf2/XqqK3eO0=
367
+ github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
368
+ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
369
+ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
370
+ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
371
+ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
372
+ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
373
+ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
344
374
  github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
345
375
  github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
346
376
  github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
377
+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
347
378
  github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
348
379
  github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
349
380
  github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -775,11 +806,13 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur
775
806
  google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
776
807
  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
777
808
  gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
809
+ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
778
810
  gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
779
811
  gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
780
812
  gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
781
813
  gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
782
814
  gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
815
+ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
783
816
  gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
784
817
  gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
785
818
  gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gsx/gsx.go CHANGED
@@ -2,6 +2,7 @@ package gsx
2
2
 
3
3
  import (
4
4
  "bytes"
5
+ "context"
5
6
  "fmt"
6
7
  "io"
7
8
  "reflect"
@@ -9,6 +10,7 @@ import (
9
10
  "runtime"
10
11
  "strings"
11
12
 
13
+ "github.com/alecthomas/repr"
12
14
  "golang.org/x/net/html"
13
15
  "golang.org/x/net/html/atom"
14
16
  )
@@ -31,29 +33,92 @@ type (
31
33
  Func interface{}
32
34
  Args []string
33
35
  }
36
+ link struct {
37
+ Rel string
38
+ Href string
39
+ Type string
40
+ As string
41
+ }
42
+ Context struct {
43
+ context.Context
34
- Html map[string]interface{}
44
+ data map[string]interface{}
45
+ metas map[string]string
46
+ links map[string]link
47
+ scripts map[string]bool
48
+ }
35
49
  Node struct {
36
- *html.Node
50
+ html.Node
37
51
  }
38
52
  )
39
53
 
54
+ func NewContext(c context.Context) Context {
55
+ return Context{Context: c, data: map[string]interface{}{}, metas: map[string]string{}, links: map[string]link{}, scripts: map[string]bool{}}
56
+ }
57
+
58
+ func (h Context) Get(k string) interface{} {
59
+ return h.data[k]
60
+ }
61
+
62
+ func (h Context) Set(k string, v interface{}) {
63
+ h.data[k] = v
64
+ }
65
+
66
+ func (h Context) Meta(k, v string) {
67
+ h.metas[k] = v
68
+ }
69
+
70
+ func (h Context) Link(rel, href, t, as string) {
71
+ h.links[href] = link{rel, href, t, as}
72
+ }
73
+
74
+ func (h Context) Script(src string, sdefer bool) {
75
+ h.scripts[src] = sdefer
76
+ }
77
+
40
- func (h Html) Render(tpl string) Node {
78
+ func (h Context) Render(tpl string) *Node {
41
79
  newTpl := stripWhitespace(tpl)
42
80
  doc, err := html.ParseFragment(bytes.NewBuffer([]byte(newTpl)), contextNode)
43
81
  if err != nil {
44
82
  panic(err)
45
83
  }
46
84
  populate(h, doc[0])
47
- return Node{doc[0]}
85
+ return &Node{*doc[0]}
48
86
  }
49
87
 
50
- func (n Node) Write(w io.Writer) error {
88
+ func (n *Node) Write(ctx Context, w io.Writer) {
89
+ w.Write([]byte(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">`))
90
+ w.Write([]byte(`<meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta content="utf-8" http-equiv="encoding">`))
91
+ w.Write([]byte(`<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover">`))
92
+ for k, v := range ctx.metas {
93
+ w.Write([]byte(fmt.Sprintf(`<meta name="%s" content="%s">`, k, v)))
94
+ }
95
+ for k, v := range ctx.metas {
96
+ if k == "title" {
97
+ w.Write([]byte(fmt.Sprintf(`<title>%s</title>`, v)))
98
+ }
99
+ }
100
+ for _, v := range ctx.links {
101
+ if v.Type != "" || v.As != "" {
102
+ w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s" type="%s" as="%s">`, v.Rel, v.Href, v.Type, v.As)))
103
+ } else {
104
+ w.Write([]byte(fmt.Sprintf(`<link rel="%s" href="%s">`, v.Rel, v.Href)))
105
+ }
106
+ }
107
+ for src, sdefer := range ctx.scripts {
108
+ if sdefer {
109
+ w.Write([]byte(fmt.Sprintf(`<script src="%s" defer="true"></script>`, src)))
110
+ } else {
111
+ w.Write([]byte(fmt.Sprintf(`<script src="%s"></script>`, src)))
112
+ }
113
+ }
114
+ w.Write([]byte(`</head><body>`))
51
- return html.Render(w, n.Node)
115
+ html.Render(w, &n.Node)
116
+ w.Write([]byte(`</body></html>`))
52
117
  }
53
118
 
54
- func (n Node) String() string {
119
+ func (n *Node) String() string {
55
120
  b := bytes.NewBuffer(nil)
56
- html.Render(b, n.Node)
121
+ html.Render(b, &n.Node)
57
122
  return b.String()
58
123
  }
59
124
 
@@ -121,18 +186,18 @@ func convert(ref string, i interface{}) interface{} {
121
186
  return nil
122
187
  }
123
188
 
124
- func getRefValue(ctx map[string]interface{}, ref string) interface{} {
189
+ func getRefValue(ctx Context, ref string) interface{} {
125
190
  if f, ok := funcMap[ref]; ok {
126
191
  return f.(func() string)()
127
192
  } else {
128
193
  parts := strings.Split(strings.ReplaceAll(ref, "!", ""), ".")
129
194
  if len(parts) == 2 {
130
- if v, ok := ctx[parts[0]]; ok {
195
+ if v, ok := ctx.data[parts[0]]; ok {
131
196
  i := reflect.ValueOf(v).Elem().FieldByName(parts[1]).Interface()
132
197
  return convert(ref, i)
133
198
  }
134
199
  }
135
- return convert(ref, ctx[ref])
200
+ return convert(ref, ctx.data[ref])
136
201
  }
137
202
  }
138
203
 
@@ -140,7 +205,7 @@ func removeBrackets(v string) string {
140
205
  return strings.ReplaceAll(strings.ReplaceAll(v, "{", ""), "}", "")
141
206
  }
142
207
 
143
- func substituteString(ctx map[string]interface{}, v string) string {
208
+ func substituteString(ctx Context, v string) string {
144
209
  found := refRegex.FindString(v)
145
210
  if found != "" {
146
211
  varName := removeBrackets(found)
@@ -201,7 +266,7 @@ func cloneNode(n *html.Node) *html.Node {
201
266
  return newNode
202
267
  }
203
268
 
204
- func populate(ctx Html, n *html.Node) {
269
+ func populate(ctx Context, n *html.Node) {
205
270
  if n.Type == html.TextNode {
206
271
  if n.Data != "" && strings.Contains(n.Data, "{") && n.Data != "{children}" {
207
272
  n.Data = substituteString(ctx, n.Data)
@@ -213,15 +278,28 @@ func populate(ctx Html, n *html.Node) {
213
278
  arr := strings.Split(xfor, " in ")
214
279
  ctxItemKey := arr[0]
215
280
  ctxKey := arr[1]
216
- data := ctx[ctxKey]
281
+ data := ctx.data[ctxKey]
217
282
  switch reflect.TypeOf(data).Kind() {
218
283
  case reflect.Slice:
219
284
  v := reflect.ValueOf(data)
285
+ if v.Len() == 0 {
286
+ if n.FirstChild != nil {
287
+ n.RemoveChild(n.FirstChild)
288
+ }
289
+ continue
290
+ }
291
+ repr.Println("AAAAAAA", n.Data)
292
+ if n.FirstChild == nil {
293
+ continue
294
+ }
220
295
  firstChild := cloneNode(n.FirstChild)
221
296
  n.RemoveChild(n.FirstChild)
222
297
  for i := 0; i < v.Len(); i++ {
298
+ compCtx := Context{
299
+ Context: ctx.Context,
223
- compCtx := map[string]interface{}{
300
+ data: map[string]interface{}{
224
- ctxItemKey: v.Index(i).Interface(),
301
+ ctxItemKey: v.Index(i).Interface(),
302
+ },
225
303
  }
226
304
  itemChild := cloneNode(firstChild)
227
305
  itemChild.Parent = nil
@@ -261,21 +339,20 @@ func populate(ctx Html, n *html.Node) {
261
339
  }
262
340
  }
263
341
  }
342
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
343
+ populate(ctx, c)
344
+ }
264
345
  if comp, ok := compMap[n.Data]; ok {
265
346
  newNode := populateComponent(ctx, comp, n, true)
266
347
  n.AppendChild(newNode)
267
348
  }
268
- for c := n.FirstChild; c != nil; c = c.NextSibling {
269
- populate(ctx, c)
270
- }
271
349
  }
272
350
  }
273
351
 
274
- func renderComponent(ctx Html, comp ComponentFunc, n *html.Node) Node {
352
+ func renderComponent(ctx Context, comp ComponentFunc, n *html.Node) *Node {
275
- h := Html(ctx)
276
- args := []reflect.Value{reflect.ValueOf(h)}
353
+ args := []reflect.Value{reflect.ValueOf(ctx)}
277
354
  for _, arg := range comp.Args {
278
- if v, ok := ctx[arg]; ok {
355
+ if v, ok := ctx.data[arg]; ok {
279
356
  args = append(args, reflect.ValueOf(v))
280
357
  } else {
281
358
  v := getAttribute(arg, n.Attr)
@@ -283,11 +360,11 @@ func renderComponent(ctx Html, comp ComponentFunc, n *html.Node) Node {
283
360
  }
284
361
  }
285
362
  result := reflect.ValueOf(comp.Func).Call(args)
286
- compNode := result[0].Interface().(Node)
363
+ compNode := result[0].Interface().(*Node)
287
364
  return compNode
288
365
  }
289
366
 
290
- func populateComponent(ctx Html, comp ComponentFunc, n *html.Node, remove bool) *html.Node {
367
+ func populateComponent(ctx Context, comp ComponentFunc, n *html.Node, remove bool) *html.Node {
291
368
  compNode := renderComponent(ctx, comp, n)
292
369
  if n.FirstChild != nil {
293
370
  newChild := cloneNode(n.FirstChild)
@@ -298,7 +375,9 @@ func populateComponent(ctx Html, comp ComponentFunc, n *html.Node, remove bool)
298
375
  if !remove {
299
376
  populate(ctx, newChild)
300
377
  }
378
+ if compNode.FirstChild != nil {
301
- populateChildren(compNode.FirstChild, newChild)
379
+ populateChildren(compNode.FirstChild, newChild)
380
+ }
302
381
  }
303
- return compNode.Node
382
+ return &compNode.Node
304
383
  }
gsx/gsx_test.go CHANGED
@@ -12,7 +12,7 @@ type TodoData struct {
12
12
  Completed bool
13
13
  }
14
14
 
15
- func Todo(h Html, todo *TodoData) Node {
15
+ func Todo(h Context, todo *TodoData) *Node {
16
16
  return h.Render(`
17
17
  <li id="todo-{todo.ID}" class="{ completed: todo.Completed }">
18
18
  <div class="view">
@@ -28,6 +28,14 @@ func Todo(h Html, todo *TodoData) Node {
28
28
  `)
29
29
  }
30
30
 
31
+ func TodoList(ctx Context, todos []*TodoData) (*Node, error) {
32
+ return ctx.Render(`
33
+ <ul id="todo-list" class="relative" x-for="todo in todos">
34
+ <Todo />
35
+ </ul>
36
+ `), nil
37
+ }
38
+
31
39
  func WebsiteName() string {
32
40
  return "My Website"
33
41
  }
@@ -36,9 +44,11 @@ func TestComponent(t *testing.T) {
36
44
  r := require.New(t)
37
45
  RegisterComponent(Todo, "todo")
38
46
  RegisterFunc(WebsiteName)
47
+ h := Context{
39
- h := Html(map[string]interface{}{
48
+ data: map[string]interface{}{
40
- "todo": &TodoData{ID: "4", Text: "My fourth todo", Completed: false},
49
+ "todo": &TodoData{ID: "4", Text: "My fourth todo", Completed: false},
41
- })
50
+ },
51
+ }
42
52
  actual := h.Render(`
43
53
  <div>
44
54
  <Todo>
@@ -67,13 +77,15 @@ func TestFor(t *testing.T) {
67
77
  r := require.New(t)
68
78
  RegisterComponent(Todo, "todo")
69
79
  RegisterFunc(WebsiteName)
80
+ h := Context{
70
- h := Html(map[string]interface{}{
81
+ data: map[string]interface{}{
71
- "todos": []*TodoData{
82
+ "todos": []*TodoData{
72
- {ID: "1", Text: "My first todo", Completed: true},
83
+ {ID: "1", Text: "My first todo", Completed: true},
73
- {ID: "2", Text: "My second todo", Completed: false},
84
+ {ID: "2", Text: "My second todo", Completed: false},
74
- {ID: "3", Text: "My third todo", Completed: false},
85
+ {ID: "3", Text: "My third todo", Completed: false},
86
+ },
75
87
  },
76
- })
88
+ }
77
89
  actual := h.Render(`
78
90
  <div>
79
91
  <ul x-for="todo in todos" class="relative">
@@ -121,3 +133,65 @@ func TestFor(t *testing.T) {
121
133
  `)
122
134
  r.Equal(expected, actual)
123
135
  }
136
+
137
+ func TestForComponent(t *testing.T) {
138
+ r := require.New(t)
139
+ RegisterComponent(Todo, "todo")
140
+ RegisterComponent(TodoList, "todos")
141
+ RegisterFunc(WebsiteName)
142
+ h := Context{
143
+ data: map[string]interface{}{
144
+ "todos": []*TodoData{
145
+ {ID: "1", Text: "My first todo", Completed: true},
146
+ {ID: "2", Text: "My second todo", Completed: false},
147
+ {ID: "3", Text: "My third todo", Completed: false},
148
+ },
149
+ },
150
+ }
151
+ actual := h.Render(`
152
+ <div>
153
+ <TodoList />
154
+ </div>
155
+ `).String()
156
+ expected := stripWhitespace(`
157
+ <div>
158
+ <ul id="todo-list" class="relative" x-for="todo in todos">
159
+ <li id="todo-1" class="completed">
160
+ <div class="view"><span>My first todo</span><span>My first todo</span></div>
161
+ <div class="todo-panel"><span>My first todo</span><span>true</span></div>
162
+ <div class="count"><span>true</span><span>true</span></div>
163
+ </li>
164
+ <li id="todo-2" class="">
165
+ <div class="view"><span>My second todo</span><span>My second todo</span></div>
166
+ <div class="todo-panel"><span>My second todo</span><span>false</span></div>
167
+ <div class="count"><span>false</span><span>false</span></div>
168
+ </li>
169
+ <li id="todo-3" class="">
170
+ <div class="view"><span>My third todo</span><span>My third todo</span></div>
171
+ <div class="todo-panel"><span>My third todo</span><span>false</span></div>
172
+ <div class="count"><span>false</span><span>false</span></div>
173
+ </li>
174
+ </ul>
175
+ </div>
176
+ `)
177
+ r.Equal(expected, actual)
178
+ }
179
+
180
+ // func TestPage(t *testing.T) {
181
+ // r := require.New(t)
182
+ // RegisterFunc(WebsiteName)
183
+ // h := Context{
184
+ // data: map[string]interface{}{},
185
+ // }
186
+ // actual := h.Render(`
187
+ // <Page title="test">
188
+ // <span>Hello</span>
189
+ // </Page}>
190
+ // `).String()
191
+ // expected := stripWhitespace(`
192
+ // <page title="test">
193
+ // <meta charset="UTF-8"/>
194
+ // </page>
195
+ // `)
196
+ // r.Equal(expected, actual)
197
+ // }
http.go CHANGED
@@ -21,6 +21,7 @@ import (
21
21
 
22
22
  "github.com/alecthomas/repr"
23
23
  "github.com/go-playground/validator/v10"
24
+ "github.com/google/uuid"
24
25
  "github.com/gorilla/mux"
25
26
  "github.com/pyros2097/gromer/assets"
26
27
  "github.com/pyros2097/gromer/gsx"
@@ -224,10 +225,15 @@ func addRouteDef(method, route string, h interface{}) {
224
225
  })
225
226
  }
226
227
 
227
- func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) {
228
+ func PerformRequest(route string, h interface{}, ctx context.Context, w http.ResponseWriter, r *http.Request) {
228
229
  params := GetRouteParams(route)
229
- htmlTemplate := gsx.Html(map[string]interface{}{})
230
+ renderContext := gsx.NewContext(ctx)
231
+ renderContext.Set("requestId", uuid.NewString())
232
+ renderContext.Link("rel", GetAssetUrl("images/icon.png"), "", "")
233
+ renderContext.Link("stylesheet", GetStylesUrl(), "", "")
234
+ renderContext.Script(GetAlpineJsUrl(), true)
235
+ renderContext.Script(GetHtmxJsUrl(), false)
230
- args := []reflect.Value{reflect.ValueOf(htmlTemplate), reflect.ValueOf(ctx)}
236
+ args := []reflect.Value{reflect.ValueOf(renderContext)}
231
237
  funcType := reflect.TypeOf(h)
232
238
  icount := funcType.NumIn()
233
239
  vars := mux.Vars(r)
@@ -312,11 +318,11 @@ func PerformRequest(route string, h interface{}, ctx interface{}, w http.Respons
312
318
  RespondError(w, responseStatus, responseError.(error))
313
319
  return
314
320
  }
315
- if v, ok := response.(string); ok {
321
+ if v, ok := response.(*gsx.Node); ok {
316
322
  w.Header().Set("Content-Type", "text/html")
317
323
  // This has to be at end always
318
324
  w.WriteHeader(responseStatus)
319
- w.Write([]byte(v))
325
+ v.Write(renderContext, w)
320
326
  return
321
327
  }
322
328
  // if v, ok := response.(handlebars.CssContent); ok {
@@ -445,7 +451,7 @@ func CacheMiddleware(next http.Handler) http.Handler {
445
451
  func StatusHandler(h interface{}) http.Handler {
446
452
  return LogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
447
453
  ctx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
448
- values := reflect.ValueOf(h).Call([]reflect.Value{reflect.ValueOf(ctx)})
454
+ values := reflect.ValueOf(h).Call([]reflect.Value{reflect.ValueOf(map[string]interface{}{}), reflect.ValueOf(ctx)})
449
455
  response := values[0].Interface()
450
456
  responseStatus := values[1].Interface().(int)
451
457
  responseError := values[2].Interface()
@@ -457,7 +463,7 @@ func StatusHandler(h interface{}) http.Handler {
457
463
 
458
464
  // This has to be at end always after headers are set
459
465
  w.WriteHeader(responseStatus)
460
- w.Write([]byte(response.(string)))
466
+ response.(*gsx.Node).Write(gsx.NewContext(r.Context()), w)
461
467
  })).(http.Handler)
462
468
  }
463
469