読者です 読者をやめる 読者になる 読者になる

演算子 | を overload して TT のフィルタみたいな記法を実装する

Perl

TT (Template-Toolkit) で値にフィルタを掛ける時に

[% value | replace('x','y') | uri | html %]

みたいに書きますが、Text::MicroTemplate だと仮に replace(), uri() という関数を用意しても

<?= uri( replace($value,'x','y') ) ?>

こんな感じ。なんか読みにくい気がする。(まあ PHP だってそうだといえばそうなんだけど)

<?= $value | replace('x','y') | uri ?>

こんな風に書けたら素敵ですよね。ということでこんなのを書いてみた。

package Filter;
use strict;
require Exporter;
our @ISA       = qw/ Exporter /;
our @EXPORT_OK = qw/ html uri replace html_line_break /;
our %EXPORT_TAGS = ( all => \@EXPORT_OK );
use overload
    '|'  => sub {
        my ($self, $arg) = @_;
        $self->($arg);
    };
sub html() {
    bless sub {
        local $_ = $_[0];
        s{&}{&amp;}g;
        s{<}{&lt;}g;
        s{>}{&gt;}g;
        s{"}{&quot;}g;
        s{'}{&#39;}g;
        $_;
    }, __PACKAGE__;
}
sub uri() {
    use URI::Escape;
    bless sub {
        uri_escape_utf8($_[0]);
    }, __PACKAGE__;
}
sub replace($$) {
    my ($regexp, $replace) = @_;
    bless sub {
        local $_ = $_[0];
        s{$regexp}{$replace}g;
        $_;
    }, __PACKAGE__;
}
sub html_line_break() {
    bless sub {
        local $_ = $_[0];
        s{\r*\n}{<br/>}g;
        $_;
    }, __PACKAGE__;
}
1;

要は、

  • 関数を「bless されたコードリファレンスを返す」ように実装
  • 演算子 | を overload して、自分自身のコードリファレンスをデリファレンスすることで実行

というやり方です。
これで以下のようなコードが動きます。

use Filter qw/:all/;
print "<a>" | html;                  # &lt;a&gt;
print "a&b" | uri;                   # a%26b
print "foo" | replace('f','b');      # boo
print "\n"  | html_line_break | uri; # %3Cbr%2F%3E

複数つなげて連鎖もできる。すばらしい。
ただ、同じようなものが CPAN にあるような気がしてならないんだけど、見つけられませんでした。ご存知の方は教えてください。