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   1161860 use 5.008;
  17         223  
3 17     17   97 use strict;
  17         48  
  17         394  
4 17     17   84 use warnings;
  17         44  
  17         1466  
5              
6             local $SIG{__WARN__} = sub { confess(shift); };
7             our $VERSION = '1.26';
8              
9 17     17   138 use Carp qw(confess croak);
  17         32  
  17         1487  
10 17     17   123 use Exporter;
  17         35  
  17         800  
11 17     17   114 use Fcntl qw(:flock);
  17         54  
  17         2414  
12 17     17   9060 use File::Find::Rule;
  17         140692  
  17         121  
13 17     17   7503 use File::Temp;
  17         184832  
  17         1312  
14 17     17   8481 use POSIX qw(uname);
  17         109093  
  17         103  
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 52127 return bless {}, shift;
24             }
25             sub read {
26 112     112 1 32441 my $self = shift;
27 112         210 my ($file, $testing);
28              
29 112 100       293 if ($_[0] eq 'file'){
30 8         22 $self->_config(@_);
31             }
32             else {
33 104         208 $file = shift;
34 104 100       317 $testing = shift if @_;
35 104         307 $self->_config(file => $file, testing => $testing);
36             }
37              
38 112         230 $file = $self->{file};
39 112         183 $testing = $self->{testing};
40              
41 112 100       249 if (! $file){
42 1         115 confess "read() requires a file name sent in!";
43             }
44              
45 111         355 $self->recsep($file);
46 111         424 $self->{files}{$file}{is_read} = 1;
47 111         295 $self->{files}{$file}{recsep} = $self->{recsep};
48 111         172 $self->{reads}{count} = keys %{ $self->{files} };
  111         368  
49              
50 111         181 my $fh;
51              
52 111 100       266 if (! wantarray){
53 54         159 $fh = $self->_handle($file);
54 54         1810 return $fh;
55             }
56             else {
57 57         199 $fh = $self->_binmode_handle($file);
58 57         1199 my @contents = <$fh>;
59 57 50       743 close $fh or confess "read() can't close file $file!: $!";
60              
61 57 100       169 if (! $testing){
62 55         128 for (@contents){
63 474         869 $_ = $self->_strip_ends($_);
64             }
65             }
66 57         451 return @contents;
67             }
68             }
69             sub write {
70 76     76 1 14494 my $self = shift;
71 76         297 my %p = @_;
72 76         299 $self->_config(%p);
73              
74 76 100       1296 if (! $self->{file}){
75 2         439 confess "write() requires a file to be passed in!";
76             }
77              
78 74   100     226 my $reads_count = $self->{reads}{count} || 0;
79              
80 74 100 100     257 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         4 "open: " . join(' ', keys %{ $self->{files} }) . "\n";
  2         300  
85             }
86 72 100       226 if (! $self->{contents}){
87 1         101 confess "write() requires 'contents' param sent in";
88             }
89              
90 71         266 my $file = $self->{file}; # needed for cleanup of open file list
91              
92 71 100       263 if (! $self->{files}{$file}{is_read}){
93 6         17 $self->{files}{$file}{recsep} = $self->recsep($file);
94             }
95              
96 71 100       240 $self->{file} = $self->{copy} if $self->{copy};
97              
98 71         190 my $wfh = $self->_binmode_handle($self->{file}, 'w');
99              
100             # certain FreeBSD versions on amd64 don't work
101             # with flock()
102              
103 71         756 my @os = uname();
104              
105 71 50 33     309 unless ($os[0] eq 'FreeBSD' && $os[-1] eq 'amd64'){
106 71         681 flock $wfh, LOCK_EX;
107             }
108              
109             my $recsep = defined $self->{custom_recsep}
110             ? $self->{custom_recsep}
111 71 100       345 : $self->{files}{$file}{recsep};
112              
113 71         122 my $contents = $self->{contents};
114              
115 71 100 100     360 if (ref($contents) eq 'GLOB' || ref($contents) eq 'File::Temp') {
116             {
117 49         78 my $warn;
  49         88  
118 49     1   476 local $SIG{__WARN__} = sub { $warn = shift; };
  1         7  
119              
120 49         962 seek $contents, 0, 0;
121              
122 49 100       421 if ($warn) {
123 1         200 confess "\nthe file handle you're passing into write() as ".
124             "the contents param has already been closed\n";
125             }
126             };
127              
128 48         711 while (<$contents>){
129 221         580 $_ = $self->_strip_ends($_);
130 221         1413 print $wfh $_ . $recsep;
131             }
132 48         661 close $contents;
133             }
134             else {
135 22         57 for (@$contents){
136 250         447 $_ = $self->_strip_ends($_);
137 250         621 print $wfh $_ . $recsep;
138             }
139             }
140              
141 70         4815 close $wfh;
142 70         479 delete $self->{files}{$file}; # cleanup open list
143 70 100       126 $self->{reads}{count} = 0 if keys %{ $self->{files} } == 0;
  70         329  
