File Coverage

blib/lib/MooseX/amine.pm
Criterion Covered Total %
statement 167 167 100.0
branch 60 64 93.7
condition 1 2 50.0
subroutine 27 27 100.0
pod 2 2 100.0
total 257 262 98.0


line stmt bran cond sub pod time code
1             package MooseX::amine;
2             # ABSTRACT: Examine Yr Moose
3             $MooseX::amine::VERSION = '0.5';
4 14     14   282632 use Moose;
  14         5238238  
  14         112  
5 14     14   77816 use Moose::Meta::Class;
  14         27  
  14         237  
6 14     14   54 use Moose::Meta::Role;
  14         26  
  14         228  
7 14     14   57 use Moose::Util::TypeConstraints;
  14         19  
  14         116  
8              
9 14     14   20827 use 5.010;
  14         40  
  14         531  
10 14     14   9193 use autodie qw(open close);
  14         184627  
  14         78  
11 14     14   12521 use PPI;
  14         1259517  
  14         545  
12 14     14   6329 use Test::Deep::NoTest qw/eq_deeply/;
  14         103823  
  14         105  
13 14     14   2462 use Try::Tiny;
  14         28  
  14         25626  
14              
15              
16             has 'include_accessors_in_method_list' => (
17             is => 'ro' ,
18             isa => 'Bool' ,
19             default => 0 ,
20             );
21              
22             has 'include_moose_in_isa' => (
23             is => 'ro' ,
24             isa => 'Bool' ,
25             default => 0 ,
26             );
27              
28             has 'include_private_attributes' => => (
29             is => 'ro' ,
30             isa => 'Bool' ,
31             default => 0 ,
32             );
33              
34             has 'include_private_methods' => => (
35             is => 'ro' ,
36             isa => 'Bool' ,
37             default => 0 ,
38             );
39              
40             has 'include_standard_methods' => (
41             is => 'ro' ,
42             isa => 'Bool' ,
43             default => 0 ,
44             );
45              
46             has 'module' => ( is => 'ro' , isa => 'Str' );
47             has 'path' => ( is => 'ro' , isa => 'Str' );
48              
49             has '_attributes' => (
50             is => 'ro' ,
51             isa => 'HashRef' ,
52             traits => [ 'Hash' ] ,
53             handles => {
54             _get_attribute => 'get' ,
55             _store_attribute => 'set' ,
56             _check_for_stored_attribute => 'exists' ,
57             },
58             );
59              
60             has '_exclusions' => (
61             is => 'ro' ,
62             isa => 'HashRef' ,
63             handles => {
64 51     51   66 _add_exclusion => sub { my( $self , $ex ) = @_; $self->{_exclusions}{$ex}++ } ,
  51         149  
65 143     143   156 _check_exclusion => sub { my( $self , $ex ) = @_; return $self->{_exclusions}{$ex} } ,
  143         373  
66             }
67             );
68              
69             has '_metaobj' => (
70             is => 'ro' ,
71             isa => 'Object' ,
72             lazy => 1 ,
73             builder => '_build_metaobj' ,
74             );
75              
76             sub _build_metaobj {
77 14     14   26 my $self = shift;
78 14   50     85 return $self->{module}->meta
79             || die "Can't get meta object for module!" ;
80             }
81              
82             has '_methods' => (
83             is => 'ro' ,
84             isa => 'HashRef' ,
85             traits => [ 'Hash' ] ,
86             handles => {
87             _store_method => 'set' ,
88             },
89             );
90              
91             has '_sub_nodes' => (
92             is => 'ro' ,
93             isa => 'HashRef' ,
94             traits => [ 'Hash' ] ,
95             handles => {
96             _get_sub_node => 'get' ,
97             _store_sub_node => 'set' ,
98             },
99             );
100              
101             sub BUILDARGS {
102 18     18 1 12760 my $class = shift;
103              
104 18         73 my $args = _convert_to_hashref_if_needed( @_ );
105              
106 18 100       82 if ( $args->{module}) {
    100          
107 13         1745 eval "require $args->{module};";
108 13 100       501413 die $@ if $@;
109              
110 12         56 my $path = $args->{module} . '.pm';
111 12         67 $path =~ s|::|/|g;
112 12         50 $args->{path} = $INC{$path};
113             }
114             elsif ( $args->{path} ) {
115 4         21 open( my $IN , '<' , $args->{path} );
116 3         2065 while (<$IN>) {
117 4 100       26 if ( /^package ([^;]+);/ ) {
118 3         9 my $module = $1;
119 3         14 $args->{module} = _load_module_from_path( $module , $args->{path} );
120 2         3 last;
121             }
122             }
123 2         10 close( $IN );
124             }
125 1         13 else { die "Need to provide 'module' or 'path'" }
126 14         1115 return $args;
127             }
128              
129              
130             sub examine {
131 14     14 1 43193 my $self = shift;
132 14         494 my $meta = $self->_metaobj;
133              
134 14 100       111 if ( $meta->isa( 'Moose::Meta::Role' )) {
135 1         4 $self->_dissect_role( $meta );
136             }
137             else {
138 13         61 foreach my $class ( reverse $meta->linearized_isa ) {
139 33 100       7714 if ( $class =~ /^Moose::/) {
140 13 100       386 next unless $self->include_moose_in_isa;
141             }
142 21         87 $self->_dissect_class( $class );
143             }
144             }
145              
146             # Now that we've dissected everything, load the extracted sub nodes into the
147             # appropriate methods
148 14         6839 foreach ( keys %{ $self->{_methods} } ) {
  14         79  
149 42         1404 $self->{_methods}{$_}{code} = $self->_get_sub_node( $_ );
150             }
151              
152             return {
153 14         130 attributes => $self->{_attributes} ,
154             methods => $self->{_methods} ,
155             }
156             }
157              
158             # given two attribute data structures, compare them. returns the older one if
159             # they're the same; the newer one if they're not.
160             #
161             # ignores the value of the 'from' key, since the point here is to check if two
162             # attributes from different packages are otherwise identical.
163             sub _compare_attributes {
164 14     14   20 my( $new_attr , $old_attr ) = @_;
165              
166 14         33 my $new_from = delete $new_attr->{from};
167 14         38 my $old_from = delete $old_attr->{from};
168              
169 14 100       57 if ( eq_deeply( $new_attr , $old_attr )) {
170 10         54926 $old_attr->{from} = $old_from;
171 10         29 return $old_attr;
172             }
173             else {
174 4         24559 $new_attr->{from} = $new_from;
175 4         12 return $new_attr;
176             }
177             }
178              
179             # given a list of args that may or may not be a hashref, do whatever munging
180             # is needed to return a hashref.
181             sub _convert_to_hashref_if_needed {
182 18     18   40 my( @list_of_args ) = @_;
183              
184 18 100       67 return $_[0] if ref $_[0];
185              
186 14 100       79 return { module => $_[0] } if @_ == 1;
187              
188 2         9 my %hash = @_;
189 2         5 return \%hash;
190             }
191              
192             # given a meta object and an attribute name (that is an attribute of that meta
193             # object), extract a bunch of info about it and store it in the _attributes
194             # attr.
195             sub _dissect_attribute {
196 54     54   94 my( $self , $meta , $attribute_name ) = @_;
197              
198 54 100       159 if ( $attribute_name =~ /^_/ ) {
199 4 100       143 return unless $self->include_private_attributes;
200             }
201              
202 51         252 my $meta_attr = $meta->get_attribute( $attribute_name );
203              
204 51         274 my $return;
205 51         102 my $ref = ref $meta_attr;
206 51 100       132 if ( $ref eq 'Moose::Meta::Role::Attribute' ) {
207 11         57 $return = $meta_attr->original_role->name;
208 11         751 $meta_attr = $meta_attr->attribute_for_class();
209             }
210             else {
211 40         175 $return = $meta_attr->associated_class->name
212             }
213              
214 51         6535 my $extracted_attribute = $self->_extract_attribute_metainfo( $meta_attr );
215 51         103 $extracted_attribute->{from} = $return;
216              
217 51 100       1660 if ( $self->_check_for_stored_attribute( $attribute_name )) {
218 14         465 $extracted_attribute = _compare_attributes(
219             $extracted_attribute , $self->_get_attribute( $attribute_name )
220             );
221             }
222              
223 51         1834 $self->_store_attribute( $attribute_name => $extracted_attribute );
224             }
225              
226             # given a class name, extract and store info about it and any roles that it
227             # has consumed.
228             sub _dissect_class {
229 21     21   44 my( $self , $class ) = @_;
230 21         133 my $meta = $class->meta;
231              
232 21 50       530 map { $self->_dissect_role($_) } @{ $meta->roles } if ( $meta->can( 'roles' ));
  9         80  
  21         616  
233 21         3494 map { $self->_dissect_attribute( $meta , $_ ) } $meta->get_attribute_list;
  43         294  
234 21         334 map { $self->_dissect_method( $meta , $_ ) } $meta->get_method_list;
  147         11771  
235              
236 21         111 $self->_extract_sub_nodes( $meta->name );
237             }
238              
239             # given a meta object and a method name (that is a method of that meta
240             # object), extract and store info about the method.
241             sub _dissect_method {
242 167     167   201 my( $self , $meta , $method_name ) = @_;
243              
244 167 100       337 if ( $method_name =~ /^_/ ) {
245 8 100       236 return unless $self->include_private_methods;
246             }
247              
248 161         372 my $meta_method = $meta->get_method( $method_name );
249              
250 161         3119 my $src = $meta_method->original_package_name;
251              
252 161 100       5727 unless ( $self->include_accessors_in_method_list ) {
253 143 100       284 return if $self->_check_exclusion( $method_name );
254             }
255              
256 125 100       3078 unless ( $self->include_standard_methods ) {
257 107         192 my @STOCK = qw/ DESTROY meta new /;
258 107         140 foreach ( @STOCK ) {
259 255 100       526 return if $method_name eq $_;
260             }
261             }
262              
263 59         148 my $extracted_method = $self->_extract_method_metainfo( $meta_method );
264 59         2283 $self->_store_method( $method_name => $extracted_method );
265             }
266              
267             # extract and store information from a particular role
268             sub _dissect_role {
269 10     10   18 my( $self , $meta ) = @_;
270              
271 10         53 map { $self->_dissect_attribute( $meta , $_ ) } $meta->get_attribute_list;
  11         108  
272 10         149 map { $self->_dissect_method( $meta , $_ ) } $meta->get_method_list;
  20         797  
273              
274 10         89 my @names = split '\|' , $meta->name;
275 10         34 foreach my $name ( @names ) {
276 11 50       337 next if $name =~ /Moose::Meta::Role::__ANON/;
277 11         33 $self->_extract_sub_nodes( $name );
278             }
279             }
280              
281             # given a meta attribute, extract a bunch of meta info and return a data
282             # structure summarizing it.
283             sub _extract_attribute_metainfo {
284 51     51   79 my( $self , $meta_attr ) = @_;
285              
286 51         69 my $return = {};
287              
288 51         99 foreach ( qw/ reader writer accessor / ) {
289 153 100       663 next unless my $fxn = $meta_attr->$_;
290 51         143 $self->_add_exclusion( $fxn );
291 51         130 $return->{$_} = $fxn;
292             }
293              
294 51 100       2028 $return->{meta}{documentation} = $meta_attr->documentation
295             if ( $meta_attr->has_documentation );
296              
297 51 100       2255 $return->{meta}{constraint} = $meta_attr->type_constraint->name
298             if ( $meta_attr->has_type_constraint );
299              
300 51 100       4265 $return->{meta}{traits} = $meta_attr->applied_traits
301             if ( $meta_attr->has_applied_traits );
302              
303 51         414 foreach ( qw/
304             is_weak_ref is_required is_lazy is_lazy_build should_coerce
305             should_auto_deref has_trigger has_handles
306             / ) {
307 408 100       11577 $return->{meta}{$_}++ if $meta_attr->$_ ;
308             }
309              
310             ### FIXME should look at delegated methods and install exclusions for them
311              
312 51         243 return $return;
313              
314             }
315              
316             # given a meta method, extract a bunch of info and return a data structure
317             # summarizing it.
318             sub _extract_method_metainfo {
319 59     59   65 my( $self , $meta_method ) = @_;
320              
321             return {
322 59         144 from => $meta_method->original_package_name ,
323             };
324             }
325              
326             # given a module name, use PPI to extract the 'sub' nodes and store them.
327             sub _extract_sub_nodes {
328 32     32   57 my( $self , $name ) = @_;
329              
330 32         76 my $path = $name . '.pm';
331 32         139 $path =~ s|::|/|g;
332 32 50       132 if ( $path = $INC{$path} ){
333             try {
334 32 50   32   938 my $ppi = PPI::Document->new( $path )
335             or die "Can't load PPI for $path ($!)";
336              
337             my $sub_nodes = $ppi->find(
338 3869 100       37265 sub{ $_[1]->isa( 'PPI::Statement::Sub' ) && $_[1]->name }
339 32         304779 );
340              
341 32         408 foreach my $sub_node ( @$sub_nodes ) {
342 46         130 my $name = $sub_node->name;
343 46         816 $self->_store_sub_node( $name => $sub_node->content );
344             }
345 32         328 };
346             # FIXME should probably do something about errors here...
347             }
348             }
349              
350              
351             # given a module name and a path to that module, dynamically load the
352             # module. figures out the appropriate 'use lib' statement based on the path.
353             sub _load_module_from_path {
354 3     3   8 my( $module , $path ) = @_;
355              
356 3         13 $path =~ s/.pm$//;
357 3         18 my @path_parts = split '/' , $path;
358 3         12 my @module_parts = split /::/ , $module;
359 3         7 my @inc_path = ();
360              
361 3         8 while ( @path_parts ) {
362 14         19 my $path = join '/' , @path_parts;
363 14         15 my $mod = join '/' , @module_parts;
364 14 100       25 last if $path eq $mod;
365 12         25 push @inc_path , shift @path_parts;
366             }
367 3         9 my $inc_path = join '/' , @inc_path;
368              
369 3     2   237 eval "use lib '$inc_path'; require $module";
  2     1   14  
  2         2  
  2         16  
  1         7  
  1         1  
  1         9  
370 3 100       34709 die $@ if $@;
371              
372 2         14 return $module;
373             }
374              
375              
376             #__PACKAGE__->meta->make_immutable;
377             1;
378              
379             __END__
380              
381             =pod
382              
383             =encoding UTF-8
384              
385             =head1 NAME
386              
387             MooseX::amine - Examine Yr Moose
388              
389             =head1 VERSION
390              
391             version 0.5
392              
393             =head1 SYNOPSIS
394              
395             my $mex = MooseX::amine->new( 'MooseX::amine' );
396             my $data = $mex->examine;
397              
398             my $attributes = $data->{attributes};
399             my $methods = $data->{methods};
400              
401             =head1 METHODS
402              
403             =head2 new
404              
405             # these two are the same
406             my $mex = MooseX::amine->new( 'Module' );
407             my $mex = MooseX::amine->new({ module => 'Module' });
408              
409             # or you can go from the path to the file
410             my $mex = MooseX::amine->new({ path = 'path/to/Module.pm' });
411              
412             # there are a number of options that all pretty much do what they say.
413             # they all default to off
414             my $mex = MooseX::amine->new({
415             module => 'Module' ,
416             include_accessors_in_method_list => 1,
417             include_moose_in_isa => 1,
418             include_private_attributes => 1,
419             include_private_methods => 1,
420             include_standard_methods => 1,
421             });
422              
423             =head2 examine
424              
425             my $mex = MooseX::amine( 'Module' );
426             my $data = $mex->examine();
427              
428             Returns a multi-level hash-based data structure, with two top-level keys,
429             C<attributes> and C<methods>. C<attributes> points to a hash where the keys
430             are attribute names and the values are data structures that describe the
431             attributes. Similarly, C<methods> points to a hash where the keys are method
432             names and the values are data structures describing the method.
433              
434             A sample attribute entry:
435              
436             simple_attribute => {
437             accessor => 'simple_attribute',
438             from => 'Module',
439             meta => {
440             constraint => 'Str'
441             }
442             }
443              
444             The prescence of an C<accessor> key indicates that this attribute was defined
445             with C<is => 'rw'>. A read-only attribute will have a C<reader> key. A
446             C<writer> key may also be present if a specific writer method was given when
447             creating the attribute.
448              
449             Depending on the options given when creating the attribute there may be
450             various other options present under the C<meta> key.
451              
452             A sample method entry:
453              
454             simple_method => {
455             code => 'sub simple_method { return \'simple\' }',
456             from => 'Module'
457             }
458              
459             The C<code> key will contain the actual code from the method, extracted with
460             PPI. Depending on where the method code actually lives, this key may or may
461             not be present.
462              
463             =head1 CREDITS
464              
465             =over 4
466              
467             =item Semi-inspired by L<MooseX::Documenter>.
468              
469             =item Syntax highlighting Javascript/CSS stuff based on SHJS and largely stolen from search.cpan.org.
470              
471             =back
472              
473             =head1 AUTHOR
474              
475             John SJ Anderson <genehack@genehack.org>
476              
477             =head1 COPYRIGHT AND LICENSE
478              
479             This software is copyright (c) 2014 by John SJ Anderson.
480              
481             This is free software; you can redistribute it and/or modify it under
482             the same terms as the Perl 5 programming language system itself.
483              
484             =cut