diff options
author | Chad Granum <exodist7@gmail.com> | 2016-01-24 13:58:35 -0800 |
---|---|---|
committer | Chad Granum <exodist7@gmail.com> | 2016-01-24 13:58:35 -0800 |
commit | 87fd4b4276b449697590ad02c29296ac4fefe6ee (patch) | |
tree | e283c1baa927b4664de85b74d5d84f17582c4ed3 | |
parent | 529af344acfb4c0ea75317f71dcfbfbc740a86d6 (diff) |
Make it possible to get exports without symbol table munging
-rw-r--r-- | Changes | 3 | ||||
-rw-r--r-- | lib/Importer.pm | 133 | ||||
-rw-r--r-- | t/units.t | 159 |
3 files changed, 277 insertions, 18 deletions
@@ -1,5 +1,8 @@ {{$NEXT}} + - New Feature: Custom export asignments + - New Feature: Get exports as hash/list/ref + 0.007 2016-01-23 21:22:00-08:00 America/Los_Angeles - Less warnings/strict pragmas scattered everywhere diff --git a/lib/Importer.pm b/lib/Importer.pm index 50a8e50..3cacc6e 100644 --- a/lib/Importer.pm +++ b/lib/Importer.pm @@ -69,7 +69,6 @@ sub unimport { $self->do_unimport(@_); } - sub import_into { my $class = shift; my ($from, $into, @args) = @_; @@ -145,13 +144,60 @@ sub new { ########################################################################### # +# Shortcuts for getting symbols without any namespace modifications +# + +sub get { + my $proto = shift; + my @caller = caller(1); + + my $self = ref($proto) ? $proto : $proto->new( + from => shift(@_), + caller => \@caller, + ); + + my %result; + $self->do_import($caller[0], @_, sub { $result{$_[0]} = $_[1] }); + return \%result; +} + +sub get_list { + my $proto = shift; + my @caller = caller(1); + + my $self = ref($proto) ? $proto : $proto->new( + from => shift(@_), + caller => \@caller, + ); + + my @result; + $self->do_import($caller[0], @_, sub { push @result => $_[1] }); + return @result; +} + +sub get_one { + my $proto = shift; + my @caller = caller(1); + + my $self = ref($proto) ? $proto : $proto->new( + from => shift(@_), + caller => \@caller, + ); + + my $result; + $self->do_import($caller[0], @_, sub { $result = $_[1] }); + return $result; +} + +########################################################################### +# # Object methods # sub do_import { my $self = shift; - my ($into, $versions, $exclude, $import) = $self->parse_args(@_); + my ($into, $versions, $exclude, $import, $set) = $self->parse_args(@_); # Exporter supported multiple version numbers being listed... _version_check($self->from, $self->get_caller, @$versions) if @$versions; @@ -159,7 +205,7 @@ sub do_import { return unless @$import; $self->_handle_fail($into, $import) if $self->menu($into)->{fail}; - $self->_set_symbols($into, $exclude, $import); + $self->_set_symbols($into, $exclude, $import, $set); } sub do_unimport { @@ -375,6 +421,7 @@ sub parse_args { my %exclude; my @import; my @versions; + my $set; while(my $full_arg = shift @args) { my $arg = $full_arg; @@ -386,6 +433,12 @@ sub parse_args { push @versions => $arg; next; } + elsif(ref($arg) eq 'CODE') { + $self->carp("Multiple setters specified, only 1 will be used") if $set; + $set = $arg; + unshift @args => ':DEFAULT' unless @args || @import || keys %exclude || @versions; + next; + } if ($lead eq '!') { $exc = $lead; @@ -409,7 +462,7 @@ sub parse_args { else { # If the item is followed by a reference then they are asking us to # do something special... - $spec = ref($args[0]) ? shift @args : {}; + $spec = ref($args[0]) eq 'HASH' ? shift @args : {}; } # Process the item to figure out what symbols are being touched, if it @@ -447,7 +500,7 @@ sub parse_args { } } - return ($into, \@versions, \%exclude, \@import); + return ($into, \@versions, \%exclude, \@import, $set); } sub _handle_fail { @@ -475,7 +528,7 @@ sub _handle_fail { sub _set_symbols { my $self = shift; - my ($into, $exclude, $import) = @_; + my ($into, $exclude, $import, $custom_set) = @_; my $from = $self->from; my $menu = $self->menu($into); @@ -483,7 +536,7 @@ sub _set_symbols { # Turn of strict 'refs' for the sub we generate. Doing this here instead of # in the eval is faster since it only runs once. - my $set_symbol = eval <<" EOT" || die $@; + my $set_symbol = $custom_set || eval <<" EOT" || die $@; # Inherit the callers warning settings. If they have warnings and we # redefine their subs they will hear about it. If they do not have warnings # on they will not. @@ -519,7 +572,7 @@ sub { *{"$into\\::\$_[0]"} = \$_[1] } my $new_name = join '' => ($spec->{'-prefix'} || '', $spec->{'-as'} || $name, $spec->{'-postfix'} || ''); - push @{$IMPORTED{$into}} => $new_name if $sig eq '&'; + push @{$IMPORTED{$into}} => $new_name if $sig eq '&' && !$custom_set; # Set the symbol (finally!) $set_symbol->($new_name, $ref); @@ -688,7 +741,7 @@ feature (like import renaming). =head1 IMPORT PARAMETERS - use Importer $IMPORTER_VERSION, $FROM_MODULE, $FROM_MODULE_VERSION, @SYMBOLS; + use Importer $IMPORTER_VERSION, $FROM_MODULE, $FROM_MODULE_VERSION, \&SET_SYMBOL, @SYMBOLS; =over 4 @@ -708,6 +761,25 @@ symbols from. Any numeric argument following the C<$FROM_MODULE> will be treated as a version check against C<$FROM_MODULE>. +=item \&SET_SYMBOL (optional) + +Normally Importer will put the exports into your namespace. This is usually +done via a more complex form of C<*name = $ref>. If you do NOT want this to +happen then you can provide a custom sub to handle the assignment. + +This is an example that uses this feature to put all the exports into a lexical +hash instead of modifying the namespace (This is how the C<get()> method is +implemented). + + my %CARP; + use Importer Carp => sub { + my ($name, $ref) = @_; + $CARP{$name} = $ref; + }; + + $CARP{cluck}->("This will cluck"); + $CARP{croak}->("This will croak"); + =item @SYMBOLS (optional) Symbols you wish to import. If no symbols are specified then the defaults will @@ -763,6 +835,11 @@ Using this syntax to set prefix and/or postfix also works on tags and patterns that are specified for import, in which case the prefix/postfix is applied to all symbols from the tag/patterm. +=head2 CUSTOM EXPORT ASSIGNMENT + +This lets you provide an alternative to the C<*name = $ref> export assingment. +See the list of L<parameters|/"IMPORT PARAMETERS"> to C<import()> + =head2 UNIMPORTING See L</UNIMPORT PARAMETERS>. @@ -896,6 +973,26 @@ This is the magic behind C<no Importer ...>. This lets you remove imported symbols from C<$from>. C<$from> my be a package name, or a caller level. +=item my $exports = Importer->get($from, @imports) + +This returns hashref of C<< { $name => $ref } >> for all the specified imports. + +C<$from> should be the package from which to get the exports. + +=item my @export_refs = Importer->get_list($from, @imports) + +This returns a list of references for each import specified. Only the export +references are returned, the names are not. + +C<$from> should be the package from which to get the exports. + +=item $export_ref = Importer->get_one($from, $import) + +This returns a single reference to a single export. If you provide multiple +imports then only the LAST one will be used. + +C<$from> should be the package from which to get the exports. + =back =head1 USING WITH OTHER EXPORTER IMPLEMENTATIONS @@ -1003,9 +1100,9 @@ C<do_unimport()>. For import 'from' us used as the origin, in unimport it is used as the target. This means you cannot re-use an instance to import and then unimport. -=item ($into, $versions, $exclude, $symbols) = $imp->parse_args('Dest::Package') +=item ($into, $versions, $exclude, $symbols, $set) = $imp->parse_args('Dest::Package') -=item ($into, $versions, $exclude, $symbols) = $imp->parse_args('Dest::Package', @symbols) +=item ($into, $versions, $exclude, $symbols, $set) = $imp->parse_args('Dest::Package', @symbols) This parses arguments. The first argument must be the destination package. Other arguments can be a mix of symbol names, tags, patterns, version numbers, @@ -1077,6 +1174,20 @@ The menu structure looks like this: This will reload the export menu from the C<from> package. +=item my $exports = $imp->get(@imports) + +This returns hashref of C<< { $name => $ref } >> for all the specified imports. + +=item my @export_refs = $imp->get_list(@imports) + +This returns a list of references for each import specified. Only the export +references are returned, the names are not. + +=item $export_ref = $imp->get_one($import) + +This returns a single reference to a single export. If you provide multiple +imports then only the LAST one will be used. + =back =head1 SOURCE @@ -32,6 +32,25 @@ sub warns(&) { return $warn || $warned; } + +### +# These cannot be inside a subtest because of the lexical scopeing +{ + my %CARP; + use Importer Carp => qw/croak confess/, sub { + my ($name, $ref) = @_; + $CARP{$name} = $ref; + }; + is_deeply( + \%CARP, + { + croak => \&Carp::croak, + confess => \&Carp::confess, + }, + "Exports went into the hash." + ); +} + subtest _version_check => sub { my $version_check = $CLASS->can('_version_check'); @@ -514,11 +533,45 @@ subtest parse_args => sub { ['$ZAP', {}], ['%ZAP', {}], ['@ZAP', {}], - ] + ], + undef, ], "Got defaults with empty list" ); + my $set = sub { 1 }; + is_deeply( + [$one->parse_args('Dest', $set)], + [ + 'Dest', + [], + {}, + [ + ['&foo', {}], + ['&bar', {}], + ['$ZAP', {}], + ['%ZAP', {}], + ['@ZAP', {}], + ], + $set, + ], + "Got defaults with empty list + custom setter" + ); + + is_deeply( + [$one->parse_args('Dest', 'foo', $set)], + [ + 'Dest', + [], + {}, + [ + ['&foo', {}], + ], + $set, + ], + "Got defaults with 1 item + custom setter" + ); + is_deeply( [$one->parse_args('Dest', '!bar')], [ @@ -531,7 +584,8 @@ subtest parse_args => sub { ['$ZAP', {}], ['%ZAP', {}], ['@ZAP', {}], - ] + ], + undef, ], "Got defaults, exclude bar" ); @@ -548,7 +602,8 @@ subtest parse_args => sub { ['$ZAP', {}], ['%ZAP', {}], ['@ZAP', {}], - ] + ], + undef, ], "Got defaults, exclude bar" ); @@ -565,7 +620,8 @@ subtest parse_args => sub { ['$ZAP', {}], ['%ZAP', {}], ['@ZAP', {}], - ] + ], + undef, ], "Got defaults, exclude :b" ); @@ -581,7 +637,8 @@ subtest parse_args => sub { ['&baz', {-prefix => 'foo_'}], ['&x', {}], ['&y', {}], - ] + ], + undef, ], "Spec for tag" ); @@ -596,7 +653,8 @@ subtest parse_args => sub { ['$ZAP', {-postfix => '_foo'}], ['%ZAP', {-postfix => '_foo'}], ['@ZAP', {-postfix => '_foo'}], - ] + ], + undef, ], "Spec for pattern" ); @@ -611,7 +669,8 @@ subtest parse_args => sub { ['$ZAP', {-postfix => '_foo'}], ['%ZAP', {-postfix => '_foo'}], ['@ZAP', {-postfix => '_foo'}], - ] + ], + undef, ], "Spec for qr// (also test version)" ); @@ -914,4 +973,90 @@ subtest _optimal_import => sub { ok(!FDestD->can('foo'), "Removed 'foo'"); }; +subtest get => sub { + is_deeply( + Importer->get(Carp => qw/croak confess/), + { + croak => \&Carp::croak, + confess => \&Carp::confess, + }, + "Exports went into the hash (class)." + ); + + is_deeply( + Importer->new(from => 'Carp')->get(qw/croak confess/), + { + croak => \&Carp::croak, + confess => \&Carp::confess, + }, + "Exports went into the hash (instance)." + ); +}; + +subtest get_list => sub { + is_deeply( + [Importer->get_list(Carp => qw/croak confess/)], + [ \&Carp::croak, \&Carp::confess ], + "list of refs (class)." + ); + + is_deeply( + [Importer->new(from => 'Carp')->get_list(qw/croak confess/)], + [ \&Carp::croak, \&Carp::confess ], + "list of refs (instance)." + ); +}; + +subtest get_one => sub { + is_deeply( + Importer->get_one(Carp => qw/confess/), + \&Carp::confess, + "one ref (class)." + ); + + is_deeply( + Importer->new(from => 'Carp')->get_one(qw/croak/), + \&Carp::croak, + "one ref (instance)." + ); + + is_deeply( + Importer->get_one(Carp => qw/confess croak/), + \&Carp::croak, + "one ref (last)." + ); +}; + done_testing; + +__END__ + +sub get_list { + my $proto = shift; + my @caller = caller(1); + + my $self = ref($proto) ? $proto : $proto->new( + from => shift(@_), + caller => \@caller, + ); + + my @result; + $self->do_import($caller[0], @_, sub { push @result => $_[1] }); + return @result; +} + +sub get_one { + my $proto = shift; + my @caller = caller(1); + + my $self = ref($proto) ? $proto : $proto->new( + from => shift(@_), + caller => \@caller, + ); + + my $result; + $self->do_import($caller[0], @_, sub { $result = $_[1] }); + return $result; +} + + |