File Coverage

blib/lib/File/Replace.pm
Criterion Covered Total %
statement 221 225 98.6
branch 153 158 98.1
condition 62 66 96.9
subroutine 33 33 100.0
pod 7 13 53.8
total 476 495 97.1


line stmt bran cond sub pod time code
1             #!perl
2             package File::Replace;
3 8     8   372899 use warnings;
  8         19  
  8         263  
4 8     8   46 use strict;
  8         18  
  8         146  
5 8     8   38 use Carp;
  8         21  
  8         437  
6 8     8   48 use warnings::register;
  8         28  
  8         923  
7 8     8   62 use IO::Handle; # allow method calls on filehandles on older Perls
  8         29  
  8         565  
8 8     8   55 use File::Temp qw/tempfile/;
  8         14  
  8         409  
9 8     8   51 use File::Basename qw/fileparse/;
  8         16  
  8         573  
10 8     8   2741 use File::Spec::Functions qw/devnull/;
  8         5143  
  8         425  
11 8     8   3849 use File::Copy ();
  8         19018  
  8         226  
12 8     8   75 use Fcntl qw/S_IMODE/;
  8         25  
  8         373  
13 8     8   58 use Exporter ();
  8         21  
  8         110  
14 8     8   2873 use File::Replace::SingleHandle ();
  8         21  
  8         155  
15 8     8   2796 use File::Replace::DualHandle ();
  8         19  
  8         148  
16 8     8   3594 use File::Replace::Inplace ();
  8         26  
  8         649  
