| 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 | Class::Inspector::BEGIN@3 |
| 1 | 1 | 1 | 9µs | 11µs | Class::Inspector::BEGIN@17 |
| 1 | 1 | 1 | 8µs | 15µs | Class::Inspector::BEGIN@331 |
| 1 | 1 | 1 | 8µs | 8µs | Class::Inspector::BEGIN@8 |
| 1 | 1 | 1 | 5µs | 8µs | Class::Inspector::BEGIN@344 |
| 1 | 1 | 1 | 5µs | 12µs | Class::Inspector::BEGIN@6 |
| 1 | 1 | 1 | 5µs | 31µs | Class::Inspector::BEGIN@7 |
| 2 | 2 | 1 | 3µs | 3µs | Class::Inspector::CORE:qr (opcode) |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::_class |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::_inc_filename |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::_inc_to_local |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::_loaded |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::_resolved_inc_handler |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::_subnames |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::children |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::filename |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::function_exists |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::function_refs |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::functions |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::installed |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::loaded |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::loaded_filename |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::methods |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::recursive_children |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::resolved_filename |
| 0 | 0 | 0 | 0s | 0s | Class::Inspector::subclasses |
| 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 |