File Coverage

blib/lib/Perl/Tags/Naive.pm
Criterion Covered Total %
statement 65 67 97.0
branch 16 22 72.7
condition 2 3 66.6
subroutine 14 14 100.0
pod 9 11 81.8
total 106 117 90.6


line stmt bran cond sub pod time code
1             package Perl::Tags::Naive;
2              
3 7     7   4268 use strict; use warnings;
  7     7   14  
  7         221  
  7         35  
  7         11  
  7         224  
4 7     7   34 use parent 'Perl::Tags';
  7         11  
  7         59  
5              
6             our $VERSION = '0.32';
7              
8             =head1 C<Perl::Tags::Naive>
9            
10             A naive implementation. That is to say, it's based on the classic C<pltags.pl>
11             script distributed with Perl, which is by and large a better bet than the
12             results produced by C<ctags>. But a "better" approach may be to integrate this
13             with PPI.
14            
15             =head2 Subclassing
16            
17             See L<TodoTagger> in the C<t/> directory of the distribution for a fully
18             working example (tested in <t/02_subclass.t>). You may want to reuse parsers
19             in the ::Naive package, or use all of the existing parsers and add your own.
20            
21             package My::Tagger;
22             use Perl::Tags;
23             use parent 'Perl::Tags::Naive';
24            
25             sub get_parsers {
26             my $self = shift;
27             return (
28             $self->can('todo_line'), # a new parser
29             $self->SUPER::get_parsers(), # all ::Naive's parsers
30             # or maybe...
31             $self->can('variable'), # one of ::Naive's parsers
32             );
33             }
34            
35             sub todo_line {
36             # your new parser code here!
37             }
38             sub package_line {
39             # override one of ::Naive's parsers
40             }
41            
42             Because ::Naive uses C<can('parser')> instead of C<\&parser>, you
43             can just override a particular parser by redefining in the subclass.
44            
45             =head2 C<get_tags_for_file>
46            
47             ::Naive uses a simple line-by-line analysis of Perl code, comparing
48             each line against an array of parsers returned by the L<get_parsers> method.
49            
50             The first of these parsers that matches (if any) will return the
51             tag/control to be registred by the tagger.
52            
53             =cut
54              
55             {
56             # Tags that start POD:
57                 my @start_tags = qw(pod head1 head2 head3 head4 over item back begin
58             end for encoding);
59                 my @end_tags = qw(cut);
60              
61                 my $startpod = '^=(?:' . join('|', @start_tags) . ')\b';
62                 my $endpod = '^=(?:' . join('|', @end_tags) . ')\b';
63              
64 12     12 0 459     sub STARTPOD { qr/$startpod/ }
65 12     12 0 93     sub ENDPOD { qr/$endpod/ }
66             }
67              
68             sub get_tags_for_file {
69 12     12 1 23     my ($self, $file) = @_;
70              
71 12         59     my @parsers = $self->get_parsers(); # function refs
72              
73 12 50       781     open (my $IN, '<', $file) or die "Couldn't open file `$file`: $!\n";
74              
75 12         37     my $start = STARTPOD();
76 12         34     my $end = ENDPOD();
77              
78 12         21     my @all_tags;
79              
80 12         266     while (<$IN>) {
81 190 100       534         next if (/$start/o .. /$end/o); # Skip over POD.
82 141         171         chomp;
83 141         213         my $statement = my $line = $_;
84 141         199         PARSELOOP: for my $parser (@parsers) {
85 1398         2401             my @tags = $parser->(
86                             $self,
87                           $line,
88                           $statement,
89                           $file
90                         );
91 1398         2389             push @all_tags, @tags;
92                     }
93                 }
94 12         290     return @all_tags;
95             }
96              
97             =head2 C<get_parsers>
98            
99             The following parsers are defined by this module.
100            
101             =over 4
102            
103             =cut
104              
105             sub get_parsers {
106 12     12 1 42     my $self = shift;
107                 return (
108 12         307         $self->can('trim'),
109                     $self->can('variable'),
110                     $self->can('package_line'),
111                     $self->can('sub_line'),
112                     $self->can('use_constant'),
113                     $self->can('use_line'),
114                     $self->can('label_line'),
115                 );
116             }
117              
118             =item C<trim>
119            
120             A filter rather than a parser, removes whitespace and comments.
121            
122             =cut
123              
124             sub trim {
125 141     141 1 149     shift;
126             # naughtily work on arg inplace
127 141         236     $_[1] =~ s/#.*//; # remove comment. Naively
128 141         419     $_[1] =~ s/^\s*//; # Trim spaces
129 141         638     $_[1] =~ s/\s*$//;
130              
131 141         240     return;
132             }
133              
134             =item C<variable>
135            
136             Tags definitions of C<my>, C<our>, and C<local> variables.
137            
138             Returns a L<Perl::Tags::Tag::Var> if found
139            
140             =cut
141              
142             sub variable {
143             # don't handle continuing thingy for now
144 141     141 1 223     my ($self, $line, $statement, $file) = @_;
145              
146 141 50       424     return unless $self->{do_variables};
147             # I'm not sure I see this as all that useful
148              
149 141 100 66     666     if ($self->{var_continues} || $statement =~/^(my|our|local)\b/) {
150              
151 7         43         $self->{current}{var_continues} = ! ($statement=~/;$/);
152 7         18         $statement =~s/=.*$//;
153             # remove RHS with extreme prejudice
154             # and also not accounting for things like
155             # my $x=my $y=my $z;
156              
157 7         55         my @vars = $statement=~/[\$@%]((?:\w|:)+)\b/g;
158              
159             # use Data::Dumper;
160             # print Dumper({ vars => \@vars, statement => $statement });
161              
162 14         107         return map {
163 7         16             Perl::Tags::Tag::Var->new(
164                             name => $_,
165                             file => $file,
166                             line => $line,
167                             linenum => $.,
168                         );
169                     } @vars;
170                 }
171 134         246     return;
172             }
173              
174             =item C<package_line>
175            
176             Parse a package declaration, returning a L<Perl::Tags::Tag::Package> if found.
177            
178             =cut
179              
180             sub package_line {
181 141     141 1 210     my ($self, $line, $statement, $file) = @_;
182              
183 141 100       338     if ($statement=~/^package\s+((?:\w|:)+)\b/) {
184                     return (
185 12         148             Perl::Tags::Tag::Package->new(
186                             name => $1,
187                             file => $file,
188                             line => $line,
189                             linenum => $.,
190                         )
191                     );
192                 }
193 129         240     return;
194             }
195              
196             =item C<sub_line>
197            
198             Parse the declaration of a subroutine, returning a L<Perl::Tags::Tag::Sub> if found.
199            
200             =cut
201              
202             sub sub_line {
203 141     141 1 207     my ($self, $line, $statement, $file) = @_;
204 141 100       314     if ($statement=~/sub\s+(\w+)\b/) {
205                     return (
206 7         75             Perl::Tags::Tag::Sub->new(
207                             name => $1,
208                             file => $file,
209                             line => $line,
210                             linenum => $.,
211                         )
212                     );
213                 }
214              
215 134         217     return;
216             }
217              
218             =item C<use_constant>
219            
220             Parse a use constant directive
221            
222             =cut
223              
224             sub use_constant {
225 141     141 1 213     my ($self, $line, $statement, $file) = @_;
226 141 50       274     if ($statement =~/^\s*use\s+constant\s+([^=[:space:]]+)/) {
227                     return (
228 0         0             Perl::Tags::Tag::Constant->new(
229                             name => $1,
230                             file => $file,
231                             line => $line,
232                             linenum => $.,
233                         )
234                     );
235                 }
236 141         284     return;
237             }
238              
239             =item C<use_line>
240            
241             Parse a use, require, and also a use_ok line (from Test::More).
242             Uses a dummy tag (L<Perl::Tags::Tag::Recurse> to do so).
243            
244             =cut
245              
246             sub use_line {
247 141     141 1 196     my ($self, $line, $statement, $file) = @_;
248              
249 141         149     my @ret;
250 141 100       359     if ($statement=~/^(?:use|require)(_ok\(?)?\s+(.*)/) {
251 19         78         my @packages = split /\s+/, $2; # may be more than one if base
252 19 50       66         @packages = ($packages[0]) if $1; # if use_ok ecc. from Test::More
253              
254 19         37         for (@packages) {
255 33         60             s/^q[wq]?[[:punct:]]//;
256 33         117             /((?:\w|:)+)/;
257 33 50       259             $1 and push @ret, Perl::Tags::Tag::Recurse->new(
258                             name => $1,
259                             line=>'dummy' );
260                     }
261                 }
262 141         276     return @ret;
263             }
264              
265             =item C<label_line>
266            
267             Parse label declaration
268            
269             =cut
270              
271             sub label_line {
272 141     141 1 208     my ($self, $line, $statement, $file) = @_;
273 141 50       340     if ($statement=~/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:(?:[^:]|$)/) {
274                     return (
275 0         0             Perl::Tags::Tag::Label->new(
276                             name => $1,
277                             file => $file,
278                             line => $line,
279                             linenum => $.,
280                         )
281                     );
282                 }
283 141         231     return;
284             }
285              
286             =back
287            
288             =cut
289              
290             1;
291