こんにちわ。Perl 界の桜井章一と呼ばれている mattn です。キメゼリフは「あんた、コードが煤けてるぜ」です*1。
今回初めて JPerl Advent Calendar に参加しましたが、この Advent Calendar を動かしてるサーバアプリのソースをガチャガチャと触っている、とてもしつけのなっていない大人です。
このサーバアプリで今回私が手を入れたのは、RSS の出力部分ですが今日はどの様に対応したかを説明します。どちらかというと作業ログです。
App::AdventCalendar は kan さんがこの JPerl Advent Calendar の為に書いたサーバアプリで、テキストファイルを食わしてブログ形式に仕上げる代物です。中身は PSGI 形式のリクエスト handler と、Text::Xatena を使った整形、Router::Simple を使ったルーティング、Text::Xslate をテンプレートエンジンに使っています。結構人気のある物を集めた作りだと思います。
非常に分かりやすく作ってあると思うので、ぜひ参考にソースを見ると勉強になるかと思います。ちなみに私が確認しているので Windows 上での動作は保障します。
RSS 対応するにあたり、まずルーティングしている部分を触りました。通常の記事をサーブしているハンドラは以下の部分です。
$router->connect( '/{year:\d{4}}/{name:[a-zA-Z0-9_-]+?}/', { tmpl => 'index.html', act => sub { # snip }, } );
この URL の後ろに rss を付けて RSS を出力する様にしたいので、この部分をコピペして以下を追加。
$router->connect( '/{year:\d{4}}/{name:[a-zA-Z0-9_-]+?}/rss', { content_type => 'application/xml', tmpl => 'index.xml', act => sub { # snip }, } );
各ハンドラに共通的に設定されている content_type を "application/xml" に、tmpl を "index.xml" に変更しました。ハンドラの中身は通常のHTML版と変わりませんが、RSS を配信するには幾らか情報が足りません。link、category、author に設定する内容です。
link は HTML 版のリンクを指せば良いのでカテゴリと日付番号を足して設定。残る category と author については記事を書いた本人が設定すべきものなので、メタ情報を吸い上げる仕組みを別途実装しました。
どんな風に記述するかは、先日書いた記事を見て頂く事とします。
RSS 出力対応を行った際、通常 HTML 版と共通するテキストファイル解析処理を parse_entry というサブルーチンにまとめましたが、そこに処理を追加しました。テキストを読み込んでいる部分を見ると
my $text = $file->slurp( iomode => '<:utf8' ); my ( $title, $body ) = split( "\n\n", $text, 2 );
テキストと本文は最初の空行で区切られるので、上記リンク先で説明している記述方法の場合、タイトル側にメタ情報が含まれれますね。
my ( $tmp, %meta ) = ( '', () ); for ( split /\n/, $title ) { if ($tmp) { my ( $key, $value ) = m{^meta-(\w+):\s*(.+)$}; if ($key) { $meta{$key} = $value; } } else { $tmp = $_; } } $title = $tmp;
こんな感じにタイトル部から「meta-XXX」となっている部分を拾い上げ、タイトルを再設定します。また parse_entry が返すハッシュリファレンスに対して以下の様にメタ情報をマージしました。
return { title => $title, text => $text, update_at => $ftime->strftime( '%c' ), pubdate => $ftime->strftime( '%Y-%m-%dT%H:%M:%S' ), footnotes => $inline->can('footnotes') ? $inline->footnotes : {}, %meta, # ここ };
これで RSS テンプレートで entry.author という変数が参照出来る様になります。
さて最後に、category です。meta-tags というメタ情報から複数の category ノードを作る必要があります。meta-tags で記事の著者に設定してもらったカンマセパレート文字列を配列に乗せ変えます。
メタ情報の吸い上げは汎用的に作ったので簡単ですね。
my @tags = split /,\s*/, $entry->{tags} || ''; $entry->{categories} = \@tags;
あとはテンプレートで entry.categories をループで回してあげればok。
<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>[% name %] - [% conf.title %]</title> <link>[% uri_for(year _ '/' _ name _ '/') %]</link> <description>[% name %] - [% conf.title %]</description> [% FOR entry IN entries %]<item> <title>[% entry.title %]</title> <pubDate>[% entry.pubdate %]</pubDate> <link>[% uri_for(entry.link) %]</link> [% FOR category IN entry.categories %]<category>[% category %]</category> [% END %] <guid isPermaLink="true">[% uri_for(entry.link) %]</guid> <description> [% entry.text | unmark_raw %] </description> <author>[% entry.author %]</author> </item> [% END %] </channel> </rss>
簡単ですね。