line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Text::Patch; |
2
|
1
|
|
|
1
|
|
52871
|
use Exporter; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
65
|
|
3
|
|
|
|
|
|
|
our @ISA = qw( Exporter ); |
4
|
|
|
|
|
|
|
our @EXPORT = qw( patch ); |
5
|
|
|
|
|
|
|
our $VERSION = '1.8'; |
6
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
31
|
|
7
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
33
|
|
8
|
1
|
|
|
1
|
|
4
|
use Carp; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
54
|
|
9
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
5
|
use constant NO_NEWLINE => '\\ No newline at end of file'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2230
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub patch |
13
|
|
|
|
|
|
|
{ |
14
|
0
|
|
|
0
|
1
|
|
my $text = shift; |
15
|
0
|
|
|
|
|
|
my $diff = shift; |
16
|
0
|
0
|
|
|
|
|
my %options = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_; |
|
0
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
|
18
|
0
|
|
|
|
|
|
my %handler = ('unified' => \&patch_unified, |
19
|
|
|
|
|
|
|
'context' => \&patch_context, |
20
|
|
|
|
|
|
|
'oldstyle' => \&patch_oldstyle, |
21
|
|
|
|
|
|
|
); |
22
|
0
|
|
|
|
|
|
my $style = $options{STYLE}; |
23
|
0
|
0
|
|
|
|
|
croak "required STYLE option is missing" unless $style; |
24
|
0
|
0
|
|
|
|
|
croak "source required" unless defined $text; |
25
|
0
|
0
|
|
|
|
|
croak "diff required" unless defined $diff; |
26
|
0
|
|
0
|
|
|
|
my $code = $handler{lc($style)} || croak "unrecognised STYLE '$style'"; |
27
|
|
|
|
|
|
|
|
28
|
0
|
|
|
|
|
|
my @text = split /^/m, $text; |
29
|
0
|
|
|
|
|
|
my @diff = split /^/m, $diff; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# analyse source/diff to determine line ending used. |
32
|
|
|
|
|
|
|
# (if source is only 1 line, can't use it to determine line endings) |
33
|
0
|
0
|
|
|
|
|
my $line1 = @text > 1 ? $text[0] : $diff[0]; |
34
|
0
|
|
|
|
|
|
my($line1c, $sep) = _chomp($line1); |
35
|
0
|
|
0
|
|
|
|
$sep ||= "\n"; # default to unix line ending |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# apply patch |
38
|
0
|
|
|
|
|
|
DUMP("got patch", \@diff); |
39
|
0
|
|
|
|
|
|
my $out = $code->(\@text, \@diff, $sep); |
40
|
|
|
|
|
|
|
|
41
|
0
|
|
|
|
|
|
my $lastline = _chomp($diff[-1], $sep); |
42
|
0
|
0
|
|
|
|
|
$out = _chomp($out, $sep) if $lastline eq NO_NEWLINE; |
43
|
0
|
|
|
|
|
|
return $out; |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub patch_unified |
47
|
|
|
|
|
|
|
{ |
48
|
0
|
|
|
0
|
0
|
|
my($text, $diff, $sep) = @_; |
49
|
0
|
|
|
|
|
|
my @hunks; |
50
|
|
|
|
|
|
|
my %hunk; |
51
|
|
|
|
|
|
|
|
52
|
0
|
|
|
|
|
|
for( @$diff ) |
53
|
|
|
|
|
|
|
{ |
54
|
|
|
|
|
|
|
#print STDERR ">>> ... [$_]"; |
55
|
0
|
0
|
|
|
|
|
if( /^\@\@\s*-([\d,]+)/ ) |
56
|
|
|
|
|
|
|
{ |
57
|
|
|
|
|
|
|
#print STDERR ">>> *** HUNK!\n"; |
58
|
0
|
|
|
|
|
|
my($pos1, $count1) = split /,/, $1; |
59
|
0
|
|
|
|
|
|
push @hunks, { %hunk }; |
60
|
0
|
|
|
|
|
|
%hunk = (); |
61
|
0
|
|
|
|
|
|
$hunk{ FROM } = $pos1 - 1; # diff is 1-based |
62
|
|
|
|
|
|
|
# Modification by Ben L., patches may have @@ -0,0 if the source is empty. |
63
|
0
|
0
|
|
|
|
|
$hunk{ FROM } = 0 if $hunk{ FROM } < 0; |
64
|
0
|
0
|
|
|
|
|
$hunk{ LEN } = defined $count1 ? $count1 : $pos1 == 0 ? 0 : 1; |
|
|
0
|
|
|
|
|
|
65
|
0
|
|
|
|
|
|
$hunk{ DATA } = []; |
66
|
|
|
|
|
|
|
} |
67
|
0
|
|
|
|
|
|
push @{ $hunk{ DATA } }, $_; |
|
0
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
} |
69
|
0
|
|
|
|
|
|
push @hunks, { %hunk }; # push last hunk |
70
|
0
|
|
|
|
|
|
shift @hunks; # first is always empty |
71
|
|
|
|
|
|
|
|
72
|
0
|
|
|
|
|
|
return _patch($text, \@hunks, $sep); |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub patch_oldstyle { |
76
|
0
|
|
|
0
|
0
|
|
my($text, $diff, $sep) = @_; |
77
|
0
|
|
|
|
|
|
my @hunks; |
78
|
0
|
|
|
|
|
|
my $i = 0; |
79
|
|
|
|
|
|
|
|
80
|
0
|
|
|
|
|
|
my $hunk_head = qr/^([\d,]+)([acd])([\d,]+)$/; |
81
|
0
|
|
|
|
|
|
while($i < @$diff) { |
82
|
0
|
|
|
|
|
|
my $l = $diff->[$i]; |
83
|
0
|
|
|
|
|
|
my($r1, $type, $r2) = $l =~ $hunk_head; |
84
|
0
|
0
|
0
|
|
|
|
die "Malformed patch at line ".($i + 1)."\n" |
|
|
|
0
|
|
|
|
|
85
|
|
|
|
|
|
|
unless defined $r1 && $type && defined $r2; |
86
|
0
|
|
|
|
|
|
my($pos1, $count1) = _range($r1); |
87
|
0
|
|
|
|
|
|
my($pos2, $count2) = _range($r2); |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# parse chunk data |
90
|
0
|
|
|
|
|
|
my @data; |
91
|
0
|
|
|
|
|
|
my $j = $i + 1; |
92
|
0
|
|
|
|
|
|
for(; $j < @$diff; $j++) { |
93
|
0
|
|
|
|
|
|
$l = $diff->[$j]; |
94
|
0
|
0
|
|
|
|
|
last if $l =~ $hunk_head; |
95
|
0
|
0
|
|
|
|
|
next if $l =~ /^---/; # separator |
96
|
0
|
|
|
|
|
|
push @data, $l; |
97
|
|
|
|
|
|
|
} |
98
|
0
|
|
|
|
|
|
my $datalen = $j - $i - 1; |
99
|
|
|
|
|
|
|
|
100
|
0
|
0
|
|
|
|
|
if($type eq 'a') { # add |
101
|
0
|
|
|
|
|
|
$count1 = 0; # don't remove any lines |
102
|
0
|
|
|
|
|
|
$pos1++; # add to line after pos1 |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# convert data to a format _patch() will understand |
106
|
0
|
|
|
|
|
|
for(@data) { |
107
|
0
|
|
|
|
|
|
$_ =~ s/^< /-/; |
108
|
0
|
|
|
|
|
|
$_ =~ s/^> /+/; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
0
|
|
|
|
|
|
push @hunks, { FROM => $pos1 - 1, |
112
|
|
|
|
|
|
|
LEN => $count1, |
113
|
|
|
|
|
|
|
DATA => \@data, |
114
|
|
|
|
|
|
|
}; |
115
|
0
|
|
|
|
|
|
$i += $datalen + 1; |
116
|
|
|
|
|
|
|
} |
117
|
0
|
|
|
|
|
|
return _patch($text, \@hunks, $sep); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# NB: this works by converting hunks into a kind of unified format |
121
|
|
|
|
|
|
|
sub patch_context { |
122
|
0
|
|
|
0
|
0
|
|
my($text, $diff, $sep) = @_; |
123
|
0
|
|
|
|
|
|
my $i = 0; |
124
|
0
|
|
|
|
|
|
my @hunks; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# skip past header |
127
|
0
|
|
|
|
|
|
for(@$diff) { |
128
|
0
|
|
|
|
|
|
$i++; |
129
|
0
|
0
|
|
|
|
|
last if /^\Q***************\E$/; # end header marker |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# this sub reads one half of a hunk (from/to part) |
133
|
|
|
|
|
|
|
my $read_part = sub { |
134
|
0
|
|
|
0
|
|
|
my $l = $diff->[$i++]; |
135
|
0
|
|
|
|
|
|
TRACE("got line: $l"); |
136
|
0
|
0
|
|
|
|
|
die "Malformed patch at line $i\n" |
137
|
|
|
|
|
|
|
unless $l =~ /^(?:\*\*\*|---)\s+([\d,]+)\s+(?:\*\*\*|---)/; |
138
|
0
|
|
|
|
|
|
my($pos, $count) = _range($1); |
139
|
0
|
|
|
|
|
|
my @part; |
140
|
0
|
|
|
|
|
|
while($i < @$diff) { |
141
|
0
|
|
|
|
|
|
my $l = $diff->[$i]; |
142
|
0
|
0
|
|
|
|
|
last if $l =~ /^(\*\*\*|---)/; |
143
|
0
|
|
|
|
|
|
push @part, $l; |
144
|
0
|
|
|
|
|
|
$i++; |
145
|
|
|
|
|
|
|
} |
146
|
0
|
|
|
|
|
|
DUMP("got part", \@part); |
147
|
0
|
|
|
|
|
|
return (\@part, $pos, $count); |
148
|
0
|
|
|
|
|
|
}; |
149
|
|
|
|
|
|
|
|
150
|
0
|
|
|
|
|
|
while($i < @$diff) { |
151
|
|
|
|
|
|
|
# read the from and to part of this hunk |
152
|
0
|
|
|
|
|
|
my($part1, $pos1, $count1) = $read_part->(); |
153
|
0
|
|
|
|
|
|
my($part2, $pos2, $count2) = $read_part->(); |
154
|
0
|
|
|
|
|
|
$i++; # skip chunk separator |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
# convert operations to unified style ones |
157
|
0
|
|
|
|
|
|
$_ =~ s/^(.)\s/$1/ for @$part1, @$part2; |
158
|
0
|
|
|
|
|
|
$_ =~ s/^\!/-/ for @$part1; # remove |
159
|
0
|
|
|
|
|
|
$_ =~ s/^\!/+/ for @$part2; # add |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# merge the parts to create a unified style chunk |
162
|
0
|
|
|
|
|
|
my @data; |
163
|
0
|
|
|
|
|
|
for(;;) { |
164
|
0
|
|
|
|
|
|
my $c1 = $part1->[0]; |
165
|
0
|
|
|
|
|
|
my $c2 = $part2->[0]; |
166
|
0
|
0
|
0
|
|
|
|
last unless defined $c1 || defined $c2; |
167
|
|
|
|
|
|
|
|
168
|
0
|
0
|
0
|
|
|
|
if(defined $c1 && $c1 =~ /^-/) { |
|
|
0
|
0
|
|
|
|
|
169
|
0
|
|
|
|
|
|
push @data, shift @$part1; # remove line |
170
|
|
|
|
|
|
|
} elsif(defined $c2 && $c2 =~ /^\+/) { |
171
|
0
|
|
|
|
|
|
push @data, shift @$part2; # add line |
172
|
|
|
|
|
|
|
} else { # context |
173
|
0
|
|
|
|
|
|
my($x1, $x2) = (shift @$part1, shift @$part2); |
174
|
0
|
0
|
|
|
|
|
push @data, defined $x1 ? $x1 : $x2; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
} |
177
|
0
|
|
|
|
|
|
push @hunks, { FROM => $pos1 - 1, |
178
|
|
|
|
|
|
|
LEN => $count1, |
179
|
|
|
|
|
|
|
DATA => \@data, |
180
|
|
|
|
|
|
|
}; |
181
|
0
|
|
|
|
|
|
DUMP("merged data", \@data); |
182
|
|
|
|
|
|
|
} |
183
|
0
|
|
|
|
|
|
return _patch($text, \@hunks, $sep); |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
###################################################################### |
187
|
|
|
|
|
|
|
# private |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
# returns (start line, line count) |
190
|
|
|
|
|
|
|
sub _range { |
191
|
0
|
|
|
0
|
|
|
my($range) = @_; |
192
|
0
|
|
|
|
|
|
my($pos1, $pos2) = split /,/, $range; |
193
|
0
|
0
|
|
|
|
|
return ($pos1, defined $pos2 ? $pos2 - $pos1 + 1 : 1); |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub _patch { |
197
|
0
|
|
|
0
|
|
|
my($text, $hunks, $sep) = @_; |
198
|
0
|
|
|
|
|
|
my $hunknum = scalar @$hunks + 1; |
199
|
0
|
0
|
|
|
|
|
die "No hunks found\n" unless @$hunks; |
200
|
0
|
|
|
|
|
|
for my $hunk ( reverse @$hunks ) |
201
|
|
|
|
|
|
|
{ |
202
|
0
|
|
|
|
|
|
$hunknum--; |
203
|
0
|
|
|
|
|
|
DUMP("hunk", $hunk); |
204
|
0
|
|
|
|
|
|
my @pdata; |
205
|
0
|
|
|
|
|
|
my $num = $hunk->{FROM}; |
206
|
0
|
|
|
|
|
|
for( @{ $hunk->{ DATA } } ) |
|
0
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
{ |
208
|
0
|
0
|
|
|
|
|
next unless s/^([ \-\+])//; |
209
|
|
|
|
|
|
|
#print STDERR ">>> ($1) $_"; |
210
|
0
|
0
|
|
|
|
|
if($1 ne '+') { |
211
|
|
|
|
|
|
|
# not an addition, check line for match against existing text. |
212
|
|
|
|
|
|
|
# ignore line endings for comparison |
213
|
0
|
|
|
|
|
|
my $orig = _chomp($text->[$num++], $sep); # num 0 based here |
214
|
0
|
|
|
|
|
|
my $expect = _chomp($_, $sep); |
215
|
0
|
|
|
|
|
|
TRACE("checking >>$orig<<"); |
216
|
0
|
|
|
|
|
|
TRACE(" against >>$expect<<"); |
217
|
0
|
0
|
|
|
|
|
die "Hunk #$hunknum failed at line $num.\n" # actual line number |
218
|
|
|
|
|
|
|
unless $orig eq $expect; |
219
|
|
|
|
|
|
|
} |
220
|
0
|
0
|
|
|
|
|
next if $1 eq '-'; # removals |
221
|
0
|
|
|
|
|
|
push @pdata, $_; # add/replace line |
222
|
|
|
|
|
|
|
} |
223
|
0
|
|
|
|
|
|
splice @$text, $hunk->{ FROM }, $hunk->{ LEN }, @pdata; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
0
|
|
|
|
|
|
return join '', @$text; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
# chomp $sep from the end of line |
230
|
|
|
|
|
|
|
# if $sep is not given, chomp unix or dos line ending |
231
|
|
|
|
|
|
|
sub _chomp { |
232
|
0
|
|
|
0
|
|
|
my($text, $sep) = @_; |
233
|
0
|
0
|
|
|
|
|
if($sep) { |
234
|
0
|
|
|
|
|
|
$text =~ s/($sep)$//; |
235
|
|
|
|
|
|
|
} else { |
236
|
0
|
|
|
|
|
|
$text =~ s/(\r\n|\n)$//; |
237
|
|
|
|
|
|
|
} |
238
|
0
|
0
|
|
|
|
|
return wantarray ? ($text, $1) : $text; |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
0
|
|
|
0
|
0
|
|
sub DUMP {} |
242
|
0
|
|
|
0
|
0
|
|
sub TRACE {} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
#sub DUMP { |
245
|
|
|
|
|
|
|
#use Data::Dumper; |
246
|
|
|
|
|
|
|
#print STDERR Dumper(@_); |
247
|
|
|
|
|
|
|
#} |
248
|
|
|
|
|
|
|
#sub TRACE { |
249
|
|
|
|
|
|
|
#use Data::Dumper; |
250
|
|
|
|
|
|
|
#print STDERR Dumper(@_); |
251
|
|
|
|
|
|
|
#} |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=pod |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=head1 NAME |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
Text::Patch - Patches text with given patch |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=head1 SYNOPSIS |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
use Text::Patch; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
$output = patch( $source, $diff, STYLE => "Unified" ); |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
use Text::Diff; |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
$src = ... |
269
|
|
|
|
|
|
|
$dst = ... |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
$diff = diff( \$src, \$dst, { STYLE => 'Unified' } ); |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
$out = patch( $src, $diff, { STYLE => 'Unified' } ); |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
print "Patch successful" if $out eq $dst; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=head1 DESCRIPTION |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Text::Patch combines source text with given diff (difference) data. |
280
|
|
|
|
|
|
|
Diff data is produced by Text::Diff module or by the standard diff |
281
|
|
|
|
|
|
|
utility (man diff, see -u option). |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
=over 4 |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=item patch( $source, $diff, options... ) |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
First argument is source (original) text. Second is the diff data. |
288
|
|
|
|
|
|
|
Third argument can be either hash reference with options or all the |
289
|
|
|
|
|
|
|
rest arguments will be considered patch options: |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
$output = patch( $source, $diff, STYLE => "Unified", ... ); |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
$output = patch( $source, $diff, { STYLE => "Unified", ... } ); |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
Options are: |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
STYLE => 'Unified' |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
STYLE can be "Unified", "Context" or "OldStyle". |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
The 'Unified' diff format looks like this: |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
@@ -1,7 +1,6 @@ |
304
|
|
|
|
|
|
|
-The Way that can be told of is not the eternal Way; |
305
|
|
|
|
|
|
|
-The name that can be named is not the eternal name. |
306
|
|
|
|
|
|
|
The Nameless is the origin of Heaven and Earth; |
307
|
|
|
|
|
|
|
-The Named is the mother of all things. |
308
|
|
|
|
|
|
|
+The named is the mother of all things. |
309
|
|
|
|
|
|
|
+ |
310
|
|
|
|
|
|
|
Therefore let there always be non-being, |
311
|
|
|
|
|
|
|
so we may see their subtlety, |
312
|
|
|
|
|
|
|
And let there always be being, |
313
|
|
|
|
|
|
|
@@ -9,3 +8,6 @@ |
314
|
|
|
|
|
|
|
The two are the same, |
315
|
|
|
|
|
|
|
But after they are produced, |
316
|
|
|
|
|
|
|
they have different names. |
317
|
|
|
|
|
|
|
+They both may be called deep and profound. |
318
|
|
|
|
|
|
|
+Deeper and more profound, |
319
|
|
|
|
|
|
|
+The door of all subtleties! |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
=back |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=head1 TODO |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
Interfaces with files, arrays, etc. |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=head1 AUTHOR |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
Vladi Belperchinov-Shabanski "Cade" |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
http://cade.datamax.bg |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=head1 VERSION |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
$Id: Patch.pm,v 1.6 2007/04/07 19:57:41 cade Exp $ |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=cut |