Hugo and ORG workflow

2018/10/01

While Hugo now supports (basic) org functionality, it will not support any fancy features such as babel executed code or bibliographies generated with org-ref. Since I need these, I settled for a ad-hoc solution initially based on the code described here.

(defun hugo ()
  (interactive)
  (message "Exporting Hugo")
  (let* ((title    (concat "title = \"" (org-global-prop-value "TITLE") "\"\n"))
         (date     (concat "date = \"" (org-global-prop-value-default "DATE" "2018-1-1") "\"\n"))
         (topics   (concat "topics = [ \"" (mapconcat 'identity (split-string (org-global-prop-value "CATEGORIES") "\\( *, *\\)" t) "\", \"") "\" ]\n"))
         (tags     (concat "tags = [ \"" (mapconcat 'identity (split-string (org-global-prop-value "TAGS") "\\( *, *\\)" t) "\", \"") "\" ]\n"))
         (draft    (concat "draft = " (org-global-prop-value-default "DRAFT" "false") "\n"))
         (fm (concat "+++\n"
		     title
		     date
		     tags
		     topics
		     draft
		     "+++\n\n"))
         (message "setting file")
         (file (file-name-sans-extension (buffer-file-name)))
         (coding-system-for-write buffer-file-coding-system)
         (backend  'html)
         (blog))

    (unless (string-match "\\.html$" file)
      (setq file (concat file ".html")))

    (message file)
    (message "Starting export")

    (setq blog (org-export-as backend nil t t nil))

    ;; image folder
    (setq impath 
          (concat (file-name-directory file) (file-name-sans-extension (file-name-nondirectory file)))

          )

    (message "saving html")
    (with-temp-buffer
      (insert fm)
      (insert (concat "<body>\n" blog "</body>\n"))
					;(untabify (point-min) (point-max))
      (write-file file)
      (message "Exported to %s" file))
    ))

After editing the org file, I can export it to Hugo compatible html using the M-g h key combination (as in Go Hugo) which can be setup with:

(define-key org-mode-map (kbd "M-g h") 'hugo)

My workflow uses a single org mode file per post, so it is convenient to be able to access the global properties of the document. This is not as trivial, but I found a handy solution at this link:

(defun org-global-props (&optional property buffer)
  "Get the plists of global org properties of current buffer."
  (unless property (setq property "PROPERTY"))
  (with-current-buffer (or buffer (current-buffer))
    (org-element-map (org-element-parse-buffer) 'keyword (lambda (el) (when (string-match property (org-element-property :key el)) el)))))

(defun org-global-prop-value (key)
  "Get global org property KEY of current buffer."
  (org-element-property :value (car (org-global-props key))))

(defun org-global-prop-value-default (key default)
  "Get global org property KEY of current buffer."
  (cond ( (org-element-property :value (car (org-global-props key))))
	(t default))
  )

About local files and naming

In order for Hugo to automatically copy files that are relative to the org document, I found that the best solution is to place an index.org file in a subdirectory of content/post. The script above will then generate a file index.html, which will be prioritized by Hugo with respect to the org file. Relative folders will then automatically copied in the generated website and relative links will function as expected.

So far I did not find any better method to handle relative links seamlessly without having to perform manual copies (I guess this is due to how Hugo is designed, or maybe due to my inexperience with its workings).