Emacs 上の調べ物は langhelp で!

書いた人:rubikitch

はじめに

プログラミングなどの知的作業の効率を上げるために、Emacs 上でスムーズに調べ事ができるようにするツール langhelp を紹介します。

langhelp は名前の通り「言語」を扱うことを助けるツールです。 この業界で「言語」といえばプログラミングを連想しますが、それだけではありません。 何十ものオプションがあるコマンドのコマンドラインはまさに言語と呼ぶにふさわしいです。 ソフトウェアの設定ファイルも、制御構造がないものの言語です。 このように目的に応じた大小様々な言語が存在するのです。

本稿では「るびま」の名前通り Ruby プログラミングに焦点をしぼって話を進めていきます。

対象読者

Emacs の操作に一通り慣れ、もっと便利に使おうとする「言語」使い達が対象です。 langhelp を使うだけならとくに Ruby の知識を必要としません。

背景

langhelp 作成に至った背景について軽く流しておきます。

プログラミングは調べ事の連続

コンピュータというのはとても気難しい相手です。 プログラミングはプログラミング言語を使ってコンピュータに命令を伝える作業です。 厳格なので、少しでも言語の使い方を間違ってしまうと言うことを聞いてくれません。 人間と違って融通がきかないのです。 そのため、語法を正確に記憶する必要があります。

しかし筆者はコンピュータの天才ではないです。 記憶力にも自信がありません。 少しでも疑問に思ったら調べないといけません。 調べる手段として、ブラウザでオンラインリファレンスマニュアルを開いたり、本を読んだり、ウェブ検索をしたり……多種多様ですね。

対象言語の熟練度が低いと一歩進むごとに調べないといけないのでいつまでたっても問題解決ができません。 苦痛そのものですね。 長年親しんだ言語でも、新しいライブラリを使いこなせるまでには時間がかかります。

多くの人にとってプログラミングは調べ事の連続なのは間違いないです。

多種多様なフォーマットのドキュメント。検索ツールも様々。

調べ事はプログラマの悩みの種なので、労力を軽減するためのツールが開発されるのは自然な流れです。 Ruby の場合は標準添付の ri と ReFe が有名ですね。 筆者もかつて RDindex というツールを作っていました。(RDindex は現在メンテナンスされていません。) 決まった書式でドキュメントを書いていれば、あとはツールが面倒見てくれます。 これらのツールは開発を劇的に効率よくしてくれました。標準万歳!!

けれども、時とともに状況は変わってくるものです。 昔は RD がデファクトスタンダードだったので RD さえ扱えればよかったです。 今は RDoc が世界標準となり RD の利用率は減少一方です。 マニュアルにおいては、日本語が RD、英語が RDoc というふうに棲み分けされているのが現状です。 こうなると ReFe と ri を使い分ける必要がでてきました。

さらに追い討ちをかけるのが、標準に従っていないドキュメントです。 構造化もされていない plain text だったり、ウェブ上の HTML だったり、自分の日記だったり…… こういうものはいちいちファイルを開いて手検索しなければならず、 検索ツールに慣れた人にとっては耐えがたいです。

情報をまとめて検索するツールが欲しい。

この現状を打破するために、情報源によらず情報の目次を一箇所に固めておく必要性を感じました。 もちろん目次から内容を参照することができるべきです。本がそうであるように。 Ruby の場合、ReFe へのリンク、ri へのリンク、RD の特定行などで目次を構成します。 情報を一元化することで、使うツールを選択することなくすかさず検索にうつれます。

S 式ハイパーリンク

langhelp のリンク機能そのものは自分で実装していません。 既存の機能をうまく使っています。 EmacsLisp 式 (S 式) をハイパーリンクとみなすのです。

たとえば、

 (find-file "~/.emacs")

の行末へカーソルを持っていって C-x C-e を押してください。 すぐさま ~/.emacs が開かれてバッファが切り替わりました。 C-x C-e はマイナーだと思いますが、カーソル直前の S 式を評価するコマンドです。

S 式の評価というと、どうしても変数の値や関数の戻り値に注目されがちですが、 この場合はファイルを開くという副作用が目的です。 ブラウザのリンクをクリックするとリンク先のページが表示されることを思い出してください。 C-x C-e は一種のハイパーリンクと思えませんか? さらに S 式に色をつけてみると、見た目上ブラウザのリンクと変わらないでしょう?

もちろん langhelp を使う上では押しやすいキーにバインドしていますが、S 式ハイパーリンクは langhelp の中核をなすものです。

このアイデアのおかげで、「目次作成」と「内容閲覧」を完全に別個の問題として扱え、限りない柔軟性を得ることができました。 ○○へのリンク機能が欲しいならば、関数を書くだけでいいのです。

このアイデアのもうひとつの利点は、S 式が Emacs 上のどこでも評価できることです。 langhelp で調べた結果を表示する S 式をソースコードや自分のメモに簡単に貼り付けることができます。 極論すると、あらゆるテキストが Wiki になるのです。 同じことを調べなおさずにすむので未来の自分へのメッセージになります。 共同開発者が langhelp を使ってくれれば、情報交換もできるでしょう。

インストール・設定

Cygwin (Windows 環境の場合)

