モーレツ! Org mode 教室 その7: org2blog で WordPress に投稿する

Org mode でブログを書きたい

Org modeに慣れてくるとなんでも Org modeで書きたくなってくるもので、ブログも Org 記法で書きたいのである。本ブログのような WordPress ベースのシステムでこれを実現するのが org2blog で、XML-RPC を使って Emacs からウェブなどを介さず直に WordPress へ投稿する手段を提供してくれる。XML-RPC (というか MetaWeblog API)ベースなので、Wordpress 以外の CMS でもおそらく使えるとは思うが(名前も org2 blog だし)、基本的には WordPress が想定されているようである。SOAPだJSON-RPCだというご時世にXML-RPCというのはやや古風だが、ちゃんと動く。

例によって org2blog もすでに MELPA レポジトリに入っているので、前回説明した straight.el と use-package の組み合わせで手軽にインストールできる…はずなのだが、残念ながら若干の小細工が必要だ。

org2blog のインストール

話の前提として、Wordpress の XML-RPC インターフェースにアクセスできなければならない。最近では脆弱性対策ということでサーバのほうでアクセスが制限されていることが多いので、これから org2blog を使うというマシンから、Wordpressを入れたディレクトリの直下にある xmlrpc.php にウェブブラウザからアクセスできることを確認すると良い(「XML-RPC server accepts POST requests only.」と表示されるはず)。おそらくhttp://www.yourblog.com/xmlrpc.php といった感じのURLで、WordPress.com のブログホスティングでも同じである。もちろん強いパスワードを設定し、アクセスできるIPアドレスを絞るなどの対策はしておくべきだろう。

さて、org2blog そのものには特に問題ないのだが、org2blog が中で使っている xml-rpc-el の現在のバージョンには若干問題があり、日本語のような Unicode キャラクタを含むリクエストがエラーになってしまう。そこで、早速前回紹介した straight.el の出番となる。直したバージョンを自分の Github レポジトリで公開している人がいるので、オフィシャルではなくそちらを使ってインストールするわけだ。具体的には、org2blog に関する記述の前に、init.el に

