Once upon a time this blog was powered by make

Nostalgia meets wisdom. This article explores the freedom perhaps lost due to accumulated experience while diving into one my many forgotten projects; a static site generator powered by a Makefile.

Table of Contents

Once upon a time there was a blog powered by a makefile

Time plays tricks on us programmers. We often forget how simple things were back in the days, though we also forget the hassle of reinventing the wheel over and over. Like the fact that my previous blog was powered entirely by a Makefile and a handful of tools for converting markdown into a statically generated site.

Sure, the implementation wasn't clean or modern by any stretch of the word, but it worked. No endless documentation rabbit holes, no trendy frameworks with confusing praxis. It was just me, my terminal, and code that got the job done.

Note: The makefile can be found at the end of this post, or by following this link.

The advantage of time is that what was difficult has become easier. My original Makefile required several command-line tools, and I naively assumed that I would remember exactly what tools would be required to use it.

Ten years on, I wanted to resurrect the project and thank god for Docker which is perfectly suitable for reviving the dusty code in a standalone container. After a few iterations of banging my head against the desk due to missing dependencies, things just.. worked.

Nostalgia and reflection

My hacky blog engine does whatever is expected of, even modern, static site generators. It powered my home page for over a decade without a single change. And despite the chaotic comments, there's something beautiful about the fact that all it took was a sleepless night and a few weird ideas.

I wish I'd saved more work like this. There's so much code I've forgotten, so many hidden implementations that made my projects work. I remember sharing the implementation with a few friends back then, somewhat ashamed of the "shit I had put together", but now.. with different eyes.. oh damn; past-me had a creativity that I sometimes wonder if I lost.

I am no longer ashamed of what I wrote, honestly I am quite proud of it, even if I certainly see what all those frustrated comments were getting at.

The code is now a piece of nostalgia I hope I never forget, and this post is published to lower the odds of memories drifting into the equivalence of /dev/null.

Knowledge kills creativity

Perhaps it is not entirely true that the more you know, the less creative you are, but there is a heavy truth that comes with experience; the more you know, the more you think ahead, and foresight has a way of limiting your immediate options.

When I was younger the immediate task was everything, and as long as said "everything" was solved, there was nothing more to do. "No need to worry about tomorrow, because today I'm creating a static site generator."

Getting started on any project was blazingly fast:

  • There was no need to evaluate dependency-X vs dependency-Y, besides on a surface level.
  • There was no need to think about future features, those were not part of the immediate problem.
  • There was no need to plan ahead, and no need to worry; I only wrote code.

Nowadays there is a lot more to do. There are caveats to consider, and many of those come with years of experience from shooting yourself in the foot. And even though I am happy that there is not that much foot-shooting going on nowadays, I do miss the creative soul that simply... created stuff.

At a previous job I was called "the guy who worries", and said alternate persona made us avoid long detours down the road for many of our projects. But damn... I miss the freedom that comes without worrying, the freedom where your brain is not limited by future potential outcomes, the freedom where you can start coding immediately.

Senior developers rarely get to experience such freedom; with experience comes knowledge, and knowledge can be such a buzzkill.

A message to the younger generation

I am getting older, and I have yet to know all of life's lessons, but if there is one piece of advice I can give to the younger generation of programmers it is that of saving your work. Even if it at the time feels like the shittiest, hackiest, and ugliest piece of code; one day you will come to appreciate the glories of past accomplishments.

  • Be proud of what you accomplish today, hack or not.
  • Always push your work, whether it gets used or not.
  • Keep your repository data safe, it will one day be a goldmine for nostalgia.

Your future self will forever be thankful for what one day will become a glimpse into the past, a faded mirror image of yourself, and proof that you were once free.

The Makefile

MUSTACHE := /usr/bin/mustache
CMARK := /usr/bin/cmark --unsafe
OUTDIR := public_html

YAML_ENTITY_HEADER := yaml-entity-specific

