My beorg to blog workflow

Posted on — | 5 min read

Note: Most part of this blog post was written using the workflow described below.

Alright. This needs some explaining and prerequisites.

I’m a heavy emacs user. I use it for a ton of things, one of them is to maintain and update my blog, which is a static site generated by Hugo. There is already a great package called ox-hugo which helps with blogging maintenance and workflows for Hugo-generated blogs.

My emacs workflows involve adding tasks, writing down ideas, updating my journal, etc. Beorg is a great app for iPhone that syncs well with org files either via Dropbox (which is what I use for syncing) or by other means.

Typically I don’t use Beorg on my phone for adding/updating blog posts. But when I do get ideas for a blog post, I use Beorg since that’s my main driver. Most of the time I just write down the outline of the blog post on my phone using Beorg, and add the rest like links, images, etc on my computer. But sometimes, I’ll be in the flow and end up writing an entire blog post on my phone.

What I mention below may seem a little overwhelming for someone that doesn’t use emacs, so read with caution.

The workflow

So here’s my workflow.

  1. I get an idea for a blog post and I quickly capture it. There’s a capture template I’ve set up on beorg, that goes to a specific location with appropriate properties set.
  2. Once I capture the idea, I make whatever modifications are needed to polish it.
  3. Whenever I hop on to the computer, I go to the blog post I just created and run a function I wrote to generate the export filename for the post. As soon as I hit save, the post will be formatted to Hugo standards and will be exported to the right location.
  4. I run the Hugo server to quickly check if everything looks good.
  5. I run the deploy.sh script to commit the code in the blog directory, commit the generated files in the GitHub pages directory and push both of them. That’s it.

Code snippets to make everything work

Here are some of the code snippets that are used for different part of the workflow.

Beorg capture

Below is the screenshot. The subtree can be configured in any way one sees fit.

Generating Hugo post filename

This is a code snippet that is required for generating the filename for the blog post. This function parses the post title and creates a filename that ends with a ‘.md’ extension.

	(defun rr/extract-hugo-post-file-name ()
		"Create a filename out of blog post's title.

This method is expected to be executed on a TODO heading on a an
org file containing blog posts that would be exported using
ox-hugo. Running this interactive command would set an org
property called EXPORT_FILE_NAME that is required by ox-hugo to
generate a Hugo-friendly markdown file in the location specified
in HUGO_BASE_DIR property."
		(interactive)
		(setq-local title-line (thing-at-point 'line t))
		(unless (not (string-match "TODO " title-line))
			(let* ((lines (split-string title-line "TODO "))
						 (blog-post-title (nth 1 lines))
						 (file-name (replace-regexp-in-string "_+" "-" (replace-regexp-in-string "\\W" "_" (string-trim (downcase blog-post-title)))))
						 (blog-post-file-name (concat file-name ".md")))
				(org-set-property "EXPORT_FILE_NAME" blog-post-file-name))))

This is an interactive function and I run it by hitting M-x.

Turn on auto-export-mode when I visit blog.org

All my blog posts are saved in an org file called blog.org. Whenever I open this file, org-hugo-auto-export-mode is turned on.

The minor mode org-hugo-auto-export-mode enables auto export hugo posts on saving. However, this minor mode is disabled by default. It doesn’t make sense to have this turned on globally. So, the following piece of code enables the minor mode only when the buffer is blog.org. Found the code in a stack overflow post.

(defun rr/enable-hugo-auto-export-mode ()
	(if (equal (buffer-name) "blog.org")
			(org-hugo-auto-export-mode)))

(add-hook 'find-file-hook 'rr/enable-hugo-auto-export-mode)

With this mode enabled, every time I hit save, any edited post gets exported to a Hugo-compatible markdown and saved to the right location specified in both the header of the file as well as the EXPORT_FILE_NAME property.

deploy.sh script

Once the post is ready to be published, the blog post must be committed to the current repository and pushed. Since I’m also hosting the blog on GitHub using GitHub Pages, there’s an additional repository to which I have to copy the public directory generated in this code repository. Then those files must be committed and pushed as well. All of this is done using script called deploy.sh that needs to be run once. Here’s the script.

#!/bin/sh

# If a command fails then the deploy stops
set -e

printf "\033[0;32mDeploying updates to GitHub...\033[0m\n"

# Build the project.
hugo -t ezhil # if using a theme, replace with `hugo -t <YOURTHEME>`

# git push committed changes in existing folder
git push origin master

# Copy public folder to the repo outside the submodule
# The issue is, submodule does not get updated when running this script either because of incorrect setup
# or maybe because public is part of gitignore and the remote points to blog.git instead of rrajath.github.io.git.
# In order to make this script work properly, the public folder that is generated is copied to the repo outside
# this folder and then committed and pushed
cp -r public/* ../rrajath.github.io

# Go To Public folder
cd ../rrajath.github.io

# Add changes to git.
git add -A

# Commit changes.
msg="rebuilding site $(date)"
if [ -n "$*" ]; then
	msg="$*"
fi
git commit -m "$msg"

# Push source and build repos.
git push origin master

Conclusion

This seems like a lot of work, but it’s not. All of this works beautifully and it’s a one-time setup. The satisfaction of coming up with a system that is tailored to work exactly to my needs feels great.

Also, workflows are hard to explain using text or in a blog post. There are quite a lot of moving parts and different parts of the workflow need different pre-requisites. When all of them are aligned, it works smoothly.