langhelp は Unix 指向のプログラムです。 そのため Windows 使いの人は Cygwin をインストールする必要があります。 Ruby も Cygwin 版を使うほうがいいと思います。

Meadow Memo - Cygwin

あと、環境変数 HOME も忘れずに設定しておいてください。 ホームディレクトリを指定します。

以後、Unix 系 OS を前提に話をすすめていきます。

el4r と langhelp のインストール

langhelp は el4r (Emacs Lisp for Ruby) に依存しています。 el4r は Emacs を Ruby でコントロールするプログラムです。 Emacs をコントロールするための Lisp を EmacsLisp と呼ぶように、Emacs をコントロールするための Ruby を EmacsRuby と呼んでいます。 langhelp は EmacsRuby で書かれているため、最初に el4r をインストールする必要があるのです。*1

Debian GNU/Linux

Debian GNU/Linux ならばインストールは簡単です。 以下の行を /etc/apt/sources.list に加えます。

 deb http://alysse.dyndns.org/~bdaix/debian/packages unstable/
 deb-src http://alysse.dyndns.org/~bdaix/debian/packages unstable/

そして

 apt-get update
 apt-get install langhelp

としてください。

その他のシステム

その他のシステムではソースからインストールする必要があります。 まず el4r からインストールしてください。執筆時点でバージョン 1.0.4 です。 以下のコマンドを実行してください。 ruby -ropen-uri の行はブラウザや wget でダウンロードしても構いません。 なお ruby setup.rb でインストールするので sudo が必要な環境もあります。

 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 -d   # 変更点を diff で表示
 ruby -S el4r-rctool -i

次は langhelp をインストールします。執筆時点でバージョン 0.9.8 です。

 ruby -ropen-uri -e 'URI("http://www.rubyist.net/~rubikitch/archive/langhelp-0.9.8.tar.gz").read.display' > langhelp-0.9.8.tar.gz
 tar xzvf langhelp-0.9.8.tar.gz
 cd langhelp-0.9.8
 ruby setup.rb

langhelp が利用するパッケージのインストール

langhelp はいろいろなツールを統合するものです。 そのため、外部プログラムを利用しまくります。 langhelp を試用してみるだけなら、とくにインストールする必要はありません。 単体でも Ri と RD の目次は作成できます。 ツールが多ければ多いほど langhelp は力を発揮します。

本稿では Ruby プログラミングに便利なツールを紹介しておきます。 ほかのツールについては附属ドキュメントを参照してください。

ReFe

ri は英語ですが、 ReFe は日本語でメソッドのドキュメントを参照できます。 日本人 Rubyist 必携です。 是非ともインストールしましょう。

ReFe

Ri for (X)Emacs

ri をコマンドラインで使うと出力されるまで数秒待たされますが、これを入れることで一瞬でドキュメントを参照することができます。 Emacs 使いは是非ともインストールしておきましょう。

Ri for (X)Emacs

w3m と emacs-w3m

Unix 系 OS の世界で有名なページャ兼テキストブラウザ兼 HTML → Text 変換器です。 動作が軽いのが特徴です。 emacs-w3m は Emacs 内で w3m を使うれっきとしたウェブブラウザです。 emacs-w3m があると HTML ドキュメントを langhelp 内で参照できます。

emacs-w3m

Debian GNU/Linux ならば

 apt-get install w3m-el

一発でおしまいです。

Windows 使いの人は Cygwin → w3m → emacs-w3m とインストール作業が大変ですが、がんばってください。 ちょっとした調べ物のためにいちいちブラウザを立ち上げるのは面倒でしょう?

Meadow Memo - Cygwin Meadow Memo - emacs-w3m

bm.el

はっきりと印がつくバッファ内ブックマークです。 通常の編集・閲覧作業にもとても便利なのでインストールする価値は十分あります。 langhelp では後で参照できるように簡単にブックマークできるようになっています。

bm.el

migemo

ローマ字で日本語が検索できる優れ物です。 古いコンピュータでなければかなり快適に動作します。 langhelp は検索ベースのツールなのでインストールして損はありません。

Migemo

まず初めにやること

初めて langhelp を使う人はサンプル設定ファイルをコピーしてください。

 mkdir -p ~/.langhelp; cp data/langhelp/config.sample ~/.langhelp/config

そして、とりあえずすべての言語の目次を作成しておきます。

 mklanghelp --all

を実行して langhelp で定義されているすべての言語の目次ファイルを生成してください。 目次ファイル名は言語名に .e という拡張子をつけたものです。

この際、エラーがたくさん出てくると思いますが、慌てないように。 あくまで設定ファイルは筆者の環境をもとに作られているため、指定された場所にドキュメントが存在しないからです。 それでも現時点でやれるだけのことはしてくれます。

様々な言語の様々な形式のドキュメントをサポートしてるため設定ファイルは複雑

のっけからエラー連発で前途多難な思いをさせて申し訳ありません。 設定ファイルは Ruby スクリプトですが、Ruby を知らない人はぎょっとするかもしれません。 langhelp は様々な形式のドキュメントをサポートする必要があるため、柔軟性が高くないといけません。 悲しいことにドキュメント事情は複雑なのです。

Ruby を知る人が見たら、設定ファイルはたんなるインスタンス変数定義の羅列であることがわかるでしょう。 いろいろな変数が定義されていますが、設定で最重要なのが @lang 変数です。

