CGI.pm の importメソッドの仕組みを見てみる

続・初めてのPerl p.202 を見て、 CGI.pm の import メソッドの仕組が気になったのでソースを読んでみることにする。


まずは、CGI.pm が置いてある場所を調べよう。
モジュールのパスを最初に調べて、

% perl -e 'print map "$_\n", @INC;'
/usr/lib/perl5/site_perl/5.8.8/i386-linux-thread-multi
/usr/lib/perl5/site_perl/5.8.7/i386-linux-thread-multi
/usr/lib/perl5/site_perl/5.8.6/i386-linux-thread-multi
/usr/lib/perl5/site_perl/5.8.5/i386-linux-thread-multi
/usr/lib/perl5/site_perl/5.8.8
/usr/lib/perl5/site_perl/5.8.7
/usr/lib/perl5/site_perl/5.8.6
/usr/lib/perl5/site_perl/5.8.5
/usr/lib/perl5/site_perl
/usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi
/usr/lib/perl5/vendor_perl/5.8.7/i386-linux-thread-multi
/usr/lib/perl5/vendor_perl/5.8.6/i386-linux-thread-multi
/usr/lib/perl5/vendor_perl/5.8.5/i386-linux-thread-multi
/usr/lib/perl5/vendor_perl/5.8.8
/usr/lib/perl5/vendor_perl/5.8.7
/usr/lib/perl5/vendor_perl/5.8.6
/usr/lib/perl5/vendor_perl/5.8.5
/usr/lib/perl5/vendor_perl
/usr/lib/perl5/5.8.8/i386-linux-thread-multi
/usr/lib/perl5/5.8.8


次に、適当そうな深さのディレクトリで find してみる。おっ、あったあった。

% find /usr/lib/perl5/ -name CGI.pm
/usr/lib/perl5/5.8.8/CGI.pm


今日は遅いので寝る。つづきは、明日書くかもしれないし書かないかもしれない。というより、ソース読んでも理解できない可能性が大。もし、全く理解できなかったら、エントリは消さないで読める日が来ることを待つことにしとく。


なんとなくしか分からなかったけど、分かったところまでメモしておくことにした。


基本的に、CGI.pm の import のやっていることを要約すると以下のようになる。(続・初めてのPerl p.204)

sub import {
  # (1) シンボリックリファレンスを許可する。ラクダ本 p.304, p.1019
  no strict 'refs';
  # (2) インポートリストに debug が含まれていたら $debug を真にする
  my $debug = grep { $_ eq 'debug' } @_;
  # (3) 呼び出し元のパッケージ名を取得する
  my ($package, $file, $line) = caller;
  warn "I was called by $package in $file\n" if $debug;
  # (4) 現在のパッケージを呼び出し元パッケージにエクスポートする
  for (qw(filename basename fileparse)) {
    # 型グロブに代入することによって、現在のパッケージの内容を呼び出し元パッケージにエクスポートする(エイリアス処理)
    # ラクダ本 p.341
    *{$package . "::$_"} = \&$_;
  }
}


実際の、CGI.pm 中の importの抜粋。番号は上に示した単純な例との対応関係を表す。

# to import symbols into caller
sub import {
    # use strict を使ってないので、(1)の工程は必要無い。
    my $self = shift;

    # This causes modules to clash.
    undef %EXPORT_OK;
    undef %EXPORT;

    # (2) インポートリストに応じてフラグを立てる
    $self->_setup_symbols(@_);
    # (3) 呼び出し元のパッケージ名を取得する
    my ($callpack, $callfile, $callline) = caller;

    # To allow overriding, search through the packages
    # Till we find one in which the correct subroutine is defined.
    # (4) 現在のパッケージを呼び出し元パッケージにエクスポートする
    #     親にエクスポートしたい内容があるかもしれないので、親も検索対象にする
    my @packages = ($self,@{"$self\:\:ISA"});
    foreach $sym (keys %EXPORT) {
        my $pck;
        my $def = ${"$self\:\:AutoloadClass"} || $DefaultClass;
        foreach $pck (@packages) {
            if (defined(&{"$pck\:\:$sym"})) {
                $def = $pck;
                last;
            }
        }
        *{"${callpack}::$sym"} = \&{"$def\:\:$sym"};
    }
}

sub _setup_symbols {
  my $self = shift;
  my $compile = 0;

  # to avoid reexporting unwanted variables
  undef %EXPORT;

  foreach (@_) {
        $HEADERS_ONCE++,         next if /^[:-]unique_headers$/;
        $NPH++,                  next if /^[:-]nph$/;
        $NOSTICKY++,             next if /^[:-]nosticky$/;
        $DEBUG=0,                next if /^[:-]no_?[Dd]ebug$/;
        $DEBUG=2,                next if /^[:-][Dd]ebug$/;
        $USE_PARAM_SEMICOLONS++, next if /^[:-]newstyle_urls$/;
        $XHTML++,                next if /^[:-]xhtml$/;
        $XHTML=0,                next if /^[:-]no_?xhtml$/;
        $USE_PARAM_SEMICOLONS=0, next if /^[:-]oldstyle_urls$/;
        $PRIVATE_TEMPFILES++,    next if /^[:-]private_tempfiles$/;
        $TABINDEX++,             next if /^[:-]tabindex$/;
        $CLOSE_UPLOAD_FILES++,   next if /^[:-]close_upload_files$/;
        $EXPORT{$_}++,           next if /^[:-]any$/;
        $compile++,              next if /^[:-]compile$/;
        $NO_UNDEF_PARAMS++,      next if /^[:-]no_undef_params$/;

        # This is probably extremely evil code -- to be deleted some day.
        if (/^[-]autoload$/) {
            my($pkg) = caller(1);
            *{"$ {pkg}::AUTOLOAD"} = sub {
                my($routine) = $AUTOLOAD;
                $routine =~ s/^.*::/CGI::/;
                &$routine;
            };
            next;
        }

            foreach (&expand_tags($_)) {
            tr/a-zA-Z0-9_//cd;  # don't allow weird function names
            $EXPORT{$_}++;
        }
    }
    _compile_all(keys %EXPORT) if $compile;
    @SAVED_SYMBOLS = @_;
}


また後で書き足していくかもしれない。