144              
145 70         464 return 1;
146             }
147             sub splice {
148 17     17 1 128 my $self = shift;
149 17         53 $self->_config(@_);
150              
151 17         37 my $file = $self->{file};
152 17         23 my $copy = $self->{copy};
153 17         28 my $insert = $self->{insert};
154 17 100       46 my $limit = defined $self->{limit} ? $self->{limit} : 1;
155              
156 17 100       42 if (! $insert){
157 1         211 confess "splice() requires insert => [aref] param";
158             }
159              
160 16         35 my ($line, $find) = ($self->{line}, $self->{find});
161              
162 16 100 100     77 if (! defined $line && ! defined $find){
163 1         208 confess
164             "splice() requires either the 'line' or 'find' parameter sent in.";
165             }
166              
167 15 100 100     46 if (defined $line && defined $find){
168 1         19 warn
169             "splice() can't search for both line and find. Operating on 'line'.";
170             }
171              
172 15         41 my @contents = $self->read($file);
173              
174 14 100       40 if (defined $line){
175 4 100       22 if ($line !~ /^[0-9]+$/){
176 1         123 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     57 if (defined $find && ! defined $line){
183 10 50       81 $find = qr{$find} if ! ref $find ne 'Regexp';
184              
185 10         24 my $i = 0;
186 10         13 my $inserts = 0;
187              
188 10         27 for (@contents){
189 75         96 $i++;
190 75 100       259 if (/$find/){
191 21         32 $inserts++;
192 21         62 splice @contents, $i, 0, @$insert;
193 21 100       45 if ($limit){
194 17 100       43 last if $inserts == $limit;
195             }
196             }
197             }
198             }
199              
200 13         54 $self->write(contents => \@contents, copy => $copy);
201              
202 13         115 return @contents;
203             }
204             sub dir {
205 18     18 1 10862 my $self = shift;
206 18         74 $self->_config(@_);
207              
208 18         42 my $recsep = $self->{custom_recsep};
209              
210 18         29 my @types;
211              
212 18 100       52 if ($self->{types}){
213 8         12 @types = @{ $self->{types} };
  8         19  
214             }
215             else {
216 10         20 @types = qw(*);
217             }
218              
219 18         119 my $find = File::Find::Rule->new;
220              
221 18 100       229 $find->maxdepth($self->{maxdepth}) if $self->{maxdepth};
222 18         553 $find->file;
223 18         599 $find->name(@types);
224              
225 18         2047 my @files = $find->in($self->{dir});
226              
227 18 100       18021 return @files if $self->{list};
228              
229 10         32 for my $file (@files){
230              
231 23         72 my $fh = $self->read($file);
232 23         76 my $wfh = $self->tempfile;
233              
234 23         399 while(<$fh>){
235 69         470 print $wfh $_;
236             }
237 23         344 close $fh;
238              
239 23 100       139 $self->write(
240             file => $file,
241             contents => $wfh,
242             recsep => defined $recsep
243             ? $recsep
244             : $self->platform_recsep,
245             );
246             }
247              
248 10         175 return @files;
249             }
250             sub recsep {
251 226 100   226 1 14633 my $self = ref $_[0] eq __PACKAGE__
252             ? shift
253             : __PACKAGE__->new;
254              
255 226         346 my $file = shift;
256 226         355 my $want = shift;
257              
258 226         359 my $fh;
259 226         381 eval {
260 226         494 $fh = $self->_binmode_handle($file);
261             };
262              
263 226 100 100     3365 if ($@ || -z $fh){
264              
265             # we've got an empty file...
266             # we'll set recsep to the local platform's
267              
268 9         40 $self->{recsep} = $self->platform_recsep;
269              
270             return $want
271             ? $self->_convert_recsep($self->{recsep}, $want)
272 9 100       61 : $self->{recsep};
273             }
274              
275 217         1711 seek $fh, 0, 0;
276              
277 217         742 my $recsep_regex = $self->_recsep_regex;
278              
279 217 50       5045 if (<$fh> =~ /$recsep_regex/){
280 217         987 $self->{recsep} = $1;
281             }
282              
283 217 50       3234 close $fh or confess "recsep() can't close file $file!: $!";
284              
285             return $want
286             ? $self->_convert_recsep($self->{recsep}, $want)
287 217 100       1257 : $self->{recsep};
288             }
289             sub platform_recsep {
290 183 50   183 1 1351 my $self = ref $_[0] eq __PACKAGE__
291             ? shift
292             : __PACKAGE__->new;
293              
294 183         280 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         363 my $file = $self->_temp_filename;
300              
301 183 50       50686 open my $wfh, '>', $file
302             or confess
303             "platform_recsep() can't open temp file $file for writing!: $!";
304              
305 183         1676 print $wfh "abc\n";
306              
307 183 50       6114 close $wfh
308             or confess "platform_recsep() can't close write temp file $file: $!";
309              
310 183         826 my $fh = $self->_binmode_handle($file);
311              
312 183         487 my $recsep_regex = $self->_recsep_regex;
313              
314 183 50       3623 if (<$fh> =~ /$recsep_regex/){
315 183         859 $self->{platform_recsep} = $1;
316             }
317              
318 183 50       1855 close $fh
319             or confess "platform_recsep() can't close temp file $file after run: $!";
320              
321 183 50       8071 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       1833 : $self->{platform_recsep};
326             }
327             sub tempfile {
328             # returns a temporary file handle in write mode
329              
330 41     41 1 240 my $wfh = File::Temp->new(UNLINK => 1);
331 41         14219 return $wfh;
332             }
333             sub _config {
334             # configures self with incoming params
335              
336 223     223   358 my $self = shift;
337 223         727 my %p = @_;
338              
339 223         543 $self->{custom_recsep} = $p{recsep};
340 223         372 delete $p{recsep};
341              
342 223         648 my @params = qw(
343             testing copy types list maxdepth
344             insert line find limit
345             );
346              
347 223         462 for (@params){
348 2007         2901 delete $self->{$_};
349             }
350              
351 223         575 for (keys %p){
352 489         2612 $self->{$_} = $p{$_};
353             }
354             }
355             sub _handle {
356             # returns a handle with platform's record separator
357              
358 54     54   94 my $self = shift;
359 54         113 my $file = shift;
360              
361 54         84 my $fh;
362              
363 54 100       121 if ($self->recsep($file, 'hex') ne $self->platform_recsep('hex')){
364              
365 16         49 $fh = $self->_binmode_handle($file);
366 16         98 my $temp_wfh = $self->tempfile;
367 16         76 binmode $temp_wfh, ':raw';
368              
369 16         71 my $temp_filename = $temp_wfh->filename;
370              
371 16         346 while (<$fh>){
372 92         289 $_ = $self->_platform_replace($_);
373 92         666 print $temp_wfh $_;
374             }
375              
376 16 50       189 close $fh or confess "can't close file $file: $!";
377 16 50       522 close $temp_wfh or confess "can't close file $temp_filename: $!";
378              
379 16         87 my $ret_fh = $self->_binmode_handle($temp_filename);
380              
381 16         83 return $ret_fh;
382             }
383             else {
384 38         107 $fh = $self->_binmode_handle($file);
385 38         129 return $fh;
386             }
387             }
388             sub _binmode_handle {
389             # returns a handle opened with binmode :raw
390              
391 608     608   1035 my $self = shift;
392 608         929 my $file = shift;
393 608   100     2019 my $mode = shift || 'r';
394              
395 608         876 my $fh;
396              
397 608 100       1696 if ($mode =~ /^w/){
398 71 50       6347 open $fh, '>', $file
399             or confess "_binmode_handle() can't open file $file for writing!: $!";
400             }
401             else {
402 537 100       20649 open $fh, '<', $file
403             or confess "_binmode_handle() can't open file $file for reading!: $!";
404             }
405              
406 601         3653 binmode $fh, ':raw';
407              
408 601         1693 return $fh;
409             }
410             sub _convert_recsep {
411             # converts recsep to either hex or OS name (ie. type)
412              
413 175     175   4443 my ($self, $sep, $want) = @_;
414              
415 175         650 $sep = unpack "H*", $sep;
416 175         799 $sep =~ s/0/\\0/g;
417              
418 175 100       1223 return $sep if $want eq 'hex';
419              
420 23         123 my %seps = (
421             '\0a' => 'nix',
422             '\0d\0a' => 'win',
423             '\0d' => 'mac',
424             );
425              
426 23   100     216 return $seps{$sep} || 'unknown';
427             }
428             sub _recsep_regex {
429             # returns a regex object representing all recseps
430 1437     1437   4752 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   208 my ($self, $str) = @_;
436 92         183 my $re = $self->_recsep_regex;
437 92         644 $str =~ s/$re/$self->platform_recsep/ge;
  98         241  
438 92         345 return $str;
439             }
440             sub _strip_ends {
441             # strip all line endings from string
442              
443 945     945   1738 my ($self, $str) = @_;
444 945         1449 my $re = $self->_recsep_regex;
445 945         3485 $str =~ s/$re//g;
446 945         2435 return $str;
447             }
448             sub _temp_filename {
449             # return the name of a temporary file
450              
451 186     186   3922 my $temp_fh = File::Temp->new(UNLINK => 1);
452 186         67841 my $filename = $temp_fh->filename;
453 186         1477 return $filename;
454             }
455 1     1   10 sub _vim_placeholder { return 1; }; # for folding
456              
457             1;
458             __END__