File Coverage

blib/lib/Mite/Trait/HasRoles.pm
Criterion Covered Total %
statement 88 99 88.8
branch 15 26 57.6
condition 6 11 54.5
subroutine 16 17 94.1
pod 0 5 0.0
total 125 158 79.1


line stmt bran cond sub pod time code
1 109     109   2259 use 5.010001;
  109         519  
2 109     109   717 use strict;
  109         291  
  109         3073  
3 109     109   659 use warnings;
  109         284  
  109         5735  
4              
5             package Mite::Trait::HasRoles;
6 109     109   820 use Mite::Miteception -role, -all;
  109         336  
  109         1008  
7              
8             our $AUTHORITY = 'cpan:TOBYINK';
9             our $VERSION = '0.012000';
10              
11             requires qw(
12             source
13             native_methods
14             );
15              
16             has roles =>
17             is => rw,
18             isa => ArrayRef[MiteRole],
19 153     153   613 builder => sub { [] };
20              
21             has role_args =>
22             is => rw,
23             isa => Map[ NonEmptyStr, HashRef|Undef ],
24 155     155   513 builder => sub { {} };
25              
26             sub methods_to_import_from_roles {
27 135     135 0 396 my $self = shift;
28              
29 135         335 my %methods;
30 135         291 for my $role ( @{ $self->roles } ) {
  135         490  
31 13   100     73 my $role_args = $self->role_args->{ $role->name } || {};
32 13         33 my %exported = %{ $role->methods_to_export( $role_args ) };
  13         85  
33 13         65 for my $name ( sort keys %exported ) {
34 13 50 33     42 if ( defined $methods{$name} and $methods{$name} ne $exported{$name} ) {
35             croak "Conflict between %s and %s; %s must implement %s\n",
36 0         0 $methods{$name}, $exported{$name}, $self->name, $name;
37             }
38             else {
39 13         35 $methods{$name} = $exported{$name};
40             }
41             }
42             }
43              
44             # This package provides a native version of these
45             # methods, so don't import.
46 135         351 my %native = %{ $self->native_methods };
  135         716  
47 135         529 for my $name ( keys %native ) {
48 57         149 delete $methods{$name};
49             }
50              
51             # Never propagate
52 135         731 delete $methods{$_} for qw(
53             new
54             DESTROY
55             DOES
56             does
57             __META__
58             __FINALIZE_APPLICATION__
59             CREATE_CLASS
60             APPLY_TO
61             );
62              
63 135         508 return \%methods;
64             }
65              
66             sub add_role {
67 11     11 0 40 my ( $self, $role ) = @_;
68              
69 6         18 my @attr = sort { $a->_order <=> $b->_order }
70 11         51 values %{ $role->attributes };
  11         89  
71 11         37 for my $attr ( @attr ) {
72             $self->add_attribute( $attr )
73 10 100       58 unless $self->attributes->{ $attr->name };
74             }
75 11         25 push @{ $self->roles }, $role;
  11         96  
76              
77 11         36 return;
78             }
79              
80             sub add_roles_by_name {
81 11     11 0 46 my ( $self, @names ) = @_;
82              
83 11         35 for my $name ( @names ) {
84 11         65 my $role = $self->_get_role( $name );
85 11         62 $self->add_role( $role );
86             }
87              
88 11         53 return;
89             }
90              
91             sub _get_role {
92 11     11   40 my ( $self, $role_name ) = ( shift, @_ );
93              
94 11         68 my $project = $self->project;
95              
96             # See if it's already loaded
97 11         73 my $role = $project->class($role_name);
98 11 100       56 return $role if $role;
99              
100             # If not, try to load it
101             eval "require $role_name; 1"
102 2 50       143 or do {
103 0         0 my $file_name = $role_name;
104 0 0       0 if ( my $yuck = $project->_module_fakeout_namespace ) {
105 0         0 $file_name =~ s/$yuck\:://g;
106             }
107 0         0 $file_name =~ s/::/\//g;
108 0         0 $file_name = "lib/$file_name.pm";
109 0         0 $project->_load_file( $file_name );
110             };
111 2 50 33     23 if ( $INC{'Role/Tiny.pm'} and 'Role::Tiny'->is_role( $role_name ) ) {
112 2         899 require Mite::Role::Tiny;
113 2         21 $role = 'Mite::Role::Tiny'->inhale( $role_name );
114             }
115             else {
116 0         0 $role = $project->class( $role_name, 'Mite::Role' );
117             }
118 2 50       14 return $role if $role;
119              
120 0         0 croak <<"ERROR", $role_name;
121             %s loaded but is not a recognized role. Mite roles and Role::Tiny
122             roles are the only supported roles. Sorry.
123             ERROR
124             }
125              
126             sub does_list {
127 24     24 0 48 my $self = shift;
128             return (
129             $self->name,
130 24         73 map( $_->does_list, @{ $self->roles } ),
  24         77  
131             );
132             }
133              
134             sub handle_with_keyword {
135 11     11 0 31 my $self = shift;
136              
137 11         56 while ( @_ ) {
138 11         28 my $role = shift;
139 11 50       62 my $args = Str->check( $_[0] ) ? undef : shift;
140 11         243 $self->role_args->{$role} = $args;
141 11         62 $self->add_roles_by_name( $role );
142             }
143              
144 11         37 return;
145             }
146              
147             before inject_mite_functions => sub {
148             my ( $self, $file, $arg ) = ( shift, @_ );
149              
150             my $requested = sub { $arg->{$_[0]} ? 1 : $arg->{'!'.$_[0]} ? 0 : $arg->{'-all'} ? 1 : $_[1]; };
151             my $defaults = ! $arg->{'!-defaults'};
152             my $shim = $self->shim_name;
153             my $package = $self->name;
154             my $fake_ns = $self->project->can('_module_fakeout_namespace') && $self->project->_module_fakeout_namespace;
155              
156 109     109   1070 no strict 'refs';
  109         383  
  109         86238  
157              
158             if ( $requested->( 'with', $defaults ) ) {
159              
160             *{ $package .'::with' } = sub {
161 0 0   0     return $self->handle_with_keyword(
    0          
162             defined( $fake_ns )
163             ? ( map Str->check($_) ? "$fake_ns\::$_" : $_, @_ )
164             : @_
165             );
166             };
167              
168             $self->imported_keywords->{with} = 'sub { $SHIM->HANDLE_with( $CALLER, @_ ) }';
169             }
170             };
171              
172             around compilation_stages => sub {
173             my ( $next, $self ) = ( shift, shift );
174             my @stages = $self->$next( @_ );
175             push @stages, qw(
176             _compile_with
177             _compile_does
178             _compile_composed_methods
179             );
180             return @stages;
181             };
182              
183             sub _compile_with {
184 123     123   324 my $self = shift;
185              
186 123         290 my $roles = [ map $_->name, @{ $self->roles } ];
  123         658  
187 123 100       869 return unless @$roles;
188              
189 11         61 my $source = $self->source;
190              
191             my $require_list = join "\n\t",
192 2         12 map { "require $_;" }
193             # Don't require a role from the same source
194 11   66     96 grep { !$source || !$source->has_class($_) }
  11         104  
195             @$roles;
196              
197             my $version_tests = join "\n\t",
198             map { sprintf '%s->VERSION( %s );',
199             B::perlstring( $_ ),
200 0         0 B::perlstring( $self->role_args->{$_}{'-version'} )
201             }
202             grep {
203 11         53 $self->role_args->{$_}
204 11 100       46 and $self->role_args->{$_}{'-version'}
205             }
206             @$roles;
207              
208 11         72 my $does_hash = join ", ", map sprintf( "%s => 1", B::perlstring($_) ), $self->does_list;
209              
210 11         120 return <<"END";
211             BEGIN {
212             $require_list
213             $version_tests
214             our \%DOES = ( $does_hash );
215             }
216             END
217             }
218              
219             sub _compile_does {
220 123     123   347 my $self = shift;
221             return <<'CODE'
222             # See UNIVERSAL
223             sub DOES {
224             my ( $self, $role ) = @_;
225             our %DOES;
226             return $DOES{$role} if exists $DOES{$role};
227             return 1 if $role eq __PACKAGE__;
228             if ( $INC{'Moose/Util.pm'} and my $meta = Moose::Util::find_meta( ref $self or $self ) ) {
229             $meta->can( 'does_role' ) and $meta->does_role( $role ) and return 1;
230             }
231             return $self->SUPER::DOES( $role );
232             }
233              
234             # Alias for Moose/Moo-compatibility
235             sub does {
236             shift->DOES( @_ );
237             }
238             CODE
239 123         632 }
240              
241             sub _compile_composed_methods {
242 123     123   385 my $self = shift;
243 123         378 my $code = '';
244              
245 123         295 my %methods = %{ $self->methods_to_import_from_roles };
  123         602  
246 123 100       1130 keys %methods or return;
247              
248 4         11 $code .= "# Methods from roles\n";
249 4         14 for my $name ( sort keys %methods ) {
250             # Use goto to help namespace::autoclean recognize these as
251             # not being imported methods.
252 7         33 $code .= sprintf 'sub %s { goto \&%s; }' . "\n", $name, $methods{$name};
253             }
254              
255 4         44 return $code;
256             }
257              
258             around _compile_mop_postamble => sub {
259             my ( $next, $self ) = ( shift, shift );
260             my $code = $self->$next( @_ );
261              
262             my @roles = @{ $self->roles || [] }
263             or return $code;
264              
265             for my $role ( @roles ) {
266             $code .= sprintf "Moose::Util::find_meta( %s )->add_role( Moose::Util::find_meta( %s ) );\n",
267             B::perlstring( $self->name ), B::perlstring( $role->name );
268             }
269              
270             return $code;
271             };
272              
273             1;