I have been wondering if the benefits of using ox-hugo just so I can write posts using Org-mode format is worth the extra layer of abstraction. I prefer Org-mode to Markdown, but Markdown is fine. In fact, Markdown-mode makes editing Markdown in Emacs quite pleasant. Ox-hugo is a great package, but increasingly seemed like a clever but unnecessary abstraction. One of its best features is that it makes creating new posts super easy. I never liked using the Hugo CLI, so ox-hugo solved that problem.

I started looking for a way to make generating posts easier, but without using Hugo. At first I figured I would need some kind of wrapper around the CLI’s hugo new post/... command, but then wondered if I should do something right within Emacs. I mean, someone’s had to solve this problem before, right?

I’d used a package called Easy Hugo before, but it’s way overkill for what I need.

After a brief search, I spotted a post from Jeremy Friesen which included a function that does something very close to what I needed. (Thanks, Jeremy!).

My theme at the time of this writing is Congo. Congo pushes one toward using Hugo bundles for posts, meaning rather than posts being in a file named my-blog-post.md I need to name them index.md in a folder for each post. I tweaked Jeremy’s code a bit and now I can just call jab/post-new and I instantly get the proper folder, file, and front matter and can simply start typing. Here’s the code:

(defun jab/post-new (title &optional)
  "Create and visit a new draft blog post for the prompted TITLE."
  (interactive "sTitle: ")

  (let* ((slug (s-dashed-words title))
         (default-directory (concat "~/sites/blog/content/posts/"
                                    (format-time-string "%Y/%m-%B/%Y-%m-%d-")
                                    slug "/"))
         (fpath (concat default-directory "index.md")))
         
    (make-directory default-directory)
    (write-region (concat
                   "---"
                   "\ntitle: '" title "'"
                   "\ndate: " (format-time-string "%Y-%m-%d %H:%M:%S %z")
                   "\nslug: " slug
                   "\ncategories: [\"\"]"
                   "\ntags: [\"\"]"
                   "\ndraft: true"
                   "\n---\n")
                  nil (expand-file-name fpath) nil nil nil t)
    (find-file (expand-file-name fpath))))

I create two slightly different kinds of posts, regular posts (which are created using the above function) and “journal” posts, which use a different file name and URL structure. Rather than modifying the existing functions with a bunch of conditionals, I did what all professional programmers do, I copied and pasted the existing function and tweaked it to suit. It’s called jab/post-daily and looks like this:

(defun jab/daily-new ()
  "Create and visit a new j blog post for the prompted TITLE."
  (interactive)

  (let* ((slug (concat (format-time-string "%Y-%m-%d") "-journal"))
         (default-directory (concat "~/sites/blog/content/posts/"
                                    (format-time-string "%Y/%m-%B/")
                                    slug "/"))
         (fpath (concat default-directory "index.md")))
         
         (make-directory default-directory)
    (write-region (concat
                   "---"
                   "\ntitle: '" (format-time-string "%A, %B %d, %Y") "'"
                   "\ndate: " (format-time-string "%Y-%m-%d %H:%M:%S %z")
                   "\nslug: " slug
                   "\ncategories: [\"Journal\"]"
                   "\ntags: [\"\"]"
                   "\ndraft: true"
                   "\n---\n")
                  nil (expand-file-name fpath) nil nil nil t)
    (find-file (expand-file-name fpath))))

The only real difference is the folder name and slug are different and the title is pre-determined so there’s no need for a prompt.

I’ll miss the one-post-per-heading-in-one-big-file approach of ox-hugo, but editing the Markdown files directly has fewer dependencies and feels cleaner to me.