ついにアイデアがやってきた。
MewというGNU Emacs 上 のMUAを使っている。なかなか気に入っているのだが、ひとつ不満 に思うのは、1メイル1ファイル方式のため、「ある語句が含まれるメイ ルを検索する」ということが不得手な点。
いや、ぼくが調査探究を怠っているだけで、できるのかも知れない。
でもそうするとRubyでプログラムを書く機会が奪われるので、そんな機 能はないことにしてもらおう。
そこで、このいわゆる「串刺し検索」を行なうツールを書いてみよう。 まずはコマンド行から使うバージョンを作り、それをRuby/TkでGUI化し てみよう。
要するに、ディレクトリを降りていきながらの再帰的grepである。途 中まで書いてふと気づいたのだが、 Ruby本に載って いるサンプルrgrep.rbをちょっと変更すればできてしまうのである。 くやしいのである。だから自己流で行くだけ行ってみるのである。
rgrep.rbは手続き的に書かれているが、こちらは「フォルダひとつの 処理をひとつのオブジェクト(インスタンス)が受け持つ」ということ にして書いてみた。インスタンスの生成や消滅のコストを考えれば、こ ちらの方が遅い(筈)。でも普通の再帰(?)はもうさんざん書き飽きてい るし。
駄洒落じゃないんです。
いちおうこんな風にしておこう。
% mewgrep.rb <-a | foldername> [pattern]
folder/subfolder
のようにして指定
する。
このツールは当然ながらMewに依存している。だから仕様や内部構造 がMewの仕様に依存するのも当然である。そこでまず、Mewの仕様をよく 観察する。
というところから、次のようにやっつけることにしよう。
コメントとかメソッド名、変数名などを エスペラントで書いたりしている ところがあるけれど、気にしないでください。
1: #! c://loka/ruby/bin/ruby 2: # 3: # mewgrep.rb -- sercxi sxablonon el mesagxojn de Mew 4: # 5: # mewgrep.rb <-a | dosierujanomo> [sxablono] 6: # 7: # Se oni specifas opcion -a, gxi sercxas el cxiuj dosierujoj. 8: # Se oni funkciigas gxin sen sxablono, gxi postulas enigon de 9: # uzanto. 10: # 11: 12: require 'jcode' 13: $KCODE = 'EUC' 14: 15: class MewFolder 16: require 'kconv' 17: require 'nkf' 18: 19: # path .. Mewのメイルフォルダを指すパス 20: def initialize(path) 21: @path = File::expand_path(path) 22: @dir = Dir.new(@path) 23: @summary = {} 24: end 25: attr_reader :path 26: 27: def retrieve(pattern) 28: # .mew-cacheの内容を読み込んでおく 29: fari_resumtabelon() 30: 31: # ディレクトリ内の各ファイルを数値的に整列 32: # (ファイル名 = メッセージ番号のため) 33: entries = @dir.collect{|name| 34: [name, 35: path = File::expand_path("#{@path}/#{name}"), 36: File::stat(path).ftype] 37: }.sort{|a, b| a[0].to_i <=> b[0].to_i} 38: 39: # メッセージがあればそれを検索 40: entries.each do |name, path, ftype| 41: if /^\./ =~ name then next 42: elsif ftype == "file" && /^\d+$/ =~ name then 43: if ekzameni(path, pattern) then 44: # .mew-cacheから該当するメッセージの行を抜いて印字 45: printline(akiri_resumon(name)) 46: else 47: # nothing.do 48: end 49: end 50: end 51: 52: # サブフォルダは後でまとめて再帰 53: entries.each do |name, path, ftype| 54: if /^\./ =~ name then next 55: elsif ftype == "directory" then 56: print "#{path}: \n" 57: daughter = MewFolder.new("#{@path}/#{name}") 58: daughter.retrieve(pattern) 59: end 60: end 61: end 62: 63: protected # 以下、無関係なクラスには見せない 64: 65: # ファイル内の各行を指定の文型と照合する 66: def ekzameni(path, pattern) 67: open(path, "r") do |f| 68: while(line = f.gets) do 69: if pattern =~ NKF::nkf("-e", line) then 70: return true 71: end 72: end 73: end 74: return false 75: end 76: 77: def printline(resumo) 78: print NKF::nkf("-s", resumo) 79: end 80: 81: # 指定のメッセージに対応するサマリーの行を得る 82: def akiri_resumon(msgno) 83: @summary.key?(msgno) ? @summary[msgno] : 84: "#{msgno}: not found in summary\n" 85: end 86: 87: # サマリー(.mew-cache)の内容を読み込んでおく 88: # rescueはトップレベル(~/Mail)にはないため 89: # @summaryはメッセージ番号をキーとする、サマリー行のハッシュ 90: def fari_resumtabelon 91: begin 92: sumtmp = [] 93: open("#{@path}/.mew-cache") do |f| 94: sumtmp = f.readlines 95: end 96: sumtmp.each do |elem| 97: momo = elem.split(" ", 2) 98: @summary[momo[0]] = elem.sub(/\r.*$/, "") 99: end 100: rescue 101: end 102: end 103: 104: end 105: 106: # コマンドの側 107: 108: if $0 == __FILE__ 109: 110: MewTop = "~/Mail" 111: 112: if(ARGV.size == 0) then 113: print <<-USAGE 114: usage: mewgrep.rb <-a | fildername> [pattern] 115: USAGE 116: exit 0 117: end 118: 119: folder = ARGV.shift 120: if folder == "-a" then 121: folder = MewTop 122: else 123: folder = "#{MewTop}/#{folder}" 124: end 125: 126: pattern = NKF::nkf("-e", ARGV.shift) 127: if pattern == nil then 128: $stderr.print "pattern = " 129: pattern = gets 130: end 131: if /^\s*$/ =~ pattern then 132: $stderr.print "Aborted.\n" 133: exit 0 134: end 135: 136: pattern = Regexp.new(pattern, 0, "euc") 137: 138: folder = MewFolder.new(folder) 139: folder.retrieve(pattern) 140: 141: end # $0 == __FILE__ 142: 143: # end of file
最初のバージョンを書いてちょっと使ってみたが、遅かったのであれ
これチューニングしてみた。早速プロファイラーも使ってみた(^^)
ライブラリをロードするだけでソースには全く手を加えずにプロファ
イルがとれるのは、動的言語のうれしさ、なんだろうか。
測ってみて、遅そうなところを早そうなコードに書き換えてみるが、 有意な差は出なかった。配列の処理などで小細工をしてみても、全体に は大きな影響を与えないようだ。それは当然なので、いちばん時間を食っ ているのがファイルからの読み込みと文型探索だとプロファイラーは報 告している。ここに手を入れなければ大して意味がないのだ。小細工に 血道を上げるよりは、判りやすさ、構造の簡単さを保つ方が引き合うと いう、当たり前の結論に達した。300個のメッセージを検索するのに30 秒かかるのは仕方ないと(笑)
RubyはJISコードによる正規表現は実装していない。作者によれば 「状態を持つコード体系なので原理的に対応できない」のだそうだ。そ うなのかも知れない。だとすると、これは正規表現の致命的な限界を示 唆しているのかも知れない。正規表現自体は数学の裏づけを持つ形式言 語だが、どのような文字コード体系でも実装できるわけではない、とい うことだ。
あるいはそれは別に驚くべきことではないのかも知れない。正規表現 と文字コード体系は互いに独立であり、特に文字コードは正規表現のこ となどこれっぽっちも気にかけない放蕩息子、父帰る、海原雄山だった りする。いくら貞淑な正規表現としても我慢の限界がある。しかし一方、 正規表現の実装だって、もとはといえばASCIIという1バイト(8ビット) も使わない文字コード体系の上で練り上げられてきたものだ。他の世界 を知らずに育った箱入り娘なのだ。「状態を持つ家系の方とは結婚でき ません」と言い切ってしまうのもいかがなものか。
ぼく自身は普段の生活でJISコードをそのまま使うことは殆どないから いいけど、メイルの検索なんていう時にはJISのまま文型探索できた方 が効率がいいに決まっている。いつか、どこかに、放蕩息子と箱入り娘 が折り合いをつける地平が開けることを期待したい。(自分で切り開けっ て? ごもっとも)
(2002.03.14)
(C) ©Copyright Noboru HIWAMATA (nulpleno). All rights reserved.