git » homepage.git » commit ce52bc2

move to make+cmark-gfm

author Alan Dipert
2025-10-08 04:24:13 UTC
committer Alan Dipert
2025-10-08 04:24:13 UTC
parent 49ccd6c41a7100705bc8577f95f498e8ae1d80a8

move to make+cmark-gfm

.gitignore +2 -0
Makefile +82 -10
README.md +23 -0
md_export/AsyncAwaitGotchas.md +32 -0
md_export/CoffeeTime.md +27 -0
md_export/CoffeeTime/coffee_float_right_200px.jpg +0 -0
md_export/ConsultingPractice.md +15 -0
md_export/FairDivision.md +41 -0
md_export/GitOnSharedHost.md +20 -0
md_export/Home.md +39 -0
md_export/Home/headshot_2018_float_right_200px.jpg +0 -0
md_export/HomePhotos.md +34 -0
md_export/HomePhotos/Dockerfile +36 -0
md_export/HomePhotos/photoprism.png +0 -0
md_export/Lisp.md +9 -0
md_export/Lisp/CommonLisp.md +10 -0
md_export/Lisp/CommonLispIteration.md +129 -0
md_export/Lisp/GherkinHistory.md +85 -0
md_export/Lisp/GherkinHistory/IMG_2485.JPG +0 -0
md_export/Lisp/GherkinHistory/alan_wearing_gherkin_shirt.jpg +0 -0
md_export/PersonalBackground.md +3 -0
md_export/RandallRDipert.md +10 -0
md_export/TechSolutions.md +29 -0
md_export/TechWorks.md +40 -0
md_export/TechWorks/2015-04-20_ClojureWestBoot.pdf +0 -0
md_export/TechWorks/2020-01-28-RStudio-Conf-Integration-Testing-ePoster.pdf +0 -0
md_export/TechWorks/Dipert-FRP_in_ClojureScript_with_Javelin.pdf +0 -0
md_export/TechWorks/Dipert-ProgrammingWithValues.pdf +0 -0
md_export/TechWorks/jacl-demo-els-2020.pdf +0 -0
md_export/WellReadUndergrad.md +35 -0
md_export/WellReadUndergrad/phil-lt5.doc +0 -0
md_export/WellReadUndergrad/phil-lt5.docx +0 -0
tools/mdlink2html.awk +47 -0
tpl/foot.html +8 -0
tpl/head.html +22 -0
tpl/style.css +77 -0

diff --git a/.gitignore b/.gitignore
index e1f60ea..61351e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@
 auto-save-list
 tramp
 .\#*
+
+/out/
diff --git a/Makefile b/Makefile
index 367edb0..a64d929 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,86 @@
-.PHONY: deploy
+.PHONY: all assets clean deploy help tree check-git-clean
 
-PAGES=$(shell find . -maxdepth 1 -name '*.md' -exec basename '{}' ';')
-HTML=$(PAGES:%.md=OUT/%.html)
+SRC := md_export
+OUT := out
+HEAD := tpl/head.html
+FOOT := tpl/foot.html
+CSS := tpl/style.css
+MD2HTML ?= /usr/bin/cmark-gfm
+CMARK_FLAGS := --to html --extension table --validate-utf8
+DEPLOY_HOST ?= arsien23i2@dreamhost:tailrecursion.com/~alan
 
-all: $(HTML)
+MD_FILES := $(shell find $(SRC) -type f -name '*.md' | LC_ALL=C sort)
+HTML := $(patsubst $(SRC)/%.md,$(OUT)/%.html,$(MD_FILES))
 
-OUT/%.html: %.md template.html
-	pandoc --standalone --template=template.html --from=gfm+wikilinks_title_after_pipe --metadata title=$(basename $<) --metadata date="$(shell date -d @$(shell stat -c %Y $<))" --metadata date-meta=$(shell date -d @$(shell stat -c %Y $<) +%Y-%m-%d) --lua-filter=links-to-html.lua -o $@ $<
-	# Copy option resource directory for page (images, attachments, etc)
-	rsync --ignore-missing-args --delete -a $(basename $@) OUT
+all: check-git-clean $(OUT)/style.css $(HTML)
 