17             BEGIN {
18 8     8   4231 require Hash::Util;
19             # apparently this wasn't available until 0.06 / Perl 5.8.9
20             # since this is just for internal typo prevention,
21             # we can fake it when it's not available
22             # uncoverable branch false
23             # uncoverable condition right
24             # uncoverable condition false
25 8 50 33     22480 if ($] ge '5.010' || defined &Hash::Util::lock_ref_keys)
26 8         55 { Hash::Util->import('lock_ref_keys') }
27 0         0 else { *lock_ref_keys = sub {} } # uncoverable statement
28             }
29              
30             # For AUTHOR, COPYRIGHT, AND LICENSE see the bottom of this file
31              
32             ## no critic (RequireArgUnpacking)
33              
34             our $VERSION = '0.14';
35              
36             our @EXPORT_OK = qw/ replace replace2 replace3 inplace /;
37             our @CARP_NOT = qw/ File::Replace::SingleHandle File::Replace::DualHandle File::Replace::Inplace /;
38              
39             our $GlobalInplace; ## no critic (ProhibitPackageVars)
40             sub import {
41 12     12   176262 my @mine;
42 12         52 for my $i (reverse 1..$#_)
43 11 100       105 { unshift @mine, splice @_, $i, 1 if $_[$i]=~/^-i|^-D$/ }
44 12 100 100     71 if ( @mine and my @i = grep {/^-i/} @mine ) {
  5         42  
45 2 100       407 croak "$_[0]: can't specify more than one -i switch" if @i>1;
46             # the following double-check is currently just paranoia, so ignore it in code coverage:
47             # uncoverable branch true
48 1 50       21 my ($ext) = $i[0]=~/^-i(.*)$/ or croak "failed to parse '$i[0]'";
49 1         9 my $debug = grep {/^-D$/} @mine;
  1         10  
50 1         42 $GlobalInplace = File::Replace::Inplace->new(backup=>$ext, debug=>$debug);
51             }
52 11         813 goto &Exporter::import;
53             }
54              
55 9     9 1 18774 sub inplace { return File::Replace::Inplace->new(@_) }
56              
57             our $DISABLE_CHMOD;
58              
59             my %NEW_KNOWN_OPTS = map {$_=>1} qw/ debug layers create chmod
60             perms autocancel autofinish in_fh backup /;
61             sub new { ## no critic (ProhibitExcessComplexity)
62 164     164 1 210065 my $class = shift;
63 164 100       1083 @_ or croak "$class->new: not enough arguments";
64             # set up the object
65 161         273 my $filename = shift;
66 161 100       416 my $_layers = @_%2 ? shift : undef;
67 161         450 my %opts = @_;
68 161         487 for (keys %opts) { croak "$class->new: unknown option '$_'"
69 147 100       4009 unless $NEW_KNOWN_OPTS{$_} }
70             croak "$class->new: can't use autocancel and autofinish at once"
71 137 100 100     561 if $opts{autocancel} && $opts{autofinish};
72 136 100       382 unless (defined wantarray) { warnings::warnif("Useless use of $class->new in void context"); return }
  1         295  
  1         13  
73 135 100       323 if (defined $opts{create}) { # normalize 'create' values
74 17 100 100     134 if ( $opts{create} eq 'off' || $opts{create} eq 'no' )
    100 100        
75 4         22 { $opts{create} = 'off' }
76             elsif ( $opts{create} eq 'now' || $opts{create} eq 'later' )
77             { } # nothing needed
78 3         476 else { croak "bad value for 'create' option, must be one of off/no/later/now" }
79             }
80 118         262 else { $opts{create} = 'later' } # default
81             # create the object
82 132         615 my $self = bless { chmod=>!$DISABLE_CHMOD, %opts, is_open=>0 }, $class;
83 132 100 100     448 $self->{debug} = \*STDERR if $self->{debug} && !ref($self->{debug});
84 132 100       293 if (defined $_layers) {
85 7 100       211 exists $self->{layers} and croak "$class->new: layers specified twice";
86 6         17 $self->{layers} = $_layers }
87 131         719 lock_ref_keys $self, keys %NEW_KNOWN_OPTS, qw/ ifn ifh ofn ofh is_open setperms /;
88             # note: "perms" is the option the user explicitly sets and that options()
89             # needs to return, "setperms" is what finish() will actually set
90 131 100       13988 $self->{setperms} = $self->{perms} if defined $self->{perms};
91             # temporary output file
92 131         2659 my ($basename,$path) = fileparse($filename);
93 131         679 ($self->{ofh}, $self->{ofn}) = tempfile( # croaks on error
94             ".${basename}_XXXXXXXXXX", DIR=>$path, SUFFIX=>'.tmp', UNLINK=>1 );
95 131 100       46756 binmode $self->{ofh}, $self->{layers} if defined $self->{layers};
96             # input file
97             #TODO Later: A "noopen" option where the input file just isn't opened?
98 131 100       421 my $openmode = defined $self->{layers} ? '<'.$self->{layers} : '<';
99 131 100       3935 if ( defined $self->{in_fh} ) {
    100          
100 5 100       187 croak "in_fh appears to be closed" unless defined fileno($self->{in_fh});
101 4         21 $self->{ifh} = delete $self->{in_fh};
102             }
103             elsif ( not open $self->{ifh}, $openmode, $filename ) {
104             # No such file or directory:
105 44 100 100     437 if ( $!{ENOENT} && ($self->{create} eq 'now' || $self->{create} eq 'later') ) {
      66        
106 41 100       989 $self->{create} eq 'now' and $openmode = defined $self->{layers} ? '+>'.$self->{layers} : '+>';
    100          
107             # note we call &devnull() like this because otherwise it would
108             # be inlined and we want to be able to mock it for testing
109 41 100       1342 if ( open $self->{ifh}, $openmode, $self->{create} eq 'now' ? $filename : &devnull() )
    50          
110 41 100       455 { $self->{setperms}=oct('666')&~umask unless defined $self->{setperms} }
111 0         0 else { $self->{ifh}=undef }
112 3         86 } else { $self->{ifh}=undef }
113             }
114 130 100       567 if ( !defined $self->{ifh} ) {
115 3         11 my $e=$!;
116 3         34 close $self->{ofh}; $self->{ofh} = undef;
  3         14  
117 3         116 unlink $self->{ofn}; $self->{ofn} = undef;
  3         24  
118 3         10 $!=$e; ## no critic (RequireLocalizedPunctuationVars)
119 3         907 croak "$class->new: failed to open '$filename': $!" }
120             else {
121 127 100       358 if (!defined $self->{setperms}) {
122 83 100       252 if ($self->{chmod}) {
123             # we're providing our own error, don't need the extra warning
124 8     8   7900 no warnings 'unopened'; ## no critic (ProhibitNoWarnings)
  8         21  
  8         16278  
125             my (undef,undef,$mode) = stat($self->{ifh})
126 81 100       1026 or croak "stat failed: $!";
127 80         468 $self->{setperms} = S_IMODE($mode);
128             }
129 2         6 else { $self->{setperms}=0 }
130             }
131             }
132 126         268 $self->{ifn} = $filename;
133             # backup
134 126         221 my $debug_backup='';
135 126 100 100     391 if (defined($self->{backup}) && length($self->{backup})) {
136 5         17 my $bakfile = $filename . $self->{backup};
137 5 100       21 if ( $self->{backup}=~/\*/ ) {
138 1         6 ($bakfile = $self->{backup}) =~ s/\*/$basename/;
139 1         3 $bakfile = $path.$bakfile;
140             }
141 5 100       260 croak "backup failed: file '$bakfile' exists" if -e $bakfile;
142             #TODO Later: Maybe a backup_link option that uses hard links instead of copy?
143 4 100       25 File::Copy::syscopy($filename, $bakfile)
144             or croak "backup failed: couldn't copy '$filename' to '$bakfile': $!";
145 3         936 $debug_backup = ', backup to \''.$bakfile."'";
146             }
147             # finish init
148 124         232 $self->{is_open} = 1;
149             $self->_debug("$class->new: input '", $self->{ifn},
150             "', output '", $self->{ofn}, "', layers ",
151 124 100       834 (defined $self->{layers} ? "'".$self->{layers}."'" : 'undef'),
152             $debug_backup, "\n");
153 124         653 return $self;
154             }
155              
156             sub replace3 {
157 4 100   4 1 12748 unless (defined wantarray) { warnings::warnif("Useless use of "
  1         216  
158 1         9 .__PACKAGE__."::replace3 in void context"); return }
159 3         20 my $repl = __PACKAGE__->new(@_);
160 3         11 return ($repl->in_fh, $repl->out_fh, $repl);
161             }
162              
163             sub replace2 {
164 21 100   21 1 34441 unless (defined wantarray) { warnings::warnif("Useless use of "
  1         271  
165 1         9 .__PACKAGE__."::replace2 in void context"); return }
166 20         127 my $repl = __PACKAGE__->new(@_);
167 18 100       46 if (wantarray) {
168             return (
169 15         125 File::Replace::SingleHandle->new($repl, 'in'),
170             File::Replace::SingleHandle->new($repl, 'out') );
171             }
172             else {
173 3         22 return File::Replace::SingleHandle->new($repl, 'onlyout');
174             }
175             }
176              
177             sub replace {
178 17 100   17 1 28881 unless (defined wantarray) { warnings::warnif("Useless use of "
  1         269  
179 1         10 .__PACKAGE__."::replace in void context"); return }
180 16         97 my $repl = __PACKAGE__->new(@_);
181 14         110 return File::Replace::DualHandle->new($repl);
182             }
183              
184 2     2 0 25 sub is_open { return !!shift->{is_open} }
185 1     1 0 768 sub filename { return shift->{ifn} }
186 126     126 1 2574 sub in_fh { return shift->{ifh} }
187 122     122 0 2251 sub out_fh { return shift->{ofh} }
188              
189             sub options {
190 7     7 0 28 my $self = shift;
191 7         12 my %opts;
192 7         42 for my $o (keys %NEW_KNOWN_OPTS)
193 63 100       131 { exists $self->{$o} and $opts{$o} = $self->{$o} }
194 7 100       64 return wantarray ? %opts : \%opts;
195             }
196              
197             our $COPY_DEFAULT_BUFSIZE = 4096;
198             my %COPY_KNOWN_OPTS = map {$_=>1} qw/ count bufsize less /;
199             sub copy { ## no critic (ProhibitExcessComplexity)
200 23     23 1 9158 my $self = shift;
201 23 100       186 croak ref($self)."->copy: already closed" unless $self->{is_open};
202 22 100       61 my $_count = @_%2 ? shift : undef;
203 22         67 my %opts = @_;
204 22 100       47 if (defined $_count) {
205 14 100       147 exists $opts{count} and croak ref($self)."->copy: count specified twice";
206 13         28 $opts{count} = $_count }
207 21         51 for (keys %opts) { croak ref($self)."->copy: unknown option '$_'"
208 32 100       201 unless $COPY_KNOWN_OPTS{$_} }
209 20 100       53 $opts{bufsize} = $COPY_DEFAULT_BUFSIZE unless defined $opts{bufsize};
210 20 100 100     391 croak ref($self)."->copy: bad count" unless $opts{count} && $opts{count}=~/\A\d+\z/;
211 18 100 100     313 croak ref($self)."->copy: bad bufsize" unless $opts{bufsize} && $opts{bufsize}=~/\A\d+\z/;
212             croak ref($self)."->copy: bad less option" if defined $opts{less}
213 16 100 100     178 && $opts{less}!~/\A(?:ok|ignore)\z/;
214 15         27 my $remain = $opts{count};
215 15   100     131 while ( $remain>0 && !eof($self->{ifh}) ) {
216             my $in = read $self->{ifh}, my $buf,
217 15 100       73 $remain > $opts{bufsize} ? $opts{bufsize} : $remain;
218 15 100       217 defined $in or croak ref($self)."->copy: read failed: $!";
219 14 100       19 print {$self->{ofh}} $buf or croak ref($self)."->copy: write failed: $!";
  14         67  
220 13         42 $remain -= $in;
221             }
222             warnings::warnif(ref($self)."->copy: read $remain less characters than requested")
223 13 100 100     742 if $remain && !$opts{less};
224 12         128 return $opts{count}-$remain;
225             }
226              
227             sub finish {
228 102     102 0 7832 my $self = shift;
229 102 100       437 @_ and warnings::warnif(ref($self)."->finish: too many arguments");
230 102 100       314 if (!$self->{is_open}) {
231 4         734 warnings::warnif(ref($self)."->finish: already closed");
232 4         163 return }
233 98         159 my ($ifn,$ifh,$ofn,$ofh) = @{$self}{qw/ifn ifh ofn ofh/};
  98         307  
234 98         259 @{$self}{qw/ifh ofh ofn ifn is_open/} = (undef) x 5;
  98         220  
235             # Note we're being conservative here because if any of the steps fail,
236             # then it's fairly safe to assume the following steps will fail too.
237 98         151 my $fail;
238 98 100 100     18388 if ( defined(fileno($ifh)) && !close($ifh) ) ## no critic (ProhibitCascadingIfElse)
    100 100        
    50 66        
    50          
239 3         26 { $fail = "couldn't close input handle" }
240             elsif ( defined(fileno($ofh)) && !close($ofh) )
241 3         34 { $fail = "couldn't close output handle" }
242             elsif ( $self->{chmod} && !chmod($self->{setperms}, $ofn) )
243 0         0 { $fail = "couldn't chmod '$ofn'" }
244             elsif ( not rename($ofn, $ifn) )
245 0         0 { $fail = "couldn't rename '$ofn' to '$ifn'" }
246 98 100       578 if ( defined $fail ) {
247 6         42 my $e=$!; unlink($ofn); $!=$e; ## no critic (RequireLocalizedPunctuationVars)
  6         218  
  6         31  
248 6         763 croak ref($self)."->finish: $fail: $!";
249             }
250             $self->_debug(ref($self),"->finish: renamed '$ofn' to '$ifn', perms ",
251 92         1030 sprintf('%05o',$self->{setperms}), "\n");
252 92         440 return 1;
253             }
254              
255             sub _cancel {
256 153     153   230 my $self = shift;
257 153         230 my $from = shift;
258 153 100       404 if ($from eq 'destroy')
    100          
259             { $self->{is_open} and warnings::warnif(ref($self)
260 133 100       2062 .": unclosed file '".$self->{ifn}."' not replaced!") }
261             elsif ($from eq 'cancel')
262 18 100       606 { $self->{is_open} or warnings::warnif(ref($self)."->cancel: already closed") }
263 153 100 100     1035 if (!($from eq 'destroy' && !$self->{is_open}))
264             { $self->_debug(ref($self), "->cancel: not replacing input file ",
265             (defined $self->{ifn} ? "'$self->{ifn}'" : "(unknown)"),
266 28 100       227 (defined $self->{ofn} ? ", will attempt to unlink '$self->{ofn}'" : ""), "\n") }
    100          
267 153         319 my ($ifh,$ofh,$ofn) = @{$self}{qw/ifh ofh ofn/};
  153         447  
268 153         356 @{$self}{qw/ifh ofh ofn ifn is_open/} = (undef) x 5;
  153         330  
269 153         239 my $success = 1;
270 153 100 100     727 defined($ifh) and defined(fileno($ifh)) and close($ifh) or $success=0;
      100        
271 153 100 100     791 defined($ofh) and defined(fileno($ofh)) and close($ofh) or $success=0;
      100        
272 153 100       1481 defined($ofn) and unlink($ofn);
273 153 100       369 if ($success) { return 1 } else { return }
  23         140  
  130         269  
274             }
275              
276 18     18 0 2118 sub cancel { return shift->_cancel('cancel') }
277              
278             sub DESTROY {
279 133     133   17680 my $self = shift;
280 133 100       350 if ($self->{is_open}) {
281 22 100       75 if ($self->{autocancel}) { $self->cancel }
  10 100       36  
282 4         17 elsif ($self->{autofinish}) { $self->finish }
283             }
284 133         399 $self->_cancel('destroy');
285 133         964 return;
286             }
287              
288             sub _debug { ## no critic (RequireArgUnpacking)
289 288     288   544 my $self = shift;
290 288 100       889 return 1 unless $self->{debug};
291 10 100       185 confess "not enough arguments to _debug" unless @_;
292 9         33 local ($",$,,$\) = (' ');
293 9         12 return print {$self->{debug}} @_;
  9         266  
294             }
295              
296             1;
297             __END__