File Coverage

blib/lib/Data/Skeleton.pm
Criterion Covered Total %
statement 73 73 100.0
branch 34 36 94.4
condition 4 6 66.6
subroutine 8 8 100.0
pod 1 1 100.0
total 120 124 96.7


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