optparseを使って、ターミナルで動くカレンダーを作る

optparseってなに?

optparseというのはRubyのライブラリで、 ターミナル側へ入力したオプションを処理してくれるものです。 これに使うことでcalで呼び出せるカレンダーを作ることができます

全体のコードは以下のようになります

# frozen_string_literal: true

require 'date'
require 'optparse'

option = {}
opt = OptionParser.new
opt.on('-m MONTH', Integer) do |month|
  option[:month] = month
end
opt.parse!(ARGV)

if option[:month] && (option[:month] < 1 || option[:month] > 12)
  puts "#{option[:month]} is neither a month number (1..12) nor a name"
  exit
end

year = Date.today.year
month = option[:month] || Date.today.month
first_day = Date.new(year, month, 1)
last_day = Date.new(year, month, -1)

puts "#{year}#{month}".center(20)
puts '月 火 水 木 金 土 日'

print '   ' * ((first_day.wday + 6) % 7)

(first_day..last_day).each do |day|
  day = if day.sunday?
          "#{day.day.to_s.rjust(2)}\n"
        else
          "#{day.day.to_s.rjust(2)} "
        end
  print day
end

詳しく解説します

ライブラリの呼び出し

require 'date'
require 'optparse'

これは、デフォルトではdateとoptparseのライブラリを使えないので、呼び出しています。 dateは日付関係のライブラリです。 optparseは、この記事のメインのテーマのライブラリで、引数でオプションを受け取って、処理するためのものです。

オプションを受けれるようにする

option = {}
opt = OptionParser.new
opt.on('-m MONTH', Integer) do |month|
  option[:month] = month
end
opt.parse!(ARGV)

optionというハッシュを初期化します。

OptionParserのインスタンスを作り、optに入れます。
onメソッドはルールを定義するものであり、parse!をすることによって実行されます。

optのonメソッドを使って-mとその引数を、整数でMONTHとして受取り、
更にそれをmonthとして、ブロックに渡します。
ブロックの中で、monthをさっき作ったoptionというハッシュに、キーは:monthとして入れます。 そうすると、optionの中が{ month: 9 }のようになります
opt.parse!(ARGV)により、
ターミナルで引き受けた["-m", "9"]のような配列を、onで定義した通りに実行します。 これで-mと月を書くことで、月を指定できるようになりました。

1~12月以外が入らないようにする

if option[:month] && (option[:month] < 1 || option[:month] > 12)
  puts "#{option[:month]} is neither a month number (1..12) nor a name"
  exit
end

optionの:monthの値が存在し、かつ1から12以外の場合に、エラーのメッセージを出力し、終了させます

日付を定義する

year = Date.today.year
month = option[:month] || Date.today.month
first_day = Date.new(year, month, 1)
last_day = Date.new(year, month, -1)

1から12の月が入力されたことを想定して、
まず今日の日付から、年を出します。
次に、もし引数として、月を受けた場合はそれを使い、なければ今月をmonthに代入します。
月の初めの日をfirst_dayに入れます。
月の最後の日をlast_dayに入れます。

年と月と曜日の文字を出力する

puts "#{year}#{month}".center(20)
puts '月 火 水 木 金 土 日'

ここから出力する部分を作っていきます。 最初に、中央揃えで、先ほど定義した年と月を出力します。

最初の空白を揃える

print '   ' * ((first_day.wday + 6) % 7)

これは、最初の日の曜日を揃えるために空白を出しています。
今回は、月曜日から始まるので、このように書きます。
wdayは日曜日なら0、月曜日なら1を返します。
(wday + 6)を7でわって余った分だけ、半角スペース3つ与えるときれいに並びます。
最初の日が月曜日の場合、(1 + 6) % 7 = 0なので、きれいに左に並びます。

実際に日付を出力する

(first_day..last_day).each do |day|
  day = if day.sunday?
          "#{day.day.to_s.rjust(2)}\n"
        else
          "#{day.day.to_s.rjust(2)} "
        end
  print day
end

(first_day..last_day)と書くことによって、この範囲を、末尾を含むレンジオブジェクトとします。
eachにブロックを渡し、先程のレンジオブジェクトの中身をdayとして、if文を書きます。

day.day.to_s.rjust(2)というのは、レンジメソッドのアイテムをdayとして、その中の日の値を取って、文字列に変換し、2文字になるようにスペースで埋めます。

カレンダーは月曜日から日曜日まで並ぶので、日曜日のdayの後ろに改行文字をつけて、それ以外の日の後ろに半角スペースを付けます。 dayを再定義します。

そしてprintを使ってdayを一つずつ出力します

まとめ

複数の基礎的な操作を組み合わせることによって、カレンダーを作ることができました。 わからないことあったら、基礎に戻って復習しましょう。 今回のポイントは、OptionParserを初期化して、onメソッドで定義して、parse!メソッドでそれを実行することでした。