org-publishを使ってorgでブログを書く話



# はじめに

この記事は [Emacs Advent Calendar 2018 - Qiita](https://qiita.com/advent-calendar/2018/emacs) の14日目の記事です。
前日はtakuchalleさんの「[Emacs で Dart 開発環境をセットアップ](https://blog.takuchalle.me/post/2018/12/13/emacs_dart_setup/)」でした。

# モチベーション

私はOrg-Modeで日常のほとんどすべての文書作成を行っており、そもそも私は「Org-Modeを使うためにEmacs入門した」ほど大きな存在です。

最初にOrg-Modeの公式サイトで紹介されている[論文](https://www.jstatsoft.org/article/view/v046i03)から画像を引用します。

![org-mode-thesis.png](https://qiita-image-store.s3.amazonaws.com/0/9567/a8e4e394-fad9-9cae-8f79-5b5ac6b8ef96.png)


この画像の通り、Org-Modeはそれ自身が高性能なマークアップ言語でありながら、[任意の言語のコード実行](http://misohena.jp/blog/2017-10-26-how-to-use-code-block-of-emacs-org-mode.html)、さらには[表計算](http://lioon.net/org-table-tutorial)！などを用いて文書を作成し、それを[任意のフォーマットへのアウトプット](https://orgmode.org/manual/Exporting.html)が可能で、すべての文書のハブともいうべき存在になっています。これを万能文書と呼ぶことにします。（21フォーマットのエクスポーターが標準添付（contrib）され、MELPAにはさらに29種ものエクスポーターが登録されています）

例えば現代においてばHTMLをタグ直書きで文書を書いている人はほとんどいないと思います。なぜならHTMLタグは人間が書くには冗長すぎる他（閉じタグなど）、本質的でないメタ情報が文書中に散らばり（head, id, class）、文書の再利用性が極端に低くなるからです。

よって賢明な方ならばPHPのようなテンプレート言語で生成したり、マークダウンから[pandoc](https://pandoc.org/)で変換したりすると思います。これによりメタ情報部と文書部が分離でき、再利用性が上がり、保守性も向上します。

つまりHTMLとかいう冗長で扱いづらいフォーマットは動的に生成されるべきターゲットであって、人間はもっと書きやすい別のフォーマットで文書を作成するべきなのです。

一方、マークダウンには根強いファンがあり、そしてpandocにより万能文書としての機能を備えてきたように思えますが、いろいろ[苦悩している](https://note.solarsolfa.net/n/n697389a6be3b)ようです。

Orgユーザーからすれば、なぜそんな茨の道を進もうとしているのか理解できません。
Org文書で作成しさえすれば豊富なターゲットフォーマット、つまりWebには静的HTMLやHugo, Wordpress, Qiitaにより公開し、印刷にはLaTeXから生成したPDF、スライドにはブラウザで表示する[Reveal.js](https://revealjs.com/#/)や[LaTeX+Beamer](http://ayapin-film.sakura.ne.jp/LaTeX/Slides/Beamer-tutorial.pdf)で生成したスライド投影用のPDFを使用でき、編集時にもOrg-Modeが提供するOrg文書のための様々なコマンドが利用できます。
こんな悲しい事態にはならなくてすむのです。 ~~（この講義、今期末まであるの辛い。。）~~
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">今受けてる授業大学4年間で最悪の可能性ある。環境の意味で。<br>数学記号変な意味でつかうし、「Wordで」作ったA4の資料を「そのまま」プロジェクターで写すから、いちいち写真撮ってデジタルズームしないと読めない。そして読めるとは限らない。つらすぎる、、 <a href="https://t.co/QDRQIOD6ZL">pic.twitter.com/QDRQIOD6ZL</a></p>&mdash; Conao (@conao_3) <a href="https://twitter.com/conao_3/status/1073019805936971776?ref_src=twsrc%5Etfw">2018年12月13日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


さて、あるOrg文書を変換するのはとても簡単で、例えばこの[Org文書](https://raw.githubusercontent.com/conao3/shogi-detect/master/Readme.org)からこの[PDFファイル](https://github.com/conao3/shogi-detect/blob/master/report.pdf)をコマンド1発で変換できます。（内容はやっつけなので(ry&#x2026;）

上の文書には数式や表が含まれていませんが、私は電気系の学生として毎期の実験レポートをOrg文書から生成したLaTeXから変換したPDFですべてのレポートを提出しましたし、卒論を[書いた人](http://d.hatena.ne.jp/r_takaishi/20100209/1265680490)や[作成入門を書かれている方](http://akisute3.hatenablog.com/entry/2013/12/28/144918)もいます。

一方Org文書が複数に渡る場合、（最終的に1つのファイルにするならinclude文を使えばよいだけなのですが、それぞれを違うファイルとして変換したい場合）Org文書同士のリンクやOrg文書へのリンクを張るメタ的なOrg文書を生成したくなったりと途端に難しくなります。

この操作を実現するのがorg-publishです。ただ、設定に癖があるのでその設定を紹介するとともに、もっとOrg-Modeのユーザーが増えたらいいなと布教をするものです。


# マニュアル

日本語マニュアルは少々古いですが[ここ](https://takaxp.github.io/org-ja.html#g_t_00e5_0085_00ac_00e9_0096_008b)、英語のマニュアルは[ここ](https://orgmode.org/manual/Publishing.html)においてあります。


# なにがどうなるのか

`<root>/src/` 下に自由にフォルダやOrg文書を作成して配置します。そしてPublishコマンドを発行することにより `<root>/src/` 下のすべてのOrg文書を「その階層を保存した上で」 `<root>/archives/` にHTMLで書き出すことが出来ます。

    ~/public_html/orglyth/
       ├── index.inc
       ├── index.org
       ├── archives/
       ├── parts/
       │   └── index.html
       └── src/
           ├── index.inc
           └── index.org
           ├── elisp/
           │   ├── index.inc
           │   ├── index.org
           │   ├── boolean.org
           │   └── with-temp-file.org
           └── emacs/
               ├── index.inc
               ├── index.org
               ├── install.org
               └── org-mode
                   ├── index.inc
                   ├── index.org
                   ├── basis-syntax.org
                   ├── org-list.html
                   ├── org-list.org
                   └── ox-publish.org

org-publishを使用する第一の利点として[sitemapを自動生成](https://conao3.com/archives/sitemap.html)させることが出来ます。

別途 `<root>` に置いた `.htaccess` の設定をすれば、例えば(<https://conao3.com/archives/blog/advent/emacs/org-publish-blog>)にアクセスすると普通にHTMLを閲覧させてarchivesの部分をsrcに変更した(<https://conao3.com/src/blog/advent/emacs/org-publish-blog>)にアクセスするとソースのOrg文書を見せることも出来ます。

```el
AddDefaultCharset utf-8
AddType "text/plain" org

ErrorDocument 503 /archives/error/maintenance.html
ErrorDocument 404 /archives/error/404.html
DirectoryIndex index.html index.org

SetEnvIf REDIRECT_HTTPS (.*) HTTPS=$1 
<IfModule mod_rewrite.c>
RewriteEngine on 
RewriteCond %{ENV:HTTPS} !on 
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 
</IfModule>

# temporary-maintenance
# RewriteCond %{REQUEST_URI} !=/archives/error/maintenance.html
# RewriteRule ^.*$ - [R=503,L]


RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.org -f
RewriteRule ^(/src/.*)$ $1.org [L]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(/archives/.*)$ $1.html [L]
```

`archive` を `src` に変えてソースのOrg文書を見せることはすでに出来ているのですが、例えば `archive` を `pdf` に変えたら記事のPDFをダウンロードさせることも簡単にできると思います。

もちろん文書間リンクや画像リンクなどはいつもどおり書いたものをorg-publishがWebで表示できるように、よしなに変更してくれます。具体的には `.org` へのリンクは `.html` へのリンクに変更され、メディアはそのままターゲットディレクトリの対応するパスにコピーします。（orglyth.elでは `orglyth-html-default-resources-option` で制御してます）


# init.elの設定

私は今init.elの大改革（[.emacs.dの構造から見直したり](https://qiita.com/conao3/items/851f6dea9e94ce73f385)、 [use-packageの別実装を作ったり](https://github.com/conao3/leaf.el)（Qiita記事は21日目に予約済み）、[新しいテストフレームワークを作ったり](https://qiita.com/conao3/items/098242804eb34da61f49)）を行っていて、init.elの編集まで作業が追いついていないのですがとりあえずOrg-Modeに関する今の設定は以下です。

新しいパッケージマネージャ(feather.el)の開発が終わり次第、シームレスに設定できるようになりますが、現状では伝統的なパッケージ管理のように `site-lisp` にレポジトリをクローンして `load-path` を通す必要があります。

-   [leaf.el](https://github.com/conao3/leaf.el) - Emacs-22から利用できる `use-package` の別実装
-   [feather.el](https://github.com/conao3/feather.el)(開発中) - Emacs-22から利用できる全く新しいパッケージマネージャ
-   [orglyth.el](https://github.com/conao3/orglyth.el) - ox-\*の設定集

```el
(leaf org
  :init
  (leaf org-plus-contrib :ensure t :require nil)

  :setq
  ((org-directory                         . "~/Documents/org/")
   (org-default-notes-file                . "~/Documents/org/notes.org")
   (org-agenda-files                      . "~/Documents/org/notes.org")
   (org-return-follows-link               . t)
   (org-startup-indented                  . t)
   (org-indent-mode-turns-on-hiding-stars . t)
   (org-indent-indentation-per-level      . 2)
   (org-src-window-setup                  . 'other-window)
   (org-use-sub-superscripts              . '{})
   (org-image-actual-width                . nil)
   (org-highlight-latex-and-related       . '(latex script entities)))

  :config  
  (leaf ob
    :setq ((org-confirm-babel-evaluate . nil))
    :config
    (leaf ob-ipython
      :when (executable-find "jupyter")
      :ensure t
      :config
      ;; depend of jypyter, ipython
      (add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append))

    (leaf ob-plantuml
      :when (executable-find "plantuml")
      :ensure t
      :setq ((org-plantuml-jar-path . plantuml-jar-path))
      :setq ((org-confirm-babel-evaluate . nil)))

    (org-babel-do-load-languages 'org-babel-load-languages
                                 '((ipython . t)
                                   (plantuml . t)
                                   (org . t)
                                   (R . t)
                                   (C . t)
                                   (emacs-lisp . t))))
  (leaf ox
    :config
    (leaf orglyth
      :config
      (leaf orglyth-html
        :setq
        ((orglyth-html-enable-option    . t)
         (orglyth-html-use-ftp          . nil)
         (orglyth-html-local-root-path  . "~/Documents/sakura/orglyth/")
         (orglyth-html-remote-root-path . "~/Documents/sakura/remote/")
         (orglyth-html-ftp-root-path    . "/ftp:conao3@conao3.com:~/www/orglyth/"))
        :config
        (orglyth-html-init)
        (orglyth-html-project-init))

      (leaf orglyth-latex
        :setq ((orglyth-latex-enable-option . t))
        :config
        (orglyth-latex-init)))))
```

この設定は拙作の[leaf.el](https://github.com/conao3/leaf.el)により以下に変換されます。

```el
(progn
  (progn
    (progn
      (leaf-backend/:ensure-package 'org-plus-contrib 'org-plus-contrib)
      (progn)))
  (progn
    (require 'org)
    (setq org-directory "~/Documents/org/")
    (setq org-default-notes-file "~/Documents/org/notes.org")
    (setq org-agenda-files "~/Documents/org/notes.org")
    (setq org-return-follows-link t)
    (setq org-startup-indented t)
    (setq org-indent-mode-turns-on-hiding-stars t)
    (setq org-indent-indentation-per-level 2)
    (setq org-src-window-setup 'other-window)
    (setq org-use-sub-superscripts '{})
    (setq org-image-actual-width nil)
    (setq org-highlight-latex-and-related
          '(latex script entities))
    (progn
      (require 'ob)
      (setq org-confirm-babel-evaluate nil)
      (if
          (executable-find "jupyter")
          (progn
            (leaf-backend/:ensure-package 'ob-ipython 'ob-ipython)
            (progn
              (require 'ob-ipython)
              (add-hook 'org-babel-after-execute-hook 'org-display-inline-images 'append))))
      (if
          (executable-find "plantuml")
          (progn
            (leaf-backend/:ensure-package 'ob-plantuml 'ob-plantuml)
            (progn
              (require 'ob-plantuml)
              (setq org-plantuml-jar-path plantuml-jar-path)
              (setq org-confirm-babel-evaluate nil))))
      (org-babel-do-load-languages 'org-babel-load-languages
                                   '((ipython . t)
                                     (plantuml . t)
                                     (org . t)
                                     (R . t)
                                     (C . t)
                                     (emacs-lisp . t))))
    (progn
      (require 'ox)
      (progn
        (require 'orglyth)
        (progn
          (require 'orglyth-html)
          (setq orglyth-html-enable-option t)
          (setq orglyth-html-use-ftp nil)
          (setq orglyth-html-local-root-path "~/Documents/sakura/orglyth/")
          (setq orglyth-html-remote-root-path "~/Documents/sakura/remote/")
          (setq orglyth-html-ftp-root-path "/ftp:conao3@conao3.com:~/www/orglyth/")
          (orglyth-html-init)
          (orglyth-html-project-init))
        (progn
          (require 'orglyth-latex)
          (setq orglyth-latex-enable-option t)
          (orglyth-latex-init))))))
```

Orgの命名規則として `ob-*` はbabel（任意コード実行）などコードブロック関連、`ox-*` はエクスポート関連のパッケージとなっています。

`ox` ブロックは実験レポートを日常的に書いていたこともあり、いろいろとカオスだったのですが[orglyth.el](https://github.com/conao3/orglyth.el)というパッケージに括りだすことでinit.elの見通しを良くすることが出来ました。

orglyth.elの設定部を抜き出すと以下になります。このように少しの変数のみを定義するのみです。後はorglyth.elに丸投げします。

-   `orglyth-html-enable-option` : tの時、`(orglyth-html-init)` をした時に関連する変数を変更します。(何を変えるかはソースを見たほうが早いです)
-   `orglyth-html-use-ftp` : tの時、プロジェクト:websiteの設定がftp転送したサーバー上になります。 遅いのでnilにしてます。
-   `orglyth-html-local-root-path` : ローカルのOrg文書ルートフォルダです。
-   `orglyth-html-remote-root-path` : プロジェクト:websiteのターゲットフォルダです。
-   `orglyth-html-ftp-root-path` : プロジェクト:websiteのターゲットフォルダです。
-   `(orglyth-html-init)` : 変数の初期化
-   `(orglyth-html-project-init)` : orglyth.elのプロジェクトをOrgに登録する。

```el
(leaf orglyth
  :config
  (leaf orglyth-html
    :setq
    ((orglyth-html-enable-option    . t)
     (orglyth-html-use-ftp          . nil)
     (orglyth-html-local-root-path  . "~/Documents/sakura/orglyth/")
     (orglyth-html-remote-root-path . "~/Documents/sakura/remote/")
     (orglyth-html-ftp-root-path    . "/ftp:conao3@conao3.com:~/www/orglyth/"))
    :config
    (orglyth-html-init)
    (orglyth-html-project-init))

  (leaf orglyth-latex
    :setq ((orglyth-latex-enable-option . t))
    :config
    (orglyth-latex-init)))
```

`(orglyth-html-projet-init)` はpublishターゲットを複数持っていると、候補が溢れて管理しづらくなる（かもしれない）ので必要な時に登録できるようにしています。私はorg-publishを他の用途には使っていないので起動時に登録するようにしています。


# 使い方

![org-publish-1.png](https://qiita-image-store.s3.amazonaws.com/0/9567/7b55ab8c-a965-5699-c901-ea3a899340e0.png)
![org-publish-2.png](https://qiita-image-store.s3.amazonaws.com/0/9567/416e470c-7cd9-27c2-523e-29c49643c003.png)


前段の設定を行うと `C-c C-e (org-export-dispatch)` を押し、ディスパッチャに入り、`P x` を押すと登録されているプロジェクトが確認できます。

様々なプロジェクトが登録されていますが、我々が実行するのは一般的に`local` か `website` のメタプロジェクトです。

org-publishの思想から言うと `local` で変換結果を確認してその後 `website` を実行してftpで転送させたいのでしょうが、遅すぎてその間Emacsのメインスレッドがブロックされるので、実際には `local` を実行してその後ターミナルでサーバーに `rsync` しています。


# まとめ

`org-publish` により静的HTMLサイトを構築する方法を説明しました。

私はもともと `org2blog` を使ってWordpressに投稿していたのですが、過去記事が編集しづらかったり、そもそもWordpressが嫌いになってしまったりと使うのを諦めてしまいました。

最近Org-Mode界隈では `ox-hugo` を用いてブログを書くのがブームになっている([takaxp](https://pxaka.tokyo/blog/categories/hugo/)さんなど)ようですが、あまり日の目を見ない `org-publish` でも同様のことができることを知ってもらえればと。

Org文書変換前や変換後に任意のElisp関数を登録できたりと、とても自由度高そうなので`org-publish` でブログを書いてみるのも面白いかもしれません。

プログラマたる者、素人の方が作られた[阿部寛さんのHP](http://abehiroshi.la.coocan.jp/)より高速なウェブページを構築したいですよね？実際それは[けっこう大変らしい](https://qiita.com/Morix1500/items/0eac072a027d478a6b83)のですが、org-publishでは静的HTMLを吐くので、表示速度追求も面白いかと思います。

AMP対応してGoogleのサーバーにホスティングしてもらうのも手かもしれません。（orglyth.elで将来的に対応予定）

（ちなみに[るびきちさん](https://github.com/rubikitch/daily-emacs-jp)に触発されて、私もブログのソースを[GitHubに公開](https://github.com/conao3/orglyth-src)してます。実際、URLの `archive` を `src` に変えれば見えるんですが、一覧したいときなどに利用ください。）