views:

108

answers:

1

If a file is already loaded, is there anyway to hook into the use/require so I can throw an exception? In my upcoming nextgen::blacklist, I'm trying to die if certain modules are used. I'm using the object-hook method as mentioned in perldoc -f require: there are three-like hooks object, array with subref, and subref. The example in this post is the object-hook, you can find my attempt of the sub-ref hook in nextgen::blacklist.

The syntax I'm desiring is something like:

perl -Mnextgen -E"use NEXT"

package Foo;
use nextgen;
use NEXT;

Ideally it is supposed to throw a message like this:

nextgen::blacklist violation with import attempt for: [ NEXT (NEXT.pm) ] try 'use mro' instead.

I've tried this a bunch of different ways.

package Class;
use Data::Dumper;
use strict;
use warnings;

sub install {
  unshift @main::INC, bless {}, __PACKAGE__
    unless ref $main::INC[0] eq __PACKAGE__
  ;
}

sub reset_cache { undef %main::INC }

sub Class::INC {
  my ( $self, $pmfile ) = @_;
  warn Dumper [\%main::INC, $pmfile];
  #undef %INC;
} 

package main;
  BEGIN { Class->install; undef %main::INC }
  use strict;
  use strict;
  use strict;
  use strict;
  use warnings;
  use strict;
  use warnings;

It seems as if %INC is only set after these hooks. I'm interested in anything that will allow me to throw an exception. If an attempt is made to load/reload a module dispite the status of it as a dependency of other modules that don't use my pragma, I want to die.

package Foo;
use NEXT;

package main;
use Foo; (which uses Next.pm);
use NEXT.pm; ## Throw exception
+4  A: 

You probably want to put a coderef onto the beginning @INC, as described in perldoc -f require. From there, you can raise exceptions to prevent certain modules from being loaded, or do nothing to let require carry on with its normal job of looking up the module in the other @INC entries.

$ perl -E'BEGIN { unshift @INC, sub { die q{no NEXT} if pop eq q{NEXT.pm}; () }; }; use Carp; say q{success}'
success
$ perl -E'BEGIN { unshift @INC, sub { die q{no NEXT} if pop eq q{NEXT.pm}; () }; }; use NEXT; say q{success}'
no NEXT at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

If you want that behaviour to be lexical, you should make use of Perl's hints hash %^H. Dealing with that is a little fiddly, so I'd recommend using Devel::Pragma, which can take care of all the gory details for you.

As you pointed out, the @INC hooks won't be executed for a module that's already loaded. If you also need to hook into the use or require of a loaded module, overriding CORE::GLOBAL::require would work, as it is called for every attempt to load a module.

$ perl -E'BEGIN { *CORE::GLOBAL::require = sub { warn @_ } } use NEXT; use NEXT;'
NEXT.pm at -e line 1
NEXT.pm at -e line 1.

Also, as the maintainer of NEXT, I completely approve of preventing people from using it, at all, ever. :-)

rafl
Thanks for the words of encouragement, I've tried putting a coderef in `@INC`, check out [nextgen::blacklist](http://github.com/EvanCarroll/nextgen/tree/blacklist/lib/nextgen/), for that attempt. There are three hooks that seem to all provide this functionality (array w/ coderef, object, and coderef), also in the perldoc you reference.
Evan Carroll
So what's the issue? I've just added a tiny example of that to my answer, and it works just as expected.
rafl
Per the title, (and text) If `NEXT` is included by a dependency, I still want `use NEXT` to throw an exception. That means if you use Foo.pm, which depends on `NEXT`. I still don't want `NEXT` permitted. But, I don't want the include for Foo.pm to cause an exception.
Evan Carroll
Got it. Answer updated.
rafl
Yes. That way might work, [Khisanth just turned me on his approach](http://scsys.co.uk:8002/52489) inspired by [ACME::Magic::Poney](http://cpansearch.perl.org/src/JLAVALLEE/Acme-Magic-Pony-0.03/lib/Acme/Magic/Pony.pm)
Evan Carroll
Very well done. Thanks for the help I've uploaded nextgen::blacklist and it is on its way to CPAN now.
Evan Carroll