line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Data::KeyDiff; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
60687
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
67
|
|
4
|
2
|
|
|
2
|
|
9
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
139
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
=head1 NAME |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
Data::KeyDiff - Diff one set/list against another with a key basis |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 VERSION |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
Version 0.021 |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=cut |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
$Data::KeyDiff::VERSION = '0.021'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 SYNOPSIS |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# For each item in the list, the number is the item "key", the letter is the item "data" |
21
|
|
|
|
|
|
|
my @A = qw/1a 2b 3c 4d 5e 6f/; |
22
|
|
|
|
|
|
|
my @B = qw/5e 1f 2b 3r 4d 7q j n/; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
use Data::KeyDiff qw/diff/; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
diff( \@A, \@B, |
27
|
|
|
|
|
|
|
key => |
28
|
|
|
|
|
|
|
sub($item) { |
29
|
|
|
|
|
|
|
# Return the leading number from $item |
30
|
|
|
|
|
|
|
}, |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
is_different => |
33
|
|
|
|
|
|
|
sub($a, $b) { |
34
|
|
|
|
|
|
|
# Is the letter on $a different from $b? |
35
|
|
|
|
|
|
|
}, |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
is_new => |
38
|
|
|
|
|
|
|
sub($item) { |
39
|
|
|
|
|
|
|
# Does $item already have a key? |
40
|
|
|
|
|
|
|
}, |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# "j" and "n" are new! |
43
|
|
|
|
|
|
|
new => sub($element) { |
44
|
|
|
|
|
|
|
# Handle a new $element |
45
|
|
|
|
|
|
|
}, |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# "7q" was inserted (already had a key) |
48
|
|
|
|
|
|
|
insert => sub($element) { |
49
|
|
|
|
|
|
|
# $element was "inserted" into @B |
50
|
|
|
|
|
|
|
}, |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# "1f" and "3r" were updated |
53
|
|
|
|
|
|
|
update => sub($element) { |
54
|
|
|
|
|
|
|
# $element was "update" in @B |
55
|
|
|
|
|
|
|
}, |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# "6f" was deleted |
58
|
|
|
|
|
|
|
delete => sub($element) { |
59
|
|
|
|
|
|
|
# $element was "deleted" in @B |
60
|
|
|
|
|
|
|
}, |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# "5e", "2b", and "4d" changed rank |
63
|
|
|
|
|
|
|
update_rank => sub($element) { |
64
|
|
|
|
|
|
|
# $element had it's rank changed in @B |
65
|
|
|
|
|
|
|
}, |
66
|
|
|
|
|
|
|
); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head1 DESCRIPTION |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Data::KeyDiff performs a diff-like operation on sets that have unique keys associated with each element. |
71
|
|
|
|
|
|
|
Instead of looking at the whole list, C looks at each element on a case-by-case basis to see whether it's state or |
72
|
|
|
|
|
|
|
inclusion has changed from the "before" set to the "after" set. |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=head1 METHODS |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=head2 Data::KeyDiff->diff( , , ) |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
Compare the before-set to the after-set. Call handlers in as defined. |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Besides the before-set and after-set, this method accepts the following: |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=over |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=item ignore($item) OPTIONAL |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
A subroutine that returns true if $item should be ignored (e.g. commented). If an item ignored, the rank counter is not incremented, but the position counter still is. |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=item prepare($item) OPTIONAL |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
A subroutine that returns a replacement for $item in further processing. Basically, this allows you to preprocess the $item before passing it to C, C, etc. |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=item is_new($item) |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
A subroutine that returns true if $item is "new" and so doesn't already have a key. |
95
|
|
|
|
|
|
|
Note, this subroutine is not run on the before-set (every item in that set should already have a key). |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=item key($item) |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
A subroutine that returns the key of $item. |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=item is_different($before_item, $after_item, $before_element, $after_element) |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=item compare($before_item, $after_item, $before_element, $after_element) |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
A subroutine that returns true if $before_item is different from $after_item. |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=item new($element) OPTIONAL |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
Called for each new $element |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=item insert($element) OPTIONAL |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
Called for each $element that should be inserted |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=item update($element) OPTIONAL |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Called for each $element that should be updated |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=item update_rank($element) OPTIONAL |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Called for each $element that is otherwise the same, but has a different rank |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=item delete($element) OPTIONAL |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Called for each $element that should be deleted |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=back |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=head1 EXPORTS |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head2 diff( ... ) |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Same syntax as above. See above for more information. |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=head1 AUTHOR |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
Robert Krimen, C<< >> |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=head1 BUGS |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
Please report any bugs or feature requests to C, or through |
142
|
|
|
|
|
|
|
the web interface at L. I will be notified, and then you'll |
143
|
|
|
|
|
|
|
automatically be notified of progress on your bug as I make changes. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=head1 SUPPORT |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
perldoc Data::KeyDiff |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
You can also look for information at: |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=over 4 |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
L |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
L |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item * CPAN Ratings |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
L |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=item * Search CPAN |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
L |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=back |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
Copyright 2007 Robert Krimen, all rights reserved. |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
186
|
|
|
|
|
|
|
under the same terms as Perl itself. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=cut |
190
|
|
|
|
|
|
|
|
191
|
2
|
|
|
2
|
|
1027
|
use Data::KeyDiff::Element; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
16
|
|
192
|
2
|
|
|
2
|
|
46
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
1479
|
|
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
require Exporter; |
195
|
|
|
|
|
|
|
@Data::KeyDiff::ISA = qw/Exporter/; |
196
|
|
|
|
|
|
|
@Data::KeyDiff::EXPORT_OK = qw/diff/; |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
sub diff { |
199
|
6
|
50
|
33
|
6
|
1
|
145229
|
shift if $_[0] && $_[0] eq __PACKAGE__; |
200
|
6
|
|
|
|
|
214
|
my $before = shift; |
201
|
6
|
|
|
|
|
15
|
my $after = shift; |
202
|
6
|
|
|
|
|
113
|
my %in = @_; |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
my $get_key = $in{key} || $in{get_key} || sub { |
205
|
0
|
|
|
0
|
|
|
return shift; |
206
|
6
|
|
50
|
|
|
42
|
}; |
207
|
6
|
|
|
|
|
18
|
my $is_new = $in{is_new}; |
208
|
6
|
|
|
|
|
15
|
my $prepare = $in{prepare}; |
209
|
|
|
|
|
|
|
my $is_different = $in{is_different} || $in{compare} || sub { |
210
|
0
|
|
|
0
|
|
|
my $left = shift; |
211
|
0
|
|
|
|
|
|
my $right = shift; |
212
|
0
|
|
|
|
|
|
return ((defined $left ^ defined $right) || (defined $left && $left ne $right)); |
213
|
6
|
|
50
|
|
|
21
|
}; |
214
|
6
|
|
|
|
|
15
|
my $ignore = $in{ignore}; |
215
|
6
|
|
|
|
|
25
|
my ($on_new, $on_insert, $on_update, $on_update_rank, $on_delete) = @in{qw/new insert update update_rank delete/}; |
216
|
|
|
|
|
|
|
|
217
|
6
|
|
|
|
|
8
|
my %before; |
218
|
|
|
|
|
|
|
my %after; |
219
|
0
|
|
|
|
|
0
|
my (@new, %insert, %update, %update_rank, %delete); |
220
|
|
|
|
|
|
|
|
221
|
6
|
|
|
|
|
12
|
my $position = my $rank = 0; |
222
|
6
|
|
|
|
|
11
|
$position--; |
223
|
6
|
|
|
|
|
9
|
my $item; |
224
|
6
|
|
|
|
|
18
|
for $item (@$before) { |
225
|
24
|
|
|
|
|
29
|
$position++; |
226
|
24
|
50
|
33
|
|
|
56
|
next if $ignore && $ignore->($item); |
227
|
24
|
50
|
|
|
|
50
|
my $value = $prepare ? $prepare->($item) : $item; |
228
|
24
|
|
|
|
|
61
|
my $key = $get_key->($value, $item); |
229
|
24
|
|
|
|
|
183
|
my $element = Data::KeyDiff::Element->new(key => $key, value => $value, position => $position, rank => $rank++, item => $item, in_before => 1); |
230
|
24
|
|
|
|
|
336
|
$before{$key} = $element; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
6
|
|
|
|
|
16
|
$position = $rank = 0; |
234
|
6
|
|
|
|
|
9
|
$position--; |
235
|
6
|
|
|
|
|
17
|
for $item (@$after) { |
236
|
32
|
|
|
|
|
49
|
$position++; |
237
|
32
|
50
|
33
|
|
|
84
|
next if $ignore && $ignore->($item); |
238
|
32
|
50
|
|
|
|
64
|
my $value = $prepare ? $prepare->($item) : $item; |
239
|
32
|
100
|
66
|
|
|
126
|
if ($is_new && $is_new->($value, $item)) { |
240
|
8
|
|
|
|
|
88
|
my $element = Data::KeyDiff::Element->new(value => $value, position => $position, rank => $rank++, item => $item, is_new => 1); |
241
|
8
|
|
|
|
|
89
|
push @new, $element; |
242
|
8
|
|
|
|
|
25
|
next; |
243
|
|
|
|
|
|
|
} |
244
|
24
|
|
|
|
|
192
|
my $key = $get_key->($value, $item); |
245
|
24
|
|
|
|
|
157
|
my $element = Data::KeyDiff::Element->new(key => $key, value => $value, position => $position, rank => $rank++, item => $item, in_after => 1); |
246
|
24
|
|
|
|
|
244
|
$after{$key} = $element; |
247
|
24
|
100
|
|
|
|
1048
|
if (! $before{$key}) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
248
|
7
|
|
|
|
|
24
|
$insert{$key}++; |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
elsif ($is_different->($before{$key}->value, $element->value, $before{$key}, $element)) { |
251
|
8
|
|
|
|
|
409
|
$update{$key}++; |
252
|
|
|
|
|
|
|
} |
253
|
|
|
|
|
|
|
elsif ($before{$key}->rank != $after{$key}->rank) { |
254
|
9
|
|
|
|
|
429
|
$update_rank{$key}++; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
6
|
|
|
|
|
51
|
for my $key (keys %before) { |
259
|
24
|
100
|
|
|
|
54
|
next if exists $after{$key}; |
260
|
7
|
|
|
|
|
13
|
$delete{$key}++; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
|
263
|
6
|
50
|
|
|
|
22
|
if ($on_new) { |
264
|
6
|
|
|
|
|
21
|
$on_new->($_) for @new; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
6
|
50
|
|
|
|
419
|
if ($on_insert) { |
268
|
6
|
|
|
|
|
26
|
$on_insert->($after{$_}) for keys %insert; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
6
|
50
|
|
|
|
202
|
if ($on_update) { |
272
|
6
|
|
|
|
|
27
|
$on_update->($after{$_}, $before{$_}) for keys %update; |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
6
|
50
|
|
|
|
202
|
if ($on_update_rank) { |
276
|
6
|
|
|
|
|
30
|
$on_update_rank->($after{$_}, $before{$_}) for keys %update_rank; |
277
|
|
|
|
|
|
|
} |
278
|
|
|
|
|
|
|
|
279
|
6
|
50
|
|
|
|
267
|
if ($on_delete) { |
280
|
6
|
|
|
|
|
74
|
$on_delete->($before{$_}) for keys %delete; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
1; # End of Data::KeyDiff |