この記事はACCESS Advent Calendar 17日目の記事です。
こんにちは!無人島にEmacs拡張一つ持っていけるとしたらelscreenを持っていくEmacsライトユーザの@kumabookです。
Emacsは自分でカスタマイズできることが大きな特徴・メリットですが、今まで自分は他の方が作ってくれている拡張を入れて少し設定ファイルを書くぐらいで自分で拡張を作ったことはありませんでした。
今回は脱ライトユーザすべく簡単なEmacs拡張を作ってみたいと思います。
拡張の作り方を学ぶのが目的なので、あまり機能に悩まないものにしたいと思います。以前LGTM.inから画像を取ってくるというFirefox拡張を作ったので、それのEmacs版を作ってみたいと思います。
Caskを使った開発環境の構築
プロジェクトの作成
Caskはパッケージ管理をしてくれるものですが、実は開発環境の構築も可能みたいです。
mkdir lgtm-emacs
cd lgtm-emacs
cask init --dev
とすると開発用のCaskファイルが生成されます。
(source gnu)
(source melpa)
(package-file "TODO")
(development
(depends-on "f")
(depends-on "ecukes")
(depends-on "ert-runner")
(depends-on "el-mock"))
TODOとなっているところに拡張のメインのelisp のファイル名に変更します。今回は lgtm.el
にします。
依存ライブラリなどを指定することができますが、それは後述します。
次に、lgtm.elを作成していくわけですが、拡張用のelispはお作法に沿って書く必要があります。
具体的にはファイルの先頭にコードの情報をコメントとして書く必要があります。これを書かないとcaskコマンドでエラーになってしまいます。自分はこれで少しハマってしまいました。
詳しくは こちらを参照してください。
;;; lgtm.el --- lgtm.in client
;; Copyright (C) 2015 Hiroki Kumamoto
;; Author: Hiroki Kumamoto <kumabook@live.jp>
;; Version: 0.1
;; Package-Requires: ((request "1.0"))
;; Keywords: multimedia, frobnicate
;; URL: https://github.com/kumabook/lgtm-emacs
;;; Commentary:
;; Show a LGTM image from lgtm.in at random
;;;###autoload
重要なのはPackage-Requiresの部分です。
ここの部分に依存する別のパッケージを書く必要あります。
ライブラリの利用
拡張はelispを使って書くのですが、すべての機能を自分で書く必要はなく他の人が作ったelisp利用することができます。
またその依存を宣言しておいて、依存するライブラリも一緒にインストールされるようにすることができます。
依存は先述したPackage-Requiresに書きます。合わせて、Caskを使った開発の際にはCaskファイルにも依存を書きます。
今回は、http requestをいい感じにかけるrequest.elというライブラリを利用したいと思います。
ですので、Caskファイルに以下を加えます。
(depends-on "request")
以下のコマンドでプロジェクトの.cask以下にライブラリがダウンロードされます。
cask install
Elispを書く
まずはHello world
準備ができたので、まずはhello worldしてみたいと思います。
(message "Hello World. LGTM!")
messageは*Message*
にprintする関数です。
cask exec
コマンドで.cask以下
をload-pathに追加した状態でemacsを実行できます。デフォルトだとcuiのemacsが立ち上がるので、今回はGUIのEmacs.appを指定しています。また、-l で読み込むファイルを指定できるので、lgtm.el
を指定しています。
cask exec ~/Applications/Emacs.app/Contents/MacOS/Emacs -l lgtm.el
開発用のEmacsが立ち上がって*Message*
を開くと
メッセージが出力されていると思います。
コマンド化
(defun lgtm ()
"Show a LGTM image from lgtm.in at random"
(interactive)
(message "Hello World. LGTM!"))
defunで関数を定義できます。関数の先頭でinteractiveを呼ぶと
M-x
から呼べるコマンドになります。
request.elを使ったJSON APIアクセス
lgtm.inはJSON APIを提供してくれています。
これを叩けばLGTM画像のURLとmarkdown用のテキストが入ったJSONが取得できます。
また、request.elを使えばjQuery.getJSON のように簡単にJSON APIを叩けます。
(defun lgtm ()
"Show a LGTM image from lgtm.in at random"
(interactive)
(request
"http://www.lgtm.in/g"
:type "GET"
:params '()
:parser 'json-read
:headers '(("Accept" . "application/json"))
:success (function*
(lambda (&key data &allow-other-keys)
(message (assoc-default 'actualImageUrl data))
(message (assoc-default 'markdown data))))))
テンポラリバッファへの画像・markdownテキストの表示
画像のURLとmarkdownテキストが取得できたので、これをテンポラリバッファに表示するようにしたいと思います。
URLの画像を表示するにはダウンロードしてその結果からcreate-imageする必要があるようです。 参考URL
(defun insert-image-from-url (url)
(let ((buffer (url-retrieve-synchronously url)))
(unwind-protect
(let ((data (with-current-buffer buffer
(goto-char (point-min))
(search-forward "\n\n")
(buffer-substring (point) (point-max)))))
(insert-image (create-image data nil t)))
(kill-buffer buffer))))
このままだと現在開いているバッファに出力されてしまうので、*lgtm*
という名前でテンポラリバッファを作ってそこに出力したいと思います。
テンプラリバッファはget-buffer-createで作れるようです。
今回はコマンドが叩かれるたびに画像を差し替えたいので、
すでにbufferが存在すればkill-bufferするようにしたいと思います。またswitch-bufferで作成したバッファへ切り替えることができるようです。
(kill-buffer (get-buffer-create "*lgtm*"))
(switch-to-buffer (get-buffer-create "*lgtm*"))
(insert-image-from-url (assoc-default 'actualImageUrl data))
(insert "\n")
(insert (assoc-default 'markdown data)
これで、M-x lgtm
と打てばLGTM画像とマークダウンテキストが表示されるようになりました。
まとめ
今回は、簡単なJSON APIを叩くEmacs拡張を作ってみました。
- Caskを使った開発環境の構築
- request.elを使ったJSON APIクライアントの作成
- テンポラリバッファへの画像・markdownテキストの表示
ライブラリを利用できるので、思ったよりも簡単に拡張が書けることがわかりました。
また、今回は試しませんでしたが、テスト周りも充実していて
- unittest, mock
- evmを使った複数バージョンのEmacsのテスト
- Travis CI
といったものは今後試してみたいです。
画像の読み込みが同期的になってしまっています。UIをブロックしないように並列・非同期にしたいのですが、Emacs本体はマルチスレッドに対応しておらず、外部プログラムを駆使しなければならないようで、中々大変そうです。良さそうなライブラリもありそうなので、うまく利用できれば、画像をもう少しまとまった数同時に読み込むことも実装できそうです。
最後に
今回作ったものは簡単なものでしたが、
普段ブラックボックスとして利用しているものの中身を勉強して、拡張してみるというのは、自分の手の届く範囲が広がった感覚がありとても新鮮でした。まずは、lisp力をもっと上げたいですね。
ともあれEmacsはサイコーです。
今回作ったものはこちらになります。
では C-x C-c!
--
この記事はACCESS Advent Calendar 17日目の記事です
明日は @pankona さんがgo mobileの話をしてくれます。