エンジニアブログ

エンジニアブログ
Perl

Mojo::Templateで自分好みのテンプレートクラスを作る

8c280f4554be533e4c1d3b0b489bbb0c-1.jpeg sugama 2012年10月29日

こんにちは。須釜です。

今日は、Mojoliciousに同梱される便利なモジュール群の中からMojo::Templateを紹介したいと思います。

Mojo::Templateはテキストの中にPerlコードをそのまま書いてしまおうという趣旨のテンプレートエンジンです。実際にはMojoliciousのフレームワークの標準のレンダラーのバックエンドとして使われるのみで、単体で使おうとする人はあまりいないかも知れませんが、少し工夫すると結構便利に使えそうです。

下記のようなデータがあって、テンプレートで出力したいとします。

my $people = [
    {name => 'Sato',    age => '30'},
    {name => 'Saito',   age => '30'},
    {name => 'Suzuki',  age => '30'},
];

printfを使って書くと、こうでしょうか。

my $template = <<EOF;
name    : %s
age     : %s
EOF

for my $person (@$people) {
    printf($template, $person->{name}, $person->{age});
}

結果はこうなります。

name    : Sato
age     : 30
name    : Saito
age     : 30
name    : Suzuki
age     : 30

これをMojo::Templateで書き直してみます。

my $template = <<'EOF';
name    : <%= $_[0]->{name} %>
age     : <%= $_[0]->{age} %>
EOF

my $mt = Mojo::Template->new;

for my $person (@$people) {
    print $mt->render($template, $person);
}

こうすることで、名前付き引数風になってテンプレートの可読性があがりますし、 Perlコードで何でもできるようになります。 具体的な例は公式ドキュメントを参照してください。

コンパイルしたテンプレートを使い回すと少し速くなるかも知れません。

my $template = <<'EOF';
name    : <%= $_[0]->{name} %>
age     : <%= $_[0]->{age} %>
EOF

my $mt = Mojo::Template->new;
$mt->parse($template)->build->compile;

for my $person (@$people) {
    print $mt->interpret($person);
}

ちなみに、$mt->codeには下記のようなテンプレートのコンパイル結果が格納されています。

package Mojo::Template::SandBox;
no warnings 'redefine';
sub _escape {
    no warnings 'uninitialized';
    ref $_[0] eq 'Mojo::ByteStream'? $_[0] : Mojo::Util::xml_escape("$_[0]")
}
use Mojo::Base -strict;
sub {
    my $_M = '';
    ;
    do {
        $_M .= "name    : ";
        $_M .= scalar  $_[0]->{name} ;
        $_M .= "\n";
        $_M .= "age     : ";
        $_M .= scalar  $_[0]->{age} ;
        $_M .= "\n";
        ;
        $_M
    }
};

Mojo::Templateにはprependメソッドというものが用意されています。これは、文字列で渡したPerlコードをテンプレートの前段の処理として追加できるというものです。prependを使えば、テンプレート内で使用できる自作の関数を定義することもできます。

my $template = q{<%= my_func(shift) %>};
my $mt = Mojo::Template->new;

$mt->prepend('sub my_func { $_[0] ** 2 }');

$mt->parse($template)->build->compile;
print $mt->interpret(1); # 1
print $mt->interpret(2); # 4
print $mt->interpret(3); # 9

ただ、さすがにPerlコードを文字列で組み立てるのは汚らしいので、自前のクラスでMojo::Templateを隠蔽してしまいましょう。

package Text::MojoEP;
use Mojo::Base -base;

__PACKAGE__->attr(mt => sub { Mojo::Template->new });
__PACKAGE__->attr(funcs => sub {{}});

sub add_function {
    my ($self, $name, $cb) = @_;
    $self->funcs->{$name} = $cb;
    return $self;
}

sub render {
    my ($self, $path, @args) = @_;
    
    my $mt = $self->mt;
    
    if (! $mt->compiled) {
        my $prepend = q/no strict 'refs'; no warnings 'redefine';/;
    
        $prepend .= 'my $_H = shift; my $_F = $_H->funcs;';
        for my $name (keys %{$self->funcs}) {
            $prepend .= "sub $name; *$name = sub {\$_F->{$name}->(\@_)};";
        }
        
        $mt->prepend($prepend);
        
        return $mt->render($path, $self, @args);
    }
    
    return $mt->interpret($self, @args);
}

これで、関数の追加が楽なテンプレートクラスの出来上がりです。下記のように使うことができます。

my $template = q{<%= my_func(shift) %>};
my $mt = Text::MojoEP->new;

$mt->add_function('my_func',  sub { $_[0] ** 2 });

print $mt->render($template, 1); # 1
print $mt->render($template, 2); # 4
print $mt->render($template, 3); # 9

テンプレート関数に関してはMojolicious::Plugin::EPの内部でも、ほぼ同様の実装がなされています。ただ、 EPではその他にキャッシュの仕組みや、フレームワーク固有の処理なども追加されているので、単体では使えそうもない感じに仕上がっています。そういう訳で、上記のText::MojoEPクラスのようなものを自作しておくと、自分好みのテンプレートエンジンとして使えて便利かもしれません。

Text::MojoEPはgithubに置いておきましたので、興味がありましたら使ってみてください。