calコマンドとExerb + Rakefileのサンプル

UNIXでよく使われるカレンダーを表示するプログラムをRubyで実装してみました。かなり前に自分で実装したHaskell版を参考に、できるだけRubyぽさがでるように心掛けてみました。
これだけだとさみしいので、ついでにExerb + Rakefileのサンプルもつけておきます。rakeでコンパイル、rake packageで配布用のアーカイブを作成します。あ、そうそうExerb化したプログラムを圧縮するのにUPXを使っています。不要な人はRakefileの該当行を適当に削ってください。
ファイル: cal.rb

#!/usr/bin/env ruby

require 'date'

class Date
  def first_day_of_month
    Date.new(year, month, 1)
  end

  def last_day_of_month
    Date.new(year, month, ndays_of_month)
  end

  def each_days_of_month(&block)
    first_day_of_month.step(last_day_of_month, 1, &block)
  end

  def ndays_of_month
    case month
    when 2
      leap? ? 29 : 28
    when 4, 6, 9, 11
      30
    else
      31
    end
  end
end

class Calendar
  WEEK_HEADER = "Su Mo Tu We Th Fr Sa"

  def initialize(date)
    @fdate = date.first_day_of_month
  end

  def to_s
    s = ''
    s << @fdate.strftime('%B %Y').center(WEEK_HEADER.size) + "\n"
    s << WEEK_HEADER + "\n"
    s << '   ' * @fdate.wday
    @fdate.each_days_of_month do |date|
      s << sprintf('%2d%s', date.day, newline?(date) ? "\n" : ' ')
    end
    s << "\n" unless s[-1] == ?\n
    s
  end

  private

  def newline?(date)
    (date.day + @fdate.wday) % 7 == 0
  end
end

if $0 == __FILE__
  today = Date.today
  month = ARGV[0] ? ARGV[0] : today.month
  year  = ARGV[1] ? ARGV[1] : today.year

  print Calendar.new(Date.parse("#{year}/#{month}/1"))
end

ファイル: cal.exy

general:
  startup: cal.rb
  core: cui
  kcode: none

file:
  cal.rb:
  rational.rb:
    file: C:/Program Files/ruby-1.8/lib/ruby/1.8/rational.rb
  date/format.rb:
    file: C:/Program Files/ruby-1.8/lib/ruby/1.8/date/format.rb
  date.rb:
    file: C:/Program Files/ruby-1.8/lib/ruby/1.8/date.rb

ファイル: Rakefile

# -*- ruby -*-

require 'rake/clean'
require 'rake/packagetask'

task :default => ['cal.exe']

file 'cal.exe' => ['cal.rb', 'cal.exy'] do
  sh 'exerb.bat cal.exy'
  sh 'upx cal.exe'
end

CLEAN.include('cal.exe')

Rake::PackageTask.new('cal', '0.1a') do |p|
  p.package_dir = './pkg'
  p.package_files.include('Rakefile')
  p.package_files.include('cal.*')
  p.need_zip = true
end

実行例:

>cal.exe
     April 2007
Su Mo Tu We Th Fr Sa
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30

参照: calコマンドを実装してみました - 趣味的にっき