My first advice! (in Emacs Lisp)
It was really fun to learn about
advising Lisp functions
to extend functionality in Emacs. My first use case was to run a custom function every time a certain function in
Bastian Bechtold’s
org-static-blog is called. Of course, I could customize that function directly in my own fork, but Lisp advice allows you to modify functions without clobbering them directly. This approach has aesthetic and practical advantages.
But I’ve struggled to understand the concepts and implementation of advice. Today I posted to Mastodon about how excited I was to get my first working use, and Philip asked me to share the code.
My problem was specifying HTML boilerplate that org-static-blog puts in each post when it publishes all the static files, using string variables like org-static-blog-page-header. These strings are complex enough that I put them in their own files, like these lines in the #header.html file that specifies the page metadata:
<script type="module" src="https://esm.sh/emfed@"></script> <meta name="author" content="James Endres Howell"> <meta name="referrer" content="no-referrer"> <link href="static/style.css" rel="stylesheet" type="text/css" /> <meta name="fediverse:creator" content="@jameshowell@fediscience.org"> <meta property="og:image" content="https://jamesendreshowell.com/static/education-of-james-endres-howell.png">
First, Stack Exchange and I solved the problem of reading a file into a string. (?! How is this not a native function!? Maybe I missed something obvious.)
(defun jeh/file-to-string (file) "Return a string that is the contents of FILE." (with-temp-buffer (insert-file-contents file) (buffer-string)))
And then, for example:
(setq org-static-blog-page-header (jeh/file-to-string (expand-file-name "#header.html" org-static-blog-template-blocks-directory)))
The unscratched itch was that every time I edit one of these files, I always, but always, forget to update the appropriate variable with the contents of the file! And so publishing doesn’t reflect the changes, and I get confused, and then I remember….
Here is the solution:
(defun jeh/org-static-blog-read-templates (&rest ignore) "Set org-static-blog-page -header, -preamble, -postamble variables by reading files from `org-static-blog-template-blocks-directory'." (setq org-static-blog-page-header ;;; HTML to put in the <head> of each page. (jeh/file-to-string (expand-file-name "#header.html" org-static-blog-template-blocks-directory))) (setq org-static-blog-page-preamble ;;; HTML to put before the content of each page. (jeh/file-to-string (expand-file-name "#preamble.html" org-static-blog-template-blocks-directory))) (setq org-static-blog-page-postamble ;;; HTML to put after the content of each page. (format (jeh/file-to-string (expand-file-name "#postamble.html" org-static-blog-template-blocks-directory)) (number-to-string emacs-major-version) (number-to-string emacs-minor-version) org-version))) ;;; Re-read the template files before publishing, ;;; so changes will be included in output. (advice-add #'org-static-blog-publish :before #'jeh/org-static-blog-read-templates)
The function jeh/org-static-blog-read-templates sets the variables
org-static-blog-page-header,
org-static-blog-page-preamble, and
org-static-blog-page-postamble to the contents of the appropriate files. Making that function a hook to the function which generates all the static pages, org-static-blog-publish, solves my problem. But there is no hook for it! I had a suspicion that add-advice could give me the same result, and—I hope you’re sitting down—I
read the fine manual
and learned that the syntax of the last line accomplishes that very thing.
Of course, advice-add (and related functions) can do much more! Maybe as I learn I will be able to customize functions from other packages without just banging on a local fork.