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