File Coverage

blib/lib/Data/Abridge.pm
Criterion Covered Total %
statement 112 116 96.5
branch 31 44 70.4
condition 3 3 100.0
subroutine 29 30 96.6
pod 4 4 100.0
total 179 197 90.8


line stmt bran cond sub pod time code
1             package Data::Abridge;
2             BEGIN {
3 5     5   174399 $Data::Abridge::VERSION = '0.03.01';
4             }
5              
6 5     5   48 use strict;
  5         13  
  5         299  
7 5     5   31 use warnings;
  5         10  
  5         218  
8              
9 5     5   27 use Exporter qw( import );
  5         11  
  5         182  
10 5     5   30 use Scalar::Util qw( blessed reftype refaddr );
  5         9  
  5         709  
11 5     5   2126 use overload ();
  5         1233  
  5         154  
12              
13 5     5   28 use Carp;
  5         9  
  5         591  
14              
15             our @EXPORT_OK = qw(
16             abridge_item abridge_items
17             abridge_recursive abridge_items_recursive
18             );
19              
20              
21 5     5   30 use constant BLESSED_REGEXP => blessed qr/foo/;
  5         11  
  5         585  
22 5     5   29 use constant REFTYPE_REGEXP => reftype qr/foo/;
  5         9  
  5         10031  
