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


80a2a235 Peter John

tag: v0.6.0

v0.6.0

4 years ago
improve api
Files changed (3) hide show
  1. cmd/wapp/main.go +28 -21
  2. html.go +13 -38
  3. readme.md +10 -0
cmd/wapp/main.go CHANGED
@@ -7,18 +7,20 @@ import (
7
7
  "log"
8
8
  "os"
9
9
  "path/filepath"
10
+ "regexp"
10
11
  "strings"
11
12
 
12
13
  "github.com/gobuffalo/velvet"
13
14
  "golang.org/x/mod/modfile"
14
15
  )
15
16
 
17
+ var pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
18
+
16
19
  type Route struct {
17
20
  Method string
18
21
  Path string
19
22
  Pkg string
20
- ApiSrc string
23
+ Params []string
21
- IsApi bool
22
24
  }
23
25
 
24
26
  type ApiCall struct {
@@ -86,9 +88,8 @@ func rewritePkg(pkg string) string {
86
88
  return lastItem
87
89
  }
88
90
 
89
- func getApiFunc(method, route string) ApiCall {
91
+ func getApiFunc(method, route string, params []string) ApiCall {
90
92
  muxRoute := bytes.NewBuffer(nil)
91
- params := ""
92
93
  foundStart := false
93
94
  funcName := strings.ToLower(method)
94
95
  parts := strings.Split(route, "/")
@@ -104,18 +105,18 @@ func getApiFunc(method, route string) ApiCall {
104
105
  } else if string(v) == "_" && foundStart {
105
106
  foundStart = false
106
107
  muxRoute.WriteString("}")
107
- params += ", "
108
108
  } else {
109
- if foundStart {
110
- params += string(v)
111
- }
112
109
  muxRoute.WriteString(string(v))
113
110
  }
114
111
  }
112
+ paramsStrings := ""
113
+ if len(params) > 0 {
114
+ paramsStrings += strings.Join(params, ", ") + ", params"
115
+ }
115
116
  return ApiCall{
116
117
  Method: method,
117
118
  Name: funcName,
118
- Params: params + "params",
119
+ Params: paramsStrings,
119
120
  Path: muxRoute.String(),
120
121
  }
121
122
  }
@@ -143,6 +144,8 @@ func main() {
143
144
  method := getMethod(route)
144
145
  path := getRoute(method, route)
145
146
  pkg := getPackage(path)
147
+ routePath := rewritePath(path)
148
+ params := pathParamsRegex.FindAllString(routePath, -1)
146
149
  allPkgs[pkg] = ""
147
150
  if path == "" { // for index page
148
151
  path = "/"
@@ -150,11 +153,12 @@ func main() {
150
153
  }
151
154
  routes = append(routes, &Route{
152
155
  Method: method,
153
- Path: rewritePath(path),
156
+ Path: routePath,
154
157
  Pkg: rewritePkg(pkg),
158
+ Params: params,
155
159
  })
156
160
  if strings.Contains(path, "/api/") {
157
- apiCalls = append(apiCalls, getApiFunc(method, path))
161
+ apiCalls = append(apiCalls, getApiFunc(method, path, params))
158
162
  }
159
163
  }
160
164
  return nil
@@ -163,7 +167,7 @@ func main() {
163
167
  log.Fatal(err)
164
168
  }
165
169
  for _, r := range routes {
166
- println(r.Method, r.Path)
170
+ fmt.Printf("%-6s %s\n", r.Method, r.Path)
167
171
  }
168
172
  ctx := velvet.NewContext()
169
173
  ctx.Set("moduleName", moduleName)
@@ -197,7 +201,7 @@ func main() {
197
201
  isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
198
202
  r := mux.NewRouter()
199
203
  r.PathPrefix("/assets/").Handler(http.FileServer(http.FS(assetsFS)))
200
- {{#each routes as |route| }}r.HandleFunc("{{ route.Path }}", wrap({{ route.Pkg }}.{{ route.Method }})).Methods("{{ route.Method }}")
204
+ {{#each routes as |route| }}handle(r, "{{ route.Method }}", "{{ route.Path }}", {{ route.Pkg }}.{{ route.Method }})
201
205
  {{/each}}
202
206
  if !isLambda {
203
207
  println("http server listening on http://localhost:3000")
@@ -214,20 +218,20 @@ func main() {
214
218
  }
215
219
  }
216
220
 
217
- func wrap(h interface{}) func(http.ResponseWriter, *http.Request) {
221
+ func handle(router *mux.Router, method, route string, h interface{}) {
218
- return func(w http.ResponseWriter, r *http.Request) {
222
+ router.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
219
223
  ctx, err := context.NewReqContext(r)
220
224
  if err != nil {
221
225
  wapp.RespondError(w, 500, err)
222
226
  return
223
227
  }
224
- err = wapp.PerformRequest(h, ctx, w, r)
228
+ err = wapp.PerformRequest(route, h, ctx, w, r)
225
229
  if err != nil {
226
230
  ctx.Rollback()
227
231
  } else {
228
232
  ctx.Commit()
229
233
  }
230
- }
234
+ }).Methods(method)
231
235
  }
232
236
  `, ctx)
233
237
  if err != nil {
@@ -239,14 +243,17 @@ func wrap(h interface{}) func(http.ResponseWriter, *http.Request) {
239
243
  }
240
244
  js, err := velvet.Render(`// Code generated by wapp. DO NOT EDIT.
241
245
  import queryString from 'query-string';
246
+ import config from './config';
242
247
 
243
248
  const apiCall = async (method, route, params) => {
244
249
  const qs = method === 'GET' && params ? '?' + queryString.stringify(params) : '';
245
250
  const body = method !== 'GET' ? JSON.stringify(params) : null;
251
+ const endpoint = await config.getApiEndpoint();
252
+ const token = await config.getAuthToken();
246
- const res = await fetch({{tick}}/api/${route}${qs}{{tick}}, {
253
+ const res = await fetch({{tick}}${endpoint}/api/${route}${qs}{{tick}}, {
247
254
  method,
248
255
  headers: {
249
- Authorization: localStorage.getItem('token'),
256
+ Authorization: token,
250
257
  },
251
258
  body,
252
259
  });
@@ -259,8 +266,8 @@ const apiCall = async (method, route, params) => {
259
266
  }
260
267
 
261
268
  export default {
262
- {{#each apiCalls as |api| }}{{api.Name}}: ({{api.Params}}) => apiCall('{{api.Method}}', {{tick}}{{api.Path}}{{tick}}, params),
269
+ {{#each apiCalls as |api| }}{{api.Name}}: ({{api.Params}}) => apiCall('{{api.Method}}', {{tick}}{{api.Path}}{{tick}}, params),
263
- {{/each}}
270
+ {{/each}}
264
271
  }
265
272
  `, ctx)
266
273
  if err != nil {
html.go CHANGED
@@ -7,9 +7,11 @@ import (
7
7
  "io"
8
8
  "net/http"
9
9
  "reflect"
10
- "runtime"
10
+ "regexp"
11
11
  "strconv"
12
12
  "strings"
13
+
14
+ "github.com/gorilla/mux"
13
15
  )
14
16
 
15
17
  func writeIndent(w io.Writer, indent int) {
@@ -75,22 +77,10 @@ func (p *HtmlPage) computeCss(elems []*Element) {
75
77
  }
76
78
  }
77
79
 
78
- func (p *HtmlPage) computeJs(elems []*Element) {
79
- for _, el := range elems {
80
- if strings.HasPrefix(el.text, "wapp_js|") {
81
- p.js.WriteString(strings.Replace(el.text, "wapp_js|", "", 1)) // + "\n" // TODO check with multiple components
82
- }
83
- if len(el.children) > 0 {
84
- p.computeJs(el.children)
85
- }
86
- }
87
- }
88
-
89
80
  func (p *HtmlPage) WriteHtml(w io.Writer) {
90
81
  w.Write([]byte("<!DOCTYPE html>\n"))
91
82
  w.Write([]byte("<html>\n"))
92
83
  p.computeCss(p.Body.children)
93
- p.computeJs(p.Body.children)
94
84
  p.Head.children = append(p.Head.children, StyleTag(Text(normalizeStyles+p.css.String())))
95
85
  p.Head.writeHtmlIndent(w, 1)
96
86
  p.Body.children = append(p.Body.children, Script(Text(fmt.Sprintf(`
@@ -124,28 +114,6 @@ func Body(elems ...*Element) *Element {
124
114
  return &Element{tag: "body", children: elems}
125
115
  }
126
116
 
127
- func Component(state interface{}, uis ...interface{}) *Element {
128
- pc, _, _, _ := runtime.Caller(1)
129
- details := runtime.FuncForPC(pc)
130
- arr := strings.Split(details.Name(), ".")
131
- name := strings.ToLower(arr[len(arr)-1])
132
- // actionsMap := map[string]interface{}{}
133
- // actionsType := reflect.TypeOf(actions)
134
- // for k, v := range r.Actions {
135
- // actionsMap[k] = "() {" + v() + "}"
136
- // }
137
- stateData, err := json.MarshalIndent(state, " ", " ")
138
- if err != nil {
139
- panic(err)
140
- }
141
- js := fmt.Sprintf(`
142
- Alpine.data('%s', () => (%s));`, name, string(stateData))
143
- if err != nil {
144
- panic(err)
145
- }
146
- return mergeAttributes(&Element{tag: name, text: "wapp_js|" + js}, append([]interface{}{XData(name)}, uis...)...)
147
- }
148
-
149
117
  type Element struct {
150
118
  tag string
151
119
  attrs map[string]string
@@ -446,12 +414,19 @@ func RespondError(w http.ResponseWriter, status int, err error) {
446
414
  w.Write(data)
447
415
  }
448
416
 
417
+ var pathParamsRegex = regexp.MustCompile(`{(.*?)}`)
418
+
449
- func PerformRequest(h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) error {
419
+ func PerformRequest(route string, h interface{}, ctx interface{}, w http.ResponseWriter, r *http.Request) error {
420
+ params := pathParamsRegex.FindAllString(route, -1)
450
421
  args := []reflect.Value{reflect.ValueOf(ctx)}
451
422
  funcType := reflect.TypeOf(h)
452
423
  icount := funcType.NumIn()
424
+ vars := mux.Vars(r)
425
+ for _, k := range params {
426
+ args = append(args, reflect.ValueOf(vars[k]))
427
+ }
453
- if icount == 2 {
428
+ if len(args) != icount {
454
- structType := funcType.In(1)
429
+ structType := funcType.In(icount - 1)
455
430
  instance := reflect.New(structType)
456
431
  if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
457
432
  err := json.NewDecoder(r.Body).Decode(instance.Interface())
readme.md CHANGED
@@ -26,3 +26,13 @@ go get -u -v github.com/pyros2097/wapp/cmd/wapp
26
26
  # Example
27
27
 
28
28
  https://github.com/pyros2097/wapp-example
29
+
30
+ ```go
31
+ func WithWappContext(ctx context.Context) context.Context {
32
+ return context.WithValue(ctx, "wapp", WappContext{})
33
+ }
34
+
35
+ func GetWappContext(ctx context.Context) WappContext {
36
+ return ctx.Value("wapp").(WaåppContext)
37
+ }
38
+ ```