File Coverage

blib/lib/Role/Inspector.pm
Criterion Covered Total %
statement 74 111 66.6
branch 25 76 32.8
condition 20 55 36.3
subroutine 17 18 94.4
pod 3 3 100.0
total 139 263 52.8


line stmt bran cond sub pod time code
1 4     4   238205 use 5.006;
  4         9  
  4         140  
2 4     4   20 use strict;
  4         4  
  4         113  
3 4     4   24 use warnings;
  4         6  
  4         275  
4              
5             package Role::Inspector;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.005';
9              
10 4     4   2496 use Exporter::Shiny qw( get_role_info learn does_role );
  4         1547  
  4         22  
11 4     4   215 use Module::Runtime qw( use_package_optimistically );
  4         7  
  4         25  
12 4     4   165 use Scalar::Util qw( blessed );
  4         6  
  4         1057  
13              
14             BEGIN {
15 4         4652 *uniq = eval { require List::MoreUtils }
16             ? \&List::MoreUtils::uniq
17 27     27   23 : sub { my %already; grep !$already{$_}++, @_ }
  27         149  
18 4 50   4   9 }
19              
20             our @SCANNERS;
21              
22             sub learn (&)
23             {
24 20     20 1 56 push @SCANNERS, $_[0];
25             }
26              
27             sub get_role_info
28             {
29 9     9 1 15 my $me = shift;
30 9         41 use_package_optimistically($_[0]);
31 9         16013 my ($info) = grep defined, map $_->(@_), @SCANNERS;
32 9         37 $me->_canonicalize($info, @_);
33 9         20 return $info;
34             }
35              
36             sub _generate_get_role_info
37             {
38 3     3   500 my $me = shift;
39 3         7 my ($name, $args, $globals) = @_;
40             return sub {
41 6     6   2228 my $info = $me->get_role_info(@_);
42 6 50       17 delete($info->{meta}) if $args->{no_meta};
43 6         50 return $info;
44 3         23 };
45             }
46              
47             sub _canonicalize
48             {
49 9     9   10 my $me = shift;
50 9         9 my ($info) = @_;
51            
52 9 100 66     62 if ( $info->{api} and not( $info->{provides} && $info->{requires} ) )
      66        
53             {
54 1         1 my @provides;
55             my @requires;
56 1         2 for my $method (@{ $info->{api} })
  1         2  
57             {
58 2 100       28 push @{
59 2         3 $info->{name}->can($method) ? \@provides : \@requires
60             }, $method;
61             }
62 1   50     5 $info->{provides} ||= \@provides;
63 1   50     10 $info->{requires} ||= \@requires;
64             }
65            
66 9 50       18 if ( not $info->{api} )
67             {
68 0   0     0 $info->{api} = [sort(
69 0   0     0 @{ $info->{provides} ||= [] },
70 0         0 @{ $info->{requires} ||= [] },
71             )];
72             }
73            
74 9         16 for my $k (qw/ api provides requires /) {
75 27         94 @{ $info->{$k} } =
  27         46  
76             map ref($_) ? $_->{name} : $_,
77 27 50       19 uniq @{ $info->{$k} };
78             }
79             }
80              
81             sub _expand_attributes
82             {
83 0     0   0 my $me = shift;
84 0         0 my ($role, $meta) = @_;
85            
86 0         0 my @attrs = map {
87 0         0 my $data = $meta->get_attribute($_);
88 0 0       0 $data->{name} = $_ unless exists($data->{name});
89 0         0 $data;
90             } $meta->get_attribute_list;
91 0         0 my %methods;
92            
93 0         0 for my $attr (@attrs)
94             {
95 0 0 0     0 my $is = blessed($attr) && $attr->can('is') ? $attr->is : $attr->{is};
96 0 0 0     0 $methods{blessed($attr) && $attr->can('name') ? $attr->name : $attr->{name} }++
    0          
97             if $is =~ /\A(ro|rw|lazy|rwp)\z/i;
98            
99 0         0 for my $method_type (qw(reader writer accessor clearer predicate))
100             {
101 0 0       0 my $method_name = blessed($attr) ? $attr->$method_type : $attr->{$method_type};
102 0 0       0 ($method_name) = %$method_name if ref($method_name); # HASH :-(
103 0 0       0 $methods{$method_name}++ if defined $method_name;
104             }
105            
106 0         0 my $handles;
107 0 0 0     0 if (blessed($attr) and $attr->can('_canonicalize_handles'))
108             {
109 0 0       0 $handles =
    0          
110             $attr->can('_canonicalize_handles') ? +{ $attr->_canonicalize_handles } :
111             $attr->can('handles') ? $attr->handles :
112             $attr->{handles};
113             }
114             else
115             {
116 0         0 $handles = $attr->{handles};
117             }
118            
119 0 0       0 if (!defined $handles)
    0          
    0          
    0          
120             {
121             # no-op
122             }
123             elsif (not ref($handles))
124             {
125 0         0 $methods{$_}++ for @{ $me->get_info($handles)->{api} };
  0         0  
126             }
127             elsif (ref($handles) eq q(ARRAY))
128             {
129 0         0 $methods{$_}++ for @$handles;
130             }
131             elsif (ref($handles) eq q(HASH))
132             {
133 0         0 $methods{$_}++ for keys %$handles;
134             }
135             else
136             {
137 0         0 require Carp;
138 0         0 Carp::carp(
139             sprintf(
140             "%s contains attribute with delegated methods, but %s cannot determine which methods are being delegated",
141             $role,
142             $me,
143             )
144             );
145             }
146             }
147            
148 0         0 return keys(%methods);
149             }
150              
151             # Learn about mop
152             learn {
153             my $role = shift;
154             return unless $INC{'mop.pm'};
155            
156             my $meta = mop::meta($role);
157             return unless $meta && $meta->isa('mop::role');
158            
159             return {
160             name => $role,
161             type => 'mop::role',
162             provides => [ sort(map($_->name, $meta->methods)) ],
163             requires => [ sort($meta->required_methods) ],
164             meta => $meta,
165             };
166             };
167              
168             # Learn about Role::Tiny and Moo::Role
169             learn {
170             my $role = shift;
171             return unless $INC{'Role/Tiny.pm'};
172            
173             # Moo 1.003000 added is_role, but that's too new to rely on.
174             my @methods;
175             return unless eval {
176             @methods = 'Role::Tiny'->methods_provided_by($role);
177             1;
178             };
179            
180 4     4   24 no warnings qw(once);
  4         5  
  4         3198  
181             my $type =
182             ($INC{'Moo/Role.pm'} and $Moo::Role::INFO{$role}{accessor_maker})
183             ? 'Moo::Role'
184             : 'Role::Tiny';
185            
186             @methods = $type->methods_provided_by($role)
187             if $type ne 'Role::Tiny';
188            
189             return {
190             name => $role,
191             type => $type,
192             api => [ sort(@methods) ], # keep: potentially more accurate
193             provides => [ sort keys %{ $type->_concrete_methods_of($role) } ],
194             requires => [ sort @{ $Role::Tiny::INFO{$role}{requires} or [] } ],
195             };
196             };
197              
198             # Learn about Moose
199             learn {
200             my $role = shift;
201             return unless $INC{'Moose.pm'};
202            
203             require Moose::Util;
204             my $meta = Moose::Util::find_meta($role);
205             return unless $meta && $meta->isa('Moose::Meta::Role');
206            
207             return {
208             name => $role,
209             type => 'Moose::Role',
210             meta => $meta,
211             provides => [ sort($meta->get_method_list, __PACKAGE__->_expand_attributes($role, $meta)) ],
212             requires => [ sort(map($_->name, $meta->get_required_method_list)) ],
213             };
214             };
215              
216             # Learn about Mouse
217             learn {
218             my $role = shift;
219             return unless $INC{'Mouse.pm'};
220            
221             require Mouse::Util;
222             my $meta = Mouse::Util::find_meta($role);
223             return unless $meta && $meta->isa('Mouse::Meta::Role');
224            
225             return {
226             name => $role,
227             type => 'Mouse::Role',
228             meta => $meta,
229             provides => [ sort($meta->get_method_list, __PACKAGE__->_expand_attributes($role, $meta)) ],
230             requires => [ sort($meta->get_required_method_list) ],
231             };
232             };
233              
234             # Learn about Role::Basic
235             learn {
236             my $role = shift;
237             return unless $INC{'Role/Basic.pm'};
238            
239             return unless eval { 'Role::Basic'->_load_role($role) };
240            
241             return {
242             name => $role,
243             type => 'Role::Basic',
244             provides => [ sort(keys(%{ 'Role::Basic'->_get_methods($role) })) ],
245             requires => [ sort('Role::Basic'->get_required_by($role)) ],
246             };
247             };
248              
249             sub does_role
250             {
251 5     5 1 8 my $me = shift;
252 5         7 my ($thing, $role) = @_;
253            
254 5 50       13 return !!0 if !defined($thing);
255 5 50 33     10 return !!0 if ref($thing) && !blessed($thing);
256            
257 5   33     21 ref($_) or use_package_optimistically($_) for @_;
258            
259 5 100 66     6806 return !!1 if $thing->can('does') && $thing->does($role);
260 3 100 66     39 return !!1 if $thing->can('DOES') && $thing->DOES($role);
261            
262 2 50       5 my $info = $me->get_role_info($role)
263             or return !!0;
264            
265 2 50 66     8 if ($info->{type} eq 'Role::Tiny' or $info->{type} eq 'Moo::Role')
266             {
267 2 100       6 return !!1 if Role::Tiny::does_role($thing, $role);
268             }
269            
270 1 50       15 if ($info->{type} eq 'Moose::Role')
271             {
272 0         0 require Moose::Util;
273 0 0       0 return !!1 if Moose::Util::does_role($thing, $role);
274             }
275            
276 1 50       3 if ($info->{type} eq 'Mouse::Role')
277             {
278 0         0 require Mouse::Util;
279 0 0       0 return !!1 if Mouse::Util::does_role($thing, $role);
280             }
281            
282 1 50       3 if (not ref $thing)
283             {
284 1   50     3 my $info2 = $me->get_role_info($thing) || { type => '' };
285            
286 1 50 33     7 if ($info2->{type} eq 'Role::Tiny' or $info2->{type} eq 'Moo::Role')
287             {
288 1 50       4 return !!1 if Role::Tiny::does_role($thing, $role);
289             }
290            
291 1 50 33     16 if ($info2->{type} eq 'Moose::Role'
      33        
292             or $INC{'Moose.pm'} && Moose::Util::find_meta($thing))
293             {
294 0         0 require Moose::Util;
295 0 0       0 return !!1 if Moose::Util::does_role($thing, $role);
296             }
297            
298 1 50 33     8 if ($info2->{type} eq 'Mouse::Role'
      33        
299             or $INC{'Mouse.pm'} && Mouse::Util::find_meta($thing))
300             {
301 0         0 require Mouse::Util;
302 0 0       0 return !!1 if Mouse::Util::does_role($thing, $role);
303             }
304             }
305            
306             # No special handling for Role::Basic, but hopefully checking
307             # `DOES` worked!
308            
309 1         6 !!0;
310             }
311              
312             # very simple class method curry
313             sub _generate_does_role
314             {
315 1     1   42 my $me = shift;
316 1     5   5 sub { $me->does_role(@_) };
  5         939  
317             }
318              
319              
320             1;
321              
322             __END__