File Coverage

inc/Class/Inspector.pm
Criterion Covered Total %
statement 82 184 44.5
branch 18 92 19.5
condition 0 7 0.0
subroutine 13 25 52.0
pod 10 12 83.3
total 123 320 38.4


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