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


a49aad0d pyros2097

5 years ago
improve example
app_nowasm.go CHANGED
@@ -2,10 +2,72 @@
2
2
 
3
3
  package app
4
4
 
5
+ import (
6
+ "bytes"
7
+ "fmt"
8
+ "net/http"
9
+ "os"
10
+ "path/filepath"
11
+ "strconv"
12
+ "strings"
13
+
14
+ "github.com/akrylysov/algnhsa"
15
+ "github.com/markbates/pkger"
16
+ )
17
+
18
+ func Run(isAwsLambda bool, routes map[string]RenderFunc) {
19
+ wd, err := os.Getwd()
20
+ if err != nil {
21
+ fmt.Printf("could not get wd")
22
+ return
23
+ }
24
+ assetsFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
25
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
26
+ println("route: " + r.URL.Path)
27
+ renderFunc := MatchRoute(routes, r.URL.Path)
28
+ if strings.Contains(r.URL.Path, "/assets") {
29
+ r.URL.Path = strings.Replace(r.URL.Path, "/assets", "", 1)
30
+ assetsFileServer.ServeHTTP(w, r)
31
+ return
32
+ }
33
+ page := createPage("wapp-example", renderFunc(NewRenderContext()))
34
+ w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
35
+ w.Header().Set("Content-Type", "text/html")
36
+ w.WriteHeader(http.StatusOK)
37
+ w.Write(page.Bytes())
38
+ })
39
+ if !isAwsLambda {
40
+ println("Serving on HTTP port: 1234")
41
+ http.ListenAndServe(":1234", nil)
42
+ } else {
43
+ println("algnhsa serving default mux")
44
+ algnhsa.ListenAndServe(http.DefaultServeMux, nil)
45
+ }
46
+ }
47
+
5
48
  func Reload() {
6
49
  panic("wasm required")
7
50
  }
8
51
 