(straight-use-package
 '(xml-rpc-el :type git :host github :repo "grettke/xml-rpc-el"))
(use-package xml-rpc
  :defer t  
  )

などと書くだけでよい。

org2blog そのものの設定は、最小限で

(use-package org2blog
  :after org
  :defer t
  :config
  (setq org2blog/wp-blog-alist
        `(("myblog"
           :url "https://www.yourwordpresshost.com/xmlrpc.php"
           :username ,(car (auth-source-user-and-password "myblog"))
           :password ,(cadr (auth-source-user-and-password "myblog"))
           )
          ))
  (setq org2blog/wp-buffer-template
        "#+TITLE: 
#+CATEGORY: 
#+TAGS: 
#+OPTIONS:
#+PERMALINK: \n")
  )

こんな感じになるだろうか。 細かい説明は不要だと思うが、 wp-blog-alist には当然複数のブログを設定することもできる。

auth-source を用いた認証

問題は認証で、ユーザ名とパスワードを与える必要があるわけだが、流石に平文で init.el に書きこむのは気が引ける。そんなときは、Emacs 標準の認証管理機能である auth-source を使うと良いだろう。具体的には、~/.authinfo というファイルを用意し、

machine myblog login WordPressのユーザ名 password WordPressのパスワード

などと書いた上で、

$ gpg -c .authinfo

と GnuPG で暗号化して ~/.authinfo.gpg として置いておけばよい。あとは、 必要なときに auth-source が pinentry のようなパスフレーズ入力ダイアログを呼び出すので、そこから復号化できる。GPG は、Windows でも gpg4win などをインストールすれば普通に使えるだろう。org2blog を毎回使うわけではないのに起動時に毎回 GPG のパスフレーズを聞いてきて面倒くさいという場合は、上記の例のように (use-package org2blog):defer t を指定してやれば、必要なときまで読み込みが遅延されるので、パスフレーズ入力用ダイアログも org2blog の起動時に呼び出されるようになる。

org2blog でブログを書く

ここまで設定した上で、 M-x org2blog/wp-login を実行すると、Wordpress にログインして準備完了となる。あとは、 M-x org2blog/wp-new-entry を実行するとドラフトバッファが準備される。このドラフトのひな形を指定しているのが org2blog/wp-buffer-template だ。Org mode は、ファイル(というかバッファ)に #+FOO: bar という形式で書くことで、ファイル毎に細かく設定を変える仕組みがあるのだが(詳しくはThe Org Manual: In-buffer settingを参照)、ここではWordpressの記事のタイトルやパーマリンクを指定するのに使われている。 #+DATE: を使えば公開日時も指定できる。

ここまで来れば、後は Org 記法で記事を書くだけだ。書くにあたって記法に全く制限はなく、リンクを張ればリンクとして扱われるし、画像ファイルへリンクを張れば自動的にアップロードされる。書き終わったら、 M-x org2blog/wp-post-buffer でドラフトがサーバに送られ、希望すればウェブブラウザを呼び出してプレビューも見られる。もちろんいきなり公開することも可能だ。

ちなみに一度サーバに記事が送られると、ドラフトバッファには先ほどの In-buffer setting の一つとして #+POSTID: 数字 という行が追記される。これがあれば、Emacs 側で追記や編集をした上でまた M-x org2blog/wp-post-buffer を実行すれば、記事がダブることなくちゃんとサーバ側も変更に追従してアップデートされるのである。他にも、サブツリー単位で送信したり MathJax を用いた数式を書いたりといろいろなことが出来るのだが、細かいコマンド等は org2blog の Gihub レポジトリにある説明を参照してほしい。

いずれにせよ、org2blog を使うと WordPress のウェブインターフェースからちまちまパーマリンクやらタグやらを入力する必要がなくなり、まあ大したことではないのだが、気分的には大変楽である。Wordpress をお使いならぜひお試しください。

 

モーレツ! Org mode 教室 その6: straight.el で Org mode をインストールする

straight.el と use-package を使ったパッケージ管理

厳密にはOrg mode特有の話ではないのだが、最近のEmacsのパッケージ管理について。

Emacsは、Emacs Lispで書かれたパッケージを導入することで柔軟に機能を拡張できる。大昔はEmacs Lispのファイルをひとつひとつ手で落としてきて設定していたのだが、しばらく前からはpackage.elを使い、パッケージ間の依存関係に従ってMELPAのようなパッケージ・アーカイヴから芋づる式に自動インストール、といった管理が主流になった。さらに最近では、Caskel-getBorgQuelpaなど様々なパッケージマネージャが登場している。

なぜそういうことになるかというと要はどれも一長一短あるからだが、比較的最近開発されたstraight.elは先行する他のパッケージマネージャをよく研究していて、なかなか筋が良い設計になっている。私自身ここ数年 Cask を使っていたのだが、つい最近straight.elに乗り換えた。というわけで、その紹介をしたい。

straight.el のインストール

先日の記事でもちらっと書いたが、 Cask は実装がEmacs Lispで完結しておらず、 Cask 自体のインストールにPythonが必要だった。最近では大概のシステムにPythonが入っているので、ある意味どうでもいいとは言えるのだが、Windowsなどではひと手間増えてしまうのは間違いない。一方 straight.el はパッケージの管理にGitが必要だが、インストールそのものは ~/.emacs.d/init.el に以下を書いてEmacsを起動するだけである(これは el-get も同じだが)。

;; straight.el のインストール
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

ようはGithubのstraight.elのレポジトリからinstall.elを落としてきて、それを実行しているわけですね。 straight.el 関係の全ては基本的に ~/.emacs.d/straight の中にあるので、何かおかしくなったらこのディレクトリを消してやり直せばよい。

straight.el が好ましい点の一つは、use-packageと緊密に連携しているところである。 use-package に関しては日本語でも他に詳しい解説がいくらもあるのでそちらを参照してもらいたいが、大ざっぱに言えば、 ~/.emacs.d/init.el でのライブラリの読み込みや、起動時の遅延読み込みの設定が明快に書けるようになる便利なマクロだ。 (require 'foobar) と書き続けて数十年のオールドタイマーだと目が慣れるまでは違和感があると思うが、いったん要領を掴めば書き換えはそんなに手間ではない。

straight.eluse-package の連携を使うには、

(straight-use-package 'use-package)
(setq straight-use-package-by-default t)

と書いておく。このように設定すると、

(use-package foobar)

~/.emacs.d/init.el に書くだけで、MELPAなどのレポジトリから適当に foobar パッケージを落としてきてインストールしてくれるのである。しかも、 straight.el の場合落としてきたパッケージは ~/.emacs.d/straight/repos 以下にクローンされたGitレポジトリとして展開されているので、内容確認や手直しも簡単だ。なので straight.el にはパッケージのアップデートという概念はなく、fetchとかmergeとかpushなのである。

また、自分で手でインストールしたとか、Emacsと一緒に配布されていてダウンロードする必要がないというライブラリであれば、

(use-package foobar
  :straight nil)

と個別に straight.el 対応を無効にしてやればよい。あるいは、そのパッケージがMELPAにあるのは知っているが、MELPAのバージョンだと不具合があるので、他の人がGithubかどこか別のところで公開しているバージョンが使いたい、ということもあるだろう。そうした場合は、

(straight-use-package
 '(foobar :type git :host github :repo "mhatta/foobar"))
(use-package foobar)

とか、

(straight-use-package
 '(foobar :type git :host github :repo "upstream/foobar"
            :fork (:host github
                   :repo "mhatta/foobar")))

などとフォークを明示的に指定してインストールすることもできてしまうのである。他にも straight.el は、パッケージのバージョンを固定したりいろいろなことが出来るのだが、そのへんは例えば このあたりの記事を参照してもらいたい。

というわけで、straight.el を上手く使うと、例えば他のコンピュータにinit.elだけ持っていけば、あとは Emacs を起動するだけでパッケージのインストールから設定まで終わってしまうのである。これはとても便利だ。

straight.el を使った Org modeのインストール

さて、今まで基本的にはOrg modeの話をしてきたのに、なぜ straight.el について延々と書いているかと言えば、straight.el で Org modeをインストールするときに若干の小細工が必要になるからである。しばらく前からEmacs本体にOrg modeが同梱されているため、レポジトリから最新の Org をインストールしようというとき、システムには古いバージョンの Org が存在していてすでに読み込まれている、ということが避けられない。これがインストール時に思わぬ不具合をもたらすことがあるので、init.elに

;; straight.elによるorg modeのインストールに先立つ準備
(require 'subr-x)
(straight-use-package 'git)

(defun org-git-version ()
  "The Git version of org-mode.
Inserted by installing org-mode or when a release is made."
  (require 'git)
  (let ((git-repo (expand-file-name
                   "straight/repos/org/" user-emacs-directory)))
    (string-trim
     (git-run "describe"
              "--match=release\*"
              "--abbrev=6"
              "HEAD"))))

(defun org-release ()
  "The release version of org-mode.
Inserted by installing org-mode or when a release is made."
  (require 'git)
  (let ((git-repo (expand-file-name
                   "straight/repos/org/" user-emacs-directory)))
    (string-trim
     (string-remove-prefix
      "release_"
      (git-run "describe"
               "--match=release\*"
               "--abbrev=0"
               "HEAD")))))

(provide 'org-version)

;; 以下Org modeの設定
(use-package org)

などと書いておくとよい。ちなみに前回までの話を踏まえた Org の設定を use-package で書くと、

(use-package org
  ;; :defer t で起動時に Org を読み込まない(起動が速くなる)
  :defer t
  :config
  (setq org-directory "~/Dropbox/Org"
        org-default-notes-file "notes.org"
        org-agenda-files '("~/Dropbox/Org/gtd.org"
                           "~/Dropbox/Org/notes.org")
        org-log-done 'time
        org-startup-truncated nil
        org-use-speed-commands t
        org-enforce-todo-dependencies t)
  (setq org-todo-keywords
        '((sequence "TODO(t)" "SOMEDAY(s)" "WAITING(w)" "|" "DONE(d)" "CANCELED(c@)")))
  (setq org-refile-targets
        (quote ((nil :maxlevel . 3)
                (mhatta/org-buffer-files :maxlevel . 1)
                (org-agenda-files :maxlevel . 3))))
  (setq org-capture-templates
       '(("t" "Todo" entry (file+headline "~/Dropbox/Org/gtd.org" "INBOX")
         "* TODO %?\n %i\n %a")
        ("j" "Journal" entry (function org-journal-find-location)
         "* %(format-time-string org-journal-time-format)%^{Title}\n%i%?")
        ("n" "Note" entry (file+headline "~/Dropbox/Org/notes.org" "Notes")
         "* %?\nEntered on %U\n %i\n %a")
        ))
  ;; org-tempo
  ;; Org 9.1.14 では明示的に読み込む必要があるかも
  (use-package org-tempo
    :straight nil)
  :bind
  (("\C-ca" . org-agenda)
   ("\C-cc" . org-capture)
   ("\C-ch" . org-store-link))
  )

こんな感じになるのでしょうかねえ。