A recurring concern I have with this website
(or blog) is to make
sure links don’t die. One of the measures I take is to never change
a URL once it is published. I mean, changing the heading and the
content is possible, but not the URL.
How is it done? As previously mentioned1, 2, this website is built with a mix of Org Mode and Hugo. In this case, the Hugo feature I’m using is the slug attribute on the page’s front matter. This basically overrides the file path that would be on the URL with a string of your choice.
Let’s imagine you were writing plain Markdown files and thought a title was not adequate anymore, but the content was still sound. This how this article would look like:
+++ title = "Slugifying this blog's headings" slug = "slugifying-this-blog-s-headings" date = 2023-05-17 +++ <!-- content -->
slug would keep the URL the same even if the heading
changed. In my case, this is how this article’s portion looks like in
** Slugifying this blog's headings :emacs:shell: :PROPERTIES: :EXPORT_DATE: 2023-05-17 :EXPORT_FILE_NAME: slugifying-this-blog-s-headings :EXPORT_HUGO_CUSTOM_FRONT_MATTER: :slug slugifying-this-blog-s-headings :END: # content
Okay, but how to create the slugs? This can’t be a manual task3
as it is too error prone, I can make typos easily on bigger headings!
Then I thought: “I’m already inside GNU Emacs, why not use it?”. The
idea is really simple, I select the heading text and run a function
me/slugify that will copy the slugified version on my clipboard.
🗒️ I might write a function that populates the heading plus properties automatically for me… in the future…
The first step I took was to search on the internet some bash script that I could just glue (😜) together with Emacs Lisp. I ended up finding this gist comment that had a really straightforward command:
# note that the `echo` and last `tr` was added by me echo "Slugifying this blog's headings" \ | iconv -t ascii//TRANSLIT \ | sed -E -e 's/[^[:alnum:]]+/-/g' -e 's/^-+|-+$//g' \ | tr '[:upper:]' '[:lower:]' \ | tr -d '\n'
This is exactly what I was looking for, but how to make this an Emacs Lisp function I can call? Note that I know barely nothing about Emacs Lisp, the only thing I know here is that I want an interactive command. In this sense, we need to declare a function with these properties/steps:
- is interactive
- gets the selected region
- slugifies the selected region
- copies the sligfied text to clipboard
- notifies us somehow that it was successful
After doing some
C-h f and online research, I found out the
functions I needed:
- the region functions:
(defun gluer/slugify () (interactive) ;; only run this if there's an active region (when (use-region-p) ;; `let*` bounds the variables sequentially, useful to use a ;; previously declared variable (let* ( ;; stores the selected region on the `str` variable (str (buffer-substring (region-beginning) (region-end))) ;; interpolates the selected region on the `echo` command ;; that will pipe it to the rest of the chain (cmd (format "echo %s | iconv -t ascii//TRANSLIT | sed -E -e 's/[^[:alnum:]]+/-/g' -e 's/^-+|-+$//g' | tr '[:upper:]' '[:lower:]' | tr -d '\n'" str)) ;; stores the command output on the `slug` variable (slug (shell-command-to-string cmd))) ;; copies the slug to the clipboard (kill-new slug) ;; diplays the slug on the bottom of the screen (message "copied '%s' to the clipboard" slug))))
Selecting the region and running
M-x gluer/slugify gets us a nice
slugified string on our clipboard! But it’s not over yet!
The funny part was that I didn’t do the most obvious thing: search for
a “slug” function on GNU Emacs itself! Well, guess what? ox-hugo has
a function called
org-hugo-slug (!!!) that does exactly what I
(org-hugo-slug STR &optional ALLOW-DOUBLE-HYPHENS) Convert string STR to a ‘slug’ and return that string.
Let’s give it a try and adapt our previous one:
(defun gluer/slugify () (interactive) (when (use-region-p) (let* ((str (buffer-substring (region-beginning) (region-end))) (slug (org-hugo-slug str))) (kill-new slug) (message "copied '%s' to the clipboard" slug))))
This is way better than what I previously had. It’s cleaner, doesn’t add weird shell strings and uses the same mechanism behind the website generation.
Lesson learned: use the
describe-* functions (in this case,
describe-function) on GNU Emacs, they are there for a reason!