File Coverage

blib/lib/Class/Inspector.pm
Criterion Covered Total %
statement 170 193 88.0
branch 86 108 79.6
condition 8 16 50.0
subroutine 23 25 92.0
pod 10 12 83.3
total 297 354 83.9


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