Whipping Up a Quick Emacs Helper Function for Hugo

Continuing to Build Out Utility Functions

I’ve previously written about and since then, I’ve added more functions. Someday, I’ll get around to sharing more of them. They’re almost ready to packaged up, but I haven’t spent the mental cycles thinking what’s in the package and what’s my local needs.

I was thinking about my process for finding the Hugo 🔍 file associated with a blog post.

The specific situation was that I wanted to update to include a reference to the follow-up post .

I had the URL for the post I wanted to update. I also had some existing functions that I’d written to help me find all of the drafts in my Hugo repository.

Code for finding a Hugo file based on a URL.

These constants and functions were things I'd previously written.

Note: This implementation assumes you are using the f package and have installed ripgrep, which is aliased as rg in the command shell.


(defconst jnf/tor-home-directory
  (file-truename "~/git/takeonrules.github.io")
  "The home directory of TakeOnRules.com Hugo repository.")

(defconst jnf/tor-hostname-regexp
  "https?://takeonrules\.com"
  "A regular expression for checking if it's TakeOnRules.com.")

(cl-defun jnf/tor-prompt-or-kill-ring-for-url (&key
                                               (url-regexp "^https?://"))
  "Prompt and return a url.

If the \`car' of \`kill-ring' matches the URL-REGEXP, default the
prompt value to the \`car' of `kill-ring'."
  (let ((car-of-kill-ring (substring-no-properties (car kill-ring))))
    (read-string "URL (optional): "
                 (when (string-match url-regexp car-of-kill-ring)
                   car-of-kill-ring))))

(cl-defun jnf/list-filenames-with-file-text (&key matching in)
  "Build a list of filenames MATCHING IN the given directory."
  (let ((default-directory (f-join jnf/tor-home-directory in)))
    (split-string-and-unquote
     (shell-command-to-string
      (concat
       "rg \""
       matching "\" --only-matching --files-with-matches "
       "| sort | tr '\n' '~'"))
     "~")))

They provided the bits and pieces for crafting jnf/tor-find-hugo-file-by-url, the function that prompts for a URL and finds the associated Hugo file.


(cl-defun jnf/tor-find-hugo-file-by-url (url)
  "Find the associated TakeOnRules.com file for the given URL."
  (interactive (list
                (jnf/tor-prompt-or-kill-ring-for-url
                 :url-regexp jnf/tor-hostname-regexp)))
  ;; With the given URL extract the slug
  (let* ((slug (car (last (split-string-and-unquote url "/"))))
         (filename (car
                    (jnf/list-filenames-with-file-text
                     :matching (concat "^slug: .*" slug "$")
                     :in "content"))))
    (find-file (f-join
                jnf/tor-home-directory
                "content"
                filename))))

Conclusion

With the above Elisp 🔍, I can now use M-x jnf/tor-find-hugo-file-by-url, type (or paste) the URL into the prompt, and Emacs 🔍 will open the corresponding blog post.

This does require that all of my blog posts have a slug frontmatter entry. This function does not work for non-blog post pages on my site. They have a different frontmatter structure.

To handle both pages and posts, I’m going to need to introduce some switching logic. But I don’t yet need it, so I’ll hold off.