さて、前回の続き、今回もPerlによる超手抜き家計簿の実装法を紹介する。
前回は、
2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイート列を、
日付 時刻 店舗 品目 金額 残高
2012-03-12 13:00 ? - 100円 3850円
2012-03-12 12:00 ココイチ カレー 900円 3950円
2012-03-12 10:00 ドトール コーヒー 150円 4850円
2012-03-12 09:00 残高設定 - 5000円 5000円
という家計簿に組み立てる仕様を検討した。
今回はこの仕様を実装する。
前回は、
2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイート列を、
日付 時刻 店舗 品目 金額 残高
2012-03-12 13:00 ? - 100円 3850円
2012-03-12 12:00 ココイチ カレー 900円 3950円
2012-03-12 10:00 ドトール コーヒー 150円 4850円
2012-03-12 09:00 残高設定 - 5000円 5000円
という家計簿に組み立てる仕様を検討した。
今回はこの仕様を実装する。
ではここで、プログラムをドバーンと公開してみる。
前回との違いは
・ツイートを読み込んで家計簿形式に組み立てている
・美しい表組みにしている
・メールで送り返している
というところである。
Encodeは、日本語コードの変換に使うモジュールだ。
Mail::Mailerはメールを送信するのに使うモジュールである。
DateTime::Format::Strptimeは日付の計算に使う。
今回はTwitterの標準であるUTC(協定世界時)と日本時間の変換、および「2日前からのツイートを取得」という計算に使う。
HTML::TemplateはHTMLをPerlで簡単に生成するのに使う。
特に表組などの繰り返し要素を使うには必携だ。
過去1日分のツイートについて$statusesというリファレンスに取得している。
これは配列リファレンスで、各要素には1ツイートごとのハッシュリファレンスが入っている。
特に難しいことを言っていないのだが、わけが分からない方は拙著「すぐわかるオブジェクト指向Perl」をお読みください。
(ステマかよ!)
配列は空リスト()で初期化する。
foreachとも書けるが通っぽくはforと書く。
ハッシュキーがtextの$status->{text}にはツイートしたテキストが入っているが、今回はスペース区切りで日付、金額、店舗、品目を与えることになっているので、スペースでsplitする。
splitした結果を配列@twに入れる。
各配列要素は変数を省略すると例のアレ$_に入る。
例のアレが分からない人は拙著「すぐわかるPerl」を読めばいいのではないだろうか、
(ああステマ・・・)
ここからは@twの各要素について、ある条件を満たしていればある明細が決まる、という処理である。
よって$dateを埋める。
入力は時:分で入れて、秒は00固定とする。
もしすでに店舗が決まっていれば、文字列は品目になるので$itemに入れる。
これは店舗であるので、$shopに入れる。
それは家計簿の明細ではないとみなして、次のツイートまで飛ばしている。
これを、日本時間の日付、時刻に変換するサブルーチンtzChangeを読んで、戻り値を$nowdate、$nowtimeに入れている。
なうの日付、時刻である。
ちょっと下のほうに移ってサブルーチンの中身を研究する。
引数(ツイートのcreated_at要素、つまり現在時刻)を変数$utcに入れてある。
これは日付を解釈するテンプレートのようなものだ。
次に、このテンプレートにしたがって$utcを変換して$dtに入れる。
$dtは日付オブジェクトである。
メインルーチンに戻る。
これで日付、時刻を省略可能にする。
ほとんど買い物した直後に起票するので、省略値を与えたのである。
いま、配列@outには、
2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイートを受けて、
2012-03-12 09:00 残高設定 - 5000
2012-03-12 12:00 ココイチ カレー 900
2012-03-12 10:00 ドトール コーヒー 150
2012-03-12 13:00 ? - 100
というハッシュ配列が入っているはずだ。
すみませんここで一週間休んでいいですか。



