Catalyst::Plugin::TagCloud

CatalystのTagCloudのプラグイン欲しいナァってことで、Catalyst::Plugin::Markdownをまねして作ってみた。Catalyst::Plugin::Markdownのソースがさらりとしているので、この部分とアノ部分いじればOKね、、、みたいのはわかるんだけど、何故動くのかさっぱしわからん。

結局、コードを書くよりも出来たコードが何故動くのかを理解する(したふり?)にほとんどの時間を使ってしまった。

  • Class::Data::Inheritableって何ヨ?
  • NEXT.pmさっぱしわかんね。

ほとんど上の二つのお勉強だ。

プラグイン自体はCatalyst::Plugin::TagCloudという名前で、

package Catalyst::Plugin::TagCloud;

use strict;
use warnings;
use base 'Class::Data::Inheritable';
use HTML::TagCloud;

our $VERSION = '0.01';

__PACKAGE__->mk_classdata('tagcloud');
__PACKAGE__->tagcloud( HTML::TagCloud->new );

1;

とかやっておいて、コントローラーに

use Catalyst qw/-Debug ConfigLoader Static::Simple TagCloud/;

とプラグインを追加しておけば、$c->tagcloudはHTML::TagCloudのインスタンスなので

sub default : Private {
    my ( $self, $c ) = @_;
    $c->tagcloud->add("tag1","url1",10);
    $c->tagcloud->add("tag2","url2",15);
    $c->tagcloud->add("tag3","url3",50);
    my $html = $c->tagcloud->html_and_css(10);
    # Hello TagCloud
    $c->response->body( $html );
}

と書けばめでたくタグクラウドが出力されている。

で、Class::Data::Inheritableって何ヨ?ってことだがココみてなんとなく納得した気になったり。でも、ちゃんとわかってないのかも、、、、なんで常にインスタンスが返ってくるのか実はよくわからん。 HTML::TagCloud->newってなんどもnewされないのは何故だろうか。

sub setup {
    my $c = shift;

$c->config->{markdown}->{empty_element_suffix} ||= '';
    $c->config->{markdown}->{tab_width} ||= '';

return $c->NEXT::setup(@_);
};

1;

で、Catalyst::Plugin::Markdownにはさらにsetupっていうメソッドがあって、NEXT.pmが使われている。これはココのメルマガアーカイブでかなり納得したんだけれども、$c->config->{markdown}->{}がどうしてmy $html = $c->markdown->markdown($text);に関わってくるのかさっぱりなままだ。

結局今回のプラグインだとnewするときにlevelsの設定が出来ないので常にデフォルトの24であんま使えん。

my $cloud = HTML::TagCloud->new(levels=>10);
みたいなのを$c->config->{tagcloud}->{levels}みたいなのをnewするときに指定してやりたいが、なんかいまいち。というより、そもそもClass::Data::Inheritable継承する必要がないような気がしてきた。

もうちょい、他のプラグイン読んだりしてちゃんと理解しないとあかんっぽい。勢いとまねまねで初めて書いたプラグインはやはりダメダメだったヨ。

CatalystでMarkdown & flavour

面白そうなCatalystのプラグインを見つけたので後で試す。

Catalystでmarkdown記法で書かれたテキストをHTMLに変換するプラグイン。このblosxomではMarkdown使いまくりなのでCatalystで作った何かに、今までのエントリ移すときとか楽そう。あと、Kwiki::Markdownってのもある。普段PukiWiki使ってるんだけど記法が混在すると混乱するからKwikiに変更しようかな。WYSIWYGなエディタもあるので、便利そうだし、タグもつけられるし。

Catalystでblosxomのフレーバーなことができるプラグイン。htmlとかrssとかURLの最後の.xxxでテンプレートを切り替えられるのでCatalystでその手の機能が欲しい場合には。というか、そういう場面って結構あるはず。

Catalystのアクションの優先度(2)

Catalystのアクションの優先度でまたもはまったのでメモ。

最上位のコントローラー(要するにcatalyst.plでつくられる)にRegexでアクションを定義すると、script/xxxx_create.pl controller test とかでlib/Controllerの下に作成したtest.pmのデフォルトよりも優先されてしまう。

例えば

lib/Test.pm

