File Coverage

blib/lib/File/Edit/Portable.pm
Criterion Covered Total %
statement 233 233 100.0
branch 88 102 86.2
condition 24 27 88.8
subroutine 27 27 100.0
pod 8 8 100.0
total 380 397 95.7


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