File Coverage

blib/lib/Text/Patch/Rred.pm
Criterion Covered Total %
statement 52 130 40.0
branch 14 80 17.5
condition 1 3 33.3
subroutine 10 12 83.3
pod 6 6 100.0
total 83 231 35.9


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             # Copyright (C) 2006-2009 Jakob Bohm. All Rights Reserved.
4             # See README in the distribution for the current license status of the
5             # entire package, including this file.
6              
7             =head1 NAME
8              
9             Text::Patch::Rred - Safely apply diff --ed style patches
10              
11             =head1 VERSION
12              
13             This is version 0.06
14              
15             =cut
16              
17             package Text::Patch::Rred;
18              
19 2     2   41325 use 5.006; # Even older might work, but are not supported
  2         7  
  2         102  
20 2     2   14 use strict;
  2         6  
  2         82  
21 2     2   12 use warnings;
  2         11  
  2         125  
22 2     2   12 use Carp qw(carp croak);
  2         4  
  2         172  
23 2     2   19 use base qw(Exporter);
  2         4  
  2         4466  
24             our $VERSION = '0.06';
25              
26             =head1 USAGE
27              
28             =head2 Command Line:
29              
30             S F F F [ F ... ]>
31              
32             =head2 Functional Interface (preferred, faster, type checked)
33              
34             my @lines = ;
35             my $edState = Text::Patch::Rred::Init(@lines);
36             # or &Text::Patch::Rred::Init(\@lines);
37             while () { Text::Patch::Rred::Do1($edState, $_); }
38             @lines = Text::Patch::Rred::Result($edState);
39             $edState = undef; # free memory
40             print PATCHED @lines;
41              
42             =head2 Object Interface:
43              
44             my @lines = ;
45             my $edState = Text::Patch::Rred->new(\@lines);
46             while () { $edState->Do1($_); }
47             @lines = $edState->Result;
48             $edState = undef; # free memory
49             print PATCHED @lines;
50              
51             =head2 Example:
52              
53             $ diff --ed file.v1 file.v2 >file.patch1
54             $ diff --ed file.v2 file.v3 >file.patch2
55             $ rred file.v1 file.new file.patch1 [ file.patch2 ... ]
56             $ # Now file.new is the same as file.v3
57             $ # Alternative:
58             $ cat file.patch1 file.patch2 | rred file.v1 file.new -
59              
60             =head1 DESCRIPTION
61              
62             This module and program safely and securely applies one or more
63             S patches as produced by the command S
64             newfile>>. It does exactly what you tell it to and no more, even
65             with wildly bad or evil input.
66              
67             Unlike the traditional programs L|patch(1)>, L|ed(1)>,
68             L|red(1)> and L|sed(1)>, Rred does not allow the data
69             in the patch to run arbitrary commands, read or write files or
70             otherwise cause havoc. Only the handful of safe "commands" actually
71             used by L|diff(1)> are recognized and processed.
72              
73             Unlike the L|patch(1)> program and the perl modules
74             L|Text::Patch>, L|PatchReader> and
75             L|Meta::Development::Patch>, this module
76             does NOT try to doubleguess what kind of data it is given or which
77             file to process.
78              
79             (Note that the other perl modules just mentioned cannot actually
80             apply an ed-style patch, though some can parse it).
81              
82             The name B is short for "Really Restricted ED" (as compared to
83             L|red(1)>). This is the name given to a similar utility used
84             inside the Debian projects apt facility.
85              
86              
87             =head1 REQUIRED ARGUMENTS
88              
89             =over 4
90              
91             =item F
92              
93             Original file whose contents is to be patch. This file should be identical
94             to the first file passed to I>> when the patches were made. In
95             this implementation, perl magics are supported for this file name.
96              
97             =item F
98              
99             Output file where the fully patched contents is to be written. This file
100             will become identical to the last file passed to I>> when the
101             patches were made. This file can be the same file as any of the input
102             files. In this implementation, perl magics are B supported for this
103             file name.
104              
105             =item F [ F ... ]
106              
107             One or more --ed style patch files to be applied (in sequence) to the
108             contents of F to produce F. These files should be
109             identical to the output of one or more invocations of I>> on
110             F and F plus optionally any intermediary files. The
111             patch files may optionally be concatenated before being passed to B,
112             the result will be the same as passing them individually. In this
113             implementation, perl magics are supported for these file names.
114              
115             =back
116              
117             =head1 EXPORT
118              
119             L|/Init @lines>, L|/Do1 $edState, $patchline>,
120             L|/Do $edState, @lines>, L|/Result $edState> and
121             L|/main @ARGV> can be exported. B<:all> is short for
122             L|/Init @lines>, L|/Do1 $edState, $patchline> and
123             L|/Result $edState>. Nothing is exported by default.
124              
125             =cut
126              
127             # This allows declaration use Text::Patch::Rred ':all';
128             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
129             # will save memory.
130             our %EXPORT_TAGS = ('all' => [qw(Init Do1 Result)]);
131              
132             our @EXPORT_OK = qw(Init Do1 Result Do main);
133              
134             # Items to export into callers namespace by default. Note: do not export
135             # names by default without a very good reason. Use EXPORT_OK instead.
136             # Do not simply export all your public functions/methods/constants.
137             # Perl::Critic wrongly complains about this line: our @EXPORT = qw();
138              
139             =begin comment
140              
141             # Internal data format:
142             # For efficiency, the state is an array with the following elements
143             # $a[0] 1 in insert mode, 0 in command mode
144             # $a[1] index in $a of start of hole after current line
145             # == 1 + index in $a of current line
146             # == 1 + current line no + 3
147             # == current line no + 4
148             # (so current line = 0 is stored as 4)
149             # $a[2] index in $a storing first line after current
150             # == index in $a after end of hole after current line
151             # $a[3] index in $a after last line
152             # $a[4 ..$a[1]-1] lines up to and including current line
153             # $a[$a[1]..$a[2]-1] hole: free entries to make insertion easy
154             # $a[$a[2]..$a[3]-1] lines after current line
155             # $a[$a[3]..$#a ] possibly unused entries
156             # Each of the 4 ranges can be empty (i..i-1) but not less than that
157             # Each line is stored as a reference to a string, not as a string, this is
158             # done to avoid massive memory thrashing when patching large files.
159             #
160             # To allow the object oriented variant of the code to work too, the array
161             # reference above is actually stored as the sole member of a returned
162             # hash reference, to conform to normal subclassing behaviour.
163              
164             =end comment
165              
166             =cut
167              
168             =head1 FUNCTIONS and METHODS
169              
170             =over
171              
172             =item B I<\@lines>
173              
174             =cut
175              
176             sub new($$)
177             {
178 1     1 1 2 my ($class, $rlins) = @_;
179 1         3 my $n = scalar @$rlins;
180             ## no critic (ProhibitComplexMappings)
181 2         2 my $ro = {
182             RA => [
183             0, $n + 4, $n + 1004, $n + 1004,
184 1         5 (map { my $a = $_; \$a } @$rlins),
  2         80  
185             (undef) x 1000
186             ]
187             };
188             ## use critic (ProhibitComplexMappings)
189 1   33     20 bless $ro, ((ref $class) or $class);
190 1         9 return $ro;
191             }
192              
193             =item B I<@lines>
194              
195             Initializes a new patch state and sets the initial file content to a
196             copy of the lines of text (each a string) supplied. Returns a new
197             B object if successful, undef on error (there
198             are no current error scenarios).
199              
200             Lines in C<@lines> must use the same line endings (none or "\n") as
201             lines passed to L|/Do1 $edState, $patchline> and L|/Do
202             $edState, @lines>
203              
204             =cut
205              
206             sub Init(\@)
207             {
208 1     1 1 18 my ($rlins) = @_;
209 1         5 return new(__PACKAGE__, $rlins);
210             }
211              
212             =item B I<$edState>
213              
214             =item I<$edState>->B
215              
216             Returns the lines of the patched file as a list of strings, does not
217             destroy C<$edState> so you can apply more patches later.
218              
219             =cut
220              
221             sub Result($)
222             {
223 3     3 1 327 my $ra = shift->{RA};
224              
225             return
226 4         16 map { $$_ }
  3         10  
227 3         10 @{$ra}[ 4 .. ($ra->[1] - 1), ($ra->[2]) .. ($ra->[3] - 1) ];
228             }
229              
230             # Internal function: Change current pos to line $n-1, deleting lines
231             # $n .. $p inclusive
232             sub _GoPos($$$)
233             {
234 2     2   7 my ($ra, $n, $p) = @_;
235 2         5 my $i = $ra->[1];
236 2         3 my $j = $ra->[2];
237              
238 2 50       8 $n = 1 if ($n <= 0);
239 2         4 $n += 3;
240 2         4 $p += 4;
241 2         4 my $m = $j - $i + $p;
242              
243             # Change hole start to $n, delete first $p-$n lines after hole
244             # new hole size is then ($j-$i) + ($p-$n)
245             # new hole end+1 is then $n + ($j-$i) + ($p-$n)
246             # == ($j-$i) + $p
247             # == $m
248             # In summary:
249             # Now: $i..$j-1 is the old position and hole as indexes
250             # $n..$m-1 is the new position and hole as indexes
251             # $p == $m - ($j - $i)
252              
253 2 100       8 if ($m > $ra->[3]) {
254 1         2 $m = $ra->[3];
255 1         2 $p = $m - $j + $i;
256             }
257              
258 2 100       7 $n = $p if ($n > $p);
259              
260             # Now: $i..$j-1 is the old position and hole as indexes
261             # $n..$m-1 is the new position and hole as indexes
262             # $p == $m - ($j - $i)
263             # The new hole size is >= the old hole size
264             # The new hole does not go outside the actual lines available
265 2         3 $ra->[1] = $n;
266 2         6 $ra->[2] = $m;
267 2 50       6 if ($n != $m) {
268 2 50       17 if ($m < $j) { # The most common case, diff works backwards
    50          
269 0 0       0 if ($j - $m < 1000) {
270 0         0 @{$ra}[ $m .. ($j - 1) ] = @{$ra}[ $p .. ($i - 1) ];
  0         0  
  0         0  
271             } else {
272 0         0 splice @$ra, $i, ($j - $i);
273 0         0 splice @$ra, $n, 0, ((undef) x ($m - $n));
274             }
275             } elsif ($n > $i) { # This mostly happens when combining diffs
276 0 0       0 if ($n - $i < 1000) {
277 0         0 @{$ra}[ $i .. ($n - 1) ] =
  0         0  
278 0         0 @{$ra}[ $j .. ($n + $j - $i - 1) ];
279             } else {
280 0         0 splice @$ra, $i, ($j - $i);
281 0         0 splice @$ra, $n, 0, ((undef) x ($m - $n));
282             }
283             } # If neither is true, the old hole touches/overlaps/is the
284             # the old hole, and no data needs to be moved
285             }
286 2         4 return;
287             }
288              
289             =item B I<$edState>, I<$patchline>
290              
291             =item I<$edState>->B(I<$patchline>)
292              
293             Applies one line from a patch file to C<$edState>. Returns a true
294             value if the line was understood. Carps and returns C if an
295             unsupported command is input.
296              
297             =cut
298              
299             ## no critic (ProhibitExcessComplexity, ProhibitCascadingIfElse)
300             sub Do1($$)
301             {
302 1     1 1 3 my $ra = shift->{RA};
303 1         3 local $_ = shift;
304 1         16 my $i = $ra->[1];
305 1         2 my $j = $ra->[2];
306              
307 1 50       21 if ($ra->[0]) {
    50          
    50          
    50          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
308 0 0       0 unless (/^\.$/) {
309 0         0 my $v = $_;
310 0 0       0 if ($i == $j) {
311 0         0 splice @$ra, $i, 0, ((undef) x 1000);
312 0         0 $ra->[2] += 1000;
313 0         0 $ra->[3] += 1000;
314             }
315 0         0 $ra->[ $i++ ] = \$v;
316 0         0 $ra->[1] = $i;
317             } else {
318 0         0 $ra->[0] = 0;
319             }
320             } elsif (/^a\s*$/) {
321 0         0 $ra->[0] = 1;
322             } elsif (/^([0-9]+)a/) {
323 0         0 $ra->[0] = 1;
324 0         0 _GoPos($ra, 1 + $1, $1); # Delete nothing
325             } elsif (/^d\s*$/) {
326 0 0       0 if ($i > 4) {
    0          
327 0 0       0 if ($j < $ra->[3]) { # Set current line to line after
328             # Deleting current line in the process
329 0         0 $ra->[ $i - 1 ] = $ra->[$j];
330 0         0 $ra->[2] = ++$j;
331             } else { # delete last line and stay at last line
332 0         0 $ra->[1] = --$i;
333             }
334             } elsif ($j < $ra->[3]) {
335              
336             # delete non-existant line 0 and advance to line 1
337 0         0 $ra->[4] = $ra->[$j];
338 0         0 $ra->[1] = 5;
339 0         0 $ra->[2] = ++$j;
340             } # Last possibility is an empty file, do nothing
341             } elsif (/^([0-9]+)d/) {
342 0         0 _GoPos($ra, $1, $1);
343 0         0 _GoPos($ra, 1 + $1, $1); # Set current line to line after
344             } elsif (/^([0-9]+),([0-9]+)d/) {
345 1         5 _GoPos($ra, $1, $2);
346 1         5 _GoPos($ra, 1 + $1, $1); # Set current line to line after
347             } elsif (/^c\s*$/) {
348 0         0 $ra->[0] = 1;
349 0 0       0 if ($i > 4) {
350 0         0 $ra->[1] = --$i; # delete cur line and append after the one bef
351             } # Otherwise delete nonexistent line 0 and append there
352             } elsif (/^([0-9]+)c/) {
353 0         0 $ra->[0] = 1;
354 0         0 _GoPos($ra, $1, $1);
355             } elsif (/^([0-9]+),([0-9]+)c/) {
356 0         0 $ra->[0] = 1;
357 0         0 _GoPos($ra, $1, $2);
358             } elsif (m!^s/\^?\\?\.//\s*$!) {
359 0 0       0 if ($i > 4) {
360              
361             # diff uses this to insert lines consisting of a single .
362 0         0 $_ = $ra->[ --$i ];
363 0         0 $$_ =~ s/^\.//;
364 0         0 $ra->[$i] = $_;
365             }
366             } elsif (/^w/) {
367              
368             # ignore write command sometimes appended to patch files
369             } elsif (/^\s*$/) {
370              
371             # ignore blank command lines (but not blank lines in added text)
372             } else {
373 0         0 carp "Unexpected non-patch ed command: '" . $_ . "'\n";
374 0         0 return;
375             }
376 1         7 return 1;
377             }
378             ## use critic (ProhibitExcessComplexity, ProhibitCascadingIfElse)
379              
380             =item B I<$edState>, I<@lines>
381              
382             =item I<$edState>->B(I<@lines>)
383              
384             Simply calls L|/Do1 $edState, $patchline> for each element of
385             C<@lines>, returns a true value if all calls were successful or
386             C<@lines> was empty. Otherwise returns C.
387              
388             =cut
389              
390             ## no critic (RequireArgUnpacking)
391             sub Do($;)
392             {
393 0     0 1   my $self = shift;
394 0           local $_ = undef;
395 0           my $ok = 1;
396 0           for (@_) {
397 0 0         unless (ref $_) {
398 0 0         Do1($self, $_) or ($ok = undef);
399             } else {
400 0           for (@$_) {
401 0 0         Do1($self, $_) or ($ok = undef);
402             }
403             }
404             }
405 0           return $ok;
406             }
407             ## use critic (RequireArgUnpacking)
408              
409             =item B
I<@ARGV>
410              
411             The main program code of rred as a function, accepting the command
412             line syntax in the L|/Command Line:> above. Returns the
413             program exit code (0 ok, 1 error, 2 bad syntax). Running C
414             args> is the same as running
415              
416             perl -MText::Patch::Rred \
417             -e 'exit Text::Patch::Rred::main @ARGV' args
418              
419             =cut
420              
421             sub main(@)
422             {
423 0     0 1   my (@args) = @_;
424              
425 0 0         if (@args < 3) {
426 0 0         print STDERR <<'ENDUSAGE'
427             Copyright (C) 2006-2009 Jakob Bohm. All Rights Reserved.
428             usage: rred file.old file.new file.patch1 [ file.patch2 ... ]
429             (perl open magics are supported in all file names)
430             Applies one or more diff --ed or diff -e patches to file.old, creating
431             file.new . file.old and file.new can be the same file. Perl open
432             magics are supported in all input file names. This help is displayed
433             on stderr, use rred --help 2>file to capture it.
434             example:
435             zcat Packages.diff/2006-04-23-1343.24.gz \
436             Packages.diff/2006-04-24-1329.37.gz |
437             rred Packages Packages -
438             ENDUSAGE
439             or croak $0. ': Printing usage: ' . $!;
440 0           return 2;
441             }
442              
443 0           my $namIn = shift @args;
444 0           my $namOut = shift @args;
445              
446             ## no critic (ProhibitTwoArgOpen)
447              
448 0 0         open FH, $namIn or croak "Loading '" . $namIn . "': " . $!;
449 0 0         my @lines = or croak "Loading '" . $namIn . "': " . $!;
450 0 0         close FH or croak "Loading '" . $namIn . "': " . $!;
451 0           my $edState = Init(@lines);
452 0           @lines = (); # Release loading memory
453              
454 0           for my $fn (@args) {
455 0 0         open FH, $fn or croak "Reading '" . $fn . "': " . $!;
456 0           while () { Do1($edState, $_); }
  0            
457 0 0         close FH or croak "Reading '" . $fn . "': " . $!;
458             }
459              
460 0           @lines = Result($edState);
461 0           $edState = undef; # free memory
462 0 0         open FH, '>', $namOut or croak "Saving '" . $namOut . "': " . $!;
463 0 0         print FH @lines or croak "Saving '" . $namOut . "': " . $!;
464 0 0         close FH or croak "Saving '" . $namOut . "': " . $!;
465              
466             ## use critic (ProhibitTwoArgOpen)
467              
468 0           return 0;
469             }
470              
471             # Automatically run main if Rred.pm is invoked as a perl program, rather
472             # than a script.
473             # Note: This line would be more readable with paranthesis, but Perl::Critic
474             # complains about such clarity.
475             exit main(@ARGV) unless defined caller;
476              
477             1;
478             __END__