File Coverage

blib/lib/Devel/PartialDump.pm
Criterion Covered Total %
statement 131 154 85.0
branch 54 68 79.4
condition 25 32 78.1
subroutine 31 36 86.1
pod 20 24 83.3
total 261 314 83.1


line stmt bran cond sub pod time code
1 3     3   58134 use strict;
  3         7  
  3         117  
2 3     3   13 use warnings;
  3         4  
  3         156  
3             package Devel::PartialDump; # git description: v0.17-22-g89868be
4             # ABSTRACT: Partial dumping of data structures, optimized for argument printing.
5             # KEYWORDS: development debugging dump dumper diagnostics deep data structures
6             # vim: set ts=8 sts=4 sw=4 tw=78 et :
7              
8             our $VERSION = '0.18';
9              
10 3     3   11 use Carp ();
  3         5  
  3         47  
11 3     3   12 use Scalar::Util qw(looks_like_number reftype blessed);
  3         4  
  3         406  
12              
13 3     3   1518 use namespace::clean 0.19;
  3         41822  
  3         17  
14              
15             use Class::Tiny {
16 3         39 max_length => undef,
17             max_elements => 6,
18             max_depth => 2,
19             stringify => 0,
20             pairs => 1,
21             objects => 1,
22             list_delim => ", ",
23             pair_delim => ": ",
24 3     3   2159 };
  3         10788  
25              
26             use Sub::Exporter -setup => {
27             exports => [qw(dump warn show show_scalar croak carp confess cluck $default_dumper)],
28             groups => {
29             easy => [qw(dump warn show show_scalar carp croak)],
30             carp => [qw(croak carp)],
31             },
32             collectors => {
33             override_carp => sub {
34 3     3   4041 no warnings 'redefine';
  3         4  
  3         260  
35 0         0 require Carp;
36 0         0 *Carp::caller_info = \&replacement_caller_info;
37             },
38             },
39 3     3   1878 };
  3         30055  
  3         68  
