Necessity is the mother of invention

2004年4月号

../images/misc/red-white.jpg

ビルの屋上に赤白のクレーンが乗っかっているのを 見るたびに 「ビルがニョキニョキ伸びで行き、 近くの駅の乗降車数もどんどん増えて行き、 町ががばがば発展する」 という姿を連想してしまいます。

そうそれは「 A列車で行こう 」の世界です。

A列車で行こう 」は典型的なバブルの気分を反映したゲームだったと思います。 けっこう熱中したものです。でも続編の話はとんと聞こえてきません。 バブル崩壊後は、 「人やお金の流れをそんなにも単純にモデル化できない」 ということがわかって(バレて)しまい、 このようなゲームは実社会とかけ離れてしまってウケなくなってしまったので がでないのかな、とふとそう思いました。 まったくの的外れかもしてませんが。

そういった感慨を覚えてこのような風景に感傷をいだいてしまうのでした。

でも著者はゲームにはとんとご無沙汰していまして、もしかすると 「A列車で行こう IV」 が最後に熱中したゲームだったかもしれません...。

search & search

「知りたいことを即座に探し出す能力」がますます重要となってきている 昨今、今月号はメールの全文検索を取り上げます。 そういえばその昔 「読みもしないメーリングリストにどーして入っているの?」 と問われたことがありました。 その時は 「キーワード検索してヒットすれば読むんだもん」 と応えたことがありましたっけ。

それが現在では 「知りたいことを探し出す」のはもっぱら Google 等の検索エンジンが使われるようになりました。

両者は一長一短ありまして、で、 今回は「メールの全文検索」から取りあげましょうってことなのです。

しかし残念ながらこれは Courier IMAP と Gnus に特化した内容となっています。 「特化した」などと言うとりっぱに聞こえますが、 とても場あたり的で泥臭い Courier IMAP と Gnus にしか適用できない内容となっています。 でも、結果はいたって良好なもの[1] なのです。

なお、Courier IMAP 側は同じ方法で行けるでしょうから、 MUA 側の対応はそれぞれをお使いの方にお任せします。 ここにリンク場所を予約しておきますので、 できたら教えてくださいな。

この号がきっかけで様々な環境が便利になりますように.....。

なお、「X-ML-Count: のシーケンス番号で記事を表示」と 「Namazuを使った全文検索」は独立しているのでどちらか片方だけ 導入するのでも便利でしょう。

X-ML-Count:

メーリングリストではそれぞれのメールにユニークな ID として シーケンス番号がふられることが多々あります。 シーケンス番号はそれぞれのメールに次の形式のヘッダとして付加されます。

	X-ML-Count: 1024

この番号の記事を一発で表示できたらどんなに便利だろう、 番号が 1024 の記事を表示するのに 該当するグループのサマリバッファ上で「j 1024 RET」とキー入力するだけと、 そーその だけ は便利だろうとずっとやりたかったことなのでした。 今まではできなかったのです。

このような素敵なことができるようになったので「書かずにはいられない」 と思ったわけなのです。この方法を次節でお話します。

なお、本稿では“メール”を Gnus の Info に記載されている “Article” の訳語 である“記事”と記載しています。 Gnus ユーザには違和感はないと思いますがね。

courierimapuiddb

Courier imap では ~/Maildir/.foo/courierimapuiddb が記事番号とファイル名の 対応付け DB となっています。

courierimapuiddb は先頭行を除き「記事番号 ファイル名のベース」という形式に なっています。 先頭行では次に受け取って振られる予定の記事番号が格納されます。 次例の 4225 がそうで「最終行の記事番号 +1」が格納されます。

~/Maildir/.meadow-develop/courierimapuiddb の例

1 1077756319 4225
1 1077633880.29676_1.lucasi
2 1077633881.29677_1.lucasi
  :
4224 1077975507.20357_1.lucasi

1番目の記事の X-Ml-Count: ヘッダを調べると 次のようになっています。

$ ls .meadow-develop/cur/1077633880.29676_1.lucasi*
.meadow-develop/cur/1077633880.29676_1.lucasi:2,S

$ grep "^X-Ml" .meadow-develop/cur/1077633880.29676_1.lucasi:2,S
X-Ml-Count: 540

