X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=p5sagit%2FImport-Into.git;a=blobdiff_plain;f=lib%2FImport%2FInto.pm;h=74b7358d9093c688b80beb9bb8d2c62745580e21;hp=4f3255c0b66c9000d9716ab383daa83a081aed90;hb=995d8262bd1c7cc306aee068943efa7d205605c0;hpb=2e89909bdeffad61d6fe3e5f54f4c1a8347af5b6 diff --git a/lib/Import/Into.pm b/lib/Import/Into.pm index 4f3255c..74b7358 100644 --- a/lib/Import/Into.pm +++ b/lib/Import/Into.pm @@ -3,18 +3,30 @@ package Import::Into; use strict; use warnings FATAL => 'all'; -our $VERSION = '1.000002'; # 1.0.2 +our $VERSION = '1.001001'; # 1.1.1 + +sub _prelude { + my $target = shift; + my ($package, $file, $line) + = $target =~ /[^0-9]/ ? ($target) : caller($target + 2); + qq{package $package;\n} + . ($file ? "#line $line \"$file\"\n" : '') +} -my %importers; +sub _make_action { + my ($action, $target) = @_; + eval _prelude($target).qq{sub { shift->$action(\@_) }} + or die "Failed to build action sub to ${action} for ${target}: $@"; +} sub import::into { my ($class, $target, @args) = @_; - $class->${\( - $importers{$target} ||= eval qq{ - package $target; - sub { shift->import(\@_) }; - } or die "Couldn't build importer for $target: $@" - )}(@args); + _make_action(import => $target)->($class, @args); +} + +sub unimport::out_of { + my ($class, $target, @args) = @_; + _make_action(unimport => $target)->($class, @args); } 1; @@ -31,11 +43,13 @@ Import::Into - import packages into other packages use Thing1 (); use Thing2 (); + use Thing3 (); sub import { my $target = caller; Thing1->import::into($target); Thing2->import::into($target, qw(import arguments)); + Thing3->import::into(1); # import to level } Note: you don't need to do anything more clever than this provided you @@ -56,6 +70,27 @@ Notably, this works: Thing1->import::into(scalar caller); } +Note 2: You do B need to do anything to Thing1 to be able to call +C on it. This is a global method, and is callable on any +package (and in fact on any object as well, although it's rarer that you'd +want to do that). + +If you provide C with an integer instead of a package name, it +will be used as the number of stack frames to skip to find where to export to. +This has the advantage of preserving the apparent filename and line number +being exported to, which some modules (L, L) check. + +Finally, we also provide an C to allow the exporting of the +effect of C: + + # unimport::out_of was added in 1.1.0 (1.001000) + sub unimport { + Moose->unimport::out_of(scalar caller); # no MyThing == no Moose + } + +If how and why this all works is of interest to you, please read on to the +description immediately below. + =head1 DESCRIPTION Writing exporters is a pain. Some use L, some use L, @@ -101,7 +136,7 @@ know if something's a pragma, and second that you can't use either of these approaches alone on something like L or L that's both an exporter and a pragma. -So, the complete solution is: +So, a solution for that is: my $sub = eval "package $target; sub { shift->import(\@_) }"; $sub->($thing, @import_args); @@ -110,17 +145,58 @@ which means that import is called from the right place for pragmas to take effect, and from the right package for caller checking to work - and so behaves correctly for all types of exporter, for pragmas, and for hybrids. +Additionally, some import routines check the filename they are being imported +to. This can be dealt with by generating a L<#line directive|perlsyn/Plain +Old Comments (Not!)> in the eval, which will change what C reports for +the filename when called in the importer. The filename and line number to use +in the directive then need to be fetched using C: + + my ($target, $file, $line) = caller(1); + my $sub = eval qq{ + package $target; + #line $line "$file" + sub { shift->import(\@_) } + }; + $sub->($thing, @import_args); + Remembering all this, however, is excessively irritating. So I wrote a module -so I didn't have to anymore. Loading L will create a method -C which you can call on a package to import it into another +so I didn't have to anymore. Loading L creates a global method +C which you can call on any package to import it into another package. So now you can simply write: use Import::Into; $thing->import::into($target, @import_args); -Just make sure you already loaded C<$thing> - if you're receiving this from -a parameter, I recommend using L: +This works because of how perl resolves method calls - a call to a simple +method name is resolved against the package of the class or object, so + + $thing->method_name(@args); + +is roughly equivalent to: + + my $code_ref = $thing->can('method_name'); + $code_ref->($thing, @args); + +while if a C<::> is found, the lookup is made relative to the package name +(i.e. everything before the last C<::>) so + + $thing->Package::Name::method_name(@args); + +is roughly equivalent to: + + my $code_ref = Package::Name->can('method_name'); + $code_ref->($thing, @args); + +So since L defines a method C in package C +the syntax reliably calls that. + +For more craziness of this order, have a look at the article I wrote at +L which covers +coderef abuse and the C<${\...}> syntax. + +Final note: You do still need to ensure that you already loaded C<$thing> - if +you're receiving this from a parameter, I recommend using L: use Import::Into; use Module::Runtime qw(use_module); @@ -129,17 +205,23 @@ a parameter, I recommend using L: And that's it. +=head1 ACKNOWLEDGEMENTS + +Thanks to Getty for asking "how can I get C<< use strict; use warnings; >> +turned on for all consumers of my code?" and then "why is this not a +module?!". + =head1 AUTHOR mst - Matt S. Trout (cpan:MSTROUT) =head1 CONTRIBUTORS -None yet - maybe this software is perfect! (ahahahahahahahahaha) +haarg - Graham Knop (cpan:HAARG) =head1 COPYRIGHT -Copyright (c) 2010-2011 the Import::Into L and L +Copyright (c) 2012 the Import::Into L and L as listed above. =head1 LICENSE