2019-04-10

Automated personal content page with Jekyll and GitHub Actions

736 words - 2 minutes

I post blog posts, personal projects and weekly collections of interesting links on this page. Most of the process is automated.

Credit for the initial idea goes to Lawrence Wu. Posting interesting links bundled as weekly unorganized sets has an interesting outcome: while browsing previously posted links as a user or the author, you’re not biased by any kind of categorization. Namely, a Bandcamp find stands on the same line as a blog post by a famed startup CEO.

I wanted to build a platform to share three kinds of content: link collections, blog posts and brief descriptions of projects I am working on or have done previously.

Approaching the problem, I had a few requirements:

Git and Jekyll seemed to fit the bill the best. By using GitHub Pages as the hosting platform, I don’t have to maintain any servers myself. Updating the page is as simple as pushing to Git. Additionally, Jekyll comes with the support of Liquid templating, which can be with some effort used to quite cleanly do all home page related operations, namely grouping links automatically and keeping both links and posts in chronological order.

Here’s (simplified) how both posts and weekly links are grouped together and showed in one feed:

{% assign weekly_items = posts_and_links |
  group_by_exp: "item", "item.date | date: '%U'" %}

{% for week in weekly_items %}
  {% assign posts = week.items | where_exp: "item", "item.title != nil" %}
  {% for post in posts %}
    <h2>{{ post.title }}</h2>
  {% endfor %}
  
  {% assign links = week.items | where_exp: "item", "item.title == nil" %}
  <h2>This week's links:</h2>
  {% for link in links %}
    Link: {{ link.name }}<br>
  {% endfor %}
{% endfor %}

This leaves point number two: how to make inserting links so easy that I will actually bother doing it?

GitHub Actions solve the problem quite smoothly. I hooked a workflow to automatically run a shell script every time I create or edit an issue.

Here’s what the script looks like in a simplified form:

# Read URL/Name from Title/Body of the issue
url=$(jq -r ".issue.title" "$GITHUB_EVENT_PATH")
name=$(jq -r ".issue.body" "$GITHUB_EVENT_PATH")

# If Name has not been set, attempt to fetch it from <title> with curl + perl
if [[ -z "${name// }" ]]
then
  name=$(curl $url -so - | \
    perl -l -0777 -ne 'print $1 if /<title.*?>\s*(.*?)\s*<\/title/si' | \
    perl -C -MHTML::Entities -pe 'decode_entities($_);')
fi

# Upsert link/name to .json data file
jq --arg url "$url" \
  --arg name "$name" \
  --arg date "$(date +"%Y-%m-%d")" \
  'if any(.url == $url) \
    then (.[] | select(.url == $url) | .name) = $name \
    else . + [{name: $name, url: $url, date: $date}] end' \
  "_data/links.json" | jq . | sponge "_data/links.json"

# Add a commit and push, GitHub Pages will handle the deployment!
git add -A && git commit -m "Add '$name'" && git push -u origin HEAD