HOME

这个网站是如何用 Org-mode 生成的?

Table of Contents

本站是用 Emacs Org-mode 制作而成,不仅内容用的是 Org-mode 格式,就连网站本身也是用 Org-mode 的 (org) Publishing 功能生成的。这篇文档不仅介绍了这个生成过程,同时还包括了执行该过程的源代码,而且可以直接用来运行。

目前为止,Org-mode 充当了 3 个角色:

  1. Org-mode 作为一种 Markup 语言,类似 Markdown
  2. Org-mode 作为静态网站生成器 (Static Site Generator),类似 Jekyll
  3. Org-mode 作为 Literate programming 工具,类似 Jupyter

1 Code

首先加载所需的库

(require 'ox-publish)
(require 'cl-lib)

1.1 项目定义

(setq chunyang-website
      (list "xuchunyang.me"
            :base-directory "~/xuchunyang.me"
            :recursive t
            :base-extension "org"
            :publishing-directory "~/html"
            ;; HTML publishing setting
            :publishing-function 'org-html-publish-to-html
            :html-preamble 'chunyang-html-preamble
            :html-postamble 'chunyang-html-postamble
            ;; Use sitemap as index.html
            :auto-sitemap t
            :sitemap-filename "index.org"
            :sitemap-title "Welcome to xuchunyang.me"
            :sitemap-format-entry 'chunyang-sitemap-entry
            :sitemap-sort-files 'anti-chronologically))

(setq chunyang-website-images
      (list "xuchunyang.me's images"
            :base-directory "~/xuchunyang.me"
            :recursive t
            :base-extension "png"
            :publishing-directory "~/html"
            :publishing-function 'org-publish-attachment))

1.2 帮助函数

(defun chunyang-not-index-p (filename)
  (not (string= (file-name-nondirectory filename) "index.org")))

(defun chunyang-abbrev-file-name (filename)
  "E.g. convert /Users/xcy/xuchunyang.me/Notes/eshell.org -> Notes/eshell.org."
  (let ((directory-abbrev-alist
         (list (cons (expand-file-name "~/xuchunyang.me/") ""))))
    (abbreviate-file-name filename)))

(defun chunyang-get-history (filename)
  (concat "https://github.com/xuchunyang/xuchunyang.me/commits/master/"
          (chunyang-abbrev-file-name filename)))

(defun chunyang-get-org-source (filename)
  (concat "https://raw.githubusercontent.com/xuchunyang/xuchunyang.me/master/"
          (chunyang-abbrev-file-name filename)))

(defun chunyang-create-html-link (href desc)
  (format "<a href=\"%s\">%s</a>" href desc))

(defun chunyang-html-preamble (pl)
  "Return a string which will be inserted at the begining of <body>.
PL is the property list of export options."
  (when (chunyang-not-index-p (plist-get pl :input-file))
    "<div id=\"org-div-home-and-up\"><a href=\"/\"> HOME </a></div>"))

(setq chunyang-author
      (format "<p>Author: %s <a href=\"%s\">&lt;%s&gt;</a></p>"
              "Chunyang Xu"
              "mailto:mail@xuchunyang.me"
              "mail@xuchunyang.me"))

(setq chunyang-creator
      (format "<p>Proudly Powered by %s &amp; %s</p>"
              "<a href=\"https://www.gnu.org/software/emacs/\">Emacs</a>"
              "<a href=\"http://orgmode.org\">Org</a> mode"))

(setq chunyang-validation-link
      ;; Image
      ;; "<p><a href=\"https://validator.w3.org/check?uri=referer\"><img src=\"http://www.w3.org/Icons/valid-xhtml10\" alt=\"Valid XHTML 1.0 Strict\" height=\"31\" width=\"88\" /></a></p>"
      "<p><a href=\"https://validator.w3.org/check?uri=referer\">Validate XHTML 1.0</a></p>")

(setq chunyang-org-link
      "<p><a href=\"http://orgmode.org\"><img src=\"http://orgmode.org/img/org-mode-unicorn-logo.png\" alt=\"Org-mode\" height=\"31\" width=\"31\" /></a></p>")

(defun chunyang-html-postamble (pl)
  "Return a string which will be inserted at the end of <body>.
PL is the property list of export options."
  (if (chunyang-not-index-p (plist-get pl :input-file))
      (let ((filename (plist-get pl :input-file))
            (created (and (plist-get pl :date)
                          (org-export-get-date pl "Created: %Y-%m-%d | ")))
            (modified (let ((time (chunyang-org-export-get-modified pl)))
                        (and time (format-time-string "Modified: %Y-%m-%d | " time)))))
        (concat (concat "<p>"
                        created
                        modified
                        (chunyang-create-html-link (chunyang-get-org-source filename) "Org source")
                        " | "
                        (chunyang-create-html-link (chunyang-get-history filename) "History")
                        "</p>")
                chunyang-author
                chunyang-creator
                chunyang-validation-link))
    (concat (format-time-string "<p>Modified: %Y-%m-%d</p>")
            chunyang-author
            chunyang-creator
            chunyang-validation-link)))

;; Adapted from `org-publish-sitemap-default-entry'.
(defun chunyang-sitemap-entry (entry style project)
  (if (directory-name-p entry)
      (file-name-nondirectory (directory-file-name entry))
    (let* ((file entry)
           (date (org-publish-find-date file project))
           (title (org-publish-find-title file project)))
      (if (string= (expand-file-name "Logs/")
                   (file-name-directory (expand-file-name file)))
          (format "%s » [[file:%s][%s]]"
                  (format-time-string "%Y %h %e" date)
                  file
                  title)
        (format "[[file:%s][%s]]"
                entry
                (org-publish-find-title entry project))))))

(defun chunyang-org-export-get-modified (pl)
  (let ((string
         ;; FIXME: Find a simple way
         (loop for sec in (assoc-default 'section (plist-get pl :parse-tree))
               when (and (eq 'keyword (car sec))
                         (string= (plist-get (cadr sec) :key) (upcase "Modified")))
               return (plist-get (cadr sec) :value))))
    (and string
         (apply #'encode-time (org-parse-time-string string)))))

1.3 开始导出

org-publish 默认根据源文件的时间截判断是否导出,所以如果我现在修改我的导出代码时,org-publish 也不一定会重新导出,为此需要禁用。

(defmacro chunyang-export-without-theme (&rest body)
  `(let ((original-theme (car custom-enabled-themes)))
     (when original-theme (disable-theme original-theme))
     ,@body
     (when original-theme (enable-theme original-theme))))

(chunyang-export-without-theme
 ;; Republish everything every time
 (let ((org-publish-use-timestamps-flag nil))
   (org-publish chunyang-website)))

(let ((org-publish-use-timestamps-flag nil))
  (org-publish chunyang-website-images))

2 如何导出

M-x org-babel-load-file

(org-babel-load-file (buffer-file-name))

3 如何部署

rsync -avz ~/html/ root@xuchunyang.me:/var/www/html/

我直接用了 root 账户,因为 rsync 通过需要输密码的 sudo 可能比较麻烦。

4 TODO Issues [1/6]

4.1 TODO 从 Batch 模式导出时,保持 Syntax Highlight

  • Syntax highlight 受到 Emacs Theme 的影响
  • rainbow-delimiters 如果开启会影响括号的颜色
  • 也许看看 e2ansi 会对解决这个问题有启发

4.2 TODO 自动化导出网站和部署

Docker, Travis CI, GitHub etc.

4.3 TODO 修改 CSS

4.4 TODO 收集其它也使用 Org 生成的网站

Sitemap for project site-org
GitLab Page 的 Org mode 示例,同样用的是 org-publish,源代码在 pages / org-mode · GitLab

4.5 DONE 使用 S-expression 表示 HTML

使用 SXML - Wikipedia 格式,从 MELPA 安装 esxml.el

(require 'esxml)

(sxml-to-xml
 '(html
   (body
    (h1 "Head")
    (p "Visit " (a (@ (href "https://example.com")) "example.com") "."))))
<html><body><h1>Head</h1><p>Visit <a href="https://example.com">example.com</a>.</p></body></html>

4.6 TODO 导出比较慢

考虑不再 let-bind org-publish-use-timestamps-flag 为 nil。

Created: 2017-03-14 | Modified: 2017-03-21 | Org source | History

Author: Chunyang Xu <mail@xuchunyang.me>

Proudly Powered by Emacs & Org mode

Validate XHTML 1.0