/[cvs]/nfo/perl/libs/Hash/Merge.pm
ViewVC logotype

Contents of /nfo/perl/libs/Hash/Merge.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (show annotations)
Thu Feb 20 05:53:44 2003 UTC (21 years, 2 months ago) by joko
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +11 -2 lines
+ sub set_mixin_behavior

1 #!/usr/bin/perl -w
2
3 package Hash::Merge;
4
5
6 # Id: Merge.pm,v 0.07 2002/02/19 00:21:27 mneylon Exp
7 # Revision: 0.07
8 # Author: mneylon
9 # Date: 2002/02/19 00:21:27
10 # Log: Merge.pm,v
11 # Revision 0.07 2002/02/19 00:21:27 mneylon
12 # Fixed problem with ActiveState Perl's Clone.pm implementation.
13 # Fixed typo in POD.
14 # Fixed formatting of code in general.
15 #
16 # Revision 0.06.01.2 2002/02/17 03:18:20 mneylon
17 # Fixed problem with ActiveState Perl's Clone.pm implementation.
18 # Fixed typo in POD.
19 # Fixed formatting of code in general.
20 #
21 # Revision 0.06.01.1 2002/02/17 02:48:54 mneylon
22 # Branched version.
23 #
24 # Revision 0.06 2001/11/10 03:30:34 mneylon
25 # Version 0.06 release (and more CVS fixes)
26 #
27 # Revision 0.05.02.2 2001/11/10 03:22:58 mneylon
28 # Updated documentation
29 #
30 # Revision 0.05.02.1 2001/11/08 00:14:48 mneylon
31 # Fixing CVS problems
32 #
33 # Revision 0.05.01.1 2001/11/06 03:26:56 mneylon
34 # Fixed some undefined variable problems for 5.005.
35 # Added cloning of data and set/get_clone_behavior functions
36 # Added associated testing of data cloning
37 # Fixed some problems with POD
38 #
39 # Revision 0.05 2001/11/02 02:15:54 mneylon
40 # Yet another fix to Test::More requirement (=> 0.33)
41 #
42 # Revision 0.04 2001/10/31 03:59:03 mneylon
43 # Forced Test::More requirement in makefile
44 # Fixed problems with pod documentation
45 #
46 # Revision 0.03 2001/10/28 23:36:12 mneylon
47 # CPAN Release with CVS fixes
48 #
49 # Revision 0.02 2001/10/28 23:05:03 mneylon
50 # CPAN release
51 #
52 # Revision 0.01.1.1 2001/10/23 03:01:34 mneylon
53 # Slight fixes
54 #
55 # Revision 0.01 2001/10/23 03:00:21 mneylon
56 # Initial Release to PerlMonks
57 #
58 #
59 #=============================================================================
60
61 use strict;
62 use Clone qw(clone);
63
64 BEGIN {
65 use Exporter ();
66 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK
67 %EXPORT_TAGS $CLONE_SUPPORT);
68 $VERSION = sprintf( "%d.%02d", q($Revision: 1.1 $) =~ /\s(\d+)\.(\d+)/ );
69 @ISA = qw(Exporter);
70 @EXPORT = qw();
71 @EXPORT_OK = qw( merge _hashify _merge_hashes );
72 %EXPORT_TAGS = ( custom => [ qw( _hashify _merge_hashes )] );
73 $CLONE_SUPPORT = ( $Clone::VERSION > 0.09 );
74
75 }
76
77 my %left_precedent = (
78 SCALAR => {
79 SCALAR => sub { $_[0] },
80 ARRAY => sub { $_[0] },
81 HASH => sub { $_[0] } },
82 ARRAY => {
83 SCALAR => sub { [ @{$_[0]}, $_[1] ] },
84 ARRAY => sub { [ @{$_[0]}, @{$_[1]} ] },
85 HASH => sub { [ @{$_[0]}, values %{$_[1]} ] } },
86 HASH => {
87 SCALAR => sub { $_[0] },
88 ARRAY => sub { $_[0] },
89 HASH => sub { _merge_hashes( $_[0], $_[1] ) } }
90 );
91
92 my %right_precedent = (
93 SCALAR => {
94 SCALAR => sub { $_[1] },
95 ARRAY => sub { [ $_[0], @{$_[1]} ] },
96 HASH => sub { $_[1] } },
97 ARRAY => {
98 SCALAR => sub { $_[1] },
99 ARRAY => sub { [ @{$_[0]}, @{$_[1]} ] },
100 HASH => sub { $_[1] } },
101 HASH => {
102 SCALAR => sub { $_[1] },
103 ARRAY => sub { [ values %{$_[0]}, @{$_[1]} ] },
104 HASH => sub { _merge_hashes( $_[0], $_[1] ) } }
105 );
106
107 my %storage_precedent = (
108 SCALAR => {
109 SCALAR => sub { $_[0] },
110 ARRAY => sub { [ $_[0], @{$_[1]} ] },
111 HASH => sub { $_[1] } },
112 ARRAY => {
113 SCALAR => sub { [ @{$_[0]}, $_[1] ] },
114 ARRAY => sub { [ @{$_[0]}, @{$_[1]} ] },
115 HASH => sub { $_[1] } },
116 HASH => {
117 SCALAR => sub { $_[0] },
118 ARRAY => sub { $_[0] },
119 HASH => sub { _merge_hashes( $_[0], $_[1] ) } }
120 );
121
122 my %retainment_precedent = (
123 SCALAR => {
124 SCALAR => sub { [ $_[0], $_[1] ] },
125 ARRAY => sub { [ $_[0], @{$_[1]} ] },
126 HASH => sub { _merge_hashes( _hashify( $_[0] ), $_[1] ) } },
127 ARRAY => {
128 SCALAR => sub { [ @{$_[0]}, $_[1] ] },
129 ARRAY => sub { [ @{$_[0]}, @{$_[1]} ] },
130 HASH => sub { _merge_hashes( _hashify( $_[0] ), $_[1] ) } },
131 HASH => {
132 SCALAR => sub { _merge_hashes( $_[0], _hashify( $_[1] ) ) },
133 ARRAY => sub { _merge_hashes( $_[0], _hashify( $_[1] ) ) },
134 HASH => sub { _merge_hashes( $_[0], $_[1] ) } }
135 );
136
137 my %behaviors = (
138 LEFT_PRECEDENT => \%left_precedent,
139 RIGHT_PRECEDENT => \%right_precedent,
140 STORAGE_PRECEDENT => \%storage_precedent,
141 RETAINMENT_PRECEDENT => \%retainment_precedent
142 );
143
144 my $merge_behavior = 'LEFT_PRECEDENT';
145 my $merge_matrix = \%{ $behaviors{ $merge_behavior } };
146
147 my $clone_behavior = 1;
148 my $mixin_behavior = 0;
149
150 sub set_behavior {
151 my $value = uc(shift);
152 die "Behavior must be one of : " , join ' ', keys %behaviors
153 unless exists $behaviors{ $value };
154 $merge_behavior = $value;
155 $merge_matrix = \%{ $behaviors{ $merge_behavior } };
156 }
157
158 sub get_behavior {
159 return $merge_behavior;
160 }
161
162 sub specify_behavior {
163 my $matrix = shift;
164 my $name = shift || "user defined";
165 my @required = qw ( SCALAR ARRAY HASH );
166
167 foreach my $left ( @required ) {
168 foreach my $right ( @required ) {
169 die "Behavior does not specify action for $left merging with $right"
170 unless exists $matrix->{ $left }->{ $right };
171 }
172 }
173
174 $merge_behavior = $name;
175 $merge_matrix = $matrix;
176 }
177
178 sub set_clone_behavior {
179 my $temp = shift;
180 $clone_behavior = ( $temp ) ? 1 : 0;
181 }
182
183 sub get_clone_behavior {
184 return $clone_behavior;
185 }
186
187 sub set_mixin_behavior {
188 my $temp = shift;
189 $mixin_behavior = ( $temp ) ? 1 : 0;
190 }
191
192 sub merge {
193 my ( $left, $right ) = ( shift, shift );
194
195 # For the general use of this module, we want to create duplicates
196 # of all data that is merged. This behavior can be shut off, but
197 # can mess havoc if references are used heavily.
198
199 my ( $lefttype, $righttype );
200 if ( !defined( $left ) ) { # Perl 5.005 compatibility
201 $lefttype = 'SCALAR';
202 } elsif ( UNIVERSAL::isa( $left, 'HASH' ) ) {
203 $lefttype = 'HASH';
204 } elsif ( UNIVERSAL::isa( $left, 'ARRAY' ) ) {
205 $lefttype = 'ARRAY';
206 } else {
207 $lefttype = 'SCALAR';
208 }
209
210 if ( !defined( $right ) ) { # Perl 5.005 compatibility
211 $righttype = 'SCALAR';
212 } elsif ( UNIVERSAL::isa( $right, 'HASH' ) ) {
213 $righttype = 'HASH';
214 } elsif ( UNIVERSAL::isa( $right, 'ARRAY' ) ) {
215 $righttype = 'ARRAY';
216 } else {
217 $righttype = 'SCALAR';
218 }
219
220 if ( $clone_behavior ) {
221 $left = _my_clone ( $left, 1 );
222 $right = _my_clone ( $right, 1 );
223 }
224
225 return &{ $merge_matrix->{ $lefttype }->{ $righttype }}
226 ( $left, $right );
227 }
228
229 # This does a straight merge of hashes, delegating the merge-specific
230 # work to 'merge'
231
232 sub _merge_hashes {
233 my ( $left, $right ) = ( shift, shift );
234 die "Arguments for _merge_hashes must be hash references" unless
235 UNIVERSAL::isa( $left, 'HASH' ) && UNIVERSAL::isa( $right, 'HASH' );
236
237 my %newhash;
238 foreach my $leftkey ( keys %$left ) {
239 if ( exists $right->{ $leftkey } ) {
240 ( $mixin_behavior ) ? $left->{ $leftkey } : $newhash{ $leftkey } =
241 merge ( $left->{ $leftkey }, $right->{ $leftkey } )
242 } else {
243 $newhash{ $leftkey } =
244 ( $clone_behavior ) ? _my_clone( $left->{ $leftkey } )
245 : $left->{ $leftkey };
246 }
247 }
248 foreach my $rightkey ( keys %$right ) {
249 if ( !exists $left->{ $rightkey } ) {
250 $newhash{ $rightkey } =
251 ( $clone_behavior ) ? _my_clone( $right->{ $rightkey } )
252 : $right->{ $rightkey };
253 }
254 }
255 return \%newhash;
256 }
257
258 # Given a scalar or an array, creates a new hash where for each item in
259 # the passed scalar or array, the key is equal to the value. Returns
260 # this new hash
261
262 sub _hashify {
263 my $arg = shift;
264 die "Arguement for _hashify must not be a HASH ref" if
265 UNIVERSAL::isa( $arg, 'HASH' );
266
267 my %newhash;
268 if ( UNIVERSAL::isa( $arg, 'ARRAY' ) ) {
269 foreach my $item ( @$arg ) {
270 my $suffix = 2;
271 my $name = $item;
272 while ( exists $newhash{ $name } ) {
273 $name = $item . $suffix++;
274 }
275 $newhash{ $name } = $item;
276 }
277 } else {
278 $newhash{ $arg } = $arg;
279 }
280 return \%newhash;
281 }
282
283 # This adds some checks to the clone process, to deal with problems that
284 # the current distro of ActiveState perl has (specifically, it uses 0.09
285 # of Clone, which does not support the cloning of scalars). This simply
286 # wraps around clone as to prevent a scalar from being cloned via a
287 # Clone 0.09 process. This might mean that CODEREFs and anything else
288 # not a HASH or ARRAY won't be cloned.
289
290 sub _my_clone {
291 my ( $arg, $depth ) = @_;
292 if ( !$CLONE_SUPPORT &&
293 !UNIVERSAL::isa( $arg, 'HASH' ) &&
294 !UNIVERSAL::isa( $arg, 'ARRAY' )) {
295 my $var = $arg; # Forced clone
296 return $var;
297 } else {
298 if ($depth ) {
299 return clone( $arg, $depth );
300 } else {
301 return clone( $arg );
302 }
303 }
304 }
305
306 1;
307 __END__
308
309 =head1 NAME
310
311 Hash::Merge - Merges arbitrarily deep hashes into a single hash
312
313 =head1 SYNOPSIS
314
315 use Hash::Merge qw( merge );
316 my %a = ( foo => 1,
317 bar => [ a, b, e ],
318 querty => { bob => alice } );
319 my %b = ( foo => 2,
320 bar => [ c, d ],
321 querty => { ted => margeret } );
322
323 my %c = %{ merge( \%a, \%b ) };
324
325 Hash::Merge::set_behavior( RIGHT_PRECEDENT );
326
327 # This is the same as above
328
329 Hash::Merge::specify_behavior( {
330 SCALAR => {
331 SCALAR => sub { $_[1] },
332 ARRAY => sub { [ $_[0], @{$_[1]} ] },
333 HASH => sub { $_[1] } },
334 ARRAY => {
335 SCALAR => sub { $_[1] },
336 ARRAY => sub { [ @{$_[0]}, @{$_[1]} ] },
337 HASH => sub { $_[1] } },
338 HASH => {
339 SCALAR => sub { $_[1] },
340 ARRAY => sub { [ values %{$_[0]}, @{$_[1]} ] },
341 HASH => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) } }
342 }, "My Behavior" );
343
344 =head1 DESCRIPTION
345
346 Hash::Merge merges two arbitrarily deep hashes into a single hash. That
347 is, at any level, it will add non-conflicting key-value pairs from one
348 hash to the other, and follows a set of specific rules when there are key
349 value conflicts (as outlined below). The hash is followed recursively,
350 so that deeply nested hashes that are at the same level will be merged
351 when the parent hashes are merged. B<Please note that self-referencing
352 hashes, or recursive references, are not handled well by this method.>
353
354 Values in hashes are considered to be either ARRAY references,
355 HASH references, or otherwise are treated as SCALARs. By default, the
356 data passed to the merge function will be cloned using the Clone module;
357 however, if necessary, this behavior can be changed to use as many of
358 the original values as possible. (See C<set_clone_behavior>).
359
360 Because there are a number of possible ways that one may want to merge
361 values when keys are conflicting, Hash::Merge provides several preset
362 methods for your convenience, as well as a way to define you own.
363 These are (currently):
364
365 =over
366
367 =item Left Precedence
368
369 The values buried in the left hash will never
370 be lost; any values that can be added from the right hash will be
371 attempted.
372
373 =item Right Precedence
374
375 Same as Left Precedence, but with the right
376 hash values never being lost
377
378 =item Storage Precedence
379
380 If conflicting keys have two different
381 storage mediums, the 'bigger' medium will win; arrays are preferred over
382 scalars, hashes over either. The other medium will try to be fitted in
383 the other, but if this isn't possible, the data is dropped.
384
385 =item Retainment Precedence
386
387 No data will be lost; scalars will be joined
388 with arrays, and scalars and arrays will be 'hashified' to fit them into
389 a hash.
390
391 =back
392
393 Specific descriptions of how these work are detailed below.
394
395 =over
396
397 =item merge ( <hashref>, <hashref> )
398
399 Merges two hashes given the rules specified. Returns a reference to
400 the new hash.
401
402 =item _hashify( <scalar>|<arrayref> ) -- INTERNAL FUNCTION
403
404 Returns a reference to a hash created from the scalar or array reference,
405 where, for the scalar value, or each item in the array, there is a key
406 and it's value equal to that specific value. Example, if you pass scalar
407 '3', the hash will be { 3 => 3 }.
408
409 =item _merge_hashes( <hashref>, <hashref> ) -- INTERNAL FUNCTION
410
411 Actually does the key-by-key evaluation of two hashes and returns
412 the new merged hash. Note that this recursively calls C<merge>.
413
414 =item set_clone_behavior( <scalar> )
415
416 Sets how the data cloning is handled by Hash::Merge. If this is true,
417 then data will be cloned; if false, then original data will be used
418 whenever possible. By default, cloning is on (set to true).
419
420 =item get_clone_behavior( )
421
422 Returns the current behavior for data cloning.
423
424 =item set_behavior( <scalar> )
425
426 Specify which built-in behavior for merging that is desired. The scalar
427 must be one of those given below.
428
429 =item get_behavior( )
430
431 Returns the behavior that is currently in use by Hash::Merge.
432
433 =item specify_behavior( <hashref>, [<name>] )
434
435 Specify a custom merge behavior for Hash::Merge. This must be a hashref
436 defined with (at least) 3 keys, SCALAR, ARRAY, and HASH; each of those
437 keys must have another hashref with (at least) the same 3 keys defined.
438 Furthermore, the values in those hashes must be coderefs. These will be
439 called with two arguments, the left and right values for the merge.
440 Your coderef should return either a scalar or an array or hash reference
441 as per your planned behavior. If necessary, use the functions
442 _hashify and _merge_hashes as helper functions for these. For example,
443 if you want to add the left SCALAR to the right ARRAY, you can have your
444 behavior specification include:
445
446 %spec = ( ...SCALAR => { ARRAY => sub { [ $_[0], @$_[1] ] }, ... } } );
447
448 Note that you can import _hashify and _merge_hashes into your program's
449 namespace with the 'custom' tag.
450
451 =back
452
453 =head1 BUILT-IN BEHAVIORS
454
455 Here is the specifics on how the current internal behaviors are called,
456 and what each does. Assume that the left value is given as $a, and
457 the right as $b (these are either scalars or appropriate references)
458
459 LEFT TYPE RIGHT TYPE LEFT_PRECEDENT RIGHT_PRECEDENT
460 SCALAR SCALAR $a $b
461 SCALAR ARRAY $a ( $a, @$b )
462 SCALAR HASH $a %$b
463 ARRAY SCALAR ( @$a, $b ) $b
464 ARRAY ARRAY ( @$a, @$b ) ( @$a, @$b )
465 ARRAY HASH ( @$a, values %$b ) %$b
466 HASH SCALAR %$a $b
467 HASH ARRAY %$a ( values %$a, @$b )
468 HASH HASH merge( %$a, %$b ) merge( %$a, %$b )
469
470 LEFT TYPE RIGHT TYPE STORAGE_PRECEDENT RETAINMENT_PRECEDENT
471 SCALAR SCALAR $a ( $a ,$b )
472 SCALAR ARRAY ( $a, @$b ) ( $a, @$b )
473 SCALAR HASH %$b merge( hashify( $a ), %$b )
474 ARRAY SCALAR ( @$a, $b ) ( @$a, $b )
475 ARRAY ARRAY ( @$a, @$b ) ( @$a, @$b )
476 ARRAY HASH %$b merge( hashify( @$a ), %$b )
477 HASH SCALAR %$a merge( %$a, hashify( $b ) )
478 HASH ARRAY %$a merge( %$a, hashify( @$b ) )
479 HASH HASH merge( %$a, %$b ) merge( %$a, %$b )
480
481
482 (*) note that merge calls _merge_hashes, hashify calls _hashify.
483
484 =head1 CAVEATS
485
486 This will not handle self-referencing/recursion within hashes well.
487 Plans for a future version include incorporate deep recursion protection.
488
489 As of Feb 16, 2002, ActiveState Perl's PPM of Clone.pm is only at
490 0.09. This version does not support the cloning of scalars if passed
491 to the function. This is fixed by 0.10 (and currently, Clone.pm is at
492 0.13). So while most other users can upgrade their Clone.pm
493 appropriately (and I could put this as a requirement into the
494 Makefile.PL), those using ActiveState would lose out on the ability to
495 use this module. (Clone.pm is not pure perl, so it's not simply a
496 matter of moving the newer file into place). Thus, for the time
497 being, a check is done at the start of loading of this module to see
498 if a newer version of clone is around. Then, all cloning calls have
499 been wrapped in the internal _my_clone function to block any scalar
500 clones if Clone.pm is too old. However, this also prevents the
501 cloning of anything that isn't a hash or array under the same
502 conditions. Once ActiveState updates their Clone, I'll remove this
503 wrapper.
504
505 =head1 AUTHOR
506
507 Michael K. Neylon E<lt>mneylon-pm@masemware.comE<gt>
508
509 =head1 COPYRIGHT
510
511 Copyright (c) 2001,2002 Michael K. Neylon. All rights reserved.
512
513 This library is free software. You can redistribute it and/or modify it
514 under the same terms as Perl itself.
515
516 =head1 HISTORY
517
518 $Log: Merge.pm,v $
519 Revision 1.1 2003/02/20 05:18:04 joko
520 + initial commit, to-be-enhanced
521
522 Revision 0.07 2002/02/19 00:21:27 mneylon
523 Fixed problem with ActiveState Perl's Clone.pm implementation.
524 Fixed typo in POD.
525 Fixed formatting of code in general.
526
527 Revision 0.06.01.2 2002/02/17 03:18:20 mneylon
528 Fixed problem with ActiveState Perl's Clone.pm implementation.
529 Fixed typo in POD.
530 Fixed formatting of code in general.
531
532 Revision 0.06.01.1 2002/02/17 02:48:54 mneylon
533 Branched version.
534
535 Revision 0.06 2001/11/10 03:30:34 mneylon
536 Version 0.06 release (and more CVS fixes)
537
538 Revision 0.05.02.2 2001/11/10 03:22:58 mneylon
539 Updated documentation
540
541 Revision 0.05.02.1 2001/11/08 00:14:48 mneylon
542 Fixing CVS problems
543
544 Revision 0.05.01.1 2001/11/06 03:26:56 mneylon
545 Fixed some undefined variable problems for 5.005.
546 Added cloning of data and set/get_clone_behavior functions
547 Added associated testing of data cloning
548 Fixed some problems with POD
549
550 Revision 0.05 2001/11/02 02:15:54 mneylon
551 Yet another fix to Test::More requirement (=> 0.33)
552
553 Revision 0.04 2001/10/31 03:59:03 mneylon
554 Forced Test::More requirement in makefile
555 Fixed problems with pod documentation
556
557 Revision 0.03 2001/10/28 23:36:12 mneylon
558 CPAN Release with CVS fixes
559
560 Revision 0.02 2001/10/28 23:05:03 mneylon
561 CPAN release
562
563 =cut

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed