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.21';
5 2     2   182525 use 5.008;
  2         12  
6 2     2   9 use strict;
  2         2  
  2         33  
7 2     2   8 use warnings;
  2         3  
  2         45  
8 2     2   8 use Carp;
  2         3  
  2         81  
9 2     2   755 use Try::Tiny;
  2         3239  
  2         90  
10 2     2   10 use base 'PPIx::EditorTools';
  2         2  
  2         455  
11 2     2   11 use Class::XSAccessor accessors => {};
  2         3  
  2         10  
12              
13 2     2   220 use PPI;
  2         3  
  2         576  
14              
15             sub find {
16 9     9 1 22950 my ( $self, %args ) = @_;
17 9         42 $self->process_doc(%args);
18              
19 9         22 my $ppi = $self->ppi;
20              
21 9 50       21 return [] unless defined $ppi;
22 9         28 $ppi->index_locations;
23              
24             # Search for interesting things
25 9         24218 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   9289 return 1 if ref $_[0] eq 'PPI::Statement::Package';
33 1131 100       1511 return 1 if ref $_[0] eq 'PPI::Statement::Include';
34 1114 100       1459 return 1 if ref $_[0] eq 'PPI::Statement::Sub';
35 1108 100       2052 return 1 if ref $_[0] eq 'PPI::Statement';
36             }
37 9         926 )->in($ppi);
38              
39             # Define a flag indicating that further Method::Signature/Moose check should run
40 9         154 my $check_alternate_sub_decls = 0;
41              
42             # Build the outline structure from the search results
43 9         17 my @outline = ();
44 9         13 my $cur_pkg = {};
45 9         13 my $not_first_one = 0;
46 9         20 foreach my $thing (@things) {
47 63 100       1111 if ( ref $thing eq 'PPI::Statement::Package' ) {
    100          
    100          
    50          
48 2 50       6 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         4 $not_first_one = 1;
56 2         9 $cur_pkg->{name} = $thing->namespace;
57 2         53 $cur_pkg->{line} = $thing->location->[0];
58             } elsif ( ref $thing eq 'PPI::Statement::Include' ) {
59 17 50       56 next if $thing->type eq 'no';
60 17 100       313 if ( $thing->pragma ) {
    100          
61 9         220 push @{ $cur_pkg->{pragmata} }, { name => $thing->pragma, line => $thing->location->[0] };
  9         20  
62             } elsif ( $thing->module ) {
63 7         287 push @{ $cur_pkg->{modules} }, { name => $thing->module, line => $thing->location->[0] };
  7         22  
64 7 50       213 unless ($check_alternate_sub_decls) {
65             $check_alternate_sub_decls = 1
66 7 100       13 if grep { $thing->module eq $_ } (
  35         467  
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         9 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         76 my $node1 = $thing->first_element;
81 38         115 my $node2 = $thing->child(2);
82              
83 38 100       218 next unless defined $node2;
84              
85             # Tests for has followed by new line
86             try {
87 2     2   25 no warnings 'exiting'; # suppress warning Exiting eval via next
  2         4  
  2         1353  
88 35 100   35   679 if ( defined $node2->{content} ) {
89 33 50       79 if ( $node2->{content} =~ /\n/ ) {
90 0         0 next;
91             }
92             }
93 35         139 };
94              
95             # Moose attribute declaration
96 35 100 66     386 if ( $node1->isa('PPI::Token::Word') && $node1->content eq 'has' ) {
97              
98             # p $_[1]->next_sibling->isa('PPI::Token::Whitespace');
99 17         91 $self->_Moo_Attributes( $node2, $cur_pkg, $thing );
100 17         37 next;
101             }
102              
103             # MooseX::POE event declaration
104 18 50 33     106 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       97 if ($check_alternate_sub_decls) {
112             $ppi->find(
113             sub {
114 886 100   886   7774 $_[1]->isa('PPI::Token::Word') or return 0;
115 129 100       186 $_[1]->content =~ /^(?:func|method|before|after|around|override|augment|class|role)\z/ or return 0;
116 19 50       105 $_[1]->next_sibling->isa('PPI::Token::Whitespace') or return 0;
117 19 50       469 my $sib_content = $_[1]->next_sibling->next_sibling->content or return 0;
118              
119 19         1145 my $name = eval $sib_content;
120              
121             # if eval() failed for whatever reason, default to original trimmed original token
122 19   66     121 $name ||= ( $sib_content =~ m/^\b(\w+)\b/ )[0];
123              
124 19 50       33 return 0 unless defined $name;
125              
126             # test for MooseX::Declare class, role
127 19 100       46 if ( $_[1]->content =~ m/(class|role)/ ) {
128 4         34 $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         68 push @{ $cur_pkg->{methods} }, { name => $name, line => $_[1]->line_number };
  15         50  
133              
134 15         216 return 1;
135             }
136 5         33 );
137             }
138              
139 9 100       82 if ( not $cur_pkg->{name} ) {
140 4         8 $cur_pkg->{name} = 'main';
141             }
142              
143 9         14 push @outline, $cur_pkg;
144              
145 9         35 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   28 my ( $self, $ma_node2, $ma_cur_pkg, $ma_thing ) = @_;
156              
157 17         45 my $line_num = $ma_thing->location->[0];
158              
159 17 100       234 if ( $ma_node2->content =~ /[\n|;]/ ) {
160 1         6 return;
161             }
162              
163 16         110 my $attrs = eval $ma_node2->content;
164              
165             # if eval() failed for whatever reason, default to original token
166 16   66     146 $attrs ||= $ma_node2->content;
167              
168 16 100       47 if ( ref $attrs eq 'ARRAY' ) {
169 10         11 map { push @{ $ma_cur_pkg->{attributes} }, { name => $_, line => $line_num, } }
  10         23  
170 2         4 grep {defined} @{$attrs};
  10         14  
  2         5  
171              
172             } else {
173              
174 14         17 push @{ $ma_cur_pkg->{attributes} },
  14         44  
175             {
176             name => $attrs,
177             line => $line_num,
178             };
179             }
180 16         27 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   10 my ( $self, $mpn_cur_pkg, $mpn_sib_content, $mpn_ppi_tuple ) = @_;
190 4 100       10 if ( $mpn_cur_pkg->{name} ) { return 1; } # break if we have a pkg name
  1         3  
191             # add to outline
192 3         5 $mpn_cur_pkg->{name} = $mpn_sib_content; # class or role name
193 3         8 $mpn_cur_pkg->{line} = $mpn_ppi_tuple->line_number; # class or role location
194 3         42 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.21
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.
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 to process or a string containing
237             the code (which will be converted into a C) 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.
255             Also see L, L, and L.
256              
257             =head1 AUTHORS
258              
259             =over 4
260              
261             =item *
262              
263             Steffen Mueller C
264              
265             =item *
266              
267             Mark Grimes C
268              
269             =item *
270              
271             Ahmad M. Zawawi
272              
273             =item *
274              
275             Gabor Szabo
276              
277             =item *
278              
279             Yanick Champoux
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__