our
$VERSION
=
'2.002004'
;
$VERSION
=~
tr
/_//d;
our
%INFO
;
our
%APPLIED_TO
;
our
%COMPOSED
;
our
%COMPOSITE_INFO
;
our
@ON_ROLE_CREATE
;
BEGIN {
*_WORK_AROUND_BROKEN_MODULE_STATE
=
"$]"
< 5.009 ?
sub
(){1} :
sub
(){0};
*_WORK_AROUND_HINT_LEAKAGE
=
"$]"
< 5.011 && !(
"$]"
>= 5.009004 &&
"$]"
< 5.010001)
?
sub
(){1} :
sub
(){0};
*_CONSTANTS_DEFLATE
=
"$]"
>= 5.012 &&
"$]"
< 5.020 ?
sub
(){1} :
sub
(){0};
}
sub
_getglob {
no
strict
'refs'
; \*{
$_
[0]} }
sub
_getstash {
no
strict
'refs'
; \%{
"$_[0]::"
} }
sub
croak {
no
warnings
'redefine'
;
*croak
= \
&Carp::croak
;
goto
&Carp::croak
;
}
sub
Role::Tiny::__GUARD__::DESTROY {
delete
$INC
{
$_
[0]->[0]}
if
@{
$_
[0]};
}
sub
_load_module {
my
(
$module
) =
@_
;
(
my
$file
=
"$module.pm"
) =~ s{::}{/}g;
return
1
if
$INC
{
$file
};
return
1
if
grep
!/::\z/,
keys
%{_getstash(
$module
)};
my
$guard
= _WORK_AROUND_BROKEN_MODULE_STATE
&&
bless
([
$file
],
'Role::Tiny::__GUARD__'
);
local
%^H
if
_WORK_AROUND_HINT_LEAKAGE;
require
$file
;
pop
@$guard
if
_WORK_AROUND_BROKEN_MODULE_STATE;
return
1;
}
sub
_require_module {
_load_module(
$_
[1]);
}
sub
_all_subs {
my
(
$me
,
$package
) =
@_
;
my
$stash
= _getstash(
$package
);
return
{
map
{;
no
strict
'refs'
;
${
"${package}::${_}"
} = ${
"${package}::${_}"
}
if
_CONSTANTS_DEFLATE;
$_
=> \&{
"${package}::${_}"
}
}
grep
exists
&{
"${package}::${_}"
},
grep
!/::\z/,
keys
%$stash
};
}
sub
import
{
my
$target
=
caller
;
my
$me
=
shift
;
strict->
import
;
warnings->
import
;
my
$non_methods
=
$me
->_non_methods(
$target
);
$me
->_install_subs(
$target
,
@_
);
$me
->make_role(
$target
);
$me
->_mark_new_non_methods(
$target
,
$non_methods
)
if
$non_methods
&&
%$non_methods
;
return
;
}
sub
_mark_new_non_methods {
my
(
$me
,
$target
,
$old_non_methods
) =
@_
;
my
$non_methods
=
$INFO
{
$target
}{non_methods};
my
$subs
=
$me
->_all_subs(
$target
);
for
my
$sub
(
keys
%$subs
) {
if
(
exists
$old_non_methods
->{
$sub
} &&
$non_methods
->{
$sub
} !=
$subs
->{
$sub
} ) {
$non_methods
->{
$sub
} =
$subs
->{
$sub
};
}
}
return
;
}
sub
make_role {
my
(
$me
,
$target
) =
@_
;
return
if
$me
->is_role(
$target
);
$INFO
{
$target
}{is_role} = 1;
my
$non_methods
=
$me
->_all_subs(
$target
);
delete
@{
$non_methods
}{
grep
/\A\(/,
keys
%$non_methods
};
$INFO
{
$target
}{non_methods} =
$non_methods
;
$APPLIED_TO
{
$target
} = {
$target
=>
undef
};
foreach
my
$hook
(
@ON_ROLE_CREATE
) {
$hook
->(
$target
);
}
}
sub
_install_subs {
my
(
$me
,
$target
) =
@_
;
return
if
$me
->is_role(
$target
);
my
%install
=
$me
->_gen_subs(
$target
);
*{_getglob(
"${target}::${_}"
)} =
$install
{
$_
}
for
sort
keys
%install
;
return
;
}
sub
_gen_subs {
my
(
$me
,
$target
) =
@_
;
(
(
map
{;
my
$type
=
$_
;
$type
=>
sub
{
my
$code
=
pop
;
my
@names
=
ref
$_
[0] eq
'ARRAY'
? @{
$_
[0] } :
@_
;
push
@{
$INFO
{
$target
}{modifiers}||=[]}, [
$type
,
@names
,
$code
];
return
;
};
}
qw(before after around)
),
requires
=>
sub
{
push
@{
$INFO
{
$target
}{requires}||=[]},
@_
;
return
;
},
with
=>
sub
{
$me
->apply_roles_to_package(
$target
,
@_
);
return
;
},
);
}
sub
role_application_steps {
qw(
_install_methods
_check_requires
_install_modifiers
_copy_applied_list
)
;
}
sub
_copy_applied_list {
my
(
$me
,
$to
,
$role
) =
@_
;
@{
$APPLIED_TO
{
$to
}||={}}{
keys
%{
$APPLIED_TO
{
$role
}}} = ();
}
sub
apply_roles_to_object {
my
(
$me
,
$object
,
@roles
) =
@_
;
my
$class
=
ref
(
$object
);
bless
(
$_
[1],
$me
->create_class_with_roles(
$class
,
@roles
));
}
my
$role_suffix
=
'A000'
;
sub
_composite_name {
my
(
$me
,
$superclass
,
@roles
) =
@_
;
my
$new_name
=
$superclass
.
'__WITH__'
.
join
'__AND__'
,
@roles
;
if
(
length
(
$new_name
) > 252) {
$new_name
=
$COMPOSED
{abbrev}{
$new_name
} ||=
do
{
my
$abbrev
=
substr
$new_name
, 0, 250 -
length
$role_suffix
;
$abbrev
=~ s/(?<!:):$//;
$abbrev
.
'__'
.
$role_suffix
++;
};
}
return
$new_name
;
}
sub
create_class_with_roles {
my
(
$me
,
$superclass
,
@roles
) =
@_
;
$me
->_require_module(
$superclass
);
$me
->_check_roles(
@roles
);
my
$new_name
=
$me
->_composite_name(
$superclass
,
@roles
);
return
$new_name
if
$COMPOSED
{class}{
$new_name
};
return
$me
->_build_class_with_roles(
$new_name
,
$superclass
,
@roles
);
}
sub
_build_class_with_roles {
my
(
$me
,
$new_name
,
$superclass
,
@roles
) =
@_
;
$COMPOSED
{base}{
$new_name
} =
$superclass
;
@{*{_getglob(
"${new_name}::ISA"
)}} = (
$superclass
);
$me
->apply_roles_to_package(
$new_name
,
@roles
);
$COMPOSED
{class}{
$new_name
} = 1;
return
$new_name
;
}
sub
_check_roles {
my
(
$me
,
@roles
) =
@_
;
croak
"No roles supplied!"
unless
@roles
;
my
%seen
;
if
(
my
@dupes
=
grep
1 ==
$seen
{
$_
}++,
@roles
) {
croak
"Duplicated roles: "
.
join
(
', '
,
@dupes
);
}
foreach
my
$role
(
@roles
) {
$me
->_require_module(
$role
);
croak
"${role} is not a ${me}"
unless
$me
->is_role(
$role
);
}
}
our
%BACKCOMPAT_HACK
;
$BACKCOMPAT_HACK
{+__PACKAGE__} = 0;
sub
_want_backcompat_hack {
my
$me
=
shift
;
return
$BACKCOMPAT_HACK
{
$me
}
if
exists
$BACKCOMPAT_HACK
{
$me
};
no
warnings
'uninitialized'
;
$BACKCOMPAT_HACK
{
$me
} =
$me
->can(
'apply_single_role_to_package'
) != \
&apply_single_role_to_package
&&
$me
->can(
'role_application_steps'
) == \
&role_application_steps
}
our
$IN_APPLY_ROLES
;
sub
apply_single_role_to_package {
return
if
$IN_APPLY_ROLES
;
local
$IN_APPLY_ROLES
= 1;
my
(
$me
,
$to
,
$role
) =
@_
;
$me
->apply_roles_to_package(
$to
,
$role
);
}
sub
apply_role_to_package {
my
(
$me
,
$to
,
$role
) =
@_
;
$me
->apply_roles_to_package(
$to
,
$role
);
}
sub
apply_roles_to_package {
my
(
$me
,
$to
,
@roles
) =
@_
;
croak
"Can't apply roles to object with apply_roles_to_package"
if
ref
$to
;
$me
->_check_roles(
@roles
);
my
@have_conflicts
;
my
%role_methods
;
if
(
@roles
> 1) {
my
%conflicts
= %{
$me
->_composite_info_for(
@roles
)->{conflicts}};
@have_conflicts
=
grep
$to
->can(
$_
),
keys
%conflicts
;
delete
@conflicts
{
@have_conflicts
};
if
(
keys
%conflicts
) {
my
$class
=
$COMPOSED
{base}{
$to
} ||
$to
;
my
$fail
=
join
"\n"
,
map
{
"Due to a method name conflict between roles "
.
join
(
' and '
,
map
"'$_'"
,
sort
values
%{
$conflicts
{
$_
}})
.
", the method '$_' must be implemented by '$class'"
}
sort
keys
%conflicts
;
croak
$fail
;
}
%role_methods
=
map
+(
$_
=>
$me
->_concrete_methods_of(
$_
)),
@roles
;
}
if
(!
$IN_APPLY_ROLES
and _want_backcompat_hack(
$me
)) {
local
$IN_APPLY_ROLES
= 1;
foreach
my
$role
(
@roles
) {
$me
->apply_single_role_to_package(
$to
,
$role
);
}
}
my
$role_methods
;
foreach
my
$step
(
$me
->role_application_steps) {
foreach
my
$role
(
@roles
) {
$role_methods
=
$role_methods
{
$role
} and (
(
local
@{
$role_methods
}{
@have_conflicts
}),
(
delete
@{
$role_methods
}{
@have_conflicts
}),
);
$me
->
$step
(
$to
,
$role
);
}
}
$APPLIED_TO
{
$to
}{
join
(
'|'
,
@roles
)} = 1;
}
sub
_composite_info_for {
my
(
$me
,
@roles
) =
@_
;
$COMPOSITE_INFO
{
join
(
'|'
,
sort
@roles
)} ||=
do
{
my
%methods
;
foreach
my
$role
(
@roles
) {
my
$this_methods
=
$me
->_concrete_methods_of(
$role
);
$methods
{
$_
}{
$this_methods
->{
$_
}} =
$role
for
keys
%$this_methods
;
}
delete
$methods
{
$_
}
for
grep
keys
(%{
$methods
{
$_
}}) == 1,
keys
%methods
;
+{
conflicts
=> \
%methods
}
};
}
sub
_check_requires {
my
(
$me
,
$to
,
$name
,
$requires
) =
@_
;
$requires
||=
$INFO
{
$name
}{requires} || [];
if
(
my
@requires_fail
=
grep
!
$to
->can(
$_
),
@$requires
) {
if
(
my
$to_info
=
$INFO
{
$to
}) {
push
@{
$to_info
->{requires}||=[]},
@requires_fail
;
}
else
{
croak
"Can't apply ${name} to ${to} - missing "
.
join
(', ',
@requires_fail
);
}
}
}
sub
_non_methods {
my
(
$me
,
$role
) =
@_
;
my
$info
=
$INFO
{
$role
} or
return
{};
my
%non_methods
= %{
$info
->{non_methods} || {} };
my
%not_methods
=
reverse
%{
$info
->{not_methods} || {} };
return
\
%non_methods
unless
keys
%not_methods
;
my
$subs
=
$me
->_all_subs(
$role
);
for
my
$sub
(
grep
!/\A\(/,
keys
%$subs
) {
my
$code
=
$subs
->{
$sub
};
if
(
exists
$not_methods
{
$code
}) {
$non_methods
{
$sub
} =
$code
;
}
}
return
\
%non_methods
;
}
sub
_concrete_methods_of {
my
(
$me
,
$role
) =
@_
;
my
$info
=
$INFO
{
$role
};
return
$info
->{methods}
if
$info
&&
$info
->{methods};
my
$non_methods
=
$me
->_non_methods(
$role
);
my
$subs
=
$me
->_all_subs(
$role
);
for
my
$sub
(
keys
%$subs
) {
if
(
exists
$non_methods
->{
$sub
} &&
$non_methods
->{
$sub
} ==
$subs
->{
$sub
} ) {
delete
$subs
->{
$sub
};
}
}
if
(
$info
) {
$info
->{methods} =
$subs
;
}
return
$subs
;
}
sub
methods_provided_by {
my
(
$me
,
$role
) =
@_
;
$me
->_require_module(
$role
);
croak
"${role} is not a ${me}"
unless
$me
->is_role(
$role
);
sort
(
keys
%{
$me
->_concrete_methods_of(
$role
)}, @{
$INFO
{
$role
}->{requires}||[]});
}
sub
_install_methods {
my
(
$me
,
$to
,
$role
) =
@_
;
my
$methods
=
$me
->_concrete_methods_of(
$role
);
my
%existing_methods
;
@existing_methods
{
keys
%{
$me
->_all_subs(
$to
) }} = ();
delete
$INFO
{
$to
}{methods}
if
$INFO
{
$to
};
foreach
my
$i
(
keys
%$methods
) {
next
if
exists
$existing_methods
{
$i
};
my
$glob
= _getglob
"${to}::${i}"
;
*$glob
=
$methods
->{
$i
};
next
unless
$i
=~ /^\(/
&& ((
defined
&overload::nil
&&
$methods
->{
$i
} == \
&overload::nil
)
|| (
defined
&overload::_nil
&&
$methods
->{
$i
} == \
&overload::_nil
));
my
$overload
= ${ _getglob
"${role}::${i}"
};
next
unless
defined
$overload
;
*$glob
= \
$overload
;
}
$me
->_install_does(
$to
);
}
sub
_install_modifiers {
my
(
$me
,
$to
,
$name
) =
@_
;
return
unless
my
$modifiers
=
$INFO
{
$name
}{modifiers};
my
$info
=
$INFO
{
$to
};
my
$existing
= (
$info
?
$info
->{modifiers} :
$COMPOSED
{modifiers}{
$to
}) ||= [];
my
@modifiers
=
grep
{
my
$modifier
=
$_
;
!
grep
$_
==
$modifier
,
@$existing
;
} @{
$modifiers
||[]};
push
@$existing
,
@modifiers
;
if
(!
$info
) {
foreach
my
$modifier
(
@modifiers
) {
$me
->_install_single_modifier(
$to
,
@$modifier
);
}
}
}
my
$vcheck_error
;
sub
_install_single_modifier {
my
(
$me
,
@args
) =
@_
;
defined
(
$vcheck_error
) or
$vcheck_error
=
do
{
local
$@;
eval
{
Class::Method::Modifiers->VERSION(1.05);
1;
} ? 0 : $@;
};
$vcheck_error
and
die
$vcheck_error
;
Class::Method::Modifiers::install_modifier(
@args
);
}
my
$FALLBACK
=
sub
{ 0 };
sub
_install_does {
my
(
$me
,
$to
) =
@_
;
return
if
$me
->is_role(
$to
);
my
$does
=
$me
->can(
'does_role'
);
*{_getglob
"${to}::does"
} =
$does
unless
$to
->can(
'does'
);
return
if
$to
->can(
'DOES'
) and
$to
->can(
'DOES'
) != (UNIVERSAL->can(
'DOES'
) || 0);
my
$existing
=
$to
->can(
'DOES'
) ||
$to
->can(
'isa'
) ||
$FALLBACK
;
my
$new_sub
=
sub
{
my
(
$proto
,
$role
) =
@_
;
$proto
->
$does
(
$role
) or
$proto
->
$existing
(
$role
);
};
no
warnings
'redefine'
;
return
*{_getglob
"${to}::DOES"
} =
$new_sub
;
}
if
"$]"
>= 5.009_005;
if
(
defined
&mro::get_linear_isa
) {
*_linear_isa
= \
&mro::get_linear_isa
;
}
else
{
my
$e
;
{
local
$@;
eval
<<'END_CODE' or $e = $@;
sub _linear_isa($;$) {
if (defined &mro::get_linear_isa) {
no warnings 'redefine', 'prototype';
*_linear_isa = \&mro::get_linear_isa;
goto &mro::get_linear_isa;
}
my @check = shift;
my @lin;
my %found;
while (defined(my $check = shift @check)) {
push @lin, $check;
no strict 'refs';
unshift @check, grep !$found{$_}++, @{"$check\::ISA"};
}
return \@lin;
}
1;
END_CODE
}
die
$e
if
defined
$e
;
}
sub
does_role {
my
(
$proto
,
$role
) =
@_
;
foreach
my
$class
(@{_linear_isa(
ref
(
$proto
)||
$proto
)}) {
return
1
if
exists
$APPLIED_TO
{
$class
}{
$role
};
}
return
0;
}
sub
is_role {
my
(
$me
,
$role
) =
@_
;
return
!!(
$INFO
{
$role
} && (
$INFO
{
$role
}{is_role}
||
$INFO
{
$role
}{requires}
||
$INFO
{
$role
}{not_methods}
||
$INFO
{
$role
}{non_methods}
));
}
1;