File Coverage

blib/lib/File/Edit/Portable.pm
Criterion Covered Total %
statement 233 233 100.0
branch 88 102 86.2
condition 25 27 92.5
subroutine 27 27 100.0
pod 8 8 100.0
total 381 397 95.9


line stmt bran cond sub pod time code
1             package File::Edit::Portable;
2 17     17   1202812 use 5.008;
  17         209  
3 17     17   97 use strict;
  17         29  
  17         413  
4 17     17   96 use warnings;
  17         56  
  17         1457  
5              
6             $SIG{__WARN__} = sub { confess(shift); };
7             our $VERSION = '1.25';
8              
9 17     17   115 use Carp qw(confess croak);
  17         33  
  17         1118  
10 17     17   140 use Exporter;
  17         30  
  17         740  
11 17     17   108 use Fcntl qw(:flock);
  17         40  
  17         2449  
12 17     17   10058 use File::Find::Rule;
  17         144203  
  17         128  
13 17     17   8116 use File::Temp;
  17         190742  
  17         1347  
14 17     17   8982 use POSIX qw(uname);
  17         113364  
  17         124  
15              
16             our @ISA = qw(Exporter);
17             our @EXPORT_OK = qw(
18             recsep
19             platform_recsep
20             );
21              
22             sub new {
23 60     60 1 53449 return bless {}, shift;
24             }
25             sub read {
26 112     112 1 32963 my $self = shift;
27 112         217 my ($file, $testing);
28              
29 112 100       291 if ($_[0] eq 'file'){
30 8         23 $self->_config(@_);
31             }
32             else {
33 104         210 $file = shift;
34 104 100       373 $testing = shift if @_;
35 104         280 $self->_config(file => $file, testing => $testing);
36             }
37              
38 112         229 $file = $self->{file};
39 112         186 $testing = $self->{testing};
40              
41 112 100       264 if (! $file){
42 1         133 confess "read() requires a file name sent in!";
43             }
44              
45 111         323 $self->recsep($file);
46 111         459 $self->{files}{$file}{is_read} = 1;
47 111         271 $self->{files}{$file}{recsep} = $self->{recsep};
48 111         179 $self->{reads}{count} = keys %{ $self->{files} };
  111         357  
49              
50 111         195 my $fh;
51              
52 111 100       292 if (! wantarray){
53 54         147 $fh = $self->_handle($file);
54 54         1758 return $fh;
55             }
56             else {
57 57         149 $fh = $self->_binmode_handle($file);
58 57         1086 my @contents = <$fh>;
59 57 50       637 close $fh or confess "read() can't close file $file!: $!";
60              
61 57 100       166 if (! $testing){
62 55         124 for (@contents){
63 474         882 $_ = $self->_strip_ends($_);
64             }
65             }
66 57         455 return @contents;
67             }
68             }
69             sub write {
70 76     76 1 14842 my $self = shift;
71 76         316 my %p = @_;
72 76         314 $self->_config(%p);
73              
74 76 100       1429 if (! $self->{file}){
75 2         446 confess "write() requires a file to be passed in!";
76             }
77              
78 74   100     244 my $reads_count = $self->{reads}{count} || 0;
79              
80 74 100 100     283 if ($reads_count > 1 && ! $p{file}){
81             confess "\nif calling write() with more than one read() open, you must " .
82             "send in a file name with the 'file' parameter so we know " .
83             "which file to write. You currently have the following files " .
84 2         5 "open: " . join(' ', keys %{ $self->{files} }) . "\n";
  2         279  
85             }
86 72 100       216 if (! $self->{contents}){
87 1         104 confess "write() requires 'contents' param sent in";
88             }
89              
90 71         277 my $file = $self->{file}; # needed for cleanup of open file list
91              
92 71 100       204 if (! $self->{files}{$file}{is_read}){
93 6         20 $self->{files}{$file}{recsep} = $self->recsep($file);
94             }
95              
96 71 100       216 $self->{file} = $self->{copy} if $self->{copy};
97              
98 71         185 my $wfh = $self->_binmode_handle($self->{file}, 'w');
99              
100             # certain FreeBSD versions on amd64 don't work
101             # with flock()
102              
103 71         721 my @os = uname();
104              
105 71 50 33     316 unless ($os[0] eq 'FreeBSD' && $os[-1] eq 'amd64'){
106 71         667 flock $wfh, LOCK_EX;
107             }
108              
109             my $recsep = defined $self->{custom_recsep}
110             ? $self->{custom_recsep}
111 71 100       333 : $self->{files}{$file}{recsep};
112              
113 71         127 my $contents = $self->{contents};
114              
115 71 100 100     372 if (ref($contents) eq 'GLOB' || ref($contents) eq 'File::Temp') {
116             {
117 49         84 my $warn;
  49         69  
118 49     1   417 local $SIG{__WARN__} = sub { $warn = shift; };
  1         6  
119              
120 49         835 seek $contents, 0, 0;
121              
122 49 100       381 if ($warn) {
123 1         201 confess "\nthe file handle you're passing into write() as ".
124             "the contents param has already been closed\n";
125             }
126             };
127              
128 48         564 while (<$contents>){
129 221         510 $_ = $self->_strip_ends($_);
130 221         1275 print $wfh $_ . $recsep;
131             }
132 48         608 close $contents;
133             }
134             else {
135 22         62 for (@$contents){
136 250         426 $_ = $self->_strip_ends($_);
137 250         645 print $wfh $_ . $recsep;
138             }
139             }
140              
141 70         4203 close $wfh;
142 70         466 delete $self->{files}{$file}; # cleanup open list
143 70 100       121 $self->{reads}{count} = 0 if keys %{ $self->{files} } == 0;
  70         338  
144              
145 70         454 return 1;
146             }
147             sub splice {
148 17     17 1 135 my $self = shift;
149 17         57 $self->_config(@_);
150              
151 17         35 my $file = $self->{file};
152 17         31 my $copy = $self->{copy};
153 17         24 my $insert = $self->{insert};
154 17 100       49 my $limit = defined $self->{limit} ? $self->{limit} : 1;
155              
156 17 100       45 if (! $insert){
157 1         224 confess "splice() requires insert => [aref] param";
158             }
159              
160 16         47 my ($line, $find) = ($self->{line}, $self->{find});
161              
162 16 100 100     99 if (! defined $line && ! defined $find){
163 1         182 confess
164             "splice() requires either the 'line' or 'find' parameter sent in.";
165             }
166              
167 15 100 100     54 if (defined $line && defined $find){
168 1         17 warn
169             "splice() can't search for both line and find. Operating on 'line'.";
170             }
171              
172 15         50 my @contents = $self->read($file);
173              
174 14 100       43 if (defined $line){
175 4 100       20 if ($line !~ /^[0-9]+$/){
176 1         107 confess "splice() requires its 'line' param to contain only an " .
177             "integer. You supplied: $line\n";
178             }
179 3         14 splice @contents, $line, 0, @$insert;
180             }
181              
182 13 100 100     65 if (defined $find && ! defined $line){
183 10 50       92 $find = qr{$find} if ! ref $find ne 'Regexp';
184              
185 10         23 my $i = 0;
186 10         19 my $inserts = 0;
187              
188 10         25 for (@contents){
189 75         102 $i++;
190 75 100       214 if (/$find/){
191 21         37 $inserts++;
192 21         62 splice @contents, $i, 0, @$insert;
193 21 100       42 if ($limit){
194 17 100       42 last if $inserts == $limit;
195             }
196             }
197             }
198             }
199              
200 13         52 $self->write(contents => \@contents, copy => $copy);
201              
202 13         107 return @contents;
203             }
204             sub dir {
205 18     18 1 11136 my $self = shift;
206 18         70 $self->_config(@_);
207              
208 18         36 my $recsep = $self->{custom_recsep};
209              
210 18         27 my @types;
211              
212 18 100       49 if ($self->{types}){
213 8         11 @types = @{ $self->{types} };
  8         22  
214             }
215             else {
216 10         24 @types = qw(*);
217             }
218              
219 18         148 my $find = File::Find::Rule->new;
220            
221 18 100       237 $find->maxdepth($self->{maxdepth}) if $self->{maxdepth};
222 18         535 $find->file;
223 18         609 $find->name(@types);
224              
225 18         2066 my @files = $find->in($self->{dir});
226              
227 18 100       18298 return @files if $self->{list};
228              
229 10         29 for my $file (@files){
230              
231 23         68 my $fh = $self->read($file);
232 23         67 my $wfh = $self->tempfile;
233              
234 23         325 while(<$fh>){
235 69         406 print $wfh $_;
236             }
237 23         322 close $fh;
238              
239 23 100       127 $self->write(
240             file => $file,
241             contents => $wfh,
242             recsep => defined $recsep
243             ? $recsep
244             : $self->platform_recsep,
245             );
246             }
247              
248 10         95 return @files;
249             }
250             sub recsep {
251 226 100   226 1 16207 my $self = ref $_[0] eq __PACKAGE__
252             ? shift
253             : __PACKAGE__->new;
254              
255 226         372 my $file = shift;
256 226         313 my $want = shift;
257              
258 226         328 my $fh;
259 226         375 eval {
260 226         462 $fh = $self->_binmode_handle($file);
261             };
262              
263 226 100 100     3444 if ($@ || -z $fh){
264              
265             # we've got an empty file...
266             # we'll set recsep to the local platform's
267              
268 9         34 $self->{recsep} = $self->platform_recsep;
269              
270             return $want
271             ? $self->_convert_recsep($self->{recsep}, $want)
272 9 100       56 : $self->{recsep};
273             }
274              
275 217         1655 seek $fh, 0, 0;
276              
277 217         762 my $recsep_regex = $self->_recsep_regex;
278              
279 217 50       4400 if (<$fh> =~ /$recsep_regex/){
280 217         954 $self->{recsep} = $1;
281             }
282              
283 217 50       2855 close $fh or confess "recsep() can't close file $file!: $!";
284              
285             return $want
286             ? $self->_convert_recsep($self->{recsep}, $want)
287 217 100       1207 : $self->{recsep};
288             }
289             sub platform_recsep {
290 183 50   183 1 1662 my $self = ref $_[0] eq __PACKAGE__
291             ? shift
292             : __PACKAGE__->new;
293              
294 183         286 my $want = shift;
295              
296             # for platform_recsep(), we need the file open in ASCII mode,
297             # so we can't use _binmode_handle() or File::Temp
298              
299 183         381 my $file = $self->_temp_filename;
300              
301 183 50       49092 open my $wfh, '>', $file
302             or confess
303             "platform_recsep() can't open temp file $file for writing!: $!";
304              
305 183         1678 print $wfh "abc\n";
306              
307 183 50       5133 close $wfh
308             or confess "platform_recsep() can't close write temp file $file: $!";
309              
310 183         787 my $fh = $self->_binmode_handle($file);
311              
312 183         551 my $recsep_regex = $self->_recsep_regex;
313              
314 183 50       3140 if (<$fh> =~ /$recsep_regex/){
315 183         794 $self->{platform_recsep} = $1;
316             }
317              
318 183 50       1707 close $fh
319             or confess "platform_recsep() can't close temp file $file after run: $!";
320              
321 183 50       7399 unlink $file or confess "Can't unlink temp file '$file': $!";
322              
323             return $want
324             ? $self->_convert_recsep($self->{platform_recsep}, $want)
325 183 100       1770 : $self->{platform_recsep};
326             }
327             sub tempfile {
328             # returns a temporary file handle in write mode
329              
330 41     41 1 246 my $wfh = File::Temp->new(UNLINK => 1);
331 41         14476 return $wfh;
332             }
333             sub _config {
334             # configures self with incoming params
335              
336 223     223   348 my $self = shift;
337 223         739 my %p = @_;
338              
339 223         555 $self->{custom_recsep} = $p{recsep};
340 223         382 delete $p{recsep};
341              
342 223         626 my @params = qw(
343             testing copy types list maxdepth
344             insert line find limit
345             );
346              
347 223         453 for (@params){
348 2007         2972 delete $self->{$_};
349             }
350            
351 223         563 for (keys %p){
352 489         2528 $self->{$_} = $p{$_};
353             }
354             }
355             sub _handle {
356             # returns a handle with platform's record separator
357              
358 54     54   100 my $self = shift;
359 54         81 my $file = shift;
360            
361 54         81 my $fh;
362              
363 54 100       118 if ($self->recsep($file, 'hex') ne $self->platform_recsep('hex')){
364            
365 16         49 $fh = $self->_binmode_handle($file);
366 16         55 my $temp_wfh = $self->tempfile;
367 16         77 binmode $temp_wfh, ':raw';
368              
369 16         67 my $temp_filename = $temp_wfh->filename;
370              
371 16         321 while (<$fh>){
372 92         285 $_ = $self->_platform_replace($_);
373 92         608 print $temp_wfh $_;
374             }
375            
376 16 50       168 close $fh or confess "can't close file $file: $!";
377 16 50       417 close $temp_wfh or confess "can't close file $temp_filename: $!";
378              
379 16         73 my $ret_fh = $self->_binmode_handle($temp_filename);
380            
381 16         88 return $ret_fh;
382             }
383             else {
384 38         145 $fh = $self->_binmode_handle($file);
385 38         116 return $fh;
386             }
387             }
388             sub _binmode_handle {
389             # returns a handle opened with binmode :raw
390              
391 608     608   1080 my $self = shift;
392 608         951 my $file = shift;
393 608   100     2004 my $mode = shift || 'r';
394              
395 608         860 my $fh;
396              
397 608 100       1681 if ($mode =~ /^w/){
398 71 50       5646 open $fh, '>', $file
399             or confess "_binmode_handle() can't open file $file for writing!: $!";
400             }
401             else {
402 537 100       19763 open $fh, '<', $file
403             or confess "_binmode_handle() can't open file $file for reading!: $!";
404             }
405              
406 601         3571 binmode $fh, ':raw';
407              
408 601         1635 return $fh;
409             }
410             sub _convert_recsep {
411             # converts recsep to either hex or OS name (ie. type)
412              
413 175     175   4811 my ($self, $sep, $want) = @_;
414              
415 175         648 $sep = unpack "H*", $sep;
416 175         761 $sep =~ s/0/\\0/g;
417              
418 175 100       1240 return $sep if $want eq 'hex';
419              
420 23         102 my %seps = (
421             '\0a' => 'nix',
422             '\0d\0a' => 'win',
423             '\0d' => 'mac',
424             );
425              
426 23   100     210 return $seps{$sep} || 'unknown';
427             }
428             sub _recsep_regex {
429             # returns a regex object representing all recseps
430 1437     1437   4705 return qr/([\n\x{0B}\f\r\x{85}]{1,2})/;
431             }
432             sub _platform_replace {
433             # replace recseps in a string with the platform recsep
434              
435 92     92   218 my ($self, $str) = @_;
436 92         195 my $re = $self->_recsep_regex;
437 92         625 $str =~ s/$re/$self->platform_recsep/ge;
  98         222  
438 92         351 return $str;
439             }
440             sub _strip_ends {
441             # strip all line endings from string
442              
443 945     945   1633 my ($self, $str) = @_;
444 945         1560 my $re = $self->_recsep_regex;
445 945         3457 $str =~ s/$re//g;
446 945         2448 return $str;
447             }
448             sub _temp_filename {
449             # return the name of a temporary file
450              
451 186     186   4288 my $temp_fh = File::Temp->new(UNLINK => 1);
452 186         68656 my $filename = $temp_fh->filename;
453 186         1445 return $filename;
454             }
455 1     1   10 sub _vim_placeholder { return 1; }; # for folding
456              
457             1;
458             __END__