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