jcalコマンドとして仕上げる

前回からの続き

数年分をリスト出力

  • さらに、数年分を並べてリスト表示するカレンダーも作ってみた。
...中略...
def list_cal(y, col)
  week_ja = %w(日 月 火 水 木 金 土)
  date366 = (Date.new(2004, 1, 1)..Date.new(2004, 12, 31)).to_a
  list366 = Array.new(366, '')
  (y...y + col).each do |y|
    holiday = JPHoliday.new(y)
    date366.each_with_index do |date, i|
      date = Date.new(y, date.month, date.day) rescue nil
      today_marker = (date == Date.today) ? "\e[7m" : '' rescue ''
      holiday_name = holiday.lookup(date).last.ljust_ja(12) rescue ' ' * 12
      case
      when date == nil
        list366[i] += sprintf("\e[ 0m%s%s%s\e[0m",  ' ' * 10,            ' ' * 2, holiday_name)
      when holiday.lookup(date)
        list366[i] += sprintf("\e[31m%s%s%s\e[0;31m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
      when date.wday == 0
        list366[i] += sprintf("\e[31m%s%s%s\e[0;31m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
      when date.wday == 6
        list366[i] += sprintf("\e[36m%s%s%s\e[0;36m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
      else
        list366[i] += sprintf("\e[ 0m%s%s%s\e[0; 0m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
      end
    end
  end
  list366.each {|list| puts list}
end

matrix_cal(2014, 9)
list_cal(2014, 5)

カレンダー出力機能をモジュールにまとめる

  • ここまで二つのカレンダー出力機能をJcalモジュールとしてまとめてみた。
  • refineのString拡張もJcalモジュールに含めたかったが、それはできない。
    • Ruby 2.0では、refineはトップレベルのモジュールとして定義して置く必要がある。
  • モジュール名をJcalExとして、トップレベルのモジュールとしておいた。
...中略...
module JcalEx
  refine String do
    def length_ja
      half_lenght = count(" -~")
      full_length = (length - half_lenght) * 2
      half_lenght + full_length
    end

    def ljust_ja(width, padstr=' ')
      n = [0, width - length_ja].max
      self + padstr * n
    end

    def rjust_ja(width, padstr=' ')
      n = [0, width - length_ja].max
      padstr * n + self
    end

    def center_ja(width, padstr=' ')
      n = [0, width - length_ja].max
      padstr * (n/2) + self
    end
  end
end
using JcalEx

module Jcal
  include JPCalendar

  module_function

  def matrix(y, m)
    start_date = Date.new(y, m) - Date.new(y, m).wday
    end_date   = Date.new(y, m, -1) + (6 - Date.new(y, m, -1).wday)
    date_list = start_date..end_date
    holiday = JPHoliday.new(y)

    puts sprintf("%4d年 %2d月", y, m).center_ja(16 * 7)
    header = %w(日 月 火 水 木 金 土).map {|s| s.rjust_ja(16)}
    header[0] = "\e[31m" + header[0] + "\e[0m"
    header[6] = "\e[36m" + header[6] + "\e[0m"
    print header.join, "\n"

    date_list.each_slice(7) do |week|
      week.each do |date|
        today_marker = date == Date.today ? "\e[7m" : ''
        holiday_name = holiday.lookup(date).last.rjust_ja(14) rescue ' ' * 14
        case
        when date.month != m
          printf "\e[37m%s%s%2d\e[0m", holiday_name, today_marker, date.day
        when holiday.lookup(date)
          printf "\e[31m%s%s%2d\e[0m", holiday_name, today_marker, date.day
        when date.wday == 0
          printf "\e[31m%s%s%2d\e[0m", holiday_name, today_marker, date.day
        when date.wday == 6
          printf "\e[36m%s%s%2d\e[0m", holiday_name, today_marker, date.day
        else
          printf       "%s%s%2d\e[0m", holiday_name, today_marker, date.day
        end
      end
      puts
    end
    puts
  end

  def list(y, col)
    week_ja = %w(日 月 火 水 木 金 土)
    date366 = (Date.new(2004, 1, 1)..Date.new(2004, 12, 31)).to_a
    list366 = Array.new(366, '')
    (y...y + col).each do |y|
      holiday = JPHoliday.new(y)
      date366.each_with_index do |date, i|
        date = Date.new(y, date.month, date.day) rescue nil
        today_marker = (date == Date.today) ? "\e[7m" : '' rescue ''
        holiday_name = holiday.lookup(date).last.ljust_ja(12) rescue ' ' * 12
        case
        when date == nil
          list366[i] += sprintf("\e[ 0m%s%s%s\e[0m",  ' ' * 10,            ' ' * 2, holiday_name)
        when holiday.lookup(date)
          list366[i] += sprintf("\e[31m%s%s%s\e[0;31m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
        when date.wday == 0
          list366[i] += sprintf("\e[31m%s%s%s\e[0;31m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
        when date.wday == 6
          list366[i] += sprintf("\e[36m%s%s%s\e[0;36m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
        else
          list366[i] += sprintf("\e[ 0m%s%s%s\e[0; 0m%s\e[0m",today_marker , date.to_s, week_ja[date.wday], holiday_name)
        end
      end
    end
    list366.each {|list| puts list}
  end
end # module Jcal

Jcal::matrix(2014, 9)
Jcal::list(2014, 5)

オプションと引数を解析してコマンドにする

  • 最後の仕上げに、オプションと引数の解析機能を追加するのだ。
  • オプションと引数の指定から、できる限りユーザーの意図を読み取って、良きに計らい解釈してカレンダー出力するようにしておきたい。
...中略...
require 'optparse'

# オプション解析
options = {}
OptionParser.new do |opt|
  opt.banner = 'Usage: jcal [options] [yyyy|mm] [yyyy|mm]'
  opt.separator('')
  opt.on('-y[NUM]', 'List NUM years.(0-10)', Integer) {|v| options[:years] = v}
  opt.on('-m[NUM]', 'Show NUM months.(0-12)', Integer) {|v| options[:months] = v}
  opt.separator('')
  opt.on('Example:',
         '    jcal                           # Show monthly calendar of this month.',
         '    jcal 8                         # Show monthly calendar of Aug.',
         '    jcal 8 2                       # Show monthly calendar from Aug. to Feb. of next year.',
         '    jcal 2010                      # Show all monthly calendar of 2010.',
         '    jcal -y                        # Show all monthly calendar of this year.',
         '    jcal -y5                       # List from this year to after 5 years.',
         '    jcal 2011 2012                 # List from 2011 to 2012.',
         '    jcal -m                        # Show monthly calendar from last month to next month.',
         '    jcal -m6 2010 1                # Show monthly calendar from Jan.2010 to Jun.2010.',
         )
  begin
    opt.parse!(ARGV)
  rescue => e
    puts e
    exit
  end
end

# ARGVから読み込み
ARGV[0] && (ARGV[0].to_i > 12 ? y1 = ARGV[0].to_i : m1 = ARGV[0].to_i)
ARGV[1] && (ARGV[1].to_i > 12 ? y2 = ARGV[1].to_i : m2 = ARGV[1].to_i)

# '.'を今月に変換
m1 == 0 && m1 = Date.today.month
m2 == 0 && m2 = Date.today.month

# nilの処理
m1 ||= m2
m1 ||= Date.today.month if ARGV.empty? || options.key?(:months)
y1 ||= y2
y1 ||= Date.today.year

# mオプション引数なしの場合は、前月から翌月まで表示する準備
if options.key?(:months) && options[:months].to_i == 0
  m2 = m1 + 1
  m1 -= 1
  m1 <  1 && (m1 = 12 ; y1 -= 1)
  m2 > 12 && (m2 =  1)
end

# mオプション引数ありの場合は、指定した月数分表示する準備
if options.key?(:months) && options[:months].to_i > 0
  m2 = m1 + options[:months].to_i - 1
  m2 > 12 && m2 %= 12
end

# 西暦2つの場合は、リスト表示する準備
if y1 && y2 && (y2 - y1) > 0
  options[:years] ||= y2 - y1 + 1
end

# yオプションが西暦なら、西暦と解釈
# yオプションの最大値は、10
if options[:years].to_i >= 1900
  y1 = options[:years]
  options[:years] = 0
elsif options[:years].to_i > 10
  options[:years] = 10
end

# カレンダー出力
case
when options[:years].to_i > 0
  Jcal::list(y1, options[:years])
when m1 && !m2 && !options.key?(:years)
  Jcal::matrix(y1, m1)
when m1 &&  m2 && !options.key?(:years)
  if m1 <= m2
    (m1..m2).each {|i| Jcal::matrix(y1, i)}
  else
    (m1..12).each {|i| Jcal::matrix(y1, i)}
    ( 1..m2).each {|i| Jcal::matrix(y1 + 1, i)}
  end
else
  (1..12).each {|i| Jcal::matrix(y1, i)}
end

実行例

  • 年・月の指定がない場合は、今年・今月と解釈される。
  • "jcal.rb 年 月"あるいは"jcal.rb 月 年"どちらでもOK。
  • 月の指定として、"."を指定できる。"."=今月の意味。
  • 通常、yオプションには出力する年数を指定する。
    • 但し、y2014とした場合などは、西暦とみなして処理する。
  • yオプションの引数なしは、表形式のカレンダーを1年分出力する。
  • yオプションの引数がある場合は、リスト形式のカレンダーをその年数分出力する。
  • "jcal.rb 2010 2015"は、2010年から2015年までリスト形式のカレンダーを出力する。
  • "jcal.rb 8 2"のような年を跨ぐ期間指定は、来年の2月と解釈する。
  • mオプションには出力する月数を指定する。
  • mオプションの引数なしは、指定月の前後1カ月を含めた3カ月分を出力する。
$ jcal.rb -h
Usage: jcal [options] [yyyy|mm] [yyyy|mm]

    -y[NUM]                          List NUM years.(0-10)
    -m[NUM]                          Show NUM months.(0-12)

Example:
    jcal                           # Show monthly calendar of this month.
    jcal 8                         # Show monthly calendar of Aug.
    jcal 8 2                       # Show monthly calendar from Aug. to Feb. of next year.
    jcal 2010                      # Show all monthly calendar of 2010.
    jcal -y                        # Show all monthly calendar of this year.
    jcal -y5                       # List from this year to after 5 years.
    jcal 2011 2012                 # List from 2011 to 2012.
    jcal -m                        # Show monthly calendar from last month to next month.
    jcal -m6 2010 1                # Show monthly calendar from Jan.2010 to Jun.2010.
$ jcal.rb
  • 今月のカレンダー

$ jcal.rb 8
  • 8月のカレンダー

$ jcal.rb 8 2
  • 8月から来年2月までのカレンダー(7カ月分)

$ jcal.rb 2010
  • 2010年のカレンダー(12カ月分)

$ jcal.rb -y
  • 今年のカレンダー(12カ月分)

$ jcal.rb -y5
  • 今年から5年分のカレンダーをリスト表示

$ jcal.rb 2010 2011
  • 2010年から2011年までのカレンダーをリスト表示

$ jcal.rb -m
  • 今月とその前後1カ月のカレンダー(3カ月分)

$ jcal.rb -m6 2010 1
  • 2010年1月から6カ月分のカレンダー(6カ月分)