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.6';
4 14     14   268714 use Moose;
  14         5114690  
  14         147  
5 14     14   79376 use Moose::Meta::Class;
  14         29  
  14         229  
6 14     14   57 use Moose::Meta::Role;
  14         21  
  14         271  
7 14     14   57 use Moose::Util::TypeConstraints;
  14         20  
  14         113  
8              
9 14     14   21641 use 5.010;
  14         41  
  14         539  
10 14     14   9781 use autodie qw(open close);
  14         188892  
  14         94  
11 14     14   13075 use PPI;
  14         1305184  
  14         558  
12 14     14   6118 use Test::Deep::NoTest qw/eq_deeply/;
  14         102889  
  14         96  
13 14     14   2477 use Try::Tiny;
  14         23  
  14         24963  
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   188 _add_exclusion => sub { my( $self , $ex ) = @_; $self->{_exclusions}{$ex}++ } ,
  51         171  
65 143     143   159 _check_exclusion => sub { my( $self , $ex ) = @_; return $self->{_exclusions}{$ex} } ,
  143         471  
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   30 my $self = shift;
78 14   50     93 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 11943 my $class = shift;
103              
104 18         69 my $args = _convert_to_hashref_if_needed( @_ );
105              
106 18 100       78 if ( $args->{module}) {
    100          
107 13         1580 eval "require $args->{module};";
108 13 100       500429 die $@ if $@;
109              
110 12         56 my $path = $args->{module} . '.pm';
111 12         63 $path =~ s|::|/|g;
112 12         50 $args->{path} = $INC{$path};
113             }
114             elsif ( $args->{path} ) {
115 4         22 open( my $IN , '<' , $args->{path} );
116 3         1966 while (<$IN>) {
117 4 100       27 if ( /^package ([^;]+);/ ) {
118 3         8 my $module = $1;
119 3         14 $args->{module} = _load_module_from_path( $module , $args->{path} );
120 2         4 last;
121             }
122             }
123 2         9 close( $IN );
124             }
125 1         11 else { die "Need to provide 'module' or 'path'" }
126 14         1137 return $args;
127             }
128              
129              
130             sub examine {
131 14     14 1 41305 my $self = shift;
132 14         469 my $meta = $self->_metaobj;
133              
134 14 100       120 if ( $meta->isa( 'Moose::Meta::Role' )) {
135 1         3 $self->_dissect_role( $meta );
136             }
137             else {
138 13         57 foreach my $class ( reverse $meta->linearized_isa ) {
139 33 100       6826 if ( $class =~ /^Moose::/) {
140 13 100       376 next unless $self->include_moose_in_isa;
141             }
142 21         88 $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         6729 foreach ( keys %{ $self->{_methods} } ) {
  14         78  
149 42         1400 $self->{_methods}{$_}{code} = $self->_get_sub_node( $_ );
150             }
151              
152             return {
153 14         134 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   19 my( $new_attr , $old_attr ) = @_;
165              
166 14         34 my $new_from = delete $new_attr->{from};
167 14         40 my $old_from = delete $old_attr->{from};
168              
169 14 100       60 if ( eq_deeply( $new_attr , $old_attr )) {
170 10         56526 $old_attr->{from} = $old_from;
171 10         35 return $old_attr;
172             }
173             else {
174 4         25446 $new_attr->{from} = $new_from;
175 4         16 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   45 my( @list_of_args ) = @_;
183              
184 18 100       63 return $_[0] if ref $_[0];
185              
186 14 100       68 return { module => $_[0] } if @_ == 1;
187              
188 2         5 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   82 my( $self , $meta , $attribute_name ) = @_;
197              
198 54 100       165 if ( $attribute_name =~ /^_/ ) {
199 4 100       106 return unless $self->include_private_attributes;
200             }
201              
202 51         265 my $meta_attr = $meta->get_attribute( $attribute_name );
203              
204 51         280 my $return;
205 51         109 my $ref = ref $meta_attr;
206 51 100       116 if ( $ref eq 'Moose::Meta::Role::Attribute' ) {
207 11         49 $return = $meta_attr->original_role->name;
208 11         760 $meta_attr = $meta_attr->attribute_for_class();
209             }
210             else {
211 40         196 $return = $meta_attr->associated_class->name
212             }
213              
214 51         6641 my $extracted_attribute = $self->_extract_attribute_metainfo( $meta_attr );
215 51         105 $extracted_attribute->{from} = $return;
216              
217 51 100       1653 if ( $self->_check_for_stored_attribute( $attribute_name )) {
218 14         438 $extracted_attribute = _compare_attributes(
219             $extracted_attribute , $self->_get_attribute( $attribute_name )
220             );
221             }
222              
223 51         1786 $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   36 my( $self , $class ) = @_;
230 21         119 my $meta = $class->meta;
231              
232 21 50       492 map { $self->_dissect_role($_) } @{ $meta->roles } if ( $meta->can( 'roles' ));
  9         80  
  21         589  
233 21         3748 map { $self->_dissect_attribute( $meta , $_ ) } $meta->get_attribute_list;
  43         299  
234 21         321 map { $self->_dissect_method( $meta , $_ ) } $meta->get_method_list;
  147         11432  
235              
236 21         112 $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   199 my( $self , $meta , $method_name ) = @_;
243              
244 167 100       354 if ( $method_name =~ /^_/ ) {
245 8 100       209 return unless $self->include_private_methods;
246             }
247              
248 161         391 my $meta_method = $meta->get_method( $method_name );
249              
250 161         3178 my $src = $meta_method->original_package_name;
251              
252 161 100       5803 unless ( $self->include_accessors_in_method_list ) {
253 143 100       271 return if $self->_check_exclusion( $method_name );
254             }
255              
256 125 100       3079 unless ( $self->include_standard_methods ) {
257 107         200 my @STOCK = qw/ DESTROY meta new /;
258 107         147 foreach ( @STOCK ) {
259 255 100       532 return if $method_name eq $_;
260             }
261             }
262              
263 59         139 my $extracted_method = $self->_extract_method_metainfo( $meta_method );
264 59         2193 $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   21 my( $self , $meta ) = @_;
270              
271 10         60 map { $self->_dissect_attribute( $meta , $_ ) } $meta->get_attribute_list;
  11         107  
272 10         148 map { $self->_dissect_method( $meta , $_ ) } $meta->get_method_list;
  20         802  
273              
274 10         86 my @names = split '\|' , $meta->name;
275 10         36 foreach my $name ( @names ) {
276 11 50       354 next if $name =~ /Moose::Meta::Role::__ANON/;
277 11         31 $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   74 my( $self , $meta_attr ) = @_;
285              
286 51         73 my $return = {};
287              
288 51         104 foreach ( qw/ reader writer accessor / ) {
289 153 100       629 next unless my $fxn = $meta_attr->$_;
290 51         148 $self->_add_exclusion( $fxn );
291 51         123 $return->{$_} = $fxn;
292             }
293              
294 51 100       1812 $return->{meta}{documentation} = $meta_attr->documentation
295             if ( $meta_attr->has_documentation );
296              
297 51 100       2374 $return->{meta}{constraint} = $meta_attr->type_constraint->name
298             if ( $meta_attr->has_type_constraint );
299              
300 51 100       4369 $return->{meta}{traits} = $meta_attr->applied_traits
301             if ( $meta_attr->has_applied_traits );
302              
303 51         455 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       11691 $return->{meta}{$_}++ if $meta_attr->$_ ;
308             }
309              
310             ### FIXME should look at delegated methods and install exclusions for them
311              
312 51         253 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   72 my( $self , $meta_method ) = @_;
320              
321             return {
322 59         147 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   51 my( $self , $name ) = @_;
329              
330 32         76 my $path = $name . '.pm';
331 32         130 $path =~ s|::|/|g;
332 32 50       130 if ( $path = $INC{$path} ){
333             try {
334 32 50   32   926 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       34942 sub{ $_[1]->isa( 'PPI::Statement::Sub' ) && $_[1]->name }
339 32         291921 );
340              
341 32         401 foreach my $sub_node ( @$sub_nodes ) {
342 46         121 my $name = $sub_node->name;
343 46         760 $self->_store_sub_node( $name => $sub_node->content );
344             }
345 32         287 };
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   7 my( $module , $path ) = @_;
355              
356 3         12 $path =~ s/.pm$//;
357 3         17 my @path_parts = split '/' , $path;
358 3         9 my @module_parts = split /::/ , $module;
359 3         5 my @inc_path = ();
360              
361 3         9 while ( @path_parts ) {
362 14         21 my $path = join '/' , @path_parts;
363 14         13 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         8 my $inc_path = join '/' , @inc_path;
368              
369 3     2   222 eval "use lib '$inc_path'; require $module";
  2     1   10  
  2         2  
  2         14  
  1         7  
  1         1  
  1         8  
370 3 100       34651 die $@ if $@;
371              
372 2         15 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.6
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