File Coverage

blib/lib/File/Replace.pm
Criterion Covered Total %
statement 192 220 87.7
branch 142 158 91.1
condition 57 66 89.3
subroutine 26 30 86.6
pod 7 13 53.8
total 424 487 88.0


line stmt bran cond sub pod time code
1             #!perl
2             package File::Replace;
3 5     5   694666 use warnings;
  5         26  
  5         171  
4 5     5   30 use strict;
  5         11  
  5         95  
5 5     5   23 use Carp;
  5         10  
  5         270  
6 5     5   31 use warnings::register;
  5         21  
  5         613  
7 5     5   36 use IO::Handle; # allow method calls on filehandles on older Perls
  5         10  
  5         330  
8 5     5   30 use File::Temp qw/tempfile/;
  5         18  
  5         234  
9 5     5   27 use File::Basename qw/fileparse/;
  5         15  
  5         384  
10 5     5   1874 use File::Spec::Functions qw/devnull/;
  5         3735  
  5         290  
11 5     5   2412 use File::Copy ();
  5         12242  
  5         151  
12 5     5   42 use Fcntl qw/S_IMODE/;
  5         11  
  5         233  
13 5     5   27 use Exporter ();
  5         11  
  5         341  
14             BEGIN {
15 5     5   2697 require Hash::Util;
16             # apparently this wasn't available until 0.06 / Perl 5.8.9
17             # since this is just for internal typo prevention,
18             # we can fake it when it's not available
19             # uncoverable branch false
20             # uncoverable condition right
21             # uncoverable condition false
22 5 50 33     15298 if ($] ge '5.010' || defined &Hash::Util::lock_ref_keys)
23 5         34 { Hash::Util->import('lock_ref_keys') }
24 0         0 else { *lock_ref_keys = sub {} } # uncoverable statement
25             }
26              
27             # For AUTHOR, COPYRIGHT, AND LICENSE see the bottom of this file
28              
29             ## no critic (RequireArgUnpacking)
30              
31             our $VERSION = '0.16';
32              
33             our @EXPORT_OK = qw/ replace replace2 replace3 inplace /;
34             our @CARP_NOT = qw/ File::Replace::SingleHandle File::Replace::DualHandle File::Replace::Inplace /;
35              
36             sub import {
37 5     5   57 my @mine;
38 5         17 for my $i (reverse 1..$#_)
39 1 50       12 { unshift @mine, splice @_, $i, 1 if $_[$i]=~/^-i|^-D$/ }
40 5 50 33     30 if ( @mine and my @i = grep {/^-i/} @mine ) {
  0         0  
41 0 0       0 croak "$_[0]: can't specify more than one -i switch" if @i>1;
42             # the following double-check is currently just paranoia, so ignore it in code coverage:
43             # uncoverable branch true
44 0 0       0 my ($ext) = $i[0]=~/^-i(.*)$/ or croak "failed to parse '$i[0]'";
45 0         0 my $debug = grep {/^-D$/} @mine;
  0         0  
46 0         0 require File::Replace::Inplace;
47 0         0 $File::Replace::Inplace::GlobalInplace = File::Replace::Inplace->new(backup=>$ext, debug=>$debug); ## no critic (ProhibitPackageVars)
48             }
49 5         607 goto &Exporter::import;
50             }
51              
52             sub inplace {
53 0     0 1 0 require File::Replace::Inplace;
54 0         0 return File::Replace::Inplace->new(@_);
55             }
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 79     79 1 211574 my $class = shift;
63 79 100       455 @_ or croak "$class->new: not enough arguments";
64             # set up the object
65 78         133 my $filename = shift;
66 78 100       261 my $_layers = @_%2 ? shift : undef;
67 78         193 my %opts = @_;
68 78         247 for (keys %opts) { croak "$class->new: unknown option '$_'"
69 76 100       3205 unless $NEW_KNOWN_OPTS{$_} }
70             croak "$class->new: can't use autocancel and autofinish at once"
71 56 100 100     336 if $opts{autocancel} && $opts{autofinish};
72 55 100       139 unless (defined wantarray) { warnings::warnif("Useless use of $class->new in void context"); return }
  1         206  
  1         9  
73 54 100       112 if (defined $opts{create}) { # normalize 'create' values
74 9 100 100     67 if ( $opts{create} eq 'off' || $opts{create} eq 'no' )
    100 100        
75 3         10 { $opts{create} = 'off' }
76             elsif ( $opts{create} eq 'now' || $opts{create} eq 'later' )
77             { } # nothing needed
78 3         500 else { croak "bad value for 'create' option, must be one of off/no/later/now" }
79             }
80 45         97 else { $opts{create} = 'later' } # default
81             # create the object
82 51         239 my $self = bless { chmod=>!$DISABLE_CHMOD, %opts, is_open=>0 }, $class;
83 51 100 100     198 $self->{debug} = \*STDERR if $self->{debug} && !ref($self->{debug});
84 51 100       118 if (defined $_layers) {
85 6 100       140 exists $self->{layers} and croak "$class->new: layers specified twice";
86 5         12 $self->{layers} = $_layers }
87 50         265 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 50 100       5188 $self->{setperms} = $self->{perms} if defined $self->{perms};
91             # temporary output file
92 50         945 my ($basename,$path) = fileparse($filename);
93 50         243 ($self->{ofh}, $self->{ofn}) = tempfile( # croaks on error
94             ".${basename}_XXXXXXXXXX", DIR=>$path, SUFFIX=>'.tmp', UNLINK=>1 );
95 50 100       22272 binmode $self->{ofh}, $self->{layers} if defined $self->{layers};
96             # input file
97             # Possible To-Do for Later: A "noopen" option where the input file just isn't opened?
98 50 100       163 my $openmode = defined $self->{layers} ? '<'.$self->{layers} : '<';
99 50 100       1462 if ( defined $self->{in_fh} ) {
    100          
100 5 100       256 croak "in_fh appears to be closed" unless defined fileno($self->{in_fh});
101 4         22 $self->{ifh} = delete $self->{in_fh};
102             }
103             elsif ( not open $self->{ifh}, $openmode, $filename ) {
104             # No such file or directory:
105 25 100 100     238 if ( $!{ENOENT} && ($self->{create} eq 'now' || $self->{create} eq 'later') ) {
      66        
106 23 100       546 $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 23 100       881 if ( open $self->{ifh}, $openmode, $self->{create} eq 'now' ? $filename : &devnull() )
    50          
110 23 100       246 { $self->{setperms}=oct('666')&~umask unless defined $self->{setperms} }
111 0         0 else { $self->{ifh}=undef }
112 2         44 } else { $self->{ifh}=undef }
113             }
114 49 100       199 if ( !defined $self->{ifh} ) {
115 2         6 my $e=$!;
116 2         25 close $self->{ofh}; $self->{ofh} = undef;
  2         7  
117 2         73 unlink $self->{ofn}; $self->{ofn} = undef;
  2         8  
118 2         7 $!=$e; ## no critic (RequireLocalizedPunctuationVars)
119 2         362 croak "$class->new: failed to open '$filename': $!" }
120             else {
121 47 100       116 if (!defined $self->{setperms}) {
122 21 100       61 if ($self->{chmod}) {
123             # we're providing our own error, don't need the extra warning
124 5     5   4855 no warnings 'unopened'; ## no critic (ProhibitNoWarnings)
  5         13  
  5         10674  
125             my (undef,undef,$mode) = stat($self->{ifh})
126 19 100       337 or croak "stat failed: $!";
127 18         104 $self->{setperms} = S_IMODE($mode);
128             }
129 2         25 else { $self->{setperms}=0 }
130             }
131             }
132 46         100 $self->{ifn} = $filename;
133             # backup
134 46         72 my $debug_backup='';
135 46 100 100     136 if (defined($self->{backup}) && length($self->{backup})) {
136 4         12 my $bakfile = $filename . $self->{backup};
137 4 100       27 if ( $self->{backup}=~/\*/ ) {
138 1         5 ($bakfile = $self->{backup}) =~ s/\*/$basename/;
139 1         4 $bakfile = $path.$bakfile;
140             }
141 4 100       231 croak "backup failed: file '$bakfile' exists" if -e $bakfile;
142             # Possible To-Do for Later: Maybe a backup_link option that uses hard links instead of copy?
143 3 100       17 File::Copy::syscopy($filename, $bakfile)
144             or croak "backup failed: couldn't copy '$filename' to '$bakfile': $!";
145 2         731 $debug_backup = ', backup to \''.$bakfile."'";
146             }
147             # finish init
148 44         83 $self->{is_open} = 1;
149             $self->_debug("$class->new: input '", $self->{ifn},
150             "', output '", $self->{ofn}, "', layers ",
151 44 100       276 (defined $self->{layers} ? "'".$self->{layers}."'" : 'undef'),
152             $debug_backup, "\n");
153 44         233 return $self;
154             }
155              
156             sub replace3 {
157 4 100   4 1 13090 unless (defined wantarray) { warnings::warnif("Useless use of "
  1         179  
158 1         9 .__PACKAGE__."::replace3 in void context"); return }
159 3         31 my $repl = __PACKAGE__->new(@_);
160 3         13 return ($repl->in_fh, $repl->out_fh, $repl);
161             }
162              
163             sub replace2 {
164 0     0 1 0 require File::Replace::SingleHandle;
165 0 0       0 unless (defined wantarray) { warnings::warnif("Useless use of "
  0         0  
166 0         0 .__PACKAGE__."::replace2 in void context"); return }
167 0         0 my $repl = __PACKAGE__->new(@_);
168 0 0       0 if (wantarray) {
169             return (
170 0         0 File::Replace::SingleHandle->new($repl, 'in'),
171             File::Replace::SingleHandle->new($repl, 'out') );
172             }
173             else {
174 0         0 return File::Replace::SingleHandle->new($repl, 'onlyout');
175             }
176             }
177              
178             sub replace {
179 0     0 1 0 require File::Replace::DualHandle;
180 0 0       0 unless (defined wantarray) { warnings::warnif("Useless use of "
  0         0  
181 0         0 .__PACKAGE__."::replace in void context"); return }
182 0         0 my $repl = __PACKAGE__->new(@_);
183 0         0 return File::Replace::DualHandle->new($repl);
184             }
185              
186 0     0 0 0 sub is_open { return !!shift->{is_open} }
187 1     1 0 871 sub filename { return shift->{ifn} }
188 18     18 1 2195 sub in_fh { return shift->{ifh} }
189 20     20 0 2073 sub out_fh { return shift->{ofh} }
190              
191             sub options {
192 3     3 0 18 my $self = shift;
193 3         4 my %opts;
194 3         13 for my $o (keys %NEW_KNOWN_OPTS)
195 27 100       55 { exists $self->{$o} and $opts{$o} = $self->{$o} }
196 3 100       38 return wantarray ? %opts : \%opts;
197             }
198              
199             our $COPY_DEFAULT_BUFSIZE = 4096;
200             my %COPY_KNOWN_OPTS = map {$_=>1} qw/ count bufsize less /;
201             sub copy { ## no critic (ProhibitExcessComplexity)
202 23     23 1 9206 my $self = shift;
203 23 100       165 croak ref($self)."->copy: already closed" unless $self->{is_open};
204 22 100       415 my $_count = @_%2 ? shift : undef;
205 22         57 my %opts = @_;
206 22 100       48 if (defined $_count) {
207 14 100       140 exists $opts{count} and croak ref($self)."->copy: count specified twice";
208 13         22 $opts{count} = $_count }
209 21         54 for (keys %opts) { croak ref($self)."->copy: unknown option '$_'"
210 32 100       185 unless $COPY_KNOWN_OPTS{$_} }
211 20 100       52 $opts{bufsize} = $COPY_DEFAULT_BUFSIZE unless defined $opts{bufsize};
212 20 100 100     410 croak ref($self)."->copy: bad count" unless $opts{count} && $opts{count}=~/\A\d+\z/;
213 18 100 100     293 croak ref($self)."->copy: bad bufsize" unless $opts{bufsize} && $opts{bufsize}=~/\A\d+\z/;
214             croak ref($self)."->copy: bad less option" if defined $opts{less}
215 16 100 100     162 && $opts{less}!~/\A(?:ok|ignore)\z/;
216 15         24 my $remain = $opts{count};
217 15   100     136 while ( $remain>0 && !eof($self->{ifh}) ) {
218             my $in = read $self->{ifh}, my $buf,
219 15 100       156 $remain > $opts{bufsize} ? $opts{bufsize} : $remain;
220 15 100       160 defined $in or croak ref($self)."->copy: read failed: $!";
221 14 100       17 print {$self->{ofh}} $buf or croak ref($self)."->copy: write failed: $!";
  14         74  
222 13         46 $remain -= $in;
223             }
224             warnings::warnif(ref($self)."->copy: read $remain less characters than requested")
225 13 100 100     609 if $remain && !$opts{less};
226 12         121 return $opts{count}-$remain;
227             }
228              
229             sub finish {
230 32     32 0 8413 my $self = shift;
231 32 100       293 @_ and warnings::warnif(ref($self)."->finish: too many arguments");
232 32 100       122 if (!$self->{is_open}) {
233 1         167 warnings::warnif(ref($self)."->finish: already closed");
234 1         35 return }
235 31         58 my ($ifn,$ifh,$ofn,$ofh) = @{$self}{qw/ifn ifh ofn ofh/};
  31         94  
236 31         87 @{$self}{qw/ifh ofh ofn ifn is_open/} = (undef) x 5;
  31         68  
237             # Note we're being conservative here because if any of the steps fail,
238             # then it's fairly safe to assume the following steps will fail too.
239 31         56 my $fail;
240 31 100 66     3846 if ( defined(fileno($ifh)) && !close($ifh) ) ## no critic (ProhibitCascadingIfElse)
    100 66        
    50 66        
    50          
241 1         39 { $fail = "couldn't close input handle" }
242             elsif ( defined(fileno($ofh)) && !close($ofh) )
243 1         35 { $fail = "couldn't close output handle" }
244             elsif ( $self->{chmod} && !chmod($self->{setperms}, $ofn) )
245 0         0 { $fail = "couldn't chmod '$ofn'" }
246             elsif ( not rename($ofn, $ifn) )
247 0         0 { $fail = "couldn't rename '$ofn' to '$ifn'" }
248 31 100       273 if ( defined $fail ) {
249 2         12 my $e=$!; unlink($ofn); $!=$e; ## no critic (RequireLocalizedPunctuationVars)
  2         65  
  2         10  
250 2         221 croak ref($self)."->finish: $fail: $!";
251             }
252             $self->_debug(ref($self),"->finish: renamed '$ofn' to '$ifn', perms ",
253 29         318 sprintf('%05o',$self->{setperms}), "\n");
254 29         150 return 1;
255             }
256              
257             sub _cancel {
258 64     64   171 my $self = shift;
259 64         116 my $from = shift;
260 64 100       178 if ($from eq 'destroy')
    100          
261             { $self->{is_open} and warnings::warnif(ref($self)
262 51 100       560 .": unclosed file '".$self->{ifn}."' not replaced!") }
263             elsif ($from eq 'cancel')
264 11 100       586 { $self->{is_open} or warnings::warnif(ref($self)."->cancel: already closed") }
265 64 100 100     427 if (!($from eq 'destroy' && !$self->{is_open}))
266             { $self->_debug(ref($self), "->cancel: not replacing input file ",
267             (defined $self->{ifn} ? "'$self->{ifn}'" : "(unknown)"),
268 15 100       95 (defined $self->{ofn} ? ", will attempt to unlink '$self->{ofn}'" : ""), "\n") }
    100          
269 64         109 my ($ifh,$ofh,$ofn) = @{$self}{qw/ifh ofh ofn/};
  64         175  
270 64         158 @{$self}{qw/ifh ofh ofn ifn is_open/} = (undef) x 5;
  64         138  
271 64         97 my $success = 1;
272 64 100 100     368 defined($ifh) and defined(fileno($ifh)) and close($ifh) or $success=0;
      100        
273 64 100 66     492 defined($ofh) and defined(fileno($ofh)) and close($ofh) or $success=0;
      100        
274 64 100       845 defined($ofn) and unlink($ofn);
275 64 100       157 if ($success) { return 1 } else { return }
  13         101  
  51         122  
276             }
277              
278 11     11 0 2295 sub cancel { return shift->_cancel('cancel') }
279              
280             sub DESTROY {
281 51     51   20605 my $self = shift;
282 51 100       155 if ($self->{is_open}) {
283 7 100       29 if ($self->{autocancel}) { $self->cancel }
  3 100       47  
284 2         7 elsif ($self->{autofinish}) { $self->finish }
285             }
286 51         160 $self->_cancel('destroy');
287 51         480 return;
288             }
289              
290             sub _debug { ## no critic (RequireArgUnpacking)
291 89     89   177 my $self = shift;
292 89 100       256 return 1 unless $self->{debug};
293 10 100       188 confess "not enough arguments to _debug" unless @_;
294 9         42 local ($",$,,$\) = (' ');
295 9         14 return print {$self->{debug}} @_;
  9         285  
296             }
297              
298             1;