File Coverage

blib/lib/Hash/Merge.pm
Criterion Covered Total %
statement 158 175 90.2
branch 33 48 68.7
condition 11 22 50.0
subroutine 54 56 96.4
pod 7 8 87.5
total 263 309 85.1


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