ファイル名の“:”より後ろは“既読であること”や“移動したこと” 等を現わす識別に使われているようです。 この例では シーケンス番号は 540 というのがわかります。 この 540 で Gnus から記事を参照するには、 逆にこれを courierimapuiddb に反映させてあげれば良いわけで、つまり

540 1077633880.29676_1.lucasi

となれば、meadow-develop グループの540番目の記事としてアクセスでき、 つまりGnus のサマリバッファ中で、j 540 RET を実行すれば該当記事が 表示されるという、そういう仕組みです。

ではこのようにアクセスできるように courierimapuiddb を変換しましょう。 次のスクリプト(renumid)で変換できます。

#!/bin/sh

# for meadow-develop, semi-gnus-ja, emacs-w3m, mew-win32...
# cd Maildir/.meadow-develop ; ./renumid

DB=courierimapuiddb
TFILE=./tmpfile

cp /dev/null $TFILE

for F in cur/*
do
  (echo -n $F ; sed -n -e '1,/^$/s/^[Xx]-[Mm](ai)*[Ll]-[Cc][Oo][Uu][Nn][Tt]:/0/p' $F)
  | sed -e 's/cur/(.*):2.* (0*)([1-9][0-9]*)/3 1/g' >> $TFILE
done 

NUM=`tail -1 $TFILE | sed -e 's/(.*) .*/1/g'`
NUM2=`expr $NUM + 1`
HEAD=`head -1 $DB | sed -e 's/(.* .*) .*/1/g'`

mv $DB $DB.orig
echo "$HEAD $NUM2" > $DB
sort -n $TFILE >> $DB

rm $TFILE

このスクリプトは次のように変換したいメールフォルダ上で実行します。 もし courierimapuiddb が壊れてしまったら迷わず courierimapuiddb を消しちゃいましょう。 MUA で該当グループをアクセスすれば Courier imapd がまた作ってくれますから。 だから心配せずに...、やり直しがきくのです。

cd Maildir/.meadow-develop
renumid 

なんだか騙されたような方法ですがこれでうまく行くのです。 ここではシェルスクリプトで書いて[2]いますが、 どのような手段で実現しても良いでしょう。 (もっと効率の良い、実行が速いものができたらちょうだいねってことで)

Do Namazu!!

前節では、Gnus からみた記事番号と Courier imap が扱う ファイル名との対応付けの仕組みを述べました。 このことを応用すれば「 Namazu で全文検索できるやん!」 ということに思い至るでしょう。とーぜんの帰結ですね。

uidDB

ふつうに Namazu で Maildir をインデックスして、検索すると次のように なります。

$ namazu -q -a -l "namazu maildir" mail.orig
/home/kose/Maildir/.semi-gnus-ja/cur/1077643681.26442_1.lucasi:2,S
/home/kose/Maildir/.semi-gnus-ja/cur/1077643680.26426_1.lucasi:2,S
/home/kose/Maildir/.semi-gnus-ja/cur/1077643680.26437_1.lucasi:2,S
/home/kose/Maildir/.semi-gnus-ja/cur/1077643308.23846_1.lucasi:2,S
/home/kose/Maildir/.semi-gnus-ja/cur/1077643681.26444_1.lucasi:2,S
/home/kose/Maildir/.semi-gnus-ja/cur/1077643682.26445_1.lucasi:2,S
/home/kose/Maildir/.mew-win32/cur/1077612082.2953_1.lucasi:2,S
/home/kose/Maildir/.debian-users/cur/1077624714.12629_1.lucasi:2,S
/home/kose/Maildir/.semi-gnus-ja/cur/1077643681.26440_1.lucasi:2,S
/home/kose/Maildir/.cmail/cur/1077623593.6013_1.lucasi:2,S
/home/kose/Maildir/.Wanderlust/cur/1077619308.28047_1.lucasi:2,S
/home/kose/Maildir/.Wanderlust/cur/1077619306.28029_1.lucasi:2,S
/home/kose/Maildir/.Wanderlust/cur/1077619307.28030_1.lucasi:2,S

ここで1件目にヒットしたファイルは、courierimapuiddb を参照すると、

6445 1077643681.26442_1.lucasi

でした。この DB を使って Namazu の検索結果を 次の「グループ名、記事番号」形式に変換することさえできれば、 Gnus から表示できるってことになります。