40              
41             # a replacement for Carp::caller_info
42             sub replacement_caller_info {
43 0     0 0 0 my $i = shift(@_) + 1;
44              
45             package DB; # git description: v0.17-22-g89868be
46 0         0 my %call_info;
47             @call_info{
48 0         0 qw(pack file line sub has_args wantarray evaltext is_require)
49             } = caller($i);
50              
51 0 0       0 return unless (defined $call_info{pack});
52              
53 0         0 my $sub_name = Carp::get_subname(\%call_info);
54              
55 0 0       0 if ($call_info{has_args}) {
56 0         0 $sub_name .= '(' . Devel::PartialDump::dump(@DB::args) . ')';
57             }
58              
59 0         0 $call_info{sub_name} = $sub_name;
60              
61 0 0       0 return wantarray() ? %call_info : \%call_info;
62             }
63              
64              
65             sub warn_str {
66 3     3 0 6 my ( @args ) = @_;
67 3         3 my $self;
68              
69 3 100 66     21 if ( blessed($args[0]) and $args[0]->isa(__PACKAGE__) ) {
70 1         3 $self = shift @args;
71             } else {
72 2         3 $self = our $default_dumper;
73             }
74             return $self->_join(
75             map {
76 3 50 33     5 !ref($_) && defined($_)
  3         16  
77             ? $_
78             : $self->dump($_)
79             } @args
80             );
81             }
82              
83             sub warn {
84 2     2 1 826 Carp::carp(warn_str(@_));
85             }
86              
87             foreach my $f ( qw(carp croak confess cluck) ) {
88 3     3   2212 no warnings 'redefine';
  3         6  
  3         4423  
89 1     1 1 1213 eval "sub $f {
  1     0 1 4  
  0     0 1 0  
  0     0 1 0  
  0         0  
  0         0  
  0         0  
  0         0  
90             local \$Carp::CarpLevel = \$Carp::CarpLevel + 1;
91             Carp::$f(warn_str(\@_));
92             }";
93             }
94              
95             sub show {
96 1     1 1 1056 my ( @args ) = @_;
97 1         1 my $self;
98              
99 1 50 33     8 if ( blessed($args[0]) and $args[0]->isa(__PACKAGE__) ) {
100 0         0 $self = shift @args;
101             } else {
102 1         2 $self = our $default_dumper;
103             }
104              
105 1         3 $self->warn(@args);
106              
107 1 50       670 return ( @args == 1 ? $args[0] : @args );
108             }
109              
110 0     0 1 0 sub show_scalar ($) { goto \&show }
111              
112             sub _join {
113 3     3   6 my ( $self, @strings ) = @_;
114              
115 3         4 my $ret = "";
116              
117 3 50       6 if ( @strings ) {
118 3   50     13 my $sep = $, || $" || " ";
119 3         32 my $re = qr/(?: \s| \Q$sep\E )$/x;
120              
121 3         4 my $last = pop @strings;
122              
123 3         8 foreach my $string ( @strings ) {
124 0         0 $ret .= $string;
125 0 0       0 $ret .= $sep unless $string =~ $re;
126             }
127              
128 3         7 $ret .= $last;
129             }
130              
131 3         47 return $ret;
132             }
133              
134             sub dump {
135 38     38 1 8085 my ( @args ) = @_;
136 38         38 my $self;
137              
138 38 100 66     313 if ( blessed($args[0]) and $args[0]->isa(__PACKAGE__) ) {
139 37         53 $self = shift @args;
140             } else {
141 1         3 $self = our $default_dumper;
142             }
143              
144 38 100       82 my $method = "dump_as_" . ( $self->should_dump_as_pairs(@args) ? "pairs" : "list" );
145              
146 38         188 my $dump = $self->$method(1, @args);
147              
148 38 100 100     808 if ( defined $self->max_length and length($dump) > $self->max_length ) {
149 2         67 my $max_length = $self->max_length - 3;
150 2 100       12 $max_length = 0 if $max_length < 0;
151 2         6 substr( $dump, $max_length, length($dump) - $max_length ) = '...';
152             }
153              
154 38 50       331 if ( not defined wantarray ) {
155 0         0 CORE::warn "$dump\n";
156             } else {
157 38         191 return $dump;
158             }
159             }
160              
161             sub should_dump_as_pairs {
162 38     38 0 55 my ( $self, @what ) = @_;
163              
164 38 100       862 return unless $self->pairs;
165              
166 29 100       286 return if @what % 2 != 0; # must be an even list
167              
168 7         18 for ( my $i = 0; $i < @what; $i += 2 ) {
169 23 100       54 return if ref $what[$i]; # plain strings are keys
170             }
171              
172 6         16 return 1;
173             }
174              
175             sub dump_as_pairs {
176 11     11 1 28 my ( $self, $depth, @what ) = @_;
177              
178 11         11 my $truncated;
179 11 100 100     175 if ( defined $self->max_elements and ( @what / 2 ) > $self->max_elements ) {
180 2         46 $truncated = 1;
181 2         27 @what = splice(@what, 0, $self->max_elements * 2 );
182             }
183              
184 11 100       381 return join( $self->list_delim, $self->_dump_as_pairs($depth, @what), ($truncated ? "..." : ()) );
185             }
186              
187             sub _dump_as_pairs {
188 33     33   96 my ( $self, $depth, @what ) = @_;
189              
190 33 100       107 return unless @what;
191              
192 22         28 my ( $key, $value, @rest ) = @what;
193              
194             return (
195 22         33 ( $self->format_key($depth, $key) . $self->pair_delim . $self->format($depth, $value) ),
196             $self->_dump_as_pairs($depth, @rest),
197             );
198             }
199              
200             sub dump_as_list {
201 40     40 1 64 my ( $self, $depth, @what ) = @_;
202              
203 40         33 my $truncated;
204 40 100 100     689 if ( defined $self->max_elements and @what > $self->max_elements ) {
205 2         44 $truncated = 1;
206 2         36 @what = splice(@what, 0, $self->max_elements );
207             }
208              
209 40 100       1557 return join( $self->list_delim, ( map { $self->format($depth, $_) } @what ), ($truncated ? "..." : ()) );
  100         312  
210             }
211              
212             sub format {
213 127     127 1 259 my ( $self, $depth, $value ) = @_;
214              
215 127 100       446 defined($value)
    100          
    100          
    100          
216             ? ( ref($value)
217             ? ( blessed($value)
218             ? $self->format_object($depth, $value)
219             : $self->format_ref($depth, $value) )
220             : ( looks_like_number($value)
221             ? $self->format_number($depth, $value)
222             : $self->format_string($depth, $value) ) )
223             : $self->format_undef($depth, $value),
224             }
225              
226             sub format_key {
227 22     22 1 22 my ( $self, $depth, $key ) = @_;
228 22         305 return $key;
229             }
230              
231             sub format_ref {
232 20     20 1 21 my ( $self, $depth, $ref ) = @_;
233              
234 20 100       350 if ( $depth > $self->max_depth ) {
235 2         15 return overload::StrVal($ref);
236             } else {
237 18         122 my $reftype = reftype($ref);
238 18 100 100     72 $reftype = 'SCALAR'
239             if $reftype eq 'REF' || $reftype eq 'LVALUE';
240 18         41 my $method = "format_" . lc $reftype;
241              
242 18 50       79 if ( $self->can($method) ) {
243 18         45 return $self->$method( $depth, $ref );
244             } else {
245 0         0 return overload::StrVal($ref);
246             }
247             }
248             }
249              
250             sub format_array {
251 8     8 1 11 my ( $self, $depth, $array ) = @_;
252              
253 8   100     30 my $class = blessed($array) || '';
254 8 100       14 $class .= "=" if $class;
255              
256 8         56 return $class . "[ " . $self->dump_as_list($depth + 1, @$array) . " ]";
257             }
258              
259             sub format_hash {
260 5     5 1 7 my ( $self, $depth, $hash ) = @_;
261              
262 5   100     22 my $class = blessed($hash) || '';
263 5 100       13 $class .= "=" if $class;
264              
265 5         48 return $class . "{ " . $self->dump_as_pairs($depth + 1, map { $_ => $hash->{$_} } sort keys %$hash) . " }";
  7         22  
266             }
267              
268             sub format_scalar {
269 5     5 0 4 my ( $self, $depth, $scalar ) = @_;
270              
271 5   100     19 my $class = blessed($scalar) || '';
272 5 100       12 $class .= "=" if $class;
273              
274 5         52 return $class . "\\" . $self->format($depth + 1, $$scalar);
275             }
276              
277             sub format_object {
278 9     9 1 6 my ( $self, $depth, $object ) = @_;
279              
280 9 100       140 if ( $self->objects ) {
281 3         23 return $self->format_ref($depth, $object);
282             } else {
283 6 100       113 return $self->stringify ? "$object" : overload::StrVal($object);
284             }
285             }
286              
287             sub format_string {
288 21     21 1 25 my ( $self, $depth, $str ) =@_;
289             # FIXME use String::Escape ?
290              
291             # remove vertical whitespace
292 21         34 $str =~ s/\n/\\n/g;
293 21         25 $str =~ s/\r/\\r/g;
294              
295             # reformat nonprintables
296 3     3   2215 $str =~ s/(\P{IsPrint})/"\\x{" . sprintf("%x", ord($1)) . "}"/ge;
  3         27  
  3         39  
  21         42  
  1         11  
297              
298 21         51 $self->quote($str);
299             }
300              
301             sub quote {
302 21     21 1 27 my ( $self, $str ) = @_;
303              
304 21         159 qq{"$str"};
305             }
306              
307 1     1 1 7 sub format_undef { "undef" }
308              
309             sub format_number {
310 79     79 1 65 my ( $self, $depth, $value ) = @_;
311 79         146 return "$value";
312             }
313              
314             our $default_dumper = __PACKAGE__->new;
315              
316             1;
317              
318             __END__
319              
320             =pod
321              
322             =encoding UTF-8
323              
324             =head1 NAME
325              
326             Devel::PartialDump - Partial dumping of data structures, optimized for argument printing.
327              
328             =head1 VERSION
329              
330             version 0.18
331              
332             =head1 SYNOPSIS
333              
334             use Devel::PartialDump;
335              
336             sub foo {
337             print "foo called with args: " . Devel::PartialDump->new->dump(@_);
338             }
339              
340             use Devel::PartialDump qw(warn);
341              
342             # warn is overloaded to create a concise dump instead of stringifying $some_bad_data
343             warn "this made a boo boo: ", $some_bad_data
344              
345             =head1 DESCRIPTION
346              
347             This module is a data dumper optimized for logging of arbitrary parameters.
348              
349             It attempts to truncate overly verbose data, in a way that is hopefully more
350             useful for diagnostics warnings than
351              
352             warn Dumper(@stuff);
353              
354             Unlike other data dumping modules there are no attempts at correctness or cross
355             referencing, this is only meant to provide a slightly deeper look into the data
356             in question.
357              
358             There is a default recursion limit, and a default truncation of long lists, and
359             the dump is formatted on one line (new lines in strings are escaped), to aid in
360             readability.
361              
362             You can enable it temporarily by importing functions like C<warn>, C<croak> etc
363             to get more informative errors during development, or even use it as:
364              
365             BEGIN { local $@; eval "use Devel::PartialDump qw(...)" }
366              
367             to get DWIM formatting only if it's installed, without introducing a
368             dependency.
369              
370             =head1 SAMPLE OUTPUT
371              
372             =over 4
373              
374             =item C<< "foo" >>
375              
376             "foo"
377              
378             =item C<< "foo" => "bar" >>
379              
380             foo: "bar"
381              
382             =item C<< foo => "bar", gorch => [ 1, "bah" ] >>
383              
384             foo: "bar", gorch: [ 1, "bah" ]
385              
386             =item C<< [ { foo => ["bar"] } ] >>
387              
388             [ { foo: ARRAY(0x9b265d0) } ]
389              
390             =item C<< [ 1 .. 10 ] >>
391              
392             [ 1, 2, 3, 4, 5, 6, ... ]
393              
394             =item C<< "foo\nbar" >>
395              
396             "foo\nbar"
397              
398             =item C<< "foo" . chr(1) >>
399              
400             "foo\x{1}"
401              
402             =back
403              
404             =head1 ATTRIBUTES
405              
406             =over 4
407              
408             =item max_length
409              
410             The maximum character length of the dump.
411              
412             Anything bigger than this will be truncated.
413              
414             Not defined by default.
415              
416             =item max_elements
417              
418             The maximum number of elements (array elements or pairs in a hash) to print.
419              
420             Defaults to 6.
421              
422             =item max_depth
423              
424             The maximum level of recursion.
425              
426             Defaults to 2.
427              
428             =item stringify
429              
430             Whether or not to let objects stringify themselves, instead of using
431             L<overload/StrVal> to avoid side effects.
432              
433             Defaults to false (no overloading).
434              
435             =item pairs
436              
437             =for stopwords autodetect
438              
439             Whether or not to autodetect named args as pairs in the main C<dump> function.
440             If this attribute is true, and the top level value list is even sized, and
441             every odd element is not a reference, then it will dumped as pairs instead of a
442             list.
443              
444             =back
445              
446             =head1 EXPORTS
447              
448             All exports are optional, nothing is exported by default.
449              
450             This module uses L<Sub::Exporter>, so exports can be renamed, curried, etc.
451              
452             =over 4
453              
454             =item warn
455              
456             =item show
457              
458             =item show_scalar
459              
460             =item croak
461              
462             =item carp
463              
464             =item confess
465              
466             =item cluck
467              
468             =item dump
469              
470             See the various methods for behavior documentation.
471              
472             These methods will use C<$Devel::PartialDump::default_dumper> as the invocant if the
473             first argument is not blessed and C<isa> L<Devel::PartialDump>, so they can be
474             used as functions too.
475              
476             Particularly C<warn> can be used as a drop in replacement for the built in
477             warn:
478              
479             warn "blah blah: ", $some_data;
480              
481             by importing
482              
483             use Devel::PartialDump qw(warn);
484              
485             C<$some_data> will be have some of it's data dumped.
486              
487             =item $default_dumper
488              
489             The default dumper object to use for export style calls.
490              
491             Can be assigned to to alter behavior globally.
492              
493             This is generally useful when using the C<warn> export as a drop in replacement
494             for C<CORE::warn>.
495              
496             =back
497              
498             =head1 METHODS
499              
500             =over 4
501              
502             =item warn @blah
503              
504             A wrapper for C<dump> that prints strings plainly.
505              
506             =item show @blah
507              
508             =item show_scalar $x
509              
510             Like C<warn>, but instead of returning the value from C<warn> it returns its
511             arguments, so it can be used in the middle of an expression.
512              
513             Note that
514              
515             my $x = show foo();
516              
517             will actually evaluate C<foo> in list context, so if you only want to dump a
518             single element and retain scalar context use
519              
520             my $x = show_scalar foo();
521              
522             which has a prototype of C<$> (as opposed to taking a list).
523              
524             =for stopwords Ingy
525              
526             This is similar to the venerable Ingy's fabulous and amazing L<XXX> module.
527              
528             =item carp
529              
530             =item croak
531              
532             =item confess
533              
534             =item cluck
535              
536             Drop in replacements for L<Carp> exports, that format their arguments like
537             C<warn>.
538              
539             =item dump @stuff
540              
541             Returns a one line, human readable, concise dump of @stuff.
542              
543             If called in void context, will C<warn> with the dump.
544              
545             Truncates the dump according to C<max_length> if specified.
546              
547             =item dump_as_list $depth, @stuff
548              
549             =item dump_as_pairs $depth, @stuff
550              
551             Dump C<@stuff> using the various formatting functions.
552              
553             Dump as pairs returns comma delimited pairs with C<< => >> between the key and the value.
554              
555             Dump as list returns a comma delimited dump of the values.
556              
557             =item format $depth, $value
558              
559             =item format_key $depth, $key
560              
561             =item format_object $depth, $object
562              
563             =item format_ref $depth, $Ref
564              
565             =item format_array $depth, $array_ref
566              
567             =item format_hash $depth, $hash_ref
568              
569             =item format_undef $depth, undef
570              
571             =item format_string $depth, $string
572              
573             =item format_number $depth, $number
574              
575             =item quote $string
576              
577             The various formatting methods.
578              
579             You can override these to provide a custom format.
580              
581             C<format_array> and C<format_hash> recurse with C<$depth + 1> into
582             C<dump_as_list> and C<dump_as_pairs> respectively.
583              
584             C<format_ref> delegates to C<format_array> and C<format_hash> and does the
585             C<max_depth> tracking. It will simply stringify the ref if the recursion limit
586             has been reached.
587              
588             =back
589              
590             =head1 AUTHOR
591              
592             יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
593              
594             =head1 CONTRIBUTORS
595              
596             =for stopwords Karen Etheridge Florian Ragwitz Steven Lee Jesse Luehrs David Golden Leo Lapworth
597              
598             =over 4
599              
600             =item *
601              
602             Karen Etheridge <ether@cpan.org>
603              
604             =item *
605              
606             Florian Ragwitz <rafl@debian.org>
607              
608             =item *
609              
610             Steven Lee <stevenwh.lee@gmail.com>
611              
612             =item *
613              
614             Jesse Luehrs <doy@tozt.net>
615              
616             =item *
617              
618             David Golden <dagolden@cpan.org>
619              
620             =item *
621              
622             Leo Lapworth <web@web-teams-computer.local>
623              
624             =back
625              
626             =head1 COPYRIGHT AND LICENSE
627              
628             This software is copyright (c) 2008 by יובל קוג'מן (Yuval Kogman).
629              
630             This is free software; you can redistribute it and/or modify it under
631             the same terms as the Perl 5 programming language system itself.
632              
633             =cut