今は昔、のtodayとその他もろもろ

まだ perl が 4 だった頃、「today」という単純なスクリプトが出回ったことがあって。

asciiコマンドと同じで、なくても何にも困りはしないのだけれど、未知の Unix ライクな環境に飛び込んだ際には、つい期待して入ってないか確認してしまうものの一つ。

別に何か凝ったことをしてくれるわけではなくて、当月カレンダーで「今日」がハイライトされるだけの CUI。

ね? いらないでしょう? けどね、なんでこういうものが時として欲しくなるか、というと、これは「現在進行形の作業状態」と「あれ、今日って何日?」と思うタイミングが予測不能だということね。

まず「リアル」、つまり仮想世界にいないならば、身の回りのカレンダーを探すでしょう。日めくりカレンダーがあるとありがたいね。街なかなら、最近だと自販機とかトレインチャンネルなんかもいいね。ま、要するに「自分の今の本題作業の邪魔をしないもののなかで一番目線に近いもの」を探すわけだね。

そういった意味で、Windows のタスクバーまでマウスを移動して確認するのは、作業状態によってはほんの少しばかりのストレスになることがあって。また、Windows 7 とかの「ガジェット」は、特に画面のあまり広くないノートPCなんかでは、「常に見える状態に」して作業するのは厳しくて、ウィンドウの状態を壊さずに見るのは割と苦痛になる場合も多い。

ブラウザ上で作業してる場合ならどうするかな? Google カレンダーとかかなぁ。

emacs で作業中なら M-x calendar ね:
emacs-calendar

で。その「まだ perl が 4 だった頃、「today」という単純なスクリプトが出回った」なんだけれど、見つからないんだなぁ…。仕方ないので、python の calendar モジュールを使ってもどきを作った:

 1 #! /bin/env python
 2 # -*- coding: utf-8 -*-
 3 # filename: today
 4 import sys
 5 import calendar
 6 from datetime import datetime
 7 _NOW = datetime.now()
 8 class _TextCalendar(calendar.TextCalendar):
 9     def __init__(self, firstweekday=0):
10         calendar.TextCalendar.__init__(self, firstweekday)
11         if sys.platform == 'win32':
12             self.prmonth = self._prmonth_bogus
13 
14     def formatweek(self, theweek, width):
15         a = []
16         for (d, wd) in theweek:
17             t = self.formatday(d, wd, width)
18             if d == _NOW.day:
19                 t = "\033[7m%s\033[0m" % t
20             a.append(t)
21         return ' '.join(a)
22 
23     def _prmonth_bogus(self, theyear, themonth, w=0, l=0):
24         # for windows with unix-like environment (MSYS, etc.)
25         from subprocess import Popen, PIPE
26         p = Popen(['cat'], stdin=PIPE)
27         p.communicate(self.formatmonth(theyear, themonth, w, l))
28 
29 if __name__ == '__main__':
30     import argparse
31     parser = argparse.ArgumentParser()
32     parser.add_argument('-s', '--first-weekday',
33                         type=int, default=6)
34     parser.add_argument('-w', '--date-column-width',
35                         type=int, default=3)
36     parser.add_argument('-l', '--lines-per-week',
37                         type=int, default=1)
38     args = parser.parse_args(sys.argv[1:])
39 
40     _TextCalendar(args.first_weekday).prmonth(
41         _NOW.year, _NOW.month,
42         args.date_column_width,
43         args.lines_per_week)

ちょっと Windows 版 CPython には問題があって、ANSI VT100 制御シーケンスがうまく扱えないんですが、真面目にやる方法が実は(それ用の OSS がいくつも出回るほど)ヘビィなので、思いっきりサボりました。つまり、MSYS とかの Unix ライクな環境の「cat」を呼び出します。

動かすとこんなです:

ま、誰も興味ないだろうね、こんなの。


2022-01-19追記:
ふと PC のディスクを整理しててこのスクリプトを発見して、そういえば、と思って。

「Windows 版 CPython には問題があって、ANSI VT100 制御シーケンスがうまく扱えない」と書いていた2015年時点でも確か存在は知ってたと記憶してるんだけど、何かの理由で言及を避けてたんだよね。colorama について。使えばこんなだよ:

 1 #! py -3
 2 # -*- coding: utf-8 -*-
 3 import sys
 4 import calendar
 5 from datetime import datetime
 6 _NOW = datetime.now()
 7 class _TextCalendar(calendar.TextCalendar):
 8     def __init__(self, firstweekday=0):
 9         calendar.TextCalendar.__init__(self, firstweekday)
10         if sys.platform == "win32":
11             try:
12                 import colorama
13                 colorama.init()
14             except ImportError as e:
15                 self.prmonth = self._prmonth_bogus
16 
17     def formatweek(self, theweek, width):
18         a = []
19         for (d, wd) in theweek:
20             t = self.formatday(d, wd, width)
21             if d == _NOW.day:
22                 # t = "\033[7m%s\033[0m" % t
23                 t = "\x1b[42m%s\033[0m" % t
24             a.append(t)
25         return ' '.join(a)
26 
27     def _prmonth_bogus(self, theyear, themonth, w=0, l=0):
28         # for windows with unix-like environment (MSYS, etc.)
29         from subprocess import Popen, PIPE
30         p = Popen(['cat'], stdin=PIPE)
31         p.communicate(self.formatmonth(theyear, themonth, w, l))
32 
33 if __name__ == '__main__':
34     import argparse
35     parser = argparse.ArgumentParser()
36     parser.add_argument('-s', '--first-weekday',
37                         type=int, default=6)
38     parser.add_argument('-w', '--date-column-width',
39                         type=int, default=3)
40     parser.add_argument('-l', '--lines-per-week',
41                         type=int, default=1)
42     args = parser.parse_args(sys.argv[1:])
43 
44     _TextCalendar(args.first_weekday).prmonth(
45         _NOW.year, _NOW.month,
46         args.date_column_width,
47         args.lines_per_week)

coloramaをインストールしてあれば、「catで誤魔化」さなくていいてこと。ただし、シーケンスを書き換えていることからわかる通り、colorama がエミュレートするのは ANSI シーケンスのフルセットではないので、最初に使ってたのは意図した装飾(反転)にならない。

2015年に書いた際は何も言わなかったんだけれど少しだけ細かい話を。

別の場所では書いてるんだけれど、正確には「Windows 版 CPython には問題があって、ANSI VT100 制御シーケンスがうまく扱えない」の状態になったのは Python の進化の結果。コンソール出力(sys.stdin/stdout/stderr)にスマートなクラスを使うようになったことが影響してるんだよね。だから、確かねぇ、Python 2.6 だと思うんだよなぁ、そこいらあたりの古い Python は、生のシーケンスをコンソールにそのまま吐き出せて、そして Windows の「コンソール(いわゆるDOS窓)」は ANSI シーケンスを理解するので、Unix と全く同じふるまいをしていた、わけね。

で、「そいつぁいか~ん」と思って作ったのだっ、かどうかは知らないけれど、とにかく colorama がこれに対する措置が出来るもの、ということになった。けれども、「フルセット」じゃないのがねぇ、ちょっといやらしくて。まぁあるだけ随分助けられてはいるんだけどね。