9
- func Run(r RenderFunc) {
10
- panic("wasm required")
52
+ func createPage(title string, ui UI) *bytes.Buffer {
53
+ page := bytes.NewBuffer(nil)
54
+ page.WriteString("<!DOCTYPE html>\n")
55
+ elems := FilterUIElems(ui)
56
+ Html(
57
+ Head(
58
+ Title(title),
59
+ Meta("author", "pyros2097"),
60
+ Meta("description", "Description"),
61
+ Meta("keywords", ""),
62
+ Meta("theme-color", ""),
63
+ Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
64
+ Link("icon", "/assets/icon.png"),
65
+ Link("apple-touch-icon", "/assets/icon.png"),
66
+ Link("stylesheet", "/assets/styles.css"),
67
+ Link("manifest", "manifest"),
68
+ Script(`const enosys = () => { const a = new Error("not implemented"); return a.code = "ENOSYS", a }; let outputBuf = ""; window.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, writeSync(a, b) { outputBuf += decoder.decode(b); const c = outputBuf.lastIndexOf("\n"); return -1 != c && (console.log(outputBuf.substr(0, c)), outputBuf = outputBuf.substr(c + 1)), b.length }, write(a, b, c, d, e, f) { if (0 !== c || d !== b.length || null !== e) return void f(enosys()); const g = this.writeSync(a, b); f(null, g) } }; const encoder = new TextEncoder("utf-8"), decoder = new TextDecoder("utf-8"); class Go { constructor() { this.argv = ["js"], this.env = {}, this.exit = a => { 0 !== a && console.warn("exit code:", a) }, this._exitPromise = new Promise(a => { this._resolveExitPromise = a }), this._pendingEvent = null, this._scheduledTimeouts = new Map, this._nextCallbackTimeoutID = 1; const a = (a, b) => { this.mem.setUint32(a + 0, b, !0), this.mem.setUint32(a + 4, Math.floor(b / 4294967296), !0) }, b = a => { const b = this.mem.getUint32(a + 0, !0), c = this.mem.getInt32(a + 4, !0); return b + 4294967296 * c }, c = a => { const b = this.mem.getFloat64(a, !0); if (0 !== b) { if (!isNaN(b)) return b; const c = this.mem.getUint32(a, !0); return this._values[c] } }, d = (a, b) => { const c = 2146959360; if ("number" == typeof b) return isNaN(b) ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 0, !0)) : 0 === b ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 1, !0)) : void this.mem.setFloat64(a, b, !0); switch (b) { case void 0: return void this.mem.setFloat64(a, 0, !0); case null: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 2, !0); case !0: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 3, !0); case !1: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 4, !0); }let d = this._ids.get(b); d === void 0 && (d = this._idPool.pop(), d === void 0 && (d = this._values.length), this._values[d] = b, this._goRefCounts[d] = 0, this._ids.set(b, d)), this._goRefCounts[d]++; let e = 1; switch (typeof b) { case "string": e = 2; break; case "symbol": e = 3; break; case "function": e = 4; }this.mem.setUint32(a + 4, 2146959360 | e, !0), this.mem.setUint32(a, d, !0) }, e = a => { const c = b(a + 0), d = b(a + 8); return new Uint8Array(this._inst.exports.mem.buffer, c, d) }, f = d => { const e = b(d + 0), f = b(d + 8), g = Array(f); for (let a = 0; a < f; a++)g[a] = c(e + 8 * a); return g }, g = a => { const c = b(a + 0), d = b(a + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, c, d)) }, h = Date.now() - performance.now(); this.importObject = { go: { "runtime.wasmExit": a => { const b = this.mem.getInt32(a + 8, !0); this.exited = !0, delete this._inst, delete this._values, delete this._goRefCounts, delete this._ids, delete this._idPool, this.exit(b) }, "runtime.wasmWrite": a => { const c = b(a + 8), d = b(a + 16), e = this.mem.getInt32(a + 24, !0); fs.writeSync(c, new Uint8Array(this._inst.exports.mem.buffer, d, e)) }, "runtime.resetMemoryDataView": () => { this.mem = new DataView(this._inst.exports.mem.buffer) }, "runtime.nanotime1": b => { a(b + 8, 1e6 * (h + performance.now())) }, "runtime.walltime1": b => { const c = new Date().getTime(); a(b + 8, c / 1e3), this.mem.setInt32(b + 16, 1e6 * (c % 1e3), !0) }, "runtime.scheduleTimeoutEvent": a => { const c = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++, this._scheduledTimeouts.set(c, setTimeout(() => { for (this._resume(); this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"), this._resume() }, b(a + 8) + 1)), this.mem.setInt32(a + 16, c, !0) }, "runtime.clearTimeoutEvent": a => { const b = this.mem.getInt32(a + 8, !0); clearTimeout(this._scheduledTimeouts.get(b)), this._scheduledTimeouts.delete(b) }, "runtime.getRandomData": a => { crypto.getRandomValues(e(a + 8)) }, "syscall/js.finalizeRef": a => { const b = this.mem.getUint32(a + 8, !0); if (this._goRefCounts[b]--, 0 === this._goRefCounts[b]) { const a = this._values[b]; this._values[b] = null, this._ids.delete(a), this._idPool.push(b) } }, "syscall/js.stringVal": a => { d(a + 24, g(a + 8)) }, "syscall/js.valueGet": a => { const b = Reflect.get(c(a + 8), g(a + 16)); a = this._inst.exports.getsp(), d(a + 32, b) }, "syscall/js.valueSet": a => { Reflect.set(c(a + 8), g(a + 16), c(a + 32)) }, "syscall/js.valueDelete": a => { Reflect.deleteProperty(c(a + 8), g(a + 16)) }, "syscall/js.valueIndex": a => { d(a + 24, Reflect.get(c(a + 8), b(a + 16))) }, "syscall/js.valueSetIndex": a => { Reflect.set(c(a + 8), b(a + 16), c(a + 24)) }, "syscall/js.valueCall": a => { try { const b = c(a + 8), e = Reflect.get(b, g(a + 16)), h = f(a + 32), i = Reflect.apply(e, b, h); a = this._inst.exports.getsp(), d(a + 56, i), this.mem.setUint8(a + 64, 1) } catch (b) { d(a + 56, b), this.mem.setUint8(a + 64, 0) } }, "syscall/js.valueInvoke": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.apply(b, void 0, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueNew": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.construct(b, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueLength": b => { a(b + 16, parseInt(c(b + 8).length)) }, "syscall/js.valuePrepareString": b => { const e = encoder.encode(c(b + 8) + ""); d(b + 16, e), a(b + 24, e.length) }, "syscall/js.valueLoadString": a => { const b = c(a + 8); e(a + 16).set(b) }, "syscall/js.valueInstanceOf": a => { this.mem.setUint8(a + 24, c(a + 8) instanceof c(a + 16)) }, "syscall/js.copyBytesToGo": b => { const d = e(b + 8), f = c(b + 32); if (!(f instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, "syscall/js.copyBytesToJS": b => { const d = c(b + 8), f = e(b + 16); if (!(d instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, debug: a => { console.log(a) } } } } async run(a) { this._inst = a, this.mem = new DataView(this._inst.exports.mem.buffer), this._values = [NaN, 0, null, !0, !1, window, this], this._goRefCounts = [], this._ids = new Map, this._idPool = [], this.exited = !1; let b = 4096; const c = a => { const c = b, d = encoder.encode(a + "\0"); return new Uint8Array(this.mem.buffer, b, d.length).set(d), b += d.length, 0 != b % 8 && (b += 8 - b % 8), c }, d = this.argv.length, e = []; this.argv.forEach(a => { e.push(c(a)) }), e.push(0); const f = Object.keys(this.env).sort(); f.forEach(a => { e.push(c(a + "=" + this.env[a])) }), e.push(0); const g = b; e.forEach(a => { this.mem.setUint32(b, a, !0), this.mem.setUint32(b + 4, 0, !0), b += 8 }), this._inst.exports.run(d, g), this.exited && this._resolveExitPromise(), await this._exitPromise } _resume() { if (this.exited) throw new Error("Go program has already exited"); this._inst.exports.resume(), this.exited && this._resolveExitPromise() } _makeFuncWrapper(a) { const b = this; return function () { const c = { id: a, this: this, args: arguments }; return b._pendingEvent = c, b._resume(), c.result } } } const go = new Go; WebAssembly.instantiateStreaming(fetch("/assets/main.wasm"), go.importObject).then(a => go.run(a.instance)).catch(a => console.error("could not load wasm", a));`),
69
+ ),
70
+ Body(elems[0]),
71
+ ).Html(page)
72
+ return page
11
73
  }
app_wasm.go CHANGED
@@ -11,7 +11,8 @@ var (
11
11
  rootPrefix string
12
12
  )
13
13
 
14
- func Run(render RenderFunc) {
14
+ func Run(isAwsLambda bool, routes map[string]RenderFunc) {
15
+ renderFunc := MatchRoute(routes, js.Window.URL().Path)
15
16
  defer func() {
16
17
  err := recover()
17
18
  // show alert
@@ -20,10 +21,10 @@ func Run(render RenderFunc) {
20
21
 
21
22
  initBody()
22
23
  initContent()
23
- if err := body.replaceChildAt(0, render); err != nil {
24
+ if err := body.replaceChildAt(0, renderFunc); err != nil {
24
25
  panic("replacing content failed")
25
26
  }
26
- content = render
27
+ content = renderFunc
27
28
 
28
29
  for {
29
30
  select {
cli/cli.go CHANGED
@@ -1,353 +1,362 @@
1
1
  package cli
2
2
 
3
- import (
4
- "bytes"
5
- "fmt"
6
- "io/ioutil"
7
- "log"
8
- "net/http"
9
- "os"
10
- "os/exec"
11
- "path/filepath"
12
- "plugin"
13
- "strconv"
14
- "strings"
15
- "text/template"
16
- "time"
17
-
18
- "github.com/markbates/pkger"
19
- app "github.com/pyros2097/wapp"
20
- "gopkg.in/fsnotify.v1"
21
- )
22
-
23
- const wasmExecTemplate = `const enosys=()=>{const a=new Error("not implemented");return a.code="ENOSYS",a};let outputBuf="";window.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync(a,b){outputBuf+=decoder.decode(b);const c=outputBuf.lastIndexOf("\n");return-1!=c&&(console.log(outputBuf.substr(0,c)),outputBuf=outputBuf.substr(c+1)),b.length},write(a,b,c,d,e,f){if(0!==c||d!==b.length||null!==e)return void f(enosys());const g=this.writeSync(a,b);f(null,g)}};const encoder=new TextEncoder("utf-8"),decoder=new TextDecoder("utf-8");class Go{constructor(){this.argv=["js"],this.env={},this.exit=a=>{0!==a&&console.warn("exit code:",a)},this._exitPromise=new Promise(a=>{this._resolveExitPromise=a}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const a=(a,b)=>{this.mem.setUint32(a+0,b,!0),this.mem.setUint32(a+4,Math.floor(b/4294967296),!0)},b=a=>{const b=this.mem.getUint32(a+0,!0),c=this.mem.getInt32(a+4,!0);return b+4294967296*c},c=a=>{const b=this.mem.getFloat64(a,!0);if(0!==b){if(!isNaN(b))return b;const c=this.mem.getUint32(a,!0);return this._values[c]}},d=(a,b)=>{const c=2146959360;if("number"==typeof b)return isNaN(b)?(this.mem.setUint32(a+4,2146959360,!0),void this.mem.setUint32(a,0,!0)):0===b?(this.mem.setUint32(a+4,2146959360,!0),void this.mem.setUint32(a,1,!0)):void this.mem.setFloat64(a,b,!0);switch(b){case void 0:return void this.mem.setFloat64(a,0,!0);case null:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,2,!0);case!0:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,3,!0);case!1:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,4,!0);}let d=this._ids.get(b);d===void 0&&(d=this._idPool.pop(),d===void 0&&(d=this._values.length),this._values[d]=b,this._goRefCounts[d]=0,this._ids.set(b,d)),this._goRefCounts[d]++;let e=1;switch(typeof b){case"string":e=2;break;case"symbol":e=3;break;case"function":e=4;}this.mem.setUint32(a+4,2146959360|e,!0),this.mem.setUint32(a,d,!0)},e=a=>{const c=b(a+0),d=b(a+8);return new Uint8Array(this._inst.exports.mem.buffer,c,d)},f=d=>{const e=b(d+0),f=b(d+8),g=Array(f);for(let a=0;a<f;a++)g[a]=c(e+8*a);return g},g=a=>{const c=b(a+0),d=b(a+8);return decoder.decode(new DataView(this._inst.exports.mem.buffer,c,d))},h=Date.now()-performance.now();this.importObject={go:{"runtime.wasmExit":a=>{const b=this.mem.getInt32(a+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._goRefCounts,delete this._ids,delete this._idPool,this.exit(b)},"runtime.wasmWrite":a=>{const c=b(a+8),d=b(a+16),e=this.mem.getInt32(a+24,!0);fs.writeSync(c,new Uint8Array(this._inst.exports.mem.buffer,d,e))},"runtime.resetMemoryDataView":()=>{this.mem=new DataView(this._inst.exports.mem.buffer)},"runtime.nanotime1":b=>{a(b+8,1e6*(h+performance.now()))},"runtime.walltime1":b=>{const c=new Date().getTime();a(b+8,c/1e3),this.mem.setInt32(b+16,1e6*(c%1e3),!0)},"runtime.scheduleTimeoutEvent":a=>{const c=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(c,setTimeout(()=>{for(this._resume();this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"),this._resume()},b(a+8)+1)),this.mem.setInt32(a+16,c,!0)},"runtime.clearTimeoutEvent":a=>{const b=this.mem.getInt32(a+8,!0);clearTimeout(this._scheduledTimeouts.get(b)),this._scheduledTimeouts.delete(b)},"runtime.getRandomData":a=>{crypto.getRandomValues(e(a+8))},"syscall/js.finalizeRef":a=>{const b=this.mem.getUint32(a+8,!0);if(this._goRefCounts[b]--,0===this._goRefCounts[b]){const a=this._values[b];this._values[b]=null,this._ids.delete(a),this._idPool.push(b)}},"syscall/js.stringVal":a=>{d(a+24,g(a+8))},"syscall/js.valueGet":a=>{const b=Reflect.get(c(a+8),g(a+16));a=this._inst.exports.getsp(),d(a+32,b)},"syscall/js.valueSet":a=>{Reflect.set(c(a+8),g(a+16),c(a+32))},"syscall/js.valueDelete":a=>{Reflect.deleteProperty(c(a+8),g(a+16))},"syscall/js.valueIndex":a=>{d(a+24,Reflect.get(c(a+8),b(a+16)))},"syscall/js.valueSetIndex":a=>{Reflect.set(c(a+8),b(a+16),c(a+24))},"syscall/js.valueCall":a=>{try{const b=c(a+8),e=Reflect.get(b,g(a+16)),h=f(a+32),i=Reflect.apply(e,b,h);a=this._inst.exports.getsp(),d(a+56,i),this.mem.setUint8(a+64,1)}catch(b){d(a+56,b),this.mem.setUint8(a+64,0)}},"syscall/js.valueInvoke":a=>{try{const b=c(a+8),e=f(a+16),g=Reflect.apply(b,void 0,e);a=this._inst.exports.getsp(),d(a+40,g),this.mem.setUint8(a+48,1)}catch(b){d(a+40,b),this.mem.setUint8(a+48,0)}},"syscall/js.valueNew":a=>{try{const b=c(a+8),e=f(a+16),g=Reflect.construct(b,e);a=this._inst.exports.getsp(),d(a+40,g),this.mem.setUint8(a+48,1)}catch(b){d(a+40,b),this.mem.setUint8(a+48,0)}},"syscall/js.valueLength":b=>{a(b+16,parseInt(c(b+8).length))},"syscall/js.valuePrepareString":b=>{const e=encoder.encode(c(b+8)+"");d(b+16,e),a(b+24,e.length)},"syscall/js.valueLoadString":a=>{const b=c(a+8);e(a+16).set(b)},"syscall/js.valueInstanceOf":a=>{this.mem.setUint8(a+24,c(a+8)instanceof c(a+16))},"syscall/js.copyBytesToGo":b=>{const d=e(b+8),f=c(b+32);if(!(f instanceof Uint8Array))return void this.mem.setUint8(b+48,0);const g=f.subarray(0,d.length);d.set(g),a(b+40,g.length),this.mem.setUint8(b+48,1)},"syscall/js.copyBytesToJS":b=>{const d=c(b+8),f=e(b+16);if(!(d instanceof Uint8Array))return void this.mem.setUint8(b+48,0);const g=f.subarray(0,d.length);d.set(g),a(b+40,g.length),this.mem.setUint8(b+48,1)},debug:a=>{console.log(a)}}}}async run(a){this._inst=a,this.mem=new DataView(this._inst.exports.mem.buffer),this._values=[NaN,0,null,!0,!1,window,this],this._goRefCounts=[],this._ids=new Map,this._idPool=[],this.exited=!1;let b=4096;const c=a=>{const c=b,d=encoder.encode(a+"\0");return new Uint8Array(this.mem.buffer,b,d.length).set(d),b+=d.length,0!=b%8&&(b+=8-b%8),c},d=this.argv.length,e=[];this.argv.forEach(a=>{e.push(c(a))}),e.push(0);const f=Object.keys(this.env).sort();f.forEach(a=>{e.push(c(a+"="+this.env[a]))}),e.push(0);const g=b;e.forEach(a=>{this.mem.setUint32(b,a,!0),this.mem.setUint32(b+4,0,!0),b+=8}),this._inst.exports.run(d,g),this.exited&&this._resolveExitPromise(),await this._exitPromise}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(a){const b=this;return function(){const c={id:a,this:this,args:arguments};return b._pendingEvent=c,b._resume(),c.result}}}const go=new Go;WebAssembly.instantiateStreaming(fetch("__path__"),go.importObject).then(a=>go.run(a.instance)).catch(a=>console.error("could not load wasm",a));`
24
-
25
- func wasmExecJs(path string) string {
26
- return strings.Replace(wasmExecTemplate, "__path__", path, 1)
27
- }
28
-
29
- var watchDelta = 1000 * time.Millisecond
30
-
31
- type Watcher struct {
32
- rootdir string
33
- watcher *fsnotify.Watcher
34
- watchVendor bool
35
- update chan string
36
- }
37
-
38
- // MustRegisterWatcher creates a new Watcher and starts listening to
39
- // given folders
40
- func MustRegisterWatcher() *Watcher {
41
- w := &Watcher{
42
- update: make(chan string),
43
- watchVendor: false,
44
- }
45
- var err error
46
- w.watcher, err = fsnotify.NewWatcher()
47
- if err != nil {
48
- log.Fatalf("Could not register watcher: %s", err)
49
- }
50
- w.watchFolders()
51
- return w
52
- }
53
-
54
- // Watch listens file updates, and sends signal to
55
- // update channel when .go and .tmpl files are updated
56
- func (w *Watcher) Watch() {
57
- eventSent := false
58
- for {
59
- select {
60
- case event := <-w.watcher.Events:
61
- // discard chmod events
62
- if event.Op&fsnotify.Chmod != fsnotify.Chmod {
63
- // test files do not need a rebuild
64
- if isTestFile(event.Name) {
65
- continue
66
- }
67
- if !isWatchedFileType(event.Name) {
68
- continue
69
- }
70
- if eventSent {
71
- continue
72
- }
73
- eventSent = true
74
- // prevent consequent builds
75
- go func() {
76
- w.update <- event.Name
77
- time.Sleep(watchDelta)
78
- eventSent = false
79
- }()
80
-
81
- }
82
- case err := <-w.watcher.Errors:
83
- if err != nil {
84
- log.Fatalf("Watcher error: %s", err)
85
- }
86
- return
87
- }
88
- }
89
- }
90
-
91
- func isTestFile(fileName string) bool {
92
- return strings.HasSuffix(filepath.Base(fileName), "_test.go")
93
- }
94
-
95
- func isWatchedFileType(fileName string) bool {
96
- ext := filepath.Ext(fileName)
97
-
98
- return ext == ".go"
99
- }
100
-
101
- // Close closes the fsnotify watcher channel
102
- func (w *Watcher) Close() {
103
- w.watcher.Close()
104
- close(w.update)
105
- }
106
-
107
- // watchFolders recursively adds folders that will be watched against the changes,
108
- // starting from the working directory
109
- func (w *Watcher) watchFolders() {
110
- wd, err := os.Getwd()
111
-
112
- if err != nil {
113
- log.Fatalf("Could not get root working directory: %s", err)
114
- }
115
-
116
- filepath.Walk(wd, func(path string, info os.FileInfo, err error) error {
117
- // skip files
118
- if info == nil {
119
- log.Fatalf("wrong watcher package: %s", path)
120
- }
121
-
122
- if !info.IsDir() {
123
- return nil
124
- }
125
-
126
- if !w.watchVendor {
127
- // skip vendor directory
128
- vendor := fmt.Sprintf("%s/vendor", wd)
129
- if strings.HasPrefix(path, vendor) {
130
- return filepath.SkipDir
131
- }
132
- }
133
-
134
- // skip hidden folders
135
- if len(path) > 1 && strings.HasPrefix(filepath.Base(path), ".") {
136
- return filepath.SkipDir
137
- }
138
-
139
- w.addFolder(path)
140
-
141
- return err
142
- })
143
- }
144
-
145
- // addFolder adds given folder name to the watched folders, and starts
146
- // watching it for further changes
147
- func (w *Watcher) addFolder(name string) {
148
- println("watch: " + name)
149
- if err := w.watcher.Add(name); err != nil {
150
- log.Fatalf("Could not watch folder: %s", err)
151
- }
152
- }
153
-
154
- type RouteInfo struct {
155
- Path string
156
- FuncName string
157
- RenderFunc func(*app.RenderContext) app.UI
158
- }
159
-
160
- var routesMap = map[string]RouteInfo{}
161
-
162
- func getSOPath(p string) string {
163
- return "build/" + filepath.Base(filepath.Dir(p)) + ".so"
164
- }
165
-
166
- func getWasmPath(p string) string {
167
- return "build/" + filepath.Base(filepath.Dir(p)) + ".wasm"
168
- }
169
-
170
- func buildSo(path string) (string, error) {
171
- soPath := getSOPath(path)
172
- out, err := exec.Command("go", "build", "-buildmode=plugin", "-o", soPath, path).CombinedOutput()
173
- if err != nil {
174
- println(string(out))
175
- println(err.Error())
176
- return "", err
177
- }
178
- fmt.Printf("wrote: %s\n", soPath)
179
- return soPath, nil
180
- }
181
-
182
- func buildWasm(path string) (string, error) {
183
- wasmPath := getWasmPath(path)
184
- cmd := exec.Command("go", "build", "-o", wasmPath, path)
185
- cmd.Env = os.Environ()
186
- cmd.Env = append(cmd.Env, "GOOS=js", "GOARCH=wasm")
187
- out, err := cmd.CombinedOutput()
188
- if err != nil {
189
- println(string(out))
190
- println(err.Error())
191
- return "", err
192
- }
193
- fmt.Printf("wrote: %s\n", wasmPath)
194
- return wasmPath, nil
195
- }
196
-
197
- func getRoute(basePath, p string) (string, string) {
198
- clean := strings.Replace(strings.Replace(p, basePath, "", 1), ".go", "", -1)
199
- return strings.Replace(clean, "index", "", -1), strings.Title(strings.Replace(clean, "/", "", -1))
200
- }
201
-
202
- func writeMainFile(basePath string) {
203
- tpl, err := template.New("writeMain").Parse(`// GENERATED FILE DO NOT EDIT
204
- package main
205
-
206
- import (
207
- . "github.com/pyros2097/wapp"
208
- "github.com/pyros2097/wapp/js"
209
- )
210
-
211
- func main() {
212
- {{range $key, $element := .}}
213
- if js.Window.URL().Path == "{{.Path}}" {
214
- Run({{.FuncName}})
215
- }
216
- {{end}}
217
- }
218
- `)
219
- if err != nil {
220
- panic(err)
221
- }
222
- buf := bytes.NewBuffer(nil)
223
- err = tpl.Execute(buf, routesMap)
224
- if err != nil {
225
- panic(err)
226
- }
227
- err = ioutil.WriteFile("pages/main.go", buf.Bytes(), 0644)
228
- if err != nil {
229
- panic(err)
230
- }
231
- }
232
-
233
- func loadRoutes(p *plugin.Plugin, basePath string, dry bool) {
234
- err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
235
- if err != nil {
236
- fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
237
- return err
238
- }
239
- if !info.IsDir() {
240
- routePath, routeFunc := getRoute(basePath, path)
241
- if routePath == "/main" {
242
- return nil
243
- }
244
- routesMap[routePath] = RouteInfo{
245
- Path: routePath,
246
- FuncName: routeFunc,
247
- }
248
- if dry {
249
- // println("Dry")
250
- return nil
251
- }
252
- fmt.Printf("route: %s routeFunc: %s\n", routePath, routeFunc)
253
- renderFn, err := p.Lookup(routeFunc)
254
- if err != nil {
255
- return err
256
- }
257
- routesMap[routePath] = RouteInfo{
258
- Path: routePath,
259
- FuncName: routeFunc,
260
- RenderFunc: renderFn.(func(*app.RenderContext) app.UI),
261
- }
262
- // println(createPage(routesMap[routePath](app.NewRenderContext())).String())
263
- }
264
- return nil
265
- })
266
- if err != nil {
267
- fmt.Printf("error walking the path %q: %v\n", basePath, err)
268
- panic(err)
269
- }
270
- }
271
-
272
- func createPage(ui app.UI, wasmPath string) *bytes.Buffer {
273
- page := bytes.NewBuffer(nil)
274
- page.WriteString("<!DOCTYPE html>\n")
275
- elems := app.FilterUIElems(ui)
276
- app.Html(
277
- app.Head(
278
- app.Title("Title"),
279
- app.Meta("author", "pyros2097"),
280
- app.Meta("description", "Description"),
281
- app.Meta("keywords", ""),
282
- app.Meta("theme-color", ""),
283
- app.Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
284
- app.Link("icon", "/assets/icon.png"),
285
- app.Link("apple-touch-icon", "/assets/icon.png"),
286
- app.Link("stylesheet", "/assets/styles.css"),
287
- app.Link("manifest", "manifest"),
288
- app.Script(wasmExecJs(wasmPath)),
289
- ),
290
- app.Body(elems[0]),
291
- ).Html(page)
292
- return page
293
- }
294
-
295
- func serve(wd string) {
296
-
297
- assetsFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
298
- buildFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "build")))
299
-
300
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
301
- if routeInfo, ok := routesMap[r.URL.Path]; ok {
302
- wasmPath := "/build/" + filepath.Base(wd) + ".wasm"
303
- page := createPage(routeInfo.RenderFunc(app.NewRenderContext()), wasmPath)
304
- w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
305
- w.Header().Set("Content-Type", "text/html")
306
- w.WriteHeader(http.StatusOK)
307
- w.Write(page.Bytes())
308
- } else if strings.Contains(r.URL.Path, "/build") {
309
- r.URL.Path = strings.Replace(r.URL.Path, "/build", "", 1)
310
- buildFileServer.ServeHTTP(w, r)
311
- } else {
312
- r.URL.Path = strings.Replace(r.URL.Path, "/assets", "", 1)
313
- assetsFileServer.ServeHTTP(w, r)
314
- }
315
- })
316
- log.Printf("Serving on HTTP port: 1234")
317
- log.Fatal(http.ListenAndServe(":1234", nil))
318
- }
319
-
320
- func buildAll(basePath string) {
321
- loadRoutes(nil, basePath, true)
322
- writeMainFile(basePath)
323
- buildWasm(basePath)
324
- soPath, err := buildSo(basePath)
325
- if err != nil {
326
- println("could not build")
327
- panic(err)
328
- }
329
- p, err := plugin.Open(soPath)
330
- if err != nil {
331
- println("could not load so plugin")
332
- panic(err)
333
- }
334
- // fmt.Printf("%+v\n", p)
335
- loadRoutes(p, basePath, false)
336
- }
337
-
338
- func Watch() {
339
- wd, err := os.Getwd()
340
- if err != nil {
341
- fmt.Printf("could not get wd")
342
- return
343
- }
344
- basePath := filepath.Join(wd, "pages")
345
- buildAll(basePath)
346
- watcher := MustRegisterWatcher()
347
- go watcher.Watch()
348
- go serve(wd)
349
- for file := range watcher.update {
350
- println("changed: " + file)
351
- buildAll(basePath)
352
- }
353
- }
3
+ // import (
4
+ // "bytes"
5
+ // "fmt"
6
+ // "io/ioutil"
7
+ // "log"
8
+ // "net/http"
9
+ // "os"
10
+ // "os/exec"
11
+ // "path/filepath"
12
+ // "plugin"
13
+ // "strconv"
14
+ // "strings"
15
+ // "text/template"
16
+ // "time"
17
+
18
+ // "github.com/akrylysov/algnhsa"
19
+ // "github.com/markbates/pkger"
20
+ // app "github.com/pyros2097/wapp"
21
+ // "gopkg.in/fsnotify.v1"
22
+ // )
23
+
24
+ // const wasmExecTemplate = `const enosys=()=>{const a=new Error("not implemented");return a.code="ENOSYS",a};let outputBuf="";window.fs={constants:{O_WRONLY:-1,O_RDWR:-1,O_CREAT:-1,O_TRUNC:-1,O_APPEND:-1,O_EXCL:-1},writeSync(a,b){outputBuf+=decoder.decode(b);const c=outputBuf.lastIndexOf("\n");return-1!=c&&(console.log(outputBuf.substr(0,c)),outputBuf=outputBuf.substr(c+1)),b.length},write(a,b,c,d,e,f){if(0!==c||d!==b.length||null!==e)return void f(enosys());const g=this.writeSync(a,b);f(null,g)}};const encoder=new TextEncoder("utf-8"),decoder=new TextDecoder("utf-8");class Go{constructor(){this.argv=["js"],this.env={},this.exit=a=>{0!==a&&console.warn("exit code:",a)},this._exitPromise=new Promise(a=>{this._resolveExitPromise=a}),this._pendingEvent=null,this._scheduledTimeouts=new Map,this._nextCallbackTimeoutID=1;const a=(a,b)=>{this.mem.setUint32(a+0,b,!0),this.mem.setUint32(a+4,Math.floor(b/4294967296),!0)},b=a=>{const b=this.mem.getUint32(a+0,!0),c=this.mem.getInt32(a+4,!0);return b+4294967296*c},c=a=>{const b=this.mem.getFloat64(a,!0);if(0!==b){if(!isNaN(b))return b;const c=this.mem.getUint32(a,!0);return this._values[c]}},d=(a,b)=>{const c=2146959360;if("number"==typeof b)return isNaN(b)?(this.mem.setUint32(a+4,2146959360,!0),void this.mem.setUint32(a,0,!0)):0===b?(this.mem.setUint32(a+4,2146959360,!0),void this.mem.setUint32(a,1,!0)):void this.mem.setFloat64(a,b,!0);switch(b){case void 0:return void this.mem.setFloat64(a,0,!0);case null:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,2,!0);case!0:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,3,!0);case!1:return this.mem.setUint32(a+4,c,!0),void this.mem.setUint32(a,4,!0);}let d=this._ids.get(b);d===void 0&&(d=this._idPool.pop(),d===void 0&&(d=this._values.length),this._values[d]=b,this._goRefCounts[d]=0,this._ids.set(b,d)),this._goRefCounts[d]++;let e=1;switch(typeof b){case"string":e=2;break;case"symbol":e=3;break;case"function":e=4;}this.mem.setUint32(a+4,2146959360|e,!0),this.mem.setUint32(a,d,!0)},e=a=>{const c=b(a+0),d=b(a+8);return new Uint8Array(this._inst.exports.mem.buffer,c,d)},f=d=>{const e=b(d+0),f=b(d+8),g=Array(f);for(let a=0;a<f;a++)g[a]=c(e+8*a);return g},g=a=>{const c=b(a+0),d=b(a+8);return decoder.decode(new DataView(this._inst.exports.mem.buffer,c,d))},h=Date.now()-performance.now();this.importObject={go:{"runtime.wasmExit":a=>{const b=this.mem.getInt32(a+8,!0);this.exited=!0,delete this._inst,delete this._values,delete this._goRefCounts,delete this._ids,delete this._idPool,this.exit(b)},"runtime.wasmWrite":a=>{const c=b(a+8),d=b(a+16),e=this.mem.getInt32(a+24,!0);fs.writeSync(c,new Uint8Array(this._inst.exports.mem.buffer,d,e))},"runtime.resetMemoryDataView":()=>{this.mem=new DataView(this._inst.exports.mem.buffer)},"runtime.nanotime1":b=>{a(b+8,1e6*(h+performance.now()))},"runtime.walltime1":b=>{const c=new Date().getTime();a(b+8,c/1e3),this.mem.setInt32(b+16,1e6*(c%1e3),!0)},"runtime.scheduleTimeoutEvent":a=>{const c=this._nextCallbackTimeoutID;this._nextCallbackTimeoutID++,this._scheduledTimeouts.set(c,setTimeout(()=>{for(this._resume();this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"),this._resume()},b(a+8)+1)),this.mem.setInt32(a+16,c,!0)},"runtime.clearTimeoutEvent":a=>{const b=this.mem.getInt32(a+8,!0);clearTimeout(this._scheduledTimeouts.get(b)),this._scheduledTimeouts.delete(b)},"runtime.getRandomData":a=>{crypto.getRandomValues(e(a+8))},"syscall/js.finalizeRef":a=>{const b=this.mem.getUint32(a+8,!0);if(this._goRefCounts[b]--,0===this._goRefCounts[b]){const a=this._values[b];this._values[b]=null,this._ids.delete(a),this._idPool.push(b)}},"syscall/js.stringVal":a=>{d(a+24,g(a+8))},"syscall/js.valueGet":a=>{const b=Reflect.get(c(a+8),g(a+16));a=this._inst.exports.getsp(),d(a+32,b)},"syscall/js.valueSet":a=>{Reflect.set(c(a+8),g(a+16),c(a+32))},"syscall/js.valueDelete":a=>{Reflect.deleteProperty(c(a+8),g(a+16))},"syscall/js.valueIndex":a=>{d(a+24,Reflect.get(c(a+8),b(a+16)))},"syscall/js.valueSetIndex":a=>{Reflect.set(c(a+8),b(a+16),c(a+24))},"syscall/js.valueCall":a=>{try{const b=c(a+8),e=Reflect.get(b,g(a+16)),h=f(a+32),i=Reflect.apply(e,b,h);a=this._inst.exports.getsp(),d(a+56,i),this.mem.setUint8(a+64,1)}catch(b){d(a+56,b),this.mem.setUint8(a+64,0)}},"syscall/js.valueInvoke":a=>{try{const b=c(a+8),e=f(a+16),g=Reflect.apply(b,void 0,e);a=this._inst.exports.getsp(),d(a+40,g),this.mem.setUint8(a+48,1)}catch(b){d(a+40,b),this.mem.setUint8(a+48,0)}},"syscall/js.valueNew":a=>{try{const b=c(a+8),e=f(a+16),g=Reflect.construct(b,e);a=this._inst.exports.getsp(),d(a+40,g),this.mem.setUint8(a+48,1)}catch(b){d(a+40,b),this.mem.setUint8(a+48,0)}},"syscall/js.valueLength":b=>{a(b+16,parseInt(c(b+8).length))},"syscall/js.valuePrepareString":b=>{const e=encoder.encode(c(b+8)+"");d(b+16,e),a(b+24,e.length)},"syscall/js.valueLoadString":a=>{const b=c(a+8);e(a+16).set(b)},"syscall/js.valueInstanceOf":a=>{this.mem.setUint8(a+24,c(a+8)instanceof c(a+16))},"syscall/js.copyBytesToGo":b=>{const d=e(b+8),f=c(b+32);if(!(f instanceof Uint8Array))return void this.mem.setUint8(b+48,0);const g=f.subarray(0,d.length);d.set(g),a(b+40,g.length),this.mem.setUint8(b+48,1)},"syscall/js.copyBytesToJS":b=>{const d=c(b+8),f=e(b+16);if(!(d instanceof Uint8Array))return void this.mem.setUint8(b+48,0);const g=f.subarray(0,d.length);d.set(g),a(b+40,g.length),this.mem.setUint8(b+48,1)},debug:a=>{console.log(a)}}}}async run(a){this._inst=a,this.mem=new DataView(this._inst.exports.mem.buffer),this._values=[NaN,0,null,!0,!1,window,this],this._goRefCounts=[],this._ids=new Map,this._idPool=[],this.exited=!1;let b=4096;const c=a=>{const c=b,d=encoder.encode(a+"\0");return new Uint8Array(this.mem.buffer,b,d.length).set(d),b+=d.length,0!=b%8&&(b+=8-b%8),c},d=this.argv.length,e=[];this.argv.forEach(a=>{e.push(c(a))}),e.push(0);const f=Object.keys(this.env).sort();f.forEach(a=>{e.push(c(a+"="+this.env[a]))}),e.push(0);const g=b;e.forEach(a=>{this.mem.setUint32(b,a,!0),this.mem.setUint32(b+4,0,!0),b+=8}),this._inst.exports.run(d,g),this.exited&&this._resolveExitPromise(),await this._exitPromise}_resume(){if(this.exited)throw new Error("Go program has already exited");this._inst.exports.resume(),this.exited&&this._resolveExitPromise()}_makeFuncWrapper(a){const b=this;return function(){const c={id:a,this:this,args:arguments};return b._pendingEvent=c,b._resume(),c.result}}}const go=new Go;WebAssembly.instantiateStreaming(fetch("__path__"),go.importObject).then(a=>go.run(a.instance)).catch(a=>console.error("could not load wasm",a));`
25
+
26
+ // func wasmExecJs(path string) string {
27
+ // return strings.Replace(wasmExecTemplate, "__path__", path, 1)
28
+ // }
29
+
30
+ // var watchDelta = 1000 * time.Millisecond
31
+
32
+ // type Watcher struct {
33
+ // rootdir string
34
+ // watcher *fsnotify.Watcher
35
+ // watchVendor bool
36
+ // update chan string
37
+ // }
38
+
39
+ // // MustRegisterWatcher creates a new Watcher and starts listening to
40
+ // // given folders
41
+ // func MustRegisterWatcher() *Watcher {
42
+ // w := &Watcher{
43
+ // update: make(chan string),
44
+ // watchVendor: false,
45
+ // }
46
+ // var err error
47
+ // w.watcher, err = fsnotify.NewWatcher()
48
+ // if err != nil {
49
+ // log.Fatalf("Could not register watcher: %s", err)
50
+ // }
51
+ // w.watchFolders()
52
+ // return w
53
+ // }
54
+
55
+ // // Watch listens file updates, and sends signal to
56
+ // // update channel when .go and .tmpl files are updated
57
+ // func (w *Watcher) Watch() {
58
+ // eventSent := false
59
+ // for {
60
+ // select {
61
+ // case event := <-w.watcher.Events:
62
+ // // discard chmod events
63
+ // if event.Op&fsnotify.Chmod != fsnotify.Chmod {
64
+ // // test files do not need a rebuild
65
+ // if isTestFile(event.Name) {
66
+ // continue
67
+ // }
68
+ // if !isWatchedFileType(event.Name) {
69
+ // continue
70
+ // }
71
+ // if eventSent {
72
+ // continue
73
+ // }
74
+ // eventSent = true
75
+ // // prevent consequent builds
76
+ // go func() {
77
+ // w.update <- event.Name
78
+ // time.Sleep(watchDelta)
79
+ // eventSent = false
80
+ // }()
81
+
82
+ // }
83
+ // case err := <-w.watcher.Errors:
84
+ // if err != nil {
85
+ // log.Fatalf("Watcher error: %s", err)
86
+ // }
87
+ // return
88
+ // }
89
+ // }
90
+ // }
91
+
92
+ // func isTestFile(fileName string) bool {
93
+ // return strings.HasSuffix(filepath.Base(fileName), "_test.go")
94
+ // }
95
+
96
+ // func isWatchedFileType(fileName string) bool {
97
+ // ext := filepath.Ext(fileName)
98
+
99
+ // return ext == ".go"
100
+ // }
101
+
102
+ // // Close closes the fsnotify watcher channel
103
+ // func (w *Watcher) Close() {
104
+ // w.watcher.Close()
105
+ // close(w.update)
106
+ // }
107
+
108
+ // // watchFolders recursively adds folders that will be watched against the changes,
109
+ // // starting from the working directory
110
+ // func (w *Watcher) watchFolders() {
111
+ // wd, err := os.Getwd()
112
+
113
+ // if err != nil {
114
+ // log.Fatalf("Could not get root working directory: %s", err)
115
+ // }
116
+
117
+ // filepath.Walk(wd, func(path string, info os.FileInfo, err error) error {
118
+ // // skip files
119
+ // if info == nil {
120
+ // log.Fatalf("wrong watcher package: %s", path)
121
+ // }
122
+
123
+ // if !info.IsDir() {
124
+ // return nil
125
+ // }
126
+
127
+ // if !w.watchVendor {
128
+ // // skip vendor directory
129
+ // vendor := fmt.Sprintf("%s/vendor", wd)
130
+ // if strings.HasPrefix(path, vendor) {
131
+ // return filepath.SkipDir
132
+ // }
133
+ // }
134
+
135
+ // // skip hidden folders
136
+ // if len(path) > 1 && strings.HasPrefix(filepath.Base(path), ".") {
137
+ // return filepath.SkipDir
138
+ // }
139
+
140
+ // w.addFolder(path)
141
+
142
+ // return err
143
+ // })
144
+ // }
145
+
146
+ // // addFolder adds given folder name to the watched folders, and starts
147
+ // // watching it for further changes
148
+ // func (w *Watcher) addFolder(name string) {
149
+ // println("watch: " + name)
150
+ // if err := w.watcher.Add(name); err != nil {
151
+ // log.Fatalf("Could not watch folder: %s", err)
152
+ // }
153
+ // }
154
+
155
+ // type RouteInfo struct {
156
+ // Path string
157
+ // FuncName string
158
+ // RenderFunc func(*app.RenderContext) app.UI
159
+ // }
160
+
161
+ // var routesMap = map[string]RouteInfo{}
162
+
163
+ // func getSOPath(p string) string {
164
+ // return "build/" + filepath.Base(filepath.Dir(p)) + ".so"
165
+ // }
166
+
167
+ // func getWasmPath(p string) string {
168
+ // return "build/" + filepath.Base(filepath.Dir(p)) + ".wasm"
169
+ // }
170
+
171
+ // func buildSo(path string) (string, error) {
172
+ // soPath := getSOPath(path)
173
+ // out, err := exec.Command("go", "build", "-buildmode=plugin", "-o", soPath, path).CombinedOutput()
174
+ // if err != nil {
175
+ // println(string(out))
176
+ // println(err.Error())
177
+ // return "", err
178
+ // }
179
+ // fmt.Printf("wrote: %s\n", soPath)
180
+ // return soPath, nil
181
+ // }
182
+
183
+ // func buildWasm(path string) (string, error) {
184
+ // wasmPath := getWasmPath(path)
185
+ // cmd := exec.Command("go", "build", "-o", wasmPath, path)
186
+ // cmd.Env = os.Environ()
187
+ // cmd.Env = append(cmd.Env, "GOOS=js", "GOARCH=wasm")
188
+ // out, err := cmd.CombinedOutput()
189
+ // if err != nil {
190
+ // println(string(out))
191
+ // println(err.Error())
192
+ // return "", err
193
+ // }
194
+ // fmt.Printf("wrote: %s\n", wasmPath)
195
+ // return wasmPath, nil
196
+ // }
197
+
198
+ // func getRoute(basePath, p string) (string, string) {
199
+ // clean := strings.Replace(strings.Replace(p, basePath, "", 1), ".go", "", -1)
200
+ // return strings.Replace(clean, "index", "", -1), strings.Title(strings.Replace(clean, "/", "", -1))
201
+ // }
202
+
203
+ // func writeMainFile(basePath string) {
204
+ // tpl, err := template.New("writeMain").Parse(`// GENERATED FILE DO NOT EDIT
205
+ // package main
206
+
207
+ // import (
208
+ // . "github.com/pyros2097/wapp"
209
+ // "github.com/pyros2097/wapp/js"
210
+ // )
211
+
212
+ // func main() {
213
+ // {{range $key, $element := .}}
214
+ // if js.Window.URL().Path == "{{.Path}}" {
215
+ // Run({{.FuncName}})
216
+ // }
217
+ // {{end}}
218
+ // }
219
+ // `)
220
+ // if err != nil {
221
+ // panic(err)
222
+ // }
223
+ // buf := bytes.NewBuffer(nil)
224
+ // err = tpl.Execute(buf, routesMap)
225
+ // if err != nil {
226
+ // panic(err)
227
+ // }
228
+ // err = ioutil.WriteFile("pages/main.go", buf.Bytes(), 0644)
229
+ // if err != nil {
230
+ // panic(err)
231
+ // }
232
+ // }
233
+
234
+ // func loadRoutes(p *plugin.Plugin, basePath string, dry bool) {
235
+ // err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
236
+ // if err != nil {
237
+ // fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
238
+ // return err
239
+ // }
240
+ // if !info.IsDir() {
241
+ // routePath, routeFunc := getRoute(basePath, path)
242
+ // if routePath == "/main" {
243
+ // return nil
244
+ // }
245
+ // routesMap[routePath] = RouteInfo{
246
+ // Path: routePath,
247
+ // FuncName: routeFunc,
248
+ // }
249
+ // if dry {
250
+ // // println("Dry")
251
+ // return nil
252
+ // }
253
+ // fmt.Printf("route: %s routeFunc: %s\n", routePath, routeFunc)
254
+ // renderFn, err := p.Lookup(routeFunc)
255
+ // if err != nil {
256
+ // return err
257
+ // }
258
+ // routesMap[routePath] = RouteInfo{
259
+ // Path: routePath,
260
+ // FuncName: routeFunc,
261
+ // RenderFunc: renderFn.(func(*app.RenderContext) app.UI),
262
+ // }
263
+ // // println(createPage(routesMap[routePath](app.NewRenderContext())).String())
264
+ // }
265
+ // return nil
266
+ // })
267
+ // if err != nil {
268
+ // fmt.Printf("error walking the path %q: %v\n", basePath, err)
269
+ // panic(err)
270
+ // }
271
+ // }
272
+
273
+ // func CreatePage(title string, ui app.UI, wasmPath string) *bytes.Buffer {
274
+ // page := bytes.NewBuffer(nil)
275
+ // page.WriteString("<!DOCTYPE html>\n")
276
+ // elems := app.FilterUIElems(ui)
277
+ // app.Html(
278
+ // app.Head(
279
+ // app.Title(title),
280
+ // app.Meta("author", "pyros2097"),
281
+ // app.Meta("description", "Description"),
282
+ // app.Meta("keywords", ""),
283
+ // app.Meta("theme-color", ""),
284
+ // app.Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
285
+ // app.Link("icon", "/assets/icon.png"),
286
+ // app.Link("apple-touch-icon", "/assets/icon.png"),
287
+ // app.Link("stylesheet", "/assets/styles.css"),
288
+ // app.Link("manifest", "manifest"),
289
+ // app.Script(`const enosys = () => { const a = new Error("not implemented"); return a.code = "ENOSYS", a }; let outputBuf = ""; window.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, writeSync(a, b) { outputBuf += decoder.decode(b); const c = outputBuf.lastIndexOf("\n"); return -1 != c && (console.log(outputBuf.substr(0, c)), outputBuf = outputBuf.substr(c + 1)), b.length }, write(a, b, c, d, e, f) { if (0 !== c || d !== b.length || null !== e) return void f(enosys()); const g = this.writeSync(a, b); f(null, g) } }; const encoder = new TextEncoder("utf-8"), decoder = new TextDecoder("utf-8"); class Go { constructor() { this.argv = ["js"], this.env = {}, this.exit = a => { 0 !== a && console.warn("exit code:", a) }, this._exitPromise = new Promise(a => { this._resolveExitPromise = a }), this._pendingEvent = null, this._scheduledTimeouts = new Map, this._nextCallbackTimeoutID = 1; const a = (a, b) => { this.mem.setUint32(a + 0, b, !0), this.mem.setUint32(a + 4, Math.floor(b / 4294967296), !0) }, b = a => { const b = this.mem.getUint32(a + 0, !0), c = this.mem.getInt32(a + 4, !0); return b + 4294967296 * c }, c = a => { const b = this.mem.getFloat64(a, !0); if (0 !== b) { if (!isNaN(b)) return b; const c = this.mem.getUint32(a, !0); return this._values[c] } }, d = (a, b) => { const c = 2146959360; if ("number" == typeof b) return isNaN(b) ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 0, !0)) : 0 === b ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 1, !0)) : void this.mem.setFloat64(a, b, !0); switch (b) { case void 0: return void this.mem.setFloat64(a, 0, !0); case null: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 2, !0); case !0: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 3, !0); case !1: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 4, !0); }let d = this._ids.get(b); d === void 0 && (d = this._idPool.pop(), d === void 0 && (d = this._values.length), this._values[d] = b, this._goRefCounts[d] = 0, this._ids.set(b, d)), this._goRefCounts[d]++; let e = 1; switch (typeof b) { case "string": e = 2; break; case "symbol": e = 3; break; case "function": e = 4; }this.mem.setUint32(a + 4, 2146959360 | e, !0), this.mem.setUint32(a, d, !0) }, e = a => { const c = b(a + 0), d = b(a + 8); return new Uint8Array(this._inst.exports.mem.buffer, c, d) }, f = d => { const e = b(d + 0), f = b(d + 8), g = Array(f); for (let a = 0; a < f; a++)g[a] = c(e + 8 * a); return g }, g = a => { const c = b(a + 0), d = b(a + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, c, d)) }, h = Date.now() - performance.now(); this.importObject = { go: { "runtime.wasmExit": a => { const b = this.mem.getInt32(a + 8, !0); this.exited = !0, delete this._inst, delete this._values, delete this._goRefCounts, delete this._ids, delete this._idPool, this.exit(b) }, "runtime.wasmWrite": a => { const c = b(a + 8), d = b(a + 16), e = this.mem.getInt32(a + 24, !0); fs.writeSync(c, new Uint8Array(this._inst.exports.mem.buffer, d, e)) }, "runtime.resetMemoryDataView": () => { this.mem = new DataView(this._inst.exports.mem.buffer) }, "runtime.nanotime1": b => { a(b + 8, 1e6 * (h + performance.now())) }, "runtime.walltime1": b => { const c = new Date().getTime(); a(b + 8, c / 1e3), this.mem.setInt32(b + 16, 1e6 * (c % 1e3), !0) }, "runtime.scheduleTimeoutEvent": a => { const c = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++, this._scheduledTimeouts.set(c, setTimeout(() => { for (this._resume(); this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"), this._resume() }, b(a + 8) + 1)), this.mem.setInt32(a + 16, c, !0) }, "runtime.clearTimeoutEvent": a => { const b = this.mem.getInt32(a + 8, !0); clearTimeout(this._scheduledTimeouts.get(b)), this._scheduledTimeouts.delete(b) }, "runtime.getRandomData": a => { crypto.getRandomValues(e(a + 8)) }, "syscall/js.finalizeRef": a => { const b = this.mem.getUint32(a + 8, !0); if (this._goRefCounts[b]--, 0 === this._goRefCounts[b]) { const a = this._values[b]; this._values[b] = null, this._ids.delete(a), this._idPool.push(b) } }, "syscall/js.stringVal": a => { d(a + 24, g(a + 8)) }, "syscall/js.valueGet": a => { const b = Reflect.get(c(a + 8), g(a + 16)); a = this._inst.exports.getsp(), d(a + 32, b) }, "syscall/js.valueSet": a => { Reflect.set(c(a + 8), g(a + 16), c(a + 32)) }, "syscall/js.valueDelete": a => { Reflect.deleteProperty(c(a + 8), g(a + 16)) }, "syscall/js.valueIndex": a => { d(a + 24, Reflect.get(c(a + 8), b(a + 16))) }, "syscall/js.valueSetIndex": a => { Reflect.set(c(a + 8), b(a + 16), c(a + 24)) }, "syscall/js.valueCall": a => { try { const b = c(a + 8), e = Reflect.get(b, g(a + 16)), h = f(a + 32), i = Reflect.apply(e, b, h); a = this._inst.exports.getsp(), d(a + 56, i), this.mem.setUint8(a + 64, 1) } catch (b) { d(a + 56, b), this.mem.setUint8(a + 64, 0) } }, "syscall/js.valueInvoke": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.apply(b, void 0, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueNew": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.construct(b, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueLength": b => { a(b + 16, parseInt(c(b + 8).length)) }, "syscall/js.valuePrepareString": b => { const e = encoder.encode(c(b + 8) + ""); d(b + 16, e), a(b + 24, e.length) }, "syscall/js.valueLoadString": a => { const b = c(a + 8); e(a + 16).set(b) }, "syscall/js.valueInstanceOf": a => { this.mem.setUint8(a + 24, c(a + 8) instanceof c(a + 16)) }, "syscall/js.copyBytesToGo": b => { const d = e(b + 8), f = c(b + 32); if (!(f instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, "syscall/js.copyBytesToJS": b => { const d = c(b + 8), f = e(b + 16); if (!(d instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, debug: a => { console.log(a) } } } } async run(a) { this._inst = a, this.mem = new DataView(this._inst.exports.mem.buffer), this._values = [NaN, 0, null, !0, !1, window, this], this._goRefCounts = [], this._ids = new Map, this._idPool = [], this.exited = !1; let b = 4096; const c = a => { const c = b, d = encoder.encode(a + "\0"); return new Uint8Array(this.mem.buffer, b, d.length).set(d), b += d.length, 0 != b % 8 && (b += 8 - b % 8), c }, d = this.argv.length, e = []; this.argv.forEach(a => { e.push(c(a)) }), e.push(0); const f = Object.keys(this.env).sort(); f.forEach(a => { e.push(c(a + "=" + this.env[a])) }), e.push(0); const g = b; e.forEach(a => { this.mem.setUint32(b, a, !0), this.mem.setUint32(b + 4, 0, !0), b += 8 }), this._inst.exports.run(d, g), this.exited && this._resolveExitPromise(), await this._exitPromise } _resume() { if (this.exited) throw new Error("Go program has already exited"); this._inst.exports.resume(), this.exited && this._resolveExitPromise() } _makeFuncWrapper(a) { const b = this; return function () { const c = { id: a, this: this, args: arguments }; return b._pendingEvent = c, b._resume(), c.result } } } const go = new Go; WebAssembly.instantiateStreaming(fetch("/assets/main.wasm"), go.importObject).then(a => go.run(a.instance)).catch(a => console.error("could not load wasm", a));`),
290
+ // ),
291
+ // app.Body(elems[0]),
292
+ // ).Html(page)
293
+ // return page
294
+ // }
295
+
296
+ // func serve(wd string, isDev bool) {
297
+ // assetsFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
298
+ // buildFileServer := http.FileServer(pkger.Dir(filepath.Join(wd, "build")))
299
+ // http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
300
+ // println("path: " + r.URL.Path)
301
+ // if routeInfo, ok := routesMap[r.URL.Path]; ok {
302
+ // wasmPath := "/build/" + filepath.Base(wd) + ".wasm"
303
+ // page := CreatePage(routeInfo.RenderFunc(app.NewRenderContext()), wasmPath)
304
+ // w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
305
+ // w.Header().Set("Content-Type", "text/html")
306
+ // w.WriteHeader(http.StatusOK)
307
+ // w.Write(page.Bytes())
308
+ // } else if strings.Contains(r.URL.Path, "/build") {
309
+ // r.URL.Path = strings.Replace(r.URL.Path, "/build", "", 1)
310
+ // buildFileServer.ServeHTTP(w, r)
311
+ // } else {
312
+ // r.URL.Path = strings.Replace(r.URL.Path, "/assets", "", 1)
313
+ // assetsFileServer.ServeHTTP(w, r)
314
+ // }
315
+ // })
316
+ // if isDev {
317
+ // println("Serving on HTTP port: 1234")
318
+ // http.ListenAndServe(":1234", nil)
319
+ // } else {
320
+ // println("algnhsa serving default mux")
321
+ // algnhsa.ListenAndServe(http.DefaultServeMux, nil)
322
+ // }
323
+ // }
324
+
325
+ // func buildAll(basePath string) {
326
+ // loadRoutes(nil, basePath, true)
327
+ // writeMainFile(basePath)
328
+ // buildWasm(basePath)
329
+ // soPath, err := buildSo(basePath)
330
+ // if err != nil {
331
+ // println("could not build")
332
+ // panic(err)
333
+ // }
334
+ // p, err := plugin.Open(soPath)
335
+ // if err != nil {
336
+ // println("could not load so plugin")
337
+ // panic(err)
338
+ // }
339
+ // // fmt.Printf("%+v\n", p)
340
+ // loadRoutes(p, basePath, false)
341
+ // }
342
+
343
+ // func Watch(isDev bool) {
344
+ // wd, err := os.Getwd()
345
+ // if err != nil {
346
+ // fmt.Printf("could not get wd")
347
+ // return
348
+ // }
349
+ // if isDev {
350
+ // basePath := filepath.Join(wd, "pages")
351
+ // buildAll(basePath)
352
+ // watcher := MustRegisterWatcher()
353
+ // go watcher.Watch()
354
+ // go serve(wd, isDev)
355
+ // for file := range watcher.update {
356
+ // println("changed: " + file)
357
+ // buildAll(basePath)
358
+ // }
359
+ // } else {
360
+ // serve(wd, isDev)
361
+ // }
362
+ // }
example/{pages/about.go → about.go} RENAMED
File without changes
example/{config.css → assets/config.css} RENAMED
File without changes
example/assets/manifest.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "short_name": "{{.ShortName}}",
3
- "name": "{{.Name}}",
4
- "icons": [
5
- {
6
- "src": "{{.DefaultIcon}}",
7
- "type": "image/png",
8
- "sizes": "192x192"
9
- },
10
- {
11
- "src": "{{.LargeIcon}}",
12
- "type": "image/png",
13
- "sizes": "512x512"
14
- }
15
- ],
16
- "scope": "{{.Scope}}",
17
- "start_url": "{{.StartURL}}",
18
- "background_color": "{{.BackgroundColor}}",
19
- "display": "standalone",
20
- "theme_color": "{{.ThemeColor}}"
21
- }
example/{pages/clock.go → clock.go} RENAMED
File without changes
example/{pages/container.go → container.go} RENAMED
File without changes
example/{pages/index.go → index.go} RENAMED
File without changes
example/main.go CHANGED
@@ -1,9 +1,16 @@
1
1
  package main
