ゆるふわに過去のライフイベントからの経過時間を管理するyattaコマンド作った

どうも私は予定の把握が苦手だ。
病院なんかで「二週間後を目安に」とか言われても、えっと何時にしようとか考えてしまって決められない。
まめな人ならその場で手帳取り出して、その場で日付決めて予約をいれるところなんだろうけど、前日やその日の気分で予定決める人間にはなかなかハードだ。

日付がぴったり決まっているならカレンダーに入れればいいし、別にどうということも無いのだけど、日付の決まらないゆるふわな日程がどうも苦手だ。
そんなわけで二週間後ぐらいに、あれ、そういえばそろそろだっけな、と予定を入れることになる。

二週間ならまだ覚えていられる。
歯医者なんかは半年か一年に一回ぐらい検診受けて下さいね、とか言われる。
半年とかもはや記憶の外だ。
まめな人なら日記を読み返せば分かるだろうし、Twitter中毒の人なら自分のツイートを見返せばいい。
でもどうで私のことだ、「廃車行って来た」とか検索性のけの字もでてこないツイートをするに違いない。
というか通院なんてわざわざツイートしない。

そんなわけで前に「やった」ライフイベントからだいたいどれくらいたったのか、を管理するツール「yatta」を作ってみた。

https://github.com/takei-yuya/yatta

# yatta list

yattaコマンドは三つのサブコマンドが有り、ひとつめは `list` だ

```console
$ ./yatta list
2week	皮膚科	2週間後目安
1week	美容院
3day	HUB
```

引数なしでも `list` になる。
`list` は過去にやってからどれくらい経ったかを表示する。
過去になんどかやったことは一つにまとめられて最新のからどれくらい経ったかを表示する。
三つ目のカラムは追加のメモだ。いれなくてもいい、というかさっき説明用に今入れた。
日付の引き算とかしたくないので day とか week とか month で表示するようになっている。
ちなみに複数形にするのはサボった。sなくても分かるし。

# yatta history

過去にやったことを全部表示するのは `hisotry` コマンドだ。
長いコマンドを打ちたくないので `h` でも `history` になるようになっている。

```console
$ ./yatta history
2016/11/25 (Fri)	HUB
2016/11/28 (Mon)	皮膚科	2週間後目安
2016/11/30 (Wed)	HUB
2016/12/03 (Sat)	HUB
2016/12/04 (Sun)	美容院
2016/12/05 (Mon)	HUB
2016/12/10 (Sat)	HUB
```

こっちはいつやったかを知りたいので日付表示になっている。

# yatta add

なにかをやったら `add` だ。
対話式にやったことを入力できる。
たとえば今朝皮膚科に行って来たので入力してみる。

```console
$ ./yatta add
What:
1: 皮膚科
2: 美容院
3: HUB
or input another choise: 1
When:
1: 2016/12/10 (Sat) # (3days ago)
2: 2016/12/11 (Sun) # (2days ago)
3: 2016/12/12 (Mon) # (yesterday)
4: 2016/12/13 (Tue) # (today)
or input another choise: 4
Memo:
2016/12/13 (Tue)	皮膚科
Append this entry? (y/N): y
Finished
```

Whatは何をやったかだ。今回は「皮膚科」を選ぶ。
日本語入力とかたるいし、あと同じ予定をまとめるためにtypoすると悲しいので、前にやったことあることは数字で入力できるようにしてある。
初めてやることは数字以外で入力すればいい。
Whenは何時やったのか。
これまた入力がめんどくさいので数字で選択だ。
そしてどうせ私のこと。やった当日に入力できるワケがない。
ということで3日前ぐらいまでは数字で入力できる。
こういう手抜きは普段使うであろうツールは結構大事。
そして最後にMemo。
これは自由記述でメモをいれる。いれなくてもいい。
というかいれてない。
typoとか入力間違えがなければ最後に `y` を入力して終わり。

```console
$ ./yatta
1week	美容院
3day	HUB
0day	皮膚科
```

無事「皮膚科」の日付が更新された。



カレンダー使えばいいじゃないか、と思うかもだけど、カレンダーって結構あれからどれくらい、が面倒くさい。
あと終わったこと、を入力するモチベーションって低い。
思いつきでやったこととか、きっちり日付が決まっていなくて前日に決めたこととか、そういうのってわざわざ入れなかったりする。
というか、その他日付がきっちり決まっているイベントなんかで埋まってしまって、思い出す、というのには不向きなように思う。
ということでなにかざっくりライフイベント管理できないかな、ということで作ってみた。
意外に便利、そう。かも。

# 実装とかの話

別にとても凄いことをやっているワケではないのでわざわざ説明するほどのことでもないのだけど一応。

ソースはここ: https://github.com/takei-yuya/yatta

見ての通りのbashスクリプトで書かれている。
コマンドライン引数のパースとかかなり適当だ。
そのうち `add` に対話的ではなくてコマンドの引数だけで登録するオプションとか欲しそうな気はしているのだけど、今の使い方的に使わなさそうなので実装していない。
全体的にTODOの嵐。
最初にlib.shの読み込みをしているけど説明は後回し。

```bash
HISTORY_FILE="${HOME}/.yatta"
```
やったことの保存先は `~/.yatta` 中身はなんてことのないただのTSVで、UNIXTIME、やったこと、メモが順に並んでいるだけ。

