File Coverage

blib/lib/Data/Skeleton.pm
Criterion Covered Total %
statement 73 73 100.0
branch 34 36 94.4
condition 5 6 83.3
subroutine 8 8 100.0
pod 1 1 100.0
total 121 124 97.5


line stmt bran cond sub pod time code
1 3     3   163511 use strict;
  3         5  
  3         105  
2 3     3   25 use warnings;
  3         5  
  3         263  
3             package Data::Skeleton;
4             $Data::Skeleton::VERSION = '0.07';
5 3     3   1750 use Moo;
  3         28114  
  3         16  
6 3     3   6467 use MooX::Types::MooseLike::Base qw/Str HashRef Bool/;
  3         27525  
  3         321  
7 3     3   29 use Scalar::Util qw(blessed);
  3         5  
  3         2511  
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 703667 my ($self, $data) = @_;
91 6 100 66     51 if (ref($data) eq 'HASH') {
    100          
    100          
92 2         11 return $self->_blank_hash($data);
93             } elsif (ref($data) eq 'ARRAY') {
94 1         23 return $self->_blank_array($data);
95 3         5 } elsif (blessed($data) && eval { keys %{$data}; 1; } ) {
  3         16  
  2         9  
96 2         6 return $self->_blank_hash($data);
97             } else {
98 1         11 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   34 my ($self, $hashref) = @_;
108             # Work on a copy
109 14         28 my %hashref = %{$hashref};
  14         67  
110 14         87 $hashref = \%hashref;
111              
112 14         24 foreach my $key (keys %{$hashref}) {
  14         47  
113 29         176 my $value = $hashref->{$key};
114 29         68 my $ref_value = ref($value);
115 29         765 my $references_seen = $self->references_seen;
116             # Skip if we've seen this ref before
117 29 100 100     320 if ($ref_value and $references_seen->{$value}) {
118 1 50       6 warn "Seen referenced value: $value before" if $self->debug_skeleton;
119 1         4 next;
120             }
121             # If we have a reference value then note it to avoid deep recursion
122             # with circular references.
123 28 100       67 if ($ref_value) {
124 16         51 $references_seen->{$value} = 1;
125 16         408 $self->references_seen($references_seen);
126             }
127 28 100       993 if (!$ref_value) {
    100          
    100          
    100          
128             # blank a value that is not a reference
129 12         289 $hashref->{$key} = $self->value_marker;
130             }
131             elsif ($ref_value eq 'SCALAR') {
132 1         33 $hashref->{$key} = $self->value_marker;
133             }
134             elsif ($ref_value eq 'HASH') {
135             # recurse when a value is a HashRef
136 7         28 $hashref->{$key} = $self->_blank_hash($value);
137             }
138              
139             # look inside ArrayRefs for HashRefs
140             elsif ($ref_value eq 'ARRAY') {
141 5         14 $hashref->{$key} = $self->_blank_array($value);
142             }
143             else {
144 3 100       11 if (blessed($value)) {
145             # Hash based objects have keys
146 2 100       5 if (eval { keys %{$value}; 1; }) {
  2         4  
  2         13  
  1         3  
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         4 $blanked_hash_object->{BLESSED_AS} = $ref_value;
151 1         4 $hashref->{$key} = $blanked_hash_object;
152             } else {
153 1         5 $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         117 return $hashref;
162             }
163              
164             sub _blank_array {
165 7     7   21 my ($self, $arrayref) = @_;
166              
167 7         161 my $references_seen = $self->references_seen;
168             my @ref_values =
169 7 100       45 grep { ref($_) eq 'HASH' or ref($_) eq 'ARRAY' } @{$arrayref};
  8         56  
  7         27  
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       41 if (!scalar @ref_values) {
172 3         7 $arrayref = [];
173             }
174             else {
175             $arrayref = [
176             map {
177 5 100       28 if (ref($_) eq 'HASH') {
    100          
178 2         30 $self->_blank_hash($_);
179             }
180             elsif (ref($_) eq 'ARRAY') {
181             # Skip if we've seen this ref before
182 2 100       8 if ($references_seen->{$_}) {
183 1 50       6 warn "Seen referenced value: $_ before" if $self->debug_skeleton;
184 1         5 return $_;
185             }
186 1         4 $references_seen->{$_} = 1;
187 1         25 $self->references_seen($references_seen);
188 1         55 $self->_blank_array($_);
189             }
190             else {
191 1         26 $self->value_marker;
192             }
193 4         9 } @{$arrayref}
  4         9  
194             ];
195             }
196 6         42 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