4

I'm having issues achieving any syntax highlighting in the results of the following command:

emacs nix.org --batch -f org-html-export-to-html --kill

I know this question has been asked a few times already in various forms, but none of the advertised solutions I've found have worked for me (linked below).

My Environment

  • spacemacs (via emacs 25.3) on the develop branch, current as of 2018 January 29
  • htmlize-20171017.141 as a dependency of the org layer
  • Attempting to highlight source blocks of haskell, lisp, yaml, and nix modes

What I'm Seeing

Naively performing the command above gives many lines of:

Cannot fontify src block (htmlize.el >= 1.34 required)

I tried copy-pasting the htmlize.el found in my .emacs.d/elpa/.. and refering to it manually:

emacs nix.org --batch -l htmlize.el -f org-html-export-to-html --kill

...and this makes the "Cannot find..." errors go away, but the output is still not highlighted properly. Actually, the lisp block does have one of its keywords bolded, but nothing else looks right. Similar hacks to manually include lisp code from say haskell-mode can achieve bold/italics-only highlighting, which is not what I want.

--batch implies -q, which ignores all user config (to speed up start-up, I'm assuming). This seems to have the effect of ignoring all the Emacs packages I have installed (via spacemacs), and so the batch process can't see htmlize or any of my major modes to achieve proper colouring.

Questions

  • Is there a way for me to tell a batch'd emacs process to respect my config and see all the packages I have? (this might tank each run of it though)
  • Is there something else simple that I'm missing to achieve this highlighting properly?

My goal is to only commit my .org files for a blog, and have my server generate each .html properly on-the-fly during a deploy. I could just generate the nice .html files on my own machine and commit those, but I really don't want to do that.

Thanks!

Related

2
  1. You have already mentioned that emacs skips the personal initialization files if it is called with --batch. Therefore, you need to call package-initialize yourself if you want to use package.el.

  2. htmlize-buffer uses the text properties added by font-lock to compile style information for the html buffer/file. Therefore, we need font-lock even if the files are processed in batch-mode.

  3. The text properties carry the faces and the faces contain the color information for the text and the background.

  4. htmlize-buffer calls face-attribute for determining the text color. That function calls in turn internal-get-lisp-face-attribute. That internal function always checks whether the frame admits colorizing. But, emacs does not have any frame in batch-mode. So we must override face-attribute to htmlize the color information we want.

The following lisp code can be used to run package-initialize and to override face-attribute. (At the end of this code there is also a little debugging tool print-args-and-ret that easies debugging emacs in batch-mode.)

(package-initialize)

(require 'font-lock)

(require 'subr-x) ;; for `when-let'

(unless (boundp 'maximal-integer)
  (defconst maximal-integer (lsh -1 -1)
    "Maximal integer value representable natively in emacs lisp."))

(defun face-spec-default (spec)
  "Get list containing at most the default entry of face SPEC.
Return nil if SPEC has no default entry."
  (let* ((first (car-safe spec))
     (display (car-safe first)))
    (when (eq display 'default)
      (list (car-safe spec)))))

(defun face-spec-min-color (display-atts)
  "Get min-color entry of DISPLAY-ATTS pair from face spec."
  (let* ((display (car-safe display-atts)))
    (or (car-safe (cdr (assoc 'min-colors display)))
    maximal-integer)))

(defun face-spec-highest-color (spec)
  "Search face SPEC for highest color.
That means the DISPLAY entry of SPEC
with class 'color and highest min-color value."
  (let ((color-list (cl-remove-if-not
             (lambda (display-atts)
               (when-let ((display (car-safe display-atts))
                  (class (and (listp display)
                          (assoc 'class display)))
                  (background (assoc 'background display)))
             (and (member 'light (cdr background))
                  (member 'color (cdr class)))))
             spec)))
    (cl-reduce (lambda (display-atts1 display-atts2)
         (if (> (face-spec-min-color display-atts1)
            (face-spec-min-color display-atts2))
             display-atts1
           display-atts2))
           (cdr color-list)
           :initial-value (car color-list))))

(defun face-spec-t (spec)
  "Search face SPEC for fall back."
  (cl-find-if (lambda (display-atts)
        (eq (car-safe display-atts) t))
          spec))

(defun my-face-attribute (face attribute &optional frame inherit)
  "Get FACE ATTRIBUTE from `face-user-default-spec' and not from `face-attribute'."
  (let* ((face-spec (face-user-default-spec face))
     (display-attr (or (face-spec-highest-color face-spec)
               (face-spec-t face-spec)))
     (attr (cdr display-attr))
     (val (or (plist-get attr attribute) (car-safe (cdr (assoc attribute attr))))))
    ;; (message "attribute: %S" attribute) ;; for debugging
    (when (and (null (eq attribute :inherit))
           (null val))
      (let ((inherited-face (my-face-attribute face :inherit)))
    (when (and inherited-face
           (null (eq inherited-face 'unspecified)))
      (setq val (my-face-attribute inherited-face attribute)))))
    ;; (message "face: %S attribute: %S display-attr: %S, val: %S" face attribute display-attr val) ;; for debugging
    (or val 'unspecified)))

(advice-add 'face-attribute :override #'my-face-attribute)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Debugging:
(defmacro print-args-and-ret (fun)
  "Prepare FUN for printing args and return value."
  `(advice-add (quote ,fun) :around
           (lambda (oldfun &rest args)
         (let ((ret (apply oldfun args)))
           (message ,(concat "Calling " (symbol-name fun) " with args %S returns %S.") args ret)
           ret))
           '((name "print-args-and-ret"))))

; (print-args-and-ret htmlize-faces-in-buffer)
; (print-args-and-ret htmlize-get-override-fstruct)
; (print-args-and-ret htmlize-face-to-fstruct)
; (print-args-and-ret htmlize-attrlist-to-fstruct)
; (print-args-and-ret face-foreground)
; (print-args-and-ret face-background)
; (print-args-and-ret face-attribute)

Let us assume that the above elisp code is stored in ~/tmp/loadme.el and the org file which we want to htmlize is ~/tmp/test.org. In that case you would call emacs as follows to produce a html file ~/tmp/test.html:

emacs --batch --load ~/tmp/loadme.el ~/tmp/test.org --eval '(progn (outline-show-all) (font-lock-flush) (font-lock-fontify-buffer) (with-current-buffer (htmlize-buffer) (write-region (point-min) (point-max) "~/tmp/test.html")))'
  • A last minute change caused an error. Fixed now. – Tobias Feb 1 '18 at 8:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.