```console
$ head -n2 ~/.yatta
1480086000	HUB
1480345200	皮膚科	2週間目安
```
ちなみにUNIXTIMEだけど全部00:00:00。日付以上の粒度を管理する必要がないので。
TSVなのはcoreutilsと相性がいいので。基本的にコマンドの中心は `sort(1)` だ。

```bash
sort -k1,1 -n -r | sort -k2,2 -u | sort -k1,1 -n
```
List関数の中の三連sortは、 1) 新しい順にソート(1カラムで数値降順ソート)、 2) やったことでuniq(2カラムめでuniq)、 3) 旧い順にソート(1カラム目で数値昇順ソート) をやっている。
1があるのは、ファイルがソートされている保証がないため。追記型のファイルはソート保証が面倒くさいので読む側でソートしている。
3に関しては、別にしなくてもいいのだけど一番古い=長時間やっていないことを先頭の選択肢にしたかった、のだけどよくよく考えてあまり意味の無いソートだし無くてもいいかなぁ、とこれを書いてて思い始めた。

lib.shのほうにはなんとなく汎用に使えそうかな、という関数をまとめている。

## lib: choise

まずはchoise。
選択肢を表示して、選んだ選択肢か入力された値を変数にいれる関数。

```console
$ choise ANS foo bar baz
1: foo
2: bar
3: baz
or input another choise: 2
$ echo $ANS
bar
$ choise ANS hoge fuga
1: hoge
2: fuga
or input another choise: hogera
$ echo $ANS
hogera
```
自由記述を許す/許さないとかオプションにするとよさそうだけど、今回の実装で需要なかったのでスルー。

bashスクリプト的な話だと、こういう変数に値を格納する、という時には `eval` などを使わずに `printf -v VAR` を使おう、というのがメモ。
evalに比べて安全だしフォーマットとかも使えて便利。
フォーマットを使わない場合でも `%s` を使っておくこと。そうでないと `%` を含む入力の時に困る。

## lib: confirm

つづいてconfirm。yesかnoかだ。
yesなら終了ステータスが0、noか無入力なら1になる。
それ以外の入力のときは聞き返す。

```console
$ confirm "OK?"
OK? (y/N): y
$ echo $?
0
$ confirm "OK?"
OK? (y/N): p
Unknown answer: p
OK? (y/N): n
$ echo $?
1
```

まぁ、スクリプト書くと三回に一回はこんなのを書いている気がするのでlib.shにいれてみた。
lib.shはそのうち汎用に使えるようにしたいな、と思いつつ `confirm` はデフォルト no で固定だ。
これまたオプションでいじれるとよさそう。 (あと^Cで抜けたときの仕様とか)

## lib: relative_time

simple_timeはただの `date(1)` のラップなのでスルー。
relative_timeは現在時刻からの相対時刻を人間が読みやすそうな表示に変える。
引数は UNIX TIME。

```console
$ relative_time $(( `date +%s` - 5*24*60*60 ))
5day
$ relative_time $(( `date +%s` - 7*24*60*60 ))
1week
$ relative_time $(( `date +%s` - 21*24*60*60 ))
3week
$ relative_time $(( `date +%s` - 40*24*60*60 ))
1month
```

それっぽいのは出るようになっているし、実装としても結構かっちょいいことが出来た気がする。可読性はともかく。

```bash
# Usage: relative_time UNIXTIME
# NOTE: a little bit sloppy
relative_time() {
  local -i at="${1}"
  local -i now="$(date +%s)"
  local -i diff=$((now - at))
  if [ ${diff} -le 0 ]; then
     # TODO:
     echo "0day"
    return
  fi
  diff=$((diff + 252460800))  # @252460800 = 1978/1/1 is first Sunday New Year's Day (to calc week number)
  local year month week day hour minute second
  eval $(TZ=GMT date +"year=%-Y month=%-m week=%-U day=%-d hour=%-H minute=%-M second=%-S" -d"@${diff}")
  year=$((year - 1978))
  month=$((month - 1))
  week=$((week - 1))
  day=$((day - 1))
  case "${year}/${month}/${week}/${day}/${hour}/${minute}/${second}" in
# TODO: select minimal unit
#    0/0/0/0/0/0/*) echo "${second}sec" ;;
#    0/0/0/0/0/*) echo "${minute}min" ;;
#    0/0/0/0/*) echo "${hour}hour" ;;
    0/0/0/*) echo "${day}day" ;;
    0/0/*) echo "${week}week" ;;
    0/*) echo "${month}month" ;;
    *) echo "${year}year" ;;
  esac
}
```
途中に出てくる `252460800` はコメントにあるとおり、UNIXTIMEの起点 `1970/1/1` 以降で、初めて1月1日が日曜日になる日を意味している。
ここに経過秒数を足して `date(1)` に投げると、経過秒数が `%Y` は+1978した経過年数、 `%m` が+1した経過月数、 `%U` が経過週数(%U自体は日曜起点の正月からの週数の意)、といった具合になる。という寸法。
ただ、経過月数に関しては月の長さ依存で少しばかし不正確。
指定時刻と現在時刻の日を比べて云々、とかやらなきゃいけないんだろうけど、正直そんな厳密性は求めていないし、実装も複雑になっちゃうし、なにより既に現時点で遅いのでこれ以上なにもしたくない、という感じ。
コメントアウトしている部分を適当にごにょると最小単位が時分秒になったりします。

# TODO

- 遅い。特に相対日付表示。
- 補完。bash補完できるなら対話いらない。……たぶんこのTODOは消化されない
- 未来の予定との連携。何時までにやる、みたいなTODO管理コマンドと多少かみ合いそうな部分もあるので