Note: I'm writing this as an experiment in coming back to a side-project that has long been sitting around, and quitetly.
Looking at it with old eyes and high level questions (of myself) like:
- Does it still work?
- Can I understand it?
- Can I still run it locally?
- Can I make changes to it?
- How long does all of that take?
Background
Four-ish years ago, I wanted to try writing a project in ClojureScript.
A few years before that I wrote a bunch of Clojure and had just wanted a brief and fun refresher project with the langauge, but trying a different runtime.
I also wanted to play around with the canvas API, because I had never really done much drawing. Why limit yourself to one thing a time, right?
This project was color-transit. It's a single static page that cycles through color graidents, and boy does it do that.
You can see it live here. This link was actually surprising to me:
it's running on this domain. In hindsight, of course it is, this is all github
pages... I just didn't immediately realize that any repo that has this gh-pages
branch would end up on this site. But here we are. Cool. Works as intended, I guess.
First look back at the repo
Wow, there's like no README
at all.
I was definitely not doing this project for any kind of reproducability, just a playground.
It's using leiningen
, which was the standard at the time when
building clj/s projects, I'm not really sure if that's still the thing people
use. Since then, or maybe even at that time, boot
was a thing that
started to exist. Both are mentioned in the clojure(script) docs.
The project uses lein-figwheel
for live reloading, which
is pretty neato.
Commits & Timeline
The first obvious thing I can see is that most of the work happened quickly, On the day before Christmas, and then the day after.
The commits on the days following were cleanup and reorganization, and somehow
a few weeks later I came back to ship it to a gh-pages
branch. I just checked
to see why I decided to do this, maybe it was when the feature shipped, but
looking at the docs, that wasn't it... I did this over two
years after the last update to that documentation (Dec, 2013 vs Jan, 2016).
Also looking at the current way to ship to the branch, dang this is a mess compared to what I'm doing for this specific site. But it worked as well as it needed to.
Does it still work?
Yep. That's pretty cool- it's just some JS that's been hanging out on a CDN for the past few years.
Local development
Instead of installing everything (clojure and lein) locally, I am going to try and run all of this in a docker container.
First attempt
The Dockerfile
....
FROM clojure:slim-buster
WORKDIR /usr/app
COPY project.clj .
RUN lein deps
And then running it...
docker build -t color-transit .
docker run --rm -v "$PWD":/usr/app color-transit lein figwheel
Looking through the checks notes, hundreds of lines of stack trace, I see
Caused by: java.lang.ExceptionInInitializerError: null
.
Full stack-trace
Warning: implicit hook found: leiningen.cljsbuild/activate Hooks are deprecated and will be removed in a future version. clojure.lang.Compiler$CompilerException: Syntax error compiling at (cljs/tagged_literals.cljc:1:1). #:clojure.error{:phase :compile-syntax-check, :line 1, :column 1, :source "cljs/tagged_literals.cljc"} at clojure.lang.Compiler.load (Compiler.java:7647) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:805) cljs.analyzer$eval1658$loading__6706__auto____1659.invoke (analyzer.cljc:9) cljs.analyzer$eval1658.invokeStatic (analyzer.cljc:9) cljs.analyzer$eval1658.invoke (analyzer.cljc:9) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:457) figwheel_sidecar.utils$eval1652$loading__6706__auto____1653.invoke (utils.clj:1) figwheel_sidecar.utils$eval1652.invokeStatic (utils.clj:1) figwheel_sidecar.utils$eval1652.invoke (utils.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:551) figwheel_sidecar.config$eval1646$loading__6706__auto____1647.invoke (config.clj:1) figwheel_sidecar.config$eval1646.invokeStatic (config.clj:1) figwheel_sidecar.config$eval1646.invoke (config.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:457) leiningen.figwheel$eval1640$loading__6706__auto____1641.invoke (figwheel.clj:1) leiningen.figwheel$eval1640.invokeStatic (figwheel.clj:1) leiningen.figwheel$eval1640.invoke (figwheel.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:408) leiningen.core.utils$require_resolve.invokeStatic (utils.clj:102) leiningen.core.utils$require_resolve.invoke (utils.clj:95) leiningen.core.utils$require_resolve.invokeStatic (utils.clj:105) leiningen.core.utils$require_resolve.invoke (utils.clj:95) leiningen.core.main$lookup_task_var.invokeStatic (main.clj:69) leiningen.core.main$lookup_task_var.invoke (main.clj:65) leiningen.core.main$pass_through_help_QMARK_.invokeStatic (main.clj:79) leiningen.core.main$pass_through_help_QMARK_.invoke (main.clj:73) leiningen.core.main$task_args.invokeStatic (main.clj:82) leiningen.core.main$task_args.invoke (main.clj:81) leiningen.core.main$resolve_and_apply.invokeStatic (main.clj:339) leiningen.core.main$resolve_and_apply.invoke (main.clj:336) leiningen.core.main$_main$fn__6681.invoke (main.clj:452) leiningen.core.main$_main.invokeStatic (main.clj:442) leiningen.core.main$_main.doInvoke (main.clj:439) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.lang.Var.applyTo (Var.java:705) clojure.core$apply.invokeStatic (core.clj:665) clojure.main$main_opt.invokeStatic (main.clj:491) clojure.main$main_opt.invoke (main.clj:487) clojure.main$main.invokeStatic (main.clj:598) clojure.main$main.doInvoke (main.clj:561) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.lang.Var.applyTo (Var.java:705) clojure.main.main (main.java:37) Caused by: java.lang.ExceptionInInitializerError: null at java.lang.Class.forName0 (Class.java:-2) java.lang.Class.forName (Class.java:398) clojure.lang.RT.classForName (RT.java:2207) clojure.lang.RT.classForName (RT.java:2216) clojure.lang.RT.loadClassForName (RT.java:2235) clojure.lang.RT.load (RT.java:453) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:408) cljs.tagged_literals$eval2011$loading__6706__auto____2012.invoke (tagged_literals.cljc:1) cljs.tagged_literals$eval2011.invokeStatic (tagged_literals.cljc:1) cljs.tagged_literals$eval2011.invoke (tagged_literals.cljc:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:805) cljs.analyzer$eval1658$loading__6706__auto____1659.invoke (analyzer.cljc:9) cljs.analyzer$eval1658.invokeStatic (analyzer.cljc:9) cljs.analyzer$eval1658.invoke (analyzer.cljc:9) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:457) figwheel_sidecar.utils$eval1652$loading__6706__auto____1653.invoke (utils.clj:1) figwheel_sidecar.utils$eval1652.invokeStatic (utils.clj:1) figwheel_sidecar.utils$eval1652.invoke (utils.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:551) figwheel_sidecar.config$eval1646$loading__6706__auto____1647.invoke (config.clj:1) figwheel_sidecar.config$eval1646.invokeStatic (config.clj:1) figwheel_sidecar.config$eval1646.invoke (config.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:457) leiningen.figwheel$eval1640$loading__6706__auto____1641.invoke (figwheel.clj:1) leiningen.figwheel$eval1640.invokeStatic (figwheel.clj:1) leiningen.figwheel$eval1640.invoke (figwheel.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:408) leiningen.core.utils$require_resolve.invokeStatic (utils.clj:102) leiningen.core.utils$require_resolve.invoke (utils.clj:95) leiningen.core.utils$require_resolve.invokeStatic (utils.clj:105) leiningen.core.utils$require_resolve.invoke (utils.clj:95) leiningen.core.main$lookup_task_var.invokeStatic (main.clj:69) leiningen.core.main$lookup_task_var.invoke (main.clj:65) leiningen.core.main$pass_through_help_QMARK_.invokeStatic (main.clj:79) leiningen.core.main$pass_through_help_QMARK_.invoke (main.clj:73) leiningen.core.main$task_args.invokeStatic (main.clj:82) leiningen.core.main$task_args.invoke (main.clj:81) leiningen.core.main$resolve_and_apply.invokeStatic (main.clj:339) leiningen.core.main$resolve_and_apply.invoke (main.clj:336) leiningen.core.main$_main$fn__6681.invoke (main.clj:452) leiningen.core.main$_main.invokeStatic (main.clj:442) leiningen.core.main$_main.doInvoke (main.clj:439) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.lang.Var.applyTo (Var.java:705) clojure.core$apply.invokeStatic (core.clj:665) clojure.main$main_opt.invokeStatic (main.clj:491) clojure.main$main_opt.invoke (main.clj:487) clojure.main$main.invokeStatic (main.clj:598) clojure.main$main.doInvoke (main.clj:561) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.lang.Var.applyTo (Var.java:705) clojure.main.main (main.java:37) Caused by: java.lang.ClassNotFoundException: java/sql/Timestamp at java.lang.Class.forName0 (Class.java:-2) java.lang.Class.forName (Class.java:398) clojure.lang.RT.classForName (RT.java:2207) clojure.lang.RT.classForNameNonLoading (RT.java:2220) clojure.instant/loading__6706__auto__ (instant.clj:9) clojure.instant__init.load (:9) clojure.instant__init.<clinit> (:-1) java.lang.Class.forName0 (Class.java:-2) java.lang.Class.forName (Class.java:398) clojure.lang.RT.classForName (RT.java:2207) clojure.lang.RT.classForName (RT.java:2216) clojure.lang.RT.loadClassForName (RT.java:2235) clojure.lang.RT.load (RT.java:453) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:408) cljs.tagged_literals$eval2011$loading__6706__auto____2012.invoke (tagged_literals.cljc:1) cljs.tagged_literals$eval2011.invokeStatic (tagged_literals.cljc:1) cljs.tagged_literals$eval2011.invoke (tagged_literals.cljc:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:805) cljs.analyzer$eval1658$loading__6706__auto____1659.invoke (analyzer.cljc:9) cljs.analyzer$eval1658.invokeStatic (analyzer.cljc:9) cljs.analyzer$eval1658.invoke (analyzer.cljc:9) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:457) figwheel_sidecar.utils$eval1652$loading__6706__auto____1653.invoke (utils.clj:1) figwheel_sidecar.utils$eval1652.invokeStatic (utils.clj:1) figwheel_sidecar.utils$eval1652.invoke (utils.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:551) figwheel_sidecar.config$eval1646$loading__6706__auto____1647.invoke (config.clj:1) figwheel_sidecar.config$eval1646.invokeStatic (config.clj:1) figwheel_sidecar.config$eval1646.invoke (config.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:457) leiningen.figwheel$eval1640$loading__6706__auto____1641.invoke (figwheel.clj:1) leiningen.figwheel$eval1640.invokeStatic (figwheel.clj:1) leiningen.figwheel$eval1640.invoke (figwheel.clj:1) clojure.lang.Compiler.eval (Compiler.java:7176) clojure.lang.Compiler.eval (Compiler.java:7165) clojure.lang.Compiler.load (Compiler.java:7635) clojure.lang.RT.loadResourceScript (RT.java:381) clojure.lang.RT.loadResourceScript (RT.java:372) clojure.lang.RT.load (RT.java:463) clojure.lang.RT.load (RT.java:428) clojure.core$load$fn__6824.invoke (core.clj:6126) clojure.core$load.invokeStatic (core.clj:6125) clojure.core$load.doInvoke (core.clj:6109) clojure.lang.RestFn.invoke (RestFn.java:408) clojure.core$load_one.invokeStatic (core.clj:5908) clojure.core$load_one.invoke (core.clj:5903) clojure.core$load_lib$fn__6765.invoke (core.clj:5948) clojure.core$load_lib.invokeStatic (core.clj:5947) clojure.core$load_lib.doInvoke (core.clj:5928) clojure.lang.RestFn.applyTo (RestFn.java:142) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$load_libs.invokeStatic (core.clj:5985) clojure.core$load_libs.doInvoke (core.clj:5969) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.core$apply.invokeStatic (core.clj:667) clojure.core$require.invokeStatic (core.clj:6007) clojure.core$require.doInvoke (core.clj:6007) clojure.lang.RestFn.invoke (RestFn.java:408) leiningen.core.utils$require_resolve.invokeStatic (utils.clj:102) leiningen.core.utils$require_resolve.invoke (utils.clj:95) leiningen.core.utils$require_resolve.invokeStatic (utils.clj:105) leiningen.core.utils$require_resolve.invoke (utils.clj:95) leiningen.core.main$lookup_task_var.invokeStatic (main.clj:69) leiningen.core.main$lookup_task_var.invoke (main.clj:65) leiningen.core.main$pass_through_help_QMARK_.invokeStatic (main.clj:79) leiningen.core.main$pass_through_help_QMARK_.invoke (main.clj:73) leiningen.core.main$task_args.invokeStatic (main.clj:82) leiningen.core.main$task_args.invoke (main.clj:81) leiningen.core.main$resolve_and_apply.invokeStatic (main.clj:339) leiningen.core.main$resolve_and_apply.invoke (main.clj:336) leiningen.core.main$_main$fn__6681.invoke (main.clj:452) leiningen.core.main$_main.invokeStatic (main.clj:442) leiningen.core.main$_main.doInvoke (main.clj:439) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.lang.Var.applyTo (Var.java:705) clojure.core$apply.invokeStatic (core.clj:665) clojure.main$main_opt.invokeStatic (main.clj:491) clojure.main$main_opt.invoke (main.clj:487) clojure.main$main.invokeStatic (main.clj:598) clojure.main$main.doInvoke (main.clj:561) clojure.lang.RestFn.applyTo (RestFn.java:137) clojure.lang.Var.applyTo (Var.java:705) clojure.main.main (main.java:37)
Down to JDK8
Changing the base image to clojure:openjdk-8-slim-buster
fixes the initial
null
pointer exception, but now I'm getting another one.
Second stack-trace
Warning: implicit hook found: leiningen.cljsbuild/activate Hooks are deprecated and will be removed in a future version. Exception in thread "main" java.io.FileNotFoundException: Could not locate clojure/tools/nrepl/middleware/interruptible_eval__init.class or clojure/tools/nrepl/middleware/interruptible_eval.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name., compiling:(figwheel_sidecar/repl.clj:1:1) at clojure.lang.Compiler.load(Compiler.java:7239) at clojure.lang.RT.loadResourceScript(RT.java:371) at clojure.lang.RT.loadResourceScript(RT.java:362) at clojure.lang.RT.load(RT.java:446) at clojure.lang.RT.load(RT.java:412) at clojure.core$load$fn__5448.invoke(core.clj:5866) at clojure.core$load.doInvoke(core.clj:5865) at clojure.lang.RestFn.invoke(RestFn.java:408) at clojure.core$load_one.invoke(core.clj:5671) at clojure.core$load_lib$fn__5397.invoke(core.clj:5711) at clojure.core$load_lib.doInvoke(core.clj:5710) at clojure.lang.RestFn.applyTo(RestFn.java:142) at clojure.core$apply.invoke(core.clj:632) at clojure.core$load_libs.doInvoke(core.clj:5749) at clojure.lang.RestFn.applyTo(RestFn.java:137) at clojure.core$apply.invoke(core.clj:632) at clojure.core$require.doInvoke(core.clj:5832) at clojure.lang.RestFn.invoke(RestFn.java:1289) at figwheel_sidecar.system$eval15$loading__5340__auto____16.invoke(system.clj:1) at figwheel_sidecar.system$eval15.invoke(system.clj:1) at clojure.lang.Compiler.eval(Compiler.java:6782) at clojure.lang.Compiler.eval(Compiler.java:6771) at clojure.lang.Compiler.load(Compiler.java:7227) at clojure.lang.RT.loadResourceScript(RT.java:371) at clojure.lang.RT.loadResourceScript(RT.java:362) at clojure.lang.RT.load(RT.java:446) at clojure.lang.RT.load(RT.java:412) at clojure.core$load$fn__5448.invoke(core.clj:5866) at clojure.core$load.doInvoke(core.clj:5865) at clojure.lang.RestFn.invoke(RestFn.java:408) at clojure.core$load_one.invoke(core.clj:5671) at clojure.core$load_lib$fn__5397.invoke(core.clj:5711) at clojure.core$load_lib.doInvoke(core.clj:5710) at clojure.lang.RestFn.applyTo(RestFn.java:142) at clojure.core$apply.invoke(core.clj:632) at clojure.core$load_libs.doInvoke(core.clj:5749) at clojure.lang.RestFn.applyTo(RestFn.java:137) at clojure.core$apply.invoke(core.clj:632) at clojure.core$require.doInvoke(core.clj:5832) at clojure.lang.RestFn.invoke(RestFn.java:457) at figwheel_sidecar.repl_api$eval9$loading__5340__auto____10.invoke(repl_api.clj:1) at figwheel_sidecar.repl_api$eval9.invoke(repl_api.clj:1) at clojure.lang.Compiler.eval(Compiler.java:6782) at clojure.lang.Compiler.eval(Compiler.java:6771) at clojure.lang.Compiler.load(Compiler.java:7227) at clojure.lang.RT.loadResourceScript(RT.java:371) at clojure.lang.RT.loadResourceScript(RT.java:362) at clojure.lang.RT.load(RT.java:446) at clojure.lang.RT.load(RT.java:412) at clojure.core$load$fn__5448.invoke(core.clj:5866) at clojure.core$load.doInvoke(core.clj:5865) at clojure.lang.RestFn.invoke(RestFn.java:408) at clojure.core$load_one.invoke(core.clj:5671) at clojure.core$load_lib$fn__5397.invoke(core.clj:5711) at clojure.core$load_lib.doInvoke(core.clj:5710) at clojure.lang.RestFn.applyTo(RestFn.java:142) at clojure.core$apply.invoke(core.clj:632) at clojure.core$load_libs.doInvoke(core.clj:5749) at clojure.lang.RestFn.applyTo(RestFn.java:137) at clojure.core$apply.invoke(core.clj:632) at clojure.core$require.doInvoke(core.clj:5832) at clojure.lang.RestFn.invoke(RestFn.java:408) at user$eval5.invoke(form-init37068904552664761.clj:1) at clojure.lang.Compiler.eval(Compiler.java:6782) at clojure.lang.Compiler.eval(Compiler.java:6771) at clojure.lang.Compiler.load(Compiler.java:7227) at clojure.lang.Compiler.loadFile(Compiler.java:7165) at clojure.main$load_script.invoke(main.clj:275) at clojure.main$init_opt.invoke(main.clj:280) at clojure.main$initialize.invoke(main.clj:308) at clojure.main$null_opt.invoke(main.clj:343) at clojure.main$main.doInvoke(main.clj:421) at clojure.lang.RestFn.invoke(RestFn.java:421) at clojure.lang.Var.invoke(Var.java:383) at clojure.lang.AFn.applyToHelper(AFn.java:156) at clojure.lang.Var.applyTo(Var.java:700) at clojure.main.main(main.java:37) Caused by: java.io.FileNotFoundException: Could not locate clojure/tools/nrepl/middleware/interruptible_eval__init.class or clojure/tools/nrepl/middleware/interruptible_eval.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name. at clojure.lang.RT.load(RT.java:449) at clojure.lang.RT.load(RT.java:412) at clojure.core$load$fn__5448.invoke(core.clj:5866) at clojure.core$load.doInvoke(core.clj:5865) at clojure.lang.RestFn.invoke(RestFn.java:408) at clojure.core$load_one.invoke(core.clj:5671) at clojure.core$load_lib$fn__5397.invoke(core.clj:5711) at clojure.core$load_lib.doInvoke(core.clj:5710) at clojure.lang.RestFn.applyTo(RestFn.java:142) at clojure.core$apply.invoke(core.clj:632) at clojure.core$load_libs.doInvoke(core.clj:5749) at clojure.lang.RestFn.applyTo(RestFn.java:137) at clojure.core$apply.invoke(core.clj:632) at clojure.core$require.doInvoke(core.clj:5832) at clojure.lang.RestFn.invoke(RestFn.java:703) at figwheel_sidecar.repl$eval2185$loading__5340__auto____2186.invoke(repl.clj:1) at figwheel_sidecar.repl$eval2185.invoke(repl.clj:1) at clojure.lang.Compiler.eval(Compiler.java:6782) at clojure.lang.Compiler.eval(Compiler.java:6771) at clojure.lang.Compiler.load(Compiler.java:7227) ... 76 more Subprocess failed
It's not all bad though, if I run lien cljsbuild once
in the container, I get some
good news...
Compiling "resources/public/js/main.js" from ["src"]...
Successfully compiled "resources/public/js/main.js" in 2.4 seconds.
Nice.
Version mismatches & figwheel
My assumption here is that I needed to update/specify all of the correct versions for Clojure, ClojureScript, and Figwheel. I looked up a working example from this flappy bird demo.
diff --git a/project.clj b/project.clj
index 644deab..babc3f0 100644
--- a/project.clj
+++ b/project.clj
@@ -1,9 +1,8 @@
(defproject color-transit "0.1.0-SNAPSHOT"
- :dependencies [[org.clojure/clojure "1.7.0"]
- [org.clojure/clojurescript "1.7.170"]]
- :plugins [[lein-figwheel "0.5.0-1"]
- [lein-cljsbuild "1.1.2"]]
- :hooks [leiningen.cljsbuild]
+ :dependencies [[org.clojure/clojure "1.9.0"]
+ [org.clojure/clojurescript "1.10.312"]]
+ :plugins [[lein-cljsbuild "1.1.4" :exclusions [org.clojure/clojure]]
+ [lein-figwheel "0.5.16"]]
:clean-targets ^{:protect false} [:target-path "out" "resources/public/js"]
:cljsbuild {
:builds {
This ended up telling me that the app was running on port 3449
, so I needed to
udpate the command...
docker run -it --rm -v "$PWD":/usr/app -p 3449:3449 color-transit lein figwheel
Though this works, the issue is that it downloads all of the figwheel
dependencies
each time it runs (for the first time).
The trick is to add this to the Dockerfile
.
RUN lein figwheel :check-config
I found this looking around the source code looking for
"Figwheel: Cutting some fruit, just a sec..." and finding that I could run :check-config
to download all of the dependencies.
Aside: Apparently there's an entirely new version called figwheel-main
, but I'm not
going to go all the way over there just yet.
How long did all that take?
To answer one of the questions I asked myself in the beginning... getting a dev environment up and running for this took about two hours.
Getting a blog post out and running took another month.
The gh-pages
script
Looking at this, it's obviously a bunch of stuff that I had copy pasted from other places on the internet... all of the whitespace is a mixture of tabs and spaces- and it just kind of works well enough.
ClojureScript?
src/
└── color-transit
├── canvas.cljs
├── canvas_set.cljs
├── color.cljs
├── core.cljs
├── dims.cljs
└── interval.cljs
Most of the effor here was going back to get this running and operational, and
I also wanted to jump into the cljs
a bit to see if I can traverse it at all,
or if I could make some kind of substantial change and deploy it. But I don't
really want to be doing that. Let's dig into the meat of the "app."
JS Wrappers
interval
, canvas
, dims
... These are all wrappers around "native" JS methods
in order to have them appear to be, or operate as immutable values, like clojure
expects idiomatically.
For example:
(defn fill-rect
[ctx x0 y0 x1 y1]
(.fillRect ctx x0 y0 x1 y1)
ctx)
(defn ctx
"Apply f to the context of the canvas, return the canvas.
This is useful for chaining ctx methods.
(-> canvas
(ctx fill-style ...)
(ctx fill-rect ...))"
[canvas f & args]
(apply f (:ctx canvas) args)
canvas)
These make it so that it's easier to chain methods, and apply transformations to the canvas context in a real clojure-y way.
Functions that do mutation of global state, (like the window), have !
at the
end. For example full-screen!
or start-app!
.
The fun part
or, how this entire thing actually works.
The app's html is defined in public/index.html
, and when the it starts,
we create the canvas context using query->Canvas
, this is a container that
holds the 2d
context of the canvas
element in the DOM
, as well as the
actual element, and dimensions, defined as:
(defrecord Canvas [el ctx w h])
The above js wrapping functions, operate on Canvas
records.
When the app starts it creates a CanvasSet
, which links the Canvas
to the
color-sets
that it will be running through. The application state operates
pretty much solely on a CanvasSet
.
Startup
The app starts with a few pre-defined colors:
(let [colors [[0 10 0]
[200 155 255]
[40 40 40]
[255 0 0]
[0 255 255]
[100 233 67]]]
;; ...
)
We take these colors (all [r g b]
format), and randomize their order into num-sets
.
This is stored in the app-state
, and we can live inspect it using the lein repl
that
get started with figwheel
app.
dev:cljs.user=> (in-ns 'color-transit.core)
dev:color-transit.core=> (->> @app-state :canvas-sets first :color-sets (map :colors))
([[0 10 0] [100 233 67] [255 0 0] [0 255 255] [40 40 40] [200 155 255]]
[[200 155 255] [0 10 0] [40 40 40] [255 0 0] [100 233 67] [0 255 255]]
[[40 40 40] [0 255 255] [0 10 0] [200 155 255] [255 0 0] [100 233 67]])
In our start up we say we want 3
sets, and each one has a shuffle
-d list of the
original colors provided. The three sets correspond to three stops in the
generated gradient, and the colors that they will be transitioning to.
An initial color-set
looks like:
{ :colors colors
:color-queue [ ]
:current-color: (first colors) }
:colors
- are each of the colors in that one gradient step we'll be fading through,:color-queue
- a queue (FIFO), of colors that we'll be moving through in the fade,:current-color
- what color are we currently displaying.
Our canvas drawer (in core
) will only draw the current-color
, and we
pre-compute the queue
up front as infrequently as possible so that we
do as little work when drawing the gradient as possible.
All of that happens in color/compute-next-state
.
Computing the color transitios
compute-next-state
has two main branches of logic:
-
the first is when the queue is non-empty, we take the color at the head of the queue, and update
current-color
with it. This runs until the queue is drained. -
the second is if both the initial state, and happens after the queue is drained... we generate a new one! Given the current color, and the next one in the
colors
, and the number ofsteps
we want to be able to take from one color to the next, we compute them.
The maths
(defn delta [n1 n2 steps] (/ (- n2 n1) steps))
delta
is simplified version of what the app does, where this operates on
one number, the app works on the destructured [r g b]
.
(defn +delta [n n+ scalar]
(-> (* scalar n+)
(+ n)
Math/floor))
Similarly, +delta
increases n
by the delta we computed earlier, n+
,
scaled up by scalar
, and then rounds it down.
We use these two functions to create the color queue.
Most of everything else in the code is transforming the colors into the gradient and canvas, and making sure it's running at a specific interval in the browser.
Wrapping up
Originally, I wasn't sure if I'd want to change anything here, but after spending the time with the codebase, I'd like to make the grandient drawing reactive to user parameterization in the UI.
So eventually, there'll be a second post, and updates to the app, and a write up of the process.
...which will hopefully take less than a couple of months to finish...
Update
As of 2023, I have not come back to this, and I don't think I will.