2
2
 
3
3
  import (
4
- "github.com/pyros2097/wapp/cli"
4
+ app "github.com/pyros2097/wapp"
5
5
  )
6
6
 
7
+ var routes = map[string]app.RenderFunc{
8
+ "/about": About,
9
+ "/clock": Clock,
10
+ "/container": Container,
11
+ "/": Index,
12
+ }
13
+
7
14
  func main() {
8
- cli.Watch()
15
+ app.Run(false, routes)
9
16
  }
example/makefile CHANGED
@@ -1,2 +1,11 @@
1
+ run:
2
+ go run *.go
3
+
4
+ build:
5
+ go build
6
+
7
+ build-wasm:
8
+ GOOS=js GOARCH=wasm go build -o assets/main.wasm
9
+
1
10
  build-css:
2
- npx tailwindcss-cli@latest build config.css -o assets/styles.css
11
+ npx tailwindcss-cli@latest build assets/config.css -o assets/styles.css
example/pages/main.go DELETED
@@ -1,27 +0,0 @@
1
- // GENERATED FILE DO NOT EDIT
2
- package main
3
-
4
- import (
5
- . "github.com/pyros2097/wapp"
6
- "github.com/pyros2097/wapp/js"
7
- )
8
-
9
- func main() {
10
-
11
- if js.Window.URL().Path == "/" {
12
- Run(Index)
13
- }
14
-
15
- if js.Window.URL().Path == "/about" {
16
- Run(About)
17
- }
18
-
19
- if js.Window.URL().Path == "/clock" {
20
- Run(Clock)
21
- }
22
-
23
- if js.Window.URL().Path == "/container" {
24
- Run(Container)
25
- }
26
-
27
- }
example/readme.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  An example to demostrate the wapp framework
4
4
 
