File Coverage

blib/lib/Devel/PerlySense/Document/Api.pm
Criterion Covered Total %
statement 109 109 100.0
branch 8 10 80.0
condition 16 25 64.0
subroutine 22 22 100.0
pod 11 11 100.0
total 166 177 93.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Devel::PerlySense::Document::Api - The methods (and their locations)
4             of a package
5              
6             =head1 DESCRIPTION
7              
8             An API is the methods/subs a module/package supports (or in some cases
9             _may_ support).
10              
11             =cut
12              
13              
14              
15              
16              
17 68     68   2121 use strict;
  68         79  
  68         1645  
18 68     68   206 use warnings;
  68         81  
  68         1465  
19 68     68   2195 use utf8;
  68         113  
  68         274  
20              
21             package Devel::PerlySense::Document::Api;
22             $Devel::PerlySense::Document::Api::VERSION = '0.0217';
23              
24              
25              
26              
27 68     68   4222 use Spiffy -Base;
  68         15772  
  68         318  
28 68     68   79709 use Carp;
  68     68   85  
  68     68   1022  
  68         190  
  68         74  
  68         1266  
  68         190  
  68         67  
  68         2900  
29 68     68   226 use Data::Dumper;
  68         72  
  68         2171  
30              
31 68     68   1365 use Devel::PerlySense::Document;
  68         98  
  68         437  
32 68     68   15274 use Devel::PerlySense::Document::Location;
  68         93  
  68         297  
