Newer
Older
Ruby / last / j2405_last.rb
#!usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'io/console' ; require 'csv' ; require 'date' ; require 'timeout'

#プログラム説明
#テーマ・目的:これは、「簡単にスケジュールやTo doを立てたい!!」という人のために作ったプログラムです。
#きっかけ    :家族が、たくさんある予定を一括で見られないと言っているのを聞いたから。また自分の使っているスケジュールアプリの操作があまりにも難解だったから。
#目標        :スケジュールの設定・カレンダー・時計など、スケジューリングに必要なものを一元化する。そして操作も簡単にする!
#結果・展望  :↑に挙げた3つは概ね完成できた。操作もほぼ矢印キーとEnterキーで完結するようにした。今度は時計などをもっと見やすくしたり、カレンダーを祝日対応にしたり、予定があるかカレンダーに表示できるようにしたい。
#他のネタ    :個人発表のMy Ruby Guide
#              暇をつぶす、もしくは頭を鍛えるゲーム(マインスイーパーなど)
#              メモ帳

Funcs = [:Schedules, :Calendar, :Clock, :End]
Schedules = CSV.read('j2405_last.csv', headers: true)
Items = ["予定", "日程", "場所", "メモ"]
LOT = 7  # lines of text in "Schedules"
Modes = ["追加", "編集", "表示", "完了", "前", "次", "<戻る"]
Frame = "+------------\e[1B\e[13D|%02d          \e[1B\e[13D|            \e[1B\e[13D|            "  # calendar's frame
Today = Date.today; status, num, mode, page, year, mon = :MENU, nil, 0, 0, Today.year, Today.month; $, = "\n"

class Object
  def plus(num1, num2); num2 ? (self + num1) % num2 : self + num1 end
end

Arrows = {'A'=>-1, 'D'=>-2, 'B'=>1, 'C'=>2}
def select(arrow = false, apart = false, num = 0, max = nil)
  loop do
    case STDIN.getch
    when "\e"
      if arrow
        STDIN.getch; char = STDIN.getch; val = apart ? Arrows[char] : Arrows[char] <=> 0 
        return $input = num.plus(val, max)
      end
    when "\r", " "
      return $input = nil 
    end
  end
end

def deco(text, bool = true, a: 7, b: 4); "\e[#{bool ? a : b}m#{text}\e[m" end

def lineup(arr, var, sep = $,, st = 0, fin = nil); arr[st..fin].map.with_index(st % arr.length) { |item, i| deco(arr[i], var == i) }.join(sep) end  # arr を並べあげる。

def disp(item, mode, text, arrow = " -> ", str = $/); text ||= ""; print nil, nil, " \e[1m#{item}\e[m", text; print arrow if mode == 1; if mode < 2; gets(str).chomp("") end end

begin
  until status == :End
    system "clear" ; system "tput civis" ; puts "\e[0;1m#{status}\e[m"
    num ||= 0; lines = `tput lines`.chomp.to_i - LOT
    case status
    when :MENU
      puts "リマインダへようこそ!", nil, "機能を選択・決定してください。(矢印キー・Enter/Return)", lineup(Funcs.map { |sym| "  #{sym}" }, num); select(true, false, num, Funcs.length)
      status = Funcs[num] unless $input; num = $input
    when :Schedules
      if lines > 0
        pages_all = (Schedules.length.to_r / lines).ceil
        puts "#{deco(Modes[-1], mode == Modes.length-1)}", nil, "  予定を #{lineup(Modes, mode, " ", 0, 3)} する(←→)  ページ #{page + 1}/#{pages_all} #{lineup(Modes, mode, "|", Modes.length-3, Modes.length-2)}", "  予定を選択(↑↓)", nil, Schedules[lines*page...lines*(page+1)].map.with_index { |row, i| "    \e[1;97m#{deco(row['schedule'], (1..3) === mode && num == i)}  \e[38;5;250m-#{row['date']}・#{row['place']}\e[m" }
        case select(true, true)
        when -1, 1
          num = num.plus($input, Schedules.length)
        when -2, 2
          mode = mode.plus($input/2, Modes.length)
        else
          case mode
          when 0..2
            system "clear" ; system "tput cnorm" if mode < 2 ; data, arr = Schedules[page*lines + num], []
            3.times { |i| arr[i] = disp(Items[i], mode, (data[i] if mode > 0)) }; arr[3] = disp("メモ(入力時は2連続の改行で終了)", mode, (data['memo'] if mode > 0), "\n -> \n", "\n\n"); select if mode == 2
            Schedules.delete(page*lines + num) if mode == 1; Schedules << arr if mode < 2
          when 3
            Schedules.delete(page*lines + num); num -= 1 if num > 0; page -= 1 if page > pages_all
          when 4
            page = page.plus(-1, pages_all)
          when 5
            page = page.plus(1, pages_all)
          else
            status, num, mode = :MENU, nil, 0
          end
        end
      else
        puts "\e[7m<戻る\e[m", nil, "  Terminalの大きさが小さすぎます。", "  画面サイズを大きくしてください。"; select; status = :MENU
      end
    when :Calendar
      case mon
      when nil..0
        year -= 1; mon = 12
      when 13..nil
        year += 1; mon = 1
      end
      calendar = Array.new(6) { Array.new(7) }.map.with_index { |week, nth| week.map.with_index(1) { |day, wday| day = nth*7 + wday - Date.new(year, mon, 1).wday; day > 0 && Date.valid_date?(year, mon, day) ? day : nil } }
      puts "\e[4m<戻る(Enter)\e[m", nil, "  \e[4m#{year}年 #{mon}月 のカレンダー (矢印キーで移動)\e[m",
           calendar.map.with_index { |week, nth| week.map.with_index { |day, wday| sprintf("\e[#{nth*4 + 6};#{wday*13 + 3}H\e[1;107;#{case wday when 0; 31 when 6; 34 else; 30 end}m#{Frame}\e[m", day ? day : 0).gsub("00", "--") } }
      select(true) ? mon += $input : status = :MENU
    when :Clock
      time = Time.now
      Timeout.timeout(1 - time.nsec.to_r/10**9) do
        puts "\e[4;7m<戻る(Enter)\e[m", nil, sprintf("  \e[1;7m#{time.year}/#{time.month}/#{time.day}   現在時刻 %02d : %02d\e[0;7;100m : %02d\e[m", time.hour, time.min, time.sec); select; status = :MENU
      end
    else
      puts "\e[7m<戻る(Enter)\e[m", nil, "  問題が発生しました。"; select; status = :MENU
    end
  end
rescue Timeout::Error
  retry
ensure
  system "clear" ; system "tput cnorm" ; puts "\e[mシステムを終了します。", "またのご利用をお待ちしております。(*˘_˘*)"
  CSV.open('j2405_last.csv', 'w') { |file| Schedules.to_a.each { |row| file << row } }
end