blib/lib/Data/Inspect.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 86 | 96 | 89.5 |
branch | 22 | 30 | 73.3 |
condition | 13 | 21 | 61.9 |
subroutine | 17 | 19 | 89.4 |
pod | 6 | 6 | 100.0 |
total | 144 | 172 | 83.7 |
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.04'; | ||||||
38 | |||||||
39 | 1 | 1 | 23356 | use strict; | |||
1 | 2 | ||||||
1 | 29 | ||||||
40 | 1 | 1 | 4 | use warnings; | |||
1 | 2 | ||||||
1 | 21 | ||||||
41 | |||||||
42 | 1 | 1 | 926 | use Data::Dumper (); | |||
1 | 9367 | ||||||
1 | 28 | ||||||
43 | 1 | 1 | 9 | use Scalar::Util (); | |||
1 | 1 | ||||||
1 | 21 | ||||||
44 | |||||||
45 | 1 | 1 | 6 | use base 'Exporter'; | |||
1 | 2 | ||||||
1 | 1523 | ||||||
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 | 15 | my ($class) = @_; | ||
62 | 2 | 6 | my $self = bless {}, $class; | ||||
63 | |||||||
64 | # Initialize values | ||||||
65 | 2 | 11 | $self->{tracker} = {}; | ||||
66 | 2 | 7 | $self->{options}{truncate_strings} = undef; | ||||
67 | |||||||
68 | 2 | 6 | 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 | 3477 | my $self = UNIVERSAL::isa($_[0], __PACKAGE__) ? shift : __PACKAGE__->new; | |
88 | 4 | 14 | 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 | 946 | 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 | 354 | if (@_ < 2 or (ref $val and | ||
33 | |||||||
139 | Scalar::Util::refaddr($val) == Scalar::Util::refaddr($self))) { | ||||||
140 | 1 | 7 | return "# |
||||
141 | } | ||||||
142 | |||||||
143 | # If it's a reference, we delegate it to _inspect_reference | ||||||
144 | 67 | 100 | 123 | if (ref $val) { | |||
145 | 18 | 42 | return $self->_inspect_reference($val); | ||||
146 | } | ||||||
147 | |||||||
148 | # Otherwise, delegate to _inspect_non_reference | ||||||
149 | else { | ||||||
150 | 49 | 98 | 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 | 438 | my ($self, $option, $value) = @_; | ||
185 | 2 | 50 | 5 | if (not grep {$_ eq $option} qw/truncate_strings sort_keys/) { | |||
4 | 16 | ||||||
186 | 0 | 0 | warn "Inspect: option '$option' is not a valid option"; | ||||
187 | 0 | 0 | return; | ||||
188 | } | ||||||
189 | 2 | 7 | $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 | 63 | 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 | 247 | my $dumper = Data::Dumper->new([$val]); | ||||
204 | 49 | 1350 | $dumper->Useqq(1); # Use double quotes so we get nice things like \n | ||||
205 | 49 | 284 | $dumper->Terse(1); # Just the data, please. No $VAR1! | ||||
206 | 49 | 250 | chomp(my $dump = $dumper->Dump); | ||||
207 | |||||||
208 | # If we're truncating strings, do it here | ||||||
209 | 49 | 100 | 100 | 1045 | if ($self->{options}{truncate_strings} and | ||
66 | |||||||
210 | length $dump > $self->{options}{truncate_strings}+2 | ||||||
211 | and $dump =~ /^"/) { | ||||||
212 | 1 | 5 | $dump = substr($dump, 0, $self->{options}{truncate_strings}+1).'..."'; | ||||
213 | } | ||||||
214 | |||||||
215 | 49 | 391 | 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 | 38 | 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 | 38 | my $refaddr = Scalar::Util::refaddr($val); | ||||
230 | 21 | 100 | 100 | 103 | if (not $reftype and exists $self->{tracker}{$refaddr}) { | ||
231 | 1 | 9 | return sprintf "# |
||||
232 | } | ||||||
233 | 20 | 55 | local $self->{tracker}{$refaddr} = 1; | ||||
234 | |||||||
235 | # Set $reftype to 'HASH', 'ARRAY', 'object' etc. | ||||||
236 | 20 | 100 | 40 | if (not $reftype) { | |||
237 | 17 | 100 | 43 | if (Scalar::Util::blessed($val)) { | |||
238 | 5 | 28 | $reftype = 'object'; | ||||
239 | } | ||||||
240 | else { | ||||||
241 | 12 | 24 | $reftype = ref $val; | ||||
242 | } | ||||||
243 | } | ||||||
244 | |||||||
245 | # If we have a method for inspecting this reftype, call it | ||||||
246 | 20 | 37 | my $method = "_inspect_$reftype"; | ||||
247 | 20 | 100 | 78 | if ($self->can($method)) { | |||
248 | 19 | 49 | $self->$method($val); | ||||
249 | } | ||||||
250 | # Otherwise, we call the _inspect_other with $val and $reftype | ||||||
251 | else { | ||||||
252 | 1 | 4 | $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 | 9 | my ($self, $val) = @_; | |||
261 | # Object's class defines an 'inspect' | ||||||
262 | 5 | 100 | 64 | if ($val->can('inspect')) { | |||
263 | 2 | 8 | 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 | 8 | my $class = Scalar::Util::blessed($val); | ||||
269 | 3 | 18 | my $inspected = | ||||
270 | $self->_inspect_reference($val, Scalar::Util::reftype($val)); | ||||||
271 | 3 | 36 | 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 | 3 | my ($self, $val) = @_; | |||
279 | 1 | 4 | 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 | 11 | my ($self, $val) = @_; | |||
287 | |||||||
288 | # Do the keys need sorting? | ||||||
289 | 7 | 9 | my @keys; | ||||
290 | 7 | 50 | 33 | 38 | if ($self->{options}{sort_keys} and $self->{options}{sort_keys} eq 'cmp') { | ||
0 | 0 | ||||||
291 | 7 | 42 | @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 | 13 | my $ostr = '{'; | ||||
301 | 7 | 12 | my @vals = map { $self->inspect($_).' => '.$self->inspect($val->{$_}) } @keys; | ||||
12 | 26 | ||||||
302 | 7 | 20 | $ostr .= join ', ', @vals; | ||||
303 | 7 | 55 | return "$ostr}"; | ||||
304 | } | ||||||
305 | |||||||
306 | # Inspect an array reference. | ||||||
307 | sub _inspect_ARRAY { | ||||||
308 | 5 | 5 | 7 | my ($self, $val) = @_; | |||
309 | 5 | 9 | my $ostr = '['; | ||||
310 | 5 | 9 | my @vals = map { $self->inspect($_) } @$val; | ||||
20 | 45 | ||||||
311 | 5 | 16 | $ostr .= join ', ', @vals; | ||||
312 | 5 | 54 | return "$ostr]"; | ||||
313 | } | ||||||
314 | |||||||
315 | # Inspect a glob reference. | ||||||
316 | sub _inspect_GLOB { | ||||||
317 | 1 | 1 | 2 | my ($self, $val) = @_; | |||
318 | 1 | 2 | my $ostr = '# | ||||
319 | 1 | 4 | foreach (qw/NAME SCALAR ARRAY HASH CODE IO GLOB FORMAT PACKAGE/) { | ||||
320 | 9 | 100 | 10 | if (defined *{$val}{$_}) { | |||
9 | 33 | ||||||
321 | 6 | 9 | $ostr .= " $_=".$self->inspect(*{$val}{$_}); | ||||
6 | 18 | ||||||
322 | } | ||||||
323 | } | ||||||
324 | 1 | 5 | 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 | 6 | 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.04 Fixed test case 7 to work with Perl 5.11.5 | ||||||
414 | |||||||
415 | - 0.03 Fixed documentation and tests further. | ||||||
416 | |||||||
417 | - 0.02 Added support and documentation for recursive inspecting. | ||||||
418 | Fixed tests on versions of perl built without useperlio. | ||||||
419 | |||||||
420 | - 0.01 Initial revision | ||||||
421 | |||||||
422 | =head1 AUTHOR | ||||||
423 | |||||||
424 | Rich Daley |
||||||
425 | |||||||
426 | =head1 COPYRIGHT | ||||||
427 | |||||||
428 | Copyright (c) 2009 Rich Daley. All rights reserved. | ||||||
429 | |||||||
430 | This library is free software; you can redistribute it and/or modify | ||||||
431 | it under the same terms as Perl itself. | ||||||
432 | |||||||
433 | =cut | ||||||
434 | |||||||
435 | 1; |