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:

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)
                     (goto-char (point-min))
                     (search-forward substr nil t))))
               (file-contains-p (fname substr)
                   (insert-file-contents fname)
                   (buffer-contains-p substr)))
               (read-file-as-one-line (fname)
                 "Read a file and remove all newlines"
                   (insert-file-contents fname)
                   (replace-string "\n" "" nil (point-min) (point-max))
               (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"))
               (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))
      (let ((root (this-project-root)))
        (when root
          (main root))))))))))


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.


As usual: C-c C-e and pick your poison.

Date: 2021-06-26

Copyright © 2021 Hraban Luyat