5
- Just run,
5
+ 1. Compile wasm frontend,
6
+ `GOOS=js GOARCH=wasm go build -o assets/main.wasm`
6
7
 
8
+ 2. Run the server,
7
- `go run main.go`
9
+ `go run *.go`
10
+
11
+ These commands are also available in the makefile for convenience
example/samconfig.toml ADDED
@@ -0,0 +1,9 @@
1
+ version = 0.1
2
+ [default]
3
+ [default.deploy]
4
+ [default.deploy.parameters]
5
+ stack_name = "go-app"
6
+ s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-grb3hle00qk9"
7
+ s3_prefix = "go-app"
8
+ region = "us-east-1"
9
+ capabilities = "CAPABILITY_IAM"
example/template.yml ADDED
@@ -0,0 +1,43 @@
1
+ AWSTemplateFormatVersion: 2010-09-09
2
+ Outputs:
3
+ ApiUrl:
4
+ Export:
5
+ Name: ApiUrl
6
+ Value:
7
+ Fn::Sub: https://${HttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/
8
+ Resources:
9
+ HttpApi:
10
+ Properties:
11
+ AccessLogSettings:
12
+ DestinationArn:
13
+ Fn::GetAtt:
14
+ - HttpApiAccessLogs
15
+ - Arn
16
+ Format:
17
+ '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp",
18
+ "caller":"$context.identity.caller", "user":"$context.identity.user", "requestTime":"$context.requestTime","routeKey":"$context.routeKey",
19
+ "status":"$context.status", "error": "$context.integrationErrorMessage"}'
20
+ StageName: Prod
21
+ Type: AWS::Serverless::HttpApi
22
+ HttpApiAccessLogs:
23
+ Properties:
24
+ LogGroupName: /aws/http/py-app
25
+ RetentionInDays: 365
26
+ Type: AWS::Logs::LogGroup
27
+ RouteIndex:
28
+ Properties:
29
+ Handler: ./example
30
+ Events:
31
+ ApiEvent:
32
+ Properties:
33
+ ApiId:
34
+ Ref: HttpApi
35
+ Method: GET
36
+ Path: $default
37
+ PayloadFormatVersion: '1.0'
38
+ Type: HttpApi
39
+ MemorySize: 128
40
+ Runtime: go1.x
41
+ Timeout: 30
42
+ Type: AWS::Serverless::Function
43
+ Transform: AWS::Serverless-2016-10-31
go.mod CHANGED
@@ -3,7 +3,9 @@ module github.com/pyros2097/wapp
3
3
  go 1.15
