todoと連動したカレンダー作例

# 背景
~/org/gtd.org というスケジュールテキストに書いた日時が把握しにくかった。
そして1週間の単位が月火水木金土日というのに馴染んでおり、一般のカレンダーの
日曜日スタートに違和感があった。
また、 cal -3 のような複数のカレンダーが横に表示されていくのにも不満があった。
30や31日の月末ごとに意識が分断されていた。30や31の隣に1が表示されてほしかった。
好みに合わない数が多いので作り直すことにした。

# 概要
gtd.orgと連動した好みに合うように再開発されたカレンダーは以下の特徴を持つ。
~/org/gtd.org を読み込み TODO,Deadline のある日をハイライトする。
それらの日のtodoは右に簡易表示させる。
そのうち、今日行うべきことがあれば当日専用のハイライトで強調する。
月曜日スタートで始まる。
月末を終えても改ページ等々は無し。日付は連続する。
表示する月を増やせる。
祝日など任意の日を自由にハイライトさせられる。

# コード説明
org_todo_finder.pl はgtd.orgを簡単に読むためのスクリプトだ。
それは何ヶ月分なのか、見出しだけなのか、本文も必要なのか、について
TODO、Deadlineのあるものを日付順にソートして返してくる。
ソートされるのが便利なので、独立させた。
cal サブルーチンで使う @dye_way が今回のハイライト実装の中心を担っている。
土日だからシアンや赤に染めることと、todoが被ってアンダーバーを引くなど
複数のハイライト修飾ができる限り両立する為には、
1日1日を何年何月の何日であるのか認識してコードを書くのが
理路整然としていて保守がしやすい。
整列も printf や format には頼れないが、
padding_1t_10_day を書くことで矛盾無く整列させられる。
# 使用例
![cal_up.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2781693/488e12ab-735c-5d56-8b16-1b7add68d170.png)

# コード
```perl:
#!/usr/bin/env perl
use strict;
use warnings;
require Colorlizer;
sub say {print @_, "\n"};
sub dye {Colorlizer::dye(@_)};
use constant ONE_DAY => 60*60*24;
use Time::Piece;

# private
my $extra_ref = [ 
                    [ 20220824, qw(orange) ],
                    [ 20220825, qw(orange) ],
                    [ 20220826, qw(orange) ],
                    [ 20220829, qw(orange) ],
                    [ 20220830, qw(orange) ],
                    [ 20220831, qw(orange) ],
                    [ 20220901, qw(orange) ],
                    [ 20220902, qw(orange) ],
                    [ 20220919, qw(cyan) ], # 敬老の日
                    [ 20220923, qw(cyan) ], # 秋分の日
                    [ 20230101, qw(blink) ], # happy new year
                ];

# import schedule from org_todo file
my @todo ;
my @dates;
my $numbers_of_month = shift // 2;
{
    for (qx{ ~/bin/org_todo_finder.pl td$numbers_of_month }) {
        chomp;
        /^[*]+ \s+ (.*)$/x        ? push @todo , s/^[*]+ \s+ TODO \s+//xr:
        /(\d{4})-(\d{2})-(\d{2})/ ? push @dates, "$1$2$3"                :
        die "I can't understand $_ as neither date or todo.\n"                  ;
    }

}

my %dates_map = map {$_ => 1} @dates;

sub HEADER () {say " Mo Tu We Th Fr Sa Su"};
my $now = localtime;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;
++$mon and $year += 1900;

sub struct_Y4M2D2 {
    my $self = shift;
    sprintf "%4s%02s%02s" , $self->year, $self->mon, $self->mday
};
my $now_Y4M2D2 = struct_Y4M2D2($now);
sub calHeaderYYYYMM_nameMonth {
    my $d= $now + ONE_DAY * (1 - $mday);
    sprintf "%-4s %02s %13s" , $d->year, $d->mon, $d->fullmonth;
}

sub cal {
    my $self = shift;
    my $wd= $self + ONE_DAY * (1 - $mday);
    for my $d (1 .. $self->month_last_day) {

        padding_1st_day($wd);

        my $Y4M2D2 = sprintf "%4s%02s%02s"
            , $wd->year, $wd->mon, $d;

        my @dye_way = qw(itaric);
        # today
        if ($Y4M2D2 == $now_Y4M2D2) {
            push @dye_way, qw(back_white black);
        }
        # org_todo
        if ($dates_map{$Y4M2D2}) {
            push @dye_way, qw(underline);
        }

        for my $d_color_ref (@$extra_ref) {
            my($d, @cs);
            ($d, @cs) = @$d_color_ref;
            if ($Y4M2D2 == $d) {
                push @dye_way, @cs;
            }
        }

        if ($wd->wdayname eq "Sat") {
            push @dye_way, qw(cyan);
        }
        if ($wd->wdayname eq "Sun") {
            push @dye_way, qw(red);
        }

        print q( ) . dye( padding_lt_10_day($d), @dye_way);
        foldingSun($wd);
        $wd += ONE_DAY;
    }
}
sub padding_lt_10_day {
    my $day = shift;
    $day < 10 ? q( ) . $day : $day ;
}

sub padding_1st_day {
    my $self = shift;
    my %pad_map = (
        Mon => 0,
        Tue => 3,
        Wed => 6,
        Thu => 9,
        Fri => 12,
        Sat => 15,
        Sun => 18,
    );
    if ($self->mon == $mon and $self->mday == 1) {
        print q( ) x $pad_map{$self->wdayname};
    }
}

sub foldingSun {
    my $self = shift;
    if ($self->wdayname eq "Sun") {
        my ($t,$d);
        if ($t = shift @todo) {
            $d = shift @dates;
            $d = ($now_Y4M2D2 == $d) ? dye($d , qw(back_white black)) : $d;
            print "\t$d $t" =~ s/\d{4}//r;
        }
        print "\n";
    }
}

sub main {
    say calHeaderYYYYMM_nameMonth();
    HEADER;
    for my $n (1 .. $numbers_of_month) {
        cal($now->add_months($n -1));
    }
}

main();
```