この記事は Emacs Advent Calendar 2018 - Qiita の 17 日目の記事です。よく見たら去年も 17 日目の担当でした。

はじめに

このサイトは markdown で書かれた記事を、静的サイトジェネレータの Hugo で HTML 化しています。

最近、1 記事ごとに markdown を直接書く Hugo 標準の方法をやめて、Org-mode で記事を書いた後に ox-hugo で Hugo 向け markdown を生成するやり方に変更してみました。

1 記事ごとに 1 つ書く必要がある Hugo の markdown ファイルを、Org-mode + ox-hugo で書くと 1 つの Org ファイルから生成することができるので管理が非常に便利になりました。 (1 つの markdown ファイルを 1 つの Org ファイルで生成する方法もあります)

ox-hugo は Org ファイルを Hugo 向け markdown として出力できる exporter で、 C-c C-e でのエクスポートに Hugo 用の出力機能を追加してくれるパッケージになります。

ox-hugo のための Org ファイルの雛形

なぜか公式サイトでは最初の方に全体像が分かるような項目がないのですが、ドキュメントの最後のあたりのページに Hugo の Section の詳細の解説があります。

自分は以下のような構成でブログを書いています。Hugo の content/ と同じ階層に blog.org ファイルを置いています。

<HUGO_BASE_DIR>
|--blog.org
|--config.toml
|--content/
| `--post/
|   `--2018/
|     `--12/
|       `--org-mode-with-ox-hugo.md
|--public/
`--themes/

このケースでの ox-hugo に必須なプロパティである HUGO_SECTION と HUGO_BASE_DIR の書き方は以下のようになります。

  • blog.org
#+HUGO_BASE_DIR: ./
#+HUGO_SECTION: post
#+author: sfus

* Blog Entries
  :PROPERTIES:
  :VISIBILITY: children
  :END:

** TODO Org-mode で記事を書いて Hugo 向け markdown を ox-hugo で自動生成する話
   :PROPERTIES:
   :EXPORT_FILE_NAME: org-mode-with-ox-hugo
   :EXPORT_HUGO_SECTION*: 2018/12/
   :EXPORT_DATE: 2018-12-17
   :EXPORT_HUGO_CUSTOM_FRONT_MATTER: :thumbnail "img/common/emacs.png"
   :EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :toc true
   :END:

本文...

:VISIBILITY: children は初期状態で一覧に表示させるために個人的に付けています。(必須ではないです)

EXPORT_DATE は日時のみを表示させるようにしています(時刻までは不要なので)

自分は post/ 直下ではなくエントリ単位で markdown ファイルを出力する post/ 配下のディレクトリを分けているので、

各エントリに :EXPORT_HUGO_SECTION: を設定して +HUGO_SECTION: を上書きしています。

各エントリに :EXPORT_HUGO_SECTION*: を設定して +HUGO_SECTION: に追加しています。(※ 修正しました)

最終的には公開される URL は以下のように config.toml[parmalinks]post で決まるので markdown ファイルを 同じディレクトリに分ける必要はない気もしますが、 Octopress 時代からこのディレクトリ構成にしているのでこのようにしています。 (個人的にもわかりやすいので)

  • config.toml
[permalinks]
  post = "/blog/:year/:month/:filename/"

ox-hugo の導入

基本的には package-install やその他の方法でインストールしたのち、以下のように公式ドキュメントの通りに設定します。

(require 'ox-hugo-auto-export) ;If you want the auto-exporting on file saves

(with-eval-after-load 'ox
  (require 'ox-hugo))

ox-hugo-auto-export は後述する Auto-export の設定のためのパッケージで、必要な場合は設定しておいてください。

あとは C-c C-e H H (現在行のサブツリーを 1 つの markdown としてエクスポート)、または C-c C-e H A (全エントリを各 markdown ファイルにエクスポート) すれば ox-hugo の実行は完了です。

ブログエントリの TODODONE にすることで draft = false になるので、公開するときは変更を忘れないようにしましょう。(よく忘れる)

Front Matter のカスタマイズ

Hugo は Front Matter という、 +++ (TOMLの場合) などで区切られた markdown ファイルの上部に書く専用のパラメータがあります。

Hugo 標準の Front Matter については公式サイトに説明があるので、こちらを参考に PROPERTIES を設定して目的の Front Matter を出力することができます。

更に ox-hugo は任意の Front Matter を出力する PROPERTIES を設定することができます。

このエントリの PROPERTIES は以下のように書いています(抜粋)。

  • blog.org
:PROPERTIES:
:EXPORT_FILE_NAME: org-mode-with-ox-hugo
:EXPORT_HUGO_SECTION*: 2018/12/
:EXPORT_DATE: 2018-12-17
:EXPORT_HUGO_CUSTOM_FRONT_MATTER: :thumbnail "img/common/emacs.png"
:EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :toc true
:END:

この :EXPORT_HUGO_CUSTOM_FRONT_MATTER: がカスタム Front Matter の部分です。複数行に分けて書く場合は 2 つ目以降を :EXPORT_HUGO_CUSTOM_FRONT_MATTER+: のように + を付けて書くことで任意の Front Matter を追加できます。

上記の場合、以下のような Front Matter を持つ Hugo 用 markdown ファイルが出力されます。 たとえばこの thumbnail は今使っているテンプレート用の Front Matter です。

  • org-mode-with-ox-hugo.md
+++
title = "Org-mode で記事を書いて Hugo 向け markdown を ox-hugo で自動生成する話"
author = ["sfus"]
date = 2018-12-17
draft = false
thumbnail = "img/common/emacs.png"
toc = true
+++

Auto-export の設定

(2019/01/4 追記) この項目は一部古くなっています。ページ下部参照


毎回 C-c C-e H H で markdown ファイルを出力するのも面倒になってくると、保存時に自動でエクスポートしてもらいたくなります。 ox-hugo には Auto-export 機能が提供されています。

Auto-export を行うには、前述の通り ox-hugo-auto-export を require しておき、 バッファローカル変数 org-hugo-auto-export-on-savet に設定します。 設定が有効になっていれば、ファイル保存時に markdown へのエクスポート (C-c C-e H H) が自動で走ります。

バッファローカル変数の設定方法は、プロジェクト全体、または Org ファイル単体で有効にさせる方法の 2 つが公式サイトで説明されています。 自分は Org ファイル単体で有効にしています。

バッファローカル変数を有効にした状態で Org ファイルを開いた際に、この変数を有効にするかどうかを聞かれた場合は ! キーで安全な変数として保存する、を選択してください。 以下のように init.elcustom-file (未定義なら user-init-file) で設定されたファイルにカスタマイズ変数がセットされます。

(custom-set-variables
   :
 '(safe-local-variable-values (quote ((org-hugo-auto-export-on-save . t))))
   :
  )

プロジェクト全体で有効にする

プロジェクト全体で有効にする場合は、まず <HUGO_BASE_DIR>/.dir-locals.el ファイルを作成します。 上記のように Org ファイルが <HUGO_BASE_DIR> 直下にある場合は以下のように書きます。

  • .dir-locals.el
((org-mode . ((org-hugo-auto-export-on-save . t))))

公式ドキュメントでは、Org ファイルが <HUGO_BASE_DIR>/content-org/ 配下にある場合の説明が書かれています。その場合は以下のようになります。

  • .dir-locals.el
(("content-org/"
  . ((org-mode . ((org-hugo-auto-export-on-save . t))))))

この Directory Variables の説明は Emacs のマニュアルをご参照ください。(以下は日本語訳)

Org ファイル単体で有効にする

自分は以下のように、 Org ファイル末尾の Footnotes に org-hugo-auto-export-on-save: t をセットしています。

  • blog.org
* Footnotes
* COMMENT Local Variables                          :ARCHIVE:
# Local Variables:
# org-hugo-auto-export-on-save: t
# End:

org-capture の設定

Org-mode のエントリをすぐに追加できる org-capture に、上述のようなブログのテンプレートを登録しておくことで 楽にブログ記事を書き始められます。

自分の設定はおおむね上記公式サイトの設定の通りですが、前述した PROPERTIES を生成するようにしています。 EXPORT_HUGO_SECTION*EXPORT_DATE 、カスタム Front Matter を追加しています。

;; Populates only the EXPORT_FILE_NAME property in the inserted headline.
(with-eval-after-load 'org-capture
  (defun org-hugo-new-subtree-post-capture-template ()
    "Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information."
    (let* ((section (format-time-string "%Y/%m" (org-current-time)))
           (date (format-time-string "%Y-%m-%d" (org-current-time)))
           (title (read-from-minibuffer "Post Title: ")) ;Prompt to enter the post title
           (fname (org-hugo-slug title)))
      (mapconcat #'identity
                 `(
                   ,(concat "* TODO " title)
                   ":PROPERTIES:"
                   ,(concat ":EXPORT_FILE_NAME: " fname)
                   ,(concat ":EXPORT_HUGO_SECTION*: " section)
                   ,(concat ":EXPORT_DATE: " date)
                   ,(concat ":EXPORT_HUGO_CUSTOM_FRONT_MATTER: :thumbnail \"img/common/emacs.png\"" )
                   ,(concat ":EXPORT_HUGO_CUSTOM_FRONT_MATTER+: :toc true")
                   ":END:"
                   "%?\n")                ;Place the cursor here finally
                 "\n")))
  (add-to-list 'org-capture-templates
               '("h"                ;`org-capture' binding + h
                 "Hugo post"
                 entry
                 ;; It is assumed that below file is present in `org-directory'
                 ;; and that it has a "Blog Ideas" heading. It can even be a
                 ;; symlink pointing to the actual location of all-posts.org!
                 (file+olp "blog.org" "Blog Entries")
                 (function org-hugo-new-subtree-post-capture-template))))

ちなみに org-capture はデフォルトでキーにバインドされていないので C-c c に割り当てています。

(global-set-key (kbd "C-c c") 'org-capture)

上記の設定では C-c c h と打てばブログエントリを書く準備が完了します。

#BEGIN_XXX#END_XXX を展開する Easy templates

技術ブログを書く上でよく使うのがコードブロックですが、 Org-mode にそれっぽいキーバインドがなくて最初探してしまいました。

同じような質問がされているのでみんな最初悩むのかなと思います。

org mode - How to set a short-cut for #+BEGIN_SRC #+END_SRC? - Emacs Stack Exchange

上記の通り、例えば <s と打って TAB で補完すると #BEGIN_SRC#END_SRC が展開されます。

これは Org-mode 標準の Easy templates という機能になります。公式ドキュメントはこちら:

以下は自分がよく使うものの抜粋です。

<e と打って TAB

#+BEGIN_EXAMPLE

#+END_EXAMPLE

と展開されます。これは ox-hugo によって

```text
〇〇〇
```

という markdown になります。

<q と打って TAB

#+BEGIN_QUOTE

#+END_QUOTE

と展開されます。これは ox-hugo によって

> 〇〇〇

という markdown になります。

<s と打って TAB

#+BEGIN_SRC

#+END_SRC

と展開されます。 #+BEGIN_SRC を使う場合はその後に言語の名前を書きます。言語名は Hugo のドキュメントの Syntax Highlighting | Hugo あたりを参考に。

たとえば #+BEGIN_SRC emacs-lisp と書くと、ox-hugo によって

```emacs-lisp
〇〇〇
```

という markdown になります。

ソースブロックのカスタマイズについては ox-hugo のドキュメントに記載があります。

おわりに

Org-mode + ox-hugo で Hugo ブログを書く設定方法と各種ノウハウをお伝えしました。

ちなみに去年 tabulated-list-mode で作ろうとしていたのは Hugo の管理パッケージだったのですが、 Org-mode で書くほうが自分に合っていたので書きかけのパッケージはお蔵入りになりました…

(似たようなコンセプトで easy-hugo というパッケージがあります)

追記 (2018/12/24)

本エントリを公開してすぐに ox-hugo 作者の方から、 EXPORT_HUGO_SECTIONHUGO_SECTION を上書きするんじゃなくて EXPORT_HUGO_SECTION* を使えば親の HUGO_SECTION を継承できるよってコメントを頂いたので修正しました ☺️

追記 (2019/01/4)

最新版では ox-hugo-auto-exportrequire が不要になり、 .dir-locals.el の書き方も変更になったそうです…😇

Deprecation Notices — ox-hugo - Org to Hugo exporter