From 4f01edb9230f58ff84b0dd892c931ec8ac9aad55 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 13 Sep 2022 12:56:08 +0200 Subject: move src out of srv, clean up default.nix and Makefile --- src/http/static/api.js | 118 + .../static/component-oriented-design/v1/main.go | 314 + .../component-oriented-design/v1/main_test.go | 167 + .../static/component-oriented-design/v2/main.go | 320 + .../static/component-oriented-design/v3/main.go | 390 + src/http/static/markov/Makefile | 3 + src/http/static/markov/markov.nix | 63 + src/http/static/mediocre.css | 79 + src/http/static/new.css | 451 + src/http/static/solvePow.js | 28 + src/http/static/trading-in-the-rain/CW.js | 43 + src/http/static/trading-in-the-rain/Distributor.js | 42 + .../static/trading-in-the-rain/MIDI.js/LICENSE.txt | 21 + .../trading-in-the-rain/MIDI.js/inc/shim/Base64.js | 61 + .../MIDI.js/inc/shim/Base64binary.js | 81 + .../MIDI.js/inc/shim/WebAudioAPI.js | 111 + .../MIDI.js/inc/shim/WebMIDIAPI.js | 421 + .../MIDI.js/js/midi/audioDetect.js | 101 + .../trading-in-the-rain/MIDI.js/js/midi/gm.js | 161 + .../trading-in-the-rain/MIDI.js/js/midi/loader.js | 199 + .../trading-in-the-rain/MIDI.js/js/midi/player.js | 380 + .../MIDI.js/js/midi/plugin.audiotag.js | 150 + .../MIDI.js/js/midi/plugin.webaudio.js | 326 + .../MIDI.js/js/midi/plugin.webmidi.js | 93 + .../MIDI.js/js/midi/synesthesia.js | 320 + .../MIDI.js/js/util/dom_request_script.js | 225 + .../MIDI.js/js/util/dom_request_xhr.js | 146 + src/http/static/trading-in-the-rain/MusicBox.js | 70 + src/http/static/trading-in-the-rain/RainCanvas.js | 74 + .../static/trading-in-the-rain/SeriesComposer.js | 51 + src/http/static/trading-in-the-rain/key.gpg | Bin 0 -> 669 bytes src/http/static/trading-in-the-rain/main.js | 144 + .../soundfont/acoustic_grand_piano-mp3.js | 93 + .../soundfont/acoustic_grand_piano-mp3/A0.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/A1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/A2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/A3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/A4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/A5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/A6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/A7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Ab1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Ab2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Ab3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Ab4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Ab5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Ab6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Ab7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B0.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/B7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb0.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Bb7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/C8.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/D1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/D2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/D3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/D4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/D5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/D6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/D7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Db8.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/E1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/E2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/E3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/E4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/E5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/E6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/E7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Eb1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Eb2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Eb3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Eb4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Eb5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Eb6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Eb7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/F1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/F2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/F3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/F4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/F5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/F6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/F7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/G1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/G2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/G3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/G4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/G5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/G6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/G7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Gb1.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Gb2.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Gb3.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Gb4.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Gb5.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Gb6.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-mp3/Gb7.mp3 | Bin 0 -> 12065 bytes .../soundfont/acoustic_grand_piano-ogg.js | 93 + src/http/static/utils.js | 12 + src/http/static/viz/1/cljs/core.cljs | 10762 ++++++ src/http/static/viz/1/cljs/core.js | 35801 ++++++++++++++++++ src/http/static/viz/1/cljs/core.js.map | 1 + src/http/static/viz/1/cljs_deps.js | 17 + src/http/static/viz/1/clojure/set.cljs | 161 + src/http/static/viz/1/clojure/set.cljs.cache.edn | 1 + src/http/static/viz/1/clojure/set.js | 400 + src/http/static/viz/1/clojure/set.js.map | 1 + src/http/static/viz/1/clojure/string.cljs | 258 + .../static/viz/1/clojure/string.cljs.cache.edn | 1 + src/http/static/viz/1/clojure/string.js | 505 + src/http/static/viz/1/clojure/string.js.map | 1 + src/http/static/viz/1/goog/array/array.js | 1665 + src/http/static/viz/1/goog/asserts/asserts.js | 369 + src/http/static/viz/1/goog/base.js | 2727 ++ .../static/viz/1/goog/debug/entrypointregistry.js | 159 + src/http/static/viz/1/goog/debug/error.js | 63 + src/http/static/viz/1/goog/deps.js | 1508 + .../static/viz/1/goog/disposable/disposable.js | 308 + .../static/viz/1/goog/disposable/idisposable.js | 45 + src/http/static/viz/1/goog/dom/browserfeature.js | 73 + src/http/static/viz/1/goog/dom/dom.js | 2992 ++ src/http/static/viz/1/goog/dom/nodetype.js | 48 + src/http/static/viz/1/goog/dom/safe.js | 372 + src/http/static/viz/1/goog/dom/tagname.js | 160 + src/http/static/viz/1/goog/dom/tags.js | 41 + src/http/static/viz/1/goog/events/browserevent.js | 402 + .../static/viz/1/goog/events/browserfeature.js | 84 + src/http/static/viz/1/goog/events/event.js | 145 + src/http/static/viz/1/goog/events/eventid.js | 46 + src/http/static/viz/1/goog/events/events.js | 989 + src/http/static/viz/1/goog/events/eventtype.js | 239 + src/http/static/viz/1/goog/events/listenable.js | 338 + src/http/static/viz/1/goog/events/listener.js | 131 + src/http/static/viz/1/goog/events/listenermap.js | 306 + src/http/static/viz/1/goog/fs/url.js | 110 + src/http/static/viz/1/goog/html/safehtml.js | 948 + src/http/static/viz/1/goog/html/safescript.js | 234 + src/http/static/viz/1/goog/html/safestyle.js | 449 + src/http/static/viz/1/goog/html/safestylesheet.js | 278 + src/http/static/viz/1/goog/html/safeurl.js | 412 + .../static/viz/1/goog/html/trustedresourceurl.js | 244 + .../static/viz/1/goog/html/uncheckedconversions.js | 232 + src/http/static/viz/1/goog/i18n/bidi.js | 876 + .../static/viz/1/goog/labs/useragent/browser.js | 338 + .../static/viz/1/goog/labs/useragent/engine.js | 156 + .../static/viz/1/goog/labs/useragent/platform.js | 160 + src/http/static/viz/1/goog/labs/useragent/util.js | 147 + src/http/static/viz/1/goog/math/coordinate.js | 268 + src/http/static/viz/1/goog/math/integer.js | 807 + src/http/static/viz/1/goog/math/long.js | 843 + src/http/static/viz/1/goog/math/math.js | 447 + src/http/static/viz/1/goog/math/size.js | 227 + src/http/static/viz/1/goog/object/object.js | 705 + src/http/static/viz/1/goog/reflect/reflect.js | 138 + src/http/static/viz/1/goog/string/const.js | 182 + src/http/static/viz/1/goog/string/string.js | 1631 + src/http/static/viz/1/goog/string/stringbuffer.js | 103 + src/http/static/viz/1/goog/string/stringformat.js | 221 + src/http/static/viz/1/goog/string/typedstring.js | 48 + src/http/static/viz/1/goog/useragent/useragent.js | 568 + src/http/static/viz/1/processing.js | 21796 +++++++++++ src/http/static/viz/1/quil/core.cljc | 4993 +++ src/http/static/viz/1/quil/core.cljc.cache.edn | 1 + src/http/static/viz/1/quil/core.js | 6643 ++++ src/http/static/viz/1/quil/core.js.map | 1 + src/http/static/viz/1/quil/middleware.cljc | 144 + .../static/viz/1/quil/middleware.cljc.cache.edn | 1 + src/http/static/viz/1/quil/middleware.js | 118 + src/http/static/viz/1/quil/middleware.js.map | 1 + .../viz/1/quil/middlewares/deprecated_options.cljc | 26 + .../middlewares/deprecated_options.cljc.cache.edn | 1 + .../viz/1/quil/middlewares/deprecated_options.js | 95 + .../1/quil/middlewares/deprecated_options.js.map | 1 + .../static/viz/1/quil/middlewares/fun_mode.cljc | 78 + .../viz/1/quil/middlewares/fun_mode.cljc.cache.edn | 1 + src/http/static/viz/1/quil/middlewares/fun_mode.js | 155 + .../static/viz/1/quil/middlewares/fun_mode.js.map | 1 + .../viz/1/quil/middlewares/navigation_2d.cljc | 89 + .../quil/middlewares/navigation_2d.cljc.cache.edn | 1 + .../static/viz/1/quil/middlewares/navigation_2d.js | 116 + .../viz/1/quil/middlewares/navigation_2d.js.map | 1 + .../viz/1/quil/middlewares/navigation_3d.cljc | 188 + .../quil/middlewares/navigation_3d.cljc.cache.edn | 1 + .../static/viz/1/quil/middlewares/navigation_3d.js | 261 + .../viz/1/quil/middlewares/navigation_3d.js.map | 1 + src/http/static/viz/1/quil/sketch.cljs | 128 + src/http/static/viz/1/quil/sketch.cljs.cache.edn | 1 + src/http/static/viz/1/quil/sketch.js | 348 + src/http/static/viz/1/quil/sketch.js.map | 1 + src/http/static/viz/1/quil/util.cljc | 109 + src/http/static/viz/1/quil/util.cljc.cache.edn | 1 + src/http/static/viz/1/quil/util.js | 183 + src/http/static/viz/1/quil/util.js.map | 1 + src/http/static/viz/1/viz/core.cljs | 204 + src/http/static/viz/1/viz/core.cljs.cache.edn | 1 + src/http/static/viz/1/viz/core.js | 564 + src/http/static/viz/1/viz/core.js.map | 1 + src/http/static/viz/1/viz/forest.cljs | 96 + src/http/static/viz/1/viz/forest.cljs.cache.edn | 1 + src/http/static/viz/1/viz/forest.js | 111 + src/http/static/viz/1/viz/forest.js.map | 1 + src/http/static/viz/1/viz/ghost.cljs | 72 + src/http/static/viz/1/viz/ghost.cljs.cache.edn | 1 + src/http/static/viz/1/viz/ghost.js | 89 + src/http/static/viz/1/viz/ghost.js.map | 1 + src/http/static/viz/1/viz/grid.cljs | 63 + src/http/static/viz/1/viz/grid.cljs.cache.edn | 1 + src/http/static/viz/1/viz/grid.js | 27 + src/http/static/viz/1/viz/grid.js.map | 1 + src/http/static/viz/2/cljs/core.cljs | 11565 ++++++ src/http/static/viz/2/cljs/core.js | 37633 +++++++++++++++++++ src/http/static/viz/2/cljs/core.js.map | 1 + .../viz/2/cljs/user/debug9D984AE.cljs.cache.json | 1 + src/http/static/viz/2/cljs/user/debug9D984AE.js | 3 + .../static/viz/2/cljs/user/debug9D984AE.js.map | 1 + src/http/static/viz/2/cljs_deps.js | 19 + src/http/static/viz/2/cljsc_opts.edn | 1 + src/http/static/viz/2/clojure/set.cljs | 161 + src/http/static/viz/2/clojure/set.cljs.cache.json | 1 + src/http/static/viz/2/clojure/set.js | 391 + src/http/static/viz/2/clojure/set.js.map | 1 + src/http/static/viz/2/clojure/string.cljs | 289 + .../static/viz/2/clojure/string.cljs.cache.json | 1 + src/http/static/viz/2/clojure/string.js | 477 + src/http/static/viz/2/clojure/string.js.map | 1 + src/http/static/viz/2/goog/array/array.js | 1665 + src/http/static/viz/2/goog/asserts/asserts.js | 369 + src/http/static/viz/2/goog/base.js | 2921 ++ .../static/viz/2/goog/debug/entrypointregistry.js | 159 + src/http/static/viz/2/goog/debug/error.js | 63 + src/http/static/viz/2/goog/deps.js | 1564 + .../static/viz/2/goog/disposable/disposable.js | 305 + .../static/viz/2/goog/disposable/idisposable.js | 45 + src/http/static/viz/2/goog/dom/asserts.js | 311 + src/http/static/viz/2/goog/dom/browserfeature.js | 73 + src/http/static/viz/2/goog/dom/dom.js | 3233 ++ src/http/static/viz/2/goog/dom/htmlelement.js | 29 + src/http/static/viz/2/goog/dom/nodetype.js | 48 + src/http/static/viz/2/goog/dom/safe.js | 458 + src/http/static/viz/2/goog/dom/tagname.js | 562 + src/http/static/viz/2/goog/dom/tags.js | 41 + src/http/static/viz/2/goog/events/browserevent.js | 409 + .../static/viz/2/goog/events/browserfeature.js | 122 + src/http/static/viz/2/goog/events/event.js | 143 + src/http/static/viz/2/goog/events/eventid.js | 46 + src/http/static/viz/2/goog/events/events.js | 1003 + src/http/static/viz/2/goog/events/eventtype.js | 295 + src/http/static/viz/2/goog/events/listenable.js | 338 + src/http/static/viz/2/goog/events/listener.js | 128 + src/http/static/viz/2/goog/events/listenermap.js | 307 + src/http/static/viz/2/goog/fs/url.js | 106 + src/http/static/viz/2/goog/functions/functions.js | 483 + src/http/static/viz/2/goog/html/safehtml.js | 994 + src/http/static/viz/2/goog/html/safescript.js | 234 + src/http/static/viz/2/goog/html/safestyle.js | 560 + src/http/static/viz/2/goog/html/safestylesheet.js | 344 + src/http/static/viz/2/goog/html/safeurl.js | 454 + .../static/viz/2/goog/html/trustedresourceurl.js | 408 + .../static/viz/2/goog/html/uncheckedconversions.js | 228 + src/http/static/viz/2/goog/i18n/bidi.js | 876 + src/http/static/viz/2/goog/iter/iter.js | 1284 + .../static/viz/2/goog/labs/useragent/browser.js | 338 + .../static/viz/2/goog/labs/useragent/engine.js | 156 + .../static/viz/2/goog/labs/useragent/platform.js | 160 + src/http/static/viz/2/goog/labs/useragent/util.js | 147 + src/http/static/viz/2/goog/math/coordinate.js | 279 + src/http/static/viz/2/goog/math/integer.js | 808 + src/http/static/viz/2/goog/math/long.js | 965 + src/http/static/viz/2/goog/math/math.js | 448 + src/http/static/viz/2/goog/math/size.js | 227 + src/http/static/viz/2/goog/object/object.js | 751 + src/http/static/viz/2/goog/reflect/reflect.js | 138 + src/http/static/viz/2/goog/string/const.js | 186 + src/http/static/viz/2/goog/string/string.js | 1641 + src/http/static/viz/2/goog/string/stringbuffer.js | 103 + src/http/static/viz/2/goog/string/stringformat.js | 221 + src/http/static/viz/2/goog/string/typedstring.js | 48 + src/http/static/viz/2/goog/structs/map.js | 458 + src/http/static/viz/2/goog/structs/structs.js | 354 + src/http/static/viz/2/goog/uri/uri.js | 1547 + src/http/static/viz/2/goog/uri/utils.js | 1103 + src/http/static/viz/2/goog/useragent/useragent.js | 580 + src/http/static/viz/2/process/env.cljs | 12 + src/http/static/viz/2/process/env.cljs.cache.json | 1 + src/http/static/viz/2/process/env.js | 8 + src/http/static/viz/2/process/env.js.map | 1 + src/http/static/viz/2/processing.js | 21796 +++++++++++ src/http/static/viz/2/quil/core.cljc | 4993 +++ src/http/static/viz/2/quil/core.cljc.cache.json | 1 + src/http/static/viz/2/quil/core.js | 5543 +++ src/http/static/viz/2/quil/core.js.map | 1 + src/http/static/viz/2/quil/middleware.cljc | 144 + .../static/viz/2/quil/middleware.cljc.cache.json | 1 + src/http/static/viz/2/quil/middleware.js | 118 + src/http/static/viz/2/quil/middleware.js.map | 1 + .../viz/2/quil/middlewares/deprecated_options.cljc | 26 + .../middlewares/deprecated_options.cljc.cache.json | 1 + .../viz/2/quil/middlewares/deprecated_options.js | 95 + .../2/quil/middlewares/deprecated_options.js.map | 1 + .../static/viz/2/quil/middlewares/fun_mode.cljc | 78 + .../2/quil/middlewares/fun_mode.cljc.cache.json | 1 + src/http/static/viz/2/quil/middlewares/fun_mode.js | 142 + .../static/viz/2/quil/middlewares/fun_mode.js.map | 1 + .../viz/2/quil/middlewares/navigation_2d.cljc | 89 + .../quil/middlewares/navigation_2d.cljc.cache.json | 1 + .../static/viz/2/quil/middlewares/navigation_2d.js | 116 + .../viz/2/quil/middlewares/navigation_2d.js.map | 1 + .../viz/2/quil/middlewares/navigation_3d.cljc | 188 + .../quil/middlewares/navigation_3d.cljc.cache.json | 1 + .../static/viz/2/quil/middlewares/navigation_3d.js | 261 + .../viz/2/quil/middlewares/navigation_3d.js.map | 1 + src/http/static/viz/2/quil/sketch.cljs | 128 + src/http/static/viz/2/quil/sketch.cljs.cache.json | 1 + src/http/static/viz/2/quil/sketch.js | 341 + src/http/static/viz/2/quil/sketch.js.map | 1 + src/http/static/viz/2/quil/util.cljc | 109 + src/http/static/viz/2/quil/util.cljc.cache.json | 1 + src/http/static/viz/2/quil/util.js | 168 + src/http/static/viz/2/quil/util.js.map | 1 + src/http/static/viz/2/viz/core.cljs | 264 + src/http/static/viz/2/viz/core.cljs.cache.json | 1 + src/http/static/viz/2/viz/core.js | 753 + src/http/static/viz/2/viz/core.js.map | 1 + src/http/static/viz/2/viz/debug.cljs | 4 + src/http/static/viz/2/viz/debug.cljs.cache.json | 1 + src/http/static/viz/2/viz/debug.js | 37 + src/http/static/viz/2/viz/debug.js.map | 1 + src/http/static/viz/2/viz/dial.cljs | 42 + src/http/static/viz/2/viz/dial.cljs.cache.json | 1 + src/http/static/viz/2/viz/dial.js | 44 + src/http/static/viz/2/viz/dial.js.map | 1 + src/http/static/viz/2/viz/forest.cljs | 107 + src/http/static/viz/2/viz/forest.cljs.cache.json | 1 + src/http/static/viz/2/viz/forest.js | 109 + src/http/static/viz/2/viz/forest.js.map | 1 + src/http/static/viz/2/viz/ghost.cljs | 49 + src/http/static/viz/2/viz/ghost.cljs.cache.json | 1 + src/http/static/viz/2/viz/ghost.js | 65 + src/http/static/viz/2/viz/ghost.js.map | 1 + src/http/static/viz/2/viz/grid.cljs | 63 + src/http/static/viz/2/viz/grid.cljs.cache.json | 1 + src/http/static/viz/2/viz/grid.js | 27 + src/http/static/viz/2/viz/grid.js.map | 1 + src/http/static/wtfpl.txt | 14 + 369 files changed, 231737 insertions(+) create mode 100644 src/http/static/api.js create mode 100644 src/http/static/component-oriented-design/v1/main.go create mode 100644 src/http/static/component-oriented-design/v1/main_test.go create mode 100644 src/http/static/component-oriented-design/v2/main.go create mode 100644 src/http/static/component-oriented-design/v3/main.go create mode 100644 src/http/static/markov/Makefile create mode 100644 src/http/static/markov/markov.nix create mode 100644 src/http/static/mediocre.css create mode 100644 src/http/static/new.css create mode 100644 src/http/static/solvePow.js create mode 100644 src/http/static/trading-in-the-rain/CW.js create mode 100644 src/http/static/trading-in-the-rain/Distributor.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/LICENSE.txt create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/inc/shim/Base64binary.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebAudioAPI.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/inc/shim/WebMIDIAPI.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/audioDetect.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/gm.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/loader.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/player.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/plugin.audiotag.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/plugin.webaudio.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/plugin.webmidi.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/midi/synesthesia.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/util/dom_request_script.js create mode 100644 src/http/static/trading-in-the-rain/MIDI.js/js/util/dom_request_xhr.js create mode 100644 src/http/static/trading-in-the-rain/MusicBox.js create mode 100644 src/http/static/trading-in-the-rain/RainCanvas.js create mode 100644 src/http/static/trading-in-the-rain/SeriesComposer.js create mode 100644 src/http/static/trading-in-the-rain/key.gpg create mode 100644 src/http/static/trading-in-the-rain/main.js create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3.js create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A0.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/A7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Ab1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Ab2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Ab3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Ab4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Ab5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Ab6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Ab7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B0.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/B7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb0.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Bb7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/C8.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/D1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/D2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/D3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/D4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/D5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/D6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/D7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Db8.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/E1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/E2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/E3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/E4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/E5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/E6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/E7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Eb1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Eb2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Eb3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Eb4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Eb5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Eb6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Eb7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/F1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/F2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/F3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/F4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/F5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/F6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/F7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/G1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/G2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/G3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/G4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/G5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/G6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/G7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Gb1.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Gb2.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Gb3.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Gb4.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Gb5.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Gb6.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-mp3/Gb7.mp3 create mode 100644 src/http/static/trading-in-the-rain/soundfont/acoustic_grand_piano-ogg.js create mode 100644 src/http/static/utils.js create mode 100644 src/http/static/viz/1/cljs/core.cljs create mode 100644 src/http/static/viz/1/cljs/core.js create mode 100644 src/http/static/viz/1/cljs/core.js.map create mode 100644 src/http/static/viz/1/cljs_deps.js create mode 100644 src/http/static/viz/1/clojure/set.cljs create mode 100644 src/http/static/viz/1/clojure/set.cljs.cache.edn create mode 100644 src/http/static/viz/1/clojure/set.js create mode 100644 src/http/static/viz/1/clojure/set.js.map create mode 100644 src/http/static/viz/1/clojure/string.cljs create mode 100644 src/http/static/viz/1/clojure/string.cljs.cache.edn create mode 100644 src/http/static/viz/1/clojure/string.js create mode 100644 src/http/static/viz/1/clojure/string.js.map create mode 100644 src/http/static/viz/1/goog/array/array.js create mode 100644 src/http/static/viz/1/goog/asserts/asserts.js create mode 100644 src/http/static/viz/1/goog/base.js create mode 100644 src/http/static/viz/1/goog/debug/entrypointregistry.js create mode 100644 src/http/static/viz/1/goog/debug/error.js create mode 100644 src/http/static/viz/1/goog/deps.js create mode 100644 src/http/static/viz/1/goog/disposable/disposable.js create mode 100644 src/http/static/viz/1/goog/disposable/idisposable.js create mode 100644 src/http/static/viz/1/goog/dom/browserfeature.js create mode 100644 src/http/static/viz/1/goog/dom/dom.js create mode 100644 src/http/static/viz/1/goog/dom/nodetype.js create mode 100644 src/http/static/viz/1/goog/dom/safe.js create mode 100644 src/http/static/viz/1/goog/dom/tagname.js create mode 100644 src/http/static/viz/1/goog/dom/tags.js create mode 100644 src/http/static/viz/1/goog/events/browserevent.js create mode 100644 src/http/static/viz/1/goog/events/browserfeature.js create mode 100644 src/http/static/viz/1/goog/events/event.js create mode 100644 src/http/static/viz/1/goog/events/eventid.js create mode 100644 src/http/static/viz/1/goog/events/events.js create mode 100644 src/http/static/viz/1/goog/events/eventtype.js create mode 100644 src/http/static/viz/1/goog/events/listenable.js create mode 100644 src/http/static/viz/1/goog/events/listener.js create mode 100644 src/http/static/viz/1/goog/events/listenermap.js create mode 100644 src/http/static/viz/1/goog/fs/url.js create mode 100644 src/http/static/viz/1/goog/html/safehtml.js create mode 100644 src/http/static/viz/1/goog/html/safescript.js create mode 100644 src/http/static/viz/1/goog/html/safestyle.js create mode 100644 src/http/static/viz/1/goog/html/safestylesheet.js create mode 100644 src/http/static/viz/1/goog/html/safeurl.js create mode 100644 src/http/static/viz/1/goog/html/trustedresourceurl.js create mode 100644 src/http/static/viz/1/goog/html/uncheckedconversions.js create mode 100644 src/http/static/viz/1/goog/i18n/bidi.js create mode 100644 src/http/static/viz/1/goog/labs/useragent/browser.js create mode 100644 src/http/static/viz/1/goog/labs/useragent/engine.js create mode 100644 src/http/static/viz/1/goog/labs/useragent/platform.js create mode 100644 src/http/static/viz/1/goog/labs/useragent/util.js create mode 100644 src/http/static/viz/1/goog/math/coordinate.js create mode 100644 src/http/static/viz/1/goog/math/integer.js create mode 100644 src/http/static/viz/1/goog/math/long.js create mode 100644 src/http/static/viz/1/goog/math/math.js create mode 100644 src/http/static/viz/1/goog/math/size.js create mode 100644 src/http/static/viz/1/goog/object/object.js create mode 100644 src/http/static/viz/1/goog/reflect/reflect.js create mode 100644 src/http/static/viz/1/goog/string/const.js create mode 100644 src/http/static/viz/1/goog/string/string.js create mode 100644 src/http/static/viz/1/goog/string/stringbuffer.js create mode 100644 src/http/static/viz/1/goog/string/stringformat.js create mode 100644 src/http/static/viz/1/goog/string/typedstring.js create mode 100644 src/http/static/viz/1/goog/useragent/useragent.js create mode 100644 src/http/static/viz/1/processing.js create mode 100644 src/http/static/viz/1/quil/core.cljc create mode 100644 src/http/static/viz/1/quil/core.cljc.cache.edn create mode 100644 src/http/static/viz/1/quil/core.js create mode 100644 src/http/static/viz/1/quil/core.js.map create mode 100644 src/http/static/viz/1/quil/middleware.cljc create mode 100644 src/http/static/viz/1/quil/middleware.cljc.cache.edn create mode 100644 src/http/static/viz/1/quil/middleware.js create mode 100644 src/http/static/viz/1/quil/middleware.js.map create mode 100644 src/http/static/viz/1/quil/middlewares/deprecated_options.cljc create mode 100644 src/http/static/viz/1/quil/middlewares/deprecated_options.cljc.cache.edn create mode 100644 src/http/static/viz/1/quil/middlewares/deprecated_options.js create mode 100644 src/http/static/viz/1/quil/middlewares/deprecated_options.js.map create mode 100644 src/http/static/viz/1/quil/middlewares/fun_mode.cljc create mode 100644 src/http/static/viz/1/quil/middlewares/fun_mode.cljc.cache.edn create mode 100644 src/http/static/viz/1/quil/middlewares/fun_mode.js create mode 100644 src/http/static/viz/1/quil/middlewares/fun_mode.js.map create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_2d.cljc create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_2d.cljc.cache.edn create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_2d.js create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_2d.js.map create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_3d.cljc create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_3d.cljc.cache.edn create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_3d.js create mode 100644 src/http/static/viz/1/quil/middlewares/navigation_3d.js.map create mode 100644 src/http/static/viz/1/quil/sketch.cljs create mode 100644 src/http/static/viz/1/quil/sketch.cljs.cache.edn create mode 100644 src/http/static/viz/1/quil/sketch.js create mode 100644 src/http/static/viz/1/quil/sketch.js.map create mode 100644 src/http/static/viz/1/quil/util.cljc create mode 100644 src/http/static/viz/1/quil/util.cljc.cache.edn create mode 100644 src/http/static/viz/1/quil/util.js create mode 100644 src/http/static/viz/1/quil/util.js.map create mode 100644 src/http/static/viz/1/viz/core.cljs create mode 100644 src/http/static/viz/1/viz/core.cljs.cache.edn create mode 100644 src/http/static/viz/1/viz/core.js create mode 100644 src/http/static/viz/1/viz/core.js.map create mode 100644 src/http/static/viz/1/viz/forest.cljs create mode 100644 src/http/static/viz/1/viz/forest.cljs.cache.edn create mode 100644 src/http/static/viz/1/viz/forest.js create mode 100644 src/http/static/viz/1/viz/forest.js.map create mode 100644 src/http/static/viz/1/viz/ghost.cljs create mode 100644 src/http/static/viz/1/viz/ghost.cljs.cache.edn create mode 100644 src/http/static/viz/1/viz/ghost.js create mode 100644 src/http/static/viz/1/viz/ghost.js.map create mode 100644 src/http/static/viz/1/viz/grid.cljs create mode 100644 src/http/static/viz/1/viz/grid.cljs.cache.edn create mode 100644 src/http/static/viz/1/viz/grid.js create mode 100644 src/http/static/viz/1/viz/grid.js.map create mode 100644 src/http/static/viz/2/cljs/core.cljs create mode 100644 src/http/static/viz/2/cljs/core.js create mode 100644 src/http/static/viz/2/cljs/core.js.map create mode 100644 src/http/static/viz/2/cljs/user/debug9D984AE.cljs.cache.json create mode 100644 src/http/static/viz/2/cljs/user/debug9D984AE.js create mode 100644 src/http/static/viz/2/cljs/user/debug9D984AE.js.map create mode 100644 src/http/static/viz/2/cljs_deps.js create mode 100644 src/http/static/viz/2/cljsc_opts.edn create mode 100644 src/http/static/viz/2/clojure/set.cljs create mode 100644 src/http/static/viz/2/clojure/set.cljs.cache.json create mode 100644 src/http/static/viz/2/clojure/set.js create mode 100644 src/http/static/viz/2/clojure/set.js.map create mode 100644 src/http/static/viz/2/clojure/string.cljs create mode 100644 src/http/static/viz/2/clojure/string.cljs.cache.json create mode 100644 src/http/static/viz/2/clojure/string.js create mode 100644 src/http/static/viz/2/clojure/string.js.map create mode 100644 src/http/static/viz/2/goog/array/array.js create mode 100644 src/http/static/viz/2/goog/asserts/asserts.js create mode 100644 src/http/static/viz/2/goog/base.js create mode 100644 src/http/static/viz/2/goog/debug/entrypointregistry.js create mode 100644 src/http/static/viz/2/goog/debug/error.js create mode 100644 src/http/static/viz/2/goog/deps.js create mode 100644 src/http/static/viz/2/goog/disposable/disposable.js create mode 100644 src/http/static/viz/2/goog/disposable/idisposable.js create mode 100644 src/http/static/viz/2/goog/dom/asserts.js create mode 100644 src/http/static/viz/2/goog/dom/browserfeature.js create mode 100644 src/http/static/viz/2/goog/dom/dom.js create mode 100644 src/http/static/viz/2/goog/dom/htmlelement.js create mode 100644 src/http/static/viz/2/goog/dom/nodetype.js create mode 100644 src/http/static/viz/2/goog/dom/safe.js create mode 100644 src/http/static/viz/2/goog/dom/tagname.js create mode 100644 src/http/static/viz/2/goog/dom/tags.js create mode 100644 src/http/static/viz/2/goog/events/browserevent.js create mode 100644 src/http/static/viz/2/goog/events/browserfeature.js create mode 100644 src/http/static/viz/2/goog/events/event.js create mode 100644 src/http/static/viz/2/goog/events/eventid.js create mode 100644 src/http/static/viz/2/goog/events/events.js create mode 100644 src/http/static/viz/2/goog/events/eventtype.js create mode 100644 src/http/static/viz/2/goog/events/listenable.js create mode 100644 src/http/static/viz/2/goog/events/listener.js create mode 100644 src/http/static/viz/2/goog/events/listenermap.js create mode 100644 src/http/static/viz/2/goog/fs/url.js create mode 100644 src/http/static/viz/2/goog/functions/functions.js create mode 100644 src/http/static/viz/2/goog/html/safehtml.js create mode 100644 src/http/static/viz/2/goog/html/safescript.js create mode 100644 src/http/static/viz/2/goog/html/safestyle.js create mode 100644 src/http/static/viz/2/goog/html/safestylesheet.js create mode 100644 src/http/static/viz/2/goog/html/safeurl.js create mode 100644 src/http/static/viz/2/goog/html/trustedresourceurl.js create mode 100644 src/http/static/viz/2/goog/html/uncheckedconversions.js create mode 100644 src/http/static/viz/2/goog/i18n/bidi.js create mode 100644 src/http/static/viz/2/goog/iter/iter.js create mode 100644 src/http/static/viz/2/goog/labs/useragent/browser.js create mode 100644 src/http/static/viz/2/goog/labs/useragent/engine.js create mode 100644 src/http/static/viz/2/goog/labs/useragent/platform.js create mode 100644 src/http/static/viz/2/goog/labs/useragent/util.js create mode 100644 src/http/static/viz/2/goog/math/coordinate.js create mode 100644 src/http/static/viz/2/goog/math/integer.js create mode 100644 src/http/static/viz/2/goog/math/long.js create mode 100644 src/http/static/viz/2/goog/math/math.js create mode 100644 src/http/static/viz/2/goog/math/size.js create mode 100644 src/http/static/viz/2/goog/object/object.js create mode 100644 src/http/static/viz/2/goog/reflect/reflect.js create mode 100644 src/http/static/viz/2/goog/string/const.js create mode 100644 src/http/static/viz/2/goog/string/string.js create mode 100644 src/http/static/viz/2/goog/string/stringbuffer.js create mode 100644 src/http/static/viz/2/goog/string/stringformat.js create mode 100644 src/http/static/viz/2/goog/string/typedstring.js create mode 100644 src/http/static/viz/2/goog/structs/map.js create mode 100644 src/http/static/viz/2/goog/structs/structs.js create mode 100644 src/http/static/viz/2/goog/uri/uri.js create mode 100644 src/http/static/viz/2/goog/uri/utils.js create mode 100644 src/http/static/viz/2/goog/useragent/useragent.js create mode 100644 src/http/static/viz/2/process/env.cljs create mode 100644 src/http/static/viz/2/process/env.cljs.cache.json create mode 100644 src/http/static/viz/2/process/env.js create mode 100644 src/http/static/viz/2/process/env.js.map create mode 100644 src/http/static/viz/2/processing.js create mode 100644 src/http/static/viz/2/quil/core.cljc create mode 100644 src/http/static/viz/2/quil/core.cljc.cache.json create mode 100644 src/http/static/viz/2/quil/core.js create mode 100644 src/http/static/viz/2/quil/core.js.map create mode 100644 src/http/static/viz/2/quil/middleware.cljc create mode 100644 src/http/static/viz/2/quil/middleware.cljc.cache.json create mode 100644 src/http/static/viz/2/quil/middleware.js create mode 100644 src/http/static/viz/2/quil/middleware.js.map create mode 100644 src/http/static/viz/2/quil/middlewares/deprecated_options.cljc create mode 100644 src/http/static/viz/2/quil/middlewares/deprecated_options.cljc.cache.json create mode 100644 src/http/static/viz/2/quil/middlewares/deprecated_options.js create mode 100644 src/http/static/viz/2/quil/middlewares/deprecated_options.js.map create mode 100644 src/http/static/viz/2/quil/middlewares/fun_mode.cljc create mode 100644 src/http/static/viz/2/quil/middlewares/fun_mode.cljc.cache.json create mode 100644 src/http/static/viz/2/quil/middlewares/fun_mode.js create mode 100644 src/http/static/viz/2/quil/middlewares/fun_mode.js.map create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_2d.cljc create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_2d.cljc.cache.json create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_2d.js create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_2d.js.map create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_3d.cljc create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_3d.cljc.cache.json create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_3d.js create mode 100644 src/http/static/viz/2/quil/middlewares/navigation_3d.js.map create mode 100644 src/http/static/viz/2/quil/sketch.cljs create mode 100644 src/http/static/viz/2/quil/sketch.cljs.cache.json create mode 100644 src/http/static/viz/2/quil/sketch.js create mode 100644 src/http/static/viz/2/quil/sketch.js.map create mode 100644 src/http/static/viz/2/quil/util.cljc create mode 100644 src/http/static/viz/2/quil/util.cljc.cache.json create mode 100644 src/http/static/viz/2/quil/util.js create mode 100644 src/http/static/viz/2/quil/util.js.map create mode 100644 src/http/static/viz/2/viz/core.cljs create mode 100644 src/http/static/viz/2/viz/core.cljs.cache.json create mode 100644 src/http/static/viz/2/viz/core.js create mode 100644 src/http/static/viz/2/viz/core.js.map create mode 100644 src/http/static/viz/2/viz/debug.cljs create mode 100644 src/http/static/viz/2/viz/debug.cljs.cache.json create mode 100644 src/http/static/viz/2/viz/debug.js create mode 100644 src/http/static/viz/2/viz/debug.js.map create mode 100644 src/http/static/viz/2/viz/dial.cljs create mode 100644 src/http/static/viz/2/viz/dial.cljs.cache.json create mode 100644 src/http/static/viz/2/viz/dial.js create mode 100644 src/http/static/viz/2/viz/dial.js.map create mode 100644 src/http/static/viz/2/viz/forest.cljs create mode 100644 src/http/static/viz/2/viz/forest.cljs.cache.json create mode 100644 src/http/static/viz/2/viz/forest.js create mode 100644 src/http/static/viz/2/viz/forest.js.map create mode 100644 src/http/static/viz/2/viz/ghost.cljs create mode 100644 src/http/static/viz/2/viz/ghost.cljs.cache.json create mode 100644 src/http/static/viz/2/viz/ghost.js create mode 100644 src/http/static/viz/2/viz/ghost.js.map create mode 100644 src/http/static/viz/2/viz/grid.cljs create mode 100644 src/http/static/viz/2/viz/grid.cljs.cache.json create mode 100644 src/http/static/viz/2/viz/grid.js create mode 100644 src/http/static/viz/2/viz/grid.js.map create mode 100644 src/http/static/wtfpl.txt (limited to 'src/http/static') diff --git a/src/http/static/api.js b/src/http/static/api.js new file mode 100644 index 0000000..55c9ecd --- /dev/null +++ b/src/http/static/api.js @@ -0,0 +1,118 @@ +import * as utils from "/static/utils.js"; + +const doFetch = async (req) => { + let res, jsonRes; + try { + res = await fetch(req); + jsonRes = await res.json(); + + } catch (e) { + + if (e instanceof SyntaxError) + e = new Error(`status ${res.status}, empty (or invalid) response body`); + + console.error(`api call ${req.method} ${req.url}: unexpected error:`, e); + throw e; + } + + if (jsonRes.error) { + console.error( + `api call ${req.method} ${req.url}: application error:`, + res.status, + jsonRes.error, + ); + + throw jsonRes.error; + } + + return jsonRes; +} + +// may throw +const solvePow = async () => { + + const res = await call('/api/pow/challenge'); + + const worker = new Worker('/static/solvePow.js'); + + const p = new Promise((resolve, reject) => { + worker.postMessage({seedHex: res.seed, target: res.target}); + worker.onmessage = resolve; + }); + + const powSol = (await p).data; + worker.terminate(); + + return {seed: res.seed, solution: powSol}; +} + +const call = async (route, opts = {}) => { + const { + method = 'POST', + body = {}, + requiresPow = false, + } = opts; + + const reqOpts = { + method, + }; + + if (requiresPow) { + const {seed, solution} = await solvePow(); + body.powSeed = seed; + body.powSolution = solution; + } + + if (Object.keys(body).length > 0) { + const form = new FormData(); + for (const key in body) form.append(key, body[key]); + + reqOpts.body = form; + } + + const req = new Request(route, reqOpts); + return doFetch(req); +} + +const ws = async (route, opts = {}) => { + const { + requiresPow = false, + params = {}, + } = opts; + + const docURL = new URL(document.URL); + const protocol = docURL.protocol == "http:" ? "ws:" : "wss:"; + + const fullParams = new URLSearchParams(params); + + if (requiresPow) { + const {seed, solution} = await solvePow(); + fullParams.set("powSeed", seed); + fullParams.set("powSolution", solution); + } + + const rawConn = new WebSocket(`${protocol}//${docURL.host}${route}?${fullParams.toString()}`); + + const conn = { + next: () => new Promise((resolve, reject) => { + rawConn.onmessage = (m) => { + const mj = JSON.parse(m.data); + resolve(mj); + }; + rawConn.onerror = reject; + rawConn.onclose = reject; + }), + + close: rawConn.close, + }; + + return new Promise((resolve, reject) => { + rawConn.onopen = () => resolve(conn); + rawConn.onerror = reject; + }); +} + +export { + call, + ws +} diff --git a/src/http/static/component-oriented-design/v1/main.go b/src/http/static/component-oriented-design/v1/main.go new file mode 100644 index 0000000..490a516 --- /dev/null +++ b/src/http/static/component-oriented-design/v1/main.go @@ -0,0 +1,314 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "math/rand" + "net" + "net/http" + "os" + "sort" + "strconv" + "sync" + "time" +) + +// Logger describes a simple component used for printing log lines. +type Logger interface { + Printf(string, ...interface{}) +} + +//////////////////////////////////////////////////////////////////////////////// +// The scoreboard component + +// File wraps the standard os.File type. +type File interface { + io.ReadWriter + Truncate(int64) error + Seek(int64, int) (int64, error) +} + +// scoreboard loads player scores from a save file, tracks score updates, and +// periodically saves those scores back to the save file. +type scoreboard struct { + file File + scoresM map[string]int + scoresLock sync.Mutex + + // this field will only be set in tests, and is used to synchronize with the + // the for-select loop in saveLoop. + saveLoopWaitCh chan struct{} +} + +// newScoreboard initializes a scoreboard using scores saved in the given File +// (which may be empty). The scoreboard will rewrite the save file with the +// latest scores everytime saveTicker is written to. +func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger) (*scoreboard, error) { + fileBody, err := ioutil.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("reading saved scored: %w", err) + } + + scoresM := map[string]int{} + if len(fileBody) > 0 { + if err := json.Unmarshal(fileBody, &scoresM); err != nil { + return nil, fmt.Errorf("decoding saved scores: %w", err) + } + } + + scoreboard := &scoreboard{ + file: file, + scoresM: scoresM, + saveLoopWaitCh: make(chan struct{}), + } + + go scoreboard.saveLoop(saveTicker, logger) + + return scoreboard, nil +} + +func (s *scoreboard) guessedCorrect(name string) int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + s.scoresM[name] += 1000 + return s.scoresM[name] +} + +func (s *scoreboard) guessedIncorrect(name string) int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + s.scoresM[name] -= 1 + return s.scoresM[name] +} + +func (s *scoreboard) scores() map[string]int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + scoresCp := map[string]int{} + for name, score := range s.scoresM { + scoresCp[name] = score + } + + return scoresCp +} + +func (s *scoreboard) save() error { + scores := s.scores() + if _, err := s.file.Seek(0, 0); err != nil { + return fmt.Errorf("seeking to start of save file: %w", err) + } else if err := s.file.Truncate(0); err != nil { + return fmt.Errorf("truncating save file: %w", err) + } else if err := json.NewEncoder(s.file).Encode(scores); err != nil { + return fmt.Errorf("encoding scores to save file: %w", err) + } + return nil +} + +func (s *scoreboard) saveLoop(ticker <-chan time.Time, logger Logger) { + for { + select { + case <-ticker: + if err := s.save(); err != nil { + logger.Printf("error saving scoreboard to file: %v", err) + } + case <-s.saveLoopWaitCh: + // test will unblock, nothing to do here. + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The httpHandlers component + +// Scoreboard describes the scoreboard component from the point of view of the +// httpHandler component (which only needs a subset of scoreboard's methods). +type Scoreboard interface { + guessedCorrect(name string) int + guessedIncorrect(name string) int + scores() map[string]int +} + +// RandSrc describes a randomness component which can produce random integers. +type RandSrc interface { + Int() int +} + +// httpHandlers implements the http.HandlerFuncs used by the httpServer. +type httpHandlers struct { + scoreboard Scoreboard + randSrc RandSrc + logger Logger + + mux *http.ServeMux + n int + nLock sync.Mutex +} + +func newHTTPHandlers(scoreboard Scoreboard, randSrc RandSrc, logger Logger) *httpHandlers { + n := randSrc.Int() + logger.Printf("first n is %v", n) + + httpHandlers := &httpHandlers{ + scoreboard: scoreboard, + randSrc: randSrc, + logger: logger, + mux: http.NewServeMux(), + n: n, + } + + httpHandlers.mux.HandleFunc("/guess", httpHandlers.handleGuess) + httpHandlers.mux.HandleFunc("/scores", httpHandlers.handleScores) + + return httpHandlers +} + +func (h *httpHandlers) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + h.mux.ServeHTTP(rw, r) +} + +func (h *httpHandlers) handleGuess(rw http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "text/plain") + + name := r.FormValue("name") + nStr := r.FormValue("n") + if name == "" || nStr == "" { + http.Error(rw, `"name" and "n" GET args are required`, http.StatusBadRequest) + return + } + + n, err := strconv.Atoi(nStr) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + h.nLock.Lock() + defer h.nLock.Unlock() + + if h.n == n { + newScore := h.scoreboard.guessedCorrect(name) + h.n = h.randSrc.Int() + h.logger.Printf("new n is %v", h.n) + rw.WriteHeader(http.StatusOK) + fmt.Fprintf(rw, "Correct! Your score is now %d\n", newScore) + return + } + + hint := "higher" + if h.n < n { + hint = "lower" + } + + newScore := h.scoreboard.guessedIncorrect(name) + rw.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(rw, "Try %s. Your score is now %d\n", hint, newScore) +} + +func (h *httpHandlers) handleScores(rw http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "text/plain") + + h.nLock.Lock() + defer h.nLock.Unlock() + + type scoreTup struct { + name string + score int + } + + scores := h.scoreboard.scores() + scoresTups := make([]scoreTup, 0, len(scores)) + for name, score := range scores { + scoresTups = append(scoresTups, scoreTup{name, score}) + } + + sort.Slice(scoresTups, func(i, j int) bool { + return scoresTups[i].score > scoresTups[j].score + }) + + for _, scoresTup := range scoresTups { + fmt.Fprintf(rw, "%s: %d\n", scoresTup.name, scoresTup.score) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The httpServer component. + +type httpServer struct { + httpServer *http.Server + errCh chan error +} + +func newHTTPServer(listener net.Listener, httpHandlers *httpHandlers, logger Logger) *httpServer { + loggingHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + logger.Printf("HTTP request -> %s %s %s", ip, r.Method, r.URL.String()) + httpHandlers.ServeHTTP(rw, r) + }) + + server := &httpServer{ + httpServer: &http.Server{ + Handler: loggingHandler, + }, + errCh: make(chan error, 1), + } + + go func() { + err := server.httpServer.Serve(listener) + if errors.Is(err, http.ErrServerClosed) { + err = nil + } + server.errCh <- err + }() + + return server +} + +//////////////////////////////////////////////////////////////////////////////// +// main + +const ( + saveFilePath = "./save.json" + listenAddr = ":8888" + saveInterval = 5 * time.Second +) + +func main() { + logger := log.New(os.Stdout, "", log.LstdFlags) + + logger.Printf("opening scoreboard save file %q", saveFilePath) + file, err := os.OpenFile(saveFilePath, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + logger.Fatalf("failed to open file %q: %v", saveFilePath, err) + } + + saveTicker := time.NewTicker(saveInterval) + randSrc := rand.New(rand.NewSource(time.Now().UnixNano())) + + logger.Printf("initializing scoreboard") + scoreboard, err := newScoreboard(file, saveTicker.C, logger) + if err != nil { + logger.Fatalf("failed to initialize scoreboard: %v", err) + } + + logger.Printf("listening on %q", listenAddr) + listener, err := net.Listen("tcp", listenAddr) + if err != nil { + logger.Fatalf("failed to listen on %q: %v", listenAddr, err) + } + + logger.Printf("setting up HTTP handlers") + httpHandlers := newHTTPHandlers(scoreboard, randSrc, logger) + + logger.Printf("serving HTTP requests") + newHTTPServer(listener, httpHandlers, logger) + + logger.Printf("initialization done") + select {} // block forever +} diff --git a/src/http/static/component-oriented-design/v1/main_test.go b/src/http/static/component-oriented-design/v1/main_test.go new file mode 100644 index 0000000..6cfd9fb --- /dev/null +++ b/src/http/static/component-oriented-design/v1/main_test.go @@ -0,0 +1,167 @@ +package main + +import ( + "bytes" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" +) + +type nullLogger struct{} + +func (nullLogger) Printf(string, ...interface{}) {} + +//////////////////////////////////////////////////////////////////////////////// +// Test scoreboard component + +type fileStub struct { + *bytes.Buffer +} + +func newFileStub(init string) *fileStub { + return &fileStub{Buffer: bytes.NewBufferString(init)} +} + +func (fs *fileStub) Truncate(i int64) error { + fs.Buffer.Truncate(int(i)) + return nil +} + +func (fs *fileStub) Seek(i int64, whence int) (int64, error) { + return i, nil +} + +func TestScoreboard(t *testing.T) { + newScoreboard := func(t *testing.T, fileStub *fileStub, saveTicker <-chan time.Time) *scoreboard { + t.Helper() + scoreboard, err := newScoreboard(fileStub, saveTicker, nullLogger{}) + if err != nil { + t.Errorf("unexpected error checking saved scored: %v", err) + } + return scoreboard + } + + assertScores := func(t *testing.T, expScores, gotScores map[string]int) { + t.Helper() + if !reflect.DeepEqual(expScores, gotScores) { + t.Errorf("expected scores of %+v, but instead got %+v", expScores, gotScores) + } + } + + assertSavedScores := func(t *testing.T, expScores map[string]int, fileStub *fileStub) { + t.Helper() + fileStubCp := newFileStub(fileStub.String()) + tmpScoreboard := newScoreboard(t, fileStubCp, nil) + assertScores(t, expScores, tmpScoreboard.scores()) + } + + t.Run("loading", func(t *testing.T) { + // make sure loading scoreboards with various file contents works + assertSavedScores(t, map[string]int{}, newFileStub("")) + assertSavedScores(t, map[string]int{"foo": 1}, newFileStub(`{"foo":1}`)) + assertSavedScores(t, map[string]int{"foo": 1, "bar": -2}, newFileStub(`{"foo":1,"bar":-2}`)) + }) + + t.Run("tracking", func(t *testing.T) { + scoreboard := newScoreboard(t, newFileStub(""), nil) + assertScores(t, map[string]int{}, scoreboard.scores()) // sanity check + + scoreboard.guessedCorrect("foo") + assertScores(t, map[string]int{"foo": 1000}, scoreboard.scores()) + + scoreboard.guessedIncorrect("bar") + assertScores(t, map[string]int{"foo": 1000, "bar": -1}, scoreboard.scores()) + + scoreboard.guessedIncorrect("foo") + assertScores(t, map[string]int{"foo": 999, "bar": -1}, scoreboard.scores()) + }) + + t.Run("saving", func(t *testing.T) { + // this test tests scoreboard's periodic save feature using a ticker + // channel which will be written to manually. The saveLoopWaitCh is used + // here to ensure that each ticker has been fully processed. + ticker := make(chan time.Time) + fileStub := newFileStub("") + scoreboard := newScoreboard(t, fileStub, ticker) + + tick := func() { + ticker <- time.Time{} + scoreboard.saveLoopWaitCh <- struct{}{} + } + + // this should not effect the save file at first + scoreboard.guessedCorrect("foo") + assertSavedScores(t, map[string]int{}, fileStub) + + // after the ticker the new score should get saved + tick() + assertSavedScores(t, map[string]int{"foo": 1000}, fileStub) + + // ticker again after no changes should save the same thing as before + tick() + assertSavedScores(t, map[string]int{"foo": 1000}, fileStub) + + // buffer a bunch of changes, shouldn't get saved till after tick + scoreboard.guessedCorrect("foo") + scoreboard.guessedCorrect("bar") + scoreboard.guessedCorrect("bar") + assertSavedScores(t, map[string]int{"foo": 1000}, fileStub) + tick() + assertSavedScores(t, map[string]int{"foo": 2000, "bar": 2000}, fileStub) + }) +} + +//////////////////////////////////////////////////////////////////////////////// +// Test httpHandlers component + +type mockScoreboard map[string]int + +func (mockScoreboard) guessedCorrect(name string) int { return 1 } + +func (mockScoreboard) guessedIncorrect(name string) int { return -1 } + +func (m mockScoreboard) scores() map[string]int { return m } + +type mockRandSrc struct{} + +func (m mockRandSrc) Int() int { return 666 } + +func TestHTTPHandlers(t *testing.T) { + mockScoreboard := mockScoreboard{"foo": 1, "bar": 2} + httpHandlers := newHTTPHandlers(mockScoreboard, mockRandSrc{}, nullLogger{}) + + assertRequest := func(t *testing.T, expCode int, expBody string, r *http.Request) { + t.Helper() + rw := httptest.NewRecorder() + httpHandlers.ServeHTTP(rw, r) + if rw.Code != expCode { + t.Errorf("expected HTTP response code %d, got %d", expCode, rw.Code) + } else if rw.Body.String() != expBody { + t.Errorf("expected HTTP response body %q, got %q", expBody, rw.Body.String()) + } + } + + r := httptest.NewRequest("GET", "/guess?name=foo&n=665", nil) + assertRequest(t, 400, "Try higher. Your score is now -1\n", r) + + r = httptest.NewRequest("GET", "/guess?name=foo&n=667", nil) + assertRequest(t, 400, "Try lower. Your score is now -1\n", r) + + r = httptest.NewRequest("GET", "/guess?name=foo&n=666", nil) + assertRequest(t, 200, "Correct! Your score is now 1\n", r) + + r = httptest.NewRequest("GET", "/scores", nil) + assertRequest(t, 200, "bar: 2\nfoo: 1\n", r) +} + +//////////////////////////////////////////////////////////////////////////////// +// +// httpServer is NOT tested, for the following reasons: +// * It depends on a `net.Listener`, which is not trivial to mock. +// * It does very little besides passing an httpHandlers along to an http.Server +// and managing cleanup. +// * It isn't likely to be changed often. +// * If it were to break it would be very apparent in subsequent testing stages. +// diff --git a/src/http/static/component-oriented-design/v2/main.go b/src/http/static/component-oriented-design/v2/main.go new file mode 100644 index 0000000..fb5773c --- /dev/null +++ b/src/http/static/component-oriented-design/v2/main.go @@ -0,0 +1,320 @@ +package main + +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "math/rand" + "net" + "net/http" + "os" + "sort" + "strconv" + "sync" + "time" +) + +// Logger describes a simple component used for printing log lines. +type Logger interface { + Printf(string, ...interface{}) +} + +//////////////////////////////////////////////////////////////////////////////// +// The scoreboard component + +// File wraps the standard os.File type. +type File interface { + io.ReadWriter + Truncate(int64) error + Seek(int64, int) (int64, error) +} + +// scoreboard loads player scores from a save file, tracks score updates, and +// periodically saves those scores back to the save file. +type scoreboard struct { + file File + scoresM map[string]int + scoresLock sync.Mutex + + pointsOnCorrect, pointsOnIncorrect int + + // this field will only be set in tests, and is used to synchronize with the + // the for-select loop in saveLoop. + saveLoopWaitCh chan struct{} +} + +// newScoreboard initializes a scoreboard using scores saved in the given File +// (which may be empty). The scoreboard will rewrite the save file with the +// latest scores everytime saveTicker is written to. +func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger, pointsOnCorrect, pointsOnIncorrect int) (*scoreboard, error) { + fileBody, err := ioutil.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("reading saved scored: %w", err) + } + + scoresM := map[string]int{} + if len(fileBody) > 0 { + if err := json.Unmarshal(fileBody, &scoresM); err != nil { + return nil, fmt.Errorf("decoding saved scores: %w", err) + } + } + + scoreboard := &scoreboard{ + file: file, + scoresM: scoresM, + pointsOnCorrect: pointsOnCorrect, + pointsOnIncorrect: pointsOnIncorrect, + saveLoopWaitCh: make(chan struct{}), + } + + go scoreboard.saveLoop(saveTicker, logger) + + return scoreboard, nil +} + +func (s *scoreboard) guessedCorrect(name string) int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + s.scoresM[name] += s.pointsOnCorrect + return s.scoresM[name] +} + +func (s *scoreboard) guessedIncorrect(name string) int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + s.scoresM[name] += s.pointsOnIncorrect + return s.scoresM[name] +} + +func (s *scoreboard) scores() map[string]int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + scoresCp := map[string]int{} + for name, score := range s.scoresM { + scoresCp[name] = score + } + + return scoresCp +} + +func (s *scoreboard) save() error { + scores := s.scores() + if _, err := s.file.Seek(0, 0); err != nil { + return fmt.Errorf("seeking to start of save file: %w", err) + } else if err := s.file.Truncate(0); err != nil { + return fmt.Errorf("truncating save file: %w", err) + } else if err := json.NewEncoder(s.file).Encode(scores); err != nil { + return fmt.Errorf("encoding scores to save file: %w", err) + } + return nil +} + +func (s *scoreboard) saveLoop(ticker <-chan time.Time, logger Logger) { + for { + select { + case <-ticker: + if err := s.save(); err != nil { + logger.Printf("error saving scoreboard to file: %v", err) + } + case <-s.saveLoopWaitCh: + // test will unblock, nothing to do here. + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The httpHandlers component + +// Scoreboard describes the scoreboard component from the point of view of the +// httpHandler component (which only needs a subset of scoreboard's methods). +type Scoreboard interface { + guessedCorrect(name string) int + guessedIncorrect(name string) int + scores() map[string]int +} + +// RandSrc describes a randomness component which can produce random integers. +type RandSrc interface { + Int() int +} + +// httpHandlers implements the http.HandlerFuncs used by the httpServer. +type httpHandlers struct { + scoreboard Scoreboard + randSrc RandSrc + logger Logger + + mux *http.ServeMux + n int + nLock sync.Mutex +} + +func newHTTPHandlers(scoreboard Scoreboard, randSrc RandSrc, logger Logger) *httpHandlers { + n := randSrc.Int() + logger.Printf("first n is %v", n) + + httpHandlers := &httpHandlers{ + scoreboard: scoreboard, + randSrc: randSrc, + logger: logger, + mux: http.NewServeMux(), + n: n, + } + + httpHandlers.mux.HandleFunc("/guess", httpHandlers.handleGuess) + httpHandlers.mux.HandleFunc("/scores", httpHandlers.handleScores) + + return httpHandlers +} + +func (h *httpHandlers) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + h.mux.ServeHTTP(rw, r) +} + +func (h *httpHandlers) handleGuess(rw http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "text/plain") + + name := r.FormValue("name") + nStr := r.FormValue("n") + if name == "" || nStr == "" { + http.Error(rw, `"name" and "n" GET args are required`, http.StatusBadRequest) + return + } + + n, err := strconv.Atoi(nStr) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + h.nLock.Lock() + defer h.nLock.Unlock() + + if h.n == n { + newScore := h.scoreboard.guessedCorrect(name) + h.n = h.randSrc.Int() + h.logger.Printf("new n is %v", h.n) + rw.WriteHeader(http.StatusOK) + fmt.Fprintf(rw, "Correct! Your score is now %d\n", newScore) + return + } + + hint := "higher" + if h.n < n { + hint = "lower" + } + + newScore := h.scoreboard.guessedIncorrect(name) + rw.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(rw, "Try %s. Your score is now %d\n", hint, newScore) +} + +func (h *httpHandlers) handleScores(rw http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "text/plain") + + h.nLock.Lock() + defer h.nLock.Unlock() + + type scoreTup struct { + name string + score int + } + + scores := h.scoreboard.scores() + scoresTups := make([]scoreTup, 0, len(scores)) + for name, score := range scores { + scoresTups = append(scoresTups, scoreTup{name, score}) + } + + sort.Slice(scoresTups, func(i, j int) bool { + return scoresTups[i].score > scoresTups[j].score + }) + + for _, scoresTup := range scoresTups { + fmt.Fprintf(rw, "%s: %d\n", scoresTup.name, scoresTup.score) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The httpServer component. + +type httpServer struct { + httpServer *http.Server + errCh chan error +} + +func newHTTPServer(listener net.Listener, httpHandlers *httpHandlers, logger Logger) *httpServer { + loggingHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + logger.Printf("HTTP request -> %s %s %s", ip, r.Method, r.URL.String()) + httpHandlers.ServeHTTP(rw, r) + }) + + server := &httpServer{ + httpServer: &http.Server{ + Handler: loggingHandler, + }, + errCh: make(chan error, 1), + } + + go func() { + err := server.httpServer.Serve(listener) + if errors.Is(err, http.ErrServerClosed) { + err = nil + } + server.errCh <- err + }() + + return server +} + +//////////////////////////////////////////////////////////////////////////////// +// main + +func main() { + saveFilePath := flag.String("save-file", "./save.json", "File used to save scores") + listenAddr := flag.String("listen-addr", ":8888", "Address to listen for HTTP requests on") + saveInterval := flag.Duration("save-interval", 5*time.Second, "How often to resave scores") + pointsOnCorrect := flag.Int("points-on-correct", 1000, "Amount to change a user's score by upon a correct score") + pointsOnIncorrect := flag.Int("points-on-incorrect", -1, "Amount to change a user's score by upon an incorrect score") + flag.Parse() + + logger := log.New(os.Stdout, "", log.LstdFlags) + + logger.Printf("opening scoreboard save file %q", *saveFilePath) + file, err := os.OpenFile(*saveFilePath, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + logger.Fatalf("failed to open file %q: %v", *saveFilePath, err) + } + + saveTicker := time.NewTicker(*saveInterval) + randSrc := rand.New(rand.NewSource(time.Now().UnixNano())) + + logger.Printf("initializing scoreboard") + scoreboard, err := newScoreboard(file, saveTicker.C, logger, *pointsOnCorrect, *pointsOnIncorrect) + if err != nil { + logger.Fatalf("failed to initialize scoreboard: %v", err) + } + + logger.Printf("listening on %q", *listenAddr) + listener, err := net.Listen("tcp", *listenAddr) + if err != nil { + logger.Fatalf("failed to listen on %q: %v", *listenAddr, err) + } + + logger.Printf("setting up HTTP handlers") + httpHandlers := newHTTPHandlers(scoreboard, randSrc, logger) + + logger.Printf("serving HTTP requests") + newHTTPServer(listener, httpHandlers, logger) + + logger.Printf("initialization done") + select {} // block forever +} diff --git a/src/http/static/component-oriented-design/v3/main.go b/src/http/static/component-oriented-design/v3/main.go new file mode 100644 index 0000000..afe8bab --- /dev/null +++ b/src/http/static/component-oriented-design/v3/main.go @@ -0,0 +1,390 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "math/rand" + "net" + "net/http" + "os" + "os/signal" + "sort" + "strconv" + "sync" + "time" +) + +// Logger describes a simple component used for printing log lines. +type Logger interface { + Printf(string, ...interface{}) +} + +//////////////////////////////////////////////////////////////////////////////// +// The scoreboard component + +// File wraps the standard os.File type. +type File interface { + io.ReadWriter + Truncate(int64) error + Seek(int64, int) (int64, error) +} + +// scoreboard loads player scores from a save file, tracks score updates, and +// periodically saves those scores back to the save file. +type scoreboard struct { + file File + scoresM map[string]int + scoresLock sync.Mutex + + pointsOnCorrect, pointsOnIncorrect int + + // The cleanup method closes cleanupCh to signal to all scoreboard's running + // go-routines to clean themselves up, and cleanupWG is then used to wait + // for those goroutines to do so. + cleanupCh chan struct{} + cleanupWG sync.WaitGroup + + // this field will only be set in tests, and is used to synchronize with the + // the for-select loop in saveLoop. + saveLoopWaitCh chan struct{} +} + +// newScoreboard initializes a scoreboard using scores saved in the given File +// (which may be empty). The scoreboard will rewrite the save file with the +// latest scores everytime saveTicker is written to. +func newScoreboard(file File, saveTicker <-chan time.Time, logger Logger, pointsOnCorrect, pointsOnIncorrect int) (*scoreboard, error) { + fileBody, err := ioutil.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("reading saved scored: %w", err) + } + + scoresM := map[string]int{} + if len(fileBody) > 0 { + if err := json.Unmarshal(fileBody, &scoresM); err != nil { + return nil, fmt.Errorf("decoding saved scores: %w", err) + } + } + + scoreboard := &scoreboard{ + file: file, + scoresM: scoresM, + pointsOnCorrect: pointsOnCorrect, + pointsOnIncorrect: pointsOnIncorrect, + cleanupCh: make(chan struct{}), + saveLoopWaitCh: make(chan struct{}), + } + + scoreboard.cleanupWG.Add(1) + go func() { + scoreboard.saveLoop(saveTicker, logger) + scoreboard.cleanupWG.Done() + }() + + return scoreboard, nil +} + +func (s *scoreboard) cleanup() error { + close(s.cleanupCh) + s.cleanupWG.Wait() + + if err := s.save(); err != nil { + return fmt.Errorf("saving scores during cleanup: %w", err) + } + return nil +} + +func (s *scoreboard) guessedCorrect(name string) int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + s.scoresM[name] += s.pointsOnCorrect + return s.scoresM[name] +} + +func (s *scoreboard) guessedIncorrect(name string) int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + s.scoresM[name] += s.pointsOnIncorrect + return s.scoresM[name] +} + +func (s *scoreboard) scores() map[string]int { + s.scoresLock.Lock() + defer s.scoresLock.Unlock() + + scoresCp := map[string]int{} + for name, score := range s.scoresM { + scoresCp[name] = score + } + + return scoresCp +} + +func (s *scoreboard) save() error { + scores := s.scores() + if _, err := s.file.Seek(0, 0); err != nil { + return fmt.Errorf("seeking to start of save file: %w", err) + } else if err := s.file.Truncate(0); err != nil { + return fmt.Errorf("truncating save file: %w", err) + } else if err := json.NewEncoder(s.file).Encode(scores); err != nil { + return fmt.Errorf("encoding scores to save file: %w", err) + } + return nil +} + +func (s *scoreboard) saveLoop(ticker <-chan time.Time, logger Logger) { + for { + select { + case <-ticker: + if err := s.save(); err != nil { + logger.Printf("error saving scoreboard to file: %v", err) + } + case <-s.cleanupCh: + return + case <-s.saveLoopWaitCh: + // test will unblock, nothing to do here. + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The httpHandlers component + +// Scoreboard describes the scoreboard component from the point of view of the +// httpHandler component (which only needs a subset of scoreboard's methods). +type Scoreboard interface { + guessedCorrect(name string) int + guessedIncorrect(name string) int + scores() map[string]int +} + +// RandSrc describes a randomness component which can produce random integers. +type RandSrc interface { + Int() int +} + +// httpHandlers implements the http.HandlerFuncs used by the httpServer. +type httpHandlers struct { + scoreboard Scoreboard + randSrc RandSrc + logger Logger + + mux *http.ServeMux + n int + nLock sync.Mutex +} + +func newHTTPHandlers(scoreboard Scoreboard, randSrc RandSrc, logger Logger) *httpHandlers { + n := randSrc.Int() + logger.Printf("first n is %v", n) + + httpHandlers := &httpHandlers{ + scoreboard: scoreboard, + randSrc: randSrc, + logger: logger, + mux: http.NewServeMux(), + n: n, + } + + httpHandlers.mux.HandleFunc("/guess", httpHandlers.handleGuess) + httpHandlers.mux.HandleFunc("/scores", httpHandlers.handleScores) + + return httpHandlers +} + +func (h *httpHandlers) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + h.mux.ServeHTTP(rw, r) +} + +func (h *httpHandlers) handleGuess(rw http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "text/plain") + + name := r.FormValue("name") + nStr := r.FormValue("n") + if name == "" || nStr == "" { + http.Error(rw, `"name" and "n" GET args are required`, http.StatusBadRequest) + return + } + + n, err := strconv.Atoi(nStr) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + + h.nLock.Lock() + defer h.nLock.Unlock() + + if h.n == n { + newScore := h.scoreboard.guessedCorrect(name) + h.n = h.randSrc.Int() + h.logger.Printf("new n is %v", h.n) + rw.WriteHeader(http.StatusOK) + fmt.Fprintf(rw, "Correct! Your score is now %d\n", newScore) + return + } + + hint := "higher" + if h.n < n { + hint = "lower" + } + + newScore := h.scoreboard.guessedIncorrect(name) + rw.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(rw, "Try %s. Your score is now %d\n", hint, newScore) +} + +func (h *httpHandlers) handleScores(rw http.ResponseWriter, r *http.Request) { + r.Header.Set("Content-Type", "text/plain") + + h.nLock.Lock() + defer h.nLock.Unlock() + + type scoreTup struct { + name string + score int + } + + scores := h.scoreboard.scores() + scoresTups := make([]scoreTup, 0, len(scores)) + for name, score := range scores { + scoresTups = append(scoresTups, scoreTup{name, score}) + } + + sort.Slice(scoresTups, func(i, j int) bool { + return scoresTups[i].score > scoresTups[j].score + }) + + for _, scoresTup := range scoresTups { + fmt.Fprintf(rw, "%s: %d\n", scoresTup.name, scoresTup.score) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The httpServer component. + +type httpServer struct { + httpServer *http.Server + errCh chan error +} + +func newHTTPServer(listener net.Listener, httpHandlers *httpHandlers, logger Logger) *httpServer { + loggingHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ip, _, _ := net.SplitHostPort(r.RemoteAddr) + logger.Printf("HTTP request -> %s %s %s", ip, r.Method, r.URL.String()) + httpHandlers.ServeHTTP(rw, r) + }) + + server := &httpServer{ + httpServer: &http.Server{ + Handler: loggingHandler, + }, + errCh: make(chan error, 1), + } + + go func() { + err := server.httpServer.Serve(listener) + if errors.Is(err, http.ErrServerClosed) { + err = nil + } + server.errCh <- err + }() + + return server +} + +func (s *httpServer) cleanup() error { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + if err := s.httpServer.Shutdown(ctx); err != nil { + return fmt.Errorf("shutting down http server: %w", err) + } + return <-s.errCh +} + +//////////////////////////////////////////////////////////////////////////////// +// main + +func main() { + saveFilePath := flag.String("save-file", "./save.json", "File used to save scores") + listenAddr := flag.String("listen-addr", ":8888", "Address to listen for HTTP requests on") + saveInterval := flag.Duration("save-interval", 5*time.Second, "How often to resave scores") + pointsOnCorrect := flag.Int("points-on-correct", 1000, "Amount to change a user's score by upon a correct score") + pointsOnIncorrect := flag.Int("points-on-incorrect", -1, "Amount to change a user's score by upon an incorrect score") + flag.Parse() + + logger := log.New(os.Stdout, "", log.LstdFlags) + + logger.Printf("opening scoreboard save file %q", *saveFilePath) + file, err := os.OpenFile(*saveFilePath, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + logger.Fatalf("failed to open file %q: %v", *saveFilePath, err) + } + + saveTicker := time.NewTicker(*saveInterval) + randSrc := rand.New(rand.NewSource(time.Now().UnixNano())) + + logger.Printf("initializing scoreboard") + scoreboard, err := newScoreboard(file, saveTicker.C, logger, *pointsOnCorrect, *pointsOnIncorrect) + if err != nil { + logger.Fatalf("failed to initialize scoreboard: %v", err) + } + + logger.Printf("listening on %q", *listenAddr) + listener, err := net.Listen("tcp", *listenAddr) + if err != nil { + logger.Fatalf("failed to listen on %q: %v", *listenAddr, err) + } + + logger.Printf("setting up HTTP handlers") + httpHandlers := newHTTPHandlers(scoreboard, randSrc, logger) + + logger.Printf("serving HTTP requests") + httpServer := newHTTPServer(listener, httpHandlers, logger) + + logger.Printf("initialization done, waiting for interrupt signal") + sigCh := make(chan os.Signal) + signal.Notify(sigCh, os.Interrupt) + <-sigCh + logger.Printf("interrupt signal received, cleaning up") + go func() { + <-sigCh + log.Fatalf("interrupt signal received again, forcing shutdown") + }() + + if err := httpServer.cleanup(); err != nil { + logger.Fatalf("cleaning up http server: %v", err) + } + + // NOTE go's builtin http server does not follow component property 5a, and + // instead closes the net.Listener given to it as a parameter when Shutdown + // is called. Because of that inconsistency this Close would error if it + // were called. + // + // While there are ways to work around this, it's instead highlighted in + // this example as an instance of a language making the component-oriented + // pattern more difficult. + // + //if err := listener.Close(); err != nil { + // logger.Fatalf("closing listener %q: %v", listenAddr, err) + //} + + if err := scoreboard.cleanup(); err != nil { + logger.Fatalf("cleaning up scoreboard: %v", err) + } + + saveTicker.Stop() + + if err := file.Close(); err != nil { + logger.Fatalf("closing file %q: %v", *saveFilePath, err) + } + + os.Stdout.Sync() +} diff --git a/src/http/static/markov/Makefile b/src/http/static/markov/Makefile new file mode 100644 index 0000000..4195baf --- /dev/null +++ b/src/http/static/markov/Makefile @@ -0,0 +1,3 @@ +bundle: + nix-build markov.nix -A nixBundle + ./result/bin/nix-bundle '((import ./markov.nix) {}).entrypoint' '/bin/markov' diff --git a/src/http/static/markov/markov.nix b/src/http/static/markov/markov.nix new file mode 100644 index 0000000..042ab8d --- /dev/null +++ b/src/http/static/markov/markov.nix @@ -0,0 +1,63 @@ +{ + pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/d50923ab2d308a1ddb21594ba6ae064cab65d8ae.tar.gz") {} +}: + +rec { + + markov = pkgs.buildGoModule { + pname = "markov"; + version = "618b666484566de71f2d59114d011ff4621cf375"; + src = pkgs.fetchFromGitHub { + owner = "mediocregopher"; + repo = "markov"; + rev = "618b666484566de71f2d59114d011ff4621cf375"; + sha256 = "1sx9dr1q3vr3q8nyx3965x6259iyl85591vx815g1xacygv4i4fg"; + }; + vendorSha256 = "048wygrmv26fsnypsp6vxf89z3j0gs9f1w4i63khx7h134yxhbc6"; + }; + + entrypoint = pkgs.writeScriptBin "markov" '' + #!${pkgs.stdenv.shell} + + # On every run we create new, temporary, configuration files for redis and + # circus. To do this we create a new config directory. + markovCfgDir=$(${pkgs.coreutils}/bin/mktemp -d) + echo "generating configuration to $markovCfgDir" + + ${pkgs.coreutils}/bin/cat >$markovCfgDir/redis.conf <$markovCfgDir/circus.ini < *:first-child { + margin-top: 0; + padding-top: 0; +} + +header > *:last-child { + margin-bottom: 0; +} + +a button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + font-size: 1rem; + display: inline-block; + padding: 6px 12px; + text-align: center; + text-decoration: none; + white-space: nowrap; + background: var(--nc-lk-1); + color: var(--nc-lk-tx); + border: 0; + border-radius: 4px; + box-sizing: border-box; + cursor: pointer; + color: var(--nc-lk-tx); +} + +a button[disabled], +button[disabled], +input[type="submit"][disabled], +input[type="reset"][disabled], +input[type="button"][disabled] { + cursor: default; + opacity: .5; + + /* Set the [X] cursor while hovering a disabled link */ + cursor: not-allowed; +} + +.button:focus, +.button:enabled:hover, +button:focus, +button:enabled:hover, +input[type="submit"]:focus, +input[type="submit"]:enabled:hover, +input[type="reset"]:focus, +input[type="reset"]:enabled:hover, +input[type="button"]:focus, +input[type="button"]:enabled:hover { + background: var(--nc-lk-2); +} + +a img { + margin-bottom: 0px; +} + +code, +pre, +kbd, +samp { + /* Set the font family for monospaced elements */ + font-family: var(--nc-font-mono); +} + +code, +samp, +kbd, +pre { + /* The main preformatted style. This is changed slightly across different cases. */ + background: var(--nc-bg-2); + border: 1px solid var(--nc-bg-3); + border-radius: 4px; + padding: 3px 6px; + /* ↓ font-size is relative to containing element, so it scales for titles*/ + font-size: 0.9em; +} + +kbd { + /* Makes the kbd element look like a keyboard key */ + border-bottom: 3px solid var(--nc-bg-3); +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; +} + +pre code { + /* When is in a
, reset it's formatting to blend in */
+	background: inherit;
+	font-size: inherit;
+	color: inherit;
+	border: 0;
+	padding: 0;
+	margin: 0;
+}
+
+code pre {
+	/* When 
 is in a , reset it's formatting to blend in */
+	display: inline;
+	background: inherit;
+	font-size: inherit;
+	color: inherit;
+	border: 0;
+	padding: 0;
+	margin: 0;
+}
+
+details {
+	/* Make the 
look more "clickable" */ + padding: .6rem 1rem; + background: var(--nc-bg-2); + border: 1px solid var(--nc-bg-3); + border-radius: 4px; +} + +summary { + /* Makes the look more like a "clickable" link with the pointer cursor */ + cursor: pointer; + font-weight: bold; +} + +details[open] { + /* Adjust the
padding while open */ + padding-bottom: .75rem; +} + +details[open] summary { + /* Adjust the
padding while open */ + margin-bottom: 6px; +} + +details[open]>*:last-child { + /* Resets the bottom margin of the last element in the
while
is opened. This prevents double margins/paddings. */ + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd::before { + /* Add an arrow to data table definitions */ + content: '→ '; +} + +hr { + /* Reset the border of the
separator, then set a better line */ + border: 0; + border-bottom: 1px solid var(--nc-bg-3); + margin: 1rem auto; +} + +fieldset { + margin-top: 1rem; + padding: 2rem; + border: 1px solid var(--nc-bg-3); + border-radius: 4px; +} + +legend { + padding: auto .5rem; +} + +table { + /* border-collapse sets the table's elements to share borders, rather than floating as separate "boxes". */ + border-collapse: collapse; + width: 100% +} + +td, +th { + border: 1px solid var(--nc-bg-3); + text-align: left; + padding: .5rem; +} + +th { + background: var(--nc-bg-2); +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background: var(--nc-bg-2); +} + +table caption { + font-weight: bold; + margin-bottom: .5rem; +} + +textarea { + /* Don't let the