@lang の構造

複雑な設定ファイルですが、コツさえ理解すれば設定はそんなに難しくありません。 langhelp は様々な言語やコマンドをサポートしていますが、使いたい言語のみ設定するのが鉄則です。 Ruby の @lang 設定を見てみましょう。(コメントは省略しています)

 @lang["ruby"] =        [
   {:RubyRefm => "~/doc/rubyrefm/", :title=>"Ruby Reference Manual (Japanese)"},
   {:Ri => true, :title=>"RI"},
   {:Refe => true, :cmd=>"refe", :title=>"ReFe (Japanese)"},
   {:MoonWolfRubyDoc => "~/ruby/htmlsplit.html", :title=>"HTMLSplit (Japanese)"},
   {:RD => "~/compile/sys-proctable-0.6.4/doc/linux.rd", :title=>"ProcTable"},
   {:Grep => "~/compile/narray-0.5.7/SPEC.ja", :regexp=>/^(\S.|    \S)/,  :title=>"NArray SPEC"},
   {:RD => "~/compile/numru-units-1.5/doc/units.rd", :title=>"units"},
 ]

見てわかるように @lang["ruby"] は Hash を要素とする配列です。 Hash のキー定義順は大文字から始まるシンボルから始まり、小文字から始まるシンボルがずらりと並んでいます。 もちろん Hash のキー定義順は Ruby 的にどうでもいいですが、可読性のために先頭に書いています。 言い換えると、 langhelp が処理するドキュメントを定義する今流行の「ドメイン特化言語 (DSL)」として見てください。 「ドキュメント定義言語」は次の仕様を満たしています。

  • ドキュメント定義は一行のひとつの Hash で定義されます。
  • ドキュメント定義の配列順に目次が並びます。
  • ドキュメント定義の先頭の大文字シンボルはドキュメントを処理するクラス名です。
  • クラス名に対応するハッシュの値は (多くの場合) 処理対象のドキュメントファイル名です。
  • クラス名に対応するハッシュの値が true の場合は、専用のコマンドを使います。
  • 残りは雑多なパラメータです。
  • :title の値がドキュメントのタイトルで、一番最後に書きます。

設定のコツ

mklanghelp --all のエラーを修正するコツを紹介します。

使わない言語の設定は放置

初回の mklanghelp --all でエラーがでたもののうち、使わない言語の設定は無視しちゃってください。 エラーを放置するのは気持ち悪いと思いますが、当該言語の目次が生成されないだけなので無視してもかまいません。 コンピュータの世界では一般に、理解できないもの・得体の知れないものは放置するのが安全です。

使わないドキュメントはコメントアウト

筆者はできる限りたくさんのドキュメントの目次を設定していますが、利用者が使わないドキュメントもあるでしょう。 そういうドキュメントはコメントアウトしてください。

たとえば HTMLSplit は使わないという人は次のように行頭に # を入れます。

 # {:MoonWolfRubyDoc => "~/ruby/htmlsplit.html", :title=>"HTMLSplit (Japanese)"},

こうすることで mklanghelp は ~/ruby/htmlsplit.html を読みに行かなくなります。

また、 w3m と emacs-w3m を使わない人は RubyRefm もコメントアウトしてください。

 # {:RubyRefm => "~/doc/rubyrefm/", :title=>"Ruby Reference Manual (Japanese)"},

また、段階的に設定したい場合もコメントアウトは有効です。

「ドキュメント定義言語」でドキュメント定義を一行にしているのは、Ruby を知らない人でも簡単にコメントアウトできるようにするためです。

ドキュメントファイル名の書き換え

処理したいドキュメントにもかかわらず、筆者と利用者で置いている場所が異なる場合もあります。 そういう場合はファイル名 (ディレクトリ名) を書き換える必要があります。

みなさんはどこに何のファイルがあるか意識しているでしょうか? Unix 系 OS に親しんでいる人は自分のホームディレクトリに何があるかほとんど把握していると思います。 自分の家でどこに何があるかを覚えているようなものです。 Windows のみの人は……覚えていないかもしれませんね。

langhelp ではドキュメントを見付ける労力を軽減するために最大限の努力をしています。 設定ファイル中の [EVAL IT] 行を見てください。 これは先程触れた S 式ハイパーリンクです。 [EVAL IT] 行にカーソルを置いて C-e C-x C-e を押してください。 実行の exe と覚えればいいですね。

locate を使う行もありますが、使うためには予め GNU Findutils をインストールして updatedb しておきましょう。 locate はファイル名の一部を入力すると、ファイル名データベース内でマッチするファイル名がフルパスで出力されます。