Gnus からは実際のファイル名情報は必要なくて、 「グループ名」と「記事番号」さえわかれば十分ですからね。

$ namazu -q -a -l "namazu maildir" mail
/home/kose/Maildir/.semi-gnus-ja/6445
/home/kose/Maildir/.semi-gnus-ja/6433
/home/kose/Maildir/.semi-gnus-ja/6435
/home/kose/Maildir/.semi-gnus-ja/3869
/home/kose/Maildir/.semi-gnus-ja/6447
/home/kose/Maildir/.semi-gnus-ja/6452
/home/kose/Maildir/.mew-win32/2469
/home/kose/Maildir/.debian-users/5624
/home/kose/Maildir/.semi-gnus-ja/6443
/home/kose/Maildir/.cmail/886
/home/kose/Maildir/.Wanderlust/5155
/home/kose/Maildir/.Wanderlust/5142
/home/kose/Maildir/.Wanderlust/5147

はい。ここまではいいですよね。

ところで Namazu のファイル名データベースは NMZ.field.uri でした。 これは1行にひとつのファイル名が書かれるといった DB です。 何行目かという情報さえ保存されていれば良く、 それぞれの行を書き替え後、 rfnmz を使って NMZ.field.uri は再構築できます。

つまり、NMZ.field.uri 中の

/home/kose/Maildir/.semi-gnus-ja/cur/1077643681.26442_1.lucasi:2,S
の行を courierimapuiddb を参照して、
/home/kose/Maildir/.semi-gnus-ja/6445
に書き替え、その後 rfnmz で再構築すれば良いわけです。

mknmz & rebuild uidBD script

次にあげるスクリプトで

を行っています。

~/Maildir の全てのフォルダを検索の対象とはしていなくて GROUPs にて指定します。

このスクリプトでは Namazu のインデックス格納場所は触ってないので、 NMZ.field.uri は別の場所で変更するので差分アップデートができます。

#! /bin/ksh

MAILDIR=/home/kose/Maildir
NAMAZUDIR=/home/kose/Namazu
#LOG=$NAMAZUDIR/log
LOG=`dirname $0`/.log/`basename $0`
OUTDIR=$NAMAZUDIR/mail

export LANG=ja_JP.EUC-JP

if test ! -d $OUTDIR.orig; then
   mkdir $OUTDIR.orig
fi

# backup log
mv $LOG $LOG.d

# cleanup index.
# rm -f $OUTDIR.orig/*

## Namazu
### some folders
GROUPs='
.apel-ja
.ding
.direct
.emacs-mime-ja
.emacs-w3m
.emacs21-users-ja
.meadow-develop
.meadow-users-jp
.mule-ja
.semi-gnus-ja'

cp /dev/null $NAMAZUDIR/folders

for F in $GROUPs
do
   echo "/home/kose/Maildir/"$F >> $NAMAZUDIR/folders
done

mknmz $1 
    --all --mailnews --no-encode-uri 
    --exclude="Maildir/.spam|courierimap|/new/" 
    --include=$NAMAZUDIR/mknmzrc 
    --output-dir=$OUTDIR.orig 
    --target-list=$NAMAZUDIR/folders | tee -a $LOG

if test ! -d $OUTDIR; then
   mkdir $OUTDIR
fi

