line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Data::Compare - compare perl data structures |
2
|
|
|
|
|
|
|
# Author: Fabien Tassin |
3
|
|
|
|
|
|
|
# updated by David Cantrell |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
package Data::Compare; |
6
|
|
|
|
|
|
|
|
7
|
15
|
|
|
15
|
|
291971
|
use strict; |
|
15
|
|
|
|
|
142
|
|
|
15
|
|
|
|
|
418
|
|
8
|
15
|
|
|
15
|
|
75
|
use warnings; |
|
15
|
|
|
|
|
29
|
|
|
15
|
|
|
|
|
566
|
|
9
|
|
|
|
|
|
|
|
10
|
15
|
|
|
15
|
|
89
|
use vars qw(@ISA @EXPORT $VERSION $DEBUG %been_there); |
|
15
|
|
|
|
|
31
|
|
|
15
|
|
|
|
|
1364
|
|
11
|
15
|
|
|
15
|
|
135
|
use Exporter; |
|
15
|
|
|
|
|
56
|
|
|
15
|
|
|
|
|
608
|
|
12
|
15
|
|
|
15
|
|
105
|
use Carp; |
|
15
|
|
|
|
|
79
|
|
|
15
|
|
|
|
|
1348
|
|
13
|
15
|
|
|
15
|
|
6513
|
use Clone qw(clone); |
|
15
|
|
|
|
|
36460
|
|
|
15
|
|
|
|
|
941
|
|
14
|
15
|
|
|
15
|
|
116
|
use Scalar::Util qw(tainted); |
|
15
|
|
|
|
|
31
|
|
|
15
|
|
|
|
|
1483
|
|
15
|
15
|
|
|
15
|
|
7782
|
use File::Find::Rule; |
|
15
|
|
|
|
|
127241
|
|
|
15
|
|
|
|
|
126
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
18
|
|
|
|
|
|
|
@EXPORT = qw(Compare); |
19
|
|
|
|
|
|
|
$VERSION = 1.29; |
20
|
|
|
|
|
|
|
$DEBUG = $ENV{PERL_DATA_COMPARE_DEBUG} || 0; |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
my %handler; |
23
|
|
|
|
|
|
|
|
24
|
15
|
|
|
15
|
|
1245
|
use Cwd; |
|
15
|
|
|
|
|
34
|
|
|
15
|
|
|
|
|
28525
|
|
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub import { |
27
|
27
|
|
|
27
|
|
388
|
my $cwd = getcwd(); |
28
|
27
|
50
|
33
|
|
|
845
|
register_plugins() unless(tainted getcwd() || !chdir $cwd); |
29
|
27
|
|
|
|
|
24024
|
__PACKAGE__->export_to_level(1, @EXPORT); |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# finds and registers plugins |
33
|
|
|
|
|
|
|
sub register_plugins { |
34
|
28
|
|
|
28
|
0
|
848
|
foreach my $file ( |
35
|
|
|
|
|
|
|
File::Find::Rule->file()->name('*.pm')->in( |
36
|
88
|
|
|
|
|
352
|
map { "$_/Data/Compare/Plugins" } |
37
|
318
|
|
|
|
|
9735
|
grep { -d "$_/Data/Compare/Plugins" } |
38
|
|
|
|
|
|
|
@INC |
39
|
|
|
|
|
|
|
) |
40
|
|
|
|
|
|
|
) { |
41
|
|
|
|
|
|
|
# all of this just to avoid loading the same plugin twice and |
42
|
|
|
|
|
|
|
# generating a pile of warnings. Grargh! |
43
|
88
|
|
|
|
|
49727
|
$file =~ s!.*(Data/Compare/Plugins/.*)\.pm$!$1!; |
44
|
88
|
|
|
|
|
361
|
$file =~ s!/!::!g; |
45
|
|
|
|
|
|
|
# ignore badly named example from earlier version, oops |
46
|
88
|
50
|
|
|
|
226
|
next if($file eq 'Data::Compare::Plugins::Scalar-Properties'); |
47
|
88
|
|
|
|
|
3758
|
my $requires = eval "require $file"; |
48
|
88
|
100
|
|
|
|
390
|
next if($requires eq '1'); # already loaded this plugin? |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# not an arrayref? bail |
51
|
14
|
50
|
|
|
|
125
|
if(ref($requires) ne 'ARRAY') { |
52
|
0
|
|
|
|
|
0
|
warn("$file isn't a valid Data::Compare plugin (didn't return arrayref)\n"); |
53
|
0
|
|
|
|
|
0
|
return; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
# coerce into arrayref of arrayrefs if necessary |
56
|
14
|
50
|
|
|
|
49
|
if(ref((@{$requires})[0]) ne 'ARRAY') { $requires = [$requires] } |
|
14
|
|
|
|
|
125
|
|
|
0
|
|
|
|
|
0
|
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# register all the handlers |
59
|
14
|
|
|
|
|
30
|
foreach my $require (@{$requires}) { |
|
14
|
|
|
|
|
38
|
|
60
|
28
|
|
|
|
|
47
|
my($handler, $type1, $type2, $cruft) = reverse @{$require}; |
|
28
|
|
|
|
|
78
|
|
61
|
28
|
100
|
|
|
|
79
|
$type2 = $type1 unless(defined($type2)); |
62
|
28
|
|
|
|
|
144
|
($type1, $type2) = sort($type1, $type2); |
63
|
28
|
50
|
33
|
|
|
271
|
if(!defined($type1) || ref($type1) ne '' || !defined($type2) || ref($type2) ne '') { |
|
|
50
|
33
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
64
|
0
|
|
|
|
|
0
|
warn("$file isn't a valid Data::Compare plugin (invalid type)\n"); |
65
|
|
|
|
|
|
|
} elsif(defined($cruft)) { |
66
|
0
|
|
|
|
|
0
|
warn("$file isn't a valid Data::Compare plugin (extra data)\n"); |
67
|
|
|
|
|
|
|
} elsif(ref($handler) ne 'CODE') { |
68
|
0
|
|
|
|
|
0
|
warn("$file isn't a valid Data::Compare plugin (no coderef)\n"); |
69
|
|
|
|
|
|
|
} else { |
70
|
28
|
|
|
|
|
124
|
$handler{$type1}{$type2} = $handler; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub new { |
77
|
3
|
|
|
3
|
0
|
38
|
my $this = shift; |
78
|
3
|
|
33
|
|
|
12
|
my $class = ref($this) || $this; |
79
|
3
|
|
|
|
|
6
|
my $self = {}; |
80
|
3
|
|
|
|
|
5
|
bless $self, $class; |
81
|
3
|
|
|
|
|
8
|
$self->{'x'} = shift; |
82
|
3
|
|
|
|
|
4
|
$self->{'y'} = shift; |
83
|
3
|
|
|
|
|
8
|
return $self; |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
sub Cmp { |
87
|
7
|
|
|
7
|
0
|
39
|
my $self = shift; |
88
|
|
|
|
|
|
|
|
89
|
7
|
50
|
66
|
|
|
21
|
croak "Usage: DataCompareObj->Cmp(x, y)" unless $#_ == 1 || $#_ == -1; |
90
|
7
|
|
100
|
|
|
17
|
my $x = shift || $self->{'x'}; |
91
|
7
|
|
100
|
|
|
13
|
my $y = shift || $self->{'y'}; |
92
|
|
|
|
|
|
|
|
93
|
7
|
|
|
|
|
11
|
return Compare($x, $y); |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub Compare { |
97
|
1073
|
50
|
66
|
1073
|
0
|
7735
|
croak "Usage: Data::Compare::Compare(x, y, [opts])\n" unless $#_ == 1 || $#_ == 2; |
98
|
|
|
|
|
|
|
|
99
|
1073
|
|
|
|
|
1560
|
my $x = shift; |
100
|
1073
|
|
|
|
|
1329
|
my $y = shift; |
101
|
1073
|
|
|
|
|
1395
|
my $opts = {}; |
102
|
1073
|
100
|
|
|
|
2241
|
if(@_) { $opts = clone(shift); } |
|
7
|
|
|
|
|
55
|
|
103
|
|
|
|
|
|
|
|
104
|
1073
|
|
|
|
|
1783
|
_Compare($x, $y, $opts); |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub _Compare { |
108
|
2421
|
|
|
2421
|
|
4100
|
my($x, $y, $opts) = @_; |
109
|
|
|
|
|
|
|
my($xparent, $xpos, $yparent, $ypos) = map { |
110
|
2421
|
100
|
|
|
|
3604
|
$opts->{$_} || '' |
|
9684
|
|
|
|
|
23676
|
|
111
|
|
|
|
|
|
|
} qw(xparent xpos yparent ypos); |
112
|
|
|
|
|
|
|
|
113
|
2421
|
|
|
|
|
3717
|
my $rval = ''; |
114
|
|
|
|
|
|
|
|
115
|
2421
|
100
|
|
|
|
3996
|
if(!exists($opts->{recursion_detector})) { |
116
|
1073
|
|
|
|
|
2131
|
%been_there = (); |
117
|
1073
|
|
|
|
|
1742
|
$opts->{recursion_detector} = 0; |
118
|
|
|
|
|
|
|
} |
119
|
2421
|
|
|
|
|
2970
|
$opts->{recursion_detector}++; |
120
|
|
|
|
|
|
|
|
121
|
2421
|
100
|
|
|
|
4066
|
warn "Yaroo! deep recursion!\n" if($opts->{recursion_detector} == 99); |
122
|
|
|
|
|
|
|
|
123
|
2421
|
100
|
100
|
|
|
13321
|
if( |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
124
|
|
|
|
|
|
|
(ref($x) && exists($been_there{"$x-$xpos-$xparent"}) && $been_there{"$x-$xpos-$xparent"} > 1) || |
125
|
|
|
|
|
|
|
(ref($y) && exists($been_there{"$y-$ypos-$yparent"}) && $been_there{"$y-$ypos-$yparent"} > 1) |
126
|
|
|
|
|
|
|
) { |
127
|
6
|
|
|
|
|
7
|
$opts->{recursion_detector}--; |
128
|
6
|
|
|
|
|
18
|
return 1; # we bail as soon as possible, so if we've *not* bailed and have got here, say we're OK and go to the next sub-structure |
129
|
|
|
|
|
|
|
} else { |
130
|
2415
|
100
|
|
|
|
5517
|
$been_there{"$x-$xpos-$xparent"}++ if(ref($x)); |
131
|
2415
|
100
|
|
|
|
5062
|
$been_there{"$y-$ypos-$yparent"}++ if(ref($y)); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
$opts->{ignore_hash_keys} = { map { |
134
|
4
|
|
|
|
|
23
|
($_, 1) |
135
|
2415
|
100
|
|
|
|
4208
|
} @{$opts->{ignore_hash_keys}} } if(ref($opts->{ignore_hash_keys}) eq 'ARRAY'); |
|
4
|
|
|
|
|
8
|
|
136
|
|
|
|
|
|
|
|
137
|
2415
|
|
|
|
|
3414
|
my $refx = ref $x; |
138
|
2415
|
|
|
|
|
3054
|
my $refy = ref $y; |
139
|
|
|
|
|
|
|
|
140
|
2415
|
50
|
66
|
|
|
13821
|
if(exists($handler{$refx}) && exists($handler{$refx}{$refy})) { |
|
|
50
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
141
|
0
|
|
|
|
|
0
|
$rval = &{$handler{$refx}{$refy}}($x, $y, $opts); |
|
0
|
|
|
|
|
0
|
|
142
|
|
|
|
|
|
|
} elsif(exists($handler{$refy}) && exists($handler{$refy}{$refx})) { |
143
|
0
|
|
|
|
|
0
|
$rval = &{$handler{$refy}{$refx}}($x, $y, $opts); |
|
0
|
|
|
|
|
0
|
|
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
elsif(!$refx && !$refy) { # both are scalars |
147
|
1151
|
100
|
66
|
|
|
2930
|
if(defined $x && defined $y) { # both are defined |
148
|
1145
|
|
|
|
|
1857
|
$rval = $x eq $y; |
149
|
6
|
|
66
|
|
|
34
|
} else { $rval = !(defined $x || defined $y); } |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
elsif ($refx ne $refy) { # not the same type |
152
|
8
|
|
|
|
|
21
|
$rval = 0; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
elsif (Scalar::Util::refaddr($x) == Scalar::Util::refaddr($y)) { # exactly the same reference |
155
|
14
|
|
|
|
|
25
|
$rval = 1; |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
elsif ($refx eq 'SCALAR' || $refx eq 'REF') { |
158
|
22
|
|
|
|
|
25
|
$rval = _Compare(${$x}, ${$y}, $opts); |
|
22
|
|
|
|
|
25
|
|
|
22
|
|
|
|
|
79
|
|
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
elsif ($refx eq 'ARRAY') { |
161
|
1127
|
100
|
|
|
|
1561
|
if ($#{$x} == $#{$y}) { # same length |
|
1127
|
|
|
|
|
1667
|
|
|
1127
|
|
|
|
|
1745
|
|
162
|
1124
|
|
|
|
|
1426
|
my $i = -1; |
163
|
1124
|
|
|
|
|
1387
|
$rval = 1; |
164
|
1124
|
|
|
|
|
1846
|
for (@$x) { |
165
|
1139
|
|
|
|
|
1353
|
$i++; |
166
|
1139
|
100
|
|
|
|
1622
|
$rval = 0 unless _Compare($x->[$i], $y->[$i], { %{$opts}, xparent => $x, xpos => $i, yparent => $y, ypos => $i}); |
|
1139
|
|
|
|
|
6415
|
|
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
else { |
170
|
3
|
|
|
|
|
4
|
$rval = 0; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
elsif ($refx eq 'HASH') { |
174
|
63
|
|
|
|
|
210
|
my @kx = grep { !$opts->{ignore_hash_keys}->{$_} } keys %$x; |
|
171
|
|
|
|
|
485
|
|
175
|
63
|
|
|
|
|
165
|
my @ky = grep { !$opts->{ignore_hash_keys}->{$_} } keys %$y; # heh, KY |
|
175
|
|
|
|
|
329
|
|
176
|
63
|
|
|
|
|
143
|
$rval = 1; |
177
|
63
|
100
|
|
|
|
170
|
$rval = 0 unless scalar @kx == scalar @ky; |
178
|
|
|
|
|
|
|
|
179
|
63
|
|
|
|
|
138
|
for (@kx) { |
180
|
166
|
100
|
|
|
|
290
|
if(!exists($y->{$_})) { |
181
|
6
|
|
|
|
|
12
|
$rval = 0; |
182
|
6
|
|
|
|
|
19
|
last; |
183
|
|
|
|
|
|
|
} |
184
|
160
|
100
|
|
|
|
226
|
$rval = 0 unless _Compare($x->{$_}, $y->{$_}, { %{$opts}, xparent => $x, xpos => $_, yparent => $y, ypos => $_}); |
|
160
|
|
|
|
|
988
|
|
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
elsif($refx eq 'Regexp') { |
188
|
2
|
|
|
|
|
10
|
$rval = _Compare($x.'', $y.'', $opts); |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
elsif ($refx eq 'CODE') { |
191
|
0
|
|
|
|
|
0
|
$rval = 0; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
elsif ($refx eq 'GLOB') { |
194
|
1
|
|
|
|
|
2
|
$rval = 0; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
else { # a package name (object blessed) |
197
|
27
|
|
|
|
|
72
|
my $type = Scalar::Util::reftype($x); |
198
|
27
|
100
|
66
|
|
|
65
|
if ($type eq 'HASH') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
199
|
21
|
|
|
|
|
71
|
my %x = %$x; |
200
|
21
|
|
|
|
|
71
|
my %y = %$y; |
201
|
21
|
|
|
|
|
43
|
$rval = _Compare(\%x, \%y, { %{$opts}, xparent => $xparent, xpos => $xpos, yparent => $yparent, ypos => $ypos}); |
|
21
|
|
|
|
|
281
|
|
202
|
21
|
|
|
|
|
85
|
$been_there{\%x."-$xpos-$xparent"}--; # decrement count for temp structures |
203
|
21
|
|
|
|
|
82
|
$been_there{\%y."-$ypos-$yparent"}--; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
elsif ($type eq 'ARRAY') { |
206
|
2
|
|
|
|
|
4
|
my @x = @$x; |
207
|
2
|
|
|
|
|
5
|
my @y = @$y; |
208
|
2
|
|
|
|
|
3
|
$rval = _Compare(\@x, \@y, { %{$opts}, xparent => $xparent, xpos => $xpos, yparent => $yparent, ypos => $ypos}); |
|
2
|
|
|
|
|
9
|
|
209
|
2
|
|
|
|
|
10
|
$been_there{\@x."-$xpos-$xparent"}--; |
210
|
2
|
|
|
|
|
7
|
$been_there{\@y."-$ypos-$yparent"}--; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
elsif ($type eq 'SCALAR' || $type eq 'REF') { |
213
|
2
|
|
|
|
|
3
|
my $x = ${$x}; |
|
2
|
|
|
|
|
3
|
|
214
|
2
|
|
|
|
|
2
|
my $y = ${$y}; |
|
2
|
|
|
|
|
3
|
|
215
|
2
|
|
|
|
|
4
|
$rval = _Compare($x, $y, $opts); |
216
|
|
|
|
|
|
|
# $been_there{\$x}--; |
217
|
|
|
|
|
|
|
# $been_there{\$y}--; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
elsif ($type eq 'GLOB') { |
220
|
1
|
|
|
|
|
2
|
$rval = 0; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
elsif ($type eq 'CODE') { |
223
|
1
|
|
|
|
|
2
|
$rval = 0; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
else { |
226
|
0
|
|
|
|
|
0
|
croak "Can't handle $type type."; |
227
|
0
|
|
|
|
|
0
|
$rval = 0; |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
} |
231
|
2415
|
|
|
|
|
3355
|
$opts->{recursion_detector}--; |
232
|
2415
|
|
|
|
|
7648
|
return $rval; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub plugins { |
236
|
3
|
100
|
|
3
|
1
|
61
|
return { map { (($_ eq '') ? '[scalar]' : $_, [map { $_ eq '' ? '[scalar]' : $_ } keys %{$handler{$_}}]) } keys %handler }; |
|
4
|
50
|
|
|
|
12
|
|
|
4
|
|
|
|
|
22
|
|
|
4
|
|
|
|
|
12
|
|
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub plugins_printable { |
240
|
0
|
|
|
0
|
1
|
|
my $r = "The following comparisons are available through plugins\n\n"; |
241
|
0
|
|
|
|
|
|
foreach my $key (sort keys %handler) { |
242
|
0
|
|
|
|
|
|
foreach(sort keys %{$handler{$key}}) { |
|
0
|
|
|
|
|
|
|
243
|
0
|
0
|
|
|
|
|
$r .= join(":\t", map { $_ eq '' ? '[scalar]' : $_ } ($key, $_))."\n"; |
|
0
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
} |
246
|
0
|
|
|
|
|
|
return $r; |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
1; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head1 NAME |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
Data::Compare - compare perl data structures |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=head1 SYNOPSIS |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
use Data::Compare; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
my $h1 = { 'foo' => [ 'bar', 'baz' ], 'FOO' => [ 'one', 'two' ] }; |
260
|
|
|
|
|
|
|
my $h2 = { 'foo' => [ 'bar', 'barf' ], 'FOO' => [ 'one', 'two' ] }; |
261
|
|
|
|
|
|
|
my @a1 = ('one', 'two'); |
262
|
|
|
|
|
|
|
my @a2 = ('bar', 'baz'); |
263
|
|
|
|
|
|
|
my %v = ( 'FOO', \@a1, 'foo', \@a2 ); |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# simple procedural interface |
266
|
|
|
|
|
|
|
print 'structures of $h1 and \%v are ', |
267
|
|
|
|
|
|
|
Compare($h1, \%v) ? "" : "not ", "identical.\n"; |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
print 'structures of $h1 and $h2 are ', |
270
|
|
|
|
|
|
|
Compare($h1, $h2, { ignore_hash_keys => [qw(foo)] }) ? '' : 'not ', |
271
|
|
|
|
|
|
|
"close enough to identical.\n"; |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# OO usage |
274
|
|
|
|
|
|
|
my $c = new Data::Compare($h1, \%v); |
275
|
|
|
|
|
|
|
print 'structures of $h1 and \%v are ', |
276
|
|
|
|
|
|
|
$c->Cmp ? "" : "not ", "identical.\n"; |
277
|
|
|
|
|
|
|
# or |
278
|
|
|
|
|
|
|
my $c = new Data::Compare; |
279
|
|
|
|
|
|
|
print 'structures of $h and \%v are ', |
280
|
|
|
|
|
|
|
$c->Cmp($h1, \%v) ? "" : "not ", "identical.\n"; |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=head1 DESCRIPTION |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Compare two perl data structures recursively. Returns 0 if the |
285
|
|
|
|
|
|
|
structures differ, else returns 1. |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
A few data types are treated as special cases: |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=over 4 |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=item Scalar::Properties objects |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
This has been moved into a plugin, although functionality remains the |
294
|
|
|
|
|
|
|
same as with the previous version. Full documentation is in |
295
|
|
|
|
|
|
|
L. |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=item Compiled regular expressions, eg qr/foo/ |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
These are stringified before comparison, so the following will match: |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
$r = qr/abc/i; |
302
|
|
|
|
|
|
|
$s = qr/abc/i; |
303
|
|
|
|
|
|
|
Compare($r, $s); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
and the following won't, despite them matching *exactly* the same text: |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
$r = qr/abc/i; |
308
|
|
|
|
|
|
|
$s = qr/[aA][bB][cC]/; |
309
|
|
|
|
|
|
|
Compare($r, $s); |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Sorry, that's the best we can do. |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=item CODE and GLOB references |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
These are assumed not to match unless the references are identical - ie, |
316
|
|
|
|
|
|
|
both are references to the same thing. |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=back |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
You may also customise how we compare structures by supplying options in |
321
|
|
|
|
|
|
|
a hashref as a third parameter to the C function. This is not |
322
|
|
|
|
|
|
|
yet available through the OO-ish interface. These options will be in |
323
|
|
|
|
|
|
|
force for the *whole* of your comparison, so will apply to structures |
324
|
|
|
|
|
|
|
that are lurking deep down in your data as well as at the top level, so |
325
|
|
|
|
|
|
|
beware! |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=over 4 |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=item ignore_hash_keys |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
an arrayref of strings. When comparing two hashes, any keys mentioned in |
332
|
|
|
|
|
|
|
this list will be ignored. |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
=back |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=head1 CIRCULAR STRUCTURES |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
Comparing a circular structure to itself returns true: |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
$x = \$y; |
341
|
|
|
|
|
|
|
$y = \$x; |
342
|
|
|
|
|
|
|
Compare([$x, $y], [$x, $y]); |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
And on a sort-of-related note, if you try to compare insanely deeply nested |
345
|
|
|
|
|
|
|
structures, the module will spit a warning. For this to affect you, you need to go |
346
|
|
|
|
|
|
|
around a hundred levels deep though, and if you do that you have bigger |
347
|
|
|
|
|
|
|
problems which I can't help you with ;-) |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=head1 PLUGINS |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
The module takes plug-ins so you can provide specialised routines for |
352
|
|
|
|
|
|
|
comparing your own objects and data-types. For details see |
353
|
|
|
|
|
|
|
L. |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
Plugins are *not* available when running in "taint" mode. You may |
356
|
|
|
|
|
|
|
also make it not load plugins by providing an empty list as the |
357
|
|
|
|
|
|
|
argument to import() - ie, by doing this: |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
use Data::Compare (); |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
A couple of functions are provided to examine what goodies have been |
362
|
|
|
|
|
|
|
made available through plugins: |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=over 4 |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=item plugins |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
Returns a structure (a hash ref) describing all the comparisons made |
369
|
|
|
|
|
|
|
available through plugins. |
370
|
|
|
|
|
|
|
This function is *not* exported, so should be called as Data::Compare::plugins(). |
371
|
|
|
|
|
|
|
It takes no parameters. |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=item plugins_printable |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
Returns formatted text |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
=back |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=head1 EXPORTS |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
For historical reasons, the Compare() function is exported. If you |
382
|
|
|
|
|
|
|
don't want this, then pass an empty list to import() as explained |
383
|
|
|
|
|
|
|
under PLUGINS. If you want no export but do want plugins, then pass |
384
|
|
|
|
|
|
|
the empty list, and then call the register_plugins class method: |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
use Data::Compare (); |
387
|
|
|
|
|
|
|
Data::Compare->register_plugins; |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
or you could call it as a function if that floats your boat. |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
=head1 SOURCE CODE REPOSITORY |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
L |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=head1 BUGS |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
Plugin support is not quite finished (see the the Github |
398
|
|
|
|
|
|
|
L |
399
|
|
|
|
|
|
|
for details) but is usable. The missing bits are bells and whistles rather than |
400
|
|
|
|
|
|
|
core functionality. |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Plugins are unavailable if you can't change to the current directory. This |
403
|
|
|
|
|
|
|
might happen if you started your process as a priveleged user and then dropped |
404
|
|
|
|
|
|
|
priveleges. If this affects you, please supply a portable patch with tests. |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
Bug reports should be made on Github or by email. |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
=head1 AUTHOR |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
Fabien Tassin Efta@sofaraway.orgE |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
Portions by David Cantrell Edavid@cantrell.org.ukE |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=head1 COPYRIGHT and LICENCE |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
Copyright (c) 1999-2001 Fabien Tassin. All rights reserved. |
417
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or |
418
|
|
|
|
|
|
|
modify it under the same terms as Perl itself. |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
Some parts copyright 2003 - 2023 David Cantrell. |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
Seeing that Fabien seems to have disappeared, David Cantrell has become |
423
|
|
|
|
|
|
|
a co-maintainer so he can apply needed patches. The licence, of course, |
424
|
|
|
|
|
|
|
remains the same. As the "perl licence" is "Artistic or GPL, your choice", |
425
|
|
|
|
|
|
|
you can find them as the files ARTISTIC.txt and GPL2.txt in the |
426
|
|
|
|
|
|
|
distribution. |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=head1 SEE ALSO |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
L |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
perl(1), perlref(1) |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=cut |