さて、前回の続き、今回も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円

という家計簿に組み立てる仕様を実装した。

今回はこの仕様を実装する。

またプログラムをドバーンと公開する。
内容としては前回と一緒である。
#!/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"));
}
さて、続きからまた説明する。
@sort = reverse @sort;
現在配列@sortには明細が時間昇順で(過去から未来に向かって)入っているので、これを反転させて、一番最近の明細を上に持ってくる。
実際に家計簿をパッと見たとき、一番上に最近のレコードが集まっている方が明らかに使いやすいからだ。
(もっともその上のpushをunshiftにすればいいような気もする)
my $tmpl_file = "kakeibo.tmpl";

my $tmpl;
open (my $template, "<:utf8", $tmpl_file );
$tmpl = HTML::Template->new(filehandle => *$template);
close $template;
さて、ここが問題である。
ここではHTML::Templateというモジュールを使って、HTMLを生成している。

HTMLは、特に一覧画面のような表組みの画面がそうだが、形式が決まっていて、明細だけが変わるものが多い。
今作っている家計簿がその典型である。

で、このようなときに使うと便利なのがHTML::Templateモジュールである。
これは、プログラムとテンプレートファイルをセットで使う。
ここでは、kakeibo.tmplという外部ファイルにテンプレートを出している。
このファイルの中身は以下のようである。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=980,initial-scale=1.0,user-scalable=yes,maximum-scale=3.0" />
<title>Booky!</title>
</head>
<body>
<table BORDER>
<tr>
<th>日付</th><th>時刻</th><th>店舗</th><th>品目</th><th>金額</th><th>残高</th>
</tr>
<TMPL_LOOP NAME=Table>
<tr>
<td><TMPL_VAR NAME=Date></td>
<td><TMPL_VAR NAME=Time></td>
<td><TMPL_VAR NAME=Shop></td>
<td><TMPL_VAR NAME=Item></td>
<td><TMPL_VAR NAME=Cash></td>
<td><TMPL_VAR NAME=Hand></td>
</tr>
</TMPL_LOOP>
</table>

</body>
</html>
内容は、ほとんどHTMLだが、TMPL_LOOPと、TMPL_VARの2つのタグが見せ場である。

で、このファイルを読み込む。
$tmpl = HTML::Template->new(filehandle => *$template);
newHTML::Templateオブジェクトのコンストラクタであり、引数にファイルハンドルへの型ブログを渡す。
オブジェクトへのリファレンスを返すので、それを$tmplというスカラーで受ける。
これでもうファイルへは用なしだから速攻クローズしている。
$tmpl->param( { Table => \@sort } );
ここで、$tmplオブジェクトのparamメソッドを呼び出して、テンプレートの中の表組に値をセットしている。
ここで@sortへのリファレンスを代入している。
今、プログラム中で、@sortの各要素はdate、time、shop、item、cash、handというキー名の無名ハッシュリファレンスになっている。
このハッシュキー名が、テンプレートファイルのTMPL_VARタグのNAMEアトリビュートの名前と紐づいていて、各テーブル要素に変数の中身が展開されるようになっている。

今、家計簿の明細がこうなっているとすると、

 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円

ハッシュ構造としてはこうなっている。
@sort = (
 { date => "2012-03-12", time => "13:00", shop => "?", item => "-", cash => "100", hand => "3850" },
 { date => "2012-03-12", time => "12:00", shop => "ココイチ", item => "カレー", cash => "800", hand => "3950" },
・・・略・・・
);
で、これを$tmpl->paramメソッドで渡すと、以下のようなHTMLに展開される。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=980,initial-scale=1.0,user-scalable=yes,maximum-scale=3.0" />
<title>Booky!</title>
</head>
<body>
<table BORDER>
<tr>
<th>日付</th><th>時刻</th><th>店舗</th><th>品目</th><th>金額</th><th>残高</th>
</tr>
<tr>
<td><2012-03-12></td>
<td><13:00></td>
<td><?></td>
<td><-></td>
<td><100></td>
<td><3850></td>
</tr>
<tr>
<td><2012-03-12></td>
<td><12:00></td>
<td><ココイチ></td>
<td><カレー></td>
<td><900></td>
<td><3950></td>
</tr>
・・・略・・・
</table>

</body>
</html>
詳しい説明はHTML::Templateのperldocに譲るとして、このように動的なHTMLを短いプログラムで作れる雰囲気だけ感じて欲しい。
(スミマセン)。

次回はこのHTMLをメールで送るとする。
(つづく。次回最終回予定)
Subscribe with livedoor Reader
Add to Google
RSS
このエントリーをはてなブックマークに追加