CPAN 最速検索の劣化コピー作ってみた

mala さんの CPAN 最速検索を便利に使わせてもらってるんですが,操作上ちょっと不満なところがいくつかありまして。

  • カーソルキーのオートリピートがきかない
  • ホイールがきかない
  • 候補の同時表示数が固定

Firefox だからかもしれないですけど。

んで改造しようと思ったんですがわたしにはちと難しそうだったのでいっそ自分なりに書いてみようと思いました。もちろんライブラリを使わずに書く技能はないので jQuery を使いました。

標準機能だとマウスホイールをトラップするのがたいへんそうだったのでプラグインも使いました。


mala さんのコードをチラ見すると

してる感じでした。

私はディストリビューション(というか親パッケージ?)の情報はいらないので,なんとかパッケージ一覧を自力で作成できればなんとかなりそうだなと思いました。


cpan を実行する時によくでてくる 02packages.details.txt とかいうのにパッケージ全リストがあるみたい。

File:         02packages.details.txt
URL:          http://www.perl.com/CPAN/modules/02packages.details.txt
Description:  Package names found in directory $CPAN/authors/id/
Columns:      package name, version, path
Intended-For: Automated fetch routines, namespace documentation.
Written-By:   Id: mldistwatch.pm 1080 2008-12-16 04:08:35Z k 
Line-Count:   61204
Last-Updated: Wed, 17 Dec 2008 03:27:54 GMT

AAA::Demo                         undef  J/JW/JWACH/Apache-FastForward-1.1.tar.gz
AAA::eBay                         undef  J/JW/JWACH/Apache-FastForward-1.1.tar.gz
AAC::Pvoice                        0.91  J/JO/JOUKE/AAC-Pvoice-0.91.tar.gz

...... snip snip snip ......

このヘッダをとばして,先頭の文字列を切り出せばパッケージになりそう。

てことで,

#!/usr/bin/perl
# - package.pl

use strict;
use warnings;

while (<>) {
    chomp;
    last if $_ eq q{};
}

print "[\n";
while (<>) {
    chomp;
    s{ \s .* \z }{}xmso;

    print q{"}, $_, q{"}, q{,}, qq{\n};
}
print "]\n";

捨てスクリプトなのでかなり適当ですが,こいつを 02packages.details.txt にたいして実行すると,

[
"AAA::Demo",
"AAA::eBay",
"AAC::Pvoice",
// ...... snip snip snip ......

みたいになるのでこれを JSON で読み込めばよろしいと。


あと,Perl にバンドルされてるドキュメント…… perlsyn.pod とかも見れるようにしたほうが便利だよね,ということで,perl.pod からマニュアル一覧を取得して配列で吐くスクリプトも書きました。

#!/usr/bin/perl
# - bundles.pl

use strict;
use warnings;

my @modules;

my $started;
while (<>) {
    chomp;

    if (! $started) {
        if (m{ \A =head2 }xmso) {
            $started = 1;
        }
        next;
    }

    last if m{ \A =head1 }xmso;
    next if m{ \A =head  }xmso;

    last if m{ \A \S }xmso;

    if (m{ \w+ }xmso) {
        push @modules, $&;
    }
}

print "[", "\n";
print join qq{\n}, map { qq{"$_",} } sort @modules;
print "\n", "]", "\n";

こいつを実行すると,

[
"perl",
"perl5004delta",
"perl5005delta",
// ...... snip snip snip ......

みたいになります。


んでよくよく考えたら,全パッケージ情報を一括で読み込むのなら,Ajax で遅延ロードする必要ないんじゃね?と思って,<script> タグで読み込むようにしました。いままでに生成した2つの JavaScript ファイルに変数宣言もつけて一つのファイルに吐くように Makefile を書いてみました。

SITE=http://ftp.kddilab.jp/CPAN/

TARGET1=packages.js
SOURCE1=02packages.details.txt.gz
TARGET2=bundles.js
TARGET3=vars.js
TARGETS=$(TARGET1) $(TARGET2) $(TARGET3)

all:		$(TARGETS)

clean:
	rm -f $(TARGETS)

$(TARGET3):	$(TARGET1) $(TARGET2)
	echo "var packages_static =" >  $@
	cat $(TARGET1)               >> $@
	echo ";"                     >> $@
	echo "var bundles_static ="  >> $@
	cat $(TARGET2)               >> $@
	echo ";"                     >> $@

$(TARGET1):	$(SOURCE1)
	zcat $< | perl packages.pl > $@

$(TARGET2):
	cat `perldoc -ml perl.pod` | perl bundles.pl > $@

$(SOURCE1):
	wget $(SITE)modules/$(SOURCE1)

これで結合したファイル vars.js ができる,と。


あとはノリで JavaScript コーディングしていきました。ほんとは構造化して書いたらかっこいいんでしょうけど,めんどうだったのでかなりフラットに書いてます。

JavaScript のソースはこちら→ 502 Bad Gateway

jQuery を使ってるのに documente.getElementById() してたりするのは,理想的には jQuery に依存したくないというのがあったのと,ID 指定で単一のエレメントが返るとわかってるセレクタにたいして $('#hoge').get(0) とか $('#fuga')[0] とか書くより(体感できないとは思いますが)速いかなと思ってそうしてます。とはいってもイベントまわりとかブラウザ間非互換性を埋める上で jQuery にかなりお世話になっています。


いままででてきた登場人物を使う index.html がこちら→ 502 Bad Gateway

実際には css ファイルは分離してますが例なので組み込んでます。あと Yahoo UI の reset.cssfonts.css も使っています。

で,出来上がってから元記事を読んでみた

省メモリ、高速に動作するように工夫してあります。

  • 配列に変換せずに一つの巨大な文字列から検索 → 切り出し。
  • クロージャで次の検索結果を取得する関数を保持しておいて、描画が必要になった時点で検索を実行。
    • その代わりにトータルのヒット件数がわかりません。
http://la.ma.la/blog/diary_200604021538.htm

な,なるほどー。

わたしのコードでは

  • 配列として読み込んでいる
    • 巨大文字列より大量にメモリを消費してそう。最初に遅延してそう。
  • 検索をおこなうと,都度都度検索結果を配列に格納してます
    • 検索をおこなうたびメモリと実行時間を消費
    • そのかわりヒット件数をだせた
  • カーソルを動かすたび,再描画
    • 遅いマシンだとちらつく可能性あり

のように富豪的になってます。

そのかわり自分で手をいれられるのでトレードオフとしてまあいいか,と。PageUp,PageDown,Ctrl+Home,Ctrl+End など使いたいキーバインドも使えるようになりましたし。あと iframe で pod page を開く機能もつけてます。デフォルトで disable してますけど。


お勉強になりました。これはないわーというところがあったら遠慮なくご指摘ください。

よくある質問と答え

  • XSS がある気がしますが
    • モジュール名にヤバい文字列がないことを前提として書いてます。すいません。
    • 自分使いなのでいいかと思った。
  • IE で動きません
    • すいません。普段つかいが Firefox なもので Firefox でしか確認してません。
    • 一応そこそこ動くように調整を重ねましたが,キーボードによるセレクションがまったく動作しません。