Self-referential .dir-locals.el
For personal org notes, I like being able to publish from within Emacs using regular org export, from the buffer with the open file.
Desired features:
- only applies to files from this project / directory
- transparent; no need to add a
#+SETUP
line to every independent file - path depth agnostic (no faffing with ../ or ../../ or ../../../)
- single-file export (no faffing with extra css files)
- performant
- automatic light mode & dark mode support from viewer’s browser
- independent of the Emacs theme currently loaded
The only thing that satisfies this is using a .dir-locals.el
file, which makes sure not to leak outside of the directory.
One pitfall in using .dir-locals.el
, is that while the code is only run for files in that directory, any hooks set in there will apply to your entire Emacs instance. This is a problem when setting org mode’s org-export-before-processing-hook
.
The trick to solving this is to make the hook function figure out if it is currently in a file that is in a subdirectory of the current .dir-locals.el
. How? Using a canary string. Read on for details.
Project-wide setup
Put this in .dir-locals.el
:
((org-mode . ((eval . (add-hook 'org-export-before-processing-hook (defun hly-org-export-hook (backend) "Always export with project-wide setup" (require 'cl-lib) (cl-flet* ((buffer-contains-p (substr) "https://stackoverflow.com/a/3034272/4359699" (save-excursion (save-match-data (goto-char (point-min)) (search-forward substr nil t)))) (file-contains-p (fname substr) (with-temp-buffer (insert-file-contents fname) (buffer-contains-p substr))) (read-file-as-one-line (fname) "Read a file and remove all newlines" (with-temp-buffer (insert-file-contents fname) (replace-string "\n" "" nil (point-min) (point-max)) (buffer-string))) (this-project-root () "Find the root of this project, ONLY if the open file is actually part of this project. This is imoprtant because the hook is added Emacs-wide, but I only want it to apply to this project." ;; I know, I know, this is insane. But what other way is there ;; to only apply this hook to files in this project? The only ;; thing I can think of is finding this .dir-locals.el itself ;; in my own parent. (let ((root (locate-dominating-file (buffer-file-name) ".dir-locals.el"))) ;; Don't ever copy me. Use a fresh random string. (when (and root (file-contains-p (format "%s/.dir-locals.el" root) "JPYnNt84MBFONRMewRgeHwFX")) root))) (main (root) ;; This setq-local is the whole reason we're doing the "am I in ;; this current project" file detection rigmarole. (setq-local org-html-htmlize-output-type 'css) (goto-char (point-max)) (insert "\n#+setupfile: " root "setup.org") ;; Insert CSS directly in the HTML because it's the best way by ;; a country mile to make exporting HTML simple. Remove all ;; newlines because org-mode parser suffers HARD under large ;; number of html_head keywords. Probably a O(n²) ;; somewhere. Multiple places, actually. (insert "\n#+html_head: <style>" (read-file-as-one-line (format "%s/style.css" root)) "</style>"))) (let ((root (this-project-root))) (when root (main root))))))))))
Stylesheet
And create a style.css
with desired styles. Use M-x org-html-htmlize-generate-css
to generate full CSS for your current Emacs theme. Wrap that in a theme declaration:
@media (prefers-color-scheme: light) { /* Style generated from a light Emacs theme */ } @media (prefers-color-scheme: dark) { /* Style generated from a dark Emacs theme */ }
Yes this file will grow massive. Use gzip? Or if you have it, a post-compile CSS treeshaker.
Export
As usual: C-c C-e
and pick your poison.