33              
34              
35              
36              
37              
38             =head1 PROPERTIES
39              
40             =head2 rhSub
41              
42             Hash ref with (keys: method/sub name; values: Document::Location objects).
43              
44             Default: {}
45              
46             The Location objects have a C<sub> property which is the name of the
47             sub.
48              
49             =cut
50             field "rhSub" => {};
51              
52              
53              
54              
55              
56             =head1 API METHODS
57              
58             =head2 new()
59              
60             Create new Api object.
61              
62             =cut
63             sub new(@) {
64 120     120 1 175 my $pkg = shift;
65 120         170 my (%p) = @_;
66              
67 120         191 my $self = bless {}, $pkg;
68              
69 120         228 return($self);
70             }
71              
72              
73              
74              
75              
76             =head2 aNameSubVisible(oPerlySense => $oPs, fileCurrent => $file)
77              
78             Return array with the method/sub names in the interface that are
79             visible.
80              
81             A method is invisible if it's a private method in a base class of
82             $fileCurrent, outside the current Project, according to
83             $oPerlySense.
84              
85             =cut
86 4     4 1 6577 sub aNameSubVisible {
87 4         20 my ($oPerlySense, $fileCurrent) = Devel::PerlySense::Util::aNamedArg(["oPerlySense", "fileCurrent"], @_);
88              
89 4         15 my $rsSortSub = $self->rsSortSub($fileCurrent);
90             my @aNameSubVisible =
91             sort $rsSortSub
92 98         139 grep { ! $self->isSubVisible($oPerlySense, $fileCurrent, $_) }
93 4         10 keys %{$self->rhSub};
  4         51  
94              
95 4         98 return(@aNameSubVisible);
96             }
97              
98              
99              
100              
101              
102             =head2 isSubVisible($oPerlySense, $fileCurrent, $nameSub)
103              
104             Return true if the Sub name is visibl, else false.
105              
106             A sub/method is invisible if it's a private method in a base class of
107             $fileCurrent, outside the current Project, according to $oPerlySense.
108              
109             =cut
110 98     98 1 59 sub isSubVisible {
111 98         103 my ($oPerlySense, $fileCurrent, $nameMethod) = @_;
112              
113 98         1128 my $file = $self->rhSub->{$_}->file;
114 98   100     1715 my $isInvisible =
115             #Is it a base class (file ne current file)?
116             $file ne $fileCurrent
117             #Is it a private method? ###TODO: Extract to method, then class *::Method->isPrivate
118             && $_ =~ /^_/
119             #Is it outside the project?
120             && ! $oPerlySense->isFileInProject(
121             file => $file,
122             fileProjectOf => $fileCurrent,
123             );
124              
125 98         174 return $isInvisible;
126             }
127              
128              
129              
130              
131              
132             =head2 rsSortSub($fileCurrent)
133              
134             Return sub ref for sorting sub names of this Api, using the rhSub
135             property and given the $fileCurrent.
136              
137             =cut
138 4     4 1 5 sub rsSortSub {
139 4         7 my ($fileCurrent) = @_;
140              
141 4         57 my $rhSub = $self->rhSub();
142             return sub {
143             (
144             #If unknown location, display it first no matter what
145             ( ! $rhSub->{$b}->row ) <=> ( ! $rhSub->{$a}->row)
146             or # Then alphabetically (case insensitive)
147             uc($a) cmp uc($b)
148             )
149             || #Display the current file's methods first
150             ($rhSub->{$a}->file eq $fileCurrent) <=> ($rhSub->{$b}->file eq $fileCurrent)
151             || #then alphabetically
152             $rhSub->{$a}->file cmp $rhSub->{$b}->file ###TODO: inheritance tree
153             || # Then the order in the file
154 362 0 66 362   5811 $rhSub->{$a}->row <=> $rhSub->{$b}->row
      33        
      33        
      0        
155             || # Then method name alphabetically (case insensitive) (if on the same row)
156             uc($a) cmp uc($b)
157 4         33 };
158             }
159              
160              
161              
162              
163              
164             =head2 parsePackageSetSub(raNodeSub => $raNodeSub, source => $source, oDocument => $oDocument)
165              
166             Parse the entire package data, both $source and found method
167             nodes. Add both found subs and $raNodeSub to the rhSub property.
168              
169             Return 1 or die on errors.
170              
171             =cut
172 118     118 1 159 sub parsePackageSetSub {
173 118         437 my ($raNodeSub, $source, $oDocument) = Devel::PerlySense::Util::aNamedArg(["raNodeSub", "source", "oDocument"], @_);
174              
175             #Temporal cohesion: let the sub declarations overwrite the called subs
176             #TODO: The called subs shouldn't overwrite sub declarations of a base class
177 118         364 $self->parseSourceSetSub(source => $source, oDocument => $oDocument);
178              
179 118         167 for my $oNodeSub (@$raNodeSub) {
180 412         932 $self->oLocationSetSub(nameSub => $oNodeSub->name, oDocument => $oDocument, oNode => $oNodeSub);
181             }
182              
183 118         207 return(1);
184             }
185              
186              
187              
188              
189              
190             =head2 parseSourceSetSub(source => $source, oDocument => $oDocument)
191              
192             Parse the $source, looking for $self->method calls, and
193             $self->{hash_key}, and add them to the rhSub property.
194              
195             Return 1 or die on errors.
196              
197             =cut
198 118     118 1 132 sub parseSourceSetSub {
199 118         288 my ($source, $oDocument) = Devel::PerlySense::Util::aNamedArg(["source", "oDocument"], @_);
200              
201             ###TODO: ignore comments, POD
202              
203             #Look for $self->method calls
204 118         1202 my @aSelfMethod = $source =~ / \$self \s* -> \s* (\w+) /gsx;
205              
206             #Look for $self->{property_name}
207             my @aSelfHash =
208             #Remove quotes
209 118         622 map { s/ ^ (["']) ( [^\1]* ) \1 $ /$2/x; $_ } ## no critic
  4         6  
  4         5  
210             $source =~
211             /
212             \$self \s* -> \s* {
213             (
214             (?: " [^"\ ]+ " )
215             |
216             (?: ' [^'\ ]+ ' )
217             |
218             (?: \w+ )
219             )
220             /gsx;
221              
222 118         120 my %hSeen;
223 118         205 for my $method ( grep { ! $hSeen{$_} ++ } @aSelfMethod, @aSelfHash ) {
  818         1002  
224 343         526 $self->oLocationSetSub(nameSub => $method, oDocument => $oDocument);
225             }
226              
227 118         236 return(1);
228             }
229              
230              
231              
232              
233              
234             =head2 oLocationSetSub(nameSub => $nameSub, oDocument => $oDocument, [oNode => $oNode])
235              
236             Set the $self->rhSub->{$nameSub} to a new Document::Location with
237             $oDocument and possibly a row/col for $oNode. Set the rhProperty for:
238              
239             sub
240              
241             If no $oNode is passed, the location is supposed to be unknown, with
242             row/col: 0/0.
243              
244             Return the new oLocation. Die on errors.
245              
246             =cut
247 761     761 1 25421 sub oLocationSetSub {
248 761         1599 my ($nameSub, $oDocument) = Devel::PerlySense::Util::aNamedArg(["nameSub", "oDocument"], @_);
249 761         1137 my %p = @_; my ($oNode) = ($p{oNode});
  761         956  
250              
251 761         527 my $oLocation;
252              
253 761 100       1160 if($oNode) {
254 415         805 $oLocation = $oDocument->oLocationOfNode($oNode);
255             } else {
256 346         4757 $oLocation = Devel::PerlySense::Document::Location->new(
257             file => $oDocument->file,
258             );
259             }
260              
261 761         9128 $oLocation->rhProperty->{sub} = $nameSub;
262 761         11110 $self->rhSub->{$nameSub} = $oLocation;
263              
264 761         3570 return($oLocation);
265             }
266              
267              
268              
269              
270              
271             =head2 mergeWithBase($oApiBase)
272              
273             Adjust this object by adding appropriate parts of $oApiBase, i.e. the
274             methods in $oApiBase->rhSub that aren't overridden in this class.
275              
276             If a method has no row/col in neither base or self, it's supposed to
277             be defined in the base class. Any method definition with row/col in
278             self overrides one in base.
279              
280             Return 1 on success. Die on errors.
281              
282             =cut
283 21     21 1 2170 sub mergeWithBase {
284 21         28 my ($oApiBase) = @_;
285              
286 21         311 my $rhSub = $self->rhSub;
287 21         96 while(my ($method, $oLocationBase) = each %{$oApiBase->rhSub}) {
  236         3445  
288              
289 215 100       1044 if(my $oLocation = $rhSub->{$method}) {
290              
291             #If both are just seen as $self->X, go with the base one
292 105 100 100     1225 if($oLocation->row == 0 && $oLocationBase->row == 0) { #TODO: refactor: ! hasPosition
293 69         1155 $rhSub->{$method} = $oLocationBase;
294             }
295              
296             #If the base one is a real declaration and self is only seen as $self->X, go with the base one
297 105 100 100     1675 if($oLocationBase->row != 0 && $oLocation->row == 0) {
298 14         277 $rhSub->{$method} = $oLocationBase;
299             }
300              
301             } else {
302             #Not present in self, copy from base
303 110         177 $rhSub->{$method} = $oLocationBase;
304             }
305             }
306              
307 21         118 return(1);
308             }
309              
310              
311              
312              
313              
314             =head2 isSubSupported($nameSub)
315              
316             Return true if $nameSub is supported by this API, else false.
317              
318             =cut
319 114     114 1 385 sub isSubSupported {
320 114         121 my ($nameSub) = @_;
321 114         1371 return( exists $self->rhSub->{$nameSub} );
322             }
323              
324              
325              
326              
327              
328             =head2 percentSupportedOf($raNameSub)
329              
330             Return percent (0..100) of how many of the sub names in raNameSub that
331             are present in the api.
332              
333             =cut
334 16     16 1 311 sub percentSupportedOf {
335 16         21 my ($raNameSub) = @_;
336              
337 16         23 my $countSupported = grep { $self->isSubSupported($_) } @$raNameSub;
  68         229  
338 16   100     113 my $percent = $countSupported / (scalar(@$raNameSub) || 1);
339              
340 16         63 return($percent * 100);
341             }
342              
343              
344              
345              
346              
347             =head2 percentConsistsOf($raNameSub)
348              
349             Return percent (0..100) of how much of the api consists of the sub
350             names in raNameSub.
351              
352             I.e. a large API will have a low percentage. Extra sub names in
353             $raNameSub will not affect the percentage.
354              
355             =cut
356 19     19 1 24 sub percentConsistsOf {
357 19         20 my ($raNameSub) = @_;
358              
359 19         29 my %hNameSub = map { $_ => 1 } @$raNameSub;
  78         116  
360 19         29 my $countConsists = grep { $hNameSub{$_} } keys %{$self->rhSub};
  251         270  
  19         279  
361 19   50     29 my $percent = $countConsists / (scalar(keys %{$self->rhSub}) || 1);
362              
363 19         148 return($percent * 100);
364             }
365              
366              
367              
368              
369              
370             1;
371              
372              
373              
374              
375              
376             __END__
377              
378             =encoding utf8
379              
380             =head1 AUTHOR
381              
382             Johan Lindstrom, C<< <johanl@cpan.org> >>
383              
384             =head1 BUGS
385              
386             Please report any bugs or feature requests to
387             C<bug-devel-perlysense@rt.cpan.org>, or through the web interface at
388             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-PerlySense>.
389             I will be notified, and then you'll automatically be notified of progress on
390             your bug as I make changes.
391              
392             =head1 ACKNOWLEDGEMENTS
393              
394             =head1 COPYRIGHT & LICENSE
395              
396             Copyright 2005 Johan Lindstrom, All Rights Reserved.
397              
398             This program is free software; you can redistribute it and/or modify it
399             under the same terms as Perl itself.
400              
401             =cut