PAGES := $(patsubst pages/%,$(OUTDIR)/%/index.html,$(wildcard pages/*))
POSTS := $(patsubst posts/%,$(OUTDIR)/posts/%/index.html,$(wildcard posts/*))

all: $(POSTS) $(PAGES) purge \
   $(OUTDIR)/main.css \
   $(OUTDIR)/robots.txt \
   $(OUTDIR)/sitemap.xml \
   $(OUTDIR)/rss.xml \
   $(OUTDIR)/index.html

purge: $(POSTS) $(PAGES)
  $(foreach X, $(wildcard $(OUTDIR)/pages/*), \
    $(if $(wildcard $(X:$(OUTDIR)/%.html=pages/%)),,rm -r '$X';) )
  $(foreach X, $(wildcard $(OUTDIR)/posts/*), \
    $(if $(wildcard $(X:$(OUTDIR)/posts/%=posts/%)),,rm -r '$X';) )
  @echo -n

build/posts.yaml: $(POSTS) makefile
  $(call CreateAggregatedYaml,posts,build/posts.yaml)

build/pages.yaml: $(PAGES) makefile
  $(call CreateAggregatedYaml,pages,build/pages.yaml)

$(OUTDIR)/posts/%/index.html: makefile header.md footer.md config.yaml posts/%/*
  $(call RenderEntity,posts/$*/contents.md,posts/$*/index.html)
  cp -rT 'posts/$*' '$(OUTDIR)/posts/$*/attachments'

$(OUTDIR)/%/index.html: makefile header.md footer.md config.yaml build/posts.yaml pages/%
  $(call RenderEntity,pages/$*/contents.md,$*/index.html,'build/posts.yaml')

$(OUTDIR)/index.html: makefile header.md footer.md index.md config.yaml build/posts.yaml
  $(call RenderEntity,index.md,index.html,'build/posts.yaml')

$(OUTDIR)/robots.txt: robots.md config.yaml
  $(call RenderEntity,robots.md,robots.txt,,cat)

$(OUTDIR)/main.css: main.css config.yaml
  $(call RenderEntity,main.css,main.css,,cat)

$(OUTDIR)/sitemap.xml: makefile config.yaml sitemap.md build/posts.yaml build/pages.yaml
  $(call RenderEntity,sitemap.md,sitemap.xml,'build/posts.yaml' 'build/pages.yaml')

$(OUTDIR)/rss.xml: makefile config.yaml rss.md build/posts.yaml
  $(call RenderEntity,rss.md,rss.xml,'build/posts.yaml')

define YamlDates
  --format=' \
      post-$1:          "%aD"%n \
      post-$1-iso8601:  "%ai"%n \
      post-$1-short:    "%as"%n \
      post-$1-numeric:  "%at"%n \
  '
endef

define GenerateYaml
  {  \
    { \
      git log -1 --diff-filter=A --follow $(call YamlDates,created) -- "$1"; \
      git log -1                          $(call YamlDates,modified) -- "$1"; \
     } | awk '{$$1=$$1};1'; \
     echo '# explicit metadata'; \
     awk '{s=toupper($$0)} s~/-- YAML-METADATA/{f=1;next} /-->/{f=0} f' $1; \
  }
endef

# TODO: CLEAN THE FUCK UP
define CreateAggregatedYaml # ( file-type, out-file )
  { \
    echo "$1: "; \
    { \
      grep -H -P -o '(?<=post-created-numeric: ")[0-9]+(?=")' \
        --include='metadata.$1' -R '$(OUTDIR)' \
      | sed 's,:,\t,' \
      | sort -r -n -k 2 \
      | cut -f 1; \
    } | while read FILE; do \
      echo " -"; \
      awk 'b==1 {print "  " $$0} /$(YAML_ENTITY_HEADER)/ {b=1}' $$FILE; \
    done \
  } > '$2'
endef

# TODO: CLEAN THE FUCK UP
define RenderEntity # ( markdown_path, outfile_path, dependencies.yaml, transformer )
  $(eval OUT := build/$(subst /,__,$1))
  $(eval Y := $(shell mktemp))
  $(eval T := $(shell mktemp))

  mkdir -p build/

  { \
    echo '# config.yaml'; \
    cat  'config.yaml'; \
    echo '# build-info'; \
    echo 'build-date-string: "'$(shell date --rfc-email)'"'; \
    echo 'build-date-short: "'$(shell date '+%F')'"'; \
    echo '# $(YAML_ENTITY_HEADER)'; \
    echo 'relative-uri: "$(patsubst %index.html,%,$(2:$(OUTDIR)/%=%))"'; \
    $(call GenerateYaml,$1); \
    $(if $(1:posts/%=),,echo 'is-post: true'; ) \
  } > '$(OUT).yaml'

  { \
    cat '$(OUT).yaml' $3; \
  } > '$Y'

  { \
    echo '{{=$$'"{ }"'$$}}'; \
    cat $(if $(2:%.html=), '$1', header.md '$1' footer.md); \
    } > '$T'

  $(MUSTACHE) '$Y' '$T' \
    | $(if $4, $4, $(CMARK)) > '$(OUT).result'

  mkdir -p '$(OUTDIR)/$(dir $2)'
  mv '$(OUT).result' '$(OUTDIR)/$2'

  $(if $(subst ./,,$(dir $2)), \
    mv $(OUT).yaml $(OUTDIR)/$(dir $2)/metadata.$(firstword $(subst /, ,$1)), \
    rm $(OUT).yaml )

  rm '$Y'
  rm '$T'
endef