sub Regex : Regex('^(¥w+)$'){ my ( $self, $c ) = @_; # Hello World $c->response->body($c->req->snippets->[0]); }

lib/Test/Controller/priv.pm

sub default : Private { my ( $self, $c ) = @_; $c->response->body( "Test::Controller::priv" ); }

とかやっておいて、/privにアクセスするとRegexが実行されてしまう。

色々悩んだ挙句、

sub priv : Path('/priv'){ my ( $self, $c ) = @_; $c->response->body( "Test::Controller::priv" ); }

みたいに、Pathでルートからの位置を指定して優先度をあげることで解決。せっかくControllerで別にしてるんだから、

コントローラー内のデフォルトはコントローラー内ではデフォルトの動作をしてほしいナァ

Regexに持ってかれちゃったりすると気持ち悪いなぁと思ったり。

あと試行錯誤してて気づいたんだけど最上位のコントローラーでLocalRegexを指定すると何にもマッチしないようなんだけどこの認識であってるのかな?

Class::DBI::Pager

Catalystでページ処理をしたいということで、Class::DBI::Pagerを使ってみたヨ。

my $pager = CD->pager(20, 1); # ($items_per_page, $current_page)

という感じで書いとけば、あとはよきにはからってくれる。

新しい順に取り出したいときなんかはORDER BY入れたいが、そんなときはこんな感じで。

sub default : Private {
 my ( $self, $c ) = @_;
 $c->stash->{page} = $c->request->param('page');
 my $pager = Publicious::Model::CDBI::Items->pager(
 $c->config->{items_per_page},$c->stash->{page} ||1);

$c->stash->{it} = $pager->retrieve_from_sql(
                      qq( 1 ORDER BY id DESC));

$c->forward('display');
}

search_likeとかもいける。あとでソースをきちんと読んでみよう。

DateTimeでフォーマット変換

DateTime->todayとかDateTime->nowを知った。今までシコシコ書いてたヨ。しかもDateTime::Format::MySQLでmysqlのDatetime型に変換してくれるので快適

use DateTime; use DateTime::Format::MySQL; my $today = DateTime->now; print DateTime::Format::MySQL->format_datetime($today);

Catalystをmod_perlで使う

Catalystで幾つか書いたし、そろそろ組み込みテストサーバじゃなくてmod_perl触ってみたい!という欲求が。

バイオインフォとかケモインフォ関連でmod_perlで動かさなきゃならんプログラムなんてまずないので、初mod_perlですヨ。以前から気にはなっていたので、RPMなんかは入れていつでも試せる状況にしていたのだけど、、、FC1のmod_perl-1.99はうまく動作しなかった。というわけで、ソースからコンパイルして入れなおした。ちなみに職場のFC2のmod_perlも動かなかったので同様に処理した。

mod_perlのサイトから自分のApacheに対応した最新版(mod_perl-2.0-current.tar.gz)をダウンロード。

tar xvfz してperl Makefile.PLするとapxsの場所をたずねてくるので

/usr/sbin/apxs

あとはmake,make test,makeinstallでOK

で、早速Catalystのコードをmod_perlで動かしてみたんだけど、

リロードするたびに、notfoundとCatalystの画面が交互に出るよ

なぜ?Apacheの設定が悪いのかナァ。とりあえず、動かすのやめて終了。あとでApacheの設定を見直しする。

それから、Practical mod_perlの全文がpdfで公開されているので、読んできちんと理解しよう。

R4にCatalystとPlaggerをインストール

R4のColinuxにCatalystPlaggerをインストールしようとしたら、Test::Treeあたりで、Weak referencesのエラーが出るので、perlのバージョンあげることにした。

といってもdebなパッケージの作り方良くわからんので、素直にソースからインストール。perl5.8.8のインストール完了。

で、改めてPerlモジュール突っ込み開始(4時間ほど)。途中Expatで躓いたが、libexpat-devでもlibexpat-develでもなく

apt-get install libexpat1-dev

だった。紛らわしい。

PerlMolもこれで動くようになったはず(確認してないけど)。Chemネタ的にはChemrubyも1.0.0がリリースされていましたね。こっちも、暇をみつけていじり倒さねば。

Emacs(Meadow)のTemplate Toolkit modeは便利だ

Clouder::Blogger: html-tt - emacsのTemplate Toolkit用のmode

自作のemacsでPerlモジュールのTemplate Toolkitの文法を使うためのモードです。これはマイナーモードになっていて、html-helper-modeと一緒に使うようになっています。こちらからダウンロードできます。

Meadowで使うにはファイルを解凍後、html-tt.elをSite-lispフォルダに放り込む。その後.emacsの設定。僕は拡張子にttをつけることが多いので、ttもこのモードで開くようにちょっと追加してます。

;;tt
(autoload 'html-helper-mode "html-helper-mode" "Yay HTML" t)
(setq auto-mode-alist
      (cons
       '("\\.html$" . html-helper-mode) auto-mode-alist))
(setq auto-mode-alist
      (cons
       '("\\.tt$" . html-helper-mode) auto-mode-alist))
(require 'html-tt)
(add-hook 'html-helper-mode-hook 'html-tt-load-hook)

== キーバインディング一覧 ==

  • C-cs
  • [% %]が挿入される。
  • C-cd
  • [% etc %]が挿入される。一番良く使うかも
  • C-ch
  • [% INCLUDE "" %]
  • C-ci
  • [% IF %][% END %] これも良く使う
  • C-cl
  • [% ELSIF %]
  • C-ce
  • [% ELSE %]
  • C-cf
  • [% FOREACH %][% END %]
  • C-cw
  • [% WHILE %][% END %]
  • C-cm
  • [% SWITCH %][% CASE %][% END %]

すごく便利で手放せなくなったゾ。

CDBIでORDER BYとかLIMITとか

CDBI使っていると、ORDER BYとかLIMITとか副問い合わせなんかで、ついつい安易なset_sqlに走りがちになったりしませんか?

僕はなるヨ。

コレじゃいかんというわけで、今週を出来るだけset_sqlを使わないゾ週間に認定した。

で、ORDER BYに関しては、search のあたりに書いてあるようにorder_byというキーのハッシュを加えてやればいいらしい。

じゃぁ、DESCはどうすればいいんじゃと思ったら、後ろにつければいいみたい

{order_by => 'id DESC'}

みたいに。

ん、もしや文字列連結か?LIMITもそのまま後ろにつければOKなんではなかろうか?

OKだった。

$c->stash->{itemlist} = Publicious::Model::CDBI::Items->search_like( tags => '%test%', uid =>$c->stash->{uname}, {order_by => 'id DESC LIMIT 10'});

こんなんでバチッと動きました。

でも、こんなんやるなら素直にsql書いたほうがいいのかな?

$c->stash->{itemlist} = Publicious::Model::CDBI::Items->retrieve_from_sql(        qq( 1 ORDER BY id DESC LIMIT 10) );

set_sqlは使ってナイゾと。

Catalystのアクションの優先度

del.icio.ushttp://del.icio.us/ユーザー名でそのユーザー名のブックマークを表示できるようになっているが、その他にpostとかtagとかユーザー名以外の情報も存在してます。

そんなわけで、ユーザー名(pathの第一引数?)みて処理を分ける必要がある。

CatalystはPath,Regex.TopLevelというアクションがサポートされていて、

普通PathのほうがRegexより優先度高いだろという認識でコード書いたら、うまく動かなかった、というか職場で使っていたCatalystはRegexのほうが優先度が高かったので、結局Regexのサブルーチンの中でユーザー名が"tag"とか"post"だったらそういう処理にforwardするみたいな条件分岐を書いたのだ。

なんかif文ぐりぐり地獄になっちゃった。

納得いかないので、家のCatalyst(5.65)で次のコードを書いて確認してみた。

sub default : Private { # Private
    my ( $self, $c ) = @_;
    $c->response->body( $c->welcome_message );
}
sub tag : Path('tag') { # Path
    my ( $self, $c ) = @_;
    $c->response->body( "Taggggg" );
}
sub pop_path : Path('popular') { # Path
    my ( $self, $c ) = @_;
    $c->response->body( "Popular" );
}
sub popular : Global {# Top Level
    my ( $self, $c ) = @_; 
    $c->response->body( "!top level!" );
}
sub user : Regex('^(\w+)$') { # Regex
    my ( $self, $c ) = @_; 
    my $user = $c->req->snippets->[0];
    $c->response->body( "Tag list: $user" );
}

結果は

TopLevel > Path > Regex > Default

と納得のいく優先度が得られたヨ。職場のCatalystのバージョン古かったせいなのか、書き方に問題あったのかナァ。来週確かめよう。