2

requireステートメントを使用していくつかの perl コードをインポートします。インポートしたいコードは次のmylibA.plとおりです。

#!/usr/bin/perl
package FOO::BAR;

sub routine {
    print "A message!\n";
}

mylibB.pl:

#!/usr/bin/perl
package FOO::BAZ;

sub routine {
    print "Another message!\n";
}

次に、次のように使用します。

#!/usr/bin/perl
foreach my $lib (qw/ mylibA.pl mylibB.pl /){
     require $lib;
     print "Make a call to ${lib}'s &routine!\n";
}

私のスクリプトがrequireステートメントで取り込まれた名前空間を把握する方法はありますか?

4

4 に答える 4

2

perlソースファイルでパッケージを見つけるという主に学術的な問題について:

Perl ファイル内のすべてのパッケージを取得するには、CPAN モジュールModule::Extract::Namespacesを試すことができます。PPI を使用しているため、100% 完璧ではありませんが、ほとんどの場合は十分です。

perl -MModule::Extract::Namespaces -e 'warn join ",", Module::Extract::Namespaces->from_file(shift)' /path/to/foo.pm

ただし、大きなファイルの場合、PPI は遅くなる可能性があります。

要求の前後でアクティブなパッケージを比較してみることができます。これも完璧ではありません。なぜなら、perl ライブラリ ファイルが追加のモジュールをロードする場合、どれが主要なファイルのパッケージで、後で何がロードされるかがわからないからです。パッケージのリストを取得するには、たとえばDevel::Symdumpを使用できます。サンプル スクリプトを次に示します。

use Devel::Symdump;

my %before = map { ($_,1) } Devel::Symdump->rnew->packages;
require "/path/to/foo.pm";
my %after  = map { ($_,1) } Devel::Symdump->rnew->packages;

delete $after{$_} for keys %before;
print join(",", keys %after), "\n";

「パッケージ」宣言の perl ファイルを解析することもできます。実際、これは PAUSE アップロード デーモンが行っていることなので、ほとんどの場合はこれで十分です。https://github.com/andk/pause/blob/master/lib/PAUSE/pmfile.pmのサブルーチンpackages_per_pmfileを 見てください。

于 2013-10-18T20:52:28.213 に答える
1

ここには 2 つの問題があります。

  1. スクリプトをスタンドアロンとして実行する場合とモジュールとして使用する場合の動作を変更するにはどうすればよいですか?
  2. コンパイルしたばかりのコードのパッケージ名を見つけるにはどうすればよいですか?

質問 2 に対する一般的な答えは次のとおりです。どのコンパイル ユニットにも任意の数のパッケージが含まれている可能性があるため、必要ありません。

とにかく、ここに3つの可能な解決策があります:

  1. モジュールに名前を付けて、ロード時に名前がわかるようにします。
  2. 各モジュールを中央のランデブー ポイントに登録します。
  3. #1 と同様ですが、プラグインの自動検出を追加します。

最も簡単な解決策は、すべての API を通常のモジュールに配置し、スタンドアロン ロジックを別のスクリプトに配置することです。

/the/location/
  Module/
    A.pm
    B.pm
  a-standalone.pl
  b-standalone.pl

各スタンドアロンの基本的な外観

use Module::A;
Module::A->run();

別のスクリプトがそのコードを再利用したい場合は、そうします

use lib "/the/location";
use Module::A;
...

ロードが実行時に発生する場合は、Module::Runtimeここで役立ちます。

use Module::Runtime 'use_module';
use lib "/the/location";
my $mod_a = use_module('Module::A');
$mod_a->run();

a-standalone.plと の内容をModule/A.pm別々のファイルに配置することは厳密には必要ではありませんが、その方が明確です。スクリプトとして使用されている場合にのみ、モジュール内のコードを条件付きで実行したい場合はunless(caller)トリックを利用できます。


もちろん、これはすべてトリックです。ここでは、モジュール名からファイル名を決定しますが、その逆ではありません。既に述べたように、これはできません。

できることは、各モジュールを特定の事前定義された場所に登録することです。

Rendezvous::Point->register(__FILE__ => __PACKAGE__);

もちろん、スタンドアロン バージョンは、存在しない可能性から保護する必要があるため、次のRendezvous::Pointようになります。

if (my $register = Rendezvous::Point->can("register")) {
  $register->(__FILE__ => __PACKAGE__);
}

ええ、これはばかげており、DRY に違反しています。Rendezvous::Pointそれでは、これを処理するモジュールを作成しましょう。

/the/location/Rendezvous/Point.pm

package Rendezvous::Point;
use strict; use warnings;

my %modules_by_filename;

sub get {
  my ($class, $name) = @_;
  $modules_by_filename{$name};
}

sub register {
  my ($file, $package) = @_;
  $modules_by_filename{$file} = $package;
}

sub import {
  my ($class) = @_;
  $class->register(caller());
}

ここでuse Rendezvous::Point;、呼び出し元のパッケージを登録し、モジュール名を絶対パスで取得できるようにします。

さまざまなモジュールを使用するスクリプトは、次のようになります。

use "/the/location";
use Rendezvous::Point ();  # avoid registering ourself

my $prefix = "/the/location";
for my $filename (map "$prefix/$_", qw(Module/A.pm Module/B.pm)) {
  require $filename;
  my $module  = Rendezvous::Point->get($filename)
             // die "$filename didn't register itself at the Rendezvous::Point";
  $module->run();
}

次に、のような完全な機能を備えたプラグイン システムがありますModule::Pluggable。このシステムは、Perl モジュールが存在する可能性のあるすべてのパスを調べることによって機能し、特定のプレフィックスがある場合はそれらをロードします。それを使用したソリューションは次のようになります。

/the/location/
  MyClass.pm
  MyClass/
    Plugin/
      A.pm
      B.pm
  a-standalone.pl
  b-standalone.pl

すべてが最初のソリューションと同じです。スタンドアロン スクリプトは次のようになります。

use lib "/the/location/";
use MyClass::Plugin::A;
MyClass::Plugin::A->run;

しかし、MyClass.pm次のようになります。

package MyClass;
use Module::Pluggable require => 1;  # we can now query plugins like MyClass->plugins

sub run {
  # Woo, magic! Works with inner packages as well!
  for my $plugin (MyClass->plugins) {
    $plugin->run();
  }
}

もちろん、これには特定の命名スキームが必要ですが、可能なプラグインを自動検出します。

于 2013-10-18T18:53:56.683 に答える
0

As mentioned before it is not possible to look up the namespace of a 'required' package without extra I/O, guessing or assuming.

Like Rick said before, one have to intrude the namespace of the caller or better 'main'. I prefer to inject specific hooks within a BEGIN block of the 'required' package.

#VENDOR/App/SocketServer/Protocol/NTP.pm
package VENDOR::App::SocketServer::Protocol::NTP;

BEGIN {
  no warnings;
  *main::HANDLE_REQUEST = \&HANDLE_REQUEST;
}

sub HANDLE_REQUEST {
}

#VENDOR/App/SocketServer.pm
my $userPackage= $ARGV[0];
require $userPackage;
main::HANDLE_REQUEST();

Instead of *main:: you can get more specific with *main::HOOKS::HANDLE_REQUESTS i.e. This enables you to resolve all injected hooks easily within the caller by iterating over the HOOK's namespace portion.

foreach my $hooks( keys %main::HOOKS ) {

}
于 2016-06-17T11:31:53.213 に答える