our
$VERSION
=
'1.302210'
;
my
%SIG_TO_SLOT
= (
'&'
=>
'CODE'
,
'$'
=>
'SCALAR'
,
'%'
=>
'HASH'
,
'@'
=>
'ARRAY'
,
'*'
=>
'GLOB'
,
);
our
%IMPORTED
;
my
%NUMERIC
=
map
+(
$_
=> 1), 0 .. 9;
sub
IMPORTER_MENU() {
return
(
export_ok
=> [
qw/optimal_import/
],
export_anon
=> {
import
=>
sub
{
my
$from
=
shift
;
my
@caller
=
caller
(0);
_version_check(
$from
, \
@caller
,
shift
@_
)
if
@_
&&
$NUMERIC
{
substr
(
$_
[0], 0, 1)};
my
$file
= _mod_to_file(
$from
);
_load_file(\
@caller
,
$file
)
unless
$INC
{
$file
};
return
if
optimal_import(
$from
,
$caller
[0], \
@caller
,
@_
);
my
$self
= __PACKAGE__->new(
from
=>
$from
,
caller
=> \
@caller
,
);
$self
->do_import(
$caller
[0],
@_
);
},
},
);
}
sub
import
{
my
$class
=
shift
;
my
@caller
=
caller
(0);
_version_check(
$class
, \
@caller
,
shift
@_
)
if
@_
&&
$NUMERIC
{
substr
(
$_
[0], 0, 1)};
return
unless
@_
;
my
(
$from
,
@args
) =
@_
;
my
$file
= _mod_to_file(
$from
);
_load_file(\
@caller
,
$file
)
unless
$INC
{
$file
};
return
if
optimal_import(
$from
,
$caller
[0], \
@caller
,
@args
);
my
$self
=
$class
->new(
from
=>
$from
,
caller
=> \
@caller
,
);
$self
->do_import(
$caller
[0],
@args
);
}
sub
unimport {
my
$class
=
shift
;
my
@caller
=
caller
(0);
my
$self
=
$class
->new(
from
=>
$caller
[0],
caller
=> \
@caller
,
);
$self
->do_unimport(
@_
);
}
sub
import_into {
my
$class
=
shift
;
my
(
$from
,
$into
,
@args
) =
@_
;
my
@caller
;
if
(
ref
(
$into
)) {
@caller
=
@$into
;
$into
=
$caller
[0];
}
elsif
(
$into
=~ m/^\d+$/) {
@caller
=
caller
(
$into
+ 1);
$into
=
$caller
[0];
}
else
{
@caller
=
caller
(0);
}
my
$file
= _mod_to_file(
$from
);
_load_file(\
@caller
,
$file
)
unless
$INC
{
$file
};
return
if
optimal_import(
$from
,
$into
, \
@caller
,
@args
);
my
$self
=
$class
->new(
from
=>
$from
,
caller
=> \
@caller
,
);
$self
->do_import(
$into
,
@args
);
}
sub
unimport_from {
my
$class
=
shift
;
my
(
$from
,
@args
) =
@_
;
my
@caller
;
if
(
$from
=~ m/^\d+$/) {
@caller
=
caller
(
$from
+ 1);
$from
=
$caller
[0];
}
else
{
@caller
=
caller
(0);
}
my
$self
=
$class
->new(
from
=>
$from
,
caller
=> \
@caller
,
);
$self
->do_unimport(
@args
);
}
sub
new {
my
$class
=
shift
;
my
%params
=
@_
;
my
$caller
=
$params
{
caller
} || [
caller
()];
die
"You must specify a package to import from at $caller->[1] line $caller->[2].\n"
unless
$params
{from};
return
bless
{
from
=>
$params
{from},
caller
=>
$params
{
caller
},
},
$class
;
}
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
;
}
sub
do_import {
my
$self
=
shift
;
my
(
$into
,
$versions
,
$exclude
,
$import
,
$set
) =
$self
->parse_args(
@_
);
_version_check(
$self
->from,
$self
->get_caller,
@$versions
)
if
@$versions
;
return
unless
@$import
;
$self
->_handle_fail(
$into
,
$import
)
if
$self
->menu(
$into
)->{fail};
$self
->_set_symbols(
$into
,
$exclude
,
$import
,
$set
);
}
sub
do_unimport {
my
$self
=
shift
;
my
$from
=
$self
->from;
my
$imported
=
$IMPORTED
{
$from
} or
$self
->croak(
"'$from' does not have any imports to remove"
);
my
%allowed
=
map
{
$_
=> 1 }
@$imported
;
my
@args
=
@_
?
@_
:
@$imported
;
my
$stash
= \%{
"$from\::"
};
for
my
$name
(
@args
) {
$name
=~ s/^&//;
$self
->croak(
"Sub '$name' was not imported using "
.
ref
(
$self
))
unless
$allowed
{
$name
};
my
$glob
=
delete
$stash
->{
$name
};
local
*GLOBCLONE
=
*$glob
;
for
my
$type
(
qw/SCALAR HASH ARRAY FORMAT IO/
) {
next
unless
defined
(*{
$glob
}{
$type
});
*{
"$from\::$name"
} = *{
$glob
}{
$type
}
}
}
}
sub
from {
$_
[0]->{from} }
sub
from_file {
my
$self
=
shift
;
$self
->{from_file} ||= _mod_to_file(
$self
->{from});
return
$self
->{from_file};
}
sub
load_from {
my
$self
=
shift
;
my
$from_file
=
$self
->from_file;
my
$this_file
= __FILE__;
return
if
$INC
{
$from_file
};
my
$caller
=
$self
->get_caller;
_load_file(
$caller
,
$from_file
);
}
sub
get_caller {
my
$self
=
shift
;
return
$self
->{
caller
}
if
$self
->{
caller
};
my
$level
= 1;
while
(
my
@caller
=
caller
(
$level
++)) {
return
\
@caller
if
@caller
&& !
$caller
[0]->isa(__PACKAGE__);
last
unless
@caller
;
}
return
[
caller
(0)];
}
sub
croak {
my
$self
=
shift
;
my
(
$msg
) =
@_
;
my
$caller
=
$self
->get_caller;
my
$file
=
$caller
->[1] ||
'unknown file'
;
my
$line
=
$caller
->[2] ||
'unknown line'
;
die
"$msg at $file line $line.\n"
;
}
sub
carp {
my
$self
=
shift
;
my
(
$msg
) =
@_
;
my
$caller
=
$self
->get_caller;
my
$file
=
$caller
->[1] ||
'unknown file'
;
my
$line
=
$caller
->[2] ||
'unknown line'
;
warn
"$msg at $file line $line.\n"
;
}
sub
menu {
my
$self
=
shift
;
my
(
$into
) =
@_
;
$self
->croak(
"menu() requires the name of the destination package"
)
unless
$into
;
my
$for
=
$self
->{menu_for};
delete
$self
->{menu}
if
$for
&&
$for
ne
$into
;
return
$self
->{menu} ||
$self
->reload_menu(
$into
);
}
sub
reload_menu {
my
$self
=
shift
;
my
(
$into
) =
@_
;
$self
->croak(
"reload_menu() requires the name of the destination package"
)
unless
$into
;
my
$from
=
$self
->from;
if
(
my
$menu_sub
= *{
"$from\::IMPORTER_MENU"
}{CODE}) {
my
%got
=
$from
->
$menu_sub
(
$into
,
$self
->get_caller);
$got
{export} ||= [];
$got
{export_ok} ||= [];
$got
{export_tags} ||= {};
$got
{export_fail} ||= [];
$got
{export_anon} ||= {};
$got
{export_magic} ||= {};
$self
->croak(
"'$from' provides both 'generate' and 'export_gen' in its IMPORTER_MENU (They are exclusive, module must pick 1)"
)
if
$got
{export_gen} &&
$got
{generate};
$got
{export_gen} ||= {};
$self
->{menu} =
$self
->_build_menu(
$into
=> \
%got
, 1);
}
else
{
my
%got
;
$got
{export} = \@{
"$from\::EXPORT"
};
$got
{export_ok} = \@{
"$from\::EXPORT_OK"
};
$got
{export_tags} = \%{
"$from\::EXPORT_TAGS"
};
$got
{export_fail} = \@{
"$from\::EXPORT_FAIL"
};
$got
{export_gen} = \%{
"$from\::EXPORT_GEN"
};
$got
{export_anon} = \%{
"$from\::EXPORT_ANON"
};
$got
{export_magic} = \%{
"$from\::EXPORT_MAGIC"
};
$self
->{menu} =
$self
->_build_menu(
$into
=> \
%got
, 0);
}
$self
->{menu_for} =
$into
;
return
$self
->{menu};
}
sub
_build_menu {
my
$self
=
shift
;
my
(
$into
,
$got
,
$new_style
) =
@_
;
my
$from
=
$self
->from;
my
$export
=
$got
->{export} || [];
my
$export_ok
=
$got
->{export_ok} || [];
my
$export_tags
=
$got
->{export_tags} || {};
my
$export_fail
=
$got
->{export_fail} || [];
my
$export_anon
=
$got
->{export_anon} || {};
my
$export_gen
=
$got
->{export_gen} || {};
my
$export_magic
=
$got
->{export_magic} || {};
my
$generate
=
$got
->{generate};
$generate
||=
sub
{
my
$symbol
=
shift
;
my
(
$sig
,
$name
) = (
$symbol
=~ m/^(\W?)(.*)$/);
$sig
||=
'&'
;
my
$do
=
$export_gen
->{
"${sig}${name}"
};
$do
||=
$export_gen
->{
$name
}
if
!
$sig
||
$sig
eq
'&'
;
return
undef
unless
$do
;
$from
->
$do
(
$into
,
$symbol
);
}
if
$export_gen
&&
keys
%$export_gen
;
my
$lookup
= {};
my
$exports
= {};
for
my
$sym
(
@$export
,
@$export_ok
,
keys
%$export_gen
,
keys
%$export_anon
) {
my
(
$sig
,
$name
) = (
$sym
=~ m/^(\W?)(.*)$/);
$sig
||=
'&'
;
$lookup
->{
"${sig}${name}"
} = 1;
$lookup
->{
$name
} = 1
if
$sig
eq
'&'
;
next
if
$export_gen
->{
"${sig}${name}"
};
next
if
$sig
eq
'&'
&&
$export_gen
->{
$name
};
next
if
$got
->{generate} &&
$generate
->(
"${sig}${name}"
);
my
$fqn
=
"$from\::$name"
;
$exports
->{
"${sig}${name}"
} =
$export_anon
->{
$sym
} || (
$sig
eq
'&'
? \&{
$fqn
} :
$sig
eq
'$'
? \${
$fqn
} :
$sig
eq
'@'
? \@{
$fqn
} :
$sig
eq
'%'
? \%{
$fqn
} :
$sig
eq
'*'
? \*{
$fqn
} :
next
);
}
my
$f_import
=
$new_style
||
$from
->can(
'import'
);
$self
->croak(
"'$from' does not provide any exports"
)
unless
$new_style
||
keys
%$exports
||
$from
->isa(
'Exporter'
)
|| (
$INC
{
'Exporter.pm'
} &&
$f_import
&&
$f_import
== \
&Exporter::import
);
my
$tags
= {
%$export_tags
,
'DEFAULT'
=> [
@$export
],
};
$tags
->{ALL} ||= [
sort
grep
{m/^[\&\$\@\%\*]/}
keys
%$lookup
];
my
$fail
=
@$export_fail
? {
map
{
my
(
$sig
,
$name
) = (m/^(\W?)(.*)$/);
$sig
||=
'&'
;
(
"${sig}${name}"
=> 1,
$sig
eq
'&'
? (
$name
=> 1) : ())
}
@$export_fail
} :
undef
;
my
$menu
= {
lookup
=>
$lookup
,
exports
=>
$exports
,
tags
=>
$tags
,
fail
=>
$fail
,
generate
=>
$generate
,
magic
=>
$export_magic
,
};
return
$menu
;
}
sub
parse_args {
my
$self
=
shift
;
my
(
$into
,
@args
) =
@_
;
my
$menu
=
$self
->menu(
$into
);
my
@out
=
$self
->_parse_args(
$into
,
$menu
, \
@args
);
pop
@out
;
return
@out
;
}
sub
_parse_args {
my
$self
=
shift
;
my
(
$into
,
$menu
,
$args
,
$is_tag
) =
@_
;
my
$from
=
$self
->from;
my
$main_menu
=
$self
->menu(
$into
);
$menu
||=
$main_menu
;
my
@sets
;
my
@versions
;
my
@leftover
;
for
my
$arg
(
@$args
) {
no
warnings
'void'
;
push
@sets
=>
$arg
and
next
if
ref
(
$arg
) eq
'CODE'
;
push
@versions
=>
$arg
xor
next
if
$NUMERIC
{
substr
(
$arg
, 0, 1)};
push
@leftover
=>
$arg
;
}
$self
->carp(
"Multiple setters specified, only 1 will be used"
)
if
@sets
> 1;
my
$set
=
pop
@sets
;
$args
= \
@leftover
;
@$args
= (
':DEFAULT'
)
unless
$is_tag
||
@$args
||
@versions
;
my
%exclude
;
my
@import
;
while
(
my
$full_arg
=
shift
@$args
) {
my
$arg
=
$full_arg
;
my
$lead
=
substr
(
$arg
, 0, 1);
my
(
$spec
,
$exc
);
if
(
$lead
eq
'!'
) {
$exc
=
$lead
;
if
(
$arg
eq
'!'
) {
$arg
=
shift
@$args
;
}
else
{
substr
(
$arg
, 0, 1,
''
);
}
unshift
@$args
=>
':DEFAULT'
unless
@import
||
keys
%exclude
||
@versions
;
$lead
=
substr
(
$arg
, 0, 1);
}
else
{
$spec
=
ref
(
$args
->[0]) eq
'HASH'
?
shift
@$args
: {};
}
if
(
$lead
eq
':'
) {
substr
(
$arg
, 0, 1,
''
);
my
$tag
=
$menu
->{tags}->{
$arg
} or
$self
->croak(
"$from does not export the :$arg tag"
);
my
(
undef
,
$cvers
,
$cexc
,
$cimp
,
$cset
,
$newmenu
) =
$self
->_parse_args(
$into
,
$menu
,
$tag
,
$arg
);
$self
->croak(
"Exporter specified version numbers ("
.
join
(
', '
,
@$cvers
) .
") in the :$arg tag!"
)
if
@$cvers
;
$self
->croak(
"Exporter specified a custom symbol setter in the :$arg tag!"
)
if
$cset
;
%exclude
= (
%exclude
,
%$cexc
);
if
(
$exc
) {
$exclude
{
$_
} = 1
for
grep
{!
ref
(
$_
) &&
substr
(
$_
, 0, 1) ne
'+'
}
map
{
$_
->[0]}
@$cimp
;
}
elsif
(
$spec
&&
keys
%$spec
) {
$self
->croak(
"Cannot use '-as' to rename multiple symbols included by: $full_arg"
)
if
$spec
->{
'-as'
} &&
@$cimp
> 1;
for
my
$set
(
@$cimp
) {
my
(
$sym
,
$cspec
) =
@$set
;
my
$nspec
= {
%$cspec
,
%$spec
};
$nspec
->{
'-prefix'
} =
"$spec->{'-prefix'}$cspec->{'-prefix'}"
if
$spec
->{
'-prefix'
} &&
$cspec
->{
'-prefix'
};
$nspec
->{
'-postfix'
} =
"$cspec->{'-postfix'}$spec->{'-postfix'}"
if
$spec
->{
'-postfix'
} &&
$cspec
->{
'-postfix'
};
push
@import
=> [
$sym
,
$nspec
];
}
}
else
{
push
@import
=>
@$cimp
;
}
$menu
=
$newmenu
;
next
;
}
my
@list
;
if
(
ref
(
$arg
) eq
'Regexp'
) {
@list
=
sort
grep
/
$arg
/,
keys
%{
$menu
->{lookup}};
}
elsif
(
$lead
eq
'/'
&&
$arg
=~ m{^/(.*)/$}) {
my
$pattern
= $1;
@list
=
sort
grep
/$1/,
keys
%{
$menu
->{lookup}};
}
else
{
@list
= (
$arg
);
}
@list
=
map
{m/^\W/ ?
$_
:
"\&$_"
}
@list
;
if
(
$exc
) {
$exclude
{
$_
} = 1
for
@list
;
}
else
{
$self
->croak(
"Cannot use '-as' to rename multiple symbols included by: $full_arg"
)
if
$spec
->{
'-as'
} &&
@list
> 1;
push
@import
=> [
$_
,
$spec
]
for
@list
;
}
}
return
(
$into
, \
@versions
, \
%exclude
, \
@import
,
$set
,
$menu
);
}
sub
_handle_fail {
my
$self
=
shift
;
my
(
$into
,
$import
) =
@_
;
my
$from
=
$self
->from;
my
$menu
=
$self
->menu(
$into
);
my
@fail
=
map
{
my
$x
=
$_
->[0];
$x
=~ s/^&//;
$x
}
grep
$menu
->{fail}->{
$_
->[0]},
@$import
or
return
;
my
@real_fail
=
$from
->can(
'export_fail'
) ?
$from
->export_fail(
@fail
) :
@fail
;
if
(
@real_fail
) {
$self
->carp(
qq["$_" is not implemented by the $from module on this architecture]
)
for
@real_fail
;
$self
->croak(
"Can't continue after import errors"
);
}
$self
->reload_menu(
$menu
);
return
;
}
sub
_set_symbols {
my
$self
=
shift
;
my
(
$into
,
$exclude
,
$import
,
$custom_set
) =
@_
;
my
$from
=
$self
->from;
my
$menu
=
$self
->menu(
$into
);
my
$caller
=
$self
->get_caller();
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.
BEGIN { \${^WARNING_BITS} = \$caller->[9] if defined \$caller->[9] }
#line $caller->[2] "$caller->[1]"
sub { *{"$into\\::\$_[0]"} = \$_[1] }
EOT
for
my
$set
(
@$import
) {
my
(
$symbol
,
$spec
) =
@$set
;
my
(
$sig
,
$name
) = (
$symbol
=~ m/^(\W)(.*)$/) or
die
"Invalid symbol: $symbol"
;
my
$ref
=
$menu
->{exports}->{
$symbol
};
$ref
||=
$menu
->{generate}->(
$symbol
)
if
$menu
->{generate};
$self
->croak(
"$from does not export $symbol"
)
unless
$ref
||
$menu
->{lookup}->{
"${sig}${name}"
};
next
unless
$ref
;
my
$type
=
ref
(
$ref
);
$type
=
'SCALAR'
if
$type
eq
'REF'
;
$self
->croak(
"Symbol '$sig$name' requested, but reference ("
.
ref
(
$ref
) .
") does not match sigil ($sig)"
)
if
$ref
&&
$type
ne
$SIG_TO_SLOT
{
$sig
};
next
if
$exclude
->{
"${sig}${name}"
} && !
$spec
->{
'-as'
};
my
$new_name
=
join
''
=> (
$spec
->{
'-prefix'
} ||
''
,
$spec
->{
'-as'
} ||
$name
,
$spec
->{
'-postfix'
} ||
''
);
$set_symbol
->(
$new_name
,
$ref
,
sig
=>
$sig
,
symbol
=>
$symbol
,
into
=>
$into
,
from
=>
$from
,
spec
=>
$spec
);
next
if
$custom_set
;
push
@{
$IMPORTED
{
$into
}} =>
$new_name
if
$sig
eq
'&'
;
my
$magic
=
$menu
->{magic}->{
$symbol
};
$magic
||=
$menu
->{magic}->{
$name
}
if
$sig
eq
'&'
;
$from
->
$magic
(
into
=>
$into
,
orig_name
=>
$name
,
new_name
=>
$new_name
,
ref
=>
$ref
)
if
$magic
;
}
}
sub
_version_check {
my
(
$mod
,
$caller
,
@versions
) =
@_
;
eval
<<" EOT" or die $@;
#line $caller->[2] "$caller->[1]"
\$mod->VERSION(\$_) for \@versions;
1;
EOT
}
sub
_mod_to_file {
my
$file
=
shift
;
$file
=~ s{::}{/}g;
$file
.=
'.pm'
;
return
$file
;
}
sub
_load_file {
my
(
$caller
,
$file
) =
@_
;
eval
<<" EOT" || die $@;
#line $caller->[2] "$caller->[1]"
require \$file;
EOT
}
my
%HEAVY_VARS
= (
IMPORTER_MENU
=>
'CODE'
,
EXPORT_FAIL
=>
'ARRAY'
,
EXPORT_GEN
=>
'HASH'
,
EXPORT_ANON
=>
'HASH'
,
EXPORT_MAGIC
=>
'HASH'
,
);
sub
optimal_import {
my
(
$from
,
$into
,
$caller
,
@args
) =
@_
;
defined
(*{
"$from\::$_"
}{
$HEAVY_VARS
{
$_
}}) and
return
0
for
keys
%HEAVY_VARS
;
@args
= @{
"$from\::EXPORT"
}
unless
@args
;
my
%allowed
=
map
+(
substr
(
$_
, 0, 1) eq
'&'
?
substr
(
$_
, 1) :
$_
=> 1),
@{
"$from\::EXPORT"
}, @{
"$from\::EXPORT_OK"
};
my
%final
=
map
+(
(!
ref
(
$_
) && (
$allowed
{
$_
} || (
substr
(
$_
, 0, 1,
""
) eq
'&'
&&
$allowed
{
$_
})))
? (
$_
=> *{
"$from\::$_"
}{CODE} ||
return
0)
:
return
0
),
@args
;
eval
<<" EOT" || die $@;
# If the caller has redefine warnings enabled then we want to warn them if
# their import redefines things.
BEGIN { \${^WARNING_BITS} = \$caller->[9] if defined \$caller->[9] };
#line $caller->[2] "$caller->[1]"
(*{"$into\\::\$_"} = \$final{\$_}, push \@{\$Test2::Util::Importer::IMPORTED{\$into}} => \$_) for keys %final;
1;
EOT
}
1;