rm -f $OUTDIR/*
cd $OUTDIR
ln -s $OUTDIR.orig/* .
rm -f $OUTDIR/NMZ.field.uri $OUTDIR/NMZ.field.uri.i

cat<<EOF > $NAMAZUDIR/uid2url
#! /usr/bin/awk -f

BEGIN{
  if(ARGC != 3){
    printf("Usage: %s ./foo/courierimapuiddb NMZ.field.urin", ARGV[0]);
    exit 0;
  }
  while ((getline < ARGV[1]) > 0){
    table[$2] = $1;
  }
  ARGV[1] = "";
  FS = "/";
}
{
  if($NF ~ /:/){
    file = substr($NF, 1, index($NF, ":")-1);
    # printf("table[%s] = %sn", file, table[file]);
    if(table[file] != ""){
      fs = index($0, ".");
      fe = index($0, "/cur/");
      printf("%s%s%sn",  substr($0, 1, fs),
	     substr($0, fs+1, fe-fs),
	     table[file]);
    }else{
      printf("%sn", $0);
    }
  }else{
    printf("%sn", $0);
  }
}
END {
}
EOF


############ re-build courierimapuiddb
cd $MAILDIR

cp -p $OUTDIR.orig/NMZ.field.uri NMZ.field.uri.0

#for D in .[a-zA-Z]*
for D in $GROUPs
do
  mv NMZ.field.uri.0 NMZ.field.uri.1
  echo "== $D =="
  awk -f $NAMAZUDIR/uid2url $D/courierimapuiddb NMZ.field.uri.1 
  >> NMZ.field.uri.0
done

mv NMZ.field.uri.0 $OUTDIR/NMZ.field.uri

cd $OUTDIR
rfnmz .

#### error
grep ":" $OUTDIR/NMZ.field.uri

exit 0

最後に行っている grep で “:”を含むファイル名が存在した場合は、 NMZ.field.uri の変換漏れがあることになります。 その時は、該当ファイルのフォルダの courierimapuiddb を確認しましょう。 MUA で一度もアクセスしていないグループだとすると courierimapuiddb が存在しませんから。

このスクリプトの NMZ.field.uri 変換部分はけっこう遅いです。 どなたか高速動作するように書き替えてくださいな。

Serch Engine with Gnus

Gnus から検索するツール(elisp)には次のふたつのものがあります。

gnus-namazu.el は Namazu に特化したものです。

nnir.el は glimpse、 wais、 excite、 imap、 swish++、 swish-e、 namazu と様々な検索エンジンに対応しています。 ちなみに検索エンジンに imap を指定すれば IMAP サーバの持つ検索機能を 使って検索を行うことができます。

今回の Courier imap 対応は今のところ gnus-namazu.el にだけ施したので、 gnus-namazu.el を使います。

gnus-namazu.el

では Gnus で検索してみましょう。

gnus-namazu.el は T-gnus に含まれていて、 t-gnus-6_17-quimby 枝 に commit 済みです。 ここ( gnus-namazu.el.bz2 )からも入手できます。

これは もちろん他の Gnus (No Gnus や Oort Gnus や他の T-gnus)でも使えます。

次の設定を行います。[3]

;; Maildir を絶対パスで指定します。
(setq gnus-namazu-imap-maildir "/home/kose/Maildir")

;; Namazu の検索インデックスのディレクトリを絶対パスで指定します。
(setq gnus-namazu-index-directories (list "/home/kose/Namazu/mail"))

;; グループバッファでグループ名の前に付いている文字を指定します。
(setq gnus-namazu-imap-group-prefix "nnimap+foo:INBOX.")

もし Namazu がリモートで動作しているのなら次の設定を加えることに より ssh を使って namazu を起動することができます。 (これを設定しない場合はローカルにある namazu コンマンドを実行します)

(setq gnus-namazu-command-prefix '("ssh" "-x" "namazu-server"))

これは次のように実行されます。

	ssh -x namazu-server namazu query ...

IMAP サーバにメールも Namazu のインデックスも置いてあって、 Meadow や リナザウ などで ssh を使って検索というような 環境がこれで整うわけです。

素敵でしょ!!

今月の update

update

今月は次のものがリリースされました。

マイブーム

娘の卒園式では今回もビデオ係をやります。 編集して DVD にして配ります。 デジカメで撮った 1,000 枚ほどの JPEG ファイルもスライドショーとして 収録します。

ってなわけで Adobe Premiere で行事の映像を編集していますが、 できあがりはやっぱ素材のできにかかっています。 自分ちの子どもだけじゃなくまんべんなく撮っていないと.....。 映像がないことにはいくらがんばって編集しようとしても....なのですね。

ってなわけで、Emacs 関連ではなーんもありません。 だから来月号の予定はまだなーーーんもありません。


Footnotes:
[1]  「筆者にとってはね」とただし書きを付けておきましょう。
[2]  だって Perl語も Ruby語も話せないんだもん。
[3]  絶対パスで指定するようにしたのは、リモートとローカルで (expand-file-name “~/”) の結果が違う環境があるからです。 例えばローカルで Meadow を使う場合などです。


Copyright (C) 2004 KOSEKI Yoshinori. < kose@meadowy.org >