Use org2jekyll to publish my blog
19 February 2023
Table of Contents
I’ve already created some org jekyll function to publish blogs for
github jekyll. But there is a small issue that the org file should
contains the export_html
drawer with jekyll YAML metadata.
The emacs extension org2jekyll already supports extract metadata from orgmode files. So I reuse this extension with some modifications.
1. Publish settings
My orgmode files are located in <jekyll-folder>/_notes/_posts
and the
exported HTML files are in <jekyll-folder>/_posts
:
(custom-set-variables '(org2jekyll-blog-author "kimim") '(org2jekyll-source-directory (expand-file-name "~/kimi.im/_notes/_posts")) '(org2jekyll-jekyll-directory (expand-file-name "~/kimi.im/_posts")) '(org2jekyll-jekyll-drafts-dir "") '(org2jekyll-jekyll-posts-dir "") '(org-publish-project-alist `(("post" ;; dynamic pages like blog articles :base-directory ,(org2jekyll-input-directory) :base-extension "org\\|txt" :publishing-directory ,(org2jekyll-output-directory) :publishing-function org-html-publish-to-html :headline-levels 4 :html-head "<link rel=\"stylesheet\" href=\"./css/style.css\" type=\"text/css\"/>" :html-preamble t :recursive t :make-index t :html-extension "html" :body-only t) ("images" :base-directory ,(org2jekyll-input-directory "img") :base-extension "jpg\\|gif\\|png" :publishing-directory ,(org2jekyll-output-directory "img") :publishing-function org-publish-attachment :recursive t) ("js" :base-directory ,(org2jekyll-input-directory "js") :base-extension "js" :publishing-directory ,(org2jekyll-output-directory "js") :publishing-function org-publish-attachment :recursive t) ("css" :base-directory ,(org2jekyll-input-directory "css") :base-extension "css\\|el" :publishing-directory ,(org2jekyll-output-directory "css") :publishing-function org-publish-attachment :recursive t) ("web" :components ("images" "js" "css")))))
2. Method to create draft
;; simplify template (setq org2jekyll-default-template-entries '(("layout") ("title") ("tags") ("categories"))) (defconst org2jekyll-required-org-header-alist '((:title . 'required) (:categories . 'required) (:tags) (:layout . 'required)) ;; read filename from prompt (defun org2jekyll--read-filename () "Read the file name." (read-string "Filename: ")) (defun org2jekyll--init-buffer-metadata (&optional ignore-plist) "Return an plist holding buffer metadata information collected from the user. Any non-nil property in IGNORE-PLIST will not be collected from the user, and will instead have its value omitted in the returned plist." (-concat (unless (plist-get ignore-plist :author) (list :author org2jekyll-blog-author)) ;; (unless (plist-get ignore-plist :date) ;; (list :date (org2jekyll-now))) (unless (plist-get ignore-plist :layout) (list :layout (org2jekyll--input-read "Layout: " org2jekyll-jekyll-layouts))) (unless (plist-get ignore-plist :filename) (list :filename (org2jekyll--read-filename))) (unless (plist-get ignore-plist :title) (list :title (org2jekyll--read-title))) ;; (unless (plist-get ignore-plist :description) ;; (list :description (org2jekyll--read-description))) (unless (plist-get ignore-plist :tags) (list :tags (org2jekyll--read-tags))) (unless (plist-get ignore-plist :categories) (list :categories (org2jekyll--read-categories))))) ;; concat category, date string and filename ;; for example: _notes/_posts/language/2023-02-19-filename.org (defun org2jekyll-create-draft () "Prompt the user for org metadata and then create a new Jekyll blog post. The specified title will be used as the name of the file." (interactive) (let* ((metadata (org2jekyll--init-buffer-metadata)) (metadata-alist (org2jekyll--plist-to-alist metadata)) (category (plist-get metadata :categories)) (filename (plist-get metadata :filename)) (draft-file (org2jekyll--draft-filename "~/kimim/_draft/" filename)) (add-to-file-options (org2jekyll--get-template-entries metadata-alist)) (add-to-file-tuples (org2jekyll--alist-to-tuples add-to-file-options))) (unless (file-exists-p draft-file) (with-temp-file draft-file (insert (org2jekyll-default-headers-template add-to-file-tuples) "\n\n") (insert "* "))) (find-file draft-file)))
3. Publish
;; use .txt file ext as the temp file (defun org2jekyll--publish-post-org-file-with-metadata (org-metadata org-file) "Publish as post with ORG-METADATA the ORG-FILE." (let* ((project (-> "layout" (assoc-default org-metadata) ;; layout is the blog-project (assoc org-publish-project-alist))) (temp-file (->> org-file (replace-regexp-in-string ".org$" ".txt")))) (org2jekyll--publish-temp-file-then-cleanup org-file temp-file project))) (defun org2jekyll--space-separated-values-to-yaml (str) "Transform a STR of space separated values entries into yaml entries." (concat "[" (->> (if str str "") (s-split " ") (--filter (unless (equal it "") it)) (--map (format "%s" it)) (s-join ",")) "]")) ;; change org to txt, as .txt is the temp file, will be removed. (defun org2jekyll-install-yaml-headers (original-file published-file) "Read ORIGINAL-FILE metadata and install yaml header to PUBLISHED-FILE. Then delete the original-file which is intended as a temporary file. Only for org-mode file, for other files, it's a noop. This function is intended to be used as org-publish hook function." (let ((original-file-ext (file-name-extension original-file)) (published-file-ext (file-name-extension published-file))) ;; original-file is the temporary file generated which will be edited with ;; jekyll's yaml headers ;; careful about extensions: "post" -> org ; page -> org2jekyll ;; other stuff are considered neither, so it's a noop (when (and (or (string= "txt" original-file-ext) (string= "org2jekyll" original-file-ext)) (string= "html" published-file-ext)) (let ((yaml-headers (-> original-file org2jekyll-read-metadata org2jekyll--to-yaml-header))) (with-temp-file published-file (insert-file-contents published-file) (goto-char (point-min)) (insert yaml-headers)))))) (defun org2jekyll-publish-from-jekyll (org-file) (let* ((org-options (with-current-buffer (current-buffer) (org2jekyll-get-options-from-buffer))) (publish-fn (-> (plist-get org-options :layout) org2jekyll-post-p (if 'org2jekyll-publish-post 'org2jekyll-publish-page))) (final-message (funcall publish-fn org-file))) (progn (org2jekyll-publish-web-project) (org2jekyll-message final-message) (magit-status-setup-buffer)))) (defun org2jekyll-publish () (interactive) (load-theme 'kimim-light t) (let* ((buffer (current-buffer)) (org-file (buffer-file-name (current-buffer))) (filepath (file-name-directory org-file))) (if (string-prefix-p (file-truename org2jekyll-source-directory) filepath) (org2jekyll-publish-from-jekyll org-file) (let* ((filename (file-name-nondirectory org-file)) (movefile (concat org2jekyll-source-directory "/" (plist-get (org2jekyll-get-options-from-buffer) :categories) "/" (format-time-string "%Y-%m-%d-") filename)) (_ (rename-file buffer-file-name movefile)) (_ (switch-to-buffer (find-file-noselect movefile)))) (org2jekyll-publish-from-jekyll org-file)))))
4. Future plan
- complete TAGS, CATEGORIES from existing jekyll project
- with more and more modification, it turns out that I should fork org2jekyll now