| 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
|
|
272278
|
use strict; |
|
|
15
|
|
|
|
|
121
|
|
|
|
15
|
|
|
|
|
467
|
|
|
8
|
15
|
|
|
15
|
|
72
|
use warnings; |
|
|
15
|
|
|
|
|
24
|
|
|
|
15
|
|
|
|
|
603
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
15
|
|
|
15
|
|
85
|
use vars qw(@ISA @EXPORT $VERSION $DEBUG %been_there); |
|
|
15
|
|
|
|
|
24
|
|
|
|
15
|
|
|
|
|
1245
|
|
|
11
|
15
|
|
|
15
|
|
152
|
use Exporter; |
|
|
15
|
|
|
|
|
43
|
|
|
|
15
|
|
|
|
|
537
|
|
|
12
|
15
|
|
|
15
|
|
143
|
use Carp; |
|
|
15
|
|
|
|
|
36
|
|
|
|
15
|
|
|
|
|
1194
|
|
|
13
|
15
|
|
|
15
|
|
6152
|
use Clone qw(clone); |
|
|
15
|
|
|
|
|
33936
|
|
|
|
15
|
|
|
|
|
902
|
|
|
14
|
15
|
|
|
15
|
|
101
|
use Scalar::Util qw(tainted); |
|
|
15
|
|
|
|
|
30
|
|
|
|
15
|
|
|
|
|
1439
|
|
|
15
|
15
|
|
|
15
|
|
7211
|
use File::Find::Rule; |
|
|
15
|
|
|
|
|
117095
|
|
|
|
15
|
|
|
|
|
105
|
|
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
|
18
|
|
|
|
|
|
|
@EXPORT = qw(Compare); |
|
19
|
|
|
|
|
|
|
$VERSION = 1.28; |
|
20
|
|
|
|
|
|
|
$DEBUG = $ENV{PERL_DATA_COMPARE_DEBUG} || 0; |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
my %handler; |
|
23
|
|
|
|
|
|
|
|
|
24
|
15
|
|
|
15
|
|
1285
|
use Cwd; |
|
|
15
|
|
|
|
|
37
|
|
|
|
15
|
|
|
|
|
26184
|
|
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub import { |
|
27
|
27
|
|
|
27
|
|
353
|
my $cwd = getcwd(); |
|
28
|
27
|
50
|
33
|
|
|
763
|
register_plugins() unless(tainted getcwd() || !chdir $cwd); |
|
29
|
27
|
|
|
|
|
22853
|
__PACKAGE__->export_to_level(1, @EXPORT); |
|
30
|
|
|
|
|
|
|
} |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# finds and registers plugins |
|
33
|
|
|
|
|
|
|
sub register_plugins { |
|
34
|
28
|
|
|
28
|
0
|
752
|
foreach my $file ( |
|
35
|
|
|
|
|
|
|
File::Find::Rule->file()->name('*.pm')->in( |
|
36
|
88
|
|
|
|
|
336
|
map { "$_/Data/Compare/Plugins" } |
|
37
|
318
|
|
|
|
|
7443
|
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
|
|
|
|
|
43629
|
$file =~ s!.*(Data/Compare/Plugins/.*)\.pm$!$1!; |
|
44
|
88
|
|
|
|
|
312
|
$file =~ s!/!::!g; |
|
45
|
|
|
|
|
|
|
# ignore badly named example from earlier version, oops |
|
46
|
88
|
50
|
|
|
|
212
|
next if($file eq 'Data::Compare::Plugins::Scalar-Properties'); |
|
47
|
88
|
|
|
|
|
3860
|
my $requires = eval "require $file"; |
|
48
|
88
|
100
|
|
|
|
380
|
next if($requires eq '1'); # already loaded this plugin? |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# not an arrayref? bail |
|
51
|
14
|
50
|
|
|
|
117
|
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
|
|
|
|
84
|
if(ref((@{$requires})[0]) ne 'ARRAY') { $requires = [$requires] } |
|
|
14
|
|
|
|
|
82
|
|
|
|
0
|
|
|
|
|
0
|
|
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# register all the handlers |
|
59
|
14
|
|
|
|
|
31
|
foreach my $require (@{$requires}) { |
|
|
14
|
|
|
|
|
33
|
|
|
60
|
28
|
|
|
|
|
40
|
my($handler, $type1, $type2, $cruft) = reverse @{$require}; |
|
|
28
|
|
|
|
|
68
|
|
|
61
|
28
|
100
|
|
|
|
70
|
$type2 = $type1 unless(defined($type2)); |
|
62
|
28
|
|
|
|
|
86
|
($type1, $type2) = sort($type1, $type2); |
|
63
|
28
|
50
|
33
|
|
|
261
|
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
|
|
|
|
|
104
|
$handler{$type1}{$type2} = $handler; |
|
71
|
|
|
|
|
|
|
} |
|
72
|
|
|
|
|
|
|
} |
|
73
|
|
|
|
|
|
|
} |
|
74
|
|
|
|
|
|
|
} |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
sub new { |
|
77
|
3
|
|
|
3
|
0
|
33
|
my $this = shift; |
|
78
|
3
|
|
33
|
|
|
10
|
my $class = ref($this) || $this; |
|
79
|
3
|
|
|
|
|
4
|
my $self = {}; |
|
80
|
3
|
|
|
|
|
3
|
bless $self, $class; |
|
81
|
3
|
|
|
|
|
9
|
$self->{'x'} = shift; |
|
82
|
3
|
|
|
|
|
3
|
$self->{'y'} = shift; |
|
83
|
3
|
|
|
|
|
6
|
return $self; |
|
84
|
|
|
|
|
|
|
} |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
sub Cmp { |
|
87
|
7
|
|
|
7
|
0
|
30
|
my $self = shift; |
|
88
|
|
|
|
|
|
|
|
|
89
|
7
|
50
|
66
|
|
|
18
|
croak "Usage: DataCompareObj->Cmp(x, y)" unless $#_ == 1 || $#_ == -1; |
|
90
|
7
|
|
100
|
|
|
12
|
my $x = shift || $self->{'x'}; |
|
91
|
7
|
|
100
|
|
|
12
|
my $y = shift || $self->{'y'}; |
|
92
|
|
|
|
|
|
|
|
|
93
|
7
|
|
|
|
|
12
|
return Compare($x, $y); |
|
94
|
|
|
|
|
|
|
} |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub Compare { |
|
97
|
1073
|
50
|
66
|
1073
|
0
|
7717
|
croak "Usage: Data::Compare::Compare(x, y, [opts])\n" unless $#_ == 1 || $#_ == 2; |
|
98
|
|
|
|
|
|
|
|
|
99
|
1073
|
|
|
|
|
1516
|
my $x = shift; |
|
100
|
1073
|
|
|
|
|
1268
|
my $y = shift; |
|
101
|
1073
|
|
|
|
|
1372
|
my $opts = {}; |
|
102
|
1073
|
100
|
|
|
|
1898
|
if(@_) { $opts = clone(shift); } |
|
|
7
|
|
|
|
|
43
|
|
|
103
|
|
|
|
|
|
|
|
|
104
|
1073
|
|
|
|
|
1758
|
_Compare($x, $y, $opts); |
|
105
|
|
|
|
|
|
|
} |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub _Compare { |
|
108
|
2423
|
|
|
2423
|
|
3970
|
my($x, $y, $opts) = @_; |
|
109
|
|
|
|
|
|
|
my($xparent, $xpos, $yparent, $ypos) = map { |
|
110
|
2423
|
100
|
|
|
|
3529
|
$opts->{$_} || '' |
|
|
9692
|
|
|
|
|
23849
|
|
|
111
|
|
|
|
|
|
|
} qw(xparent xpos yparent ypos); |
|
112
|
|
|
|
|
|
|
|
|
113
|
2423
|
|
|
|
|
4071
|
my $rval = ''; |
|
114
|
|
|
|
|
|
|
|
|
115
|
2423
|
100
|
|
|
|
4208
|
if(!exists($opts->{recursion_detector})) { |
|
116
|
1073
|
|
|
|
|
2034
|
%been_there = (); |
|
117
|
1073
|
|
|
|
|
1713
|
$opts->{recursion_detector} = 0; |
|
118
|
|
|
|
|
|
|
} |
|
119
|
2423
|
|
|
|
|
3089
|
$opts->{recursion_detector}++; |
|
120
|
|
|
|
|
|
|
|
|
121
|
2423
|
100
|
|
|
|
4204
|
warn "Yaroo! deep recursion!\n" if($opts->{recursion_detector} == 99); |
|
122
|
|
|
|
|
|
|
|
|
123
|
2423
|
100
|
100
|
|
|
13007
|
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
|
|
|
|
|
8
|
$opts->{recursion_detector}--; |
|
128
|
6
|
|
|
|
|
20
|
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
|
2417
|
100
|
|
|
|
5944
|
$been_there{"$x-$xpos-$xparent"}++ if(ref($x)); |
|
131
|
2417
|
100
|
|
|
|
5183
|
$been_there{"$y-$ypos-$yparent"}++ if(ref($y)); |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
$opts->{ignore_hash_keys} = { map { |
|
134
|
4
|
|
|
|
|
18
|
($_, 1) |
|
135
|
2417
|
100
|
|
|
|
4377
|
} @{$opts->{ignore_hash_keys}} } if(ref($opts->{ignore_hash_keys}) eq 'ARRAY'); |
|
|
4
|
|
|
|
|
7
|
|
|
136
|
|
|
|
|
|
|
|
|
137
|
2417
|
|
|
|
|
3333
|
my $refx = ref $x; |
|
138
|
2417
|
|
|
|
|
3017
|
my $refy = ref $y; |
|
139
|
|
|
|
|
|
|
|
|
140
|
2417
|
50
|
66
|
|
|
13482
|
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
|
1153
|
100
|
66
|
|
|
2780
|
if(defined $x && defined $y) { # both are defined |
|
148
|
1147
|
|
|
|
|
1815
|
$rval = $x eq $y; |
|
149
|
6
|
|
66
|
|
|
25
|
} else { $rval = !(defined $x || defined $y); } |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
elsif ($refx ne $refy) { # not the same type |
|
152
|
8
|
|
|
|
|
22
|
$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
|
|
|
|
|
27
|
$rval = _Compare(${$x}, ${$y}, $opts); |
|
|
22
|
|
|
|
|
28
|
|
|
|
22
|
|
|
|
|
61
|
|
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
elsif ($refx eq 'ARRAY') { |
|
161
|
1127
|
100
|
|
|
|
1565
|
if ($#{$x} == $#{$y}) { # same length |
|
|
1127
|
|
|
|
|
1518
|
|
|
|
1127
|
|
|
|
|
1708
|
|
|
162
|
1124
|
|
|
|
|
1392
|
my $i = -1; |
|
163
|
1124
|
|
|
|
|
1350
|
$rval = 1; |
|
164
|
1124
|
|
|
|
|
1678
|
for (@$x) { |
|
165
|
1139
|
|
|
|
|
1429
|
$i++; |
|
166
|
1139
|
100
|
|
|
|
1629
|
$rval = 0 unless _Compare($x->[$i], $y->[$i], { %{$opts}, xparent => $x, xpos => $i, yparent => $y, ypos => $i}); |
|
|
1139
|
|
|
|
|
6074
|
|
|
167
|
|
|
|
|
|
|
} |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
else { |
|
170
|
3
|
|
|
|
|
8
|
$rval = 0; |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
elsif ($refx eq 'HASH') { |
|
174
|
63
|
|
|
|
|
241
|
my @kx = grep { !$opts->{ignore_hash_keys}->{$_} } keys %$x; |
|
|
171
|
|
|
|
|
382
|
|
|
175
|
63
|
|
|
|
|
156
|
my @ky = grep { !$opts->{ignore_hash_keys}->{$_} } keys %$y; # heh, KY |
|
|
175
|
|
|
|
|
343
|
|
|
176
|
63
|
|
|
|
|
106
|
$rval = 1; |
|
177
|
63
|
100
|
|
|
|
149
|
$rval = 0 unless scalar @kx == scalar @ky; |
|
178
|
|
|
|
|
|
|
|
|
179
|
63
|
|
|
|
|
120
|
for (@kx) { |
|
180
|
168
|
100
|
|
|
|
312
|
if(!exists($y->{$_})) { |
|
181
|
6
|
|
|
|
|
9
|
$rval = 0; |
|
182
|
6
|
|
|
|
|
14
|
last; |
|
183
|
|
|
|
|
|
|
} |
|
184
|
162
|
100
|
|
|
|
228
|
$rval = 0 unless _Compare($x->{$_}, $y->{$_}, { %{$opts}, xparent => $x, xpos => $_, yparent => $y, ypos => $_}); |
|
|
162
|
|
|
|
|
931
|
|
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
elsif($refx eq 'Regexp') { |
|
188
|
2
|
|
|
|
|
8
|
$rval = _Compare($x.'', $y.'', $opts); |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
elsif ($refx eq 'CODE') { |
|
191
|
0
|
|
|
|
|
0
|
$rval = 0; |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
elsif ($refx eq 'GLOB') { |
|
194
|
1
|
|
|
|
|
12
|
$rval = 0; |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
else { # a package name (object blessed) |
|
197
|
27
|
|
|
|
|
68
|
my $type = Scalar::Util::reftype($x); |
|
198
|
27
|
100
|
66
|
|
|
70
|
if ($type eq 'HASH') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
199
|
21
|
|
|
|
|
63
|
my %x = %$x; |
|
200
|
21
|
|
|
|
|
77
|
my %y = %$y; |
|
201
|
21
|
|
|
|
|
40
|
$rval = _Compare(\%x, \%y, { %{$opts}, xparent => $xparent, xpos => $xpos, yparent => $yparent, ypos => $ypos}); |
|
|
21
|
|
|
|
|
217
|
|
|
202
|
21
|
|
|
|
|
78
|
$been_there{\%x."-$xpos-$xparent"}--; # decrement count for temp structures |
|
203
|
21
|
|
|
|
|
65
|
$been_there{\%y."-$ypos-$yparent"}--; |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
elsif ($type eq 'ARRAY') { |
|
206
|
2
|
|
|
|
|
5
|
my @x = @$x; |
|
207
|
2
|
|
|
|
|
4
|
my @y = @$y; |
|
208
|
2
|
|
|
|
|
4
|
$rval = _Compare(\@x, \@y, { %{$opts}, xparent => $xparent, xpos => $xpos, yparent => $yparent, ypos => $ypos}); |
|
|
2
|
|
|
|
|
8
|
|
|
209
|
2
|
|
|
|
|
9
|
$been_there{\@x."-$xpos-$xparent"}--; |
|
210
|
2
|
|
|
|
|
8
|
$been_there{\@y."-$ypos-$yparent"}--; |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
elsif ($type eq 'SCALAR' || $type eq 'REF') { |
|
213
|
2
|
|
|
|
|
3
|
my $x = ${$x}; |
|
|
2
|
|
|
|
|
3
|
|
|
214
|
2
|
|
|
|
|
53
|
my $y = ${$y}; |
|
|
2
|
|
|
|
|
5
|
|
|
215
|
2
|
|
|
|
|
5
|
$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
|
|
|
|
|
3
|
$rval = 0; |
|
224
|
|
|
|
|
|
|
} |
|
225
|
|
|
|
|
|
|
else { |
|
226
|
0
|
|
|
|
|
0
|
croak "Can't handle $type type."; |
|
227
|
0
|
|
|
|
|
0
|
$rval = 0; |
|
228
|
|
|
|
|
|
|
} |
|
229
|
|
|
|
|
|
|
} |
|
230
|
|
|
|
|
|
|
} |
|
231
|
2417
|
|
|
|
|
3234
|
$opts->{recursion_detector}--; |
|
232
|
2417
|
|
|
|
|
7835
|
return $rval; |
|
233
|
|
|
|
|
|
|
} |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub plugins { |
|
236
|
3
|
100
|
|
3
|
1
|
52
|
return { map { (($_ eq '') ? '[scalar]' : $_, [map { $_ eq '' ? '[scalar]' : $_ } keys %{$handler{$_}}]) } keys %handler }; |
|
|
4
|
50
|
|
|
|
12
|
|
|
|
4
|
|
|
|
|
20
|
|
|
|
4
|
|
|
|
|
10
|
|
|
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 |