$Id: el4r.ja.rd.r 1380 2006-09-21 07:23:15Z rubikitch $
Emacsの拡張言語に「Ruby」も加えましょう♪
徹底した自動テストでEmacsLispのバグをなくしましょう♪
最新Version 1.0.4
EmacsRuby側で定義された関数を追加。
通常、Emacsを拡張するにはEmacsLispというLisp方言を使います。 このプログラムはEmacsLispに加えてRubyでもEmacsを操作できるようにします。 Emacsを操作するためのRubyをEmacsLispに倣ってEmacsRubyと言うことにします。 *1
Emacsは長年広く使われているので数々のEmacsLispライブラリがあります。 しかし、自分でEmacsを拡張するにはEmacsLispを書かなければいけませんでした。 このプログラムで新たにRubyという選択肢ができました。 Emacs使いではあるがLispはちょっと…という人は是非とも試してみてください。
また、RubyのTest::Unitを使うことでEmacsLispプログラムのテストを完全自動化できます。 EmacsLispを愛する人にも役立つと思います。
筆者は昔からEmacsをRubyで拡張できたらなーと思っていました。 Rubyを知る前からEmacsLispを少し書くことができましたが、自分にとってEmacsLispはストレスのたまる言語です。 なので、本処理をRubyなど他の言語で記述して、Emacsからはcall-processやshell-commandなどから呼出すことをよくやっていました。 *2
数年前、原作者の白井さんが[ruby-list:37730]にてel4rを発表しました。 当時はまだ実用にはまだほど遠い感じでしたので、頭の片隅に置くにとどまりました。 しばらく後にCVSからチェックアウトしたら、いろいろ改良され、実用の見通しが立つまでになっていました。 それから、何通かのメールのやり取りで開発を引き継ぐことになりました。
EmacsがRubyとプロセス間通信します。 より正確には、Emacsとel4r-instanceというRubyスクリプトがプロセス間通信します。 そのため、el4rはEmacsLispとRubyスクリプトが組になっています。
el4r-instanceは、El4r::ELInstanceオブジェクトを作成し、Emacsからの式を待つループに入ります。 式が来ると、El4r::ELInstanceオブジェクトの文脈(以後、el4rの文脈とする)で評価して結果をEmacsに返します。 el4rの文脈では、el4rオブジェクトが知らないメソッドはEmacsLispの関数へ丸投げされます。 そのため、ユーザにとってはEmacsにRubyが組み込まれたように見えます。
動作には以下の環境が必要です。
筆者の環境はDebian GNU/Linuxです。 他のGNU/Linux、Unix系OSでも問題なく動くと思われます。
手許にWindows環境がないため、本物のWindowsでの動作確認はしていません。 WINE上の"ruby 1.8.2 (2004-12-25) [i386-mswin32]"とMeadowの組み合わせで動作しています。
さまざまな環境での動作状況を知りたいので、次のコマンドをel4rのディレクトリから実行し、OS名を明記の上、出力とtestlogファイルをるびきちまで送ってください。
ruby bin/el4r-runtest testing/test-el4r.rb --batch -l testlog --emacs=Emacsバイナリ名
バージョン1.0.0よりインストーラがつきました。
ダウンロード時に例外が発生するようでしたら、Rubyのバージョンを上げてください。 (Emacs内でこのページを見ている人はコマンド群にリージョンをセットし、M-| shすれば楽です) 付属の el4r-rctool スクリプトは現在の環境に合うように設定ファイルを自動更新します。 すでに設定済みのファイルは、el4r blockの部分のみが el4r-rctool の設定に基いて変更されます。 他の部分には危害を加えません。
なお、バージョン0.9.1以前の el4r からバージョンアップするには、まず ~/.emacs から
(add-to-list 'load-path "~/src/el4r/elisp/") (require 'el4r) (el4r-boot)
の部分、 ~/.el4r/init.rb から
el4r_load "el4r-mode.rb"
の部分を手作業で削除してください。 御迷惑をおかけします。 以後のバージョンは el4r-rctool が設定を更新してくれます。
任意のディレクトリにて以下のコマンドを実行してください。
ruby -ropen-uri -e 'URI("http://www.rubyist.net/~rubikitch/archive/el4r-1.0.4.tar.gz").read.display' > el4r-1.0.4.tar.gz tar xzf el4r-1.0.4.tar.gz cd el4r-1.0.4 ruby setup.rb ruby -S el4r-rctool -p ruby -S el4r-rctool -i
EmacsRubyスクリプトを置くディレクトリはデフォルトで ~/.el4r です。 環境変数 EL4R_HOME で変えることもできます。
Victor BorjaさんがGentoo ebuildを作成してくれました。 ありがとうございます。
Boris DaixさんがDebian packageを作成してくれました。 ありがとうございます。 /etc/apt/sources.listに次の設定を加えてください。
deb http://alysse.dyndns.org/~bdaix/debian/packages unstable/ deb-src http://alysse.dyndns.org/~bdaix/debian/packages unstable/
Emacs上で
M-x el4r-boot
を実行します。 インストール段階でel4rはすでに起動しています。 挙動がおかしくなったとき、init.rbを書き換えた時などはel4rの再起動が必要です。 再起動も同じコマンドを使います。
el4rを起動するとき、自動的に ~/.el4r/init.rb をel4rの文脈でevalします。 el4rの文脈だと通常のRubyに加えてEmacsLisp関数・変数にアクセスしたり、EmacsLisp関数を定義したりできます。
EmacsRubyスクリプトを読み込むには、el4r_loadメソッドを使います。 引数に指定したファイル名のスクリプトをel4rの文脈でevalします。
el4r_loadで読み込まれるスクリプトは、デフォルトで ~/.el4r → ~/.el4r/site → el4rパッケージの el4r ディレクトリの順に探されます。 他パッケージのEmacsRubyスクリプトは ~/.el4r/site 以下にインストールされます。
もちろん通常のloadも使えます。 この場合、el4rの文脈ではなく通常のRubyライブラリとして読み込みます。 el4rでは、 ~/.el4r(環境変数EL4R_HOMEで指定したディレクトリ)も$:($LOAD_PATH)に加えています。
RubyとEmacsLispでは名前の付け方の慣習に違いがあります。 通常、単語の区切りとしてEmacsLispでは「-」、Rubyでは「_」が使われます。 そのためel4rではEmacsLisp側の関数・変数をRubyで指定するとき、「_」を「-」に置換します。
例:
find_file -> find-file
また、RubyとEmacsLispでは関数名や変数名やシンボルに使える文字集合が違います。 EmacsLispの方が多くの種類の文字が使えます。 ということは、Rubyでは表現できないものも出てきます。 御安心ください。 こういう場合、文字列で指定できるのです。
例:
se/make-summary-buffer関数を呼び出す。 funcall("se/make-summary-buffer")
EmacsLisp変数へのアクセスはelvar
というオブジェクトを使います。
このオブジェクトはRubyの構造体のようにアクセスできます。
例:
elvar.a_string = "Hello" # (setq a-string "Hello") elvar["a-string"] = "Hi" # (setq a-string "Hi") elvar["*an/odd+variable!*"] = 10 # (setq *an/odd+variable!* 10)
el4rオブジェクトは自分の知らない関数を呼出そうとすると、EmacsLisp側の関数を呼びます(method_missing)。
例:
buffer_string # (buffer-string) find_file("~/.emacs") # (find-file "~/.emacs") funcall("1+1") # (1+1)
EmacsRuby側でEmacsLisp関数を定義することもできます。 Rubyと同様にEmacsLispにも「関数を定義する関数」が存在します。 よって、関数呼出しができれば関数定義もできてしまうのです。
関数の引数はイテレータの引数となります。
例:
defun(:my_command2, :interactive => "d", :docstring => "description...") { |point| insert_string("Current point is #{point}."); newline }
また、Procによるinteractive指定もできます。 この場合、ArrayまたはEmacsLispのリストを返す必要があります。
例:
interactive_proc = lambda { [1+1, 1] } defun(:my_command3, :interactive => interactive_proc) { |a, b| sum = a + b }
save-excursionやwith-current-bufferのようなイテレータっぽいSpecial Formの呼出し方はやや特殊です。
EmacsLispのイテレータ*3はEmacsRubyでもやはりイテレータなのです。
with(関数名, 引数) {...}
のように使います。
関数名で直接呼び出せないのは現在の仕様上の制約です。
例:
with(:save_excursion) do goto_char 1 re_search_forward('^\\(.+\\)$') end match_string 1
よく使われる関数定義型のマクロもRuby的に自然に呼び出せるようにしました。 このタイプは define_derived_mode と define_minor_mode です。 例:
define_derived_mode(:foo_mode, :fundamental_mode, "FOO", "doc") do @passed = true end define_minor_mode(:a_minor_mode, "test minor mode") do @passed = true end
defadvice
によるadviceも定義できます。
第1引数は関数名、以後の引数はadviceのパラメータをSymbolで指定します。
最後の省略可能引数にHashが取れます。
関数定義同様、:interactive
と:docstring
を指定できます。
例:
# define a function defun(:adtest3){ 1 } # now define an advice defadvice(:adtest3, :around, :adv3, :activate, :docstring=>"test advice", :interactive=>true) { ad_do_it elvar.ad_return_value = 2 }
print / printf / puts / putc / p などの出力を行う組み込み関数の出力は *el4r:output* バッファに書き出されます。 デバッグなどに使ってください。
内部的には、 El4r::El4rOutput オブジェクトを $>
に代入しています。
El4r::El4rOutput#write メソッドは *el4r:output* バッファに文字列を書き込むメソッドです。
出力を行う組み込み関数は最終的にはwriteが呼ばれるのでprintなども使えるのです。
EmacsRubyを導入する理由のひとつに、オブジェクト指向プログラミングがしたいというのもあるでしょう。 オブジェクト指向プログラミングといえば、まずはクラス定義です。 クラスを定義してしまうと、もはやel4rの文脈ではないからEmacsLispが呼び出せなくなるのではないか? 心配無用です。 クラス・モジュールを定義し、その中で
include ElMixin
と書きましょう。
これでいつものようにEmacsLispを呼び出せます。 自分の知らないメソッドはEmacsLispに投げられます。
testing/test-el4r.rbのTestEl4r
クラスを参考にしてください。
また、ElMixinのクラス版ともいえる、ElAppクラスも用意しています。 Hashでパラメータを渡すことができます。
バージョン1.0.3以前ではElAppサブクラスのインスタンスメソッドの中でdefunする必要がありました。
class Foo < ElApp def initialize(x) elvar.v = x[:value] defun(:twice_v) do elvar.v *= 2 end defun(:str0) do do_str0 x[:str] end end def do_str0(str) (str*2).capitalize end end
バージョン1.0.4よりElAppサブクラスの中で直接defunできるようになりました。 メソッドの中でdefunする必要がないので、見通しがよくなりました。 クラス定義中のdefunは「EmacsLispを定義するdef」と思ってください。
class SmartDefunSample < ElApp def my_square(x) x*x end defun(:testdefun, :interactive=>true) do |x| # This block is evaluated within a context of the SmartDefunSample INSTANCE. # Not a context of the SmartDefunSample class!! x ||= 16 elvar.val = my_square(x) # call an instance method. end end
Rubyを使う大きな利点のひとつにTest::Unitという強力なテスト環境があります。 EmacsLisp/EmacsRubyスクリプトのテストは別個にEmacsを立ち上げてやるべきです。 Emacsの操作はEmacsセッション全体に影響を及ぼすため、テストが誤動作したときには最悪現在の編集環境を破壊しかねません。 テスト用のEmacsなら、たとえそこで事故が起きようが編集環境には何ら悪影響がないので、安心してテストが行えます。 こういう理由から、テストのためのミニ環境を用意しました。
el4r-runtestは、新たにEmacsを立ち上げ、Test::Unit(RUNIT)でテストを行い、その結果を表示します。
--batch
オプションをつけて起動するとテストを実行したら即終了します。(emacs -batch)Test::Unit::TestCase
クラスではElMixin
をinclude/extendしているのでel4rのメソッド及びEmacsLisp関数が呼べます。あくまでミニ環境なのでキー操作は単純化しています。
具体的にはテストクラスはこんな感じになります。
el4r_lisp_eval
の部分にel_require
やel_load
を使うこともできます。
require 'test/unit' class TestXXXX < Test::Unit::TestCase el4r_lisp_eval %((progn 初期化コード )) def setup # end def teardown # end def test_xxxx # end end
Usage: el4r-runtest [options] file -Q, --init load site-start.el and .emacs -b, --batch batch mode -i interactive mode -e, --emacs=EMACS set emacs binary [default: ] --ruby=RUBY set ruby binary [default: ] -I load-path set load-path -r, --el4r-root=DIR el4r package root directory [for debug] -n, --name=NAME Runs tests matching NAME. (patterns may be used). -t, --testcase=TESTCASE Runs tests in TestCases matching TESTCASE. (patterns may be used). -v verbose output --nw don't communicate with X, ignoring $DISPLAY (emacs -nw) -d, --debug debug output -l, --log=LOGFILE Specify a log file. --show Show the test information only, for diagnosis.
--batch
, --nw
はEmacsのオプションです。
-n
, -t
はTest::Unitのオプションです。
-d
はバックトレースにel4r-instanceの行も含めます。
el4rコマンドは新たにEmacsを立ち上げ、引数に指定されたEmacsRubyスクリプトを実行します。 使い方はel4r-runtestとほとんど同じです。
Usage: el4r [options] file -Q, --init load site-start.el and .emacs -b, --batch batch mode -i interactive mode -e, --emacs=EMACS set emacs binary [default: ] --ruby=RUBY set ruby binary [default: ] -I load-path set load-path -r, --el4r-root=DIR el4r package root directory [for debug] --nw don't communicate with X, ignoring $DISPLAY (emacs -nw) -d, --debug debug output -l, --log=LOGFILE Specify a log file. --show Show the test information only, for diagnosis.
funcall(name_or_lambda, *args, &block)
関数name_or_lambdaを引数argsをつけて呼出します。
with(name, *args, &block)
save-excursionやwith-current-bufferなど最後にFORMSを取るスペシャルフォームを呼出します。
defun(name, attrs = nil, &block)
関数nameを定義します。attrsは:interactiveと:docstringを取るHashです。 関数の引数はブロック引数です。
define_key(map, key, command = nil, &block)
mapはキーマップの変数名をシンボルで指定します。 keyは '\C-x\C-c' のように''文字列で指定します。 ブロックを指定することもできます。
let(*name_and_value_list, &block)
name_and_value_listは要素の数が偶数の配列で、偶数番目に変数名、奇数番目に変数の値を指定します。
defadvice(func, *args, &block)
funcのアドバイスを定義します。 argsはアドバイスのパラメータ(:around、:activateなど)。 ブロックの中でad_do_itを使うことができます。
define_minor_mode(mode, doc, init_value=nil, lighter=nil, keymap=nil, &block)
マイナーモードを定義します。
define_derived_mode(child, parent, name, docstring=nil, &block)
派生モードを定義します。
eval_after_load(lib, &block)
ライブラリlibをロードするときに実行するブロックを指定します。 すでにロードしているときは直ちにブロックを評価します。
el_require(*args)
必要ならばEmacsLispを読み込みます。
el_load(*args)
EmacsLispを読み込みます。
el_lambda(attr = nil, &block)
ラムダ式。使い方はdefunと同じです。
el4r_load(path_to_rb, is_noerror = nil)
path_to_rbで指定されたEmacsRubyスクリプトを読み込みます。 スクリプトの内容はel4rの文脈で評価されます。
通常はtrueを返します。 is_noerrorがtrueのとき、ファイルが存在しない場合はfalseを返します。
el4r_lisp_eval(lispexpr)
EmacsLispを評価します。
el4r_ruby2lisp(obj)
objをEmacsLispの表現に変換します。
el4r_log(msg)
ログにmsgを出力します。
el4r_prin1(*objs)
objsひとつひとつの文字列表現をログに出力します。
el4r_p(*objs)
objs.inspectをログに出力します。
el4r_backtrace(msg=nil)
バックトレースをログに出力します。メッセージmsgを指定できます。
el4r_debug(msg = nil, &block)
デバッグモードのとき、msgまたはブロックの結果をログに出力します。
el4r_ruby_eval(source)
el4rの文脈でsourceを評価します。 デバッグモードで例外が起きたとき、スタックトレースをログに出力します。
el(expression)
文字列やシンボルで指定したexpressionがそのままEmacsLispに展開されます。
newbuf(param, &block)
paramはハッシュで、いろいろな要素を一度に指定してバッファを作成します。 バッファの名前はもちろん、内容やポイント位置から表示方法まで指定できます。 ブロックをつけると、バッファを初期化した後にブロックを実行します。
bufstr(buf=current_buffer)
バッファの内容を文字列で返します。
EmacsRuby側で関数・変数定義ができるので必要かどうかわかりませんが、EmacsLispからRubyにアクセスすることもできます。
EmacsLispプログラム内ではel4r-ruby-eval
関数を使います。
唯一の引数が評価するRuby式です。
尤も、EmacsRubyで書いたプログラムをEmacsLisp内でも使いたいならば、defun
やelvar
でEmacsLispから見えるようにするのが賢明です。
対話的にEmacsRubyにアクセスするには
M-x el4r-ruby-eval-prompt
してください。バッファ全体のEmacsRuby式を評価するには
M-x el4r-ruby-eval-buffer
リージョン内は
M-x el4r-ruby-eval-region
してください。
また、examples/el4r-mode.rbをロードすると(初期設定でロードされています)、*ruby-scratch*バッファが作成されます。
EmacsRubyでのmajor-modeを作成例は lib/el4r/emacsruby/el4r-mode.rb を見てください。
各々のメソッドの使用例についてはtesting/test-el4r.rbを見てください。 テストプログラムは仕様書です。 正しい使い方や期待されるべき値など多くのことを語っています。
バージョン1.0.0よりEmacsRubyスクリプトはsystem wideにインストールすることが可能になりました。 Ruby 1.8を使っているものと仮定すると次のような配置になります。
site_ruby/1.8/el4r/ # el4rが使う通常のライブラリ site_ruby/1.8/el4r/emacsruby/ # EmacsRubyスクリプト site_ruby/1.8/el4r/emacsruby/autoload/ # el4r起動時に実行されるEmacsRubyスクリプト
通常のライブラリは普通に load / require で呼出されます。 EmacsRubyスクリプトは el4r_load で呼出されます。
autoloadにあるEmacsRubyスクリプト群はel4r起動時に順次実行されます。 EmacsRubyスクリプトの初期設定・起動のために使います。
このディレクトリにファイルを配置するとき、ファイル名の先頭に数字を2文字含むようにしてください。たとえば次のようなファイルを配置すると、
00init.rb 50langhelp.rb 70el4r-mode.rb
数字の若い順に実行されます。それ以外のファイルは無視されます。 Debian GNU/LinuxのEmacsが起動時に /etc/emacs/site-start.d/ に配置したEmacsLispを順次読み込むことに似ています。
あおきさんのsetup.rbを使うと簡単にパッケージングできます。 EmacsRubyを配布するためには、次のようにディレクトリをレイアウトしてください。
bin/ # 実行ファイル lib/el4r/ # el4rが使う通常のライブラリ lib/el4r/emacsruby/ # EmacsRubyスクリプト lib/el4r/emacsruby/autoload/ # el4r起動時に実行されるEmacsRubyスクリプト ext/ # 拡張ライブラリ data/ # データ
付属のel4r-mode.rb、langhelpはこの方法でパッケージングしています。
*1elispに倣ってerubyじゃありませんよ。erubyは別物ですから。
*2EmacsLisp原理主義者がうらやましい。
*3通常EmacsLispではそういう言い方はしないが。