-deploy:
-	rsync -a OUT/ arsien23i2@dreamhost:tailrecursion.com/~alan
+$(OUT)/style.css: $(CSS)
+	@mkdir -p $(OUT)
+	@cp $(CSS) $@
+
+$(OUT):
+	@mkdir -p $(OUT)
+
+$(OUT)/%.html: $(SRC)/%.md $(HEAD) $(FOOT) tools/mdlink2html.awk | $(OUT)
+	@mkdir -p $(@D)
+	@awk -f tools/mdlink2html.awk $< > $@.rewritten.md
+	@if [ ! -x $(MD2HTML) ]; then \
+		echo "Missing $(MD2HTML). Build it with:"; \
+		echo "  git clone https://github.com/github/cmark-gfm vendor/cmark-gfm"; \
+		echo "  cmake -S vendor/cmark-gfm -B vendor/cmark-gfm/build"; \
+		echo "  cmake --build vendor/cmark-gfm/build"; \
+		echo "  cp vendor/cmark-gfm/build/src/cmark-gfm bin/"; \
+		echo "Then rerun make with MD2HTML=bin/cmark-gfm"; \
+		exit 1; \
+	fi
+	@$(MD2HTML) $(CMARK_FLAGS) $@.rewritten.md > $@.body.html
+	@css_prefix="$$(dirname "$@" | sed -e 's#^$(OUT)##' -e 's#^/##' -e 's#[^/][^/]*#../#g')"; \
+		build_user=$$(whoami); \
+		build_host=$$(hostname); \
+		day=$$(date '+%-d'); \
+		case $$day in 11|12|13) suffix=th ;; *1) suffix=st ;; *2) suffix=nd ;; *3) suffix=rd ;; *) suffix=th ;; esac; \
+		dow=$$(date '+%A'); \
+		month=$$(date '+%B'); \
+		year=$$(date '+%Y'); \
+		time=$$(date '+%-I:%M%p %Z'); \
+		git_sha=$$(git rev-parse --short HEAD); \
+		build_info="Built by $$build_user@$$build_host on $$dow, $$month $$day$$suffix $$year at $$time (git $$git_sha)"; \
+		sed -e "s|@CSS@|$${css_prefix}|g" -e "s|@BUILDINFO@|$${build_info}|g" $(HEAD) > $@.head.html; \
+		sed -e "s|@CSS@|$${css_prefix}|g" -e "s|@BUILDINFO@|$${build_info}|g" $(FOOT) > $@.foot.html; \
+		cat $@.head.html $@.body.html $@.foot.html > $@; \
+		rm -f $@.head.html $@.foot.html
+	@rm -f $@.rewritten.md $@.body.html
+
+assets: check-git-clean
+	@mkdir -p $(OUT)
+	@rsync -a --include='*/' --exclude='*.md' --exclude='*.MD' --prune-empty-dirs $(SRC)/ $(OUT)/
+
+deploy: assets all
+	@if [ -z "$(DEPLOY_HOST)" ]; then \
+		echo "DEPLOY_HOST is not set"; \
+		exit 1; \
+	fi
+	rsync -a $(OUT)/ $(DEPLOY_HOST)
+
+tree:
+	@printf 'Markdown sources: ' && find $(SRC) -type f -name '*.md' | wc -l
+	@printf 'HTML outputs:   ' && if [ -d $(OUT) ]; then find $(OUT) -type f -name '*.html' | wc -l; else echo 0; fi
+
+clean:
+	rm -rf $(OUT)
+
+help:
+	@echo 'Static site generator targets:'
+	@echo '  make assets   - copy non-Markdown assets into $(OUT)/'
+	@echo '  make          - build HTML pages (same as make all)'
+	@echo '  make deploy   - rsync $(OUT)/ to $$DEPLOY_HOST'
+	@echo '  make tree     - count source and generated files'
+	@echo '  make clean    - remove $(OUT)/'
+	@echo '  (requires clean git worktree before building)'
+
+check-git-clean:
+	@if [ -n "$$(git status --porcelain)" ]; then \
+		echo 'Refusing to build: git worktree has uncommitted changes.'; \
+		git status --short; \
+		exit 1; \
+	fi
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8d4fc09
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# Homepage static site generator
+
+This converts Markdown from `md_export/` to HTML in `out/` and can deploy the result to your shared host.
+
+## Usage
+make assets && make
+xdg-open out/index.html  # or open on macOS
+
+> Note: builds require a clean git worktree. Commit or stash changes before running make.
+
+## Deploy
+make deploy  # uses rsync to upload to your shared host defined in DEPLOY_HOST
+
+## Build cmark
+
+This project defaults to `/usr/bin/cmark-gfm`. If it is missing on your system you can build a local copy:
+
+git clone https://github.com/github/cmark-gfm vendor/cmark-gfm
+cmake -S vendor/cmark-gfm -B vendor/cmark-gfm/build
+cmake --build vendor/cmark-gfm/build
+cp vendor/cmark-gfm/build/src/cmark-gfm bin/
+
+Then rerun make with `MD2HTML=bin/cmark-gfm`.
diff --git a/md_export/AsyncAwaitGotchas.md b/md_export/AsyncAwaitGotchas.md
new file mode 100644
index 0000000..c5f01c5
--- /dev/null
+++ b/md_export/AsyncAwaitGotchas.md
@@ -0,0 +1,32 @@
+# AsyncAwaitGotchas
+Created Thursday 24 June 2021
+
+Recent versions of JavaScript include support for [async functions and the await keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). Below, I enumerate a few edge cases of ``async``/``await`` in Google Chrome that produce obscure errors, and their solutions. I'm using Chrome 91.0.4472.114. I don't know if these are known bugs, but they were confusing to me. I'm grateful to anyone with the time and interest to officially catalog and/or fix them.
+
+Uncaught SyntaxError: missing ) after argument list
+---------------------------------------------------
+
+This one can come up when you use await in a function where it's not allowed. The following code doesn't work because neither the outer function nor inner arrow function were decorated with the ``async`` keyword.
+
+	function foo(xs) { return xs.map(x => await x); }
+	VM856:1 Uncaught SyntaxError: missing ) after argument list
+
+You can still get the error even if the enclosing outer function is async:
+
+	async function foo(xs) { return xs.map(x => await x); }
+	VM867:1 Uncaught SyntaxError: missing ) after argument list
+
+I can imagine how the compiler could make the inner async work, since the inner arrow function doesn't [escape](https://en.wikipedia.org/wiki/Escape_analysis) the outer function. I haven't looked at the specification, but I assume it's not supposed to work. It's OK with me that it doesn't work, because if it did, the rules about when ``async`` works would become more complicated. The error message could be better, though.
+
+Uncaught SyntaxError: Missing } in template expression
+------------------------------------------------------
+
+This one must be a consequence of the way [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) and the ``await`` keyword interact:
+
+	function foo(x) { return `${await x}`; }
+	VM960:1 Uncaught SyntaxError: Missing } in template expression
+
+Contrary to the error message, the problem is unrelated to the template expression syntax. The problem is that the ``foo`` function needs to be ``async:``
+
+	async function foo(x) { return `${await x}`; }
+
diff --git a/md_export/CoffeeTime.md b/md_export/CoffeeTime.md
new file mode 100644
index 0000000..3e5b2d0
--- /dev/null
+++ b/md_export/CoffeeTime.md
@@ -0,0 +1,27 @@
+# CoffeeTime
+Created Monday 23 November 2020
+
+![](./CoffeeTime/coffee_float_right_200px.jpg)
+
+Would you like to meet with me? Let's do it! I try to make time to reconnect with old friends and to make new ones.
+
+
+* Send an e-mail to [alan@tailrecursion.com](mailto:alan@tailrecursion.com) with a times that would work for you, or a few other times that you're available. I'll try to make something work. I'm in the US Pacific time zone.
+* Zoom and Google Meet are my go-to tools but I'm open to trying others.
+* If we haven't met before, please suggest a discussion topic in your e-mail.
+
+
+I'm open to discussing most subjects, but my own personal expertise and current interests include the following:
+
+
+* Common Lisp
+* Clojure
+* Build tools and processes
+* R and Shiny
+* Compilers and interpreters
+* Amateur radio
+* JavaScript
+* Drones
+* Lawn care
+
+
diff --git a/md_export/CoffeeTime/coffee_float_right_200px.jpg b/md_export/CoffeeTime/coffee_float_right_200px.jpg
new file mode 100644
index 0000000..13d12be
Binary files /dev/null and b/md_export/CoffeeTime/coffee_float_right_200px.jpg differ
diff --git a/md_export/ConsultingPractice.md b/md_export/ConsultingPractice.md
new file mode 100644
index 0000000..7468f8c
--- /dev/null
+++ b/md_export/ConsultingPractice.md
@@ -0,0 +1,15 @@
+# ConsultingPractice
+**As of November 2020 I'm booked and unavailable to start new projects, but please don't hesitate to e-mail me anyway at [alan@tailrecursion.com](mailto:alan@tailrecursion.com). I'll be happy to make your acquaintance and get back to you when I become available.**
+
+As of June 2020 I am an independent software consultant specializing in [Clojure](https://clojure.org/), [ClojureScript](https://clojurescript.org/), JavaScript, and web application design and architecture. I have over a decade of experience and have successfully built and operated applications in a wide variety of challenging technical and business environments. My practice is defined by the following mission statement:
+
+
+1. **Deliver** reliable software solutions regularly and rapidly to my clients.
+2. **Collaborate** whenever possible to avoid pitfalls and leverage existing expertise.
+3. **Communicate** continuously to ensure solutions meet all expectations and requirements.
+4. **Resolve**, immediately, any operational difficulties associated with delivered software.
+5. **Improve** techniques and methodologies continuously as lessons are learned.
+
+
+If you are interested in contracting my services and would like to discuss an engagement, please don't hesitate to send me an e-mail at [alan@tailrecursion.com](mailto:alan@tailrecursion.com). I look forward to hearing from you!
+
diff --git a/md_export/FairDivision.md b/md_export/FairDivision.md
new file mode 100644
index 0000000..5443e87
--- /dev/null
+++ b/md_export/FairDivision.md
@@ -0,0 +1,41 @@
+# FairDivision
+Created Monday 09 November 2020
+
+I cut, you choose
+-----------------
+
+The "[I cut, you choose](https://en.wikipedia.org/wiki/Divide_and_choose)" method of dividing something fairly between two people is well known. Given some divisible resource, like a pizza, two people may divide the resource using the following protocol:
+
+
+1. One person is chosen at random to cut the pizza in two pieces.
+2. The person who did not cut takes a piece.
+3. The person who cut takes the remaining piece.
+
+
+This protocol is easy to remember and to explain. It is also efficient in the sense that the minimal number of pieces — two — are created.
+
+If you haven't before, I encourage you to now take a moment to consider how a resource could be divided fairly between any number people, not just two.
+
+I thought about this recently myself when I needed to divide a large cookie between myself, my wife, and our 4-year-old daughter. I excused myself to think about how to proceed. When I returned, the cookie had been eaten! That's one protocol I *don't* recommend.
+
+The Fink protocol
+-----------------
+
+Later (and after eating an entire large cookie without even telling my family about it) I sat down to research the problem. I consulted with my friend Micha Niskin and he suggested the following technique he had devised, which I discovered later is known as the [Fink protocol:](https://en.wikipedia.org/wiki/Fink_protocol)
+
+
+1. If there are two people, perform "I cut, you choose".
+2. If there are three people, two are chosen randomly. The two randomly chosen people perform "I cut, you choose".
+3. The two people with a piece each cut their piece into thirds.
+4. The third person without any pieces yet chooses one piece from each of the two with pieces.
+5. All people now have two pieces each.
+6. If a fourth person joins, each of the three with pieces cut each of their pieces in two.
+7. The fourth person without any pieces yet chooses one piece from each of the three with pieces.
+8. All people now have three pieces each.
+9. ...and so on.
+
+
+The biggest drawback of the Fink protocol is that each person ends up with n-1 pieces, where n is the number of people, instead of a single piece of size 1/n. On the other hand, like "I cut, you choose", this protocol is easy to remember, and is almost as easy to explain, even to children.
+
+There are actually many approaches to problem, all with various tradeoffs. I didn't find any of them nearly as easy to remember or explain (especially to *hungry* children!) as Fink, but if you want to do your own research, [Fair cake-cutting on Wikipedia](https://en.wikipedia.org/wiki/Fair_cake-cutting) is where I started.
+
diff --git a/md_export/GitOnSharedHost.md b/md_export/GitOnSharedHost.md
new file mode 100644
index 0000000..b4e8bc2
--- /dev/null
+++ b/md_export/GitOnSharedHost.md
@@ -0,0 +1,20 @@
+# GitOnSharedHost
+Created Friday 02 December 2022
+
+Recently I set up read-only Git repository hosting on [Dreamhost](https://www.dreamhost.com/) for my project [JACL](https://tailrecursion.com/JACL). This was kind of tricky, so here are the steps:
+
+
+1. In the Dreamhost control panel, [set up SSH for a user](https://help.dreamhost.com/hc/en-us/articles/216041267-SSH-overview). 
+2. Once you can log in with a password, you probably want to [set up key-based authentication](https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server).
+3. On the remote host, ``cd`` into the directory corresponding to your hosted domain.
+4. ``mkdir your-repo.git``
+5. ``cd your-repo.git``
+6. ``git init --bare``
+7. ``cp hooks/post-update.sample hooks/post-update``
+8. Back in your local repo, run a command like the following: ``git remote add origin ssh://user@northbend.dreamhost.com:/home/user/example.com/your-repo.git`` substituting ``user``, ``northbend.dreamhost.com``, and ``example.com`` with your particulars.
+9. ``git push -u origin master``
+10. Now, you should be able to ``git push`` and anyone on the Internet should be able to ``git clone https://example.com/your-repo.git``
+
+
+
+
diff --git a/md_export/Home.md b/md_export/Home.md
new file mode 100644
index 0000000..a2992e7
--- /dev/null
+++ b/md_export/Home.md
@@ -0,0 +1,39 @@
+# Alan's Homepage
+![](./Home/headshot_2018_float_right_200px.jpg)
+
+Hello, and welcome to the personal [homepage](https://en.wikipedia.org/wiki/Home_page) and [wiki](https://en.wikipedia.org/wiki/Wiki) of Alan Dipert!
+
+
+* [ConsultingPractice](./ConsultingPractice.md) is about the professional software engineering consulting services I provide.
+* [PersonalBackground](./PersonalBackground.md) is about my personal history and interests.
+* [TechWorks](./TechWorks.md) is a list of technical presentations, workshops, papers, and other works I've created or helped to create.
+
+
+The best way to contact me is by email at [alan@tailrecursion.com](mailto:alan@tailrecursion.com). I also maintain a [CoffeeTime](./CoffeeTime.md) to meet with folks. In addition, you can find me on the follow social media websites:
+
+
+* [Twitter](https://twitter.com/alandipert) is where I post infrequently, usually about technology.
+* [GitHub](https://github.com/alandipert/) is where I typically collaborate on open source software and [star interesting projects](https://github.com/alandipert?tab=stars) I run across.
+* [LinkedIn](https://www.linkedin.com/in/alandipert/) is where I maintain my work history and professional connections.
+* [Hacker News](https://news.ycombinator.com/user?id=wooby) is where I occasionally submit links and engage The Internet in discussion.
+
+
+Updates
+-------
+| Date       | Note                                                                                                                                       |
+|:-----------|:-------------------------------------------------------------------------------------------------------------------------------------------|
+| 2022-12-02 | Added [GitOnSharedHost](./GitOnSharedHost.md) to describe setting up a read-only Git repo on Dreamhost.                                    |
+| 2021-07-01 | Added [HomePhotos](./HomePhotos.md) to document family media archival strategy.                                                            |
+| 2021-06-24 | Added [AsyncAwaitGotchas](./AsyncAwaitGotchas.md), about obscure async/await JavaScript errors in Google Chrome.                           |
+| 2021-02-05 | Added SciCloj presentation comparing Common Lisp to Clojure to [TechWorks](./TechWorks.md).                                                |
+| 2020-12-31 | Added to [Lisp:CommonLispIteration](./Lisp/CommonLispIteration.md), added ClojureScript Podcast appearance to [TechWorks](./TechWorks.md). |
+| 2020-12-15 | Added [TechSolutions](./TechSolutions.md) to record solutions to software problems and errors I encounter.                                 |
+| 2020-11-23 | Added [CoffeeTime](./CoffeeTime.md).                                                                                                       |
+| 2020-11-23 | Added [Lisp:CommonLispIteration](./Lisp/CommonLispIteration.md).                                                                           |
+| 2020-11-21 | Customized Zim export HTML/CSS template.                                                                                                   |
+| 2020-11-13 | Added [Lisp:GherkinHistory](./Lisp/GherkinHistory.md).                                                                                     |
+| 2020-11-10 | Added three [Cognicast](https://www.cognitect.com/cognicast/) appearances to [TechWorks](./TechWorks.md).                                  |
+| 2020-11-09 | Added [FairDivision](./FairDivision.md).                                                                                                   |
+| 2020-11-02 | Deployed new [Zim](https://zim-wiki.org/)-based site and established redirects from previous URLs.                                         |
+
+
diff --git a/md_export/Home/headshot_2018_float_right_200px.jpg b/md_export/Home/headshot_2018_float_right_200px.jpg
new file mode 100644
index 0000000..dc43b54
Binary files /dev/null and b/md_export/Home/headshot_2018_float_right_200px.jpg differ
diff --git a/md_export/HomePhotos.md b/md_export/HomePhotos.md
new file mode 100644
index 0000000..26ffb6f
--- /dev/null
+++ b/md_export/HomePhotos.md
@@ -0,0 +1,34 @@
+# HomePhotos
+Created Thursday 01 July 2021
+
+Recently I migrated our family off of Amazon Prime Photos after many years of use. I found the cost to maintain videos prohibitive, and had also become increasingly concerned by how difficult getting things *out* of Prime Photos was becoming. Plus, like, privacy and stuff.
+
+![](./HomePhotos/photoprism.png)
+
+My original plan was to develop my own minimal management software, so that I wouldn't be subject even to open source software churn. Since I plan on maintaining this photo archive for the rest of my life, I figured it made sense to double down on technique. After some prototyping I realized I'd bit off more than I could chew, despite the number of great libraries for working with media and metadata that there are. I was fortunate to then run across [PhotoPrism](https://photoprism.app/), which does what I'd hoped to do, and more, and seems to be very well designed. And, they're working on face recognition, which is the only thing I really miss from Prime Photos.
+
+I run PhotoPrism on Fedora on my old Lenovo T430s laptop with a 1TB SSD. I was able to import some 185gb of content - ~15,000 photos and ~300 videos - in about a day, with laptop fans blasting and all cores saturated. In addition, we're now running:
+
+
+* [PhotoSync](https://www.photosync-app.com/home.html) on Android and iOS to sync photos from devices to PhotoPrism.
+* Docker to run PhotoPrism in docker-compose, as a systemd unit ([tutorial](https://techoverflow.net/2020/10/24/create-a-systemd-service-for-your-docker-compose-project-in-10-seconds/)).
+* [Tailscale VPN](https://tailscale.com/) so photos can be accessed and uploaded even while mobile.
+* [Duplicity](http://duplicity.nongnu.org/) in cron (via [flock](https://linux.die.net/man/1/flock)) to back up regularly to AWS S3 Glacier Deep Archive. This is currently the cheapest way to store things in AWS long term. I started with [this tutorial](https://stuff.technology/making-offsite-backups-for-home-convenient-inexpensive-and-secure-4654680333e8) but ended up making my own Dockerfile and using a newer duplicity/requirements.txt:
+	* [Dockerfile](./HomePhotos/Dockerfile)
+	* [requirements.txt](https://gitlab.com/duplicity/duplicity/-/blob/d52fd888100b8fb105c696b38f2884c8173eb412/requirements.txt)
+	* I really like the look of [tarsnap](https://www.tarsnap.com/) but I find it a little too expensive for this use case. As far as I can tell, the killer feature it offers (beyond encryption) is incremental deletion. I hope to use it for system backups in the near future, though. I think it would also be good for storing recent security camera footage from [ZoneMinder](https://zoneminder.com/), once I get that set up.
+* [healthchecks.io](https://healthchecks.io/) for heartbeats on the cron job.
+
+
+Other options
+-------------
+
+Getting this all going took a few hours and required a decent amount of Linux and Docker knowledge. For less effort, you might consider a [Synology NAS](https://www.synology.com/en-us). I've heard good things about Synology from various nerds I respect. Other options I'm aware of for running "home cloud" software on commodity hardware include [ownCloud](https://owncloud.com/) and [Unraid](https://unraid.net/).
+
+Future plans
+------------
+
+In the future, I look forward to working with PhotoPrism more, and maybe even getting involved with its development. I'm interested in adding OCR indexing and full text search to navigate the many documents I photograph with my phone. In my prototype I had some success with [Tesseract](https://github.com/nguyenq/tess4j) and [SQLite FTS 5](https://sqlite.org/fts5.html) for this purpose. I would also enjoy a more powerful textual search language.
+
+A local healthcheck service would be nice too. I'd like to see a [ESP8266](https://en.wikipedia.org/wiki/ESP8266) or something somewhere in my office with a green LED glowing to know everything is fine™.
+
diff --git a/md_export/HomePhotos/Dockerfile b/md_export/HomePhotos/Dockerfile
new file mode 100644
index 0000000..d521eca
--- /dev/null
+++ b/md_export/HomePhotos/Dockerfile
@@ -0,0 +1,36 @@
+FROM python:3-alpine
+
+ARG DUPLICITY_URL="https://launchpad.net/duplicity/0.8-series/0.8.20/+download/duplicity-0.8.20.tar.gz"
+
+ENV SRC='/mnt/backup/src' \
+    MANPATH='MANPATH=/usr/local/share/man' \
+    PAGER='less'
+
+COPY requirements.txt /tmp/
+
+# Mixed Dev and Runtime dependencies
+RUN apk add --no-cache \
+        build-base \
+        ca-certificates \
+        gettext \
+        libffi-dev \
+        librsync-dev \
+        gnupg \
+        mandoc \
+        openssl-dev \
+        tzdata \
+        musl-dev \
+        python3-dev \
+        libffi-dev \
+        openssl-dev \
+        libxml2-dev \
+        libxslt-dev \
+        cargo \
+    && pip install -r /tmp/requirements.txt \
+       $DUPLICITY_URL \
+    && rm /tmp/requirements.txt
+
+# Cache and GPG key
+VOLUME [ "/root" ]
+WORKDIR /root
+CMD ["duplicity"]
diff --git a/md_export/HomePhotos/photoprism.png b/md_export/HomePhotos/photoprism.png
new file mode 100644
index 0000000..dc2bed9
Binary files /dev/null and b/md_export/HomePhotos/photoprism.png differ
diff --git a/md_export/Lisp.md b/md_export/Lisp.md
new file mode 100644
index 0000000..5fbde47
--- /dev/null
+++ b/md_export/Lisp.md
@@ -0,0 +1,9 @@
+# Lisp
+Created Thursday 12 November 2020
+
+I have been interested in all things Lisp since around 2009, when I learned Clojure.
+
+
+* [Lisp:GherkinHistory](./Lisp/GherkinHistory.md) provides context around an interpreter I wrote in 2013 that was extremely gratifying. 
+
+
diff --git a/md_export/Lisp/CommonLisp.md b/md_export/Lisp/CommonLisp.md
new file mode 100644
index 0000000..178fa30
--- /dev/null
+++ b/md_export/Lisp/CommonLisp.md
@@ -0,0 +1,10 @@
+# CommonLisp
+Created Friday 13 November 2020
+
+I've been tinkering with Common Lisp since about 2010 but did not study it in earnest until around 2016. 
+
+
+* In 2020 I shared an implementation I've been working on, [JavaScript Assisted Common Lisp (JACL)](https://tailrecursion.com/JACL/).
+* I wrote up a bunch of different ways to achieve [CommonLispIteration](./CommonLispIteration.md).
+
+
diff --git a/md_export/Lisp/CommonLispIteration.md b/md_export/Lisp/CommonLispIteration.md
new file mode 100644
index 0000000..70ab944
--- /dev/null
+++ b/md_export/Lisp/CommonLispIteration.md
@@ -0,0 +1,129 @@
+# Common Lisp iteration
+Created Monday 23 November 2020
+
+Each of the following definitions of a [factorial](https://en.wikipedia.org/wiki/Factorial) function demonstrate a way to [iterate](https://en.wikipedia.org/wiki/Iteration#Computing) in [Common Lisp](https://en.wikipedia.org/wiki/Common_Lisp), with brief notes. I hope that by demonstrating many different ways that the same thing can be written, you can develop a sense for the character of the constructs afforded by the language, and of the variety of possible styles. Common Lisp is famously syntactically extensible via [macros](https://en.wikipedia.org/wiki/Common_Lisp#Macros), so keep in mind that my examples are by no means the *only* ways to iterate.
+
+For further reading on the iteration and control structures of Common Lisp, I heartily recommend:
+
+
+* [Chapter 7](http://www.gigamonkeys.com/book/macros-standard-control-constructs.html) and [Chapter 22](http://www.gigamonkeys.com/book/loop-for-black-belts.html) of [Practical Common Lisp](https://amzn.to/3nOWKa2) by Peter Siebel.
+* A reasonably-priced used copy of [ANSI Common Lisp](https://amzn.to/2UUTfm3) by Paul Graham.
+
+
+*Note: several of the examples return nonsensical results for negative inputs. The addition of ``(assert (not (minusp n)) ``or similar is a good idea, but I have omitted it here for clarity.*
+
+DOTIMES
+-------
+	(defun factorial-dotimes (n &aux (prod 1))
+	  (dotimes (i n prod)
+	    (setq prod (* prod (1+ i)))))
+
+
+* [``&aux`` lambda list keyword](http://www.lispworks.com/documentation/HyperSpec/Body/03_dae.htm) names a local variable ``prod``. [``LET``](http://www.lispworks.com/documentation/HyperSpec/Body/s_let_l.htm) could also be used for this purpose, but at the cost of more indentation.
+* [``DOTIMES``](http://www.lispworks.com/documentation/lw50/CLHS/Body/m_dotime.htm) binds ``i`` successively from 0 to 1-n and finally evaluates to ``prod``.
+
+
+DO
+--
+	(defun factorial-do (n)
+	  (do ((i 1 (1+ i))
+	       (prod 1 (* prod i)))
+	      ((> i n) prod)))
+
+
+* [``DO``](http://www.lispworks.com/documentation/lw50/CLHS/Body/m_do_do.htm) binds ``i`` to 1 and then to (1+ i) in subsequent iterations. ``prod`` is bound first to 1 and then to ``(* prod i)`` in subsequent iterations.
+* When the test clause ``(> i n)`` becomes true, ``prod`` is returned. Contrast with the test clause of ``for`` loops in other languages, which terminate the loop when they become *false*.
+* I like the way Paul Graham explains ``DO ``and`` DO*`` in [ANSI Common Lisp](https://amzn.to/2UUTfm3).
+
+
+LOOP
+----
+	(defun factorial-loop (n)
+	  (loop
+	     for i from 1 to n
+	     for prod = 1 then (* prod i)
+	     finally (return prod)))
+
+
+* ``i`` is bound from 1 to ``n`` inclusive.
+* ``prod`` is bound to 1 and then ``(* prod i)`` in subsequent iterations in a manner similar to ``DO``.
+* In the ``finally`` clause, ``prod`` is returned by [``RETURN``](http://www.lispworks.com/documentation/lw60/CLHS/Body/m_return.htm#return) once iteration is complete. The [``BLOCK``](http://www.lispworks.com/documentation/lw60/CLHS/Body/s_block.htm#block) named NIL established by ``LOOP`` is the point of return.
+* [``LOOP``](http://www.lispworks.com/documentation/lw50/CLHS/Body/m_loop.htm) supports a comprehensive iteration and accumulation [DSL](https://en.wikipedia.org/wiki/Domain-specific_language). [Chapter 22](http://www.gigamonkeys.com/book/loop-for-black-belts.html) of [Practical Common Lisp](https://amzn.to/3nOWKa2) offers a great introduction.
+
+
+The preceding example demonstrates the "extended" form of ``LOOP``. There's also "simple" form:
+
+	(defun factorial-simple-loop (n &aux (i 0) (prod 1))
+	  (loop
+	    (when (eql i n)
+	      (return prod))
+	    (setq prod (* prod (incf i)))))
+
+Recursion
+---------
+	(defun factorial-recursive (n)
+	  (if (zerop n)
+	      1
+	      (* n (factorial-recursive (1- n)))))
+
+
+* ``FACTORIAL-RECURSIVE`` calls itself, but when ``n`` exceeds the maximum stack size supported by the implementation, an error is signaled.
+
+
+	(defun factorial-tail-recursive (n)
+	  (labels ((recur (n prod)
+	             (if (zerop n)
+	                 prod
+	                 (recur (1- n) (* n prod)))))
+	    (recur n 1)))
+
+
+* ``FACTORIAL-TAIL-RECURSIVE ``does not call itself directly.
+* Instead, it defines with [``LABELS``](http://www.lispworks.com/documentation/HyperSpec/Body/s_flet_.htm) an internal and recursive helper function, ``recur``.
+* recur [calls itself in tail position](https://en.wikipedia.org/wiki/Tail_call) and the stack never overflows in implementations that implement tail-call elimination.
+
+
+	(defun factorial-tail-recursive-opt (n &optional (prod 1))
+	  (if (zerop n)
+	      prod
+	    (factorial-tail-recursive-opt (1- n) (* n prod))))
+
+
+
+* ``FACTORIAL-TAIL-RECURSIVE-OPT`` is also tail recursive, but uses the ``&OPTIONAL`` lambda list keyword to maintain ``prod`` across iterations. This approach has the downside of exposing ``prod`` as part of the public interface of the function. Arguably, ``prod`` is an implementation detail, best kept internal.
+
+
+PROG
+----
+	(defun factorial-prog (n)
+	  (prog ((i 0) (prod 1))
+	   begin
+	   (when (eql i n)
+	     (return prod))
+	   (setq prod (* prod (incf i)))
+	   (go begin)))
+
+
+* PROG supports both declaring local lexical variables (``i`` and ``prod``) and naming GO tags (``begin``).
+* ``begin`` names a label within the *implicit ``TAGBODY``* enclosed by ``PROG`` that may be jumped to.
+* [``WHEN``](http://www.lispworks.com/documentation/HyperSpec/Body/m_when_.htm) ``i`` is [``EQL``](http://www.lispworks.com/documentation/HyperSpec/Body/f_eql.htm) to ``n``, [``RETURN``](http://www.lispworks.com/documentation/lw60/CLHS/Body/s_ret_fr.htm) returns ``prod``.
+* [``GO``](http://www.lispworks.com/documentation/HyperSpec/Body/s_go.htm) jumps to ``begin``.
+
+
+TAGBODY
+-------
+	(defun factorial-tagbody (n &aux (i 0) (prod 1))
+	  (tagbody
+	     begin
+	     (when (eql i n)
+	       (return-from factorial-tagbody prod))
+	     (setq prod (* prod (incf i)))
+	     (go begin)))
+
+
+* [``TAGBODY``](http://www.lispworks.com/documentation/HyperSpec/Body/s_tagbod.htm) is the most general but also the lowest-level and most verbose iteration construct.
+* [``&aux`` lambda list keyword](http://www.lispworks.com/documentation/HyperSpec/Body/03_dae.htm) names local variables ``i`` and ``prod``, initializing them to 0 and 1, respectively.
+* [``WHEN``](http://www.lispworks.com/documentation/HyperSpec/Body/m_when_.htm) ``i`` is [``EQL``](http://www.lispworks.com/documentation/HyperSpec/Body/f_eql.htm) to ``n``, [``RETURN-FROM``](http://www.lispworks.com/documentation/lw60/CLHS/Body/s_ret_fr.htm) returns ``prod`` from the [``BLOCK``](http://www.lispworks.com/documentation/lw60/CLHS/Body/s_block.htm#block) named after the function by [``DEFUN``](http://www.lispworks.com/documentation/lw60/CLHS/Body/m_defun.htm).
+* [``GO``](http://www.lispworks.com/documentation/HyperSpec/Body/s_go.htm) jumps to ``begin``.
+
+
diff --git a/md_export/Lisp/GherkinHistory.md b/md_export/Lisp/GherkinHistory.md
new file mode 100644
index 0000000..87f4f4a
--- /dev/null
+++ b/md_export/Lisp/GherkinHistory.md
@@ -0,0 +1,85 @@
+# GherkinHistory
+Created Thursday 12 November 2020
+
+[Gherkin](https://github.com/alandipert/gherkin) is an interpreter I wrote in bash in 2013 for a Clojure-inspired dialect of [Lisp](../Lisp.md). Gherkin was the most sophisticated Lisp implementation I had attempted up to that point. I  announced Gherkin during a [lightning talk at Clojure/conj 2013](https://www.youtube.com/watch?v=bmHTFo2Rf2w#t=28m55s). Working on and sharing Gherkin brought me great joy, and inspired others in ways that continue to inspire *me*. Gherkin is one of the most gratifying projects I've ever worked on, and the experience continues to pay dividends.
+
+So, you want to be a Lisp hacker...
+-----------------------------------
+
+Before starting on Gherkin, I had long nursed an interest in Lisp implementation [wizardry](http://www.catb.org/jargon/html/H/heavy-wizardry.html). After learning Clojure around 2009, I began work at Relevance (now [Cognitect](https://www.cognitect.com/)) where I was presented with opportunities to make small contributions to Clojure itself. At Relevance, I had the great fortune of being a fly on the wall during discussions between experts including Rich Hickey, the creator of Clojure, about exciting and mysterious aspects of language design and implementation.
+
+While my position at Relevance afforded me a front row seat to the business of language development, I was still a relatively junior programmer, and I'd never gotten my own language to a state of anything close to completion. I had been programming long enough to develop an intuition about how things like lexical scope should work, but my understanding of how such things were actually *implemented* was fuzzy at best.
+
+In a quest to become a **Real Lisp Hacker**, I bounced around Google search results and a small friend group of kindred spirits for a couple of years and found myself reading stuff like the following:
+
+
+* [Closure conversion: How to compile lambda,](http://matt.might.net/articles/closure-conversion/) a blog post by [Matt Might](http://matt.might.net/)
+* [Paradigms of Artificial Intelligence (PAIP),](https://amzn.to/3px57ZJ) a book by [Peter Norvig.](https://norvig.com/) This book is now also [freely available](https://github.com/norvig/paip-lisp) online.
+* [(How to Write a (Lisp) Interpreter (in Python))](https://norvig.com/lispy.html), a page by Peter Norvig
+* [Henry Baker's Archive of Research Papers](https://web.archive.org/web/20190927121406/http://home.pipeline.com/~hbaker1/)
+
+
+Of the stuff I read, [PAIP](https://amzn.to/3px57ZJ) probably propelled me the furthest along, but I struggled to develop a really solid comprehension of the basic implementation mechanics of things like closures. In retrospect, this is almost surely because I was experimenting in Clojure, but the examples were in [Common Lisp](./CommonLisp.md), a language I didn't know well.
+
+In the fall of 2013, in the month before Clojure/conj, I ran across [awklisp](https://github.com/darius/awklisp) by [Darius Bacon](http://wry.me/~darius/). I became obsessed with it.
+
+awklisp had a few properties unique among available implementations at the time. I believe this set of properties made it especially compelling to me as a learning aid:
+
+
+1. It was not written in C. This kept the code small and focused.
+2. It consisted of only 500 lines of code, all on one page. It was possible for me to understand the whole thing at one time.
+3. It included a mark-and-sweep garbage collector, and so illuminates the same problems of memory management confronted by Lisp's inventors. This is something most Lisp interpreters written in high-level languages do not tackle.
+4. It was written in a "lower level" language than Lisp and implemented a call stack. The emergence of Lisp is thus striking, and the mechanics of function calls are exposed.
+
+
+After messing with awklisp, I had the idea to write something like it, but in a different language. Bash is what I decided on, and Gherkin was born. Most of Gherkin was written in the week preceding Clojure/conj 2013.
+
+A pickle is plucked
+-------------------
+
+I picked bash because I knew it would be a real challenge, and boy, was I not disappointed! The reader, the first piece I wrote, was especially challenging because Lisp syntax involves various characters that have special meaning in a shell context, like ``*``. I got hung up many times by things like the differences between " and ', the consequences of various options to set, and bash ``eval``.
+
+Once I had the reader basically working I started to gain serious momentum. I felt I was over the bash syntax/craziness hump. I extended awklisp's memory model to account for more data types. awklisp only had conses, symbols, and numbers; Gherkin had these, and also arrays, strings, and closures. Objects were represented as bash strings that start with a special marker character, followed by a type tag, followed by an index into the heap for objects of that type, followed by a payload. Interestingly, because Gherkin had its own memory model and heap, and because Gherkin was larger than any C program I'd written, I experienced real pointer debugging for the first time - in bash.
+
+Implementing closures presented a special opportunity for programming skill growth. Before this work, calling conventions, memory models, and closure semantics were topics I could hand-wave about but did not understand deeply. After this work, I reached a new, palpable level of understanding. The moment I reached this new level of understanding is unforgettable. I think (I hope!) I've grown a lot in various ways over my years of programming, but never so much, and in so little time.
+
+My resulting tighter grasp on closure implementation and memory management primed me for work with and on [R](https://en.wikipedia.org/wiki/R_(programming_language)). The R language is implemented in C and features first-class lexical environments. I worked extensively with R and R extensions in C and C++ during my time at [RStudio](https://rstudio.com/). I'm positive I would not have been as successful with R were it not for my prior experience writing Gherkin.
+
+The Conj
+--------
+
+By the time I arrived at Clojure/conj, Gherkin was working well enough that I signed up to give a lightning talk about it. I was at the conference with my coworkers from LonoCloud, all Lisp and Clojure enthusiasts and afficionados themselves. They got a kick out of it and provided encouragement. One, [Joel Martin](https://twitter.com/bus_kanaka), was especially excited, and contributed ideas and code for a much cleaner reader.
+
+From my perspective, the presentation ([up on YouTube](https://www.youtube.com/watch?v=bmHTFo2Rf2w#t=28m55s)) was surreal. When I looked to the audience after moments I thought would elicit laughter, there was none. When I made what I thought was a serious observation, there was laughter! I feared I'd embarrassed myself. I was relieved to learn later from members of the audience that they thoroughly enjoyed themselves.
+
+Post-Conj developments
+----------------------
+
+After the talk, [Paula Gearon,](https://twitter.com/quoll) [Jeremy Heiler](https://twitter.com/jeremyheiler?lang=en), and [Devin Walters](https://twitter.com/devn) kindly contributed core functions. [Craig Andera](https://twitter.com/craigandera) even made an Emacs mode, [gherkin-mode.el](https://github.com/candera/gherkin-mode). I'm very grateful to these and my other friends for their interest, encouragement, and involvement.
+
+After a flurry of conference activity, progress slowed down. My stated ambition for the project, that it would replace bash, was never completely serious; ironically, I became so inured to bash that I lost what little motivation I did originally have to replace it. The evaluator and garbage collector also had serious flaws that would have required a lot of work to rectify. I think I was satisfied enough with myself for perceiving these flaws that I saw little additional value in addressing them. In 2015, after two years of quiet, I "archived" the project on GitHub.
+
+mal
+---
+
+[mal](https://github.com/kanaka/mal) or "make-a-lisp" by [Joel Martin](https://twitter.com/bus_kanaka) is a Clojure-inspired Lisp interpreter and much, *much* more. Joel started by writing an interpreter in GNU Make shortly after I showed him Gherkin at Clojure/conj. His choice of Make was audacious compared even to my choice of bash. Then he took a huge step further, and codified the interpreter development experience into a structured, gamified series of language-agnostic steps. Thanks to Joel, anyone who wants to make a Lisp for any language has resources to start from that exceed even awklisp in educational value.
+
+I highly recommend Joel's talk on YouTube, [Achievement Unlocked: A Better Path to Language Learning](https://www.youtube.com/watch?v=lgyOAiRtZGw) if you want to learn more about his fantastic project.
+
+Other related projects
+----------------------
+
+I'm aware of these projects that were inspired by or otherwise related to Gherkin. If you know of others, please let me know at [alan@tailrecursion.com](mailto:alan@tailrecursion.com) and I will happily list them below.
+
+
+* [timl](https://github.com/tpope/timl) by [Tim Pope](https://tpo.pe/) is an impressive Clojure-like language that *compiles* to VimL. It is a much more sophisticated and comprehensive effort than Gherkin. Tim [was moved](https://twitter.com/tpope/status/1202261256859729920) to create timl after seeing my lightning talk.
+* [Fleck](https://github.com/chr15m/flk) by [Chris McCormick](https://mccormick.cx/) is "a Clojure-like LISP that runs wherever Bash is".
+* Andy Chu [reported on Twitter](https://twitter.com/oilshellblog/status/1327317599558897666) that he used Gherkin at one point [as part of the tests](https://t.co/ud7Uf3bhVf?amp=1) for the parser of his [oil shell](https://www.oilshell.org/).
+
+
+Proper BDFL attire
+------------------
+
+Clinton Dreisbach made me an awesome Gherkin shirt in December of 2013, at the height of Gherkin-mania. Here I am modeling it. Thanks Clinton!
+![](./GherkinHistory/alan_wearing_gherkin_shirt.jpg)
+
diff --git a/md_export/Lisp/GherkinHistory/IMG_2485.JPG b/md_export/Lisp/GherkinHistory/IMG_2485.JPG
new file mode 100644
index 0000000..9bb297b
Binary files /dev/null and b/md_export/Lisp/GherkinHistory/IMG_2485.JPG differ
diff --git a/md_export/Lisp/GherkinHistory/alan_wearing_gherkin_shirt.jpg b/md_export/Lisp/GherkinHistory/alan_wearing_gherkin_shirt.jpg
new file mode 100644
index 0000000..bf98015
Binary files /dev/null and b/md_export/Lisp/GherkinHistory/alan_wearing_gherkin_shirt.jpg differ
diff --git a/md_export/PersonalBackground.md b/md_export/PersonalBackground.md
new file mode 100644
index 0000000..a5450cf
--- /dev/null
+++ b/md_export/PersonalBackground.md
@@ -0,0 +1,3 @@
+# PersonalBackground
+TODO
+
diff --git a/md_export/RandallRDipert.md b/md_export/RandallRDipert.md
new file mode 100644
index 0000000..d387965
--- /dev/null
+++ b/md_export/RandallRDipert.md
@@ -0,0 +1,10 @@
+# RandallRDipert
+Created Tuesday 17 November 2020
+
+Randall Roy Dipert (1951-2019) was my father. This page honors his memory and legacy.
+
+
+* [Randall Dipert on Wikipedia](https://en.wikipedia.org/wiki/Randall_Dipert)
+* [WellReadUndergrad](./WellReadUndergrad.md) is a list of philosophy readings my dad suggested for undergraduate students.
+
+
diff --git a/md_export/TechSolutions.md b/md_export/TechSolutions.md
new file mode 100644
index 0000000..1fc9141
--- /dev/null
+++ b/md_export/TechSolutions.md
@@ -0,0 +1,29 @@
+# TechSolutions
+Created Tuesday 15 December 2020
+
+Errors messages I've run across and how I resolved them.
+
+#### Google Meet in Google Chrome on Fedora 32 (2021-06-04)
+
+**Error:** Can't present screen
+**Solution:** Navigate to <chrome://flags> and enable "WebRTC PipeWire support"
+**Context:** Wayland, Fedora, WebRTC, PipeWire, Chrome, Meet
+
+#### GNU screen (2021-02-22)
+
+**Error:** ``Utmp slot not found``
+**Solution:** Add ``deflogin off`` to ``~/.screenrc``
+**Context:** Debian, Ubuntu, WSL
+
+#### aws s3 command (2021-02-05)
+
+**Error:** ``fatal error: An error occurred (RequestTimeTooSkewed) when calling the ListObjectsV2 operation: The difference between the request time and the current time is too large.``
+**Solution:** ``sudo ntpdate pool.ntp.org`` or similar to update your local system time.
+**Context:** awscli, AWS, s3, Debian, Ubuntu, Linux
+
+#### Docker on Debian Buster (2020-12-15)
+
+**Error:** ``Running iptables --wait -t nat -L -n failed with message:``
+**Solution:** ``update-alternatives --set iptables /usr/sbin/iptables-legacy``
+**Context:** ``docker start``, Docker, Debian Buster, ``/var/log/docker.log``
+
diff --git a/md_export/TechWorks.md b/md_export/TechWorks.md
new file mode 100644
index 0000000..1fc38e2
--- /dev/null
+++ b/md_export/TechWorks.md
@@ -0,0 +1,40 @@
+# TechWorks
+The following are technical presentations, workshops, papers, and other works I've created or helped to create.
+
+| Date       | Venue                                                                                         | Title                                                                                                                                                                                                                                                                       | Format         | Role               | With                       |
+|:-----------|:----------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:-------------------|:---------------------------|
+| 2021-01-28 | [SciCloj](https://scicloj.github.io/)                                                         | [Common Lisp for the Curious Clojurian](https://www.youtube.com/watch?v=44Q9ew9JH_U)                                                                                                                                                                                        | Meetup         | Presenter          |                            |
+| 2020-12-23 | [ClojureScript Podcast](https://soundcloud.com/user-959992602/s4-e19-hoplon-with-alan-dipert) | [S4 E19 Hoplon with Alan Dipert](https://soundcloud.com/user-959992602/s4-e19-hoplon-with-alan-dipert)                                                                                                                                                                      | Interview      | Interviewee        | Jacek Schae                |
+| 2020-04-28 | [ELS](https://www.european-lisp-symposium.org/)                                               | [JACL: A Common Lisp for Developing Single-Page Web Applications](https://www.youtube.com/watch?v=HGuTqsVh59w)                                                                                                                                                              | Presentation   | Presenter          |                            |
+| 2020-04-27 | [ELS](https://www.european-lisp-symposium.org/)                                               | [JACL: A Common Lisp for Developing Single-Page Web Applications](./TechWorks/jacl-demo-els-2020.pdf)                                                                                                                                                                       | Academic Paper | Author             |                            |
+| 2020-01-31 | rstudio::conf                                                                                 | [Integration Testing in Shiny Apps and Modules](./TechWorks/2020-01-28-RStudio-Conf-Integration-Testing-ePoster.pdf)                                                                                                                                                        | Poster         | Presenter          |                            |
+| 2019-12-11 | WWW                                                                                           | [Just Lisp Things](https://tailrecursion.com/jlt/)                                                                                                                                                                                                                          | Author         | Blog               |                            |
+| 2019-11-19 | [OCRUG](https://ocrug.org/)                                                                   | [Integrating React.js and Shiny](https://www.meetup.com/OC-RUG/events/265511792/), [code](https://github.com/alandipert/ocrug-2018-11-27)                                                                                                                                   | Presentation   | Presenter          |                            |
+| 2019-11-16 | [OCRUG](https://ocrug.org/)                                                                   | [Interview with Alan Dipert](https://ocrug.org/blog/2019/11/19/2019-11-19-interview-alan-dipert/)                                                                                                                                                                           | Interview      | Interviewee        |                            |
+| 2019-01-24 | rstudio::conf                                                                                 | [Integrating React.js and Shiny](https://rstudio.com/resources/rstudioconf-2019/integrating-react-js-and-shiny/)                                                                                                                                                            | Presentation   | Presenter          |                            |
+| 2018-12-17 | [Orange Combinator](https://www.meetup.com/fr-FR/orange-combinator/events/lxvjrpyxqbwb/)      | [Old School FP: A Common Lisp Experience Report](https://tailrecursion.com/~alan/documents/2018-12-17-CommonLispOrangeCombinator.pdf)                                                                                                                                       | Meetup         | Presenter          |                            |
+| 2018-09-06 | rstudio.com                                                                                   | [Load testing Shiny](https://rstudio.com/resources/webinars/load-testing-shiny/)                                                                                                                                                                                            | Webinar        | Presenter          |                            |
+| 2018-02-02 | rstudio::conf                                                                                 | [Make Shiny fast by doing as little work as possible](https://rstudio.com/resources/rstudioconf-2018/make-shiny-fast-by-doing-as-little-work-as-possible/), [slides](https://github.com/alandipert/fast-shiny)                                                              | Presentation   | Presenter          |                            |
+| 2018-01-31 | rstudio::conf                                                                                 | Intermediate Shiny                                                                                                                                                                                                                                                          | Workshop       | Teaching assistant |                            |
+| 2017-10-13 | WWW                                                                                           | [Wondr: Thoughts on R programming](https://tailrecursion.com/wondr/)                                                                                                                                                                                                        | Blog           | Author             |                            |
+| 2016-11-03 | [The Cognicast](https://www.cognitect.com/cognicast/)                                         | [Episode 112](https://www.cognitect.com/cognicast/112)                                                                                                                                                                                                                      | Interview      | Interviewee        | Craig Andera, Micha Niskin |
+| 2016-10-18 | [The Cognicast](https://www.cognitect.com/cognicast/)                                         | [Episode 111](https://www.cognitect.com/cognicast/111)                                                                                                                                                                                                                      | Interview      | Interviewee        | Craig Andera, Micha Niskin |
+| 2015-11-06 | [Øredev](https://archive.oredev.org/2015/2015.html)                                           | [Programmable Builds with Boot](https://vimeo.com/144997124)                                                                                                                                                                                                                | Presentation   | Presenter          |                            |
+| 2015-11-04 | [Øredev](https://archive.oredev.org/2015/2015.html)                                           | [Hoplon: A Simpler Way to Program the Web](https://vimeo.com/144696304)                                                                                                                                                                                                     | Presentation   | Presenter          |                            |
+| 2015-10-02 | [ClojureBridge](https://clojurebridge.org/)                                                   | ClojureBridge Durham                                                                                                                                                                                                                                                        | Workshop       | Teaching assistant | Yoko Harada                |
+| 2015-10-01 | No Starch Press                                                                               | [Clojure for the Brave and True](https://www.amazon.com/Clojure-Brave-True-Ultimate-Programmer/dp/1593275919/ref=as_li_ss_tl?dchild=1&keywords=clojure+brave+true&qid=1606509052&sr=8-1&linkCode=ll1&tag=adipert-20&linkId=6377033a07b0c6d35bdd0535a5bc8fd6&language=en_US) | Book           | Technical editor   | Daniel Higginbotham        |
+| 2015-04-20 | [Clojure/West](https://web.archive.org/web/20150325185906/http://clojurewest.org/)            | [Boot Can Build It](https://www.youtube.com/watch?v=TcnzB2tB-8Q), [slides](./TechWorks/2015-04-20_ClojureWestBoot.pdf)                                                                                                                                                      | Presentation   | Presenter          | Micha Niskin               |
+| 2014-03-24 | [Clojure/West](https://web.archive.org/web/20141115162625/http://www.clojurewest.org/)        | [Web Programming with Hoplon](https://www.youtube.com/watch?v=wVXjExRiFy0)                                                                                                                                                                                                  | Presentation   | Presenter          | Micha Niskin               |
+| 2014-03-18 | [The Cognicast](https://www.cognitect.com/cognicast/)                                         | [Episode 52](https://www.cognitect.com/cognicast/052-alan-dipert)                                                                                                                                                                                                           | Interview      | Interviewee        | Craig Andera               |
+| 2013-11-14 | [Clojure/conj](https://web.archive.org/web/20131010003442/http://clojure-conj.org/)           | [Gherkin, a Lisp 1 in Bash 4](https://www.youtube.com/watch?v=bmHTFo2Rf2w#t=28m55s), [code](https://github.com/alandipert/gherkin)                                                                                                                                          | Lightning talk | Presenter          |                            |
+| 2013-03-18 | Clojure/West                                                                                  | [FRP in ClojureScript with Javelin](http://www.infoq.com/presentations/ClojureScript-Javelin), [slides](./TechWorks/Dipert-FRP_in_ClojureScript_with_Javelin.pdf)                                                                                                           | Presentation   | Presenter          |                            |
+| 2012-11-16 | [Clojure/conj](http://2012.clojure-conj.org/)                                                 | [FRP in ClojureScript with Flapjax](https://www.youtube.com/watch?v=xaxF5RDdVRE#t=22m21s), [code](https://www.youtube.com/watch?v[[https://github.com/alandipert/flapjax-demo), [demo](http://alandipert.github.io/flapjax-demo/)                                           | Lightning talk | Presenter          |                            |
+| 2012-10-06 | BarCamp Rochester 10                                                                          | Your Own Compiler in 20 Minutes, [slides](https://github.com/alandipert/barcamp2012-jsonscript)                                                                                                                                                                             | Presentation   | Presenter          |                            |
+| 2012-07-19 | OSCON                                                                                         | Computing with Clojure, [slides](https://github.com/alandipert/oscon2012-clojure)                                                                                                                                                                                           | Workshop       | Trainer            | Clinton Dreisbach          |
+| 2012-07-13 | Pluralsight                                                                                   | [Clojure Fundamentals - Part 1](https://www.pluralsight.com/courses/clojure-fundamentals-part-one)                                                                                                                                                                          | Course         | Trainer            | Craig Andera               |
+| 2012-03-16 | Clojure/West                                                                                  | [Programming with Values in Clojure](https://www.infoq.com/presentations/Programming-with-Values-in-Clojure/), [slides](https://web.archive.org/web/20120216230608/http://clojurewest.org/[[.\\Dipert-ProgrammingWithValues.pdf)                                            | Presentation   | Presenter          |                            |
+| 2011-11-12 | [Clojure/conj](https://web.archive.org/web/20111023154311/http://clojure-conj.org/)           | [Uberlisp, a Lisp for Arduino](https://www.youtube.com/watch?v=tSw3x0rVh88#t=18m30s), [code](https://github.com/alandipert/wombat)                                                                                                                                          | Lightning talk | Presenter          | Jon Distad                 |
+| 2011-08-25 | The Ruby Hoedown V                                                                            | Functional Programming with Ruby                                                                                                                                                                                                                                            | Presentation   | Presenter          |                            |
+| 2005-01-14 | Linux.com                                                                                     | [My workstation OS: NetBSD](https://web.archive.org/web/20080201010726/http://www.linux.com/articles/41523)                                                                                                                                                                 | Article        | Author             |                            |
+
+
diff --git a/md_export/TechWorks/2015-04-20_ClojureWestBoot.pdf b/md_export/TechWorks/2015-04-20_ClojureWestBoot.pdf
new file mode 100644
index 0000000..30615c5
Binary files /dev/null and b/md_export/TechWorks/2015-04-20_ClojureWestBoot.pdf differ
diff --git a/md_export/TechWorks/2020-01-28-RStudio-Conf-Integration-Testing-ePoster.pdf b/md_export/TechWorks/2020-01-28-RStudio-Conf-Integration-Testing-ePoster.pdf
new file mode 100644
index 0000000..4740eec
Binary files /dev/null and b/md_export/TechWorks/2020-01-28-RStudio-Conf-Integration-Testing-ePoster.pdf differ
diff --git a/md_export/TechWorks/Dipert-FRP_in_ClojureScript_with_Javelin.pdf b/md_export/TechWorks/Dipert-FRP_in_ClojureScript_with_Javelin.pdf
new file mode 100644
index 0000000..b1c9994
Binary files /dev/null and b/md_export/TechWorks/Dipert-FRP_in_ClojureScript_with_Javelin.pdf differ
diff --git a/md_export/TechWorks/Dipert-ProgrammingWithValues.pdf b/md_export/TechWorks/Dipert-ProgrammingWithValues.pdf
new file mode 100644
index 0000000..16f4ee8
Binary files /dev/null and b/md_export/TechWorks/Dipert-ProgrammingWithValues.pdf differ
diff --git a/md_export/TechWorks/jacl-demo-els-2020.pdf b/md_export/TechWorks/jacl-demo-els-2020.pdf
new file mode 100644
index 0000000..d33e882
Binary files /dev/null and b/md_export/TechWorks/jacl-demo-els-2020.pdf differ
diff --git a/md_export/WellReadUndergrad.md b/md_export/WellReadUndergrad.md
new file mode 100644
index 0000000..bb80506
--- /dev/null
+++ b/md_export/WellReadUndergrad.md
@@ -0,0 +1,35 @@
+# WellReadUndergrad
+Created Tuesday 17 November 2020
+
+The following is adapted from a Word document titled [What Every Educated Person Should Know about Philosophy](./WellReadUndergrad/phil-lt5.doc). My dad [RandallRDipert](./RandallRDipert.md) created the document in 1998 while he was a philosophy professor at [West Point](https://www.westpoint.edu/). The document was later [available on his web site,](https://web.archive.org/web/20000919054115/http://www.neologic.net/rd/courses.htm) where he described it as:
+
+*A list of books and articles, concepts, and quotations which I suggest every college graduate should know; also, extended to a graduate who is a philosophy major. A bit grandiose, overreaching, and pompous--but maybe suggestive of something useful.*
+
+
+*****
+
+What Every Educated Person Should Know in Philosophy
+----------------------------------------------------
+
+A well-read undergraduate should ideally have read, or at least be somewhat familiar with the content of, many or most of the works in **boldface**. (Works that can be read first have one asterisk; works to read next, two asterisks; and the hardest introductory works have three asterisks.)  Other works are listed as part of a suggested reading list for a well-read undergraduate major in philosophy.
+
+### Novels and other Literature with Philosophical Substance
+
+| Author                          | Work                                                                                                                            |
+|:--------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|
+| Aristophanes                    | *The Clouds* (satire of philosophy and Socrates)                                                                                |
+| Alexander Pope                  | *Essay on Man* (long poem in English, religious metaphysics)                                                                    |
+| Voltaire                        | *Candide* (parody  of Leibniz) and other short works                                                                            |
+| J.W. von Goethe                 | *Faust*                                                                                                                         |
+| Fyodor Dostoevsky               | ***Crime and Punishment (or Notes from the Underground)***, ***The Brothers Karamazov*** (esp. section "Grand Inquisitor")      |
+| Hermann Hesse                   | ***Siddharta*** (novella)                                                                                                       |
+| Albert Camus                    | **The Stranger**; The Plague (novels)<br>, The Myth of Sisyphus (essays, meaning of life, suicide)                              |
+| Ayn Rand                        | *The Fountainhead, Atlas Shrugged*                                                                                              |
+| Jean-Paul Sartre                | ****No Exit** (drama)                                                                                                           |
+| Lewis Carroll (Charles Dodgson) | ****Alice in Wonderland** (read as an adult, preferably after<br> studying some logic)<br>, *Through the Looking Glass* (logic) |
+| T.S. Eliot                      | Several works, especially *The Waste Land and Four Quartets*                                                                    |
+| Robert Pirsig                   | ***Zen and the Art of Motorcycle Maintenance<br>**, Lila                                                                        |
+
+
+TODO
+
diff --git a/md_export/WellReadUndergrad/phil-lt5.doc b/md_export/WellReadUndergrad/phil-lt5.doc
new file mode 100644
index 0000000..101f900
Binary files /dev/null and b/md_export/WellReadUndergrad/phil-lt5.doc differ
diff --git a/md_export/WellReadUndergrad/phil-lt5.docx b/md_export/WellReadUndergrad/phil-lt5.docx
new file mode 100644
index 0000000..cf5e9d2
Binary files /dev/null and b/md_export/WellReadUndergrad/phil-lt5.docx differ
diff --git a/tools/mdlink2html.awk b/tools/mdlink2html.awk
new file mode 100755
index 0000000..315fa5b
--- /dev/null
+++ b/tools/mdlink2html.awk
@@ -0,0 +1,47 @@
+#!/usr/bin/env awk -f
+# Rewrite Markdown links to point at generated HTML pages.
+
+function rewrite_url(url, path, frag, hash_pos, lower) {
+  if (url ~ /:\/\//) {
+    return url
+  }
+  if (substr(url, 1, 1) == "#") {
+    return url
+  }
+
+  hash_pos = index(url, "#")
+  if (hash_pos) {
+    frag = substr(url, hash_pos)
+    path = substr(url, 1, hash_pos - 1)
+  } else {
+    frag = ""
+    path = url
+  }
+
+  lower = tolower(path)
+  if (lower ~ /\.md$/) {
+    path = substr(path, 1, length(path) - 3) ".html"
+  } else if (path != "") {
+    if (lower ~ /\.(html|png|jpg|jpeg|gif|svg|pdf|css|js|zip)$/) {
+      # leave as-is
+    } else if (path !~ /\.[A-Za-z0-9]+$/ && path ~ /^[A-Za-z0-9._\-\/]+$/) {
+      path = path ".html"
+    }
+  }
+
+  return path frag
+}
+
+{
+  output = ""
+  remaining = $0
+  while (match(remaining, /\]\([^)]+\)/)) {
+    prefix = substr(remaining, 1, RSTART - 1)
+    url = substr(remaining, RSTART + 2, RLENGTH - 3)
+    rewritten = rewrite_url(url)
+    output = output prefix "](" rewritten ")"
+    remaining = substr(remaining, RSTART + RLENGTH)
+  }
+  output = output remaining
+  print output
+}
diff --git a/tpl/foot.html b/tpl/foot.html
new file mode 100644
index 0000000..31e606f
--- /dev/null
+++ b/tpl/foot.html
@@ -0,0 +1,8 @@
+      </div>
+    </div>
+    <footer class="site-foot">
+      <p>@BUILDINFO@</p>
+    </footer>
+  </div>
+</body>
+</html>
diff --git a/tpl/head.html b/tpl/head.html
new file mode 100644
index 0000000..a83a59b
--- /dev/null
+++ b/tpl/head.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width,initial-scale=1">
+  <title>Wiki</title>
+  <link rel="stylesheet" href="@CSS@style.css">
+  <script async src="https://www.googletagmanager.com/gtag/js?id=G-XCMVL5K44X"></script>
+  <script>
+    window.dataLayer = window.dataLayer || [];
+    function gtag(){dataLayer.push(arguments);}
+    gtag('js', new Date());
+    gtag('config', 'G-XCMVL5K44X');
+  </script>
+</head>
+<body>
+  <div id="main">
+    <header class="site-head">
+      <h1><a href="@CSS@Home.html">Home</a></h1>
+    </header>
+    <div class="pages">
+      <div class="content">
diff --git a/tpl/style.css b/tpl/style.css
new file mode 100644
index 0000000..f506c5a
--- /dev/null
+++ b/tpl/style.css
@@ -0,0 +1,77 @@
+:root{--fg:#2e3436;--bg:#ffffff;--muted:#6c757d;--link:#1a73e8;--shadow:0 4px 8px rgba(0,0,0,.2),0 6px 20px rgba(0,0,0,.19)}
+html{
+  --s:82px;
+  --c1:#b2b2b2;
+  --c2:#ffffff;
+  --c3:#d9d9d9;
+  --_g:var(--c3) 0 120deg,#0000 0;
+  background:
+    conic-gradient(from -60deg at 50% calc(100%/3),var(--_g)),
+    conic-gradient(from 120deg at 50% calc(200%/3),var(--_g)),
+    conic-gradient(from  60deg at calc(200%/3),var(--c3) 60deg,var(--c2) 0 120deg,#0000 0),
+    conic-gradient(from 180deg at calc(100%/3),var(--c1) 60deg,var(--_g)),
+    linear-gradient(90deg,var(--c1)   calc(100%/6),var(--c2) 0 50%,
+                    var(--c1) 0 calc(500%/6),var(--c2) 0);
+  background-size:calc(1.732*var(--s)) var(--s);
+}
+body{
+  margin:0;
+  padding:0;
+  font:16px/1.6 "Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+  color:var(--fg);
+}
+#main{
+  margin:40px auto;
+  max-width:800px;
+  padding:1.5em;
+  background:var(--bg);
+  border-radius:.75em;
+  box-shadow:var(--shadow);
+}
+.site-head{
+  margin-bottom:1.5rem;
+  border-bottom:1px solid rgba(0,0,0,.08);
+  padding-bottom:.75rem;
+}
+.site-head h1{
+  margin:0;
+  font-size:1.5rem;
+}
+.site-head a{
+  color:var(--link);
+  text-decoration:none;
+}
+.site-head a:hover{
+  text-decoration:underline;
+}
+.content h1,
+.content h2,
+.content h3,
+.content h4,
+.content h5{
+  color:#cc3b12;
+  line-height:1.25;
+}
+.content p{margin-top:0;}
+.content pre,
+.content code{background:#f7f7f7;}
+.content pre{padding:.75rem;overflow:auto;}
+a{color:var(--link);}
+img[src*="float_right"]{float:right;border-radius:.75em;}
+img[src*="200px"]{width:200px;}
+.content table{width:100%;border-collapse:collapse;margin:1.5rem 0;box-shadow:inset 0 -1px 0 rgba(0,0,0,.05);}
+.content th,
+.content td{padding:.5rem .75rem;border-bottom:1px solid rgba(0,0,0,.08);}
+.content thead th{background:rgba(0,0,0,.04);font-weight:600;text-align:left;}
+.content td[align="right"],
+.content th[align="right"]{text-align:right;}
+.content td[align="center"],
+.content th[align="center"]{text-align:center;}
+.site-foot{
+  margin-top:2rem;
+  border-top:1px solid rgba(0,0,0,.08);
+  padding-top:1rem;
+  color:var(--muted);
+  font-size:.875rem;
+}
+.site-foot p{margin:0;}