File Coverage

blib/lib/Class/Inspector.pm
Criterion Covered Total %
statement 173 196 88.2
branch 86 108 79.6
condition 8 16 50.0
subroutine 23 25 92.0
pod 10 12 83.3
total 300 357 84.0


line stmt bran cond sub pod time code
1             package Class::Inspector;
2              
3 5     5   173346 use 5.006;
  5         40  
4             # We don't want to use strict refs anywhere in this module, since we do a
5             # lot of things in here that aren't strict refs friendly.
6 5     5   23 use strict qw{vars subs};
  5         6  
  5         139  
7 5     5   21 use warnings;
  5         14  
  5         148  
8 5     5   23 use File::Spec ();
  5         8  
  5         323  
9              
10             # ABSTRACT: Get information about a class and its structure
11             our $VERSION = '1.35_01'; # TRIAL VERSION
12             $VERSION = eval $VERSION; ## no critic (BuiltinFunctions::ProhibitStringyEval)
13              
14              
15             # If Unicode is available, enable it so that the
16             # pattern matches below match unicode method names.
17             # We can safely ignore any failure here.
18             BEGIN {
19 5     5   15 local $@;
20 5         6 eval {
21 5         2446 require utf8;
22 5         72 utf8->import;
23             };
24             }
25              
26             # Predefine some regexs
27             our $RE_IDENTIFIER = qr/\A[^\W\d]\w*\z/s;
28             our $RE_CLASS = qr/\A[^\W\d]\w*(?:(?:\'|::)\w+)*\z/s;
29              
30             # Are we on something Unix-like?
31             our $UNIX = !! ( $File::Spec::ISA[0] eq 'File::Spec::Unix' );
32              
33              
34             #####################################################################
35             # Basic Methods
36              
37              
38             sub _resolved_inc_handler {
39 5     5   9 my $class = shift;
40 5 50       13 my $filename = $class->_inc_filename(shift) or return undef;
41              
42 5         13 foreach my $inc ( @INC ) {
43 31         41 my $ref = ref $inc;
44 31 100 66     95 if($ref eq 'CODE') {
    100 66        
    100          
45 2         4 my @ret = $inc->($inc, $filename);
46 2 50 33     57 if(@ret == 1 && ! defined $ret[0]) {
    100          
47             # do nothing.
48             } elsif(@ret) {
49 1         15 return 1;
50             }
51             }
52             elsif($ref eq 'ARRAY' && ref($inc->[0]) eq 'CODE') {
53 3         6 my @ret = $inc->[0]->($inc, $filename);
54 3 100       64 if(@ret) {
55 1         15 return 1;
56             }
57             }
58 4         21 elsif($ref && eval { $inc->can('INC') }) {
59 4         11 my @ret = $inc->INC($filename);
60 4 100       71 if(@ret) {
61 1         14 return 1;
62             }
63             }
64             }
65              
66 2         21 '';
67             }
68              
69             sub installed {
70 7     7 1 14 my $class = shift;
71 7   100     31 !! ($class->loaded_filename($_[0]) or $class->resolved_filename($_[0]) or $class->_resolved_inc_handler($_[0]));
72             }
73              
74              
75             sub loaded {
76 31     31 1 672 my $class = shift;
77 31 50       65 my $name = $class->_class(shift) or return undef;
78 31         94 $class->_loaded($name);
79             }
80              
81             sub _loaded {
82 999     999   1152 my $class = shift;
83 999         1087 my $name = shift;
84              
85             # Handle by far the two most common cases
86             # This is very fast and handles 99% of cases.
87 999 100       1032 return 1 if defined ${"${name}::VERSION"};
  999         3966  
88 664 100       784 return 1 if @{"${name}::ISA"};
  664         1858  
89              
90             # Are there any symbol table entries other than other namespaces
91 442         516 foreach ( keys %{"${name}::"} ) {
  442         1253  
92 986 100       1658 next if substr($_, -2, 2) eq '::';
93 727 100       723 return 1 if defined &{"${name}::$_"};
  727         1756  
94             }
95              
96             # No functions, and it doesn't have a version, and isn't anything.
97             # As an absolute last resort, check for an entry in %INC
98 302         554 my $filename = $class->_inc_filename($name);
99 302 50       715 return 1 if defined $INC{$filename};
100              
101 302         559 '';
102             }
103              
104              
105             sub filename {
106 2     2 1 5 my $class = shift;
107 2 50       16 my $name = $class->_class(shift) or return undef;
108 2         55 File::Spec->catfile( split /(?:\'|::)/, $name ) . '.pm';
109             }
110              
111              
112             sub resolved_filename {
113 7     7 1 10 my $class = shift;
114 7 50       15 my $filename = $class->_inc_filename(shift) or return undef;
115 7         15 my @try_first = @_;
116              
117             # Look through the @INC path to find the file
118 7         16 foreach ( @try_first, @INC ) {
119 71         158 my $full = "$_/$filename";
120 71 100       742 next unless -e $full;
121 2 50       19 return $UNIX ? $full : $class->_inc_to_local($full);
122             }
123              
124             # File not found
125 5         47 '';
126             }
127              
128              
129             sub loaded_filename {
130 9     9 1 239 my $class = shift;
131 9         26 my $filename = $class->_inc_filename(shift);
132 9 50       60 $UNIX ? $INC{$filename} : $class->_inc_to_local($INC{$filename});
133             }
134              
135              
136              
137              
138              
139             #####################################################################
140             # Sub Related Methods
141              
142              
143             sub functions {
144 3     3 1 461 my $class = shift;
145 3 50       8 my $name = $class->_class(shift) or return undef;
146 3 100       9 return undef unless $class->loaded( $name );
147              
148             # Get all the CODE symbol table entries
149 18         70 my @functions = sort grep { /$RE_IDENTIFIER/o }
150 27         28 grep { defined &{"${name}::$_"} }
  27         63  
151 1         2 keys %{"${name}::"};
  1         11  
152 1         5 \@functions;
153             }
154              
155              
156             sub function_refs {
157 1     1 1 2 my $class = shift;
158 1 50       2 my $name = $class->_class(shift) or return undef;
159 1 50       4 return undef unless $class->loaded( $name );
160              
161             # Get all the CODE symbol table entries, but return
162             # the actual CODE refs this time.
163 18         20 my @functions = map { \&{"${name}::$_"} }
  18         38  
164 18         43 sort grep { /$RE_IDENTIFIER/o }
165 27         27 grep { defined &{"${name}::$_"} }
  27         58  
166 1         3 keys %{"${name}::"};
  1         8  
167 1         6 \@functions;
168             }
169              
170              
171             sub function_exists {
172 4     4 1 8 my $class = shift;
173 4 50       9 my $name = $class->_class( shift ) or return undef;
174 4 100       12 my $function = shift or return undef;
175              
176             # Only works if the class is loaded
177 3 100       7 return undef unless $class->loaded( $name );
178              
179             # Does the GLOB exist and its CODE part exist
180 2         4 defined &{"${name}::$function"};
  2         14  
181             }
182              
183              
184             sub methods {
185 23     23 1 6240 my $class = shift;
186 23 50       52 my $name = $class->_class( shift ) or return undef;
187 23         50 my @arguments = map { lc $_ } @_;
  20         53  
188              
189             # Process the arguments to determine the options
190 23         34 my %options = ();
191 23         37 foreach ( @arguments ) {
192 20 100       46 if ( $_ eq 'public' ) {
    100          
    100          
    50          
193             # Only get public methods
194 6 100       16 return undef if $options{private};
195 5         9 $options{public} = 1;
196              
197             } elsif ( $_ eq 'private' ) {
198             # Only get private methods
199 4 100       12 return undef if $options{public};
200 3         7 $options{private} = 1;
201              
202             } elsif ( $_ eq 'full' ) {
203             # Return the full method name
204 4 100       11 return undef if $options{expanded};
205 3         4 $options{full} = 1;
206              
207             } elsif ( $_ eq 'expanded' ) {
208             # Returns class, method and function ref
209 6 100       15 return undef if $options{full};
210 5         9 $options{expanded} = 1;
211              
212             } else {
213             # Unknown or unsupported options
214 0         0 return undef;
215             }
216             }
217              
218             # Only works if the class is loaded
219 19 100       39 return undef unless $class->loaded( $name );
220              
221             # Get the super path ( not including UNIVERSAL )
222             # Rather than using Class::ISA, we'll use an inlined version
223             # that implements the same basic algorithm.
224 13         19 my @path = ();
225 13         21 my @queue = ( $name );
226 13         24 my %seen = ( $name => 1 );
227 13         28 while ( my $cl = shift @queue ) {
228 21         28 push @path, $cl;
229 8         33 unshift @queue, grep { ! $seen{$_}++ }
230 8         14 map { s/^::/main::/; s/\'/::/g; $_ } ## no critic
  8         12  
  8         17  
231 8         20 map { "$_" }
232 21         25 ( @{"${cl}::ISA"} );
  21         68  
233             }
234              
235             # Find and merge the function names across the entire super path.
236             # Sort alphabetically and return.
237 13         25 my %methods = ();
238 13         22 foreach my $namespace ( @path ) {
239 246         372 my @functions = grep { ! $methods{$_} }
240 246         468 grep { /$RE_IDENTIFIER/o }
241 381         394 grep { defined &{"${namespace}::$_"} }
  381         780  
242 21         26 keys %{"${namespace}::"};
  21         112  
243 21         50 foreach ( @functions ) {
244 240         354 $methods{$_} = $namespace;
245             }
246             }
247              
248             # Filter to public or private methods if needed
249 13         144 my @methodlist = sort keys %methods;
250 13 100       42 @methodlist = grep { ! /^\_/ } @methodlist if $options{public};
  80         130  
251 13 100       26 @methodlist = grep { /^\_/ } @methodlist if $options{private};
  40         61  
252              
253             # Return in the correct format
254 13 100       25 @methodlist = map { "$methods{$_}::$_" } @methodlist if $options{full};
  40         86  
255             @methodlist = map {
256 66         130 [ "$methods{$_}::$_", $methods{$_}, $_, \&{"$methods{$_}::$_"} ]
  66         394  
257 13 100       24 } @methodlist if $options{expanded};
258              
259 13         77 \@methodlist;
260             }
261              
262              
263              
264              
265              
266             #####################################################################
267             # Search Methods
268              
269              
270             sub subclasses {
271 5     5 1 613 my $class = shift;
272 5 100       12 my $name = $class->_class( shift ) or return undef;
273              
274             # Prepare the search queue
275 4         9 my @found = ();
276 4         10 my @queue = grep { $_ ne 'main' } $class->_subnames('');
  280         384  
277 4         64 while ( @queue ) {
278 968         1373 my $c = shift(@queue); # c for class
279 968 100       1466 if ( $class->_loaded($c) ) {
280             # At least one person has managed to misengineer
281             # a situation in which ->isa could die, even if the
282             # class is real. Trap these cases and just skip
283             # over that (bizarre) class. That would at limit
284             # problems with finding subclasses to only the
285             # modules that have broken ->isa implementation.
286 678         792 local $@;
287 678         845 eval {
288 678 100       2927 if ( $c->isa($name) ) {
289             # Add to the found list, but don't add the class itself
290 8 100       27 push @found, $c unless $c eq $name;
291             }
292             };
293             }
294              
295             # Add any child namespaces to the head of the queue.
296             # This keeps the queue length shorted, and allows us
297             # not to have to do another sort at the end.
298 968         1613 unshift @queue, map { "${c}::$_" } $class->_subnames($c);
  692         1724  
299             }
300              
301 4 100       25 @found ? \@found : '';
302             }
303              
304             sub _subnames {
305 972     972   1456 my ($class, $name) = @_;
306             return sort
307             grep { ## no critic
308 19085 100       41367 substr($_, -2, 2, '') eq '::'
309             and
310             /$RE_IDENTIFIER/o
311             }
312 972         1338 keys %{"${name}::"};
  972         5092  
313             }
314              
315              
316              
317              
318              
319             #####################################################################
320             # Children Related Methods
321              
322             # These can go undocumented for now, until I decide if its best to
323             # just search the children in namespace only, or if I should do it via
324             # the file system.
325              
326             # Find all the loaded classes below us
327             sub children {
328 0     0 0 0 my $class = shift;
329 0 0       0 my $name = $class->_class(shift) or return ();
330              
331             # Find all the Foo:: elements in our symbol table
332 5     5   9021 no strict 'refs';
  5         10  
  5         653  
333 0         0 map { "${name}::$_" } sort grep { s/::$// } keys %{"${name}::"}; ## no critic
  0         0  
  0         0  
  0         0  
334             }
335              
336             # As above, but recursively
337             sub recursive_children {
338 0     0 0 0 my $class = shift;
339 0 0       0 my $name = $class->_class(shift) or return ();
340 0         0 my @children = ( $name );
341              
342             # Do the search using a nicer, more memory efficient
343             # variant of actual recursion.
344 0         0 my $i = 0;
345 5     5   31 no strict 'refs';
  5         8  
  5         1653  
346 0         0 while ( my $namespace = $children[$i++] ) {
347 0         0 push @children, map { "${namespace}::$_" }
348 0         0 grep { ! /^::/ } # Ignore things like ::ISA::CACHE::
349 0         0 grep { s/::$// } ## no critic
350 0         0 keys %{"${namespace}::"};
  0         0  
351             }
352              
353 0         0 sort @children;
354             }
355              
356              
357              
358              
359              
360             #####################################################################
361             # Private Methods
362              
363             # Checks and expands ( if needed ) a class name
364             sub _class {
365 402     402   569 my $class = shift;
366 402 100       657 my $name = shift or return '';
367              
368             # Handle main shorthand
369 400 100       616 return 'main' if $name eq '::';
370 399         546 $name =~ s/\A::/main::/;
371              
372             # Check the class name is valid
373 399 100       1929 $name =~ /$RE_CLASS/o ? $name : '';
374             }
375              
376             # Create a INC-specific filename, which always uses '/'
377             # regardless of platform.
378             sub _inc_filename {
379 325     325   643 my $class = shift;
380 325 50       459 my $name = $class->_class(shift) or return undef;
381 325         1459 join( '/', split /(?:\'|::)/, $name ) . '.pm';
382             }
383              
384             # Convert INC-specific file name to local file name
385             sub _inc_to_local {
386             # Shortcut in the Unix case
387 1 50   1   683 return $_[1] if $UNIX;
388              
389             # On other places, we have to deal with an unusual path that might look
390             # like C:/foo/bar.pm which doesn't fit ANY normal pattern.
391             # Putting it through splitpath/dir and back again seems to normalise
392             # it to a reasonable amount.
393 0           my $class = shift;
394 0 0         my $inc_name = shift or return undef;
395 0           my ($vol, $dir, $file) = File::Spec->splitpath( $inc_name );
396 0   0       $dir = File::Spec->catdir( File::Spec->splitdir( $dir || "" ) );
397 0   0       File::Spec->catpath( $vol, $dir, $file || "" );
398             }
399              
400             1;
401              
402             __END__