~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
564b4fef
—
Peter John 3 years ago
improve codegen
- api_explorer.go +5 -340
- cmd/gromer/main.go +14 -120
- css.go +0 -536
- example/assets/assets.go +8 -0
- example/components/header.go +2 -2
- example/components/page.go +2 -2
- example/context/context.go +0 -13
- example/main.go +11 -88
- example/pages/about/get.go +11 -3
- example/pages/api/recover/get.go +2 -2
- example/pages/api/todos/_todoId_/delete.go +2 -0
- example/pages/get.go +1 -0
- example/readme.md +0 -1
- hooks.go +0 -44
- hooks_test.go +0 -33
- http.go +30 -0
- readme.md +2 -0
- utils.go +4 -0
api_explorer.go
CHANGED
|
@@ -1,348 +1,13 @@
|
|
|
1
1
|
package gromer
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"context"
|
|
5
4
|
"encoding/json"
|
|
6
|
-
"fmt"
|
|
7
5
|
)
|
|
8
6
|
|
|
9
|
-
func
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
func Option(uis ...interface{}) *Element {
|
|
14
|
-
return NewElement("option", false, uis...)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
func Table(uis ...interface{}) *Element {
|
|
18
|
-
return NewElement("table", false, uis...)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
func TR(uis ...interface{}) *Element {
|
|
22
|
-
return NewElement("tr", false, uis...)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
func TD(uis ...interface{}) *Element {
|
|
26
|
-
return NewElement("td", false, uis...)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
func Section(title string) *Element {
|
|
30
|
-
return Div(Css("text-gray-700 text-sm font-bold uppercase pl-2 pt-2 pb-2 bg-gray-50 border-b border-gray-200"), Text(title))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type ApiDefinition struct {
|
|
34
|
-
Method string `json:"method"`
|
|
35
|
-
Path string `json:"path"`
|
|
36
|
-
PathParams []string `json:"pathParams"`
|
|
37
|
-
Params map[string]interface{} `json:"params"`
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
func ApiExplorer(apiDefs []ApiDefinition) func(c context.Context) (HtmlPage, int, error) {
|
|
41
|
-
return func(c context.Context) (HtmlPage, int, error) {
|
|
42
|
-
data, err := json.Marshal(apiDefs)
|
|
43
|
-
if err != nil {
|
|
44
|
-
return Html(nil, nil), 500, err
|
|
45
|
-
}
|
|
46
|
-
options := []interface{}{ID("api-select"), Css("form-select block")}
|
|
47
|
-
for i, c := range apiDefs {
|
|
48
|
-
options = append(options, Option(Attr("value", fmt.Sprintf("%d", i)), Div(Text(fmt.Sprintf("%0.6s %s", c.Method, c.Path)))))
|
|
49
|
-
}
|
|
50
|
-
return Html(
|
|
51
|
-
Head(
|
|
52
|
-
Title("Example"),
|
|
53
|
-
Meta("description", "Example"),
|
|
54
|
-
Meta("author", "pyros.sh"),
|
|
55
|
-
Meta("keywords", "pyros.sh, gromer"),
|
|
56
|
-
Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
|
|
57
|
-
Link("icon", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAMFBMVEU0OkArMjhobHEoPUPFEBIuO0L+AAC2FBZ2JyuNICOfGx7xAwTjCAlCNTvVDA1aLzQ3COjMAAAAVUlEQVQI12NgwAaCDSA0888GCItjn0szWGBJTVoGSCjWs8TleQCQYV95evdxkFT8Kpe0PLDi5WfKd4LUsN5zS1sKFolt8bwAZrCaGqNYJAgFDEpQAAAzmxafI4vZWwAAAABJRU5ErkJggg=="),
|
|
58
|
-
Link("stylesheet", "https://cdn.jsdelivr.net/npm/codemirror@5.63.1/lib/codemirror.css"),
|
|
59
|
-
StyleTag(Text(`
|
|
60
|
-
html, body {
|
|
61
|
-
height: 100vh;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#left .CodeMirror {
|
|
65
|
-
height: 400px;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#right .CodeMirror {
|
|
69
|
-
height: calc(100vh - 60px);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.form-select {
|
|
73
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23a0aec0'%3e%3cpath d='M15.3 9.3a1 1 0 0 1 1.4 1.4l-4 4a1 1 0 0 1-1.4 0l-4-4a1 1 0 0 1 1.4-1.4l3.3 3.29 3.3-3.3z'/%3e%3c/svg%3e");
|
|
74
|
-
-webkit-appearance: none;
|
|
75
|
-
-moz-appearance: none;
|
|
76
|
-
appearance: none;
|
|
77
|
-
-webkit-print-color-adjust: exact;
|
|
78
|
-
color-adjust: exact;
|
|
79
|
-
background-repeat: no-repeat;
|
|
80
|
-
background-color: #fff;
|
|
81
|
-
border-color: #e2e8f0;
|
|
82
|
-
border-width: 1px;
|
|
83
|
-
border-radius: 0.25rem;
|
|
84
|
-
padding-top: 0.5rem;
|
|
85
|
-
padding-right: 2.5rem;
|
|
86
|
-
padding-bottom: 0.5rem;
|
|
87
|
-
padding-left: 0.75rem;
|
|
88
|
-
font-size: 1rem;
|
|
89
|
-
line-height: 1.5;
|
|
90
|
-
background-position: right 0.5rem center;
|
|
91
|
-
background-size: 1.5em 1.5em;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.form-select:focus {
|
|
95
|
-
outline: none;
|
|
96
|
-
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
|
|
97
|
-
border-color: #63b3ed;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
table {
|
|
101
|
-
width: 100%;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
tr {
|
|
105
|
-
width: 100%;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
td, th {
|
|
109
|
-
border-bottom: 1px solid rgb(204, 204, 204);
|
|
110
|
-
border-left: 1px solid rgb(204, 204, 204);
|
|
111
|
-
text-align: left;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
textarea:focus, input:focus{
|
|
115
|
-
outline: none;
|
|
116
|
-
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
|
|
117
|
-
border-color: #63b3ed;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
*:focus {
|
|
121
|
-
outline: none;
|
|
122
|
-
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
|
|
123
|
-
border-color: #63b3ed;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.spinner {
|
|
127
|
-
animation: rotate 2s linear infinite;
|
|
128
|
-
width: 24px;
|
|
129
|
-
height: 24px;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.spinner .path {
|
|
133
|
-
stroke: rgba(249, 250, 251, 1);
|
|
134
|
-
stroke-linecap: round;
|
|
135
|
-
animation: dash 1.5s ease-in-out infinite;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
@keyframes rotate {
|
|
139
|
-
100% {
|
|
140
|
-
transform: rotate(360deg);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@keyframes dash {
|
|
145
|
-
0% {
|
|
146
|
-
stroke-dasharray: 1, 150;
|
|
147
|
-
stroke-dashoffset: 0;
|
|
148
|
-
}
|
|
149
|
-
50% {
|
|
150
|
-
stroke-dasharray: 90, 150;
|
|
151
|
-
stroke-dashoffset: -35;
|
|
152
|
-
}
|
|
153
|
-
100% {
|
|
154
|
-
stroke-dasharray: 90, 150;
|
|
155
|
-
stroke-dashoffset: -124;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
`)),
|
|
159
|
-
Script(Src("https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.1/codemirror.min.js")),
|
|
160
|
-
Script(Src("https://cdn.jsdelivr.net/npm/codemirror@5.63.1/mode/javascript/javascript.js")),
|
|
161
|
-
),
|
|
162
|
-
Body(
|
|
163
|
-
Div(Css("flex flex-col"),
|
|
164
|
-
Div(Css("flex w-full p-2 bg-gray-50 border-b border-gray-200 items-center justify-start"),
|
|
165
|
-
Div(Css("flex mr-4 text-gray-700 text-2xl font-bold"), Text("API Explorer")),
|
|
166
|
-
Div(Css("text-xl"),
|
|
167
|
-
Select(options...),
|
|
168
|
-
),
|
|
169
|
-
Div(Css("flex ml-3 mr-3"), Button(ID("run"), Css("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"),
|
|
170
|
-
Text("RUN"),
|
|
171
|
-
)),
|
|
172
|
-
),
|
|
173
|
-
Div(Css("flex"),
|
|
174
|
-
Div(Css("flex flex-row"), Style("width: 50%;"),
|
|
175
|
-
Div(Css("pr-8 border-r border-gray-300"), Style("background: #f7f7f7;")),
|
|
176
|
-
Div(Css("w-full"),
|
|
177
|
-
Section("Headers"),
|
|
178
|
-
Table(ID("headersTable"),
|
|
179
|
-
TR(
|
|
180
|
-
TD(Input(Css("w-full p-1"), Attr("value", "Authorization"))),
|
|
181
|
-
TD(Input(Css("w-full p-1"))),
|
|
182
|
-
),
|
|
183
|
-
),
|
|
184
|
-
Section("Path Params"),
|
|
185
|
-
Table(ID("pathParamsTable"),
|
|
186
|
-
TR(
|
|
187
|
-
TD(Css("text-gray-700"), Style("width: 50%;"), Div(Css("p-1"), Text("123"))),
|
|
188
|
-
TD(Style("width: 50%;"), Input(Css("w-full p-1"))),
|
|
189
|
-
),
|
|
190
|
-
),
|
|
191
|
-
Section("Query Params"),
|
|
192
|
-
Table(ID("queryParamsTable"),
|
|
193
|
-
TR(
|
|
194
|
-
TD(Css("text-gray-700"), Style("width: 50%;"), Div(Css("p-1"), Text("123"))),
|
|
195
|
-
TD(Style("width: 50%;"), Input(Css("w-full p-1"))),
|
|
196
|
-
),
|
|
197
|
-
),
|
|
198
|
-
Section("Body"),
|
|
199
|
-
Div(ID("left"), Css("border-b border-gray-200 text-md")),
|
|
200
|
-
),
|
|
201
|
-
),
|
|
202
|
-
Div(Css("flex flex-row"), Style("width: 50%;"),
|
|
203
|
-
Div(ID("right"), Css("w-full border-l border-l-gray-200 text-md")),
|
|
204
|
-
Div(Css("pr-8 border-l border-gray-300"), Style("background: #f7f7f7;")),
|
|
205
|
-
),
|
|
206
|
-
),
|
|
207
|
-
),
|
|
208
|
-
Script(Text("window.apiDefs = "+string(data))),
|
|
209
|
-
Script(Text("window.codeLeft = CodeMirror(document.getElementById('left'), {value: '{}',mode: 'javascript' })")),
|
|
210
|
-
Script(Text("window.codeRight = CodeMirror(document.getElementById('right'), {value: '',mode: 'javascript', lineNumbers: true, readOnly: true, lineWrapping: true })")),
|
|
211
|
-
Script(Text(`
|
|
212
|
-
const getCurrentApiCall = () => {
|
|
213
|
-
const index = document.getElementById("api-select").value;
|
|
214
|
-
return window.apiDefs[index];
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const updatePathParams = (apiCall) => {
|
|
218
|
-
const table = document.getElementById("pathParamsTable");
|
|
219
|
-
if (apiCall.pathParams.length === 0) {
|
|
220
|
-
table.innerHTML = "<div style='background-color: rgb(245, 245, 245); padding: 0.25rem; text-align: center; color: gray;'>NONE</div>";
|
|
221
|
-
} else {
|
|
222
|
-
table.innerHTML = "";
|
|
223
|
-
}
|
|
224
|
-
for(const param of apiCall.pathParams.reverse()) {
|
|
225
|
-
const row = table.insertRow(0);
|
|
226
|
-
const cell1 = row.insertCell(0);
|
|
227
|
-
const cell2 = row.insertCell(1);
|
|
228
|
-
cell1.style = "width: 30%; border-left: 0px;";
|
|
229
|
-
cell1.class = "text-gray-700";
|
|
230
|
-
cell2.style = "width: 70%;";
|
|
231
|
-
cell1.innerHTML = "<div class='p-1'>" + param + "</div>";
|
|
232
|
-
cell2.innerHTML = "<input id='path-param-" + param + "' class='w-full p-1'>";
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const updateParams = (apiCall) => {
|
|
237
|
-
const table = document.getElementById("queryParamsTable");
|
|
238
|
-
if (!apiCall.params) {
|
|
239
|
-
table.innerHTML = "<div style='background-color: rgb(245, 245, 245); padding: 0.25rem; text-align: center; color: gray;'>NONE</div>";
|
|
240
|
-
} else {
|
|
241
|
-
table.innerHTML = "";
|
|
242
|
-
}
|
|
243
|
-
if (apiCall.method === "GET" || apiCall.method === "DELETE") {
|
|
244
|
-
for(const key of Object.keys(apiCall.params)) {
|
|
245
|
-
const row = table.insertRow(0);
|
|
246
|
-
const cell1 = row.insertCell(0);
|
|
247
|
-
const cell2 = row.insertCell(1);
|
|
248
|
-
cell1.style = "width: 30%; border-left: 0px;";
|
|
249
|
-
cell1.class = "text-gray-700";
|
|
250
|
-
cell2.style = "width: 70%;";
|
|
251
|
-
cell1.innerHTML = "<div class='p-1'>" + key + "</div>";
|
|
252
|
-
cell2.innerHTML = "<input id='query-param-" + key + "' class='w-full p-1'>";
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const updateBody = (apiCall) => {
|
|
258
|
-
if (apiCall.method !== "GET" && apiCall.method !== "DELETE") {
|
|
259
|
-
window.codeLeft.setValue(JSON.stringify(apiCall.params, 2, 2));
|
|
260
|
-
} else {
|
|
261
|
-
window.codeLeft.setValue("");
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const init = () => {
|
|
266
|
-
updatePathParams(window.apiDefs[0]);
|
|
267
|
-
updateParams(window.apiDefs[0]);
|
|
268
|
-
updateBody(window.apiDefs[0]);
|
|
269
|
-
const headersJson = localStorage.getItem("headers");
|
|
270
|
-
if (headersJson) {
|
|
271
|
-
const table = document.getElementById("headersTable");
|
|
272
|
-
const headers = JSON.parse(headersJson);
|
|
273
|
-
table.innerHTML = "";
|
|
274
|
-
for(const key of Object.keys(headers)) {
|
|
275
|
-
const value = headers[key];
|
|
276
|
-
const row = table.insertRow(0);
|
|
277
|
-
const cell1 = row.insertCell(0);
|
|
278
|
-
const cell2 = row.insertCell(1);
|
|
279
|
-
cell1.style = "width: 30%; border-left: 0px;";
|
|
280
|
-
cell2.style = "width: 70%;";
|
|
281
|
-
cell1.innerHTML = "<input value='" + key + "' class='w-full p-1'>";
|
|
282
|
-
cell2.innerHTML = "<input value='" + value + "' class='w-full p-1'>";
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
window.onload = () => {
|
|
288
|
-
init();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
document.getElementById("api-select").onchange = () => {
|
|
292
|
-
const apiCall = getCurrentApiCall();
|
|
293
|
-
updatePathParams(apiCall);
|
|
294
|
-
updateParams(apiCall);
|
|
295
|
-
updateBody(apiCall);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const run = document.getElementById("run");
|
|
299
|
-
run.onclick = async () => {
|
|
300
|
-
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>";
|
|
301
|
-
const table = document.getElementById("headersTable");
|
|
302
|
-
const headers = {};
|
|
303
|
-
for(const row of table.rows) {
|
|
304
|
-
const key = row.cells[0].children[0].value;
|
|
305
|
-
const value = row.cells[1].children[0].value;
|
|
306
|
-
headers[key] = value;
|
|
307
|
-
}
|
|
308
|
-
const apiCall = getCurrentApiCall();
|
|
309
|
-
let path = apiCall.path;
|
|
310
|
-
const bodyParams = {};
|
|
311
|
-
if (apiCall.method !== "GET" && apiCall.method != "DELETE") {
|
|
312
|
-
bodyParams["body"] = window.codeLeft.getValue();
|
|
313
|
-
} else {
|
|
314
|
-
for(const param of apiCall.pathParams) {
|
|
315
|
-
const value = document.getElementById('path-param-' + param).value;
|
|
316
|
-
path = path.replace('{' + param + '}', value);
|
|
317
|
-
}
|
|
318
|
-
const paramsKeys = Object.keys(apiCall.params);
|
|
319
|
-
if (paramsKeys.length > 0) {
|
|
320
|
-
path += "?";
|
|
321
|
-
paramsKeys.forEach((key, i) => {
|
|
322
|
-
const value = document.getElementById('query-param-' + key).value;
|
|
323
|
-
path += key+"="+value;
|
|
324
|
-
if (i !== paramsKeys.length - 1) {
|
|
325
|
-
path += "&";
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
localStorage.setItem("headers", JSON.stringify(headers));
|
|
331
|
-
try {
|
|
332
|
-
const res = await fetch(path, {
|
|
333
|
-
method: apiCall.method,
|
|
334
|
-
headers,
|
|
335
|
-
...bodyParams
|
|
336
|
-
});
|
|
337
|
-
const json = await res.json();
|
|
338
|
-
window.codeRight.setValue(JSON.stringify(json, 2, 2));
|
|
339
|
-
} catch (err) {
|
|
340
|
-
window.codeRight.setValue(JSON.stringify({ error: err.message }, 2, 2));
|
|
341
|
-
}
|
|
342
|
-
run.innerHTML = "RUN";
|
|
343
|
-
}
|
|
344
|
-
`)),
|
|
345
|
-
),
|
|
346
|
-
), 200, nil
|
|
7
|
+
func ApiExplorer() (HtmlContent, int, error) {
|
|
8
|
+
_, err := json.Marshal(RouteDefs)
|
|
9
|
+
if err != nil {
|
|
10
|
+
return HtmlErr(500, err)
|
|
347
11
|
}
|
|
12
|
+
return Html("", nil)
|
|
348
13
|
}
|
cmd/gromer/main.go
CHANGED
|
@@ -4,9 +4,6 @@ import (
|
|
|
4
4
|
"bytes"
|
|
5
5
|
"flag"
|
|
6
6
|
"fmt"
|
|
7
|
-
"go/ast"
|
|
8
|
-
"go/parser"
|
|
9
|
-
"go/token"
|
|
10
7
|
"io/ioutil"
|
|
11
8
|
"log"
|
|
12
9
|
"os"
|
|
@@ -83,28 +80,6 @@ func rewritePkg(pkg string) string {
|
|
|
83
80
|
return lastItem
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
func getApiFunc(method, route string, pathParams []string, params map[string]interface{}) gromer.ApiDefinition {
|
|
87
|
-
muxRoute := bytes.NewBuffer(nil)
|
|
88
|
-
foundStart := false
|
|
89
|
-
for _, v := range route {
|
|
90
|
-
if string(v) == "_" && !foundStart {
|
|
91
|
-
foundStart = true
|
|
92
|
-
muxRoute.WriteString("{")
|
|
93
|
-
} else if string(v) == "_" && foundStart {
|
|
94
|
-
foundStart = false
|
|
95
|
-
muxRoute.WriteString("}")
|
|
96
|
-
} else {
|
|
97
|
-
muxRoute.WriteString(string(v))
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return gromer.ApiDefinition{
|
|
101
|
-
Method: method,
|
|
102
|
-
Path: muxRoute.String(),
|
|
103
|
-
PathParams: pathParams,
|
|
104
|
-
Params: params,
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
83
|
func lowerFirst(s string) string {
|
|
109
84
|
for i, v := range s {
|
|
110
85
|
return string(unicode.ToLower(v)) + s[i+1:]
|
|
@@ -129,8 +104,6 @@ func main() {
|
|
|
129
104
|
} else {
|
|
130
105
|
moduleName = *pkgFlag
|
|
131
106
|
}
|
|
132
|
-
routes := []*Route{}
|
|
133
|
-
apiDefs := []gromer.ApiDefinition{}
|
|
134
107
|
allPkgs := map[string]string{}
|
|
135
108
|
err := filepath.Walk("pages",
|
|
136
109
|
func(filesrc string, info os.FileInfo, err error) error {
|
|
@@ -147,80 +120,35 @@ func main() {
|
|
|
147
120
|
path = "/"
|
|
148
121
|
pkg = "pages"
|
|
149
122
|
}
|
|
150
|
-
routePath := rewritePath(path)
|
|
151
|
-
pathParams := gromer.GetRouteParams(routePath)
|
|
152
|
-
|
|
123
|
+
gromer.RouteDefs = append(gromer.RouteDefs, gromer.RouteDefinition{
|
|
153
124
|
Method: method,
|
|
154
|
-
Path:
|
|
125
|
+
Path: rewritePath(path),
|
|
155
126
|
Pkg: rewritePkg(pkg),
|
|
156
127
|
})
|
|
157
|
-
if strings.Contains(path, "/api/") {
|
|
158
|
-
data, err := ioutil.ReadFile(filesrc)
|
|
159
|
-
if err != nil {
|
|
160
|
-
panic(err)
|
|
161
|
-
}
|
|
162
|
-
fset := token.NewFileSet()
|
|
163
|
-
f, err := parser.ParseFile(fset, "", string(data), parser.AllErrors)
|
|
164
|
-
if err != nil {
|
|
165
|
-
panic(err)
|
|
166
|
-
}
|
|
167
|
-
var params map[string]interface{}
|
|
168
|
-
mapsOfInputParams := map[string]map[string]interface{}{}
|
|
169
|
-
for _, d := range f.Decls {
|
|
170
|
-
if decl, ok := d.(*ast.GenDecl); ok {
|
|
171
|
-
for _, spec := range decl.Specs {
|
|
172
|
-
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
173
|
-
if st, ok := typeSpec.Type.(*ast.StructType); ok {
|
|
174
|
-
mapsOfInputParams[typeSpec.Name.Name] = map[string]interface{}{}
|
|
175
|
-
for _, f := range st.Fields.List {
|
|
176
|
-
if len(f.Names) > 0 {
|
|
177
|
-
fieldName := lowerFirst(f.Names[0].Name)
|
|
178
|
-
mapsOfInputParams[typeSpec.Name.Name][fieldName] = fmt.Sprintf("%+s", f.Type)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if decl, ok := d.(*ast.FuncDecl); ok {
|
|
186
|
-
if decl.Name.Name == method {
|
|
187
|
-
list := decl.Type.Params.List
|
|
188
|
-
lastParam := list[len(list)-1]
|
|
189
|
-
if spec, ok := lastParam.Type.(*ast.Ident); ok {
|
|
190
|
-
if spec.IsExported() {
|
|
191
|
-
// TODO: need to read from imported files and pick up the struct and
|
|
192
|
-
// convert it to json
|
|
193
|
-
} else if v, ok := mapsOfInputParams[spec.Name]; ok {
|
|
194
|
-
params = v
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
apiDefs = append(apiDefs, getApiFunc(method, path, pathParams, params))
|
|
201
|
-
}
|
|
202
128
|
}
|
|
203
129
|
return nil
|
|
204
130
|
})
|
|
205
131
|
if err != nil {
|
|
206
132
|
log.Fatal(err)
|
|
207
133
|
}
|
|
208
|
-
for _, r := range
|
|
134
|
+
for _, r := range gromer.RouteDefs {
|
|
209
135
|
fmt.Printf("%-6s %s\n", r.Method, r.Path)
|
|
210
136
|
}
|
|
137
|
+
err = velvet.Helpers.Add("title", func(v string) string {
|
|
138
|
+
return strings.Title(strings.ToLower(v))
|
|
139
|
+
})
|
|
140
|
+
if err != nil {
|
|
141
|
+
log.Fatal(err)
|
|
142
|
+
}
|
|
211
143
|
ctx := velvet.NewContext()
|
|
212
144
|
ctx.Set("moduleName", moduleName)
|
|
213
145
|
ctx.Set("allPkgs", allPkgs)
|
|
214
|
-
ctx.Set("routes",
|
|
146
|
+
ctx.Set("routes", gromer.RouteDefs)
|
|
215
|
-
ctx.Set("apiDefs", apiDefs)
|
|
216
147
|
ctx.Set("tick", "`")
|
|
217
148
|
s, err := velvet.Render(`// Code generated by gromer. DO NOT EDIT.
|
|
218
149
|
package main
|
|
219
150
|
|
|
220
151
|
import (
|
|
221
|
-
c "context"
|
|
222
|
-
"embed"
|
|
223
|
-
"net/http"
|
|
224
152
|
"os"
|
|
225
153
|
|
|
226
154
|
"github.com/gorilla/mux"
|
|
@@ -228,22 +156,20 @@ import (
|
|
|
228
156
|
"github.com/rs/zerolog/log"
|
|
229
157
|
"gocloud.dev/server"
|
|
230
158
|
|
|
159
|
+
"{{ moduleName }}/assets"
|
|
231
160
|
{{#each allPkgs }}"{{ moduleName }}/pages{{ @key }}"
|
|
232
161
|
{{/each}}
|
|
233
162
|
)
|
|
234
163
|
|
|
235
|
-
//go:embed assets/*
|
|
236
|
-
var assetsFS embed.FS
|
|
237
|
-
|
|
238
164
|
func main() {
|
|
239
165
|
port := os.Getenv("PORT")
|
|
240
166
|
r := mux.NewRouter()
|
|
241
167
|
r.Use(gromer.CorsMiddleware)
|
|
242
168
|
r.Use(gromer.LogMiddleware)
|
|
243
169
|
r.NotFoundHandler = gromer.NotFoundHandler
|
|
244
|
-
|
|
170
|
+
gromer.Static(r, "/assets/", assets.FS)
|
|
245
|
-
|
|
171
|
+
gromer.Handle(r, "GET", "/api", gromer.ApiExplorer, nil)
|
|
246
|
-
{{#each routes as |route| }}
|
|
172
|
+
{{#each routes as |route| }}gromer.Handle(r, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }}, {{ route.Pkg }}.{{ title route.Method }}Params{})
|
|
247
173
|
{{/each}}
|
|
248
174
|
println("http server listening on http://localhost:"+port)
|
|
249
175
|
srv := server.New(r, nil)
|
|
@@ -251,38 +177,6 @@ func main() {
|
|
|
251
177
|
log.Fatal().Stack().Err(err).Msg("failed to listen")
|
|
252
178
|
}
|
|
253
179
|
}
|
|
254
|
-
|
|
255
|
-
func wrapCache(h http.Handler) http.Handler {
|
|
256
|
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
257
|
-
w.Header().Set("Cache-Control", "public, max-age=2592000")
|
|
258
|
-
h.ServeHTTP(w, r)
|
|
259
|
-
})
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
func handle(router *mux.Router, method, route string, h interface{}) {
|
|
263
|
-
router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
|
|
264
|
-
ctx := c.WithValue(
|
|
265
|
-
c.WithValue(
|
|
266
|
-
c.WithValue(r.Context(), "assetsFS", assetsFS),
|
|
267
|
-
"url", r.URL),
|
|
268
|
-
"header", r.Header)
|
|
269
|
-
gromer.PerformRequest(route, h, ctx, w, r)
|
|
270
|
-
}).Methods(method, "OPTIONS")
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
func apiDefinitions() []gromer.ApiDefinition {
|
|
274
|
-
return []gromer.ApiDefinition{
|
|
275
|
-
{{#each apiDefs as |api| }}
|
|
276
|
-
{
|
|
277
|
-
Method: "{{api.Method}}",
|
|
278
|
-
Path: "{{api.Path}}",
|
|
279
|
-
PathParams: []string{ {{#each api.PathParams as |param| }}"{{param}}", {{/each}} },
|
|
280
|
-
Params: map[string]interface{}{
|
|
281
|
-
{{#each api.Params }}"{{ @key }}": "{{ @value }}", {{/each}}
|
|
282
|
-
},
|
|
283
|
-
},{{/each}}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
180
|
`, ctx)
|
|
287
181
|
if err != nil {
|
|
288
182
|
panic(err)
|
css.go
DELETED
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
package gromer
|
|
2
|
-
|
|
3
|
-
type M map[string]interface{}
|
|
4
|
-
type MS map[string]string
|
|
5
|
-
type Arr []interface{}
|
|
6
|
-
|
|
7
|
-
type KeyValues struct {
|
|
8
|
-
Keys M
|
|
9
|
-
Values MS
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
var colors = KeyValues{
|
|
13
|
-
Keys: M{
|
|
14
|
-
"bg": "background-color",
|
|
15
|
-
"text": "color",
|
|
16
|
-
"divide": "border-color",
|
|
17
|
-
"border": "border-color",
|
|
18
|
-
"ring": "--tw-ring-color",
|
|
19
|
-
"border-l": "border-left-color",
|
|
20
|
-
"border-r": "border-right-color",
|
|
21
|
-
"border-t": "border-top-color",
|
|
22
|
-
"border-b": "border-bottom-color",
|
|
23
|
-
},
|
|
24
|
-
Values: MS{
|
|
25
|
-
"transparent": "transparent",
|
|
26
|
-
"current": "currentColor",
|
|
27
|
-
"black": "rgba(0, 0, 0, 1)",
|
|
28
|
-
"white": "rgba(255, 255, 255, 1)",
|
|
29
|
-
"gray-50": "rgba(249, 250, 251, 1)",
|
|
30
|
-
"gray-100": "rgba(243, 244, 246, 1)",
|
|
31
|
-
"gray-200": "rgba(229, 231, 235, 1)",
|
|
32
|
-
"gray-300": "rgba(209, 213, 219, 1)",
|
|
33
|
-
"gray-400": "rgba(156, 163, 175, 1)",
|
|
34
|
-
"gray-500": "rgba(107, 114, 128, 1)",
|
|
35
|
-
"gray-600": "rgba(75, 85, 99, 1)",
|
|
36
|
-
"gray-700": "rgba(55, 65, 81, 1)",
|
|
37
|
-
"gray-800": "rgba(31, 41, 55, 1)",
|
|
38
|
-
"gray-900": "rgba(17, 24, 39, 1)",
|
|
39
|
-
"red-50": "rgba(254, 242, 242, 1)",
|
|
40
|
-
"red-100": "rgba(254, 226, 226, 1)",
|
|
41
|
-
"red-200": "rgba(254, 202, 202, 1)",
|
|
42
|
-
"red-300": "rgba(252, 165, 165, 1)",
|
|
43
|
-
"red-400": "rgba(248, 113, 113, 1)",
|
|
44
|
-
"red-500": "rgba(239, 68, 68, 1)",
|
|
45
|
-
"red-600": "rgba(220, 38, 38, 1)",
|
|
46
|
-
"red-700": "rgba(185, 28, 28, 1)",
|
|
47
|
-
"red-800": "rgba(153, 27, 27, 1)",
|
|
48
|
-
"red-900": "rgba(127, 29, 29, 1)",
|
|
49
|
-
"yellow-50": "rgba(255, 251, 235, 1)",
|
|
50
|
-
"yellow-100": "rgba(254, 243, 199, 1)",
|
|
51
|
-
"yellow-200": "rgba(253, 230, 138, 1)",
|
|
52
|
-
"yellow-300": "rgba(252, 211, 77, 1)",
|
|
53
|
-
"yellow-400": "rgba(251, 191, 36, 1)",
|
|
54
|
-
"yellow-500": "rgba(245, 158, 11, 1)",
|
|
55
|
-
"yellow-600": "rgba(217, 119, 6, 1)",
|
|
56
|
-
"yellow-700": "rgba(180, 83, 9, 1)",
|
|
57
|
-
"yellow-800": "rgba(146, 64, 14, 1)",
|
|
58
|
-
"yellow-900": "rgba(120, 53, 15, 1)",
|
|
59
|
-
"green-50": "rgba(236, 253, 245, 1)",
|
|
60
|
-
"green-100": "rgba(209, 250, 229, 1)",
|
|
61
|
-
"green-200": "rgba(167, 243, 208, 1)",
|
|
62
|
-
"green-300": "rgba(110, 231, 183, 1)",
|
|
63
|
-
"green-400": "rgba(52, 211, 153, 1)",
|
|
64
|
-
"green-500": "rgba(16, 185, 129, 1)",
|
|
65
|
-
"green-600": "rgba(5, 150, 105, 1)",
|
|
66
|
-
"green-700": "rgba(4, 120, 87, 1)",
|
|
67
|
-
"green-800": "rgba(6, 95, 70, 1)",
|
|
68
|
-
"green-900": "rgba(6, 78, 59, 1)",
|
|
69
|
-
"blue-50": "rgba(239, 246, 255, 1)",
|
|
70
|
-
"blue-100": "rgba(219, 234, 254, 1)",
|
|
71
|
-
"blue-200": "rgba(191, 219, 254, 1)",
|
|
72
|
-
"blue-300": "rgba(147, 197, 253, 1)",
|
|
73
|
-
"blue-400": "rgba(96, 165, 250, 1)",
|
|
74
|
-
"blue-500": "rgba(59, 130, 246, 1)",
|
|
75
|
-
"blue-600": "rgba(37, 99, 235, 1)",
|
|
76
|
-
"blue-700": "rgba(29, 78, 216, 1)",
|
|
77
|
-
"blue-800": "rgba(30, 64, 175, 1)",
|
|
78
|
-
"blue-900": "rgba(30, 58, 138, 1)",
|
|
79
|
-
"indigo-50": "rgba(238, 242, 255, 1)",
|
|
80
|
-
"indigo-100": "rgba(224, 231, 255, 1)",
|
|
81
|
-
"indigo-200": "rgba(199, 210, 254, 1)",
|
|
82
|
-
"indigo-300": "rgba(165, 180, 252, 1)",
|
|
83
|
-
"indigo-400": "rgba(129, 140, 248, 1)",
|
|
84
|
-
"indigo-500": "rgba(99, 102, 241, 1)",
|
|
85
|
-
"indigo-600": "rgba(79, 70, 229, 1)",
|
|
86
|
-
"indigo-700": "rgba(67, 56, 202, 1)",
|
|
87
|
-
"indigo-800": "rgba(55, 48, 163, 1)",
|
|
88
|
-
"indigo-900": "rgba(49, 46, 129, 1)",
|
|
89
|
-
"purple-50": "rgba(245, 243, 255, 1)",
|
|
90
|
-
"purple-100": "rgba(237, 233, 254, 1)",
|
|
91
|
-
"purple-200": "rgba(221, 214, 254, 1)",
|
|
92
|
-
"purple-300": "rgba(196, 181, 253, 1)",
|
|
93
|
-
"purple-400": "rgba(167, 139, 250, 1)",
|
|
94
|
-
"purple-500": "rgba(139, 92, 246, 1)",
|
|
95
|
-
"purple-600": "rgba(124, 58, 237, 1)",
|
|
96
|
-
"purple-700": "rgba(109, 40, 217, 1)",
|
|
97
|
-
"purple-800": "rgba(91, 33, 182, 1)",
|
|
98
|
-
"purple-900": "rgba(76, 29, 149, 1)",
|
|
99
|
-
"pink-50": "rgba(253, 242, 248, 1)",
|
|
100
|
-
"pink-100": "rgba(252, 231, 243, 1)",
|
|
101
|
-
"pink-200": "rgba(251, 207, 232, 1)",
|
|
102
|
-
"pink-300": "rgba(249, 168, 212, 1)",
|
|
103
|
-
"pink-400": "rgba(244, 114, 182, 1)",
|
|
104
|
-
"pink-500": "rgba(236, 72, 153, 1)",
|
|
105
|
-
"pink-600": "rgba(219, 39, 119, 1)",
|
|
106
|
-
"pink-700": "rgba(190, 24, 93, 1)",
|
|
107
|
-
"pink-800": "rgba(157, 23, 77, 1)",
|
|
108
|
-
"pink-900": "rgba(131, 24, 67, 1)",
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
var spacing = KeyValues{
|
|
113
|
-
Keys: M{
|
|
114
|
-
"mr": "margin-right",
|
|
115
|
-
"ml": "margin-left",
|
|
116
|
-
"mt": "margin-top",
|
|
117
|
-
"mb": "margin-bottom",
|
|
118
|
-
"mx": Arr{
|
|
119
|
-
"margin-left",
|
|
120
|
-
"margin-right",
|
|
121
|
-
},
|
|
122
|
-
"my": Arr{
|
|
123
|
-
"margin-top",
|
|
124
|
-
"margin-bottom",
|
|
125
|
-
},
|
|
126
|
-
"m": "margin",
|
|
127
|
-
"pr": "padding-right",
|
|
128
|
-
"pl": "padding-left",
|
|
129
|
-
"pt": "padding-top",
|
|
130
|
-
"pb": "padding-bottom",
|
|
131
|
-
"px": Arr{
|
|
132
|
-
"padding-left",
|
|
133
|
-
"padding-right",
|
|
134
|
-
},
|
|
135
|
-
"py": Arr{
|
|
136
|
-
"padding-top",
|
|
137
|
-
"padding-bottom",
|
|
138
|
-
},
|
|
139
|
-
"p": "padding",
|
|
140
|
-
},
|
|
141
|
-
Values: MS{
|
|
142
|
-
"0": "0px",
|
|
143
|
-
"1": "0.25rem",
|
|
144
|
-
"2": "0.5rem",
|
|
145
|
-
"3": "0.75rem",
|
|
146
|
-
"4": "1rem",
|
|
147
|
-
"5": "1.25rem",
|
|
148
|
-
"6": "1.5rem",
|
|
149
|
-
"7": "1.75rem",
|
|
150
|
-
"8": "2rem",
|
|
151
|
-
"9": "2.25rem",
|
|
152
|
-
"10": "2.5rem",
|
|
153
|
-
"11": "2.75rem",
|
|
154
|
-
"12": "3rem",
|
|
155
|
-
"14": "3.5rem",
|
|
156
|
-
"16": "4rem",
|
|
157
|
-
"20": "5rem",
|
|
158
|
-
"24": "6rem",
|
|
159
|
-
"28": "7rem",
|
|
160
|
-
"32": "8rem",
|
|
161
|
-
"36": "9rem",
|
|
162
|
-
"40": "10rem",
|
|
163
|
-
"44": "11rem",
|
|
164
|
-
"48": "12rem",
|
|
165
|
-
"52": "13rem",
|
|
166
|
-
"56": "14rem",
|
|
167
|
-
"60": "15rem",
|
|
168
|
-
"64": "16rem",
|
|
169
|
-
"72": "18rem",
|
|
170
|
-
"80": "20rem",
|
|
171
|
-
"96": "24rem",
|
|
172
|
-
"auto": "auto",
|
|
173
|
-
"px": "1px",
|
|
174
|
-
"0.5": "0.125rem",
|
|
175
|
-
"1.5": "0.375rem",
|
|
176
|
-
"2.5": "0.625rem",
|
|
177
|
-
"3.5": "0.875rem",
|
|
178
|
-
},
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
var radius = KeyValues{
|
|
182
|
-
Keys: M{
|
|
183
|
-
"rounded": "border-radius",
|
|
184
|
-
"rounded-t": "border-top-radius",
|
|
185
|
-
"rounded-r": "border-right-radius",
|
|
186
|
-
"rounded-l": "border-left-radius",
|
|
187
|
-
"rounded-b": "border-bottom-radius",
|
|
188
|
-
"rounded-tl": Arr{
|
|
189
|
-
"border-top-radius",
|
|
190
|
-
"border-left-radius",
|
|
191
|
-
},
|
|
192
|
-
"rounded-tr": Arr{
|
|
193
|
-
"border-top-radius",
|
|
194
|
-
"border-right-radius",
|
|
195
|
-
},
|
|
196
|
-
"rounded-bl": Arr{
|
|
197
|
-
"border-bottom-radius",
|
|
198
|
-
"border-left-radius",
|
|
199
|
-
},
|
|
200
|
-
"rounded-br": Arr{
|
|
201
|
-
"border-bottom-radius",
|
|
202
|
-
"border-right-radius",
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
Values: MS{
|
|
206
|
-
"none": "0px",
|
|
207
|
-
"sm": "0.125rem",
|
|
208
|
-
"": "0.25rem",
|
|
209
|
-
"md": "0.375rem",
|
|
210
|
-
"lg": "0.5rem",
|
|
211
|
-
"xl": "0.75rem",
|
|
212
|
-
"2xl": "1rem",
|
|
213
|
-
"3xl": "1.5rem",
|
|
214
|
-
"full": "9999px",
|
|
215
|
-
},
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
var borders = KeyValues{
|
|
219
|
-
Keys: M{
|
|
220
|
-
"border": "border-width",
|
|
221
|
-
"border-l": "border-left-width",
|
|
222
|
-
"border-r": "border-right-width",
|
|
223
|
-
"border-t": "border-top-width",
|
|
224
|
-
"border-b": "border-bottom-width",
|
|
225
|
-
},
|
|
226
|
-
Values: MS{
|
|
227
|
-
"0": "0px",
|
|
228
|
-
"2": "2px",
|
|
229
|
-
"4": "4px",
|
|
230
|
-
"8": "8px",
|
|
231
|
-
"": "1px",
|
|
232
|
-
},
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
var sizes = KeyValues{
|
|
236
|
-
Keys: M{
|
|
237
|
-
"h": "height",
|
|
238
|
-
"w": "width",
|
|
239
|
-
"top": "top",
|
|
240
|
-
"left": "left",
|
|
241
|
-
"bottom": "bottom",
|
|
242
|
-
"right": "right",
|
|
243
|
-
"minh": "min-height",
|
|
244
|
-
"minw": "min-width",
|
|
245
|
-
"maxh": "max-height",
|
|
246
|
-
"maxw": "max-width",
|
|
247
|
-
},
|
|
248
|
-
Values: MS{
|
|
249
|
-
"auto": "auto",
|
|
250
|
-
"min": "min-content",
|
|
251
|
-
"max": "max-content",
|
|
252
|
-
"0": "0px",
|
|
253
|
-
"1": "0.25rem",
|
|
254
|
-
"2": "0.5rem",
|
|
255
|
-
"3": "0.75rem",
|
|
256
|
-
"4": "1rem",
|
|
257
|
-
"5": "1.25rem",
|
|
258
|
-
"6": "1.5rem",
|
|
259
|
-
"7": "1.75rem",
|
|
260
|
-
"8": "2rem",
|
|
261
|
-
"9": "2.25rem",
|
|
262
|
-
"10": "2.5rem",
|
|
263
|
-
"11": "2.75rem",
|
|
264
|
-
"12": "3rem",
|
|
265
|
-
"14": "3.5rem",
|
|
266
|
-
"16": "4rem",
|
|
267
|
-
"20": "5rem",
|
|
268
|
-
"24": "6rem",
|
|
269
|
-
"28": "7rem",
|
|
270
|
-
"32": "8rem",
|
|
271
|
-
"36": "9rem",
|
|
272
|
-
"40": "10rem",
|
|
273
|
-
"44": "11rem",
|
|
274
|
-
"48": "12rem",
|
|
275
|
-
"52": "13rem",
|
|
276
|
-
"56": "14rem",
|
|
277
|
-
"60": "15rem",
|
|
278
|
-
"64": "16rem",
|
|
279
|
-
"72": "18rem",
|
|
280
|
-
"80": "20rem",
|
|
281
|
-
"96": "24rem",
|
|
282
|
-
"px": "1px",
|
|
283
|
-
"0.5": "0.125rem",
|
|
284
|
-
"1.5": "0.375rem",
|
|
285
|
-
"2.5": "0.625rem",
|
|
286
|
-
"3.5": "0.875rem",
|
|
287
|
-
"1/2": "50%",
|
|
288
|
-
"1/3": "33.33%",
|
|
289
|
-
"2/3": "66.66%",
|
|
290
|
-
"1/4": "25%",
|
|
291
|
-
"2/4": "50%",
|
|
292
|
-
"3/4": "75%",
|
|
293
|
-
"1/5": "20%",
|
|
294
|
-
"2/5": "40%",
|
|
295
|
-
"3/5": "60%",
|
|
296
|
-
"4/5": "80%",
|
|
297
|
-
"1/6": "16.66%",
|
|
298
|
-
"2/6": "33.33%",
|
|
299
|
-
"3/6": "50%",
|
|
300
|
-
"4/6": "66.66%",
|
|
301
|
-
"5/6": "83.33%",
|
|
302
|
-
"1/12": "8.33%",
|
|
303
|
-
"2/12": "16.66%",
|
|
304
|
-
"3/12": "25%",
|
|
305
|
-
"4/12": "33.33%",
|
|
306
|
-
"5/12": "41.66%",
|
|
307
|
-
"6/12": "50%",
|
|
308
|
-
"7/12": "58.33%",
|
|
309
|
-
"8/12": "66.66%",
|
|
310
|
-
"9/12": "75%",
|
|
311
|
-
"10/12": "83.33%",
|
|
312
|
-
"11/12": "91.66%",
|
|
313
|
-
"full": "100%",
|
|
314
|
-
},
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
var twClassLookup = map[string]string{
|
|
318
|
-
"flex": "display: flex;",
|
|
319
|
-
"inline-flex": "display: inline-flex;",
|
|
320
|
-
"block": "display: block;",
|
|
321
|
-
"inline-block": "display: inline-block;",
|
|
322
|
-
"inline": "display: inline;",
|
|
323
|
-
"table": "display: table;",
|
|
324
|
-
"inline-table": "display: inline-table;",
|
|
325
|
-
"grid": "display: grid;",
|
|
326
|
-
"inline-grid": "display: inline-grid;",
|
|
327
|
-
"contents": "display: contents;",
|
|
328
|
-
"list-item": "display: list-item;",
|
|
329
|
-
"hidden": "display: none;",
|
|
330
|
-
"flex-1": "flex: 1;",
|
|
331
|
-
"flex-row": "flex-direction: row;",
|
|
332
|
-
"flex-col": "flex-direction: column;",
|
|
333
|
-
"flex-wrap": "flex-wrap: wrap;",
|
|
334
|
-
"flex-nowrap": "flex-wrap: nowrap;",
|
|
335
|
-
"flex-wrap-reverse": "flex-wrap: wrap-reverse;",
|
|
336
|
-
"items-baseline": "align-items: baseline;",
|
|
337
|
-
"items-start": "align-items: flex-start;",
|
|
338
|
-
"items-center": "align-items: center;",
|
|
339
|
-
"items-end": "align-items: flex-end;",
|
|
340
|
-
"items-stretch": "align-items: stretch;",
|
|
341
|
-
"justify-start": "justify-content: flex-start;",
|
|
342
|
-
"justify-end": "justify-content: flex-end;",
|
|
343
|
-
"justify-center": "justify-content: center;",
|
|
344
|
-
"justify-between": "justify-content: space-between;",
|
|
345
|
-
"justify-around": "justify-content: space-around;",
|
|
346
|
-
"justify-evenly": "justify-content: space-evenly;",
|
|
347
|
-
"uppercase": "text-transform: uppercase",
|
|
348
|
-
"lowercase": "text-transform: lowercase",
|
|
349
|
-
"capitalize": "text-transform: capitalize",
|
|
350
|
-
"normal-case": "text-transform: normal-case",
|
|
351
|
-
"text-left": "text-align: left;",
|
|
352
|
-
"text-center": "text-align: center;",
|
|
353
|
-
"text-right": "text-align: right;",
|
|
354
|
-
"text-justify": "text-align: justify;",
|
|
355
|
-
"underline": "text-decoration: underline;",
|
|
356
|
-
"line-through": "text-decoration: line-through;",
|
|
357
|
-
"no-underline": "text-decoration: none;",
|
|
358
|
-
"whitespace-normal": "white-space: normal;",
|
|
359
|
-
"whitespace-nowrap": "white-space: nowrap;",
|
|
360
|
-
"whitespace-pre": "white-space: pre;",
|
|
361
|
-
"whitespace-pre-line": "white-space: pre-line;",
|
|
362
|
-
"whitespace-pre-wrap": "white-space: pre-wrap;",
|
|
363
|
-
"break-normal": "word-break: normal; overflow-wrap: normal;",
|
|
364
|
-
"break-words": "word-break: break-word;",
|
|
365
|
-
"break-all": "word-break: break-all;",
|
|
366
|
-
"font-sans": "font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";",
|
|
367
|
-
"font-serif": "font-family: ui-serif, Georgia, Cambria, \"Times New Roman\", Times, serif;",
|
|
368
|
-
"font-mono": "font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;",
|
|
369
|
-
"font-thin": "font-weight: 100;",
|
|
370
|
-
"font-extralight": "font-weight: 200;",
|
|
371
|
-
"font-light": "font-weight: 300;",
|
|
372
|
-
"font-normal": "font-weight: 400;",
|
|
373
|
-
"font-medium": "font-weight: 500;",
|
|
374
|
-
"font-semibold": "font-weight: 600;",
|
|
375
|
-
"font-bold": "font-weight: 700;",
|
|
376
|
-
"font-extrabold": "font-weight: 800;",
|
|
377
|
-
"font-black": "font-weight: 900;",
|
|
378
|
-
"text-xs": "font-size: 0.75rem; line-height: 1rem;",
|
|
379
|
-
"text-sm": "font-size: 0.875rem; line-height: 1.25rem;",
|
|
380
|
-
"text-base": "font-size: 1rem; line-height: 1.5rem;",
|
|
381
|
-
"text-lg": "font-size: 1.125rem; line-height: 1.75rem;",
|
|
382
|
-
"text-xl": "font-size: 1.25rem; line-height: 1.75rem;",
|
|
383
|
-
"text-2xl": "font-size: 1.5rem; line-height: 2rem;",
|
|
384
|
-
"text-3xl": "font-size: 1.875rem; line-height: 2.25rem;",
|
|
385
|
-
"text-4xl": "font-size: 2.25rem; line-height: 2.5rem;",
|
|
386
|
-
"text-5xl": "font-size: 3rem; line-height: 1;",
|
|
387
|
-
"text-6xl": "font-size: 3.75rem;; line-height: 1;",
|
|
388
|
-
"text-7xl": "font-size: 4.5rem; line-height: 1;",
|
|
389
|
-
"text-8xl": "font-size: 6rem; line-height: 1;",
|
|
390
|
-
"text-9xl": "font-size: 8rem; line-height: 1;",
|
|
391
|
-
"cursor-auto": "cursor: auto;",
|
|
392
|
-
"cursor-default": "cursor: default;",
|
|
393
|
-
"cursor-pointer": "cursor: pointer;",
|
|
394
|
-
"cursor-wait": "cursor: wait;",
|
|
395
|
-
"cursor-text": "cursor: text;",
|
|
396
|
-
"cursor-move": "cursor: move;",
|
|
397
|
-
"cursor-help": "cursor: help;",
|
|
398
|
-
"cursor-not-allowed": "cursor: not-allowed;",
|
|
399
|
-
"pointer-events-none": "pointer-events: none;",
|
|
400
|
-
"pointer-events-auto": "pointer-events: auto;",
|
|
401
|
-
"select-none": "user-select: none;",
|
|
402
|
-
"select-text": "user-select: text;",
|
|
403
|
-
"select-all": "user-select: all;",
|
|
404
|
-
"select-auto": "user-select: auto;",
|
|
405
|
-
"w-screen": "100vw",
|
|
406
|
-
"h-screen": "100vh",
|
|
407
|
-
"static": "position: static;",
|
|
408
|
-
"fixed": "position: fixed;",
|
|
409
|
-
"absolute": "position: absolute;",
|
|
410
|
-
"relative": "position: relative;",
|
|
411
|
-
"sticky": "position: sticky;",
|
|
412
|
-
"overflow-auto": "overflow: auto;",
|
|
413
|
-
"overflow-hidden": "overflow: hidden;",
|
|
414
|
-
"overflow-visible": "overflow: visible;",
|
|
415
|
-
"overflow-scroll": "overflow: scroll;",
|
|
416
|
-
"overflow-x-auto": "overflow-x: auto;",
|
|
417
|
-
"overflow-y-auto": "overflow-y: auto;",
|
|
418
|
-
"overflow-x-hidden": "overflow-x: hidden;",
|
|
419
|
-
"overflow-y-hidden": "overflow-y: hidden;",
|
|
420
|
-
"overflow-x-visible": "overflow-x: visible;",
|
|
421
|
-
"overflow-y-visible": "overflow-y: visible;",
|
|
422
|
-
"overflow-x-scroll": "overflow-x: scroll;",
|
|
423
|
-
"overflow-y-scroll": "overflow-y: scroll;",
|
|
424
|
-
"origin-center": "transform-origin: center;",
|
|
425
|
-
"origin-top": "transform-origin: top;",
|
|
426
|
-
"origin-top-right": "transform-origin: top right;",
|
|
427
|
-
"origin-right": "transform-origin: right;",
|
|
428
|
-
"origin-bottom-right": "transform-origin: bottom right;",
|
|
429
|
-
"origin-bottom": "transform-origin: bottom;",
|
|
430
|
-
"origin-bottom-left": "transform-origin: bottom left;",
|
|
431
|
-
"origin-left": "transform-origin: left;",
|
|
432
|
-
"origin-top-left": "transform-origin: top left;",
|
|
433
|
-
"shadow-sm": "box-shadow: 0 0 #0000, 0 0 #0000, 0 1px 2px 0 rgba(0, 0, 0, 0.05);",
|
|
434
|
-
"shadow": "box-shadow: 0 0 #0000, 0 0 #0000, 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);",
|
|
435
|
-
"shadow-md": "box-shadow: 0 0 #0000, 0 0 #0000, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);",
|
|
436
|
-
"shadow-lg": "box-shadow: 0 0 #0000, 0 0 #0000, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);",
|
|
437
|
-
"shadow-xl": "box-shadow: 0 0 #0000, 0 0 #0000, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);",
|
|
438
|
-
"shadow-2xl": "box-shadow: 0 0 #0000, 0 0 #0000, 0 25px 50px -12px rgba(0, 0, 0, 0.25);",
|
|
439
|
-
"shadow-inner": "box-shadow: 0 0 #0000, 0 0 #0000, inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);",
|
|
440
|
-
"shadow-none": "box-shadow: 0 0 #0000, 0 0 #0000, 0 0 #0000;",
|
|
441
|
-
"ring-inset": "--tw-ring-inset: insest;",
|
|
442
|
-
"ring-0": "box-shadow: 0 0 0 calc(0px + 0px) rgba(59, 130, 246, 0.5);",
|
|
443
|
-
"ring-1": "box-shadow: 0 0 0 calc(1px + 0px) rgba(59, 130, 246, 0.5);",
|
|
444
|
-
"ring-2": "box-shadow: 0 0 0 calc(2px + 0px) rgba(59, 130, 246, 0.5);",
|
|
445
|
-
"ring-4": "box-shadow: 0 0 0 calc(4px + 0px) rgba(59, 130, 246, 0.5);",
|
|
446
|
-
"ring-8": "box-shadow: 0 0 0 calc(8px + 0px) rgba(59, 130, 246, 0.5);",
|
|
447
|
-
"ring": "box-shadow: 0 0 0 calc(3px + 0px) rgba(59, 130, 246, 0.5);",
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
func init() {
|
|
451
|
-
mapApply(sizes)
|
|
452
|
-
mapApply(spacing)
|
|
453
|
-
mapApply(colors)
|
|
454
|
-
mapApply(borders)
|
|
455
|
-
mapApply(radius)
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
func mapApply(obj KeyValues) {
|
|
459
|
-
for key, v := range obj.Keys {
|
|
460
|
-
for vkey, vv := range obj.Values {
|
|
461
|
-
suffix := ""
|
|
462
|
-
if vkey != "" {
|
|
463
|
-
suffix = "-" + vkey
|
|
464
|
-
}
|
|
465
|
-
className := key + suffix
|
|
466
|
-
if vstring, ok := v.(string); ok {
|
|
467
|
-
twClassLookup[className] = vstring + ": " + vv + ";"
|
|
468
|
-
}
|
|
469
|
-
if varr, ok := v.(Arr); ok {
|
|
470
|
-
twClassLookup[className] = ""
|
|
471
|
-
for _, kk := range varr {
|
|
472
|
-
twClassLookup[className] += kk.(string) + ": " + vv + ";"
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
func CreateTheme() {
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
func Styled(name string, classes string) {
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// var Button = Styled("button", "px-20 bg-gray-500")
|
|
486
|
-
|
|
487
|
-
var normalizeStyles = `
|
|
488
|
-
*, ::before, ::after { box-sizing: border-box; }
|
|
489
|
-
html { -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; line-height: 1.15; -webkit-text-size-adjust: 100%; }
|
|
490
|
-
body { margin: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; }
|
|
491
|
-
hr { height: 0; color: inherit; }
|
|
492
|
-
abbr[title] { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; }
|
|
493
|
-
b, strong { font-weight: bolder; }
|
|
494
|
-
code, kbd, samp, pre { font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace; font-size: 1em; }
|
|
495
|
-
small { font-size: 80%; }
|
|
496
|
-
sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
|
|
497
|
-
sub { bottom: -0.25em; }
|
|
498
|
-
sup { top: -0.5em; }
|
|
499
|
-
table { text-indent: 0; border-color: inherit; }
|
|
500
|
-
button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; }
|
|
501
|
-
button, select { text-transform: none; }
|
|
502
|
-
button, [type='button'], [type='reset'], [type='submit'] { -webkit-appearance: button; }
|
|
503
|
-
::-moz-focus-inner { border-style: none; padding: 0; }
|
|
504
|
-
:-moz-focusring { outline: 1px dotted ButtonText; outline: auto; }
|
|
505
|
-
:-moz-ui-invalid { box-shadow: none; }
|
|
506
|
-
legend { padding: 0; }
|
|
507
|
-
progress { vertical-align: baseline; }
|
|
508
|
-
::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; }
|
|
509
|
-
[type='search'] { -webkit-appearance: textfield; outline-offset: -2px; }
|
|
510
|
-
::-webkit-search-decoration { -webkit-appearance: none; }
|
|
511
|
-
::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; }
|
|
512
|
-
summary { display: list-item; }
|
|
513
|
-
blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { margin: 0; }
|
|
514
|
-
button { background-color: transparent; background-image: none; }
|
|
515
|
-
fieldset { margin: 0; padding: 0; }
|
|
516
|
-
ol, ul { list-style: none; margin: 0; padding: 0; }
|
|
517
|
-
html { font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; line-height: 1.5; }
|
|
518
|
-
body { font-family: inherit; line-height: inherit; }
|
|
519
|
-
*, ::before, ::after { box-sizing: border-box; border-width: 0; border-style: solid; border-color: currentColor; }
|
|
520
|
-
hr { border-top-width: 1px; }
|
|
521
|
-
img { border-style: solid; }
|
|
522
|
-
textarea { resize: vertical; }
|
|
523
|
-
input::-moz-placeholder, textarea::-moz-placeholder { opacity: 1; color: #9ca3af; }
|
|
524
|
-
input:-ms-input-placeholder, textarea:-ms-input-placeholder { opacity: 1; color: #9ca3af; }
|
|
525
|
-
input::placeholder, textarea::placeholder { opacity: 1; color: #9ca3af; }
|
|
526
|
-
button, [role="button"] { cursor: pointer; }
|
|
527
|
-
table { border-collapse: collapse; }
|
|
528
|
-
h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }
|
|
529
|
-
a { color: inherit; text-decoration: inherit; }
|
|
530
|
-
button, input, optgroup, select, textarea { padding: 0; line-height: inherit; color: inherit; }
|
|
531
|
-
pre, code, kbd, samp { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
|
532
|
-
img, svg, video, canvas, audio, iframe, embed, object { display: block; vertical-align: middle; }
|
|
533
|
-
img, video { max-width: 100%; height: auto; }
|
|
534
|
-
[hidden] { display: none; }
|
|
535
|
-
*, ::before, ::after { --tw-border-opacity: 1; border-color: rgba(229, 231, 235, var(--tw-border-opacity)); }
|
|
536
|
-
`
|
example/assets/assets.go
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
package assets
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"embed"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
//go:embed *
|
|
8
|
+
var FS embed.FS
|
example/components/header.go
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package components
|
|
2
2
|
|
|
3
3
|
func Header() string {
|
|
4
|
-
return `
|
|
4
|
+
return (`
|
|
5
5
|
<div class="flex flex-row justify-center items-center w-full mb-20 font-bold text-xl text-gray-700 p-4">
|
|
6
6
|
<div class="text-blue-700">
|
|
7
7
|
<a href="https://pyros.sh"> pyros.sh </a>
|
|
@@ -26,5 +26,5 @@ func Header() string {
|
|
|
26
26
|
</div>
|
|
27
27
|
{{ children }}
|
|
28
28
|
</div>
|
|
29
|
-
`
|
|
29
|
+
`)
|
|
30
30
|
}
|
example/components/page.go
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package components
|
|
2
2
|
|
|
3
3
|
func Page() string {
|
|
4
|
-
return `
|
|
4
|
+
return (`
|
|
5
5
|
<!DOCTYPE html>
|
|
6
6
|
<html lang="en">
|
|
7
7
|
<head>
|
|
@@ -22,5 +22,5 @@ func Page() string {
|
|
|
22
22
|
{{ children }}
|
|
23
23
|
</body>
|
|
24
24
|
</html>
|
|
25
|
-
`
|
|
25
|
+
`)
|
|
26
26
|
}
|
example/context/context.go
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
package context
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
c "context"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
func WithContext(ctx c.Context) (c.Context, error) {
|
|
8
|
-
return c.WithValue(ctx, "userId", "123"), nil
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
func GetUserID(ctx c.Context) string {
|
|
12
|
-
return ctx.Value("userId").(string)
|
|
13
|
-
}
|
example/main.go
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
package main
|
|
3
3
|
|
|
4
4
|
import (
|
|
5
|
-
c "context"
|
|
6
|
-
"embed"
|
|
7
|
-
"net/http"
|
|
8
5
|
"os"
|
|
9
6
|
|
|
10
7
|
"github.com/gorilla/mux"
|
|
@@ -12,6 +9,7 @@ import (
|
|
|
12
9
|
"github.com/rs/zerolog/log"
|
|
13
10
|
"gocloud.dev/server"
|
|
14
11
|
|
|
12
|
+
"github.com/pyros2097/gromer/example/assets"
|
|
15
13
|
"github.com/pyros2097/gromer/example/pages/api/todos"
|
|
16
14
|
"github.com/pyros2097/gromer/example/pages"
|
|
17
15
|
"github.com/pyros2097/gromer/example/pages/about"
|
|
@@ -20,25 +18,22 @@ import (
|
|
|
20
18
|
|
|
21
19
|
)
|
|
22
20
|
|
|
23
|
-
//go:embed assets/*
|
|
24
|
-
var assetsFS embed.FS
|
|
25
|
-
|
|
26
21
|
func main() {
|
|
27
22
|
port := os.Getenv("PORT")
|
|
28
23
|
r := mux.NewRouter()
|
|
29
24
|
r.Use(gromer.CorsMiddleware)
|
|
30
25
|
r.Use(gromer.LogMiddleware)
|
|
31
26
|
r.NotFoundHandler = gromer.NotFoundHandler
|
|
32
|
-
|
|
27
|
+
gromer.Static(r, "/assets/", assets.FS)
|
|
33
|
-
|
|
28
|
+
gromer.Handle(r, "GET", "/api", gromer.ApiExplorer, nil)
|
|
34
|
-
|
|
29
|
+
gromer.Handle(r, "GET", "/about", about.GET, about.GetParams{})
|
|
35
|
-
|
|
30
|
+
gromer.Handle(r, "GET", "/api/recover", recover.GET, recover.GetParams{})
|
|
36
|
-
|
|
31
|
+
gromer.Handle(r, "DELETE", "/api/todos/{todoId}", todos_todoId_.DELETE, todos_todoId_.DeleteParams{})
|
|
37
|
-
|
|
32
|
+
gromer.Handle(r, "GET", "/api/todos/{todoId}", todos_todoId_.GET, todos_todoId_.GetParams{})
|
|
38
|
-
|
|
33
|
+
gromer.Handle(r, "PUT", "/api/todos/{todoId}", todos_todoId_.PUT, todos_todoId_.PutParams{})
|
|
39
|
-
|
|
34
|
+
gromer.Handle(r, "GET", "/api/todos", todos.GET, todos.GetParams{})
|
|
40
|
-
|
|
35
|
+
gromer.Handle(r, "POST", "/api/todos", todos.POST, todos.PostParams{})
|
|
41
|
-
|
|
36
|
+
gromer.Handle(r, "GET", "/", pages.GET, pages.GetParams{})
|
|
42
37
|
|
|
43
38
|
println("http server listening on http://localhost:"+port)
|
|
44
39
|
srv := server.New(r, nil)
|
|
@@ -46,75 +41,3 @@ func main() {
|
|
|
46
41
|
log.Fatal().Stack().Err(err).Msg("failed to listen")
|
|
47
42
|
}
|
|
48
43
|
}
|
|
49
|
-
|
|
50
|
-
func wrapCache(h http.Handler) http.Handler {
|
|
51
|
-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
52
|
-
w.Header().Set("Cache-Control", "public, max-age=2592000")
|
|
53
|
-
h.ServeHTTP(w, r)
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
func handle(router *mux.Router, method, route string, h interface{}) {
|
|
58
|
-
router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
|
|
59
|
-
ctx := c.WithValue(
|
|
60
|
-
c.WithValue(
|
|
61
|
-
c.WithValue(r.Context(), "assetsFS", assetsFS),
|
|
62
|
-
"url", r.URL),
|
|
63
|
-
"header", r.Header)
|
|
64
|
-
gromer.PerformRequest(route, h, ctx, w, r)
|
|
65
|
-
}).Methods(method, "OPTIONS")
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func apiDefinitions() []gromer.ApiDefinition {
|
|
69
|
-
return []gromer.ApiDefinition{
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
Method: "GET",
|
|
73
|
-
Path: "/api/recover",
|
|
74
|
-
PathParams: []string{ },
|
|
75
|
-
Params: map[string]interface{}{
|
|
76
|
-
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
Method: "DELETE",
|
|
81
|
-
Path: "/api/todos/{todoId}",
|
|
82
|
-
PathParams: []string{ "todoId", },
|
|
83
|
-
Params: map[string]interface{}{
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
Method: "GET",
|
|
89
|
-
Path: "/api/todos/{todoId}",
|
|
90
|
-
PathParams: []string{ "todoId", },
|
|
91
|
-
Params: map[string]interface{}{
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
Method: "PUT",
|
|
97
|
-
Path: "/api/todos/{todoId}",
|
|
98
|
-
PathParams: []string{ "todoId", },
|
|
99
|
-
Params: map[string]interface{}{
|
|
100
|
-
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
Method: "GET",
|
|
105
|
-
Path: "/api/todos",
|
|
106
|
-
PathParams: []string{ },
|
|
107
|
-
Params: map[string]interface{}{
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
Method: "POST",
|
|
113
|
-
Path: "/api/todos",
|
|
114
|
-
PathParams: []string{ },
|
|
115
|
-
Params: map[string]interface{}{
|
|
116
|
-
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
}
|
|
120
|
-
}
|
example/pages/about/get.go
CHANGED
|
@@ -6,9 +6,17 @@ import (
|
|
|
6
6
|
. "github.com/pyros2097/gromer"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
+
type GetParams struct{}
|
|
10
|
+
|
|
9
11
|
func GET(c context.Context) (HtmlContent, int, error) {
|
|
10
12
|
return Html(`
|
|
13
|
+
{{#Page "gromer example"}}
|
|
14
|
+
<div class="flex flex-col justify-center items-center">
|
|
15
|
+
{{#Header "123"}}
|
|
16
|
+
A new link is here
|
|
17
|
+
{{/Header}}
|
|
18
|
+
<h1>About Me</h1>
|
|
11
|
-
|
|
19
|
+
</div>
|
|
12
|
-
<html lang="en">
|
|
13
|
-
|
|
20
|
+
{{/Page}}
|
|
21
|
+
`, M{})
|
|
14
22
|
}
|
example/pages/api/recover/get.go
CHANGED
|
@@ -5,11 +5,11 @@ import (
|
|
|
5
5
|
"fmt"
|
|
6
6
|
)
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type GetParams struct {
|
|
9
9
|
Limit int `json:"limit"`
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
func GET(ctx context.Context, params
|
|
12
|
+
func GET(ctx context.Context, params GetParams) (*GetParams, int, error) {
|
|
13
13
|
arr := []string{}
|
|
14
14
|
v := arr[55]
|
|
15
15
|
fmt.Printf("%s", v)
|
example/pages/api/todos/_todoId_/delete.go
CHANGED
|
@@ -6,6 +6,8 @@ import (
|
|
|
6
6
|
"github.com/pyros2097/gromer/example/db"
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
+
type DeleteParams struct{}
|
|
10
|
+
|
|
9
11
|
func DELETE(ctx context.Context, id string) (string, int, error) {
|
|
10
12
|
_, status, err := GET(ctx, id, GetParams{})
|
|
11
13
|
if err != nil {
|
example/pages/get.go
CHANGED
|
@@ -29,6 +29,7 @@ func GET(ctx context.Context, params GetParams) (HtmlContent, int, error) {
|
|
|
29
29
|
{{/Header}}
|
|
30
30
|
<h1>Hello this is a h1</h1>
|
|
31
31
|
<h2>Hello this is a h2</h2>
|
|
32
|
+
<img src="/assets/icon.png" width="48" height="48" />
|
|
32
33
|
<h3 x-data="{ message: 'I ❤️ Alpine' }" x-text="message">I ❤️ Alpine</h3>
|
|
33
34
|
<table class="table">
|
|
34
35
|
<thead>
|
example/readme.md
CHANGED
|
@@ -13,7 +13,6 @@ It uses postgres as the database and sqlc to generate queries and models. It use
|
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
15
|
make setup
|
|
16
|
-
make start-db
|
|
17
16
|
make generate
|
|
18
17
|
make run
|
|
19
18
|
```
|
hooks.go
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
package gromer
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
type StateContext struct {
|
|
8
|
-
index int
|
|
9
|
-
datas []interface{}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Go 1.18 stuff
|
|
13
|
-
// func UseState[T any](ctx *Context , s T) (func() T, func(v T)) {
|
|
14
|
-
// if len(ctx.datas) <= ctx.index + 1 {
|
|
15
|
-
// ctx .datas = append(ctx.datas, s)
|
|
16
|
-
// }
|
|
17
|
-
// return func() T {
|
|
18
|
-
// return ctx.datas[ctx.index].(T)
|
|
19
|
-
// }, func(v T) {
|
|
20
|
-
// ctx.datas[ctx.index] = v
|
|
21
|
-
// }
|
|
22
|
-
// }
|
|
23
|
-
|
|
24
|
-
func getState(ctx context.Context) *StateContext {
|
|
25
|
-
return ctx.Value("state").(*StateContext)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
func WithState(ctx context.Context) context.Context {
|
|
29
|
-
return context.WithValue(ctx, "state", &StateContext{})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func UseState(ctx context.Context, s interface{}) (func() interface{}, func(v interface{})) {
|
|
33
|
-
stateContext := getState(ctx)
|
|
34
|
-
localIndex := stateContext.index
|
|
35
|
-
if len(stateContext.datas) <= stateContext.index+1 {
|
|
36
|
-
stateContext.datas = append(stateContext.datas, s)
|
|
37
|
-
stateContext.index += 1
|
|
38
|
-
}
|
|
39
|
-
return func() interface{} {
|
|
40
|
-
return stateContext.datas[localIndex].(interface{})
|
|
41
|
-
}, func(v interface{}) {
|
|
42
|
-
stateContext.datas[localIndex] = v
|
|
43
|
-
}
|
|
44
|
-
}
|
hooks_test.go
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
package gromer
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
. "github.com/franela/goblin"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
func TestHooks(t *testing.T) {
|
|
11
|
-
g := Goblin(t)
|
|
12
|
-
g.Describe("useState", func() {
|
|
13
|
-
ctx := WithState(context.Background())
|
|
14
|
-
stateCtx := getState(ctx)
|
|
15
|
-
getValue, setValue := UseState(ctx, 12)
|
|
16
|
-
|
|
17
|
-
g.It("should be initialized ", func() {
|
|
18
|
-
g.Assert(1).Equal(stateCtx.index)
|
|
19
|
-
g.Assert(1).Equal(len(stateCtx.datas))
|
|
20
|
-
g.Assert(stateCtx.datas[0]).Equal(12)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
g.It("should get value ", func() {
|
|
24
|
-
g.Assert(getValue()).Equal(12)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
g.It("should set value", func() {
|
|
28
|
-
setValue(15)
|
|
29
|
-
g.Assert(stateCtx.datas[0]).Equal(15)
|
|
30
|
-
g.Assert(getValue()).Equal(15)
|
|
31
|
-
})
|
|
32
|
-
})
|
|
33
|
-
}
|
http.go
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package gromer
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"context"
|
|
5
|
+
"embed"
|
|
4
6
|
"encoding/json"
|
|
5
7
|
"fmt"
|
|
6
8
|
"html/template"
|
|
@@ -26,6 +28,15 @@ import (
|
|
|
26
28
|
var info *debug.BuildInfo
|
|
27
29
|
var IsCloundRun bool
|
|
28
30
|
|
|
31
|
+
var RouteDefs []RouteDefinition
|
|
32
|
+
|
|
33
|
+
type RouteDefinition struct {
|
|
34
|
+
Method string `json:"method"`
|
|
35
|
+
Pkg string `json:"pkg"`
|
|
36
|
+
Path string `json:"path"`
|
|
37
|
+
Params interface{} `json:"params"`
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
type HtmlContent string
|
|
30
41
|
|
|
31
42
|
func Html(tpl string, params map[string]interface{}) (HtmlContent, int, error) {
|
|
@@ -279,3 +290,22 @@ func CorsMiddleware(next http.Handler) http.Handler {
|
|
|
279
290
|
var NotFoundHandler = LogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
280
291
|
RespondError(w, 404, fmt.Errorf("path '%s' not found", r.URL.String()))
|
|
281
292
|
}))
|
|
293
|
+
|
|
294
|
+
func WrapCache(h http.Handler) http.Handler {
|
|
295
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
296
|
+
w.Header().Set("Cache-Control", "public, max-age=2592000")
|
|
297
|
+
h.ServeHTTP(w, r)
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
func Static(router *mux.Router, path string, fs embed.FS) {
|
|
302
|
+
router.PathPrefix(path).Handler(http.StripPrefix(path, WrapCache(http.FileServer(http.FS(fs)))))
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
func Handle(router *mux.Router, method, route string, h interface{}, params interface{}) {
|
|
306
|
+
RouteDefs = append(RouteDefs, RouteDefinition{method, route, "", params})
|
|
307
|
+
router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
|
|
308
|
+
ctx := context.WithValue(context.WithValue(r.Context(), "url", r.URL), "header", r.Header)
|
|
309
|
+
PerformRequest(route, h, ctx, w, r)
|
|
310
|
+
}).Methods(method, "OPTIONS")
|
|
311
|
+
}
|
readme.md
CHANGED
|
@@ -13,6 +13,8 @@ It also generates http handlers for your routes which follow a particular folder
|
|
|
13
13
|
go get -u -v github.com/pyros2097/gromer/cmd/gromer
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
You can install this plugin https://marketplace.visualstudio.com/items?itemName=pyros2097.vscode-go-inline-html for syntax highlighting html templates in golang.
|
|
17
|
+
|
|
16
18
|
# Using
|
|
17
19
|
|
|
18
20
|
You need to follow this directory structure similar to nextjs for the api route handlers to be generated
|
utils.go
CHANGED
|
@@ -11,6 +11,10 @@ import (
|
|
|
11
11
|
"github.com/segmentio/go-camelcase"
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
type M map[string]interface{}
|
|
15
|
+
type MS map[string]string
|
|
16
|
+
type Arr []interface{}
|
|
17
|
+
|
|
14
18
|
var Validator = validator.New()
|
|
15
19
|
var ValidatorErrorMap = map[string]string{
|
|
16
20
|
"required": "is required",
|