I generate this website on my computer with this makefile. it gives me all the dev conveniences I'd get from a templating engine or mvc framework but lets me serve plain html/css from a traditional webserver.

it builds most pages by stitching files into templates, inserting and changing text and links here and there. it builds this page by inserting itself into a template, making it its own dependency, which has a few interesting consequences. this was the first time, for instance, I had to change a regex to stop it from matching a representation of its own definition. it also means this page will always have the current version of the makefile because it watches itself to see if it's newer than this page.

I really like make. vi, sed, screen, I tend to gravitate toward tools older than I am. they're the survivors. I don't especially dislike javascript compared to most languages. but I think the web would be better if sites asking to execute code on the user's machine were the exception rather than the rule. "when I was your age, we used the hypertext transfer protocol to send hypertext, dammit." yadda yadda.

I could have used a static site generator, but that would have been too easy.


SHELL := /bin/bash
MAKEFLAGS := s

domain := https://www.alicemaz.com/
makefile := Makefile

html_base := src/base.m4
html_nav := src/nav.m4
html_index := src/pages/index.m4
html_pages := $(wildcard src/pages/*.m4)
html_out := $(addprefix build/,$(notdir $(html_pages:%.m4=%.html)))

error_base := src/error.m4
error_pages := $(wildcard src/errors/*.m4)
error_out:= $(addprefix build/,$(notdir $(error_pages:%.m4=%.html)))

makefile_staging := staging/makefile
make_page_staging := staging/pages/makefile.html
make_out := build/makefile.html

index_post_stems := $(shell ls -r src/posts/)
index_post_first := $(addprefix src/posts/,$(firstword $(index_post_stems)))

index_posts_sorted := $(addprefix src/posts/,$(index_post_stems))
index_posts_staging := staging/index_posts
index_page_staging := staging/pages/index.html
index_out := build/index.html

atom_first := src/atom/first.m4
atom_last := src/atom/last.m4
atom_block := src/atom/block.m4
atom_entries_sorted := $(addprefix staging/atom/entries/,$(basename $(index_post_stems)))
atom_out := build/atom.xml

twines := $(wildcard twine/*.html)

sitemap_first := src/sitemap/first.m4
sitemap_last := src/sitemap/last.m4
sitemap_block := src/sitemap/block.m4
sitemap_staging := staging/sitemap/index \
	$(addprefix staging/sitemap/pages/, \
		$(filter-out index,$(basename $(notdir $(html_pages))))) \
	$(addprefix staging/sitemap/,$(twines:%.html=%))
sitemap_out := build/sitemap.xml

tweet_base := src/twitter/base.m4
tweet_pages := $(wildcard src/twitter/tweets/*.m4)
tweet_staging := $(addprefix staging/tweets/,$(basename $(notdir $(tweet_pages))))
bot_page_staging := staging/pages/bots.html
bot_gameboard := src/twitter/gameboard.m4
bots_out := build/bots.html

now = date +%Y-%m-%d
pretty_datetime = date +%d\ %b\ %H:%M:%S

ifeq ($(OSTYPE),linux)
	last_mod = $(now) -r $(1)
else
	last_mod = stat -f %Sm -t %Y-%m-%d $(1)
endif

.PHONY: all localhref remotehref deploy unstage unbuild clean

.INTERMEDIATE: $(atom_entries_sorted) $(index_posts_staging) $(index_page_staging) \
$(make_page_staging) $(makefile_staging) $(bot_page_staging) $(tweet_staging)


###########
#  make   #
###########

all: $(html_out) $(error_out) $(sitemap_out) $(atom_out)


###########
# staging #
###########

$(makefile_staging): $(makefile)
	mkdir -p $(@D)
	sed 's/</\</g;s/>/\>/g;' $< > $@
	printf "($(shell $(pretty_datetime))) staged $(@F)\n"

$(index_posts_staging): $(index_posts_sorted)
	mkdir -p $(@D)
	cat $^ > $@
	printf "($(shell $(pretty_datetime))) staged $(@F)\n"

staging/atom/posts/%: src/posts/%.m4
	mkdir -p $(@D)
	m4 $< | \
		sed 's/<[\/]\{0,1\}blockquote>/"/g' | \
		sed -n 's/<[^>]*>//g;3,$$p' | \
		sed -n 's/^[[:space:]]*\([^[:space:]].*\)/\1/p' | \
		sed 'G' | \
		sed '$$d' > $@

staging/atom/entries/%: staging/atom/posts/% $(atom_block)
	mkdir -p $(@D)
	m4 -D xDEFINES=src/posts/$(@F).m4 \
		-D xCONTENT=$< $(atom_block) > $@

staging/sitemap/index: $(html_index) $(sitemap_block)
	mkdir -p $(@D)
	m4 -D xLOC \
		-D xMOD=$(shell $(call last_mod,$<)) \
		-D xFREQ=daily \
		-D xPRIORITY=0.9 $(sitemap_block) > $@

staging/sitemap/pages/%: src/pages/%.m4 $(sitemap_block)
	mkdir -p $(@D)
	m4 -D xLOC=$(@F).html \
		-D xMOD=$(shell $(call last_mod,$<)) \
		-D xFREQ=daily \
		-D xPRIORITY=0.7 $(sitemap_block) > $@

staging/sitemap/twine/%: twine/%.html $(sitemap_block)
	mkdir -p $(@D)
	m4 -D xLOC=$< \
		-D xMOD=$(shell $(call last_mod,$<)) \
		-D xFREQ=monthly \
		-D xPRIORITY=0.3 $(sitemap_block) > $@

staging/tweets/%: src/twitter/tweets/%.m4 $(tweet_base)
	mkdir -p $(@D)
	$(eval acct := $(shell echo '$<' | \
		sed -e 's/tweets/accounts/' -e 's/-[0-9]\{1,\}//'))
	m4 -D xACCT=$(acct) -D xTWEET=$< $(tweet_base) | \
	sed -e 's/^[[:space:]]*//' > $@

staging/pages/%.html: src/pages/%.m4 $(html_base) $(html_nav)
	mkdir -p $(@D)
	
	$(eval stem := $(basename $(<F)))
	$(eval pretty_name := $(shell [[ $(stem) == 'index' ]] && \
		echo 'home' || ([[ $(stem) == 'makefile' ]] && \
		echo 'make' || echo $(stem))))
	$(eval href_name := $(shell [[ $(stem) == 'index' ]] && \
		echo '/' || echo $(stem).html))
	
	m4 -D xTITLE='<title>alice maz - $(pretty_name)</title>' \
		-D xNAV=$(html_nav) \
		-D xPAGE=$< \
		-D xJUMPTOP=$(@F)'#' \
		-D xBOT=$(shell $(now)) \
		-D xMAKE='<a href="makefile.html">make</a>' $(html_base) | \
	sed -e 's|<a href="$(href_name)">$(pretty_name)</a>|$(pretty_name)|g' \
		-e 's/^[[:space:]]*//' > $@
	
	printf "($(shell $(pretty_datetime))) staged $(@F)\n"

staging/pages/%.html: src/errors/%.m4 $(error_base)
	mkdir -p $(@D)
	
	$(eval stem := $(basename $(<F)))
	
	m4 -D xTITLE='<title>alice maz - $(stem)</title>' \
		-D xH1='<h1>$(stem)</h1>' \
		-D xPAGE=$< \
		-D xJUMPTOP=$(@F)'#' \
		-D xBOT=$(shell $(now)) \
		-D xMAKE='<a href="makefile.html">make</a>' $(error_base) | \
	sed -e 's/^[[:space:]]*//' > $@
	
	printf "($(shell $(pretty_datetime))) staged $(@F)\n"


###########
#  build  #
###########

$(sitemap_out): $(sitemap_first) $(sitemap_staging) $(sitemap_last)
	mkdir -p $(@D)
	cat $^ > $@
	printf "($(shell $(pretty_datetime))) made $(@F)\n"

$(atom_out): $(atom_first) $(atom_entries_sorted) $(atom_last) $(index_post_first)
	mkdir -p $(@D)
	m4 -D xDEFINES=$(index_post_first) $(atom_first) > $@
	cat $(atom_entries_sorted) $(atom_last) >> $@
	printf "($(shell $(pretty_datetime))) made $(@F)\n"

$(make_out): $(make_page_staging) $(makefile_staging)
	mkdir -p $(@D)
	m4 -D staging/makefile=$(makefile_staging) $< > $@
	printf "($(shell $(pretty_datetime))) made $(@F)\n"

$(index_out): $(index_page_staging) $(index_posts_staging)
	mkdir -p $(@D)
	m4 -D xPOSTS=$(index_posts_staging) $< > $@
	printf "($(shell $(pretty_datetime))) made $(@F)\n"

$(bots_out): $(tweet_staging) $(bot_page_staging) $(bot_gameboard)
	mkdir -p $(@D)
	m4 -D xGAMEBOARD=$(bot_gameboard) -I $(<D) $(bot_page_staging) > $@
	printf "($(shell $(pretty_datetime))) made $(@F)\n"

build/%.html: staging/pages/%.html
	mkdir -p $(@D)
	ln -sf ../assets/ $(@D)
	ln -sf ../twine/ $(@D)
	ln -sf ../favicon.ico $(@D)
	ln -sf ../robots.txt $(@D)
	cp -r $< $@
	printf "($(shell $(pretty_datetime))) made $(@F)\n"


###########
#  tasks  #
###########

localhref:
	for f in build/*.html; do \
		sed -i 's|^\(<base href="\)$(domain)\(">\)$$|\1/\2|' $$f; \
		done
	printf "($(shell $(pretty_datetime))) base href to local\n"

remotehref:
	for f in build/*.html; do \
		sed -i 's|^\(<base href="\)/\(">\)$$|\1$(domain)\2|' $$f; \
		done
	printf "($(shell $(pretty_datetime))) base href to remote\n"

deploy:
	$(MAKE)
	$(MAKE) remotehref
	rsync -rvLptWc --stats --progress --del -e ssh \
		build/ kitchen@salacia:/usr/local/www/site
	printf "($(shell $(pretty_datetime))) deployed build/\n"
	$(MAKE) localhref

unstage:
	rm -rf staging/
	printf "($(shell $(pretty_datetime))) unmade staging/\n"

unbuild:
	rm -rf build/
	printf "($(shell $(pretty_datetime))) unmade build/\n"

clean:
	$(MAKE) unstage
	$(MAKE) unbuild