| blib/lib/Data/Inspect.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 86 | 96 | 89.5 |
| branch | 22 | 30 | 73.3 |
| condition | 12 | 21 | 57.1 |
| subroutine | 17 | 19 | 89.4 |
| pod | 6 | 6 | 100.0 |
| total | 143 | 172 | 83.1 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | =head1 NAME | ||||||
| 2 | |||||||
| 3 | Data::Inspect - human-readable object representations | ||||||
| 4 | |||||||
| 5 | =head1 SYNOPSIS | ||||||
| 6 | |||||||
| 7 | use Data::Inspect; | ||||||
| 8 | my $insp = Data::Inspect->new; | ||||||
| 9 | $insp->p($object); | ||||||
| 10 | |||||||
| 11 | use Data::Inspect qw(p); | ||||||
| 12 | p $object; | ||||||
| 13 | |||||||
| 14 | =head1 DESCRIPTION | ||||||
| 15 | |||||||
| 16 | Data::Inspect provides a human-readable representation of any Perl | ||||||
| 17 | scalar. Classes can be extended with user-defined inspect methods. It | ||||||
| 18 | is heavily inspired by Ruby's C method and inspect functionality. |
||||||
| 19 | |||||||
| 20 | The purpose of this module is to provide debugging/logging code with a | ||||||
| 21 | more readable representation of data structures than the extremely | ||||||
| 22 | literal form output by Data::Dumper. | ||||||
| 23 | |||||||
| 24 | It is especially useful in an object-oriented system, since each class | ||||||
| 25 | can define its own C |
||||||
| 26 | object should be displayed. | ||||||
| 27 | |||||||
| 28 | The L method inspects its arguments and outputs them to the default | ||||||
| 29 | filehandle. It can be exported to your package's namespace, in which | ||||||
| 30 | case it will silently create the Inspect object with the default | ||||||
| 31 | options, if this sort of brevity is desired. | ||||||
| 32 | |||||||
| 33 | =cut | ||||||
| 34 | |||||||
| 35 | package Data::Inspect; | ||||||
| 36 | |||||||
| 37 | our $VERSION = '0.05'; | ||||||
| 38 | |||||||
| 39 | 1 | 1 | 13958 | use strict; | |||
| 1 | 1 | ||||||
| 1 | 24 | ||||||
| 40 | 1 | 1 | 3 | use warnings; | |||
| 1 | 1 | ||||||
| 1 | 20 | ||||||
| 41 | |||||||
| 42 | 1 | 1 | 589 | use Data::Dumper (); | |||
| 1 | 6948 | ||||||
| 1 | 30 | ||||||
| 43 | 1 | 1 | 5 | use Scalar::Util (); | |||
| 1 | 1 | ||||||
| 1 | 15 | ||||||
| 44 | |||||||
| 45 | 1 | 1 | 3 | use base 'Exporter'; | |||
| 1 | 1 | ||||||
| 1 | 1043 | ||||||
| 46 | our @EXPORT_OK = qw(p pe pf); | ||||||
| 47 | |||||||
| 48 | =head1 PUBLIC METHODS | ||||||
| 49 | |||||||
| 50 | =over 4 | ||||||
| 51 | |||||||
| 52 | =item new | ||||||
| 53 | |||||||
| 54 | my $insp = Data::Inspect->new; | ||||||
| 55 | |||||||
| 56 | Create a new Data::Inspect object. | ||||||
| 57 | |||||||
| 58 | =cut | ||||||
| 59 | |||||||
| 60 | sub new { | ||||||
| 61 | 2 | 2 | 1 | 10 | my ($class) = @_; | ||
| 62 | 2 | 3 | my $self = bless {}, $class; | ||||
| 63 | |||||||
| 64 | # Initialize values | ||||||
| 65 | 2 | 8 | $self->{tracker} = {}; | ||||
| 66 | 2 | 3 | $self->{options}{truncate_strings} = undef; | ||||
| 67 | |||||||
| 68 | 2 | 3 | return $self; | ||||
| 69 | } | ||||||
| 70 | |||||||
| 71 | =item p | ||||||
| 72 | |||||||
| 73 | $insp->p($var1, $var2); | ||||||
| 74 | |||||||
| 75 | use Data::Inspect qw(p); | ||||||
| 76 | p $var1, $var2; | ||||||
| 77 | |||||||
| 78 | Inspects each of the provided arguments and outputs the result to the | ||||||
| 79 | default filehandle (usually STDOUT). | ||||||
| 80 | |||||||
| 81 | C can be exported to the current namespace if you don't want to |
||||||
| 82 | create a Data::Inspect object to do your inspecting for you. | ||||||
| 83 | |||||||
| 84 | =cut | ||||||
| 85 | |||||||
| 86 | sub p { | ||||||
| 87 | 4 | 100 | 4 | 1 | 1603 | my $self = UNIVERSAL::isa($_[0], __PACKAGE__) ? shift : __PACKAGE__->new; | |
| 88 | 4 | 11 | print $self->inspect($_)."\n" for @_; | ||||
| 89 | } | ||||||
| 90 | |||||||
| 91 | =item pe | ||||||
| 92 | |||||||
| 93 | $insp->pe($var1, $var2); | ||||||
| 94 | |||||||
| 95 | Exactly like L but outputs to STDERR instead of the default | ||||||
| 96 | filehandle. | ||||||
| 97 | |||||||
| 98 | =cut | ||||||
| 99 | |||||||
| 100 | sub pe { | ||||||
| 101 | 0 | 0 | 0 | 1 | 0 | my $self = UNIVERSAL::isa($_[0], __PACKAGE__) ? shift : __PACKAGE__->new; | |
| 102 | 0 | 0 | print STDERR $self->inspect($_)."\n" for @_; | ||||
| 103 | } | ||||||
| 104 | |||||||
| 105 | =item pf | ||||||
| 106 | |||||||
| 107 | $insp->pf($somefh, $var1, $var2); | ||||||
| 108 | |||||||
| 109 | Like L and L but outputs to the filehandle specified in the | ||||||
| 110 | first argument. | ||||||
| 111 | |||||||
| 112 | Note that the filehandle must be a reference. If you want to use a | ||||||
| 113 | filehandle that isn't a reference, you can create one using the | ||||||
| 114 | L |
||||||
| 115 | |||||||
| 116 | =cut | ||||||
| 117 | |||||||
| 118 | sub pf { | ||||||
| 119 | # Create a new $self if this is called in the non-OO manner. | ||||||
| 120 | 0 | 0 | 0 | 1 | 0 | my $self = UNIVERSAL::isa($_[0], __PACKAGE__) ? shift : __PACKAGE__->new; | |
| 121 | 0 | 0 | my $fh = shift; | ||||
| 122 | 0 | 0 | print $fh $self->inspect($_)."\n" for @_; | ||||
| 123 | } | ||||||
| 124 | |||||||
| 125 | =item inspect | ||||||
| 126 | |||||||
| 127 | my $value = $insp->inspect($var); | ||||||
| 128 | |||||||
| 129 | Inspects the given scalar value and returns the result. | ||||||
| 130 | |||||||
| 131 | =cut | ||||||
| 132 | |||||||
| 133 | sub inspect { | ||||||
| 134 | 68 | 68 | 1 | 383 | my ($self, $val) = @_; | ||
| 135 | |||||||
| 136 | # If no $val is provided or $val is $self, someone is probably | ||||||
| 137 | # trying to inspect this object! | ||||||
| 138 | 68 | 100 | 100 | 240 | if (@_ < 2 or (ref $val and | ||
| 33 | |||||||
| 139 | Scalar::Util::refaddr($val) == Scalar::Util::refaddr($self))) { | ||||||
| 140 | 1 | 12 | return "# |
||||
| 141 | } | ||||||
| 142 | |||||||
| 143 | # If it's a reference, we delegate it to _inspect_reference | ||||||
| 144 | 67 | 100 | 76 | if (ref $val) { | |||
| 145 | 18 | 26 | return $self->_inspect_reference($val); | ||||
| 146 | } | ||||||
| 147 | |||||||
| 148 | # Otherwise, delegate to _inspect_non_reference | ||||||
| 149 | else { | ||||||
| 150 | 49 | 54 | return $self->_inspect_non_reference($val); | ||||
| 151 | } | ||||||
| 152 | } | ||||||
| 153 | |||||||
| 154 | =item set_option | ||||||
| 155 | |||||||
| 156 | $insp->set_option('truncate_strings', 30); | ||||||
| 157 | |||||||
| 158 | Set the given option to the given value. Options alter the output of | ||||||
| 159 | Inspect. | ||||||
| 160 | |||||||
| 161 | Available options are: | ||||||
| 162 | |||||||
| 163 | =over | ||||||
| 164 | |||||||
| 165 | =item truncate_strings | ||||||
| 166 | |||||||
| 167 | If set to a positive integer, truncates strings after that number of | ||||||
| 168 | characters, replacing the end with '...'. | ||||||
| 169 | |||||||
| 170 | default: undef | ||||||
| 171 | |||||||
| 172 | =item sort_keys | ||||||
| 173 | |||||||
| 174 | If set to the string 'cmp' or '<=>', hashes will have their keys | ||||||
| 175 | sorted using the specified comparison before being output. | ||||||
| 176 | |||||||
| 177 | default: undef | ||||||
| 178 | |||||||
| 179 | =back | ||||||
| 180 | |||||||
| 181 | =cut | ||||||
| 182 | |||||||
| 183 | sub set_option { | ||||||
| 184 | 2 | 2 | 1 | 358 | my ($self, $option, $value) = @_; | ||
| 185 | 2 | 50 | 4 | if (not grep {$_ eq $option} qw/truncate_strings sort_keys/) { | |||
| 4 | 9 | ||||||
| 186 | 0 | 0 | warn "Inspect: option '$option' is not a valid option"; | ||||
| 187 | 0 | 0 | return; | ||||
| 188 | } | ||||||
| 189 | 2 | 5 | $self->{options}{$option} = $value; | ||||
| 190 | } | ||||||
| 191 | |||||||
| 192 | =back | ||||||
| 193 | |||||||
| 194 | =cut | ||||||
| 195 | |||||||
| 196 | # Aux method for inspecting non-references. Mostly the grunt work here | ||||||
| 197 | # is done by Data::Dumper. | ||||||
| 198 | sub _inspect_non_reference { | ||||||
| 199 | 49 | 49 | 38 | my ($self, $val) = @_; | |||
| 200 | |||||||
| 201 | # Data::Dumper is good at inspecting non-references. If they're | ||||||
| 202 | # strings, it sorts out all the escaping. | ||||||
| 203 | 49 | 95 | my $dumper = Data::Dumper->new([$val]); | ||||
| 204 | 49 | 634 | $dumper->Useqq(1); # Use double quotes so we get nice things like \n | ||||
| 205 | 49 | 154 | $dumper->Terse(1); # Just the data, please. No $VAR1! | ||||
| 206 | 49 | 127 | chomp(my $dump = $dumper->Dump); | ||||
| 207 | |||||||
| 208 | # If we're truncating strings, do it here | ||||||
| 209 | 49 | 100 | 100 | 634 | if ($self->{options}{truncate_strings} and | ||
| 66 | |||||||
| 210 | length $dump > $self->{options}{truncate_strings}+2 | ||||||
| 211 | and $dump =~ /^"/) { | ||||||
| 212 | 1 | 4 | $dump = substr($dump, 0, $self->{options}{truncate_strings}+1).'..."'; | ||||
| 213 | } | ||||||
| 214 | |||||||
| 215 | 49 | 226 | return $dump; | ||||
| 216 | } | ||||||
| 217 | |||||||
| 218 | # Aux method for inspecting references. This is the bit we have to do | ||||||
| 219 | # ourselves because Data::Dumper is just too literal. | ||||||
| 220 | # | ||||||
| 221 | # The second argument, reftype, is taken to be the reftype instead of | ||||||
| 222 | # the autodetected one. | ||||||
| 223 | sub _inspect_reference { | ||||||
| 224 | 21 | 21 | 21 | my ($self, $val, $reftype) = @_; | |||
| 225 | |||||||
| 226 | # Avoid circular references by keeping track of the references we're | ||||||
| 227 | # currently inspecting. We have to ignore this check if $reftype is | ||||||
| 228 | # set because we are technically re-evaluating the same old thing. | ||||||
| 229 | 21 | 23 | my $refaddr = Scalar::Util::refaddr($val); | ||||
| 230 | 21 | 100 | 66 | 50 | if (not $reftype and exists $self->{tracker}{$refaddr}) { | ||
| 231 | 1 | 6 | return sprintf "# |
||||
| 232 | } | ||||||
| 233 | 20 | 35 | local $self->{tracker}{$refaddr} = 1; | ||||
| 234 | |||||||
| 235 | # Set $reftype to 'HASH', 'ARRAY', 'object' etc. | ||||||
| 236 | 20 | 100 | 27 | if (not $reftype) { | |||
| 237 | 17 | 100 | 27 | if (Scalar::Util::blessed($val)) { | |||
| 238 | 5 | 12 | $reftype = 'object'; | ||||
| 239 | } | ||||||
| 240 | else { | ||||||
| 241 | 12 | 12 | $reftype = ref $val; | ||||
| 242 | } | ||||||
| 243 | } | ||||||
| 244 | |||||||
| 245 | # If we have a method for inspecting this reftype, call it | ||||||
| 246 | 20 | 23 | my $method = "_inspect_$reftype"; | ||||
| 247 | 20 | 100 | 52 | if ($self->can($method)) { | |||
| 248 | 19 | 31 | $self->$method($val); | ||||
| 249 | } | ||||||
| 250 | # Otherwise, we call the _inspect_other with $val and $reftype | ||||||
| 251 | else { | ||||||
| 252 | 1 | 3 | $self->_inspect_other($val, $reftype); | ||||
| 253 | } | ||||||
| 254 | } | ||||||
| 255 | |||||||
| 256 | # Inspect an object. If the object defines an inspect() method this is | ||||||
| 257 | # easy. If it doesn't, we just return the class and the underlying | ||||||
| 258 | # reference inspected. | ||||||
| 259 | sub _inspect_object { | ||||||
| 260 | 5 | 5 | 5 | my ($self, $val) = @_; | |||
| 261 | # Object's class defines an 'inspect' | ||||||
| 262 | 5 | 100 | 40 | if ($val->can('inspect')) { | |||
| 263 | 2 | 5 | return $val->inspect($self); | ||||
| 264 | } | ||||||
| 265 | # Otherwise return the object's class name followed by an inspection | ||||||
| 266 | # of the underlying representation. | ||||||
| 267 | else { | ||||||
| 268 | 3 | 4 | my $class = Scalar::Util::blessed($val); | ||||
| 269 | 3 | 11 | my $inspected = | ||||
| 270 | $self->_inspect_reference($val, Scalar::Util::reftype($val)); | ||||||
| 271 | 3 | 18 | return "#<$class $inspected>"; | ||||
| 272 | } | ||||||
| 273 | } | ||||||
| 274 | |||||||
| 275 | # Inspect a scalar reference. Well, this is just the same as | ||||||
| 276 | # inspecting the scalar it references but with a \ in front. | ||||||
| 277 | sub _inspect_SCALAR { | ||||||
| 278 | 1 | 1 | 2 | my ($self, $val) = @_; | |||
| 279 | 1 | 3 | return q{\\}.$self->inspect($$val); | ||||
| 280 | } | ||||||
| 281 | |||||||
| 282 | # Inspect a hash reference. This is a lot like Data::Dumper except we | ||||||
| 283 | # inspect all the keys and values and provide it in a nice one-line | ||||||
| 284 | # format. | ||||||
| 285 | sub _inspect_HASH { | ||||||
| 286 | 7 | 7 | 4 | my ($self, $val) = @_; | |||
| 287 | |||||||
| 288 | # Do the keys need sorting? | ||||||
| 289 | 7 | 6 | my @keys; | ||||
| 290 | 7 | 50 | 33 | 24 | if ($self->{options}{sort_keys} and $self->{options}{sort_keys} eq 'cmp') { | ||
| 0 | 0 | ||||||
| 291 | 7 | 28 | @keys = sort keys %$val; | ||||
| 292 | } | ||||||
| 293 | elsif ($self->{options}{sort_keys} and $self->{options}{sort_keys} eq '<=>') { | ||||||
| 294 | 0 | 0 | @keys = sort {$a<=>$b} keys %$val; | ||||
| 0 | 0 | ||||||
| 295 | } | ||||||
| 296 | else { | ||||||
| 297 | 0 | 0 | @keys = keys %$val; | ||||
| 298 | } | ||||||
| 299 | |||||||
| 300 | 7 | 8 | my $ostr = '{'; | ||||
| 301 | 7 | 8 | my @vals = map { $self->inspect($_).' => '.$self->inspect($val->{$_}) } @keys; | ||||
| 12 | 16 | ||||||
| 302 | 7 | 15 | $ostr .= join ', ', @vals; | ||||
| 303 | 7 | 34 | return "$ostr}"; | ||||
| 304 | } | ||||||
| 305 | |||||||
| 306 | # Inspect an array reference. | ||||||
| 307 | sub _inspect_ARRAY { | ||||||
| 308 | 5 | 5 | 4 | my ($self, $val) = @_; | |||
| 309 | 5 | 5 | my $ostr = '['; | ||||
| 310 | 5 | 6 | my @vals = map { $self->inspect($_) } @$val; | ||||
| 20 | 27 | ||||||
| 311 | 5 | 10 | $ostr .= join ', ', @vals; | ||||
| 312 | 5 | 31 | return "$ostr]"; | ||||
| 313 | } | ||||||
| 314 | |||||||
| 315 | # Inspect a glob reference. | ||||||
| 316 | sub _inspect_GLOB { | ||||||
| 317 | 1 | 1 | 1 | my ($self, $val) = @_; | |||
| 318 | 1 | 2 | my $ostr = '# | ||||
| 319 | 1 | 3 | foreach (qw/NAME SCALAR ARRAY HASH CODE IO GLOB FORMAT PACKAGE/) { | ||||
| 320 | 9 | 100 | 7 | if (defined *{$val}{$_}) { | |||
| 9 | 18 | ||||||
| 321 | 6 | 7 | $ostr .= " $_=".$self->inspect(*{$val}{$_}); | ||||
| 6 | 8 | ||||||
| 322 | } | ||||||
| 323 | } | ||||||
| 324 | 1 | 3 | return "$ostr>"; | ||||
| 325 | } | ||||||
| 326 | |||||||
| 327 | # Inspect anything else. This takes a second argument, which is the | ||||||
| 328 | # type of reference we're inspecting. | ||||||
| 329 | sub _inspect_other { | ||||||
| 330 | 1 | 1 | 2 | my ($self, $val, $reftype) = @_; | |||
| 331 | 1 | 3 | return "#<$reftype>"; | ||||
| 332 | } | ||||||
| 333 | |||||||
| 334 | =head1 EXAMPLES | ||||||
| 335 | |||||||
| 336 | =head2 Inspecting built-in Perl types | ||||||
| 337 | |||||||
| 338 | In this example, we use the L method to output the inspected contents | ||||||
| 339 | of a Perl hash: | ||||||
| 340 | |||||||
| 341 | use Data::Inspect qw(p); | ||||||
| 342 | p \%some_hash; | ||||||
| 343 | |||||||
| 344 | The output is something like: | ||||||
| 345 | |||||||
| 346 | {"baz" => "qux\n\n", "foo" => "bar"} | ||||||
| 347 | |||||||
| 348 | =head2 Changing how an object looks | ||||||
| 349 | |||||||
| 350 | In this example, objects of class C |
||||||
| 351 | containing a lot of data. They are uniquely identifiable by one key, | ||||||
| 352 | C |
||||||
| 353 | |||||||
| 354 | package Wibble; | ||||||
| 355 | |||||||
| 356 | sub inspect { | ||||||
| 357 | my ($self, $insp) = @_; | ||||||
| 358 | "# |
||||||
| 359 | } | ||||||
| 360 | |||||||
| 361 | If we have a hash full of Wibbles we can now see its contents easily | ||||||
| 362 | by inspecting it: | ||||||
| 363 | |||||||
| 364 | use Data::Inspect qw(p); | ||||||
| 365 | p \%hash_of_wibbles; | ||||||
| 366 | |||||||
| 367 | The output will be something like: | ||||||
| 368 | |||||||
| 369 | {"bar" => # |
||||||
| 370 | |||||||
| 371 | =head2 Recursive inspecting | ||||||
| 372 | |||||||
| 373 | $_[1] is set to the current Data::Inspect object in calls to an | ||||||
| 374 | object's C |
||||||
| 375 | data structures contained within the object, such as hashes: | ||||||
| 376 | |||||||
| 377 | package Wibble; | ||||||
| 378 | |||||||
| 379 | sub inspect { | ||||||
| 380 | my ($self, $insp) = @_; | ||||||
| 381 | "# |
||||||
| 382 | } | ||||||
| 383 | |||||||
| 384 | =head2 Using Data::Inspect in the OO form | ||||||
| 385 | |||||||
| 386 | The OO form provides a greater degree of flexibility than just | ||||||
| 387 | importing the L method. The behaviour of Data::Inspect can be | ||||||
| 388 | modified using the L method and there is also an | ||||||
| 389 | L method that returns the inspected form rather than | ||||||
| 390 | outputting it. | ||||||
| 391 | |||||||
| 392 | use Data::Inspect; | ||||||
| 393 | my $insp = Data::Inspect->new; | ||||||
| 394 | |||||||
| 395 | # Strings are truncated if they are more than 10 characters long | ||||||
| 396 | $insp->set_option('truncate_strings', 10); | ||||||
| 397 | |||||||
| 398 | $insp->p("Supercalifragilisticexpialidocious"); | ||||||
| 399 | |||||||
| 400 | Outputs: | ||||||
| 401 | |||||||
| 402 | "Supercalif..." | ||||||
| 403 | |||||||
| 404 | =head1 SEE ALSO | ||||||
| 405 | |||||||
| 406 | L |
||||||
| 407 | |||||||
| 408 | The Ruby documentation for C | ||||||
| 409 | http://www.ruby-doc.org/core/ | ||||||
| 410 | |||||||
| 411 | =head1 CHANGES | ||||||
| 412 | |||||||
| 413 | - 0.05 Fix deprecated regexp in test (thanks Jim Keenan!) | ||||||
| 414 | |||||||
| 415 | - 0.04 Fixed test case 7 to work with Perl 5.11.5 | ||||||
| 416 | |||||||
| 417 | - 0.03 Fixed documentation and tests further. | ||||||
| 418 | |||||||
| 419 | - 0.02 Added support and documentation for recursive inspecting. | ||||||
| 420 | Fixed tests on versions of perl built without useperlio. | ||||||
| 421 | |||||||
| 422 | - 0.01 Initial revision | ||||||
| 423 | |||||||
| 424 | =head1 AUTHOR | ||||||
| 425 | |||||||
| 426 | Rich Daley |
||||||
| 427 | |||||||
| 428 | =head1 COPYRIGHT | ||||||
| 429 | |||||||
| 430 | Copyright (c) 2009 Rich Daley. All rights reserved. | ||||||
| 431 | |||||||
| 432 | This library is free software; you can redistribute it and/or modify | ||||||
| 433 | it under the same terms as Perl itself. | ||||||
| 434 | |||||||
| 435 | =cut | ||||||
| 436 | |||||||
| 437 | 1; |