理系学生日記

おまえはいつまで学生気分なのか

Catalyst ソースリーディング1

|tcsh| $ catalyst.pl myapp ||< で、作成された myapp.pm をまず見てみます。

|perl| use Catalyst::Runtime 5.80; use parent qw/Catalyst/; use Catalyst qw/-Debug ConfigLoader Static::Simple/;

PACKAGE->config( name => 'myapp' ); PACKAGE->setup(); ||< Catalyst::Runtime はソースを読む限り空実装のようなので、無視しよう。そうなると、myapp は Catalyst を継承しているとともに、Catalyst をロードしてる。ということで、見ていく順番としては、 +Catalyst::import +Catalyst::config +Catalyst::setup となる。

**Catalyst::import

|perl| sub import { my ( $class, @arguments ) = @_;

my $caller = caller();
return if $caller eq 'main';

my $meta = Moose::Meta::Class->initialize($caller);
unless ( $caller->isa('Catalyst') ) {
    my @superclasses = ($meta->superclasses, $class, 'Catalyst::Controller');
    $meta->superclasses(@superclasses);
}
# Avoid possible C3 issues if 'Moose::Object' is already on RHS of MyApp
$meta->superclasses(grep { $_ ne 'Moose::Object' } $meta->superclasses);

unless( $meta->has_method('meta') ){
    $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } );
}

$caller->arguments( [@arguments] );
$caller->setup_home;

} ||< $caller は myapp であり、先に見たように Catalyst を継承しているので unless ブロックには入りません。 結果、 ++Catalyst::arguments ++Catalyst::setup_home が呼び出されます。ただし、Catalyst::arguments は単なる setter なので、ここでは省略して、Catalyst::setup_home を見ます。

|perl| sub setup_home { my ( $class, $home ) = @_;

if ( my $env = Catalyst::Utils::env_value( $class, 'HOME' ) ) {
    $home = $env;
}

$home ||= Catalyst::Utils::home($class);

if ($home) {
    #I remember recently being scolded for assigning config values like this
    $class->config->{home} ||= $home;
    $class->config->{root} ||= Path::Class::Dir->new($home)->subdir('root');
}

} ||< このメソッドはアプリケーションのホームディレクトリ取得メソッドです。Catalyst::Utils::env_value は CATALYST_xx とか MYAPP_xx とかの環境変数から情報を取得しようとします。この場合だと CATALYST_HOME とかですね。それが見つからない場合は、Catalyst::Utils::home が呼ばれ、この中ではいろいろ泥臭いことをやってます。 めでたくホームディレクトリが見つかると、それを config ハッシュに格納します。

ちなみにこの config ハッシュは、Catalyst の親クラスである Catalyst::Component の中で定義されています。混乱しそうですが、今のところのクラス階層は +Catalyst::Component +Catalyst +myapp という形なのですね。

結局、import が呼ばれた段階で、アプリケーションのホームディレクトリが設定されるんだ、ということでしょうか。

**Catalyst::config

|perl| PACKAGE->config( name => 'myapp' ); ||< これは前述の Catalyst::Component::config を呼んで、アプリケーション名を config ハッシュに追加しているだけなので省略。

**Catalyst::setup

|perl| sub setup { my ( $class, @arguments ) = @_; croak('Running setup more than once') if ( $class->setup_finished );

unless ( $class->isa('Catalyst') ) {

    Catalyst::Exception->throw(
        message => qq/'$class' does not inherit from Catalyst/ );
}

if ( $class->arguments ) {
    @arguments = ( @arguments, @{ $class->arguments } );
}

# Process options
my $flags = {};

||< arguments を呼び出していますが、これは import のときにセットしたものに対する getter として呼ばれています。 import のときに set されたのは '-Debug', 'ConfigLoader', 'Static::Simple' の 3 つなので、これが @arguments にセットされた状態になってるはずです。

|perl| foreach (@arguments) {

    if (/^-Debug$/) {
        $flags->{log} =
          ( $flags->{log} ) ? 'debug,' . $flags->{log} : 'debug';
    }
    elsif (/^-(\w+)=?(.*)$/) {
        $flags->{ lc $1 } = $2;
    }
    else {
        push @{ $flags->{plugins} }, $_;
    }
}

||< この for ループで @arguments の内容が plugins としてセットされてますね。また、arguments で "log=info" などとしておけば良いこともわかります。

|perl| $class->setup_home( delete $flags->{home} );

$class->setup_log( delete $flags->{log} );
$class->setup_plugins( delete $flags->{plugins} );
$class->setup_dispatcher( delete $flags->{dispatcher} );
$class->setup_engine( delete $flags->{engine} );
$class->setup_stats( delete $flags->{stats} );

||< 大量の setup_xxx メソッドがある。setup_home はもう見たので省略。

***Catalyst::setup_log

|perl| sub setup_log { my ( $class, $levels ) = @_;

$levels ||= '';
$levels =~ s/^\s+//;
$levels =~ s/\s+$//;
my %levels = map { $_ => 1 } split /\s*,\s*/, $levels;

my $env_debug = Catalyst::Utils::env_value( $class, 'DEBUG' );
if ( defined $env_debug ) {
    $levels{debug} = 1 if $env_debug; # Ugly!
    delete($levels{debug}) unless $env_debug;
}

unless ( $class->log ) {
    $class->log( Catalyst::Log->new(keys %levels) );
}

if ( $levels{debug} ) {
    Class::MOP::get_metaclass_by_name($class)->add_method('debug' => sub { 1 });
    $class->log->debug('Debug messages enabled');
}

} ||< まだ log オブジェクトが設定されていない場合は、Catalyst::Log をコンストラクトして設定しておく、くらいでしょうか。 また、debug レベルの場合は、myapp に対して常に true を返す debug メソッドを生やしています。

***Catalyst::setup_plugins 次は plugin をロードする setup_plugins。

|perl| sub setup_plugins { my ( $class, $plugins ) = @_;

    $class->_plugins( {} ) unless $class->_plugins;
    $plugins ||= [];

    my @plugins = Catalyst::Utils::resolve_namespace($class . '::Plugin', 'Catalyst::Plugin', @$plugins);

    for my $plugin ( reverse @plugins ) {
        Class::MOP::load_class($plugin);
        my $meta = find_meta($plugin);
        next if $meta && $meta->isa('Moose::Meta::Role');

        $class->_register_plugin($plugin);
    }

    my @roles =
        map { $_->name }
        grep { $_ && blessed($_) && $_->isa('Moose::Meta::Role') }
        map { find_meta($_) }
        @plugins;

    Moose::Util::apply_all_roles(
        $class => @roles
    ) if @roles;
}

||<

Catalyst::Utils::resolve_namespace は、plugin 名の接頭語に「+」とか「~」とか付けない限りは特に気にせず、Catalyst::Plugin:: が plugin 名の最初に付くものと覚えておけば良い。そうやってフルパッケージ名を解決したところで、そのプラグインをロードする。プラグインが Role の場合は、それを適用する。 だいたい他のもそんなかんじ。

次は setup_components ですが、長そうなので一旦ここで切る。