@m_seki の

I like ruby tooから引っ越し

前準備の前準備の長い長い説明

MapReduce向けの小さなクラス(Hash+Array版、SBBTree版、TokyoCabinet版)のデモのために、小さな検索エンジンのデモを書こうと思ったんだけど、普通のファイルの全文検索じゃなんだよなー、ということでCVSリポジトリの全リビジョンについてインデックスを作ってみようかな、というネタのためにRCSファイルを調べてそれぞれのリビジョンで増えた行、減った行を求めるスクリプトを書いたので、そのサンプルはgrepぽいのにしてみた。akrさんのRuby/CVSがすげーかっこいい。

require 'rcs'

class RCSCrawler
  class Emitter
    def delta=(delta)
      @delta = delta
      @trunk = @delta.rev.on_trunk?
    end
    
    def add(line, &blk)
      @trunk ? add_trunk(line, &blk) : add_branch(line, &blk)
      line
    end
    
    def del(line, &blk)
      @trunk ? del_trunk(line, &blk) : del_branch(line, &blk)
      line
    end

    def add_trunk(line)
      yield(@delta.prevrev, :del, line)
    end

    def add_branch(line)
      yield(@delta.rev, :add, line)
    end

    def del_trunk(line)
      yield(@delta.prevrev, :add, line)
    end

    def del_branch(line)
      yield(@delta.rev, :del, line)
    end

    def initial_text(rev, text)
      text.lines.each do |line|
        yield(rev, :add, line)
      end
    end
  end

  def crawl(rcs, &blk)
    out = Emitter.new
    text = Hash.new
    text[rcs.head] = RCS::Text.new(rcs[rcs.head].text)
    min = rcs.head
    rcs.each_delta do |d|
      next unless d.prevrev
      out.delta = d
      text[d.rev] = text[d.prevrev].patch(d.text,
                                          lambda {|line| out.add(line, &blk)},
                                          lambda {|line| out.del(line, &blk)})
      min = d.rev if d.rev < min
      text.delete(d.prevrev) if rcs[d.prevrev].branches.size == 0
    end
    out.initial_text(min, text[min], &blk)
  end
end

if __FILE__ == $0
  reg = Regexp.new(ARGV.shift)
  rcs = RCS.parse(ARGV.shift)
  RCSCrawler.new.crawl(rcs) do |rev, mode, line|
    next unless reg === line
    p [rev.to_s, mode, line]
  end
end

具は15行くらいのcrawlメソッドで、あとは化粧箱。
branches.sizeが0のときにdeleteしてるあたりが、富豪家になりきれなくて貧乏臭いなあ。
ところで最小のrevisionが1.1以外のことってあるんだろうか。

活発なプロジェクトはcvsを使わないと決まってるそうなので、出番はないスクリプトだなあ。