また、ファイル名を書き換える際には、 ffap と hippie-exp を使うといいでしょう。 ともに Emacs 標準添付です。 hippie-exp を使うと任意の場所でファイル名 (他いろいろ) の補完入力ができます。 ffap はファイル名にカーソルがある場合、 C-x C-f などでそのファイル名を拾ってくれます。 そのため、ファイルの存在確認にうってつけです。 筆者は次のように .emacs に設定しています。

 (ffap-bindings)
 (define-key esc-map  " " 'hippie-expand)

使い方

なんとか設定を終え、さっそく使い方の説明に入れます。

目次作成

設定ファイルを書き換えた後、その設定を反映しなければなりません。 最初は

 mklanghelp --all

で全言語の目次を作成しましたが、今回は設定を変えた Ruby の目次を作成してみましょう。

 mklanghelp ruby

エラーが出なければ無事に目次が完成しました。 エラーが出た場合は設定ファイルに書いてあるドキュメントファイルが存在するか確認してください。 ほかの言語についても同様です。

これで設定ファイルとの格闘はおしまいです。 Emacs で検索を楽しんでください。

Emacs で使ってみましょう

Emacs を起動します。 起動後すぐに langhelp が使えるようになっています。 ここで .emacs に設定を加えていないのになぜいきなり langhelp が使えるのか疑問に思うでしょう。

langhelp は el4r を使って書かれた EmacsRuby プログラムです。 また、 el4r のインストール時に el4r-rctool で .emacs を書き換えています。 そのため Emacs 起動時に el4r が使えるようになっています。 その時点で EmacsRuby プログラムの初期化は済んでいます。

現在使用中の目次を開く

langhelp は現在の major-mode で使用言語を決定します。 ruby の目次から検索するためには、 ruby-mode にしてください。 一般に major-mode の名前から -mode を抜いたのが言語名です。 graphviz-dot-mode の言語名は graphviz-dot で、 ratpoisonrc-mode の言語名は ratpoisonrc です。

まずは簡単な例からやってみましょう。 配列の値の合計を計算したい場面で、 Enumerable#inject *2の使い方がよくわからないとしましょう。 デフォルトの langhelp 起動キーは C-c s ですが、好みに応じて変えて構いません。*3 本稿ではデフォルトのままで説明します。

 C-c s inject

とさくっと打ってみましょう。 langhelp のポイントは起動した瞬間に incremental search に入っていることです。 これは、頭によぎった瞬間に検索できるようにするためです。 この 2 つのリンクが見付かるはずです。

 # (lh-ri "Enumerable#inject")
 # (lh-refe "Enumerable#inject")

そして、リンクの行で Enter *4か l を押せば内容を見ることができます。 incremental search の終了と同時に見たいならば、 Enter をポンポン♪と 2 回叩けばいいでしょう。

リンク先は別窓で表示されます。 別窓のスクロールは c と v です。 通常、 Emacs のスクロールは C-v なのでそれを連想する v と、その隣の c を割り当ててみました。 ページャのように SPC と b を押したら目次がスクロールします。 これは好みの問題なので、この挙動が気に入らない場合は config の @KEY_BINDING を書き換えて

 M-x el4r-boot

すれば反映されます。

ReFe や ri の目次はクラス・モジュール順に配列しています。 クラス名やモジュール名で検索すると、そのメソッドを一覧することができます。

こうして ReFe と ri の双方のドキュメントを統一的な方法で見ることができました!! Ruby に限らずすべての言語で同様に調べることができるのです。

言語名の目次を開く

プログラミング中にふと他の言語・コマンドのドキュメントを見たいこともたまにあります。 こういう場合、

 C-u C-c s

を押してください。 言語名がずらりと並んでいます。 さきほどと同様に incremental search に入っているので、そこから見たい言語を検索・選択してください。

langhelp のウィンドウになっている場合は d でも言語選択画面になります。

カーソル位置の単語を目次から検索

カーソル位置の単語を目次から検索することができます。 この場合、目次のうち単語を含むもののみ表示します。

 C-u C-u C-c s

あるいは

 M-x langhelp-at-point

としてください。キーに割り当ててもいいですね。

絞り込み検索

langhelp 検索中に候補を絞り込むこともできます。 検索中に

 C-o

で目次のうち検索語を含むもののみ表示します。 一覧されていて見易いし、さらに別の検索語で検索をかけることもできます。

これはいろんなクラスで定義されているメソッドのドキュメントを探すのに適しています。

リファレンスマニュアル検索

Ruby リファレンスマニュアルも目次に含まれています。 emacs-w3m がインストールされている必要があります。

たとえば組み込み変数について調べたい場合、 Migemo もインストールされていると、

 C-c s kumikomiHensuu

でリファレンスマニュアルの総目次の組み込み変数リンクへ飛びます。

ドキュメント処理クラスリファレンス

現在サポートしているドキュメント処理クラスを挙げます。 一貫した仕様として、クラス名に対応するハッシュの値はドキュメントのファイル名 (かそれに準ずるもの) です。 ファイル名中の ~ は展開されます。

regexp パラメータを指定するドキュメント処理クラスがいくつかあります。 正規表現にマッチした行を目次にするものです。

title パラメータはドキュメントのタイトルを指定します。 title はすべてのクラスで使えます。 どのドキュメントを読んでいるのかがわかるので、 title は指定しておくべきです。

動作確認には

 mklanghelp --eval='ドキュメント定義'

を使うと便利です。 標準出力に目次が出力されます。

汎用のもの

構造化された文書はこれらのクラスで処理できます。

Info

GNU Info 形式のドキュメントを処理します。 サンプル設定ファイルにも Info クラスが多いです。 GNU 標準なので当然といったら当然ですが。

パラメータ必須説明
Info必須Info ファイル。ワイルドカードも指定できます。
regexp 正規表現にマッチする行を抜き出します。省略した場合は主要な情報のみ目次になります。
extract どの情報を目次として抜き出すかを配列で指定します。

extract に指定できるのは :node, :regexp, :section, :description です。 それぞれノード、正規表現、見出し、定義リストを意味します。 省略時はそれらすべてを目次にします。 もし冗長すぎるならば、 extract パラメータを指定してください。

使用例
 {:Info => "/usr/share/info/elisp-ja*", :title=>"EmacsLisp manual (Japanese)"}

日本語版 EmacsLisp マニュアルを処理します。 このようにファイルが複数に分かれているのでワイルドカードで指定しています。

 /usr/share/info/elisp-ja.gz
 /usr/share/info/elisp-ja-1.gz
 ...
 /usr/share/info/elisp-ja-26.gz

Grep

Plain Text のドキュメントで正規表現にマッチするものを目次にします。

パラメータ必須説明
Grep 必須処理対象ファイル名。
regexp必須抜き出す正規表現。
exclude regexp にマッチしたもののうち除外するものを正規表現や文字列を要素とする配列で指定します。

旧来の定義 (src) も動作しますが、一貫性を保つために変更しました。

使用例
 {:Grep => "~/compile/narray-0.5.7/SPEC.ja", :regexp=>/^(\S.|    \S)/,  :title=>"NArray SPEC"},
 {:Grep => "/usr/share/doc/apel/README.en.gz", :regexp=>/^\*/, :title=>"apel manual"},

説明不要ですね。

Manpage

Unix 系 OS の man の目次を作成します。 Manpage クラスは例外で Manpage パラメータの値はファイル名ではありません

Manpage クラスは 2 つの使い方があります。 *5

単数の man を処理する場合

単数の man を処理する場合は man の内容を読みます。

パラメータ必須説明
Manpage必須コマンド・項目名。
section必須セクション番号。
regexp 抜き出す正規表現。
セクション中すべての man を処理する場合

C や Tcl の関数やシェルコマンドは 1 項目 1 manpage になっています。 そういうものを処理します。

パラメータ必須説明
Manpage必須セクション番号の配列。
manpath必須man が格納されているディレクトリの配列。
glob 処理するファイル名をワイルドカードで指定。
使用例
 {:Manpage=>"sgrep", :section=>1, :regexp=>/^       v\(/, :title=>"manpage"},

コマンド sgrep の man を処理する例です。 通常のコマンドなのでセクションは 1 です。 sgrep の言語仕様はこのように v(〜) という形で書かれているので、正規表現で抜き出しています。

      v(phrase):=
      v('start'):=
      v('end'):=
      ...
 {:Manpage => [1,5,6,7,8], :manpath=>manpath, :title=>"Manpages" },

シェルコマンドや設定ファイルの仕様などがセクション 1、5、6、7、8 に含まれています。

manpath は config の初めの方に定義されているローカル変数です。 *6

 manpath = %w[ /usr/share/man/ /usr/local/man/ /usr/local/share/man /usr/X11R6/man ]

環境に応じて manpath の定義を書き換えてください。

 {:Manpage => [2,3], :manpath=>manpath, :title=>"Manpages"},

C の関数などがセクション 2、3 に含まれています。

 {:Manpage => [3], :manpath=>manpath, :glob=>"*.3tcl*", :title=>"Tcl Manpages"},

Tcl のコマンドはセクション 3 でファイル名に .3tcl を含んでいます。

HTML を処理するもの

ブラウザで見られるよう HTML でドキュメントが配布されていることも多いです。 HTML ドキュメントは構成が多種多様なので臨機応変にクラスを使い分ける必要があります。

W3MLink

HTML ファイルへのハイパーリンクのみを作成します。 HTML の内容は読みに行きません。

パラメータ必須説明
W3MLink必須リンクする HTML ファイル。
label リンクの名前。
使用例
 {:W3MLink => "/usr/share/doc/stl-manual/html/index.html", :label=>"STL Guide", :title=>"Useful links"}

このドキュメント定義は次のハイパーリンクを作成します。

 # STL Guide   (lh-w3m nil "/usr/share/doc/stl-manual/html/index.html")

HTML

論理構造でマークアップされた HTML を処理するクラスです。 見出し (H1 〜 H6) と用語定義 (DT の内容) を目次にします。

パラメータ必須説明
HTML必須処理する HTML ファイル。ワイルドカード可。
noconv true ならば文字コード変換を無効にします。
recursive :next を指定すると LINK 要素で指定された次の HTML を見に行きます。
使用例

かなり多いです。

 {:HTML => "~/doc/srfi-*.html", :title=>"SRFI"},
 {:HTML=>"~/.langhelp/subversion.bluegate.org/doc/pr01.html", :recursive=>:next, :title=>"subversion book (Japanese)"}

W3MGrep

HTML を w3m -dump したもので正規表現にマッチしたものを目次にします。 w3m -dump したものを扱う以外、 Grep クラスと同じです。

HTML クラスが構造指向、 W3MGrep クラスが見た目指向です。 mklanghelp --eval で試行錯誤してみるといいでしょう。

パラメータ必須説明
W3MGrep必須処理する HTML ファイル。
regexp必須抜き出す正規表現。
exclude regexp にマッチしたもののうち除外するものを正規表現や文字列を要素とする配列で指定します。
使用例
 {:W3MGrep => "/usr/share/doc/graphviz/html/info/attrs.html", :regexp=>/^\w+$/, :title=>"Graph Attributes"}

このファイルは HTML クラスでも処理できますが、筆者の好みで W3MGrep を使っています。

W3MExtractLinks

HTML 内のリンクを目次にします。 目次ページを処理するのに適しています。

パラメータ必須説明
W3MExtractLinks必須処理する HTML ファイル。
exclude_label 抜き出さないリンク名 (の配列)
exclude_url 抜き出さない URL (の配列)

exclude_url, exclude_url は単一の文字列・正規表現か、複数の場合はそれらの配列を指定します。 目次ページを処理するときでも、ヘッダ・フッタなど余計なものがついてくるのでそれらで除外しておくといいです。

使用例
 {:W3MExtractLinks => "/usr/share/doc/stl-manual/html/stl_index.html", :title=>"STL index", :exclude_url=>[/www.sgi.com/, /index.html/]}

C++ の STL マニュアルです。 Debian パッケージとして配布されています。 その中でフッタに個々のマニュアルへのリンク以外のもの (会社のページなど) がついているので、除去しています。

検索コマンドを呼び出すもの

ドキュメントそのものだけでなく、専用の検索コマンドが用意されていることもあります。 Emacs 上で検索コマンドを実行して、その結果を別窓に表示します。 クラスの仕様は共通です。

パラメータ必須説明
クラス名必須 true
cmd 実行するコマンド

クラス名に対応するハッシュの値は必ず true になります。 それは、検索コマンドを使用することを意味します。 cmd を指定しない場合、クラス名を小文字にしたものが検索コマンド名となります。

Refe, RefeC, Ri, LuaHelp ...

 {:Refe => true, :cmd=>"refe", :title=>"ReFe (Japanese)"},

Refe クラスは、 ReFe でドキュメントを検索します。

 {:RefeC => true, :cmd=>"refe -e", :title=>"Ruby/C API (Japanese)"},

RefeC クラスは、 Ruby/C API を ReFe で検索します。

 {:Ri => true, :title=>"RI"},

Ri クラスは、 ri でドキュメントを検索します。

Ruby に特化したもの

RubyRefm

Ruby リファレンスマニュアル分割 HTML の総目次を取り込みます。

デフォルトのままの設定で使いたいならば、 ~/doc/rubyrefm 上でアーカイブを展開してください。 サブディレクトリを作らずにファイルを蒔き散らすのであらかじめディレクトリを作っておくのが肝心です。

 mkdir -p ~/doc/rubyrefm
 wget -O- http://elbereth-hp.hp.infoseek.co.jp/files/ruby/refm/temp/ruby-refm-rdp-1.9.0-ja-html.tar.gz | tar xzf -
パラメータ必須説明
RubyRefm必須リファレンスマニュアルを置いているディレクトリ

RubyRefm には総目次のファイル名そのものも指定できますが、リファレンスマニュアルを更新したときには変化してるのでおすすめできません。 ディレクトリを指定すれば、自動的に総目次を探してくれます。

使用例
 {:RubyRefm => "~/doc/rubyrefm/", :title=>"Ruby Reference Manual (Japanese)"}

説明不要ですね。

MoonWolfRubyDoc

MoonWolf 氏考案の RubyDoc 仕様のうち RubyDoc/HTMLを扱います。 しかし、その中では任意の HTML が書けてしまうので彼が書いた形式のみ動作します。 あらかじめ rubydoctool で HTML に変換してから使ってください。

パラメータ必須説明
MoonWolfRubyDoc必須rubydoctool により変換された HTML ファイル名
使用例
 {:MoonWolfRubyDoc => "~/ruby/htmlsplit.html", :title=>"HTMLSplit (Japanese)"}

~/ruby/htmlsplit.rb をあらかじめ rubydoctool で HTML を出力したものをファイル名に指定しています。

 cd ~/ruby; ruby rb2h.rb htmlsplit.rb htmlsplit.html

RD

Ruby の伝統的なドキュメントフォーマット RD を扱います。 書きやすく読みやすい*7ので、筆者も含めドキュメントや日記やメモを書くのに RD を活用している人は多いと思います。 HeadLine や DescList や MethodList を目次にします。

パラメータ必須説明
RD必須RD ファイル名。
exclude 抜き出さない行や正規表現の配列。
使用例
 {:RD => "~/compile/sys-proctable-0.6.4/doc/linux.rd", :title=>"ProcTable"}

他言語に特化したもの

Ruby 以外の言語に特化したクラスも存在します。

Perl 用クラスには PodSections, PerlFunc, PerlDoc があります。 どれも perldoc コマンドを使用するクラスです。

Lua 用クラスには LuaHelp があります。 LuaHelp は Lua のマニュアルを luahelp コマンドで検索するクラスです。 LuaHelp は検索コマンドを使用する最も簡単な目次作成クラスなので、参考になると思います。

PHP 用クラスには PHPManual があります。 マニュアル中の膨大なファイルをワイルドカードで区別しています。

Python 用クラスには PythonLib があります。

ハイパーリンク関数リファレンス

目次ファイルの行末に書かれているのが S 式ハイパーリンクです。 その行がハイパーリンクであることを明示するため、S 式には色がついています。 厳密には lh- で始まる行末の式のみ色がつきます。

ハイパーリンク関数はどれも情報源を別窓に表示します。 情報源はファイルだったり、manpage だったり、シェルコマンドの実行結果だったり、様々です。 そういう詳細は関数内部に隠しています。

また、時間とメモリの節約のためすでに開いたバッファは再利用されます。 とくに man や emacs-w3m で開くのは時間がかかります。 それに毎回新しいバッファを作成していてはすぐに大量のバッファに埋め尽くされてしまいます。

引数 LINE が文字列で指定されている場合、バッファの先頭から LINE を検索し、見付かった行をウィンドウ最上部にして表示します。 つまり、「ここから読んでほしい」という意思表示です。

アンカーとは、<<< と >>> で囲まれた文字列です。 ドキュメント定義で title を指定した文字列が目次のアンカーになっていることに気付くでしょう。 引数 ANCHOR を取る関数は、アンカーを検索し、ウィンドウ最上部に表示します。 末尾に「#文字列」がついている URL をブラウザで開くとページの途中から表示されますが、それと似ています。 もちろんアンカーのかわりに LINE を取る関数に「<<<アンカー文字列>>>」を指定してもうまくいきますが、それだとカッコ悪いですね。

(lh-language-index LANG)

言語 LANG の目次を開きます。 C-u C-c s で全言語の目次を開いたときにずらりと並びます。 現在のところ全言語の目次以外に使っていませんが、関連言語を開くのに便利でしょう。

(lh-sh COMMAND &optional LINE)

シェルコマンド COMMAND の実行結果を表示します。

(lh-file LINE FILENAME)

ファイル FILENAME を開きます。

(lh-view LINE FILENAME)

ファイル FILENAME を view-mode で開きます。 *8

(lh-anchor ANCHOR FILENAME)

ファイルを開き、アンカーへ飛びます。

(lh-man MANPAGE)

(lh-man* LINE MANPAGE)

MANPAGE を開きます。

(lh-info LINE BOOK)

info を開きます。 BOOK とは「(info名)ノード名」のことです。

(lh-to ANCHOR)

目次内のアンカーへ飛びます。

(lh-w3m LINE FILENAME)

emacs-w3m で FILENAME を開きます。

(lh-refe METH)

(lh-ri METH)

refe, ri コマンドを実行してメソッドのドキュメントを表示します。

(lh SEXP)

SEXP を評価するだけです。 これは明示的に色付けをするためだけに使います。 *9

(lh-ow SEXP)

SEXP を評価すると同じウィンドウが選択されてしまう式を評価するのに使います。 実用性のない例ですが

 (shell)

は *shell* バッファが選択されます。一方

 (lh-ow (shell))

は *shell* バッファが別窓に出てきます。

ow は other window の略です。

実装の詳細

langhelp は目次作成スクリプト mklanghelp と、Emacs をコントロールする EmacsRuby スクリプト langhelp.rb の 2 つで構成されています。

設定ファイルは両者で共有します。 適用場所こそ違うものの、どちらも Ruby スクリプトだからこそ可能なのです。

新たな目次作成クラスの書き方

目次作成クラスはどれも AbstractIndex を継承しています。 新たな目次作成クラスを作成するときは、 AbstractIndex かそのサブクラスを継承しましょう。 ユーザ定義目次作成クラスは ~/.langhelp/mklanghelp-init.rb に書いてください。

init(x={})

クラスの初期化には init メソッドが使われます。 initialize ではないことに注意してください。 ちなみに initialize では目次作成クラス共通の初期化をして最後に init を呼んでいます。 init の引数には「ドキュメント定義」が Hash で渡ります。 慣習上ドキュメント定義は x という変数名を使っています。 *10 init では目次作成処理のために必要なインスタンス変数の定義をします。 実際の目次作成処理は書いてはいけません。

さて、ドキュメント定義の「(字面上) 最初」のキーはクラス名だったことを思い出してください。 クラス名に対応するハッシュの値を取り出すには

 x[self.class.to_s.intern]

と書けますが、長いしかっこ悪いので

 arg1

というアクセサを設けました。 (くどいですがあくまで字面上) 最初の引数という意味です。

使えるアクセサをまとめておきます。

arg1 最初の引数。処理される主体 (ファイル名など) が入る。
title ドキュメント定義で :title に指定されたもの。
conf config を Hash のようにアクセスできる。

必要な情報をインスタンス変数に取り出した後、ファイル名を normalize します。 ドキュメント定義ではホームディレクトリの ~ をファイル名に含めることができます。 「ドキュメント定義言語」はドメイン特化言語なので人間にやさしくなくてはなりません。 この段階で File.expand_path しておきます。 *11 簡単のため、normalize_filename! メソッドを用意しています。

 normalize_filename! 変数, [変数...]

と使います。 もちろんファイル名を格納しているインスタンス変数を指定するのです。 破壊的に更新されます。

また、ドキュメント定義中配列が指定できるパラメータがありますが、1 要素の場合は配列ではなくてそのまま指定できます。 これも同じく「人間にやさしく」という理由です。 そのため配列を指定するパラメータについては mkarray メソッドで明示的に配列化してください。

init がやるのはあくまでここまでです。 言い換えると init は「ドキュメント定義」を解釈するメソッドです。

to_e(io)

サブクラスで定義するもうひとつのメソッドが to_e です。 このメソッドで実際に目次を書き出します。 e というのは目次ファイルの拡張子 e だと思ってください。 引数の io は出力のための IO オブジェクトかそれと同じインターフェースを持つオブジェクトです。 おおまかな構造はこんな感じになります。

 目次オブジェクトの配列.each do |目次オブジェクト|
   io << 目次オブジェクトから S 式ハイパーリンクに変換した文字列
 end

ここの部分も抽象化してしまうと、かえってわかりにくくなると判断したため、to_e をサブクラスで再定義させました。 目次オブジェクトの配列はサブクラスでメソッドに切り分けていることが多いです。

それぞれのクラスの to_e メソッドを参考にしてください。

ユーティリティメソッド

目次作成クラスを書くときに、これらのメソッドを使ってください。

normalize_filename!(*variables)

変数に格納されているファイル名を破壊的に normalize します。 現時点では File.expand_path したものに置き換えるだけです。

mkarray(obj)

obj が配列ならばそのまま、配列以外ならば obj のみを要素とする配列を返します。 Object#to_a とほぼ同じです。 *12 配列が指定できるパラメータに使用してください。

kconv(&block)

ブロック評価結果の文字コードを config の @ENCODING に応じて統一します。 ドキュメントにどの文字コードが使われていても、目次内では統一する必要があります。 さもないと文字化けが起きてしまいます。

なお、Emacs は自動で文字コードを判別してくれるので、目次とドキュメントの文字コードが異なっても大丈夫です。

検索コマンドを実行するハイパーリンク関数の書き方

検索コマンドを使う場合はドキュメント定義を書くだけで自動的にハイパーリンク関数が定義されます。 ドキュメント定義にてクラス名に対応するハッシュの値を true にします。 config は mklanghelp 側でも EmacsRuby 側でも読み込まれます。 *13

ハイパーリンク関数は次の仕様を満たしています。

  • コマンドを実行して結果を表示します。
  • 定義される関数名は "lh-#{クラス名.downcase}" です。
  • cmd パラメータは実行するコマンドを定義します。
  • cmd パラメータが定義されていない場合は「クラス名.downcase」になります。
  • コマンドの引数は関数の引数です。
 {:Refe => true, :title=>"ReFe (Japanese)"}

このドキュメント定義から、refe コマンドを実行する lh-refe 関数が定義されます。

 {:RefeC => true, :cmd=>"refe -e", :title=>"Ruby/C API (Japanese)"}

このドキュメント定義から、refe -e コマンドを実行する lh-refec 関数が定義されます。

なお、 Ri for (X)Emacs がインストールされている場合、それで定義された ri 関数を利用するように lh-ri 関数を override しています。 ちょっとしたトリックなので詳しくは langhelp.rb の LangHelp::FindXXX クラスを見てください。

もちろんこういうクラスでも to_e メソッドを定義しなくてはいけません。 一番単純な例は他言語ですが langhelp/lh_lua.rb です。

質問は随時受付中

設定ファイルは煩雑なので、

  • うまく設定できない
  • 動かない

などの質問はいつも受け付けています。 また、 config.sample に書かれていないファイルの設定例も大募集です。 動作確認次第、次期リリースに反映させます。 みなさんの力で langhelp を育てていきましょう。

おわりに

一風変わった汎用ドキュメント検索ツール langhelp を紹介しました。 ドキュメントを素早く参照することで、みなさんの生産力が向上すれば作者として最高の喜びです。

著者紹介

rubikitch

Emacs と CUI をこよなく愛する rubyist です。 Ruby 歴は8年で、 Debian GNU/Linux を10年ほど使っています。 ratpoison, GNU Screen, GNU eev, conkeror などのキーボード指向のプログラムを愛用しています。

Ruby や Emacs の雑誌記事・書籍を執筆します。 rubikitch at ruby-lang dot org までお気軽に御連絡ください。


更新日時:2006/11/22 00:56:22
キーワード:
参照:[Rubyist Magazine 0017 号] [0017 号 巻頭言] [分野別目次] [各号目次] [prep-0017]

*1 EmacsRuby の実用的なサンプルという目的もあります.

*2 inject は慣れるまでけっこう曲者です。今は筆者の大好きなメソッドのひとつ。

*3 筆者は C-@ C-s にしています。

*4 当然 C-m でもよい。筆者は C-m 派。

*5 いい名前が思い付かなかったため、パラメータの有無で動作制御しています。

*6 %w 記法は空白を含まない文字列の配列です。空白で区切ります。

*7 しかし Inline が入ると読み辛い。筆者もめったに Inline は使わない。

*8 筆者は view-mode を多用します。 view-mode では w3m に似たキー割り当てをしています。そのため閲覧のみの場合は指に負担をかけずにすみます。

*9 「(lh」という文字列で始まり行末までを色付けする仕組みから。

*10 短いから。数学の関数 f(x) の入力である x というイメージ。

*11 将来ほかのファイル名拡張記法を用意するかもしれない。その場合も normalize で展開しておくだろう。

*12 あえて用意しているのは、Object#to_a は obsolete だから。

*13 DRY 原則遵守。