Підсвітка блоків з кодом в Org-mode за допомогою Pygments

За замовчуванням, Org майже ніяк не підсвічує синтаксис у блоках коду при експорті в HTML. Попри всю потужність Org, це стає серйозним плюсом на користь комбінації Pelican + Markdown для ведення блогу. Не знайшовши ніякого адекватного рішення, яке було хоча б наближено схоже на те, що пропонує Pygments в Pelican я вирішив додати його підтримку і в Org-mode.

Як це повинно працювати?

При експорті в певний формат, org-mode використовує спеціальний бекенд в якому описуються правила для обробки і конвертування усіх елементів. Вирішення мого завдання — створити власний бекенд на основі існуючого і змінити функції які відповідають за обробку блоків коду.

Отже, нам потрібна функція яка виконуватиме наступну shell-команду та віддаватиме вивід в бекенд.

pygmentize -l <синтаксис> -f html <вхідний_файл>

Буде значно простіше писати блоки коду в тимчасові файли, а потім віддавати їх скрипту ніж намагатись екранувати всі можливі символи для вводу через stdin.

Реалізація

На основі вихідного коду бекенду для експорту в HTML та пишемо власну функцію для обробки коду яка прийматиме на вхід 3 параметри. Нас цікавить параметр code який являє собою org-element.

Згідно Org-mode API для об'єкту "Src Block" на потрібно отримати значення властивостей :language та :value що відповідають за синтаксис та вихідний текст відповідно.

(require 'org)
(require 'ox)
(require 'ox-html)

;; Шлях до pygments або назва команди
(defvar pygments-path "pygmentize")

(defun pygments-org-html-code (code contents info)
  ;; Генеруємо шлях для тимчасового файлу
  ;; Хеш поточного часу і дати ідеально підійде під це завдання.
  (setq temp-source-file(format "/tmp/pygmentize-%s.txt"(md5 (current-time-string))))
  ;; Пишемо вміст блоку в тимчасовий файл
  (with-temp-file temp-source-file (insert (org-element-property :value code)))
  ;; Запускаємо shell-комаду і отримуємо вивід
  (shell-command-to-string (format "%s -l \"%s\" -f html %s"
				   pygments-path
				   (or (org-element-property :language code)
				       "")
				   temp-source-file)))

Тепер оголосимо новий бекенд pelican-html на основі html і замінимо в ньому функції для src-block та example-block:

(org-export-define-derived-backend 'pelican-html 'html
  :translate-alist '((src-block .  pygments-org-html-code)
		     (example-block . pygments-org-html-code)))

Варто зауважити, що не зважаючи на те, що в API властивість :language для example-block присутня — на практиці цей об'єкт її не підтримує (схоже це баг в документації), тому задання синтаксису працюватиме лише для src-block.

Застосування

В моєму випадку цей бекенд використовується для експорту постів через org-reader для Pelican, а не напряму через буфер Emacs. Вихідний код мого блогу і робочий приклад конфігурації для експорту можна знайти тут.

Для детальної інформації по налаштуванню експорту через Emacs та налаштування бекенду перегляньте документацію.

comments powered by Disqus