File Coverage

blib/lib/Devel/PerlySense/Document/Meta.pm
Criterion Covered Total %
statement 179 185 96.7
branch 71 76 93.4
condition 45 49 91.8
subroutine 23 23 100.0
pod 8 8 100.0
total 326 341 95.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Devel::PerlySense::Document::Meta - Document information generated
4             during a parse
5              
6             =cut
7              
8              
9              
10              
11              
12 68     68   249 use strict;
  68         84  
  68         1641  
13 68     68   212 use warnings;
  68         82  
  68         1342  
14 68     68   206 use utf8;
  68         71  
  68         325  
15              
16             package Devel::PerlySense::Document::Meta;
17             $Devel::PerlySense::Document::Meta::VERSION = '0.0218';
18              
19              
20              
21              
22              
23 68     68   2817 use Spiffy -Base;
  68         73  
  68         340  
24 68     68   114982 use Carp;
  68     68   82  
  68     68   1022  
  68         188  
  68         74  
  68         1239  
  68         184  
  68         68  
  68         2902  
25 68     68   233 use File::Basename;
  68         75  
  68         2627  
26 68     68   220 use Path::Class;
  68         80  
  68         2199  
27 68     68   213 use Data::Dumper;
  68         89  
  68         1931  
28 68     68   247 use PPI::Document;
  68         76  
  68         1092  
29 68     68   27072 use PPI::Dumper;
  68         48948  
  68         5109  
30              
31              
32              
33              
34              
35             =head1 PROPERTIES
36              
37             =head2 raPackage
38              
39             Package declarations.
40              
41             Array ref with cloned PPI::Statement::Package objects.
42              
43             Default: []
44              
45             =cut
46             field "raPackage" => [];
47              
48              
49              
50              
51              
52             =head2 raNameModuleUse
53              
54             Array ref with module names that are "use"d.
55              
56             Default: []
57              
58             =cut
59             field "raNameModuleUse" => [];
60              
61              
62              
63              
64              
65             =head2 raNameModuleBase
66              
67             Array ref with module names that are base classes.
68              
69             Default: []
70              
71             =cut
72             field "raNameModuleBase" => [];
73              
74              
75              
76              
77              
78             =head2 rhRowColModule
79              
80             Module names.
81              
82             Hash ref with (keys: row, values:
83             hash ref with (keys: col, values:
84             hash with keys:
85             oNode => cloned PPI::Node objects
86             module => module name string
87             )
88             )
89             )
90              
91             rhRowColModule->{43}->{2}-> node
92              
93             Default: {}
94              
95             =cut
96             field "rhRowColModule" => {};
97              
98              
99              
100              
101              
102             =head2 rhRowColMethod
103              
104             Method calls.
105              
106             Hash ref with (keys: row, values:
107             hash ref with (keys: col, values:
108             {
109             oNode => cloned PPI::Node object,
110             oNodePrev => node to the left of the ->
111             }
112             )
113             )
114              
115             rhRowColModule->{43}->{2}-> node
116              
117             Default: {}
118              
119             =cut
120             field "rhRowColMethod" => {};
121              
122              
123              
124              
125              
126             =head2 raLocationPod
127              
128             POD blocks.
129              
130             Array ref with Location objects, representing each pod chunk that is a
131             heading/item. They have the following properties:
132              
133             podSection
134             pod
135              
136             Default: []
137              
138             =cut
139             field "raLocationPod" => [];
140              
141              
142              
143              
144              
145             =head2 raLocationSub
146              
147             sub definition.
148              
149             Array ref with Location objects, representing each sub
150             declaration. They have the following properties:
151              
152             nameSub
153             source
154             namePackage
155             oLocationEnd
156              
157             Default: []
158              
159             =cut
160             field "raLocationSub" => [];
161              
162              
163              
164              
165              
166              
167              
168              
169             =head2 aPluginSyntax
170              
171             Array ref with Devel::PerlySense::Plugin::Syntax objects.
172              
173             Return whatever plugins under Devel::PerlySense::Plugin::Syntax::* are
174             found.
175              
176             Readonly.
177              
178             =cut
179              
180             use Module::Pluggable (
181 68         706 sub_name => "raPluginSyntax",
182             search_path => [ "Devel::PerlySense::Plugin::Syntax" ],
183             instantiate => "new",
184 68     68   355 );
  68         87  
