File Coverage

blib/lib/Hash/Merge.pm
Criterion Covered Total %
statement 160 177 90.4
branch 33 48 68.7
condition 11 22 50.0
subroutine 54 56 96.4
pod 7 8 87.5
total 265 311 85.2


line stmt bran cond sub pod time code
1             package Hash::Merge;
2              
3 6     6   435842 use strict;
  6         60  
  6         206  
4 6     6   31 use warnings;
  6         12  
  6         156  
5              
6 6     6   29 use Carp;
  6         10  
  6         430  
7 6     6   2863 use Clone::Choose 0.008;
  6         21689  
  6         39  
8 6     6   13497 use Scalar::Util qw(blessed weaken);
  6         14  
  6         355  
9              
10 6     6   36 use base 'Exporter';
  6         20  
  6         10180  
11             our $CONTEXT;
12              
13             our $VERSION = '0.302';
14             our @EXPORT_OK = qw( merge _hashify _merge_hashes );
15             our %EXPORT_TAGS = ('custom' => [qw( _hashify _merge_hashes )]);
16              
17             sub _init
18             {
19 10     10   21 my $self = shift;
20              
21 10         18 my $weak = $self;
22 10         69 weaken $weak;
23              
24             defined $self->{behaviors}
25             or $self->{behaviors} = {
26             'LEFT_PRECEDENT' => {
27             'SCALAR' => {
28 10     10   62 'SCALAR' => sub { $_[0] },
29 4     4   14 'ARRAY' => sub { $_[0] },
30 8     8   32 'HASH' => sub { $_[0] },
31             },
32             'ARRAY' => {
33 4     4   10 'SCALAR' => sub { [@{$_[0]}, $_[1]] },
  4         19  
34 4     4   10 'ARRAY' => sub { [@{$_[0]}, @{$_[1]}] },
  4         10  
  4         20  
35 8     8   15 'HASH' => sub { [@{$_[0]}, values %{$_[1]}] },
  8         18  
  8         46  
36             },
37             'HASH' => {
38 8     8   73 'SCALAR' => sub { $_[0] },
39 8     8   38 'ARRAY' => sub { $_[0] },
40 22     22   64 'HASH' => sub { $weak->_merge_hashes($_[0], $_[1]) },
41             },
42             },
43              
44             'RIGHT_PRECEDENT' => {
45             'SCALAR' => {
46 8     8   31 'SCALAR' => sub { $_[1] },
47 4     4   8 'ARRAY' => sub { [$_[0], @{$_[1]}] },
  4         22  
48 8     8   30 'HASH' => sub { $_[1] },
49             },
50             'ARRAY' => {
51 4     4   18 'SCALAR' => sub { $_[1] },
52 4     4   9 'ARRAY' => sub { [@{$_[0]}, @{$_[1]}] },
  4         11  
  4         20  
53 8     8   38 'HASH' => sub { $_[1] },
54             },
55             'HASH' => {
56 8     8   39 'SCALAR' => sub { $_[1] },
57 8     8   19 'ARRAY' => sub { [values %{$_[0]}, @{$_[1]}] },
  8         29  
  8         38  
58 20     20   58 'HASH' => sub { $weak->_merge_hashes($_[0], $_[1]) },
59             },
60             },
61              
62             'STORAGE_PRECEDENT' => {
63             'SCALAR' => {
64 8     8   31 'SCALAR' => sub { $_[0] },
65 4     4   11 'ARRAY' => sub { [$_[0], @{$_[1]}] },
  4         20  
66 8     8   27 'HASH' => sub { $_[1] },
67             },
68             'ARRAY' => {
69 4     4   7 'SCALAR' => sub { [@{$_[0]}, $_[1]] },
  4         23  
70 4     4   8 'ARRAY' => sub { [@{$_[0]}, @{$_[1]}] },
  4         11  
  4         19  
71 8     8   35 'HASH' => sub { $_[1] },
72             },
73             'HASH' => {
74 8     8   25 'SCALAR' => sub { $_[0] },
75 8     8   29 'ARRAY' => sub { $_[0] },
76 20     20   55 'HASH' => sub { $weak->_merge_hashes($_[0], $_[1]) },
77             },
78             },
79              
80             'RETAINMENT_PRECEDENT' => {
81             'SCALAR' => {
82 8     8   33 'SCALAR' => sub { [$_[0], $_[1]] },
83 4     4   11 'ARRAY' => sub { [$_[0], @{$_[1]}] },
  4         22  
84 8     8   27 'HASH' => sub { $weak->_merge_hashes($weak->_hashify($_[0]), $_[1]) },
85             },
86             'ARRAY' => {
87 4     4   10 'SCALAR' => sub { [@{$_[0]}, $_[1]] },
  4         23  
88 4     4   10 'ARRAY' => sub { [@{$_[0]}, @{$_[1]}] },
  4         10  
  4         21  
89 8     8   27 'HASH' => sub { $weak->_merge_hashes($weak->_hashify($_[0]), $_[1]) },
90             },
91             'HASH' => {
92 8     8   31 'SCALAR' => sub { $weak->_merge_hashes($_[0], $weak->_hashify($_[1])) },
93 8     8   23 'ARRAY' => sub { $weak->_merge_hashes($_[0], $weak->_hashify($_[1])) },
94 20     20   55 'HASH' => sub { $weak->_merge_hashes($_[0], $_[1]) },
95             },
96             },
97 10 50       684 };
98              
99 10 50       61 defined $self->{behavior} or $self->{behavior} = 'LEFT_PRECEDENT';
100              
101             croak "Behavior '$self->{behavior}' does not exist"
102 10 50       50 if !exists $self->{behaviors}{$self->{behavior}};
103              
104 10         30 $self->{matrix} = $self->{behaviors}{$self->{behavior}};
105 10         27 $self->{clone} = 1;
106             }
107              
108             sub new
109             {
110 10     10 0 19260 my ($pkg, $beh) = @_;
111 10   33     57 $pkg = ref $pkg || $pkg;
112              
113 10 50       76 my $instance = bless {($beh ? (behavior => $beh) : ())}, $pkg;
114 10         35 $instance->_init;
115              
116 10         29 return $instance;
117             }
118              
119             sub set_behavior
120             {
121 16     16 1 119880 my $self = &_get_obj; # '&' + no args modifies current @_
122 16         36 my $value = shift;
123              
124 16         27 my @behaviors = grep { /^$value$/i } keys %{$self->{'behaviors'}};
  64         452  
  16         58  
125 16 50       57 if (scalar @behaviors == 0)
126             {
127 0         0 carp 'Behavior must be one of : ' . join(', ', keys %{$self->{'behaviors'}});
  0         0  
128 0         0 return;
129             }
130 16 50       52 if (scalar @behaviors > 1)
131             {
132 0         0 croak 'Behavior must be unique in uppercase letters! You specified: ' . join ', ', @behaviors;
133             }
134 16 50       38 if (scalar @behaviors == 1)
135             {
136 16         31 $value = $behaviors[0];
137             }
138              
139 16         43 my $oldvalue = $self->{'behavior'};
140 16         31 $self->{'behavior'} = $value;
141 16         32 $self->{'matrix'} = $self->{'behaviors'}{$value};
142 16         55 return $oldvalue; # Use classic POSIX pattern for get/set: set returns previous value
143             }
144              
145             sub get_behavior
146             {
147 6     6 1 31 my $self = &_get_obj; # '&' + no args modifies current @_
148 6         29 return $self->{'behavior'};
149             }
150              
151             sub add_behavior_spec
152             {
153 8     8 1 42431 my $self = &_get_obj; # '&' + no args modifies current @_
154 8         22 my ($matrix, $name) = @_;
155 8   50     30 $name ||= 'user defined';
156 8 100       35 if (exists $self->{'behaviors'}{$name})
157             {
158 4         855 carp "Behavior '$name' was already defined. Please take another name";
159 4         390 return;
160             }
161              
162 4         18 my @required = qw( SCALAR ARRAY HASH );
163              
164 4         46 foreach my $left (@required)
165             {
166 12         40 foreach my $right (@required)
167             {
168 36 50       80 if (!exists $matrix->{$left}->{$right})
169             {
170 0         0 carp "Behavior does not specify action for '$left' merging with '$right'";
171 0         0 return;
172             }
173             }
174             }
175              
176 4         11 $self->{'behavior'} = $name;
177 4         21 $self->{'behaviors'}{$name} = $self->{'matrix'} = $matrix;
178             }
179              
180 6     6   60 no strict "refs";
  6         13  
  6         405  
