Ruby の method_missing 的な tag_missing コールバックを MT に追加してみる
Movable Type Advent Calendar 2013 の8日目です。
Ruby の method_missing
だったり、Perl の AUTOLOAD
的なものが、MT のタグを利用する時に使えると便利かなと思って、TagMissingプラグインなるものを作ってみました。存在しないタグを利用しようとした時に、tag_missing
というコールバックを呼ぶ単純なものです。ダイナミックは未対応です。
ただ、これを実現する方法は、MT の処理をモンキーパッチするしかなさそうだったのと、作ってみたものの、インターフェイスも微妙な感じがしますが、せっかくなので公開します。
TagMissing プラグイン
タグが存在しない時のエラー処理は、MT::Builder::build を見る限り、下記のあたりにあって、
my $hdlr = $ctx->handler_for( $t->tag ); my ( $h, $type, $orig ) = $hdlr->values; my $conditional = defined $type && $type == 2; if ($h) { # ... 省略 ... } else { if ( $t->tag !~ m/^_/ ) { # placeholder tag. just ignore return $build->error( MT->translate( "Unknown tag found: [_1]", $t->tag ) ); } }
MT::Template::Context::handler_for メソッドの結果次第で、分岐の経路が変わる感じなので、MT::Template::Context::handler_for の中にコールバックを仕込むのが確実かなと思ったので、TagMissing プラグインでは、handler_for
をパッチする方針にしました。
モンキーパッチする時は、本体のバージョンアップの影響をなるべく受けないようにした方が良いので、無駄が多い感じですが、handler_for
をコールバック前後で2回呼び出す形にしました。
package TagMissing::Patch; use strict; use warnings; sub init {} use MT::Template::Context; my $orig_handler_for = \&MT::Template::Context::handler_for; { no warnings 'redefine'; *MT::Template::Context::handler_for = sub { my $ctx = shift; my $tag = lc $_[0]; my $hdlr = $orig_handler_for->( $ctx, $tag ); my ( $h ) = $hdlr->values; MT->run_callbacks( 'tag_missing', $ctx, $tag ) unless $h; $orig_handler_for->( $ctx, $tag ) }; } 1;
インターフェイス的にかなり微妙な感じですが、tag_missing コールバックの中で、$ctx->{__handlers}{tag_name_foo}
に MT::Templage::Handler 形式でハンドラを代入するなどして、タグの動作を動的に指定することができます。
AutoUnless プラグイン
そんな感じで、tag_missing コールバックを使ったタグのリファレンス実装として、AutoUnless プラグインというのも作ってみました。
mt:IfWebsite
みたいな、If という単語の含まれたタグがいろいろありますが、mt:UnlessWebsite
みたいな逆の意味を持つタグがあったりなかったりするので、mt:*unless*
がなければ自動的に定義するプラグインです。
tag_missing
が呼ばれた時に、動的に定義する感じの実装で、tag_missing コールバックのサンプルプラグインにもなっています。
MT の t ディレクトリにある、35-tags.t を丸々移して、mt:*If*
を mt:*Unless*
に書き換えた上で、テスト結果を逆にしたテストを書いてみましたが、一部を除いてうまく動いているようです。
https://github.com/taiju/mt-plugin-AutoUnless/blob/master/plugins/AutoUnless/t/tags.t
動的にいくつかのタグを定義するような時には、tag_missing コールバックは便利に使えそうです。
まとめ
あると便利な気はするんですけど、アイデアありきで作ってみたものの、実装は正直微妙な感じになってしまいました。
今年はコードを書くより、読む時間が圧倒的に多い年でした。来年は、書く時間を増やせたらいいなと思います。