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

理系学生日記

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

忍者TOOLS

Catalyst ソースリーディング1

study perl catalyst
$ catalyst.pl myapp

で、作成された myapp.pm をまず見てみます。

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 をロードしてる。ということで、見ていく順番としては、

  1. Catalyst::import
  2. Catalyst::config
  3. Catalyst::setup

となる。

Catalyst::import

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 ブロックには入りません。
結果、

    1. Catalyst::arguments
    2. Catalyst::setup_home

が呼び出されます。ただし、Catalyst::arguments は単なる setter なので、ここでは省略して、Catalyst::setup_home を見ます。

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 の中で定義されています。混乱しそうですが、今のところのクラス階層は

  1. Catalyst::Component
  2. Catalyst
  3. myapp

という形なのですね。

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

Catalyst::config

__PACKAGE__->config( name => 'myapp' );

これは前述の Catalyst::Component::config を呼んで、アプリケーション名を config ハッシュに追加しているだけなので省略。

Catalyst::setup

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 にセットされた状態になってるはずです。

    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" などとしておけば良いこともわかります。

    $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
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。

    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 ですが、長そうなので一旦ここで切る。