181             *specify_behavior = \&add_behavior_spec;
182 6     6   46 use strict;
  6         15  
  6         4445  
183              
184             sub get_behavior_spec
185             {
186 4     4 1 68 my $self = &_get_obj; # '&' + no args modifies current @_
187 4         14 my ($name) = @_;
188 4   50     12 $name ||= 'user defined';
189 4 50       30 exists $self->{'behaviors'}{$name} and return $self->{'behaviors'}{$name};
190             return:
191             }
192              
193             sub set_clone_behavior
194             {
195 0     0 1 0 my $self = &_get_obj; # '&' + no args modifies current @_
196 0         0 my $oldvalue = $self->{'clone'};
197 0 0       0 $self->{'clone'} = shift() ? 1 : 0;
198 0         0 return $oldvalue;
199             }
200              
201             sub get_clone_behavior
202             {
203 0     0 1 0 my $self = &_get_obj; # '&' + no args modifies current @_
204 0         0 return $self->{'clone'};
205             }
206              
207             sub merge
208             {
209 298     298 1 24057 my $self = &_get_obj; # '&' + no args modifies current @_
210              
211 298         591 my ($left, $right) = @_;
212              
213             # For the general use of this module, we want to create duplicates
214             # of all data that is merged. This behavior can be shut off, but
215             # can create havoc if references are used heavily.
216              
217 298         459 my $lefttype = ref($left);
218 298 100 66     1031 $lefttype = "SCALAR" unless defined $lefttype and defined $self->{'matrix'}->{$lefttype};
219              
220 298         477 my $righttype = ref($right);
221 298 100 66     900 $righttype = "SCALAR" unless defined $righttype and defined $self->{'matrix'}->{$righttype};
222              
223 298 50       537 if ($self->{'clone'})
224             {
225 298 100       6810 $left = ref($left) ? clone($left) : $left;
226 298 100       5962 $right = ref($right) ? clone($right) : $right;
227             }
228              
229 298         573 local $CONTEXT = $self;
230 298         805 return $self->{'matrix'}->{$lefttype}{$righttype}->($left, $right);
231             }
232              
233             # This does a straight merge of hashes, delegating the merge-specific
234             # work to 'merge'
235              
236             sub _merge_hashes
237             {
238 114     114   184 my $self = &_get_obj; # '&' + no args modifies current @_
239              
240 114         224 my ($left, $right) = (shift, shift);
241 114 50 33     463 if (ref $left ne 'HASH' || ref $right ne 'HASH')
242             {
243 0         0 carp 'Arguments for _merge_hashes must be hash references';
244 0         0 return;
245             }
246              
247 114         157 my %newhash;
248 114         347 foreach my $key (keys %$left)
249             {
250             $newhash{$key} =
251             exists $right->{$key}
252             ? $self->merge($left->{$key}, $right->{$key})
253 362 100       976 : $left->{$key};
254              
255             }
256              
257 114         320 foreach my $key (grep { !exists $left->{$_} } keys %$right)
  362         706  
258             {
259 88         162 $newhash{$key} = $right->{$key};
260             }
261              
262 114         682 return \%newhash;
263             }
264              
265             # Given a scalar or an array, creates a new hash where for each item in
266             # the passed scalar or array, the key is equal to the value. Returns
267             # this new hash
268              
269             sub _hashify
270             {
271 32     32   56 my $self = &_get_obj; # '&' + no args modifies current @_
272 32         56 my $arg = shift;
273 32 50       73 if (ref $arg eq 'HASH')
274             {
275 0         0 carp 'Arguement for _hashify must not be a HASH ref';
276 0         0 return;
277             }
278              
279 32         46 my %newhash;
280 32 100       61 if (ref $arg eq 'ARRAY')
281             {
282 16         30 foreach my $item (@$arg)
283             {
284 32         51 my $suffix = 2;
285 32         40 my $name = $item;
286 32         67 while (exists $newhash{$name})
287             {
288 0         0 $name = $item . $suffix++;
289             }
290 32         71 $newhash{$name} = $item;
291             }
292             }
293             else
294             {
295 16         39 $newhash{$arg} = $arg;
296             }
297 32         96 return \%newhash;
298             }
299              
300             my $_global;
301              
302             sub _get_obj
303             {
304 478 100   478   1088 if (my $type = ref $_[0])
305             {
306             return shift()
307 466 100 33     1274 if $type eq __PACKAGE__
      66        
308             || (blessed $_[0] && $_[0]->isa(__PACKAGE__));
309             }
310              
311 28 50       73 defined $CONTEXT and return $CONTEXT;
312 28 100       82 defined $_global or $_global = Hash::Merge->new;
313 28         51 return $_global;
314             }
315              
316             1;
317              
318             __END__