rovf has asked for the wisdom of the Perl Monks concerning the following question:

It looks simple, but I can't find but ugly solutions:

PROBLEM

Assume that $name contains some alphanumeric string, and that I can open the file "$name.pm" (which means that under windows, the file could have been created under a name which differs from $name by upper/lower case letters, since Windows is not case sensitive).

Question: Assuming that if some function I am going to call later, would do a require and import on that file, will it be, or will it not be the case, that a package with the name $name will exists?

EXAMPLE

Say, I have three files:

# This is file Test1.pm package Test1; # End of File # This is file test2.pm package Test2; # End of File # This is file Test3.pm package Test; # End of File
and I go through the files like this:
foreach my $name (qw(Test1 test2 Test3)) { unless(file_defines_package_of_same_name($name)) { print "$name\n"; } }
I would like to have printed:
test2 Test3
I would like to have test2 printed, because the package defined in test2.pm is Test2, not test2. I would like to have Test3 printed, because the package defined in Test3.pm is Test, not Test3. The problem is how to write the function file_defines_package_of_same_name.

IDEAS

I have already thought about ways to solve this, but all my solutions seem to be unnecessarily complicated. Here are the two ideas I had so far:

1. Manually open the file and grep for "package $name". Disadvantage: Might give false positive if the line in question appears within a comment or string.

2. Tentatively require the module, then inspect the Perl symbol table for the presence of a $name:: hash:

# NOT TESTED!!!! sub file_defines_package_of_same_name { my $name=shift; eval { require $name; { no strict; $name->import(); } # I guess I don't need this, do I? defined(eval 'scalar(keys(%'.$name.'::))'); } }
I might get false positives if package $name already existed before, but this is a restriction I can live with. I don't know however about what other nasty consequences this might have to my current package.

RATIONALE

Why do I want to do such a weird test? I am maintaining an application which uses Class::MixinFactory. This module exits Perl if it tries to load a mixin module, and the mixin name (which ends up being a module name) does not match the package name defined in the module.

Hence, a third option for my problem would be to fix Class::MixinFactory, so that instead of exiting, it would throw an exception; but since this is a highly complex module, I would prefer to catch the error before calling MixinFactory, provided there is an easy way to do it.

-- 
Ronald Fischer <ynnor@mm.st>