23              
24             # Munge a thing for nice serialization
25              
26             # Object -> { 'Package::Name' => };
27             # Code Ref -> '\&subname'
28             # -> '\&__ANON__'
29             # Scalar Ref -> { SCALAR => $scalar }
30             # Glob Ref -> '\*main:glob'
31              
32             my %SLOB_DISPATCH = (
33             SCALAR => \&_process_scalar,
34             REF => \&_process_ref,
35             HASH => \&_passthrough,
36             ARRAY => \&_passthrough,
37             GLOB => \&_process_glob,
38             CODE => \&_process_code,
39             BLESSED => \&_process_object,
40             REGEXP => \&_process_regexp,
41             );
42              
43             my %COPY_DISPATCH = (
44             SCALAR => \&_process_scalar,
45             REF => \&_process_ref,
46             HASH => \&_process_hash,
47             ARRAY => \&_process_array,
48             GLOB => \&_process_glob,
49             CODE => \&_process_code,
50             REGEXP => \&_process_regexp,
51             );
52              
53             my %RECURSE_DISPATCH = (
54             REF => \&_recurse_ref,
55             HASH => \&_recurse_hash,
56             ARRAY => \&_recurse_array,
57             BLESSED => \&_recurse_object,
58             );
59              
60             our $HONOR_STRINGIFY = 1;
61              
62             our %SEEN; # Global hash for tracking self-referential structures.
63             # Should be localized by entry to recursive abridge functions.
64             our @PATH; # Also localized for tracking the current path to any given entry
65             # in the abridged structure.
66              
67 37     37   93 sub _passthrough { return $_ }
68 10     10   48 sub _process_ref { return { SCALAR => $$_ } }
69 5     5   32 sub _process_glob { return { GLOB => '\\'.*$_ } }
70 14     14   45 sub _process_hash { return {%$_} }
71 3     3   11 sub _process_array { return [@$_] }
72 8     8   65 sub _process_scalar { return { SCALAR => $$_} }
73 2     2   11 sub _process_regexp { return { Regexp => "$_" } }
74              
75             sub _process_object {
76 29     29   54 my $obj = $_;
77              
78 29         80 my $class = blessed $obj;
79 29 50       69 return unless defined $class;
80              
81 29 100 100     120 if( $HONOR_STRINGIFY && overload::Method($obj, '""') ) {
82             # overloads String ?
83 3         215 return "$obj";
84             }
85             else {
86             # Shallow Copy
87 26 50       19353 my $type = _is_Regexp( $obj )
88             ? 'REGEXP'
89             : reftype $obj;
90              
91 26 50       104 my $value = exists $COPY_DISPATCH{$type}
92             ? $COPY_DISPATCH{$type}->()
93             : _unsupported_type( $obj );
94              
95 26         134 return { $class => $value };
96             }
97             }
98              
99             sub _process_code {
100 6     6   32 require B;
101 6         50 my $cv = B::svref_2object($_);
102 6 50       73 $cv->isa('B::CV') or return;
103              
104             # bail out if GV is undefined
105 6 50       629 $cv->GV->isa('B::SPECIAL') and return;
106              
107 6         77 my $subname = join "::", $cv->GV->STASH->NAME, $cv->GV->NAME;
108 6         32 return {CODE => "\\&$subname"};
109             }
110              
111             sub _unsupported_type {
112 0     0   0 my $item = shift;
113 0         0 my $type = reftype $item;
114              
115 0         0 return "Unsupported type: '$type' for $item";
116             }
117              
118             sub _is_Regexp {
119 26     26   185 require B;
120 26         123 my $sv = B::svref_2object($_);
121 26 50       236 $sv->isa('B::PVMG') or return;
122 26 50       230 my $m = $sv->MAGIC or return;
123              
124 0         0 return $m->TYPE eq 'r';
125             }
126              
127             sub abridge_items {
128 4     4 1 5465 return [ map abridge_item($_), @_ ];
129             }
130              
131             sub abridge_item {
132 109     109 1 4032 my $item = shift;
133              
134 109         204 my $type = reftype $item;
135              
136 109 100       229 return $item unless $type;
137              
138 88         160 my $blessed = blessed $item;
139 88 100       160 if( $blessed ) {
140 30 100       76 $type = $blessed eq BLESSED_REGEXP ? 'REGEXP' : 'BLESSED';
141             }
142              
143 88         142 my $slobd = $SLOB_DISPATCH{$type};
144 88 50       158 $slobd = \&_unsupported_type unless defined $slobd;;
145              
146 88         220 return $slobd->($_) for $item;
147             }
148              
149              
150             sub _recurse_ref {
151 5     5   6 my $processed_ref = shift;
152              
153 5         8 my $val = $processed_ref->{SCALAR};
154              
155 5         7 push @PATH, 'SCALAR';
156 5         9 $processed_ref->{SCALAR} = _abridge_recursive($val);
157 5         7 pop @PATH;
158              
159 5         6 return $processed_ref;
160             }
161              
162             sub _recurse_array {
163 16     16   45 my $processed_array = shift;
164              
165 29         57 my @result = map {
166 16         41 push @PATH, $_;
167 29         71 my @a = _abridge_recursive($processed_array->[$_]);
168 29         41 pop @PATH;
169 29         68 @a;
170             } 0 .. $#$processed_array;
171              
172 16         87 return \@result;
173             }
174              
175             sub _recurse_hash {
176 20     20   54 my $processed_hash = shift;
177              
178 20         25 my %new_hash;
179 20         54 for my $k ( keys %$processed_hash ) {
180 24         42 push @PATH, $k;
181 24         97 $new_hash{$k} = _abridge_recursive( $processed_hash->{$k} );
182 24         57 pop @PATH;
183             }
184              
185 20         45 return \%new_hash;
186             }
187              
188             sub _recurse_object {
189 13     13   21 my $processed_object = shift;
190              
191 13 50       35 return unless ref $processed_object;
192 13 50       50 return unless reftype $processed_object eq 'HASH';
193              
194 13         35 my ( $key, $value ) = each %$processed_object;
195 13         34 my $type = reftype $value;
196 13 50       40 $type = '' unless defined $type;
197              
198              
199 13         20 push @PATH, $key;
200              
201 13 50       55 $value = $RECURSE_DISPATCH{$type}->( $value )
202             if exists $RECURSE_DISPATCH{$type};
203              
204 13         19 pop @PATH;
205              
206 13         29 my %new_obj = ( $key => $value );
207              
208 13         29 return \%new_obj;
209             }
210              
211              
212             sub abridge_recursive {
213 25     25 1 11602 local %SEEN;
214 25         36 local @PATH;
215 25         65 &_abridge_recursive;
216             }
217              
218             sub _abridge_recursive {
219 88     88   155 my $item = shift;
220              
221 88         184 my $type = reftype $item;
222 88 100       237 $type = 'BLESSED' if blessed $item;
223 88 100       172 $type = '' unless defined $type;
224              
225 88         162 my $repl = abridge_item($item);
226              
227             # repl may have become a plain old scalar.
228             # Can't recurse that.
229 88         206 my $repl_type = reftype $repl;
230 88 100       210 return $repl unless defined $repl_type;
231              
232 66 100       149 if ( exists $RECURSE_DISPATCH{$type} ) {
233 59         138 my $id = refaddr $item;
234 59 50       110 $id = '' unless defined $id;
235              
236 59 100       173 return { SEEN => [ @{$SEEN{$id}} ] }
  18         91  
237             if exists $SEEN{$id};
238              
239 41         114 $SEEN{$id} = [@PATH];
240 41         146 $repl = $RECURSE_DISPATCH{$type}->($repl);
241              
242             }
243              
244 48         277 return $repl;
245             }
246              
247             sub abridge_items_recursive {
248 5     5 1 1240 local %SEEN;
249 5         10 &_abridge_items_recursive;
250             }
251              
252             sub _abridge_items_recursive {
253 5     5   30 return _abridge_recursive([@_]);
254             }
255              
256             1;
257              
258             # ABSTRACT: Simplify data structures for naive serialization.
259              
260             __END__