Emacs LispとRubyとmozreplを使ってFirefoxを操作する

今回は、Emacs LispRubyとmozreplを使ってEmacsからFirefoxを操作する方法を紹介したいと思います。

mozreplとは

mozreplとはFirefoxのアドオンの一つで、Firefoxtelnetサーバーにしてしまうというものです。このアドオンを導入することで、ターミナルやプログラムからFirefoxを操作することが出来ます。

mozreplはこちらからインストールすることができます。mozreplをインストールしFirefoxを再起動させると、Firefoxの「ツール」メニューにMozReplというメニューが追加されます。そこでStartを実行すると、telnetコマンドを使ってFirefoxにアクセスできるようになります。

ターミナルで次のコマンドを打ってFirefoxにアクセスします。

rlwrap telnet localhost 4242

(4242はmozreplがlistenしているポート番号でmozreplメニューの「Change Port」で変更することが出来ます。また、rlwrapは無くても動きますが、rlwrapを使うことでカーソル移動やヒストリ機能が使えるようになるのでrlwrapを使うことをオススメします。Windowsの場合はTera Term等のプログラムでtelnetが使えたと思います。)すると、

repl>

というプロンプトがでてくるので、そこで

repl> content.location.href = 'http://www.google.co.jp'

と実行すると、Firefoxの現在のタブがGoogleのページに移ります。

mozreplのコマンドはjavascriptそのものなので、その場で関数を定義するなんてこともできるようです。

mozreplセッションを終了させるには、次のコマンドを実行します。

repl> repl.quit()

任意のURLを新しいタブで開くRubyプログラム

Emacsのプロセス関係の関数を使えば、EmacsからFirefoxを直接操作できるので、わざわざ別のプログラムを介する必要はないのですが、私自身がEmacsのそうした機能についてあまり詳しくないという理由から、Firefoxと通信を行う部分をRubyで書くことにしました。

require 'net/telnet'
def mozrepl_open
  $telnet = Net::Telnet.new("Host" => "localhost", "Port" => 4242, "Prompt" => /repl\> \z/n)
end
def mozrepl_cmd(str)
  $telnet.cmd(str)
  sleep(0.5)
end
def mozrepl_close
  $telnet.puts("repl.quit()")
  $telnet.close
end
mozrepl_open
mozrepl_cmd("gBrowser.selectedTab = gBrowser.addTab()")
if ARGV.length > 0
  mozrepl_cmd("content.location.href = '#{ARGV[0]}'")
else
  mozrepl_cmd("BrowserGoHome()")
end
mozrepl_close

このプログラムはmozreplを使ってFirefoxの新しいタブを作成し、そこで引数で指定したURLを開くというプログラムです。

Emacs Lisp

まず最初に、先程のRubyプログラムをEmacsから使うためのラッパー関数とURLをエンコードするための補助関数を作成します。

(defvar mozrepl-open-uri-path "~/.emacs.d/bin/mozrepl_open_uri.rb")

(defun mozrepl-open-uri (uri)
  (interactive "suri: ")
  (let ((cmd (format "ruby %s '%s'" mozrepl-open-uri-path uri)))
  (shell-command cmd)))

(defun uri-encode (str)
  (mapconcat
   (lambda (s)
     (mapconcat
      (lambda (x) (format "%%%x" x))
      (vconcat (encode-coding-string s 'utf-8))
      ""))
   (split-string str)
   "+"
   ))

(defun utf8-escape (str)
  (mapconcat
   (lambda (x) (format "%%%x" x))
   (vconcat (encode-coding-string str 'utf-8))
   ""))

変数mozrepl-open-uri-pathに、先程のRubyプログラムのパスを設定します。(URLをエンコードする関数の実装が怪しいです。)

この関数を使ってアプリケーションを作成していきます。

手始めにGoogle検索を実行するプログラムを書いてみます。

(defun mozrepl-google-search (keywords)
  (interactive "skeywords: ")
  (mozrepl-open-uri (format "http://www.google.co.jp/search?hl=ja&q=%s" (uri-encode keywords))))

(defun mozrepl-google-feeling-lucky (keywords)
  (interactive "skeywords: ")
  (mozrepl-open-uri (format "http://www.google.co.jp/search?hl=ja&btnI=&q=%s" (uri-encode keywords))))

(defun mozrepl-google-search-region (begin end)
  (interactive "r")
  (let (str)
    (setq str (buffer-substring-no-properties begin end))
    (mozrepl-google-search str)))

(defun mozrepl-google-feeling-lucky-region (begin end)
  (interactive "r")
  (let (str)
    (setq str (buffer-substring-no-properties begin end))
    (mozrepl-google-feeling-lucky str)))

(defun mozrepl-google-translate-region (begin end)
  (interactive "r")
  (let (str uri)
    (setq str (buffer-substring-no-properties begin end))
    (setq uri (format "http://translate.google.co.jp/translate_t?hl=ja&sl=en&tl=ja#%s%s"
		      (if (equal (find-charset-region begin end) '(ascii)) "en|ja|" "ja|en|")
		      (uri-encode str)))
    (mozrepl-open-uri uri)
    ))  

別のアプリケーションとして、言語のリファレンスのページに飛ぶプログラムを作ります。

ここで紹介するプログラムのアイデアは、調べたい言語のリファレンスのページを指定してGoogle検索を行うことで、見たいページが必ず検索のトップに現れるだろうという仮定に基づいています。
Google検索のサイト内検索機能と検索1位のページに直接飛ぶI'm Feeling Lucky検索を組み合わせています。

(defmacro define-mozrepl-x-search (name site)
  (let ((sym-i (intern (concat "mozrepl-" (symbol-name name) "-search")))
	(sym-g (intern (concat "mozrepl-" (symbol-name name) "-search-result")))
	(url-i (format "http://www.google.co.jp/search?hl=ja&btnI=&as_sitesearch=%s&q=%%s" site))
	(url-g (format "http://www.google.co.jp/search?hl=ja&as_sitesearch=%s&q=%%s" site)))
    `(progn
       (defun ,sym-i (keywords)
	 (interactive "skeywords: ")
	 (mozrepl-open-uri (format ,url-i (uri-encode keywords))))
       (defun ,sym-g (keywords)
	 (interactive "skeywords: ")
	 (mozrepl-open-uri (format ,url-g (uri-encode keywords))))
       )
    ))
(defmacro mozrepl-x-search-expand ()
  (let ((mozrepl-x-search-list
	 '((ruby        . "www.ruby-lang.org")
	   (python      . "docs.python.org"  )
	   (html        . "www.htmq.com"     )
	   (lisp        . "www.lispworks.com")
	   (gauche      . "practical-scheme.net/gauche/man")
	   (mathematica . "reference.wolfram.com/mathematica")
	   (allegro     . "www.franz.com/support/documentation/6.2/doc")
	   (mop         . "www.alu.org/mop")
	   (cocoa       . "developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Reference")
	   )))
    `(progn
       ,@(loop for elt in mozrepl-x-search-list
	       collect `(define-mozrepl-x-search ,(car elt) ,(cdr elt))))    
    ))
(mozrepl-x-search-expand)

define-mozrepl-x-searchマクロはmozrepl-x-searchとmozrepl-x-search-result(xの部分にnameで指定した名前が入る)という2つの関数を定義するマクロで、mozrepl-x-search-expandマクロはたくさんの言語に対応した関数を一気に定義するためのマクロです。mozrepl-x-search関数はI'm Feeling Lucky検索を使って検索1位のページに直接飛ぶ関数で、mozrepl-x-search-result関数はGoogleの検索結果のページを表示する関数です。