How do I dynamically discover packages from a part

2019-06-14 14:13发布

问题:

I have a directory structure that looks like:

Foo::Bar::Baz::1 Foo::Bar::Baz::2 etc

Can I list the packages from something like:

use Foo::Bar::Baz;

Thanks!

Edit: Made it more clear what the modules are.

回答1:

Just to be clear, are you looking at random packages in random Perl code?

Or for Perl modules, e.g. "a/b/c/d1.pm" with module "a::b::c::d1"?

In either case, you can not use a single "use" statement to load them all.

What you need to do is to find all the appropriate files, using either glob or File::Find.

In the first case (modules), you can then load them either by require-ing each file, OR by converting filename into module name (s#/#::#g; s#\.pm$##;) and calling use on each module individually.

As far as actual packages nested in random Perl files, those packages can be:

  • Listed by grepping each file (again, found via glob or File::Find) for /^package (.*);/

  • Actually loaded by executing require $file for each file.

    In this case, please note that the package name for each of those packages in a/b/c/1.pl will NOT need to be related to "a::b::c" - e.g. they CAN be named by the file author "p1", "a::p1" or "a::b::c::p1_something".



回答2:

If you want to load all modules in your include path with a certain prefix (e.g. everything under a::b::c, you can use Module::Find.

For example:

use Module::Find 'useall';

my @loaded = useall 'Foo::Bar::Baz';  # loads everything under Foo::Bar::Baz

This depends on your @INC path being set up with the necessary directories, so do any required manipulation (e.g. with use lib) first.



回答3:

Normally a script such as a/b/c.pl won't have a namespace other than main. Perhaps you are thinking of discovering modules with names such as a/b/c.pm (which is a bad name, since lower-cased package names are generally reserved for Perl internals).

However, given a directory path, you can look for potential Perl modules using File::Find:

use strict;
use warnings;
use File::Find;
use Data::Dumper;

my @modules;
sub wanted
{
    push @modules, $_ if m/\.pm$/
}
find(\&wanted, 'A/B');

print "possible modules found:\n";
print Dumper(\@modules)'


回答4:

This might be overkill, but you can inspect the symbol table before and after loading the module and see what changed:

use strict; use warnings;
my %original = map { $_ => 1 } get_namespaces("::");
require Inline;
print "New namespaces since 'require Inline' call are:\n";
my @new_namespaces = sort grep !defined $original{$_}, get_namespaces("::");
foreach my $new_namespace (@new_namespaces) {
  print "\t$new_namespace\n";
}

sub get_namespaces {
  # recursively inspect symbol table for known namespaces
  my $pkg = shift;
  my @namespace = ();
  my %s = eval "%" . $pkg;
  foreach my $key (grep /::$/, keys %s) {
    next if $key eq "main::";
    push @namespace, "$pkg$key", get_namespaces("$pkg$key");
  }
  return @namespace;
}

New namespaces since 'require Inline' call are:
        ::AutoLoader::
        ::Config::
        ::Digest::
        ::Digest::MD5::
        ::Dos::
        ::EPOC::
        ::Exporter::
        ::Exporter::Heavy::
        ::File::
        ::File::Spec::
        ::File::Spec::Cygwin::
        ::File::Spec::Unix::
        ::File::Spec::Win32::
        ::Inline::Files::
        ::Inline::denter::
        ::Scalar::
        ::Scalar::Util::
        ::Socket::
        ::VMS::
        ::VMS::Filespec::
        ::XSLoader::
        ::vars::
        ::warnings::register::