aboutsummaryrefslogtreecommitdiff
path: root/src/blog/makefile-based-blogging
diff options
context:
space:
mode:
Diffstat (limited to 'src/blog/makefile-based-blogging')
-rw-r--r--src/blog/makefile-based-blogging/index.md249
1 files changed, 249 insertions, 0 deletions
diff --git a/src/blog/makefile-based-blogging/index.md b/src/blog/makefile-based-blogging/index.md
new file mode 100644
index 0000000..9244a0d
--- /dev/null
+++ b/src/blog/makefile-based-blogging/index.md
@@ -0,0 +1,249 @@
+---
+title: Makefile-Based Blogging
+date: December 12, 2022
+subtitle: Yet another static site generator using `pandoc(1)` and `make(1)`.
+description: Building a markdown-based static site generator using pandoc and make.
+---
+
+A few days ago, I got the gumption to start blogging again. The last time I wrote
+with any frequency, I lovingly hand-crafted each HTML file before `rsync`ing it to
+my web server. This time, I wanted a more efficient workflow.
+
+I surveyed the [vast number](https://github.com/myles/awesome-static-generators)
+of static site generators available on GitHub, but most of them seemed like
+overkill for my humble website. I figured that by the time I wrapped by head
+around one of them, I could have just written a Makefile.
+
+Finally, I came across [pandoc-blog](https://github.com/lukasschwab/pandoc-blog),
+which gave me inspiration and showed me the ideal pandoc incantations for
+generating HTML from markdown files. And thus, my
+[Makefile-based static site generator](https://git.sacredheartsc.com/www/about/)
+was born. You're reading the inaugural post!
+
+## Generating the HTML
+
+The workhorse of this thing is [pandoc](https://pandoc.org), which is a ubiquitous
+open-source document converter. Transforming markdown into HTML is as simple as:
+
+```bash
+pandoc document.md -o document.html
+```
+
+Simple! But to generate an entire website, we'll need some of pandoc's additional
+features: custom templates and document metadata.
+
+### Custom Templates
+
+The layout of pandoc's output document is determined by the
+[template](https://pandoc.org/MANUAL.html#templates) in use. Pandoc includes
+default templates for a variety of document formats, but you can also specify
+your own.
+
+A very simple HTML template might look something like this:
+
+```html
+<html lang="en">
+ <head>
+ <meta name="author" content="$author-meta$">
+ <meta name="description" content="$description$">
+ </head>
+ <body>
+ <h1 class="title">$title$</h1>
+$body$
+ </body>
+</html>
+```
+
+[My pandoc template](https://git.sacredheartsc.com/www/tree/templates/default.html)
+is what generates the navigation bar at the top of this page.
+
+The variable `$body$` is replaced by the content of your markdown document when
+pandoc renders the template. The other variables are replaced by their
+corresponding values from the document's metadata.
+
+### Document Metadata
+
+Each pandoc source document can have associated metadata values. There are three
+ways of specifying metadata: the `--medatata` [flag](https://pandoc.org/MANUAL.html#option--metadata),
+a dedicated [metadata file](https://pandoc.org/MANUAL.html#option--metadata-file), or
+a [YAML metadata block](https://pandoc.org/MANUAL.html#extension-yaml_metadata_block)
+embedded within the document itself. We'll be using the embedded metadata blocks.
+
+Each markdown document for my website starts with a YAML metadata block. The
+metadata for the post you're
+[currently reading](https://git.sacredheartsc.com/www/tree/src/blog/makefile-based-blogging/index.md)
+looks like this:
+
+
+```yaml
+---
+title: Makefile-Based Blogging
+date: December 12, 2022
+subtitle: Yet another static site generator using `pandoc(1)` and `make(1)`.
+description: Building a markdown-based static site generator using pandoc and make.
+---
+```
+
+You can put whatever YAML you like in your markdown files, as long as the metadata
+starts and ends with three hyphens.
+
+## Automating pandoc with make
+
+Using a Makefile, we can automatically invoke pandoc to convert each markdown
+file in our blog to HTML. In addition, `make` will keep track of which source
+files have changed since the last run and rebuild them accordingly.
+
+First, lets describe the project layout:
+
+- **src/**: the source files of our blog, including markdown files and static
+ assets (CSS, images, etc). The subdirectory structure is entirely up to you.
+
+- **public/**: the output directory. After running `make`, the contents of this
+ directory can be `rsync`'d straight to your web server.
+
+- **scripts/**: helper scripts for generating the blog artifacts. Currently there
+ are only two:
+
+ - [bloglist.py](https://git.sacredheartsc.com/www/tree/scripts/bloglist.py)
+ generates a markdown-formatted list of all your blog posts, sorted by the
+ `date` field in the YAML metadata block.
+
+ - [rss.py](https://git.sacredheartsc.com/www/tree/scripts/rss.py) generates
+ an RSS feed for your blog.
+
+- **templates/**: pandoc templates which generate HTML from markdown files
+ (currently, there is only one).
+
+The Makefile used to build this website is located [here](https://git.sacredheartsc.com/www/tree/Makefile).
+I've reproduced a simplified version below, to make it easier to step through.
+
+```makefile
+######################
+# Variable definitions
+######################
+
+# These variables are used to generate the RSS feed
+URL = https://www.sacredheartsc.com
+FEED_TITLE = sacredheartsc blog
+FEED_DESCRIPTION = Carolina-grown articles about self-hosting, privacy, unix, and more.
+
+# The number of blog posts to show on the homepage
+BLOG_LIST_LIMIT = 5
+
+# File extensions (other than .md) that should be included in public/ directory
+STATIC_REGEX = .*\.(html|css|jpg|jpeg|png|xml|txt)
+
+# Pandoc template used to generate HTML
+TEMPLATE = templates/default.html
+
+# List of subdirectories to create
+SOURCE_DIRS := $(shell find src -mindepth 1 -type d)
+
+# List of source markdown files
+SOURCE_MARKDOWN := $(shell find src -type f -name '*.md' -and ! -name .bloglist.md)
+
+# List of static assets
+SOURCE_STATIC := $(shell find src \
+ -type f \
+ -regextype posix-extended \
+ -iregex '$(STATIC_REGEX)')
+
+# List of all blog posts (excluding the main blog page)
+BLOG_POSTS := $(shell find src/blog \
+ -type f \
+ -name '*.md' \
+ -and ! -name .bloglist.md \
+ -and ! -path src/blog/index.md)
+
+# Subdirectories to create under public/
+OUTPUT_DIRS := $(patsubst src/%, public/%, $(SOURCE_DIRS))
+
+# .html files under public/, corresponding to each .md file under src/
+OUTPUT_MARKDOWN := $(patsubst src/%, public/%, $(patsubst %.md, %.html, $(SOURCE_MARKDOWN)))
+
+# Static file targets under public/
+OUTPUT_STATIC := $(patsubst src/%, public/%, $(SOURCE_STATIC))
+
+# Script to generate RSS feed
+RSSGEN = scripts/rss.py \
+ src/blog \
+ --title="$(FEED_TITLE)" \
+ --description="$(FEED_DESCRIPTION)" \
+ --url=$(URL) \
+ --blog-path=/blog \
+ --feed-path=/blog/rss/feed.xml
+
+
+######################
+# File Targets
+######################
+
+# Default target: convert .md to .html, copy static assets, and generate RSS
+public: \
+ $(OUTPUT_DIRS) \
+ $(OUTPUT_MARKDOWN) \
+ $(OUTPUT_STATIC) \
+ public/blog/feed.xml
+
+# Homepage (/)
+public/index.html: src/index.md src/.bloglist.md $(TEMPLATE)
+ sed $$'/__BLOG_LIST__/{r src/.bloglist.md\nd}' $< \
+ | pandoc --template=$(TEMPLATE) --output=$@
+
+# Markdown list of 5 most recent blog posts
+src/.bloglist.md: $(BLOG_POSTS) scripts/bloglist.py
+ scripts/bloglist.py src/blog $(BLOG_LIST_LIMIT) > $@
+
+# The main blog listing (/blog/)
+public/blog/index.html: src/blog/index.md src/blog/.bloglist.md $(TEMPLATE)
+ sed $$'/__BLOG_LIST__/{r src/blog/.bloglist.md\nd}' $< \
+ | pandoc --template=$(TEMPLATE) --output=$@
+
+# Markdown list of _all_ blog posts
+src/blog/.bloglist.md: $(BLOG_POSTS) scripts/bloglist.py
+ scripts/bloglist.py src/blog > $@
+
+# Convert all other .md files to .html
+public/%.html: src/%.md $(TEMPLATE)
+ pandoc --template=$(TEMPLATE) --output=$@ $<
+
+# Catch-all: copy static assets in src/ to public/
+public/%: src/%
+ cp --preserve=timestamps $< $@
+
+# RSS feed
+public/blog/feed.xml: $(BLOG_POSTS) scripts/rss.py
+ $(RSSGEN) > $@
+
+
+######################
+# Phony Targets
+######################
+
+.PHONY: serve rsync clean
+
+# Run a local HTTP server in the output directory
+serve: public
+ cd public && python3 -m http.server
+
+# Deploy the site to your webserver
+rsync: public
+ rsync -rlphv --delete public/ webserver.example.com:/var/www/html
+
+clean:
+ rm -rf public
+ rm -f src/.bloglist.md
+ rm -f src/blog/.bloglist.md
+```
+
+## Closing Thoughts
+
+I admit, there is a small amount of hackery involved. You obviously can't generate
+a time-sorted list of blog posts using pure markdown, so I'm generating the
+markdown list using a Python script in an intermediate step. I then (ab)use `sed`
+to shove that list into the markdown source on the fly. This means that changing
+the look of the [blog list](/blog/) requires hacking up the Python code.
+
+But overall, I've been quite happy with this little project. There's just something
+about writing paragraphs in `vi` and typing `:!make` that warms my soul with
+memories of simpler times.