4
4
 
5
5
  require (
6
+ github.com/akrylysov/algnhsa v0.12.1
7
+ github.com/aws/aws-lambda-go v1.20.0
6
- github.com/fsnotify/fsnotify v1.4.7 // indirect
8
+ github.com/awslabs/goformation/v4 v4.15.6
7
9
  github.com/markbates/pkger v0.17.1
8
10
  github.com/stretchr/testify v1.6.1
9
11
  golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect
go.sum CHANGED
@@ -1,10 +1,35 @@
1
+ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2
+ github.com/akrylysov/algnhsa v0.12.1 h1:A9Ojt4hZrL77mhBc3qGO3Sn9reyf+tvM3DmR0SfXguc=
3
+ github.com/akrylysov/algnhsa v0.12.1/go.mod h1:xAcJ/X8DV+81e+dUjIoB/r5CbISrSXV9//leoMDHcdk=
4
+ github.com/aws/aws-lambda-go v1.9.0/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A=
5
+ github.com/aws/aws-lambda-go v1.20.0 h1:ZSweJx/Hy9BoIDXKBEh16vbHH0t0dehnF8MKpMiOWc0=
6
+ github.com/aws/aws-lambda-go v1.20.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU=
7
+ github.com/awslabs/goformation v1.4.1 h1:jws9kTrcI53Hq2COJAy50uAhgxB5N/Enb9Gmclr/MP4=
8
+ github.com/awslabs/goformation/v4 v4.15.6 h1:9F0MbtJVSMkuI19G6Fm+qHc1nqScHcOIf+3YRRv+Ohc=
9
+ github.com/awslabs/goformation/v4 v4.15.6/go.mod h1:wB5lKZf1J0MYH1Lt4B9w3opqz0uIjP7MMCAcib3QkwA=
10
+ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
11
+ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
1
12
  github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2
13
  github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3
14
  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4
15
  github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
5
16
  github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
17
+ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
6
18
  github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
7
19
  github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
20
+ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
21
+ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
22
+ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
23
+ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
24
+ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
25
+ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
26
+ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
27
+ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
28
+ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
29
+ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
30
+ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
31
+ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
32
+ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
8
33
  github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
9
34
  github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
10
35
  github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -12,20 +37,70 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
12
37
  github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
13
38
  github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno=
14
39
  github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
40
+ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
41
+ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
42
+ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
43
+ github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
44
+ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
45
+ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
46
+ github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
15
47
  github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
16
48
  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
49
+ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
50
+ github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
51
+ github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
52
+ github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 h1:fOCp11H0yuyAt2wqlbJtbyPzSgaxHTv8uN1pMpkG1t8=
53
+ github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522/go.mod h1:tQTYKOQgxoH3v6dEmdHiz4JG+nbxWwM5fgPQUpSZqVQ=
54
+ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
17
55
  github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
56
+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
18
57
  github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
19
58
  github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
20
59
  github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
60
+ github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
61
+ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
62
+ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
63
+ github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
64
+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
65
+ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
66
+ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
67
+ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
68
+ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
69
+ golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
70
+ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
71
+ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
72
+ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
73
+ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
74
+ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
75
+ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
76
+ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
77
+ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
78
+ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
79
+ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
21
80
  golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
22
81
  golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
82
+ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
83
+ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
84
+ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
85
+ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
86
+ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
87
+ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
88
+ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
89
+ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
90
+ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
91
+ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
92
+ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
23
93
  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
24
94
  gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
25
95
  gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
26
96
  gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
27
97
  gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
98
+ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
28
99
  gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
100
+ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
29
101
  gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
102
+ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
30
103
  gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
31
104
  gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
105
+ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
106
+ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
utils.go CHANGED
@@ -2,6 +2,7 @@ package app
2
2
 
3
3
  import (
4
4
  "io"
5
+ "regexp"
5
6
  "unsafe"
6
7
 
7
8
  "github.com/pyros2097/wapp/js"
@@ -48,3 +49,33 @@ func btos(b []byte) string {
48
49
  func stob(s string) []byte {
49
50
  return *(*[]byte)(unsafe.Pointer(&s))
50
51
  }
52
+
53
+ func matchPath(k, p string) bool {
54
+ validRoute := regexp.MustCompile(k)
55
+ if validRoute.MatchString(p) {
56
+ return true
57
+ }
58
+ return false
59
+ }
60
+
61
+ func MatchRoute(routes map[string]RenderFunc, path string) RenderFunc {
62
+ for key, renderFn := range routes {
63
+ if matchPath(key, path) {
64
+ return renderFn
65
+ }
66
+ }
67
+ notFound, ok := routes["/notfound"]
68
+ if ok {
69
+ return notFound
70
+ }
71
+ return func(c *RenderContext) UI {
72
+ return Col(
73
+ Row(
74
+ "This is the default 404 - Not Found Route handler",
75
+ ),
76
+ Row(
77
+ "Create a notfound.go file and add a func NotFound(c *RenderContext) UI {} to override it",
78
+ ),
79
+ )
80
+ }
81
+ }
utils_test.go ADDED
@@ -0,0 +1,22 @@
1
+ package app
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/stretchr/testify/assert"
7
+ )
8
+
9
+ func stubRoute(c *RenderContext) UI {
10
+ return Div(Text("Stub"))
11
+ }
12
+
13
+ var testRoutes = map[string]RenderFunc{
14
+ "/about": stubRoute,
15
+ "/clock": stubRoute,
16
+ "/container": stubRoute,
17
+ "/": stubRoute,
18
+ }
19
+
20
+ func TestMatchPath(t *testing.T) {
21
+ assert.Equal(t, true, matchPath("/", "/about"))
22
+ }