吉岡弘隆さんの「Rubyで習作」を添削

吉岡弘隆さんのパッケージ・リストのディストリビュート間の比較表作成 ruby スクリプトを私の流儀に直してみました。
http://blog.miraclelinux.com/yume/2007/02/ruby_b40b.html
まず、パッケージ・リスト・ファイルを全行読み込んでハッシュにしているということは、富豪的に書いてもいいのだろうということで、メモリー使用効率を気にしない書き方にしました。
試みに、うんざりするほどのパッケージ数を持つ debian の stable パッケージ http://packages.debian.org/stable/ のリストのサイズをチェックしてみると、ほぼ 1 メガバイトです。

$ curl http://packages.debian.org/stable/allpackages.en.txt.gz | gunzip | wc
  17498  145367 1243105

最近のマシンでパッケージ比較表を作る限りでは、メモリー効率を考える必要はなさそうです。なので、これでもかまわないでしょう。
(2月7日追記) ヘッダとセパレータなし、コンマ区切りで表示する版です。

#!/usr/env ruby

asianux, *dists = ARGV.collect do |filename|
  begin
    IO.readlines(filename).inject({}) {|h, s| h[s.strip] = 1; h }
  rescue
    puts "cannot read file #{filename}."
    exit 2
  end    
end

asianux.keys.sort.each do |pkg|
  a = [pkg] + dists.collect {|d|
    if d.include?(pkg) then 'include' else 'not-include' end
  }
  puts a.join(', ')
end

さらに、表の表示フォーマットを、出題元の Asianux チームさんのエントリの表示例に合わせてみました。
http://blog.miraclelinux.com/asianpen/2007/02/ctrlz_bfd3.html
(2月7日追記) レイアウト乱れ修正です。慣習に合わせて、エラー時に 2 をプロセスがリターンするように変更しました。

#!/usr/env ruby

# パッケージ有無の表示文字列
Include = 'include'
NotInclude = 'not-include'
Separator = ' | '

class <<(DistPkgs = Struct.new("DistPkgs", :name, :pkgs))
  def readpkgs(filename)
    self[
      filename.sub(/(?:_pkg)?_list\.txt\z/, ''),
      IO.readlines(filename).inject({}) {|h, s| h[s.strip] = 1; h },
    ]
  rescue
    puts "cannot read file #{filename}."
    exit 2
  end
end

def max_length(*a)
  a.flatten.collect {|s| s.length }.max
end

def make_format(asianux, dists)
  a = [max_length(asianux.name, asianux.pkgs.keys)]
  a += dists.collect {|d| max_length(d.name, Include, NotInclude) }
  a.collect {|n| "%-#{n}s" }.join(Separator)
end

asianux, *dists = ARGV.collect {|filename| DistPkgs.readpkgs(filename) }
format = make_format(asianux, dists)
puts format % ([asianux.name] + dists.collect {|d| d.name })
asianux.pkgs.keys.sort.each do |pkg|
  puts format % ([pkg] + dists.collect {|d|
    if d.pkgs.include?(pkg) then Include else NotInclude end
  })
end

当初掲載していた以下のコードは、ディストリビューション名(dist_Aなど)が 'not-include' よりも長いときレイアウトが乱れます。上の修正版に差し替えます。

#!/usr/env ruby

Include = 'include'
NotInclude = 'not-' + Include

class <<(DistPkgs = Struct.new("DistPkgs", :name, :pkgs))
  # 省略、上に同じ
end

asianux, *dists = ARGV.collect {|filename| DistPkgs.readlist(filename) }

longest_name = ([asianux.name] + asianux.pkgs.keys).keys.max {|a, b| a.length <=> b.length }
longer_label = [Include, NotInclude].max {|a, b| a.length <=> b.length }
a = [longest_name.length] + [longer_label.length] * dists.size
format = a.collect {|d| "%-#{d}s" }.join(" | ")

puts format % ([asianux.name] + dists.collect {|d| d.name })
asianux.pkgs.keys.sort.each do |pkg|
  puts format % ([pkg] + dists.collect {|d|
    if d.pkgs.include?(pkg) then Include else NotInclude end
  })
end