前回との違いは
・ツイートを読み込んで家計簿形式に組み立てている
・美しい表組みにしている
・メールで送り返している
というところである。
#!/usr/bin/perl -wでは部分ごとに順を追って紹介する。
# mailHTML -- ツイッター家計簿アルファ版
use strict;
use Encode qw(from_to encode);
use Mail::Mailer;
use lib '/home/????????/local/lib';
use lib '/home/????????/local/lib/perl5';
use lib '/home/????????/local/lib/perl5/site_perl';
use Net::Twitter;
use utf8;
use DateTime::Format::Strptime;
use HTML::Template;
my $handle = Net::Twitter->new({
traits => [qw/OAuth API::REST API::Search/],
consumer_key => "????????",
consumer_secret => "????????",
access_token => "????????",
access_token_secret => "????????",
});
my $dt = DateTime->now( time_zone => 'Asia/Tokyo' );
$dt->subtract( days => 1 );
my $screen_name = '????????'; # アカウントのscreen name
my $statuses =
$handle->user_timeline({ id => $screen_name, count => 200, since => $dt });
my @out = ();
for my $status ( @$statuses ) {
my ($date, $time, $shop, $item, $cash);
my @tw = split / /, $status->{text};
for (@tw) {
if (/(\d\d\d\d-\d\d-\d\d)/) {
$date = $1;
} elsif (/(\d\d:\d\d)/) {
$time = $1.':00';
} elsif (/(\d+)円$/) {
$cash = $1;
} elsif (defined $shop) {
$item = $_;
} else {
$shop = $_;
}
}
next unless defined $cash;
my ($nowdate, $nowtime) = &tzChange($status->{created_at});
$date = $nowdate unless defined $date;
$time = $nowtime unless defined $time;
$item = "-" unless defined $item;
$shop = "?" unless defined $shop;
push @out, { date=> $date, time => $time, shop => $shop, item => $item, cash => $cash };
}
my $zan;
my @sort;
for my $out (sort {$a->{date}.$a->{time} cmp $b->{date}.$b->{time}} @out) {
if ($out->{shop} eq '残高設定') {
$zan = $out->{cash};
} elsif (defined $zan) {
$zan -= $out->{cash};
} else {
next;
}
$out->{hand} = $zan;
push @sort, $out;
}
@sort = reverse @sort;
my $tmpl_file = "kakeibo.tmpl";
my $tmpl;
open (my $template, "<:utf8", $tmpl_file );
$tmpl = HTML::Template->new(filehandle => *$template);
close $template;
$tmpl->param( { Table => \@sort } );
my $from = 'from@example.com';
my $to = '?????@example.com';
my $subject = "家計簿";
my $mailer = Mail::Mailer->new("sendmail");
$mailer->open({ From => $from,
To => $to,
Subject => $subject,
'MIME-Version' => '1.0',
'Content-Type' => "text/html; charset=UTF-8",
'Content-Transfer-Encoding' => '8bit',
}) or die "Can't open $!\n";
print $mailer $tmpl->output;
$mailer->close();
sub tzChange ($) {
my $utc = shift;
#Sat Feb 11 03:54:53 +0000 2012
my $strp = DateTime::Format::Strptime->new(
pattern => '%a %b %d %H:%M:%S %z %Y'
);
my $dt = $strp->parse_datetime($utc);
$dt->set_time_zone('Asia/Tokyo');
return ($dt->strftime("%Y-%m-%d"), $dt->strftime("%H:%M:%S"));
}
use strict;まず必要なモジュールをuseする。
use Encode qw(from_to encode);
use Mail::Mailer;
use lib '/home/????????/local/lib';
use lib '/home/????????/local/lib/perl5';
use lib '/home/????????/local/lib/perl5/site_perl';
use Net::Twitter;
use utf8;
use DateTime::Format::Strptime;
use HTML::Template;
Encodeは、日本語コードの変換に使うモジュールだ。
Mail::Mailerはメールを送信するのに使うモジュールである。
DateTime::Format::Strptimeは日付の計算に使う。
今回はTwitterの標準であるUTC(協定世界時)と日本時間の変換、および「2日前からのツイートを取得」という計算に使う。
HTML::TemplateはHTMLをPerlで簡単に生成するのに使う。
特に表組などの繰り返し要素を使うには必携だ。
my $handle = Net::Twitter->new({ここまではすでに紹介した。
traits => [qw/OAuth API::REST API::Search/],
consumer_key => "????????",
consumer_secret => "????????",
access_token => "????????",
access_token_secret => "????????",
});
my $dt = DateTime->now( time_zone => 'Asia/Tokyo' );
$dt->subtract( days => 1 );
my $screen_name = '????????'; # アカウントのscreen name
my $statuses =
$handle->user_timeline({ id => $screen_name, count => 200, since => $dt });
過去1日分のツイートについて$statusesというリファレンスに取得している。
これは配列リファレンスで、各要素には1ツイートごとのハッシュリファレンスが入っている。
特に難しいことを言っていないのだが、わけが分からない方は拙著「すぐわかるオブジェクト指向Perl」をお読みください。
(ステマかよ!)
my @out = ();@outは各ツイートごとによって変化する家計の状況を入れる配列である。
配列は空リスト()で初期化する。
for my $status ( @$statuses ) {これは、$statusesが指し示す無名配列をデリファレンスした配列@@$statusesの各要素を、$statusに入れて周回するループである。
foreachとも書けるが通っぽくはforと書く。
my ($date, $time, $shop, $item, $cash);ここで各明細の日付、時刻、店舗、品目、使用金額を入れる変数を作る。
my @tw = split / /, $status->{text};さて、$statusは各ツイートの状態を表す無名ハッシュリファレンスであるが、デリファレンスして各要素を取り出す。
ハッシュキーがtextの$status->{text}にはツイートしたテキストが入っているが、今回はスペース区切りで日付、金額、店舗、品目を与えることになっているので、スペースでsplitする。
splitした結果を配列@twに入れる。
for (@tw) {また配列@twに関してfor(foreach)する。
各配列要素は変数を省略すると例のアレ$_に入る。
例のアレが分からない人は拙著「すぐわかるPerl」を読めばいいのではないだろうか、
(ああステマ・・・)
ここからは@twの各要素について、ある条件を満たしていればある明細が決まる、という処理である。
if (/(\d\d\d\d-\d\d-\d\d)/) {要素が2012-03-19のように、数字4桁-2桁-2桁になっていれば日付と考えられる。
$date = $1;
よって$dateを埋める。
} elsif (/(\d\d:\d\d)/) {同様に数字2桁:2桁にあんっていれば時刻と考えられるので$dateを埋める。
$time = $1.':00';
入力は時:分で入れて、秒は00固定とする。
} elsif (/(\d+)円$/) {「数字+円」の形式の文字列であったら、それを$cashに入れている。
$cash = $1;
} elsif (defined $shop) {日付、時刻、金額以外の文字列は店舗か品目になる。
$item = $_;
もしすでに店舗が決まっていれば、文字列は品目になるので$itemに入れる。
} else {ここに到達するのは、何らかの文字列が$_にあるが、日付でも、時刻でも、金額でもなく、変数$shopがまだ決まっていない場合である。
$shop = $_;
これは店舗であるので、$shopに入れる。
}ここで1ツイートの解釈が終わった。
}
next unless defined $cash;「円」という文字列が入っていないツイートは金額が決まらない。
それは家計簿の明細ではないとみなして、次のツイートまで飛ばしている。
my ($nowdate, $nowtime) = &tzChange($status->{created_at});さて、$status->{created_at}というハッシュ要素には、ツイートされた時刻が入っている。
これを、日本時間の日付、時刻に変換するサブルーチンtzChangeを読んで、戻り値を$nowdate、$nowtimeに入れている。
なうの日付、時刻である。
ちょっと下のほうに移ってサブルーチンの中身を研究する。
sub tzChange ($$) {サブルーチンtzChangeはスカラー変数1個を受ける。
my $utc = shift;
引数(ツイートのcreated_at要素、つまり現在時刻)を変数$utcに入れてある。
#Sat Feb 11 03:54:53 +0000 2012まずDateTime::Format::Strptimeオブジェクトを作って$strpに入れる。
my $strp = DateTime::Format::Strptime->new(
pattern => '%a %b %d %H:%M:%S %z %Y'
);
my $dt = $strp->parse_datetime($utc);
これは日付を解釈するテンプレートのようなものだ。
次に、このテンプレートにしたがって$utcを変換して$dtに入れる。
$dtは日付オブジェクトである。
$dt->set_time_zone('Asia/Tokyo');これで$dtのタイムゾーンを日本にする。
return ($dt->strftime("%Y-%m-%d"), $dt->strftime("%H:%M:%S"));最後は日付を「YYYY-MM-DD」形式、時刻を「HH:MM:SS」形式にしてサブルーチンを抜ける。
メインルーチンに戻る。
$date = $nowdate unless defined $date;これは、もし日付$dateや時刻$timeから手動で与えられていなければ(それらの変数はundefであるので)ツイートの日付、時刻に合わせるということだ。
$time = $nowtime unless defined $time;
これで日付、時刻を省略可能にする。
ほとんど買い物した直後に起票するので、省略値を与えたのである。
$item = "-" unless defined $item;同様に、品目$itemが決まっていなければ「-」を、店舗$shopが決まっていなければ「?」をセットする。
$shop = "?" unless defined $shop;
push @out, { date=> $date, time => $time, shop => $shop, item => $item, cash => $cash };ここで@outという配列には、日付、時刻、店舗、品目、金額を入れた無名ハッシュを配列要素として追加する。
}これで全ツイートの処理が終わった。
いま、配列@outには、
2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイートを受けて、
2012-03-12 09:00 残高設定 - 5000
2012-03-12 12:00 ココイチ カレー 900
2012-03-12 10:00 ドトール コーヒー 150
2012-03-12 13:00 ? - 100
というハッシュ配列が入っているはずだ。
すみませんここで一週間休んでいいですか。



