File Coverage

blib/lib/PPIx/EditorTools/Outline.pm
Criterion Covered Total %
statement 108 116 93.1
branch 52 64 81.2
condition 7 12 58.3
subroutine 15 15 100.0
pod 1 1 100.0
total 183 208 87.9


line stmt bran cond sub pod time code
1             package PPIx::EditorTools::Outline;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Collect use pragmata, modules, subroutiones, methods, attributes
4             $PPIx::EditorTools::Outline::VERSION = '0.20';
5 2     2   186064 use 5.008;
  2         16  
6 2     2   10 use strict;
  2         3  
  2         35  
7 2     2   8 use warnings;
  2         3  
  2         48  
8 2     2   7 use Carp;
  2         4  
  2         87  
9 2     2   536 use Try::Tiny;
  2         2853  
  2         94  
10 2     2   11 use base 'PPIx::EditorTools';
  2         4  
  2         377  
11 2     2   12 use Class::XSAccessor accessors => {};
  2         4  
  2         9  
12              
13 2     2   233 use PPI;
  2         3  
  2         633  
14              
15             sub find {
16 9     9 1 23143 my ( $self, %args ) = @_;
17 9         46 $self->process_doc(%args);
18              
19 9         29 my $ppi = $self->ppi;
20              
21 9 50       27 return [] unless defined $ppi;
22 9         36 $ppi->index_locations;
23              
24             # Search for interesting things
25 9         25321 require PPI::Find;
26              
27             # TODO things not very discriptive
28             my @things = PPI::Find->new(
29             sub {
30              
31             # This is a fairly ugly search
32 1133 100   1133   9718 return 1 if ref $_[0] eq 'PPI::Statement::Package';
33 1131 100       1623 return 1 if ref $_[0] eq 'PPI::Statement::Include';
34 1114 100       1591 return 1 if ref $_[0] eq 'PPI::Statement::Sub';
35 1108 100       2221 return 1 if ref $_[0] eq 'PPI::Statement';
36             }
37 9         831 )->in($ppi);
38              
39             # Define a flag indicating that further Method::Signature/Moose check should run
40 9         162 my $check_alternate_sub_decls = 0;
41              
42             # Build the outline structure from the search results
43 9         16 my @outline = ();
44 9         15 my $cur_pkg = {};
45 9         13 my $not_first_one = 0;
46 9         17 foreach my $thing (@things) {
47 63 100       879 if ( ref $thing eq 'PPI::Statement::Package' ) {
    100          
    100          
    50          
48 2 50       5 if ($not_first_one) {
49 0 0       0 if ( not $cur_pkg->{name} ) {
50 0         0 $cur_pkg->{name} = 'main';
51             }
52 0         0 push @outline, $cur_pkg;
53 0         0 $cur_pkg = {};
54             }
55 2         3 $not_first_one = 1;
56 2         10 $cur_pkg->{name} = $thing->namespace;
57 2         52 $cur_pkg->{line} = $thing->location->[0];
58             } elsif ( ref $thing eq 'PPI::Statement::Include' ) {
59 17 50       46 next if $thing->type eq 'no';
60 17 100       312 if ( $thing->pragma ) {
    100          
61 9         208 push @{ $cur_pkg->{pragmata} }, { name => $thing->pragma, line => $thing->location->[0] };
  9         26  
62             } elsif ( $thing->module ) {
63 7         297 push @{ $cur_pkg->{modules} }, { name => $thing->module, line => $thing->location->[0] };
  7         21  
64 7 50       228 unless ($check_alternate_sub_decls) {
65             $check_alternate_sub_decls = 1
66 7 100       14 if grep { $thing->module eq $_ } (
  35         496  
67             'Method::Signatures',
68             'MooseX::Declare',
69             'MooseX::Method::Signatures',
70             'Moose::Role',
71             'Moose',
72             );
73             }
74             }
75             } elsif ( ref $thing eq 'PPI::Statement::Sub' ) {
76 6         8 push @{ $cur_pkg->{methods} }, { name => $thing->name, line => $thing->location->[0] };
  6         20  
77             } elsif ( ref $thing eq 'PPI::Statement' ) {
78              
79             # last resort, let's analyse further down...
80 38         83 my $node1 = $thing->first_element;
81 38         131 my $node2 = $thing->child(2);
82              
83 38 100       230 next unless defined $node2;
84              
85             # Tests for has followed by new line
86             try {
87 2     2   18 no warnings 'exiting'; # suppress warning Exiting eval via next
  2         3  
  2         1206  
88 35 100   35   760 if ( defined $node2->{content} ) {
89 33 50       112 if ( $node2->{content} =~ /\n/ ) {
90 0         0 next;
91             }
92             }
93 35         148 };
94              
95             # Moose attribute declaration
96 35 100 66     414 if ( $node1->isa('PPI::Token::Word') && $node1->content eq 'has' ) {
97              
98             # p $_[1]->next_sibling->isa('PPI::Token::Whitespace');
99 17         99 $self->_Moo_Attributes( $node2, $cur_pkg, $thing );
100 17         49 next;
101             }
102              
103             # MooseX::POE event declaration
104 18 50 33     110 if ( $node1->isa('PPI::Token::Word') && $node1->content eq 'event' ) {
105 0         0 push @{ $cur_pkg->{events} }, { name => $node2->content, line => $thing->location->[0] };
  0         0  
106 0         0 next;
107             }
108             }
109             }
110              
111 9 100       105 if ($check_alternate_sub_decls) {
112             $ppi->find(
113             sub {
114 886 100   886   8556 $_[1]->isa('PPI::Token::Word') or return 0;
115 129 100       199 $_[1]->content =~ /^(?:func|method|before|after|around|override|augment|class|role)\z/ or return 0;
116 19 50       112 $_[1]->next_sibling->isa('PPI::Token::Whitespace') or return 0;
117 19 50       457 my $sib_content = $_[1]->next_sibling->next_sibling->content or return 0;
118              
119 19         1188 my $name = eval $sib_content;
120              
121             # if eval() failed for whatever reason, default to original trimmed original token
122 19   66     138 $name ||= ( $sib_content =~ m/^\b(\w+)\b/ )[0];
123              
124 19 50       34 return 0 unless defined $name;
125              
126             # test for MooseX::Declare class, role
127 19 100       44 if ( $_[1]->content =~ m/(class|role)/ ) {
128 4         35 $self->_Moo_PkgName( $cur_pkg, $sib_content, $_[1] );
129 4         8 return 1; # break out so we don't write Package name as method
130             }
131              
132 15         67 push @{ $cur_pkg->{methods} }, { name => $name, line => $_[1]->line_number };
  15         44  
133              
134 15         219 return 1;
135             }
136 5         38 );
137             }
138              
139 9 100       84 if ( not $cur_pkg->{name} ) {
140 4         7 $cur_pkg->{name} = 'main';
141             }
142              
143 9         18 push @outline, $cur_pkg;
144              
145 9         37 return \@outline;
146             }
147              
148             ########
149             # Composed Method, internal, Moose Attributes
150             # cleans moose attributes up, and single lines them.
151             # only runs if PPI finds has
152             # prefix all vars with ma_ otherwise same name
153             ########
154             sub _Moo_Attributes {
155 17     17   29 my ( $self, $ma_node2, $ma_cur_pkg, $ma_thing ) = @_;
156              
157 17         38 my $line_num = $ma_thing->location->[0];
158              
159 17 100       248 if ( $ma_node2->content =~ /[\n|;]/ ) {
160 1         6 return;
161             }
162              
163 16         123 my $attrs = eval $ma_node2->content;
164              
165             # if eval() failed for whatever reason, default to original token
166 16   66     176 $attrs ||= $ma_node2->content;
167              
168 16 100       51 if ( ref $attrs eq 'ARRAY' ) {
169 10         15 map { push @{ $ma_cur_pkg->{attributes} }, { name => $_, line => $line_num, } }
  10         28  
170 2         4 grep {defined} @{$attrs};
  10         21  
  2         7  
171              
172             } else {
173              
174 14         18 push @{ $ma_cur_pkg->{attributes} },
  14         55  
175             {
176             name => $attrs,
177             line => $line_num,
178             };
179             }
180 16         31 return;
181             }
182              
183             ########
184             # Composed Method, internal, Moose Pakage Name
185             # write first Class or Role as Package Name if none
186             # prefix all vars with mpn_ otherwise same name
187             ########
188             sub _Moo_PkgName {
189 4     4   9 my ( $self, $mpn_cur_pkg, $mpn_sib_content, $mpn_ppi_tuple ) = @_;
190 4 100       9 if ( $mpn_cur_pkg->{name} ) { return 1; } # break if we have a pkg name
  1         2  
191             # add to outline
192 3         6 $mpn_cur_pkg->{name} = $mpn_sib_content; # class or role name
193 3         7 $mpn_cur_pkg->{line} = $mpn_ppi_tuple->line_number; # class or role location
194 3         43 return 1;
195             }
196              
197             1;
198              
199             =pod
200              
201             =encoding UTF-8
202              
203             =head1 NAME
204              
205             PPIx::EditorTools::Outline - Collect use pragmata, modules, subroutiones, methods, attributes
206              
207             =head1 VERSION
208              
209             version 0.20
210              
211             =head1 SYNOPSIS
212              
213             my $outline = PPIx::EditorTools::Outline->new->find(
214             code => "package TestPackage;\nsub x { 1;\n"
215             );
216             print Dumper $outline;
217              
218             =head1 DESCRIPTION
219              
220             Return a list of pragmatas, modules, methods, attributes of a C<PPI::Document>.
221              
222             =head1 METHODS
223              
224             =over 4
225              
226             =item * new()
227              
228             Constructor. Generally shouldn't be called with any arguments.
229              
230             =item * find()
231              
232             find( ppi => PPI::Document $ppi )
233             or
234             find( code => Str $code )
235              
236             Accepts either a C<PPI::Document> to process or a string containing
237             the code (which will be converted into a C<PPI::Document>) to process.
238             Return a reference to a hash.
239              
240             =back
241              
242             =head2 Internal Methods
243              
244             =over 4
245              
246             =item * _Moo_Attributes
247              
248             =item * _Moo_PkgName
249              
250             =back
251              
252             =head1 SEE ALSO
253              
254             This class inherits from C<PPIx::EditorTools>.
255             Also see L<App::EditorTools>, L<Padre>, and L<PPI>.
256              
257             =head1 AUTHORS
258              
259             =over 4
260              
261             =item *
262              
263             Steffen Mueller C<smueller@cpan.org>
264              
265             =item *
266              
267             Mark Grimes C<mgrimes@cpan.org>
268              
269             =item *
270              
271             Ahmad M. Zawawi <ahmad.zawawi@gmail.com>
272              
273             =item *
274              
275             Gabor Szabo <gabor@szabgab.com>
276              
277             =item *
278              
279             Yanick Champoux <yanick@cpan.org>
280              
281             =back
282              
283             =head1 COPYRIGHT AND LICENSE
284              
285             This software is copyright (c) 2017, 2014, 2012 by The Padre development team as listed in Padre.pm..
286              
287             This is free software; you can redistribute it and/or modify it under
288             the same terms as the Perl 5 programming language system itself.
289              
290             =cut
291              
292             __END__
293              
294              
295              
296              
297             # Copyright 2008-2012 The Padre development team as listed in Padre.pm.
298             # LICENSE
299             # This program is free software; you can redistribute it and/or
300             # modify it under the same terms as Perl 5 itself.