Replies are listed 'Best First'.
Re: Finding out whether a module contains a certain package
by JavaFan (Canon) on May 04, 2009 at 13:30 UTC
    Question: Assuming that if some function I am going to call later, would do a require and import on that file, will it be, or will it not be the case, that a package with the name $name will exists?
    That highly depends on what you mean by "a package with the name $name".

    Although Perl has the keyword package, it doesn't have a first or even second class object which is a "package". A "package" is just a bunch of things sharing a name space. But you don't need to package keyword to put something in the name space. And, for most programming, any namespace always exists, with first putting something in it.

    Of course, the package keyword does have some visible compile time effects; it sets an entry in %::; but %:: will be populated even if you don't have nay package keyword in your program.

      That highly depends on what you mean by "a package with the name $name". Although Perl has the keyword package, it doesn't have a first or even second class object which is a "package".

      I see. This makes it more tricky.

      Right now, Class::MixinFactory works as follows: The creation function gets an array of names, say qw(Abc Xyz), and assumes that files Abc.pm Xyz.pm. Conceptionally, each of these modules is supposed to define a so-called mixin (which is done by defining the functions in those modules within packages Abc and Xyz, respectively). These "mixins" are not real classes (they don't have any constructor are nevere "blessed"); MixinFactory now makes out of these mixin-classes a new class (with a class/package name created at runtime), and this new class combines all the functions of the individual classes (plus a few more added by MixinFactory).

      Everything works fine if it is really setup that way, but consider the case if, due to an error, MixinFactory gets the list qw(Abc Uvw) or qw(Abc xYz) instead of qw(Abc Xyz) This can happen, because the names inside the qw() list actually come from an outside source (I know that this is a security hole, but let's aside this issue for the moment). The first case would still be harmless: MixinFactory would find that a file with the name Uvw.pm does not exist, and throw an exception, which we catch.

      The second case would be harmless on Unix, for the same reason: MixinFactory would find that a file with the name xYz.pm does not exist, and again throw an exception.

      The second case causes trouble, however, when you are on Windows. In Windows you can open successfully the file "xYz.pm", despite the "real" name of the file is "Xyz.pm", because Windows is not case sensitive. However, when MixinFactory tries to create the mixin class, it wants to make a mixin out of Abc and xYz, but the functions are defined in package Xyz, and it terminates the program.

      Since we have control over the mixin modules, we can ensure that a module Xyz.pm indeed contains a package of the right spelling, Xyz, so it would be sufficient to protect agains a missspelling in upper/lower case on Windows. But I think this is not easy either, isn't it? Ronald

      -- 
      Ronald Fischer <ynnor@mm.st>
        Considering that one can have a file Abc.pm
        sub Abc::foo { ... } sub Abc::bar { ... }
        without any package statement, or even:
        BEGIN { my $package = ... Complicated expression ...; eval <<"EOT" package ${package}; sub foo { ... } sub bar { ... } EOT }
        I think you can forget about catching all possible cases.

        Considering that the majority of the people will write package Abc; near the top of the file, and all you seem to want is earlier program termination, why not perform a check that will catch the majority of the error, and let the few cases where 1) the author doesn't use "package Abc", and 2) the author miscapitalizes the package name, and 3) the author uses a case insensitive file system die sometime later (due to missing subroutines)?

        It seems your problem is that Windows is not case sensitive when opening files. But its file systems are case preserving so you can overcome this by testing that the case of the file that was loaded is the same as the case of the mixin name. Testing the file name would address the root cause of the problem and avoid dependence on the internals of the mixins.

        Retreiving the case of the file isn't elegant, but it is easy enough - just read the directory to find the file name.

        But a quick review of the MixinFactory::Factory->class() method, combined with your control over the mixin modules to ensure they have package statements with package names corresponding to their file names suggests that either of your proposed tests should be adequate.

Re: Finding out whether a module contains a certain package
by ELISHEVA (Prior) on May 05, 2009 at 07:06 UTC

    If you simply want to terminate with an exception, why not wrap the calls to Class::MixinFactory with an eval block. Maybe something like this (psuedocode)?

    sub makeMixin { #input is an array of file names my @aImpliedPackageNames = map { ... } @_; my $class; eval { $class = MyClass->class(@aImpliedPackageNames); return 1; } or do { my $e=$@; # compute exception of your choosing, e.g. # a list of missing modules: my @aNotFound; foreach my $sPackage (@aImpliedPackageNames) { if (!eval("scalar keys %${sPackage}::")) { push @aNotFound, $sPackage; } } if (scalar(@aNotFound)) { die "Couldn't make mixin: the following packages " . "are undefined: @aNotFound"; } else { die $e; } } return $class; }

    Best, beth

      That's what is done anyway, but since MixinFactory does not throw an exception in this case, the eval block is of no use.

      Also, I don't want to terminate. If I would want to terminate, I just could leave it as it is.

      -- 
      Ronald Fischer <ynnor@mm.st>

        If your program is really terminating, are you sure you are even finishing compilation? eval doesn't need one to throw an explicit exception or even a die statement in the enclosed code. The only thing it won't "catch" is an explicit exit statement buried somewhere in the enclosed code. Do you mean to say that Class::MixinFactory is calling exit? I searched Class::MixinFactory::Factory where the class(...) subroutine is defined and could find no such call.

        Consider these two examples. The first terminates because of division by 0, but the second does not. Neither throw exceptions.

        First example using a hard-coded division by 0 (compilation error before we even get to eval)

        sub foo { 5/0; } print "Before foo\n"; eval { foo 0; return 1; } or do { print "Oops!\n" }; print "Going onto other things\n"; # output Illegal division by zero at ...

        Second example using a variable denominator (runtime error)

        sub foo { 5/$_[0]; } print "Before foo\n"; eval { foo 0; return 1; } or do { print "Oops!\n" }; print "Going onto other things\n"; #outputs Before foo Oops! Going onto other things

        But even if eval { ... } doesn't work, you could still scan the symbol table for the packages involved in the mix-in after you call class(...) and then throw your own exception if you don't like what you see, i.e. if (scalar @aNotFound) { die ... }. I'm sure you've already thought of that, so perhaps you could explain why that doesn't work for you?

        Best, beth

        Update: clarification of timing of compilation error.