-
-
Save yoshikouki/a44477f32a84a45f158e3802e80d88df to your computer and use it in GitHub Desktop.
# frozen_string_literal: true | |
require 'optparse' | |
require 'date' | |
require 'curses' | |
# 表示する年月を取得 | |
# 戻り値 { :year, :month } | |
def get_date | |
# コマンド引数を Hash で取得 | |
params = ARGV.getopts('y:m:c') | |
# cオプションを保存 | |
@puts_center = params['c'] | |
# 引数の有無により、年月を初期設定 | |
{ year: params['y'].nil? ? Date.today.year : params['y'].to_i, | |
month: params['m'].nil? ? Date.today.month : params['m'].to_i } | |
end | |
# 引数の年月から日付配列を作成 | |
# 戻り値 [[Date, Date, ...], [Date, Date, ...], ...] | |
def create_month_days(date) | |
year = date[:year] | |
month = date[:month] | |
# 月末の Date オブジェクトを取得 | |
end_of_month = Date.new(year, month, -1).day | |
# 当該月の日数配列を作成する | |
month_days = [] | |
week_days = [] | |
(1..end_of_month).map do |day| | |
date = Date.new(year, month, day) | |
week_days << date | |
if date.saturday? || end_of_month == day | |
month_days << week_days.dup | |
week_days.clear | |
end | |
end | |
month_days | |
end | |
# 引数をカレンダーに整形後、表示するstrを配列に入れて返す | |
# 戻り値 [String, String, ...] | |
def generate_calender(date, month_days) | |
year = date[:year] | |
month = date[:month] | |
# 表示文字列の配列を作成 | |
str_array = ["#{month}月 #{year}".center(CALENDER_WIDTH), | |
'日 月 火 水 木 金 土'] | |
# 日付テーブルを配列で作成(各行はString) | |
days_table = month_days.map do |week_days| | |
week_row = '' | |
week_days.each { |date| week_row += sprintf('%2d', date.day) + ' ' } | |
week_row | |
end | |
# 日付テーブルの第一週にインデントを追加 | |
days_table[0].insert(0, ' ' * month_days[0][0].wday) unless month_days[0][0].wday == 0 | |
str_array.push *days_table | |
end | |
def using_curses(calender) | |
cu = Curses | |
# 画面初期化 | |
cu.init_screen | |
# 表示位置を設定 | |
position_y = (Curses.lines - calender.size) / 2 | |
position_x = (Curses.cols - CALENDER_WIDTH) / 2 | |
begin | |
# 表示文字列を処理 | |
calender.each do |str| | |
cu.setpos position_y, position_x | |
cu.addstr str | |
position_y += 1 | |
end | |
# 画面を更新 | |
cu.refresh | |
# 処理を一時停止 | |
cu.getch | |
ensure | |
# 画面削除 | |
cu.close_screen | |
end | |
end | |
def using_kernel(calender) | |
calender.each { |str| puts str } | |
end | |
def puts_calender(calender) | |
@puts_center ? using_curses(calender) : using_kernel(calender) | |
end | |
CALENDER_WIDTH = 20 | |
# 処理実行 | |
date = get_date | |
month_days = create_month_days(date) | |
calender = generate_calender(date, month_days) | |
puts_calender(calender) |
udzura 19:54
String#centerを覚えた!ということで、せっかくなので
## 今のターミナルの幅に対して中央寄せのカレンダーを出す
機能をオプションで実装してみましょう。
「オプションで実装」を完全に見落としていたので、その分を追加で実装しました
-c
オプション付与で、中央表示します
まずはいいね!と思ったところから:
- コメントに戻り値の情報がある
- アフター型時代のRubyとしてもいいね+1
- モジュール化の意識がある
- mainな処理の記述がコンパクトで、無理な結合がないように見える
date = get_date
month_days = create_month_days(date)
calender = generate_calender(date, month_days)
puts_calender(calender)
- 出力に関してストラテジ層を切り出せている
- 「puts利用のクラス」と「curses利用のクラス」を横に切り出せている
ご自身がどれくらい気づかれているかはわからないんですが、設計が得意なタイプかと思います。
ベースとして人や物に対する観察力、洞察力があるのかなあと思いました(ただの人間観察です)。
ここはもっと直せそう!と思った点です
- トップレベルでインスタンス変数は使わないほうがいいかも
- どこで見えるのかスコープがわかりにくいので
Enumerable#each
と#map
の混同がある- 以下の箇所は素直には
each
を使うべきです - 作りたいものが「週ごとに区切った、月単位の日付の二重配列」ならば、
map
やそのほかのEnumerableのメソッドを適切に利用するともっと短く記述できます
- 以下の箇所は素直には
(1..end_of_month).map do |day|
date = Date.new(year, month, day)
week_days << date
if date.saturday? || end_of_month == day
month_days << week_days.dup
week_days.clear
end
end
- 定数宣言より先に利用している箇所がある(
CALENDER_WIDTH
)- Rubyのメソッドの場合結果的に呼び出し時に探すのでエラーにはならないんですが、コードが読みにくくなりますよね
- トップレベルメソッドにふさわしくない名前がある
using_kernel
などがトップレベルにdefされているのは混乱しそうです。- 小さなスクリプトなのでいいのですが、出力のためのクラスを導入すると良さそう
全体としては、この規模になってくるときちんとオブジェクト指向設計を行い、やることに対してのクラスを切り出してあげるといいのかなと思いました。
クラスの切り出し方は「はじめよう!要件定義」などを参考にできるのかな?
フィヨルドのSlackの方に張った僕の「lsのOO設計」のGistも参考になるかも...
で、先ほどのコメントの通りよしこーさんは「設計が得意な人」になれるポテンシャルがありそうなので、オブジェクト指向を自分のものにできるよういろいろ勉強してみてください!
あ、あと、ほとんどるりまぐらいしか情報がない Curses
をちゃんと使えているのは素直に素晴らしいです。
あああもう一点あって、よしコーさんがコメントされているような内容は、YARDというRubyのコメントフォーマットで記述したほうが何かといいかもしれません。
https://rubydoc.info/gems/yard/file/docs/GettingStarted.md
コアライブラリだとRDocの場合もあるかも。
https://docs.ruby-lang.org/ja/latest/library/rdoc.html
まずはYARD覚えてみてください。こんな感じになるかな。
# 引数をカレンダー向けに整形後、表示するstrを配列に入れて返す
#
# @param date [Hash] 日付情報のハッシュ。 :year :month のキー必須
# @return [Array<String>] 行ごとの表示文字列
def create_month_days(date)
...
end
とても詳細なところまでありがとうございます
適性までお伝えいただけて凄く参考になります
今はまだ可能性があるくらいのものではありますが、オブジェクト指向、意識してみたいと思います!
前職でFile Makerを使っているときにDB設計が面白くて本を購入して読んだ(達人に学ぶDB設計徹底指南書)ことがあるのですが、
もしかしてそういうのも関係しているのでしょうか・・・
フィヨルド研修のカリキュラムにこの本が入っていたので感動しました
いただいた内容を精査し、コード修正します!
YARDやGistも(Gistはlsコマンドを修了してから)覚えたいと思います
お忙しいところ本当にありがとうございました!!!
ああ、なるほどですね! > DB設計が面白くて
達人に学ぶDB設計徹底指南書は良書だと思います。私は楽々ERDレッスン(そっちも参考書にあるかな?)と応用情報処理でDB設計の修行をしました。
楽々ERDレッスンの著者の人が書かれているのが「はじめよう!要件定義」で、この本もペパボでは新卒研修で使っています。
設計が面白いというのはアプリケーションエンジニアの適性がありそうです!
研修まだまだ大変ですが、楽しんでいきましょう〜
(作業メモから抜粋)