185              
186             my $raPluginSyntax;
187 337     337 1 498 sub aPluginSyntax {
188 337   100     1233 $raPluginSyntax ||= [ $self->raPluginSyntax ];
189 337         60604 return @$raPluginSyntax;
190             }
191              
192              
193              
194              
195              
196             =head1 API METHODS
197              
198             =head2 new()
199             Create new empty Meta object
200              
201             =cut
202             sub new(@) {
203 336     336 1 1060 my $pkg = shift;
204              
205 336         872 my $self = bless {}, $pkg;
206              
207 336         754 return($self);
208             }
209              
210              
211              
212              
213              
214             =head2 parse($oDocument)
215              
216             Parse the Devel::PerlySense::Document and extract metadata. Fill
217             appropriate data structures.
218              
219             Return 1 or die on errors.
220              
221             =cut
222             sub _setRowColNodeModule(\%$$$$) {
223 6230     6230   19159 my ($rhRowCol, $row, $col, $oNode, $module) = @_;
224              
225 6230         25749 $rhRowCol->{$row}->{$col} = {
226             oNode => $oNode,
227             module => $module,
228             };
229              
230 6230         9165 return;
231             }
232              
233 335     335 1 491 sub parse {
234 335         450 my ($oDocument) = @_;
235             #PPI::Dumper->new($oDocument->oDocument)->print; use PPI::Dumper;
236              
237 335         509 my @aToken;
238             my @aPackage;
239 0         0 my %hNameModuleUse;
240 0         0 my %hNameModuleBase;
241 0         0 my %hRowColModule;
242 0         0 my %hRowColMethod;
243 0         0 my @aLocationPod;
244 0         0 my @aPodHeadingCurrent;
245 335         599 my $packageCurrent = "main";
246 335         2377 my $rhDataDocument = {
247             raPackage => \@aPackage,
248             rhNameModuleUse => \%hNameModuleUse,
249             rhNameModuleBase => \%hNameModuleBase,
250             rhRowColModule => \%hRowColModule,
251             rhRowColMethod => \%hRowColMethod,
252             raLocationPod => \@aLocationPod,
253             };
254              
255             #Optimization, avoid the method call inside the loop
256 335         1098 my @aPluginSyntax = $self->aPluginSyntax();
257              
258             $oDocument->aDocumentFind(
259             sub {
260 523635     523635   3540591 my ($oTop, $oNode) = @_;
261 523635 50       821728 my $oLocation = $oNode->location or return(0);
262 523635         3500876 eval {
263              
264 523635         456079 my ($row, $col) = ($oLocation->[0], $oLocation->[1]);
265              
266             #Optimization: compare against the string instead of
267             #doing insanely many ->isa(). This is slightly fragile
268             #wrt changes in subclasses in PPI.
269 523635         432393 my $pkgNode = ref($oNode);
270              
271              
272              
273             #Collect tokens
274 523635 100 66     1486131 if($pkgNode =~ /^PPI::Token/ && $oNode->location) {
275 431136 100 100     3378852 if($pkgNode =~ /^PPI::Token::QuoteLike/ || $pkgNode =~ /^PPI::Token::Quote/) {
276 5350         6494 push(@aToken, $oNode);
277             } else {
278             #...we're only interested in nodes which are single words
279 425786 100       617908 if( $oNode !~ /\s/) {
280 247677         787551 push(@aToken, $oNode);
281             }
282             }
283             }
284              
285              
286              
287              
288             #package
289 523635 100       1261433 if($pkgNode eq "PPI::Statement::Package") {
290 332         568 push(@aPackage, $oNode);
291 332         1543 $packageCurrent = $oNode->namespace;
292             }
293              
294              
295              
296             #use
297 523635 100       611556 if($pkgNode eq "PPI::Statement::Include") {
298 3144 100       5017 $hNameModuleUse{$1}++ if($oNode =~ /^ use \s+ ( [A-Z][\w:]* ) /xs);
299             }
300              
301              
302              
303              
304             #base class
305              
306             # use base
307 523635 100       689837 if($pkgNode eq "PPI::Statement::Include") {
308 3144 100       4931 if($oNode =~ /^ use \s+ (?:base|parent) \s+ (?:qw)? \s* (.+);$/xs) {
309 157         2978 my $modules = $1;
310 157         736 for my $module (grep { $_ ne "qw" } $modules =~ /([\w:]+)/gs) {
  169         494  
311 169         456 $hNameModuleBase{$module}++ ;
312             }
313              
314             }
315             }
316              
317             # @ISA = ...
318             ## fragile: stuff to the right...
319 523635 100 100     821223 if($pkgNode eq "PPI::Token::Symbol" && $oNode eq '@ISA') {
320 25         404 my $oStatement = $oNode->statement;
321              
322             ###TODO: ignore module names with interpolated variables
323 25 100       356 if($oStatement =~ /\@ISA \s* = \s* (.+);$/xs) {
324 17         520 my $modules = $1;
325 17         87 for my $module (grep { $_ ne "qw" } $modules =~ /([\w:]+)/gs) {
  34         344  
326 29         67 $hNameModuleBase{$module}++ ;
327             }
328             }
329             }
330              
331             #push(@ISA, )
332             ## fragile: "push() if(sdfkjs)" doesn't work
333 523635 100 100     1059864 if($pkgNode eq "PPI::Token::Symbol" && $oNode eq '@ISA' && @aToken > 2) {
      66        
334 25         333 my $prev = -1; #last one is the '@ISA'
335              
336 25 100 66     59 if($aToken[--$prev] eq "push" || $aToken[--$prev] eq "push") {
337 8         188 my $oStatement = $oNode->parent->parent;
338 8 50       83 $oStatement =~ /\@ISA \s* , \s* (.+)/xs or next;
339 8         292 my $modules = $1;
340              
341 8         62 $hNameModuleBase{$_}++ for($modules =~ /([\w:]+)/gs);
342             }
343             }
344              
345              
346              
347              
348             #module
349 523635 100 100     1345549 if(
    100          
350             $pkgNode eq "PPI::Token::Word" &&
351             $oNode =~ /^[A-Z][\w:]*$/ #Word chars and ::, Starts with uppercase, is pragma or number
352             ) {
353 7835 100 100     58155 if( ! ($aToken[-2]->isa("PPI::Token::Operator") && $aToken[-2] eq "->") ) {
354 5820         16461 _setRowColNodeModule(%hRowColModule, $row, $col, $oNode, "$oNode");
355             }
356             }
357             elsif(
358             $pkgNode =~ /^PPI::Token::Quote::/
359             # || $pkgNode =~ /^PPI::Token::QuoteLike/ ##TODO: enable when PPI gets "string" method on these classes
360             ) {
361 5070         15083 my $module = $oNode->string;
362 5070 100       37134 if($module =~ /^ [A-Z]\w* (?: :: [A-Z]\w* )+ $/x) {
    100          
363             #Well formed, likely module, i.e. at least one :: separator
364 355         1063 _setRowColNodeModule(%hRowColModule, $row, $col, $oNode, $module);
365             }
366             elsif($module =~ /^[A-Z][\w]*$/) {
367             #Check whether there is a file anywhere matching the name (because only the string contents is a weak indicator of module-ness).
368 526 100       2362 if($oDocument->fileFindModule(nameModule => $module)) {
369 55         207 _setRowColNodeModule(%hRowColModule, $row, $col, $oNode, $module);
370             }
371             }
372             }
373              
374              
375              
376              
377             #method
378 523635 100 100     981113 if($pkgNode eq "PPI::Token::Word" && @aToken > 2) {
379 60118         71819 my ($oObject, $oOperator) = @aToken[-3, -2];
380 60118 100 100     183306 if($oOperator->isa("PPI::Token::Operator") && $oOperator eq "->") {
381 17837 100 100     208317 $oObject->isa("PPI::Token::Symbol") || $oObject->isa("PPI::Token::Word") or $oObject = undef;
382             #print "$row/$col: ($oObject$oOperator$oNode)\n";
383 17837         66443 $hRowColMethod{$row}->{$col} = {
384             oNode => $oNode,
385             oNodeObject => $oObject,
386             };
387             }
388              
389             }
390              
391              
392              
393             #pod
394 523635 100       652516 if($pkgNode eq "PPI::Token::Pod") {
395 4915         12954 $self->parsePod($oDocument, $oNode, \@aLocationPod, \@aPodHeadingCurrent);
396             }
397              
398              
399              
400             #sub
401 523635         380215 my $nameSub = "";
402 523635 100 100     692116 $pkgNode eq "PPI::Statement::Sub" && !$oNode->forward and $nameSub = $oNode->name;
403 523635 100       729162 $pkgNode eq "PPI::Statement::Scheduled" and $nameSub = $oNode->type;
404 523635 100       559493 if($nameSub) {
405             push(
406 3429         3262 @{$self->raLocationSub},
  3429         66054  
407             $self->oLocationSub(
408             $oDocument,
409             $oNode,
410             $nameSub,
411             $packageCurrent,
412             ),
413             );
414             }
415              
416              
417 523635         482465 for my $plugin (@aPluginSyntax) {
418             #TODO: Set new $packageCurrent if needed
419 521049         900743 $plugin->parse(
420             rhDataDocument => $rhDataDocument,
421             oMeta => $self,
422             oDocument => $oDocument,
423             oNode => $oNode,
424             pkgNode => $pkgNode,
425             row => $row,
426             col => $col,
427             packageCurrent => $packageCurrent,
428             raToken => \@aToken,
429             );
430             }
431             };
432 523635 50       589360 $@ and warn($@);
433              
434 523635         745023 return(0);
435 335         4143 });
436              
437 335         21850 $self->raPackage(\@aPackage);
438 335         10785 $self->raNameModuleUse([sort keys %hNameModuleUse]);
439 335         8302 $self->raNameModuleBase([sort keys %hNameModuleBase]);
440 335         7261 $self->rhRowColModule(\%hRowColModule);
441 335         6999 $self->rhRowColMethod(\%hRowColMethod);
442 335         6945 $self->raLocationPod(\@aLocationPod);
443              
444 335         27351 return(1);
445             }
446              
447              
448              
449              
450              
451             =head2 moduleAt(row => $row, col => $col)
452              
453             Find the module mentioned on line $row (1..) at $col (1..).
454              
455             Return string like "My::Module" or "Module", or undef if none was
456             found.
457              
458             =cut
459 38     38 1 160 sub moduleAt {
460 38         96 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
461 38 100       506 my $rhToken = $self->rhTokenOfAt($self->rhRowColModule, $row, $col) or return(undef);
462 12         62 return( $rhToken->{module} );
463             }
464              
465              
466              
467              
468              
469             =head2 rhMethodAt(row => $row, col => $col)
470              
471             Find the module mentioned on line $row (1..) at $col (1..).
472              
473             Return hash ref with { oNode, oNodeObject } or undef if none was
474             found.
475              
476             =cut
477 88     88 1 356 sub rhMethodAt {
478 88         213 my ($row, $col) = Devel::PerlySense::Util::aNamedArg(["row", "col"], @_);
479 88         1441 return($self->rhTokenOfAt($self->rhRowColMethod, $row, $col));
480             }
481              
482              
483              
484              
485              
486             =head2 rhTokenOfAt($rhRowCol, $row, $col)
487              
488             Find the token mentioned in $rhRowCol on line $row (1..) at $col (1..).
489              
490             Return hash ref with keys oNode and possibly oNodeObject, or undef if
491             none was found.
492              
493             =cut
494 126     126 1 564 sub rhTokenOfAt {
495 126         136 my ($rhRowCol, $row, $col) = @_;
496              
497 126 100       430 my $rhCol = $rhRowCol->{$row} or return(undef);
498 69         189 for my $colToken (keys %$rhCol) {
499 88         101 my $rhToken = $rhCol->{$colToken};
500 88         95 my $oNode = $rhToken->{oNode};
501 88         290 my $colTokenEnd = $colToken + length($oNode);
502 88 100 100     620 if($col >= $colToken && $col < $colTokenEnd) {
503 57         225 return($rhToken);
504             }
505             }
506              
507 12         45 return(undef);
508             }
509              
510              
511              
512              
513              
514             =head2 parsePod($oDocument, $oNode, $raLocationPod, $raPodHeadingCurrent)
515              
516             Parse $oNode and add one or more Location objects to $raLocationPod.
517              
518             Add pod chunks that are =head or =item. Prefix the pod chunks with
519             their immediate pod heading level.
520              
521             Return 1 on success, die on errors.
522              
523             =cut
524 4915     4915 1 4607 sub parsePod {
525 4915         5505 my ($oDocument, $oNode, $raLocationPod, $raPodHeadingCurrent) = @_;
526              
527 4915         8709 my @aLine = split(/\n/, $oNode);
528 4915         26491 my $lineCur = -1;
529 4915         6464 for my $line (@aLine) {
530 53830         31302 $lineCur++;
531              
532 53830 100       143023 if($line =~ /^ (?: =head(\d+)\b ) | (?: =item\b )/x) {
533 7202   100     21408 my $headingLevel = $1 || 0;
534 7202 100       9859 if($headingLevel) {
535 6865 100       17794 @$raPodHeadingCurrent > $headingLevel and splice(@$raPodHeadingCurrent, $headingLevel); #Remove everything below this heading
536 6865         11670 $raPodHeadingCurrent->[$headingLevel - 1] = $line;
537             }
538              
539 7202         6121 my $podSection = "";
540 7202         5312 my $level = 0;
541 7202         8345 for my $heading (@$raPodHeadingCurrent) {
542 12298 50       15581 defined($heading) or $heading = ""; # Silence undef warning, is this the right thing to do?
543 12298 100 100     37934 ($level < $headingLevel - 1) || ($headingLevel == 0) and $podSection .= "$heading\n\n";
544 12298         12347 $level++;
545             }
546              
547              
548 7202         10005 my $pod = "$line\n";
549 7202         7309 my $linePod = $lineCur + 1;
550 7202   66     26022 while(defined($aLine[$linePod]) && $aLine[$linePod] !~ /^=/) {
551 41264         125275 $pod .= $aLine[$linePod++] . "\n";
552             }
553              
554 7202         147628 my $oLocation = Devel::PerlySense::Document::Location->new(
555             file => $oDocument->file,
556             row => $oNode->location->[0] + $lineCur,
557             col => 1,
558             );
559 7202         91844 $oLocation->rhProperty->{pod} = $pod;
560 7202         108176 $oLocation->rhProperty->{podSection} = $podSection;
561              
562 7202         31969 push(@$raLocationPod, $oLocation);
563             }
564             }
565              
566 4915         10111 return(1);
567             }
568              
569              
570              
571              
572              
573             =head2 oLocationSub($oDocument, $oNode, $nameSub, $packageCurrent)
574              
575             Create a Document::Location object from the sub $nameSub consisting of
576             $oNode, found in $oDocument in $packageCurrent.
577              
578             Set appropriate Location->rhProperty keys:
579              
580             nameSub
581             source
582             namePackage
583             oLocationEnd
584              
585             Return the new Location object.
586              
587             =cut
588 3440     3440 1 21456 sub oLocationSub {
589 3440         5206 my ($oDocument, $oNode, $nameSub, $packageCurrent) = @_;
590              
591 3440         45732 my $oLocation = Devel::PerlySense::Document::Location->new(
592             file => $oDocument->file,
593             row => $oNode->location->[0],
594             col => $oNode->location->[1],
595             );
596 3440         45625 $oLocation->rhProperty->{nameSub} = $nameSub;
597 3440         17694 $oLocation->rhProperty->{source} = "$oNode";
598 3440         1203165 $oLocation->rhProperty->{namePackage} = $packageCurrent;
599              
600              
601 3440         16260 my $countNewline =()= $oNode =~ /\n/g;
602 3440         866180 my ($rowEnd, $colEnd) = ($oNode->location->[0] + $countNewline, 1);
603 3440 100       38098 if ($countNewline) {
604 3420 50       5309 $oNode =~ /\n([^\n]+?)\z/ and $colEnd += length($1);
605             } else {
606 20         42 $colEnd = length($oNode);
607             }
608              
609 3440         940018 my $oLocationEnd = Devel::PerlySense::Document::Location->new(
610             file => $oDocument->file,
611             row => $rowEnd,
612             col => $colEnd,
613             );
614 3440         42905 $oLocation->rhProperty->{oLocationEnd} = $oLocationEnd;
615              
616 3440         16509 return($oLocation);
617             }
618              
619              
620              
621              
622              
623             1;
624              
625              
626              
627              
628              
629             __END__
630              
631             =encoding utf8
632              
633             =head1 AUTHOR
634              
635             Johan Lindstrom, C<< <johanl@cpan.org> >>
636              
637             =head1 BUGS
638              
639             Please report any bugs or feature requests to
640             C<bug-devel-perlysense@rt.cpan.org>, or through the web interface at
641             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-PerlySense>.
642             I will be notified, and then you'll automatically be notified of progress on
643             your bug as I make changes.
644              
645             =head1 ACKNOWLEDGEMENTS
646              
647             =head1 COPYRIGHT & LICENSE
648              
649             Copyright 2005 Johan Lindstrom, All Rights Reserved.
650              
651             This program is free software; you can redistribute it and/or modify it
652             under the same terms as Perl itself.
653              
654             =cut