A recurring concern I have with this website (or blog) is to [make
sure links don't die]({{< relref "this-will-deteriorate-quickly" >}}). 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, [^fn: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 -->
Here, the 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
Org:
** 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 task[^fn:3]
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.
{{< alert class="info" >}} I might write a function that populates the heading plus properties automatically for me... in the future... {{< /alert >}}
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'
slugifying-this-blog-s-headings
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:
- interactive
- the region functions:
use-region-p
,region-beginning
andregion-end
- shell-command-to-string
- kill-new
(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
want...
(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!
-
[How this website is built - Nix and ox-hugo]({{< relref "how-this-website-is-built-nix-ox-hugo" >}}) [^fn:2]: [Why I use Nix and make(1) to develop]({{< relref "developing-with-nix-and-make" >}}) [^fn:3]: Although it has been a manual task for as long as this article. 😁↩