File Coverage

blib/lib/Data/Skeleton.pm
Criterion Covered Total %
statement 71 76 93.4
branch 30 36 83.3
condition 3 6 50.0
subroutine 9 9 100.0
pod 1 1 100.0
total 114 128 89.0


line stmt bran cond sub pod time code
1 2     2   2621 use strictures 1;
  2         1476  
  2         58  
2 2     2   2030 use diagnostics;
  2         461045  
  2         23  
3             package Data::Skeleton;
4             {
5             $Data::Skeleton::VERSION = '0.03';
6             }
7 2     2   3586 use Moo;
  2         39409  
  2         14  
8 2     2   6015 use MooX::Types::MooseLike::Base qw/Str HashRef Bool/;
  2         14710  
  2         266  
9 2     2   23 use Scalar::Util qw(blessed);
  2         4  
  2         91  
10 2     2   1986 use Data::Dumper::Concise;
  2         24633  
  2         1534  
11              
12             =head1 NAME
13              
14             Data::Skeleton - Show the keys of a deep data structure
15              
16             =head1 SYNOPSIS
17              
18             use Data::Skeleton;
19             my $ds = Data::Skeleton->new;
20             my $deep_data_structure = {
21             id => 'hablando',
22             last_modified => 1,
23             sections => [
24             {
25             content => 'h1. Ice Cream',
26             class => 'textile'
27             },
28             {
29             content => '# Chocolate',
30             class => 'markdown'
31             },
32             ],
33             };
34             use Data::Dumper::Concise;
35             print Dumper $ds->deflesh($deep_data_structure);
36              
37             # results in:
38              
39             {
40             id => "",
41             last_modified => "",
42             sections => [
43             {
44             class => "",
45             content => ""
46             },
47             {
48             class => "",
49             content => ""
50             }
51             ]
52             }
53              
54             =head1 DESCRIPTION
55              
56             Sometimes you just want to see the "schema" of a data structure.
57             This modules shows only the keys with blanks for the values.
58              
59             =cut
60              
61             has 'value_marker' => (
62             is => 'ro',
63             isa => Str,
64             lazy => 1,
65             default => sub { '' },
66             );
67             has 'references_seen' => (
68             is => 'rw',
69             isa => HashRef,
70             );
71              
72             =head2 debug_skeleton
73              
74             Turn on/off debugging
75              
76             =cut
77              
78             has 'debug_skeleton' => (
79             is => 'ro',
80             isa => Bool,
81             );
82              
83             =head1 METHODS
84              
85             =head2 deflesh
86              
87             Signature: (HashRef|ArrayRef)
88             Returns: The data structure with values blanked
89              
90             =cut
91              
92             sub deflesh {
93 3     3 1 9543 my ($self, $data) = @_;
94 3 100 0     16 if (ref($data) eq 'HASH') {
    50          
    0          
95 2         12 return $self->_blank_hash($data);
96             } elsif (ref($data) eq 'ARRAY') {
97 1         7 return $self->_blank_array($data);
98 0         0 } elsif (blessed($data) && eval { keys %{$data}; 1; } ) {
  0         0  
  0         0  
99 0         0 return $self->_blank_hash($data);
100             } else {
101 0         0 die "You need to pass the deflesh method either a hash or an array reference";
102             }
103             }
104              
105             sub _blank_hash {
106 10     10   20 my ($self, $hashref) = @_;
107             # Work on a copy
108 10         10 my %hashref = %{$hashref};
  10         48  
109 10         20 $hashref = \%hashref;
110              
111 10         14 foreach my $key (keys %{$hashref}) {
  10         30  
112 21         96 my $value = $hashref->{$key};
113 21         34 my $ref_value = ref($value);
114 21         379 my $references_seen = $self->references_seen;
115             # Skip if we've seen this ref before
116 21 100 100     1175 if ($ref_value and $references_seen->{$value}) {
117 1 50       8 warn "Seen referenced value: $value before" if $self->debug_skeleton;
118 1         4 next;
119             }
120             # If we have a reference value then note it to avoid deep recursion
121             # with circular references.
122 20 100       43 if ($ref_value) {
123 11         86 $references_seen->{$value} = 1;
124 11         255 $self->references_seen($references_seen);
125             }
126 20 100       498 if (!$ref_value) {
    100          
    100          
    100          
127             # blank a value that is not a reference
128 9         149 $hashref->{$key} = $self->value_marker;
129             }
130             elsif ($ref_value eq 'SCALAR') {
131 1         23 $hashref->{$key} = $self->value_marker;
132             }
133             elsif ($ref_value eq 'HASH') {
134             # recurse when a value is a HashRef
135 5         24 $hashref->{$key} = $self->_blank_hash($value);
136             }
137              
138             # look inside ArrayRefs for HashRefs
139             elsif ($ref_value eq 'ARRAY') {
140 3         12 $hashref->{$key} = $self->_blank_array($value);
141             }
142             else {
143 2 50       9 if (blessed($value)) {
144             # Hash based objects have keys
145 2 100       4 if (eval { keys %{$value}; 1; }) {
  2         2  
  2         16  
  1         5  
146 1         9 my $blanked_hash_object = $self->_blank_hash($value); #[keys %{$value}];
147             # Note that we have an object
148             # WARNING: we are altering the data structure by adding a key
149 1         3 $blanked_hash_object->{BLESSED_AS} = $ref_value;
150 1         3 $hashref->{$key} = $blanked_hash_object;
151             } else {
152 1         38 $hashref->{$key} = $ref_value . ' object';
153             }
154             }
155             else {
156             # To leave value or to nuke it in this case? Leave for now.
157             }
158             }
159             }
160 10         75 return $hashref;
161             }
162              
163             sub _blank_array {
164 5     5   9 my ($self, $arrayref) = @_;
165              
166 5         96 my $references_seen = $self->references_seen;
167 5 100       43 my @ref_values =
168 5         27 grep { ref($_) eq 'HASH' or ref($_) eq 'ARRAY' } @{$arrayref};
  5         14  
169             # if no array values are a reference to either a Hash or an Array then we nuke the entire array
170 5 100       35 if (!scalar @ref_values) {
171 1         122 $arrayref = $self->value_marker;
172             }
173             else {
174             $arrayref = [
175             map {
176 5 100       22 if (ref($_) eq 'HASH') {
  4 100       27  
177 2         6 $self->_blank_hash($_);
178             }
179             elsif (ref($_) eq 'ARRAY') {
180             # Skip if we've seen this ref before
181 2 100       7 if ($references_seen->{$_}) {
182 1 50       4 warn "Seen referenced value: $_ before" if $self->debug_skeleton;
183 1         5 return $_;
184             }
185 1         3 $references_seen->{$_} = 1;
186 1         21 $self->references_seen($references_seen);
187 1         40 $self->_blank_array($_);
188             }
189             else {
190 1         18 $self->value_marker;
191             }
192 4         5 } @{$arrayref}
193             ];
194             }
195 4         22 return $arrayref;
196             }
197              
198             1;
199              
200             =head1 SEE ALSO
201              
202             Data::Dump::Partial is way more feature rich than this module.
203             The only reason I didn't use it is that the output is all on one line.
204             To get something similar to deflesh with Data::Dump::Partial do:
205              
206             say Dumper dump_partial($data, {max_total_len => $big_enough_number, max_len => 0});
207              
208             The important part being max_len = 0
209              
210             This module was inspired when I wanted to see the "schema" of a MongoDB document.
211             If you want to enforce a schema (and have a place to recall its nature)
212             then you might consider L
213              
214             =head1 AUTHORS
215              
216             Mateu Hunter C
217              
218             =head1 COPYRIGHT
219              
220             Copyright 2011-2012, Mateu Hunter
221              
222             =head1 LICENSE
223              
224             You may distribute this code under the same terms as Perl itself.
225              
226             =cut