File Coverage

lib/File/Util.pm
Criterion Covered Total %
statement 606 773 78.4
branch 381 698 54.5
condition 155 335 46.2
subroutine 58 68 85.2
pod 40 40 100.0
total 1240 1914 64.7


line stmt bran cond sub pod time code
1 21     21   530761 use 5.006;
  21         55  
2 21     21   72 use strict;
  21         26  
  21         388  
3 21     21   65 use warnings;
  21         26  
  21         547  
4              
5 21     21   62 use lib 'lib';
  21         19  
  21         75  
6              
7             package File::Util;
8             $File::Util::VERSION = '4.161950';
9 21     21   7083 use File::Util::Definitions qw( :all );
  21         32  
  21         4321  
10 21     21   5685 use File::Util::Interface::Modern qw( :all );
  21         33  
  21         2436  
11              
12 21     21   91 use Scalar::Util qw( blessed );
  21         18  
  21         601  
13 21     21   68 use Exporter;
  21         18  
  21         1819  
14              
15             our $AUTHORITY = 'cpan:TOMMY';
16             our @ISA = qw( Exporter );
17              
18             # some of the symbols below come from File::Util::Definitions
19             our @EXPORT_OK = qw(
20             NL can_flock ebcdic existent needs_binmode
21             SL strip_path is_readable is_writable valid_filename
22             OS bitmask return_path file_type escape_filename
23             is_bin created last_access last_changed last_modified
24             isbin split_path atomize_path diagnostic abort_depth
25             size can_read can_write read_limit can_utf8
26             default_path strict_path
27             );
28              
29             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ], diag => [ ] );
30              
31             our $WANT_DIAGNOSTICS = 0;
32              
33             # --------------------------------------------------------
34             # LEGACY methods (which get replaced in AUTOLOAD)
35             # --------------------------------------------------------
36 21     21   9519 use subs qw( can_read can_write isbin readlimit );
  21         374  
  21         78  
37              
38             # --------------------------------------------------------
39             # Constructor
40             # --------------------------------------------------------
41             sub new {
42 24     24 1 3298 my $this = { };
43              
44 24         58 bless $this, shift @_;
45              
46 24   50     115 my $in = $this->_parse_in( @_ ) || { };
47              
48 24   50     171 $this->{opts} = $in || { };
49              
50 24   100     134 $this->{opts}->{onfail} ||= 'die';
51              
52             # let constructor argument override globals, but set
53             # constructor opts to global values if they have not
54             # overridden them...
55              
56             $USE_FLOCK = $in->{use_flock}
57             if exists $in->{use_flock}
58 24 100 66     81 && defined $in->{use_flock};
59              
60 24         45 $this->{opts}->{use_flock} = $USE_FLOCK;
61              
62             $WANT_DIAGNOSTICS = $in->{diag}
63             if exists $in->{diag}
64 24 50 33     74 && defined $in->{diag};
65              
66 24         39 $this->{opts}->{diag} = $WANT_DIAGNOSTICS;
67              
68             $in->{read_limit} = defined $in->{read_limit}
69             ? $in->{read_limit}
70             : defined $in->{readlimit}
71             ? $in->{readlimit}
72 24 100       120 : undef;
    100          
73              
74 24         27 delete $in->{readlimit};
75 24 100       72 delete $in->{read_limit} if !defined $in->{read_limit};
76              
77             $READ_LIMIT = $in->{read_limit}
78             if exists $in->{read_limit}
79             && defined $in->{read_limit}
80 24 50 66     81 && $in->{read_limit} !~ /\D/;
      66        
81              
82 24         41 $this->{opts}->{read_limit} = $READ_LIMIT;
83              
84             $ABORT_DEPTH = $in->{abort_depth}
85             if exists $in->{abort_depth}
86             && defined $in->{abort_depth}
87 24 50 66     97 && $in->{abort_depth} !~ /\D/;
      66        
88              
89 24         38 $this->{opts}->{abort_depth} = $ABORT_DEPTH;
90              
91 24         58 return $this;
92             }
93              
94              
95             # --------------------------------------------------------
96             # File::Util::import()
97             # --------------------------------------------------------
98             sub import {
99              
100 22     22   210 my ( $class, @wanted_symbols ) = @_;
101              
102 22 50       63 ++$WANT_DIAGNOSTICS if grep { /(?
  31         85  
103              
104 22         29060 $class->export_to_level( 1, @_ );
105             }
106              
107              
108             # --------------------------------------------------------
109             # File::Util::list_dir()
110             # --------------------------------------------------------
111             sub list_dir {
112 80     80 1 1233 my $this = shift @_;
113 80         75 my $dir = shift @_;
114 80 100       205 my $opts = ref $_[0] eq 'REF' ? ${ shift @_ } : $this->_remove_opts( \@_ );
  48         47  
115              
116 80         62 my @dir_contents;
117              
118 80         105 my ( $subdirs, $files ) = ( [], [] );
119              
120 80         83 my $abort_depth = $opts->{abort_depth};
121              
122             # We can bypass all this extra checking/validation when we are recursing
123             # because we know we called ourself correctly--
124              
125             # INPUT VALIDATION AND DEFAULT VALUES
126              
127 80 100       133 if ( !$opts->{_recursing} ) { # bypass all this if recursing
128              
129 32 50 33     135 return $this->_throw(
130             'no input' => {
131             meth => 'list_dir',
132             missing => 'a directory name',
133             opts => $opts,
134             }
135             ) unless defined $dir && length $dir;
136              
137             $abort_depth =
138             defined $opts->{abort_depth}
139             ? $opts->{abort_depth}
140             : defined $this->{opts}->{abort_depth}
141             ? $this->{opts}->{abort_depth}
142 32 50       98 : $ABORT_DEPTH;
    50          
143              
144             # in case somebody wants to list_dir( "/tmp////" ) which is legal!
145 32 50       335 $dir =~ s/(?<=.)[\/\\:]+$// unless $dir =~ /^$WINROOT$/o;
146              
147             # recurse_fast implies recurse, and so does the legacy opt "follow"
148 32 50 33     124 $opts->{recurse} = 1 if $opts->{recurse_fast} || $opts->{follow};
149              
150             # "." and ".." make no sense (and cause infinite loops) when recursing...
151 32 100       61 $opts->{no_fsdots} = 1 if $opts->{recurse}; # ...so skip them
152              
153             # be compatible with GNU find
154 32 50       50 $opts->{max_depth} = delete $opts->{maxdepth} if $opts->{maxdepth};
155              
156             # break off immediately to helper function if asked to make a ref-tree
157 32 50       48 return $this->_as_tree( $dir => $opts ) if $opts->{as_tree};
158              
159 32 50       443 return $this->_throw( 'no such file' => { opts => $opts, filename => $dir } )
160             unless -e $dir;
161              
162 32 50       285 return $this->_throw (
163             'called opendir on a file' => {
164             filename => $dir,
165             opts => $opts,
166             }
167             ) unless -d $dir;
168             }
169              
170             # RUNAWAY RECURSION PREVENTION...
171              
172             # We have to keep an eye on recursion; we do it with a shared-reference.
173             # scalar references didn't work for me, so I'm using a hashref with a
174             # single key-value and it works beautifully
175             $opts->{_recursion} = {
176             _fast => $opts->{recurse_fast},
177             _base => $dir,
178             _isroot => ( $dir eq '/' || $dir =~ /^$WINROOT/ ) ? 1 : 0,
179             _depth => 0,
180             _inodes => {},
181 80 50 33     528 } unless defined $opts->{_recursion};
    100          
182              
183             # ...AND FILESYSTEM LOOPING PREVENTION ARE TIED TOGETHER...
184              
185 80 50       132 if ( !$opts->{_recursion}->{_fast} )
186             {
187 80         760 my ( $dev, $inode ) = lstat $dir;
188              
189 80 50       151 if ( $inode ) { # noop on windows which always returns zero (0) for inode
190              
191             # keep track of dir inodes or we're going to get stuck in filesystem
192             # loops the following bit of code incrementally populates (with each
193             # recursion) a hash table with keys named for the dev ID and inode of
194             # the directory, for every directory found
195              
196             warn sprintf
197             qq(*WARNING! Filesystem loop detected at %s, dev %s, inode %s\n),
198             $dir, $dev, $inode
199             and return( () )
200 80 50 0     220 if exists $opts->{_recursion}{_inodes}{ $dev, $inode };
201              
202 80         187 $opts->{_recursion}{_inodes}{ $dev, $inode } = undef;
203             }
204             }
205              
206             # DETERMINE DEPTH AND BAIL IF TOO DEEP
207              
208             # this is highly dependent on OS platform, and also whether or not we are
209             # listing a root directory, which makes optimizations harder ( / or C:\ )
210             # *note - $SL comes from File::Util::Definitions
211              
212 80         63 my $trailing_dirs;
213              
214 80 50       108 if ( $opts->{_recursion}{_isroot} )
215             {
216 0         0 ( $trailing_dirs ) =
217             $dir =~ /^ \Q$opts->{_recursion}{_base}\E (.+) /x;
218             }
219             else
220             {
221 80         461 ( $trailing_dirs ) =
222             $dir =~ /^ \Q$opts->{_recursion}{_base}$SL\E (.+) /x;
223             }
224              
225 80 50       127 if ( $SL eq '/' )
226             {
227 80 100       165 $opts->{_recursion}{_depth} = $trailing_dirs =~ tr/\/// + 1
228             if defined $trailing_dirs;
229             }
230             else
231             {
232 0 0       0 $opts->{_recursion}{_depth} = $trailing_dirs =~ tr/[\\:]// + 1
233             if defined $trailing_dirs;
234             }
235              
236             return( () ) if
237             $opts->{max_depth} &&
238 80 50 33     144 $opts->{_recursion}{_depth} >= $opts->{max_depth};
239              
240             # fail if the shared reference indicates we're to deep
241             return $this->_throw(
242             'abort_depth exceeded' => {
243             meth => 'list_dir',
244             abort_depth => $abort_depth,
245             opts => $opts,
246             dir => $dir,
247             }
248 80 50 33     148 ) if $opts->{_recursion}{_depth} == $abort_depth && $abort_depth != 0;
249              
250             # ACTUAL READING OF THE DIRECTORY
251              
252 80 50       1473 opendir my $dir_fh, $dir
253             or return $this->_throw
254             (
255             'bad opendir' => {
256             dirname => $dir,
257             exception => $!,
258             opts => $opts,
259             }
260             );
261              
262             # LEGACY_MATCHING
263              
264             # this form of matching is deprecated and is not robust. backward compat
265             # is preserved here, but it will soon no longer even be mentioned in the
266             # documentation, becoming useful only to the legacy code that relies on it
267              
268             # primitive pattern matching at top level only, applied to both files & dirs
269             @dir_contents = defined $opts->{pattern}
270 80 50       783 ? grep /$opts->{pattern}/, readdir $dir_fh
271             : readdir $dir_fh;
272              
273             # primitive pattern matching applied recursively to only files; if it were
274             # applied to both files AND dirs, recursion would often break unexpectedly
275             # for users unaware that they couldn't recurse into dirs that didn't match
276             # the pattern they probably intended only for files
277             @dir_contents = defined $opts->{rpattern}
278 80 100       308 ? grep { -d $dir . SL . $_ || /$opts->{rpattern}/ } @dir_contents
  48 100       548  
279             : @dir_contents;
280              
281 80 50       477 closedir $dir_fh
282             or return $this->_throw(
283             'close dir' => {
284             dir => $dir,
285             exception => $!,
286             opts => $opts,
287             }
288             );
289              
290             # get rid of "." and ".." if they are unwanted, and try to do it as fast
291             # as possible for large directories; Devel::NYTprof says this is faster
292 80 100       130 if ( $opts->{no_fsdots} )
293             {
294 73 50 33     133 if ( $dir_contents[0] eq '.' && $dir_contents[1] eq '..' )
295             {
296 0         0 @dir_contents = splice @dir_contents, 2;
297             }
298             else
299             {
300 73         77 @dir_contents = grep { !/$FSDOTS/ } @dir_contents;
  542         1097  
301             }
302             }
303              
304             # SEPARATION OF DIRS FROM FILES
305              
306 80 50 33     562 my $dir_base = # << we use this further down
307             ( $dir ne '/' && $dir !~ /^$WINROOT$/ )
308             ? $dir . SL
309             : $dir;
310              
311 80         125 while ( @dir_contents ) # !! don't do: while my $foo = shift !!
312             {
313 462         393 my $dir_entry = shift @dir_contents;
314              
315 462 100 66     4126 if ( -d $dir_base . $dir_entry && !-l $dir_base . $dir_entry )
316             {
317 66         152 push @$subdirs, $dir_entry
318             }
319 396         724 else { push @$files, $dir_entry }
320             }
321              
322             # ADVANCED MATCHING
323 80 100       146 if ( !defined $opts->{_matching} )
324             {
325             $opts->{_matching} =
326             $opts->{files_match} ||
327             $opts->{dirs_match} ||
328             $opts->{parent_matches} ||
329 32   100     159 $opts->{path_matches} || 0;
330              
331 32         53 $opts->{_matching} = !!$opts->{_matching};
332             }
333              
334 80 100       114 if ( $opts->{_matching} )
335             {
336 51         71 ( $subdirs, $files ) =
337             _list_dir_matching( $opts, $dir, $subdirs, $files );
338             }
339              
340             # prepend full path information to each file name if paths were
341             # requested, or if we are recursing. Then separate the directories
342             # and files off into @dirs and @itmes, respectively
343 80 100 66     199 if ( $opts->{recurse} || $opts->{with_paths} )
344             {
345 73         75 @$subdirs = map { $dir_base . $_ } @$subdirs;
  48         105  
346 73         77 @$files = map { $dir_base . $_ } @$files;
  162         205  
347             }
348              
349             # CALLBACKS (HIGHER ORDER FUNCTIONS)
350              
351             # here below is where we invoke the callbacks on dirs, files, or both.
352              
353 80 100       136 if ( my $cb = $opts->{callback} ) {
354              
355 5 50       11 $this->throw( qq(callback "$cb" not a coderef), $opts )
356             unless ref $cb eq 'CODE';
357              
358 5         12 $cb->( $dir, \@$subdirs, \@$files, $opts->{_recursion}{_depth} );
359             }
360              
361 80 50       152 if ( my $cb = $opts->{d_callback} ) {
362              
363 0 0       0 $this->throw( qq(d_callback "$cb" not a coderef), $opts )
364             unless ref $cb eq 'CODE';
365              
366 0         0 $cb->( $dir, \@$subdirs, $opts->{_recursion}{_depth} );
367             }
368              
369 80 50       102 if ( my $cb = $opts->{f_callback} ) {
370              
371 0 0       0 $this->throw( qq(f_callback "$cb" not a coderef), $opts )
372             unless ref $cb eq 'CODE';
373              
374 0         0 $cb->( $dir, \@$files, $opts->{_recursion}{_depth} );
375             }
376              
377             # RECURSION
378 80 100 33     191 if
      66        
379             (
380             $opts->{recurse} && !
381             (
382             $opts->{max_depth} && # don't recurse if we will then be at max depth
383             $opts->{_recursion}{_depth} == $opts->{max_depth} - 1
384             )
385             ) {
386             # recurse into all subdirs
387 25         30 for my $subdir ( @$subdirs ) {
388              
389             # certain opts need to be defined, overridden, added, or removed
390             # completely before recursing. That's why we redefine everything
391             # here below, eliminating potential user-error where incompatible
392             # options would otherwise break recursion and/or cause confusion
393              
394             my $recurse_opts = {
395             as_ref => 1,
396             with_paths => 1,
397             no_fsdots => 1,
398             abort_depth => $abort_depth,
399             max_depth => $opts->{max_depth},
400             onfail => $opts->{onfail},
401             diag => $opts->{diag},
402             rpattern => $opts->{rpattern},
403             files_match => $opts->{files_match},
404             dirs_match => $opts->{dirs_match},
405             parent_matches => $opts->{parent_matches},
406             path_matches => $opts->{path_matches},
407             callback => $opts->{callback},
408             d_callback => $opts->{d_callback},
409             f_callback => $opts->{f_callback},
410             _matching => $opts->{_matching},
411             _patterns => $opts->{_patterns} || {},
412             _recursion => $opts->{_recursion},
413 48   100     367 _recursing => 1,
414             };
415              
416 48         109 my ( $dirs_ref, $files_ref ) =
417             $this->list_dir( $subdir => \$recurse_opts );
418              
419 48 50 33     185 push @$subdirs, @$dirs_ref
420             if ref $dirs_ref && ref $dirs_ref eq 'ARRAY';
421              
422 48 50 33     256 push @$files, @$files_ref
423             if ref $files_ref && ref $files_ref eq 'ARRAY';
424             }
425             }
426              
427             # FINAL PREPARATIONS before returning results
428              
429 80 100 66     177 if (
      66        
430             !$opts->{_recursing} &&
431             (
432             $opts->{path_matches} || $opts->{parent_matches}
433             )
434             ) {
435 10         15 @$subdirs = _list_dir_lastround_dirmatch( $opts, $subdirs );
436             }
437              
438             # cosmetic formatting for directories/
439 80 50       104 if ( $opts->{sl_after_dirs} ) {
440              
441             # append directory separator to everything but the "dots"
442 0         0 $_ .= SL for grep { !/$FSDOTS/ } @$subdirs;
  0         0  
443             }
444              
445             # sorting
446 80 50       100 if ( $opts->{ignore_case} ) {
447              
448 0         0 $subdirs = [ sort { uc $a cmp uc $b } @$subdirs ];
  0         0  
449 0         0 $files = [ sort { uc $a cmp uc $b } @$files ];
  0         0  
450             }
451             else {
452              
453 80         155 $subdirs = [ sort { $a cmp $b } @$subdirs ];
  38         74  
454 80         134 $files = [ sort { $a cmp $b } @$files ];
  589         444  
455             }
456              
457             # RETURN based on selected opts
458              
459             return scalar @$subdirs
460 80 50 66     146 if $opts->{dirs_only} && $opts->{count_only};
461              
462             return scalar @$files
463 80 50 66     125 if $opts->{files_only} && $opts->{count_only};
464              
465             return scalar @$subdirs + scalar @$files
466 80 50       97 if $opts->{count_only};
467              
468             return $subdirs, $files
469 80 100       199 if $opts->{as_ref};
470              
471 32 50       51 $subdirs = [ $subdirs ] if $opts->{dirs_as_ref};
472 32 50       42 $files = [ $files ] if $opts->{files_as_ref};
473              
474 32 100       58 return @$subdirs if $opts->{dirs_only};
475 29 100       150 return @$files if $opts->{files_only};
476              
477 11         90 return @$subdirs, @$files;
478             }
479              
480              
481             # --------------------------------------------------------
482             # File::Util::_list_dir_matching()
483             # --------------------------------------------------------
484             sub _list_dir_matching {
485 51     51   62 my ( $opts, $path, $dirs, $files ) = @_;
486              
487             # COLLECT PATTERN(S) TO BE APPLIED
488              
489             { # memo-ize these patterns
490              
491             # FILES AND
492 51         29 $opts->{_patterns}->{_files_match_and} =
493             [ _gather_and_patterns( $opts->{files_match} ) ]
494 51 100       106 unless defined $opts->{_patterns}->{_files_match_and};
495              
496             # FILES OR
497             $opts->{_patterns}->{_files_match_or} =
498             [ _gather_or_patterns( $opts->{files_match} ) ]
499 51 100       85 unless defined $opts->{_patterns}->{_files_match_or};
500              
501             # DIRS AND
502             $opts->{_patterns}->{_dirs_match_and} =
503             [ _gather_and_patterns( $opts->{dirs_match} ) ]
504 51 100       118 unless defined $opts->{_patterns}->{_dirs_match_and};
505              
506             # DIRS OR
507             $opts->{_patterns}->{_dirs_match_or} =
508             [ _gather_or_patterns( $opts->{dirs_match} ) ]
509 51 100       85 unless defined $opts->{_patterns}->{_dirs_match_or};
510              
511             # PARENT AND
512             $opts->{_patterns}->{_parent_matches_and} =
513             [ _gather_and_patterns( $opts->{parent_matches} ) ]
514 51 100       92 unless defined $opts->{_patterns}->{_parent_matches_and};
515              
516             # PARENT OR
517             $opts->{_patterns}->{_parent_matches_or} =
518             [ _gather_or_patterns( $opts->{parent_matches} ) ]
519 51 100       88 unless defined $opts->{_patterns}->{_parent_matches_or};
520              
521             # PATH AND
522             $opts->{_patterns}->{_path_matches_and} =
523             [ _gather_and_patterns( $opts->{path_matches} ) ]
524 51 100       77 unless defined $opts->{_patterns}->{_path_matches_and};
525              
526             # PATH OR
527             $opts->{_patterns}->{_path_matches_or} =
528             [ _gather_or_patterns( $opts->{path_matches} ) ]
529 51 100       85 unless defined $opts->{_patterns}->{_path_matches_or};
530             }
531              
532             # FILE MATCHING
533              
534 51         36 for my $pattern ( @{ $opts->{_patterns}->{_files_match_and} } ) {
  51         88  
535              
536 39         37 @$files = grep { /$pattern/ } @$files;
  151         286  
537             }
538              
539             @$files = _match_and( $opts->{_patterns}->{_files_match_and}, $files )
540 51 100       38 if @{ $opts->{_patterns}->{_files_match_and} };
  51         98  
541              
542             @$files = _match_or( $opts->{_patterns}->{_files_match_or}, $files )
543 51 100       37 if @{ $opts->{_patterns}->{_files_match_or} };
  51         71  
544              
545             # DIRECTORY MATCHING
546              
547             @$dirs = _match_and( $opts->{_patterns}->{_dirs_match_and}, $dirs )
548 51 100       32 if @{ $opts->{_patterns}->{_dirs_match_and} };
  51         84  
549              
550             @$dirs = _match_or( $opts->{_patterns}->{_dirs_match_or}, $dirs )
551 51 100       37 if @{ $opts->{_patterns}->{_dirs_match_or} };
  51         71  
552              
553             # FILE &'ed DIRECTORY MATCHING
554              
555 51 100 66     97 if ( $opts->{files_match} && $opts->{dirs_match} ) {
556              
557             $files = [ ]
558             unless _match_and
559             (
560             $opts->{_patterns}->{_dirs_match_and},
561 9 100       12 [ strip_path( $path ) ]
562             );
563             }
564              
565             # MATCHING FILES BY PARENT DIR
566              
567 51 100       67 if ( $opts->{parent_matches} ) {
568              
569 15 100       11 if ( @{ $opts->{_patterns}->{_parent_matches_and} } ) {
  15 50       23  
570              
571             $files = [ ]
572             unless _match_and
573             (
574             $opts->{_patterns}->{_parent_matches_and},
575 12 100       20 [ strip_path( $path ) ]
576             );
577             }
578 3         5 elsif ( @{ $opts->{_patterns}->{_parent_matches_or} } ) {
579              
580             $files = [ ]
581             unless _match_or
582             (
583             $opts->{_patterns}->{_parent_matches_or},
584 3 50       5 [ strip_path( $path ) ]
585             );
586             }
587             }
588              
589             # MATCHING FILES BY PATH
590              
591 51 100       72 if ( $opts->{path_matches} ) {
592              
593 15 100       8 if ( @{ $opts->{_patterns}->{_path_matches_and} } ) {
  15 50       21  
594              
595             $files = [ ]
596             unless _match_and
597             (
598 9 100       16 $opts->{_patterns}->{_path_matches_and}, [ $path ]
599             );
600             }
601 6         9 elsif ( @{ $opts->{_patterns}->{_path_matches_or} } ) {
602              
603             $files = [ ]
604             unless _match_or
605             (
606 6 100       14 $opts->{_patterns}->{_path_matches_or}, [ $path ]
607             );
608             }
609             }
610              
611 51         72 return ( $dirs, $files );
612             }
613              
614              
615             # --------------------------------------------------------
616             # File::Util::_list_dir_lastround_dirmatch()
617             # --------------------------------------------------------
618             sub _list_dir_lastround_dirmatch {
619 10     10   8 my ( $opts, $dirs ) = @_;
620              
621 10         9 my @return_dirs;
622              
623             # LAST ROUND MATCHING DIRS BY PARENT DIR
624              
625 10 100       13 if ( $opts->{parent_matches} ) {
626              
627 5         4 my %return_dirs;
628              
629 5 100       4 if ( @{ $opts->{_patterns}->{_parent_matches_and} } ) {
  5 50       8  
630              
631 4         5 for my $qfd_dir ( @$dirs ) {
632              
633 8         10 my ( $root, $in_path ) = atomize_path( $qfd_dir );
634              
635 8 50       17 $in_path = $root . $in_path if $root;
636              
637             $return_dirs{ $in_path } = $in_path
638             if _match_and
639             (
640             $opts->{_patterns}->{_parent_matches_and},
641 8 100       23 [ strip_path( $in_path ) ]
642             );
643             }
644             }
645 1         4 elsif ( @{ $opts->{_patterns}->{_parent_matches_or} } ) {
646              
647 1         2 for my $qfd_dir ( @$dirs ) {
648              
649 2         4 my ( $root, $in_path ) = atomize_path( $qfd_dir );
650              
651 2 50       6 $in_path = $root . $in_path if $root;
652              
653             $return_dirs{ $in_path } = $in_path
654             if _match_or
655             (
656             $opts->{_patterns}->{_parent_matches_or},
657 2 50       5 [ strip_path( $in_path ) ]
658             );
659             }
660             }
661              
662 5         10 push @return_dirs, keys %return_dirs;
663             }
664              
665             # LAST ROUND MATCHING DIRS BY PATH
666              
667 10 100       16 if ( $opts->{path_matches} ) {
668              
669 5         3 my %return_dirs;
670              
671 5 100       4 if ( @{ $opts->{_patterns}->{_path_matches_and} } ) {
  5 50       9  
672              
673 3         4 for my $qfd_dir ( @$dirs ) {
674              
675 6         7 my ( $root, $in_path ) = atomize_path( $qfd_dir );
676              
677 6 50       15 $in_path = $root . $in_path if $root;
678              
679             $return_dirs{ $in_path } = $in_path
680             if _match_and
681             (
682 6 50       12 $opts->{_patterns}->{_path_matches_and}, [ $in_path ]
683             );
684              
685             $return_dirs{ $qfd_dir } = $qfd_dir
686             if _match_and
687             (
688 6 100       10 $opts->{_patterns}->{_path_matches_and}, [ $qfd_dir ]
689             );
690             }
691             }
692 2         5 elsif ( @{ $opts->{_patterns}->{_path_matches_or} } ) {
693              
694 2         3 for my $qfd_dir ( @$dirs ) {
695              
696 4         4 my ( $root, $in_path ) = atomize_path( $qfd_dir );
697              
698 4 50       10 $in_path = $root . $in_path if $root;
699              
700             $return_dirs{ $in_path } = $in_path
701             if _match_or
702             (
703 4 100       6 $opts->{_patterns}->{_path_matches_or}, [ $in_path ]
704             );
705              
706             $return_dirs{ $qfd_dir } = $qfd_dir
707             if _match_or
708             (
709 4 50       8 $opts->{_patterns}->{_path_matches_or}, [ $qfd_dir ]
710             );
711             }
712             }
713              
714 5         12 push @return_dirs, keys %return_dirs;
715             }
716              
717 10         17 return @return_dirs;
718             }
719              
720              
721             # --------------------------------------------------------
722             # File::Util::_gather_and_patterns()
723             # --------------------------------------------------------
724             sub _gather_and_patterns {
725              
726 68     68   68 my $pattern_ref = shift @_;
727              
728             return
729             defined $pattern_ref &&
730             ref $pattern_ref eq 'HASH' &&
731             defined $pattern_ref->{and} &&
732             ref $pattern_ref->{and} eq 'ARRAY'
733 68 100 66     300 ? @{ $pattern_ref->{and} }
  7 100 100     12  
734             : defined $pattern_ref &&
735             ref $pattern_ref eq 'Regexp'
736             ? ( $pattern_ref )
737             : ( );
738             }
739              
740              
741             # --------------------------------------------------------
742             # File::Util::_gather_or_patterns()
743             # --------------------------------------------------------
744             sub _gather_or_patterns {
745              
746 68     68   54 my $pattern_ref = shift @_;
747              
748             return
749             defined $pattern_ref &&
750             ref $pattern_ref eq 'HASH' &&
751             defined $pattern_ref->{or} &&
752             ref $pattern_ref->{or} eq 'ARRAY'
753 68 100 66     231 ? @{ $pattern_ref->{or} }
  6         13  
754             : ( );
755             }
756              
757              
758             # --------------------------------------------------------
759             # File::Util::_match_and()
760             # --------------------------------------------------------
761             sub _match_and {
762              
763 83     83   60 my ( $patterns, $items ) = @_;
764              
765 83         74 for my $pattern ( @$patterns ) {
766              
767 108         103 @$items = grep { /$pattern/ } @$items;
  84         260  
768             }
769              
770 83         197 return @$items;
771             }
772              
773              
774             # --------------------------------------------------------
775             # File::Util::_match_or()
776             # --------------------------------------------------------
777             sub _match_or {
778              
779 28     28   23 my ( $patterns, $items ) = @_;
780              
781 28         17 my $or_pattern;
782              
783 28         25 for my $pattern ( @$patterns ) {
784              
785 56 100       752 $or_pattern = $or_pattern
786             ? qr/$pattern|$or_pattern/
787             : $pattern;
788             }
789              
790 28         30 @$items = grep { /$or_pattern/ } @$items;
  39         182  
791              
792 28         71 return @$items;
793             }
794              
795              
796             # --------------------------------------------------------
797             # File::Util::_as_tree()
798             # --------------------------------------------------------
799             sub _as_tree {
800 0     0   0 my $this = shift @_;
801 0         0 my $opts = $this->_remove_opts( \@_ );
802 0         0 my $dir = shift @_;
803 0         0 my $tree = {};
804              
805             my $treeify = sub
806             {
807 0     0   0 my ( $dirname, $subdirs, $files ) = @_;
808              
809             # find root of tree (if path was absolute)
810 0         0 my ( $root, $branch, $leaf ) = atomize_path( $dirname );
811              
812 0         0 my @path_dirs = split /$DIRSPLIT/o, $branch;
813              
814             # find place in tree
815 0         0 my @lineage = ( @path_dirs, $leaf );
816              
817 0 0       0 unshift @lineage, $root if $root;
818              
819 0         0 my $ancestory = $tree;
820              
821             # recursively create hashref tree
822              
823 0         0 for ( my $i = 0; $i < @lineage; $i++ )
824             {
825 0         0 my $self = $lineage[ $i ];
826              
827 0 0       0 my $parent = $i > 0 ? $i - 1 : undef;
828              
829 0 0       0 if ( defined $parent )
830             {
831 0         0 my @predecessors = @lineage[ 0 .. $parent ];
832              
833             # for abs paths on *nix
834 0 0 0     0 shift @predecessors if
835             @predecessors > 1 &&
836             $predecessors[0] eq SL;
837              
838 0         0 $parent = join SL, @predecessors;
839              
840 0 0 0     0 $parent = $root . $parent if $root && $parent ne $root;
841             }
842              
843 0   0     0 $ancestory->{ $self } ||= { };
844              
845 0 0 0     0 unless (
      0        
846             exists $opts->{dirmeta} &&
847             defined $opts->{dirmeta} &&
848             $opts->{dirmeta} == 0
849             ) {
850 0         0 $ancestory->{ $self }{ _DIR_PARENT_ } = $parent;
851              
852             $ancestory->{ $self }{ _DIR_SELF_ } =
853 0 0       0 !defined $parent
    0          
854             ? $self
855             : $parent eq $root
856             ? $parent . $self
857             : $parent . SL . $self;
858             }
859              
860 0         0 $ancestory = $ancestory->{ $self };
861             }
862              
863             # the next two loops populate the tree
864              
865 0         0 my $parent = $ancestory;
866              
867 0         0 for my $subdir ( @$subdirs )
868             {
869 0   0     0 $parent->{ strip_path( $subdir ) } ||= { };
870             }
871              
872 0         0 for my $file ( @$files )
873             {
874 0         0 $parent->{ strip_path( $file ) } = $file;
875             }
876 0         0 };
877              
878 0         0 $opts->{callback} = $treeify;
879              
880 0         0 delete $opts->{as_tree};
881              
882 0         0 $this->list_dir( $dir => $opts );
883              
884 0         0 return $tree;
885             }
886              
887              
888             # --------------------------------------------------------
889             # File::Util::_dropdots()
890             # --------------------------------------------------------
891             sub _dropdots {
892 1     1   2 my $this = shift @_;
893 1         4 my $opts = $this->_remove_opts( \@_ );
894 1         2 my @copy = @_;
895 1         1 my @out = ();
896 1         2 my @dots = ();
897 1         1 my $gottadot = 0;
898              
899 1         3 while ( @copy ) {
900              
901 3 50       4 if ( $gottadot == 2 ) { push @out, @copy and last }
  1 100       4  
902              
903 2         2 my $dir_item = shift @copy;
904              
905 2 50       9 if ( $dir_item =~ /$FSDOTS/ ) {
906              
907 2         2 ++$gottadot;
908              
909 2         3 push @dots, $dir_item;
910              
911 2         4 next;
912             }
913              
914 0         0 push @out, $dir_item;
915             }
916              
917 1 50       2 return( \@dots, @out ) if $opts->{save_dots};
918              
919 1         4 return @out;
920             }
921              
922              
923             # --------------------------------------------------------
924             # File::Util::load_file()
925             # --------------------------------------------------------
926             sub load_file {
927 56     56 1 1109 my $this = shift @_;
928 56         118 my $in = $this->_parse_in( @_ );
929 56         48 my @dirs = ();
930 56         45 my $blocksize = 1024; # 1.24 kb
931 56         39 my $fh_passed = 0;
932 56         37 my $fh;
933              
934 56         75 my ( $file, $root, $path, $clean_name, $content, $mode ) =
935             ( '', '', '', '', '', 'read' );
936              
937             # all of this logic branching is to cover the possibilities in the way
938             # this method could have been called. we try to support as many methods
939             # as make at least some amount of sense
940              
941             $in->{read_limit} = defined $in->{read_limit}
942             ? $in->{read_limit}
943             : defined $in->{readlimit}
944             ? $in->{readlimit}
945 56 50       117 : undef;
    50          
946              
947 56         49 delete $in->{readlimit};
948 56 50       101 delete $in->{read_limit} if !defined $in->{read_limit};
949              
950             my $read_limit =
951             defined $in->{read_limit}
952             ? $in->{read_limit}
953             : defined $this->{opts}->{read_limit}
954             ? $this->{opts}->{read_limit}
955 56 0       115 : defined $READ_LIMIT
    50          
    50          
956             ? $READ_LIMIT
957             : 0;
958              
959 56 50       132 return $this->_throw(
960             'bad read_limit' => { opts => $in, bad => $read_limit }
961             ) if $read_limit =~ /\D/;
962              
963             # support old-school "FH" option, *and* the new, more sensible "file_handle"
964 56 50       72 $in->{FH} = $in->{file_handle} if defined $in->{file_handle};
965              
966 56 50       80 if ( !defined $in->{FH} ) { # unless we were passed a file handle...
967              
968             $file = defined $in->{file}
969             ? $in->{file}
970             : defined $in->{filename}
971             ? $in->{filename}
972 56 50 50     145 : shift @_ || '';
    50          
973              
974 56 50       75 return $this->_throw(
975             'no input',
976             {
977             meth => 'load_file',
978             missing => 'a file name or file handle reference',
979             opts => $in,
980             }
981             ) unless length $file;
982              
983 56         84 ( $root, $path, $file ) = atomize_path( $file );
984              
985 56         342 @dirs = split /$DIRSPLIT/, $path;
986              
987 56 50       124 unshift @dirs, $root if $root;
988              
989             # cleanup file name - if path is relative, normalize it
990             # - /foo/bar/baz.txt stays as /foo/bar/baz.txt
991             # - foo/bar/baz.txt becomes ./foo/bar/baz.txt
992             # - baz.txt stays as baz.txt
993 56 50 33     111 if ( !length $root && !length $path ) {
994              
995 0         0 $path = '.' . SL;
996             }
997             else { # otherwise path normalized at end
998              
999 56         60 $path .= SL;
1000             }
1001              
1002             # final clean filename assembled
1003 56         75 $clean_name = $root . $path . $file;
1004             }
1005             else {
1006              
1007             # did we get a filehandle?
1008 0 0       0 if ( ref $in->{FH} eq 'GLOB' ) {
1009              
1010 0         0 $fh_passed++;
1011             }
1012             else {
1013              
1014 0         0 return $this->_throw(
1015             'no input',
1016             {
1017             meth => 'load_file',
1018             missing => 'a true file handle reference (not a string)',
1019             opts => $in,
1020             }
1021             );
1022             }
1023             }
1024              
1025 56 50       75 if ( $fh_passed ) {
1026              
1027 0         0 my $buffer = 0;
1028 0         0 my $bytes_read = 0;
1029 0         0 $fh = $in->{FH};
1030              
1031 0         0 while ( <$fh> ) {
1032              
1033 0 0       0 if ( $buffer < $read_limit ) {
1034              
1035 0         0 $bytes_read = read( $fh, $content, $blocksize );
1036              
1037 0         0 $buffer += $bytes_read;
1038             }
1039             else {
1040              
1041 0         0 return $this->_throw(
1042             'read_limit exceeded',
1043             {
1044             filename => '',
1045             size => qq{[truncated at $bytes_read]},
1046             read_limit => $read_limit,
1047             opts => $in,
1048             }
1049             );
1050             }
1051             }
1052              
1053             # return an array of all lines in the file if the call to this method/
1054             # subroutine asked for an array eg- my @file = load_file('file');
1055             # otherwise, return a scalar value containing all of the file's content
1056             return split /$NL|\r|\n/o, $content
1057 0 0       0 if $in->{as_list};
1058              
1059 0         0 return $content;
1060             }
1061              
1062             # if the file doesn't exist, send back an error
1063 56 50       644 return $this->_throw(
1064             'no such file',
1065             {
1066             filename => $clean_name,
1067             opts => $in,
1068             }
1069             ) unless -e $clean_name;
1070              
1071             # it's good to know beforehand whether or not we have permission to open
1072             # and read from this file allowing us to handle such an exception before
1073             # it handles us.
1074              
1075             # first check the readability of the file's housing dir
1076 56 50       379 return $this->_throw(
1077             'cant dread',
1078             {
1079             filename => $clean_name,
1080             dirname => $root . $path,
1081             opts => $in,
1082             }
1083             ) unless -r $root . $path;
1084              
1085             # now check the readability of the file itself
1086 56 50       359 return $this->_throw(
1087             'cant fread',
1088             {
1089             filename => $clean_name,
1090             dirname => $root . $path,
1091             opts => $in,
1092             }
1093             ) unless -r $clean_name;
1094              
1095             # if the file is a directory it will not be opened
1096 56 50       380 return $this->_throw(
1097             'called open on a dir',
1098             {
1099             filename => $clean_name,
1100             opts => $in,
1101             }
1102             ) if -d $clean_name;
1103              
1104 56         341 my $fsize = -s $clean_name;
1105              
1106 56 50       127 return $this->_throw(
1107             'read_limit exceeded',
1108             {
1109             filename => $clean_name,
1110             size => $fsize,
1111             opts => $in,
1112             read_limit => $read_limit,
1113             }
1114             ) if $fsize > $read_limit;
1115              
1116             # localize the global output record separator so we can slurp it all
1117             # in one quick read. We fail if the filesize exceeds our limit.
1118 56         140 local $/;
1119              
1120             # open the file for reading (note the '<' syntax there) or fail with a
1121             # error message if our attempt to open the file was unsuccessful
1122              
1123             # lock file before I/O on platforms that support it
1124 56 50 33     232 if (
      33        
1125             $in->{no_lock} ||
1126             $this->{opts}->{no_lock} ||
1127             !$this->use_flock()
1128             ) {
1129              
1130             # if you use the 'no_lock' option you are probably inefficient
1131 0 0       0 open $fh, '<', $clean_name or
1132             return $this->_throw(
1133             'bad open',
1134             {
1135             filename => $clean_name,
1136             mode => $mode,
1137             exception => $!,
1138             cmd => qq(< $clean_name),
1139             opts => $in,
1140             }
1141             );
1142             }
1143             else {
1144 56 50       1152 open $fh, '<', $clean_name or
1145             return $this->_throw(
1146             'bad open',
1147             {
1148             filename => $clean_name,
1149             mode => $mode,
1150             exception => $!,
1151             cmd => qq(< $clean_name),
1152             opts => $in,
1153             }
1154             );
1155              
1156 56         145 $this->_seize( $clean_name, $fh, $in );
1157             }
1158              
1159             # call binmode on binary files for portability across platforms such
1160             # as MS flavor OS family
1161              
1162 56 100       1363 binmode $fh if -B $clean_name;
1163              
1164             # call binmode on the filehandle if it was requested or UTF-8
1165 56 100       108 if ( $in->{binmode} )
1166             {
1167 4 50       9 if ( lc $in->{binmode} eq 'utf8' )
    0          
1168             {
1169 4 50       8 if ( $HAVE_UU )
1170             {
1171 4     1   45 binmode $fh, ':unix:encoding(UTF-8)';
  1         6  
  1         1  
  1         6  
1172             }
1173             else
1174             {
1175 0         0 close $fh;
1176              
1177 0         0 return $this->_throw( 'no unicode' => $in );
1178             }
1179             }
1180             elsif ( $in->{binmode} == 1 )
1181             {
1182 0         0 binmode $fh;
1183             }
1184             else
1185             {
1186             binmode $fh, $in->{binmode} # apply user-specified IO layer(s)
1187 0         0 }
1188             }
1189              
1190             # assign the content of the file to this lexically scoped scalar variable
1191             # (memory for *that* variable will be freed when execution leaves this
1192             # method / sub
1193              
1194 56         8378 $content = <$fh>;
1195              
1196 56 50 33     192 if ( $in->{no_lock} || $this->{opts}->{no_lock} ) {
1197              
1198             # if execution gets here, you used the 'no_lock' option, and you
1199             # are probably inefficient
1200              
1201 0 0       0 close $fh or return $this->_throw(
1202             'bad close',
1203             {
1204             filename => $clean_name,
1205             mode => $mode,
1206             exception => $!,
1207             opts => $in,
1208             }
1209             );
1210             }
1211             else {
1212             # release shadow-ed locks on the file
1213 56         84 $this->_release( $fh, $in );
1214              
1215 56 50       221 close $fh or return $this->_throw(
1216             'bad close',
1217             {
1218             filename => $clean_name,
1219             mode => $mode,
1220             exception => $!,
1221             opts => $in,
1222             }
1223             );
1224             }
1225              
1226             # return an array of all lines in the file if the call to this method/
1227             # subroutine asked for an array eg- my @file = load_file('file');
1228             # otherwise, return a scalar value containing all of the file's content
1229             return split /$NL|\r|\n/o, $content
1230 56 50       89 if $in->{as_lines};
1231              
1232 56         350 return $content;
1233             }
1234              
1235              
1236             # --------------------------------------------------------
1237             # File::Util::write_file()
1238             # --------------------------------------------------------
1239             sub write_file {
1240 73     73 1 3189 my $this = shift @_;
1241 73         156 my $in = $this->_parse_in( @_ );
1242 73         73 my $content = '';
1243 73         57 my $raw_name = '';
1244 73         79 my $file = '';
1245 73   100     206 my $mode = $in->{mode} || 'write';
1246 73   50     183 my $bitmask = $in->{bitmask} || oct 777;
1247 73         47 my $write_fh; # will be the lexical file handle local to this block
1248 73         102 my ( $root, $path, $clean_name, @dirs ) =
1249             ( '', '', '', () );
1250              
1251             # get name of file when passed in as a name/value pair...
1252              
1253             $file =
1254             exists $in->{filename} &&
1255             defined $in->{filename} &&
1256             length $in->{filename}
1257             ? $in->{filename}
1258             : exists $in->{file} &&
1259             defined $in->{file} &&
1260             length $in->{file}
1261             ? $in->{file}
1262 73 100 66     356 : '';
    100 66        
1263              
1264             # ...or fall back to support of two-argument form of invocation
1265              
1266 73 50       71 my $maybe_file = shift @_; $maybe_file = '' if !defined $maybe_file;
  73         93  
1267 73 100       58 my $maybe_content = shift @_; $maybe_content = '' if !defined $maybe_content;
  73         96  
1268              
1269 73 100 100     204 $file = $maybe_file if !ref $maybe_file && $file eq '';
1270             $content =
1271             !ref $maybe_content &&
1272             !exists $in->{content}
1273             ? $maybe_content
1274 73 100 66     203 : $in->{content};
1275              
1276 73         431 my ( $winroot ) = $file =~ /^($WINROOT)/;
1277              
1278 73         190 $file =~ s/^($WINROOT)//;
1279 73         606 $file =~ s/$DIRSPLIT{2,}/$SL/o;
1280 73 50       611 $file =~ s/$DIRSPLIT+$//o unless $file eq SL;
1281 73 50       115 $file = $winroot . $file if $winroot;
1282              
1283 73         61 $raw_name = $file; # preserve original filename input before line below:
1284              
1285 73         101 ( $root, $path, $file ) = atomize_path( $file );
1286              
1287 73 50       173 $mode = 'trunc' if $mode eq 'truncate';
1288 73 100       101 $content = '' if $mode eq 'trunc';
1289              
1290             # if the call to this method didn't include a filename to which the caller
1291             # wants us to write, then complain about it
1292 73 50       101 return $this->_throw(
1293             'no input' => {
1294             meth => 'write_file',
1295             missing => 'a file name to create, write, or append',
1296             opts => $in,
1297             }
1298             ) unless length $file;
1299              
1300             # if the call to this method didn't include any data which the caller
1301             # wants us to write or append to the file, then complain about it
1302             return $this->_throw(
1303             'no input' => {
1304             meth => 'write_file',
1305             missing => 'the content you want to write or append',
1306             opts => $in,
1307             }
1308             ) if (
1309             ( !defined $content ||
1310             length $content == 0 )
1311             &&
1312             $mode ne 'trunc'
1313             &&
1314             !$EMPTY_WRITES_OK
1315             &&
1316             !$in->{empty_writes_OK}
1317             &&
1318             !$in->{empty_writes_ok}
1319 73 50 66     423 );
      100        
      66        
      66        
      33        
1320              
1321             # check if file already exists in the form of a directory
1322 73 50       896 return $this->_throw(
1323             'cant write_file on a dir' => {
1324             filename => $raw_name,
1325             opts => $in,
1326             }
1327             ) if -d $raw_name;
1328              
1329             # determine existance of the file path, make directory(ies) for the
1330             # path if the full directory path doesn't exist
1331 73         487 @dirs = split /$DIRSPLIT/, $path;
1332              
1333             # if prospective file name has illegal chars then complain
1334 73         152 foreach ( @dirs ) {
1335              
1336 232 50       288 return $this->_throw(
1337             'bad chars' => {
1338             string => $_,
1339             purpose => 'the name of a file or directory',
1340             opts => $in,
1341             }
1342             ) if !$this->valid_filename( $_ );
1343             }
1344              
1345             # do this AFTER the above check!!
1346 73 50       149 unshift @dirs, $root if $root;
1347              
1348             # make sure that open mode is a valid mode
1349 73 50 100     170 unless ( $mode eq 'write' || $mode eq 'append' || $mode eq 'trunc' ) {
      66        
1350              
1351 0         0 return $this->_throw(
1352             'bad openmode popen' => {
1353             meth => 'write_file',
1354             filename => $raw_name,
1355             badmode => $mode,
1356             opts => $in,
1357             }
1358             )
1359             }
1360              
1361             # cleanup file name - if path is relative, normalize it
1362             # - /foo/bar/baz.txt stays as /foo/bar/baz.txt
1363             # - foo/bar/baz.txt becomes ./foo/bar/baz.txt
1364             # - baz.txt stays as baz.txt
1365 73 50 33     144 if ( !length $root && !length $path ) {
1366              
1367 0         0 $path = '.' . SL;
1368             }
1369             else { # otherwise path normalized at end
1370              
1371 73         78 $path .= SL;
1372             }
1373              
1374             # final clean filename assembled
1375 73         90 $clean_name = $root . $path . $file;
1376              
1377             # create path preceding file if path doesn't exist
1378 73 50       668 if ( !-e $root . $path ) {
1379              
1380 0         0 my $make_dir_ok = 1;
1381              
1382             my $make_dir_return = $this->make_dir(
1383             $root . $path,
1384             exists $in->{dbitmask} &&
1385             defined $in->{dbitmask}
1386             ? $in->{dbitmask}
1387             : oct 777,
1388             {
1389             diag => $in->{diag},
1390             onfail => sub {
1391 0     0   0 my ( $err, $trace ) = @_;
1392              
1393             return $in->{onfail}
1394             if ref $in->{onfail} &&
1395 0 0 0     0 ref $in->{onfail} eq 'CODE';
1396              
1397 0         0 $make_dir_ok = 0;
1398              
1399 0         0 return $err . $trace;
1400             }
1401             }
1402 0 0 0     0 );
1403              
1404 0 0       0 die $make_dir_return unless $make_dir_ok;
1405             }
1406              
1407             # if file already exists, check if we can write to it
1408 73 100       390 if ( -e $clean_name ) {
1409              
1410 22 50       146 return $this->_throw(
1411             'cant fwrite' => {
1412             filename => $clean_name,
1413             dirname => $root . $path,
1414             opts => $in,
1415             }
1416             ) unless -w $clean_name;
1417             }
1418             else {
1419              
1420             # if file doesn't exist, see if we can create it
1421 51 50       355 return $this->_throw(
1422             'cant fcreate' => {
1423             filename => $clean_name,
1424             dirname => $root . $path,
1425             opts => $in,
1426             }
1427             ) unless -w $root . $path;
1428             }
1429              
1430             # if you use the no_lock option, please consider the risks
1431              
1432 73 50 33     242 if ( $in->{no_lock} || !$USE_FLOCK ) {
1433              
1434             # only non-existent files get bitmask arguments
1435 0 0       0 if ( -e $clean_name )
1436             {
1437             # you can't use UTF8 'mode' on system IO, so if a user requests
1438             # UTF8, we have to use PerlIO
1439 0 0 0     0 if ( $in->{binmode} && lc $in->{binmode} eq 'utf8' )
1440             {
1441             open
1442             $write_fh,
1443 0 0       0 $$MODES{popen}{ $mode },
1444             $clean_name
1445             or return $this->_throw(
1446             'bad open' => {
1447             filename => $clean_name,
1448             mode => $mode,
1449             opts => $in,
1450             exception => $!,
1451             cmd => $mode . $clean_name,
1452             }
1453             );
1454             }
1455             else
1456             {
1457             sysopen
1458             $write_fh,
1459             $clean_name,
1460 0 0       0 $$MODES{sysopen}{ $mode }
1461             or return $this->_throw(
1462             'bad open' => {
1463             filename => $clean_name,
1464             mode => $mode,
1465             opts => $in,
1466             exception => $!,
1467             cmd => qq($clean_name, $$MODES{sysopen}{ $mode }),
1468             }
1469             );
1470             }
1471             }
1472             else
1473             {
1474             sysopen
1475             $write_fh,
1476             $clean_name,
1477 0 0       0 $$MODES{sysopen}{ $mode },
1478             $bitmask
1479             or return $this->_throw(
1480             'bad open' => {
1481             filename => $clean_name,
1482             mode => $mode,
1483             exception => $!,
1484             cmd => qq($clean_name, $$MODES{sysopen}{$mode}, $bitmask),
1485             opts => $in,
1486             }
1487             );
1488             }
1489             }
1490             else
1491             {
1492             # open read-only first to safely check if we can get a lock.
1493 73 100       389 if ( -e $clean_name )
1494             {
1495 22 50       470 open $write_fh, '<', $clean_name
1496             or return $this->_throw(
1497             'bad open' => {
1498             filename => $clean_name,
1499             mode => 'read',
1500             exception => $!,
1501             cmd => $mode . $clean_name,
1502             opts => $in,
1503             }
1504             );
1505              
1506             # lock file before I/O on platforms that support it
1507 22         42 my $lockstat = $this->_seize( $clean_name, $write_fh, $in );
1508              
1509 22 50       34 return unless $lockstat;
1510              
1511             # you can't use UTF8 'mode' on system IO, so if a user requests
1512             # UTF8, we have to use PerlIO
1513 22 100 66     50 if ( $in->{binmode} && lc $in->{binmode} eq 'utf8' )
1514             {
1515             open
1516             $write_fh,
1517 2 50       68 $$MODES{popen}{ $mode },
1518             $clean_name
1519             or return $this->_throw(
1520             'bad open' => {
1521             filename => $clean_name,
1522             mode => $mode,
1523             opts => $in,
1524             exception => $!,
1525             cmd => $mode . $clean_name,
1526             }
1527             );
1528             }
1529             else
1530             {
1531             sysopen
1532             $write_fh,
1533             $clean_name,
1534 20 50       491 $$MODES{sysopen}{ $mode }
1535             or return $this->_throw(
1536             'bad open' => {
1537             filename => $clean_name,
1538             mode => $mode,
1539             opts => $in,
1540             exception => $!,
1541             cmd => qq($clean_name, $$MODES{sysopen}{ $mode }),
1542             }
1543             );
1544             }
1545             }
1546             else { # only non-existent files get bitmask arguments
1547             # ...unless doing utf8 business, in which case it's irrelevant
1548              
1549             # you can't use UTF8 'mode' on system IO, so if a user requests
1550             # UTF8, we have to use PerlIO
1551 51 50 33     114 if ( $in->{binmode} && lc $in->{binmode} eq 'utf8' )
1552             {
1553             open
1554             $write_fh,
1555 0 0       0 $$MODES{popen}{ $mode },
1556             $clean_name
1557             or return $this->_throw(
1558             'bad open' => {
1559             filename => $clean_name,
1560             mode => $mode,
1561             opts => $in,
1562             exception => $!,
1563             cmd => $mode . $clean_name,
1564             }
1565             );
1566             }
1567             else
1568             {
1569             sysopen
1570             $write_fh,
1571             $clean_name,
1572 51 50       2305 $$MODES{sysopen}{ $mode },
1573             $bitmask
1574             or return $this->_throw(
1575             'bad open' => {
1576             filename => $clean_name,
1577             mode => $mode,
1578             opts => $in,
1579             exception => $!,
1580             cmd => qq($clean_name, $$MODES{sysopen}{$mode}, $bitmask),
1581             }
1582             );
1583             }
1584              
1585             # lock file before I/O on platforms that support it
1586 51         132 my $lockstat = $this->_seize( $clean_name, $write_fh, $in );
1587              
1588 51 50       81 return unless $lockstat;
1589             }
1590              
1591             # now truncate
1592 73 100       126 if ( $mode ne 'append' ) {
1593              
1594 65 50       1082 truncate( $write_fh, 0 ) or return $this->_throw(
1595             'bad systrunc' => {
1596             filename => $clean_name,
1597             exception => $!,
1598             opts => $in,
1599             }
1600             );
1601             }
1602             }
1603              
1604 73 100       123 if ( $in->{binmode} )
1605             {
1606 2 50       4 if ( lc $in->{binmode} eq 'utf8' )
    0          
1607             {
1608 2 50       3 if ( $HAVE_UU )
1609             {
1610 2         14 binmode $write_fh, ':unix:encoding(UTF-8)';
1611              
1612 2         68 print $write_fh $content; # utf8 filehandles use PerlIO
1613             }
1614             else
1615             {
1616 0         0 close $write_fh;
1617              
1618 0         0 return $this->_throw( 'no unicode' => $in );
1619             }
1620             }
1621             elsif ( $in->{binmode} == 1 )
1622             {
1623 0         0 binmode $write_fh;
1624              
1625 0         0 syswrite( $write_fh, $content );
1626             }
1627             else
1628             {
1629 0         0 binmode $write_fh, $in->{binmode}; # apply user-specified IO layer(s)
1630              
1631 0         0 syswrite( $write_fh, $content );
1632             }
1633             }
1634             else
1635             {
1636 71         910 syswrite( $write_fh, $content );
1637             }
1638              
1639             # release lock on the file
1640              
1641 72 50 33     364 $this->_release( $write_fh, $in ) unless $$in{no_lock} || !$USE_FLOCK;
1642              
1643 72 50       362 close $write_fh or
1644             return $this->_throw(
1645             'bad close' => {
1646             filename => $clean_name,
1647             mode => $mode,
1648             exception => $!,
1649             opts => $in,
1650             }
1651             );
1652              
1653 72         339 return 1;
1654             }
1655              
1656              
1657             # --------------------------------------------------------
1658             # File::Util::_seize()
1659             # --------------------------------------------------------
1660             sub _seize {
1661 134     134   187 my ( $this, $file, $fh, $opts ) = @_;
1662              
1663 134 50       206 return $this->_throw( 'no handle passed to _seize.' => $opts )
1664             unless $fh;
1665              
1666 134 50       172 $file = defined $file ? $file : ''; # yes, even files named "0" are allowed
1667              
1668 134 50       217 return $this->_throw( 'no file name passed to _seize.' => $opts )
1669             unless length $file;
1670              
1671             # forget seizing if system can't flock
1672 134 50       178 return $fh if !$CAN_FLOCK;
1673              
1674 134         207 my @policy = @ONLOCKFAIL;
1675              
1676             # seize filehandle, return it if lock is successful
1677              
1678 134         219 while ( @policy ) {
1679              
1680 135         105 my $fh = &{ $_LOCKS->{ shift @policy } }( $this, $file, $fh, $opts );
  135         380  
1681              
1682 135 100 66     412 return $fh if $fh || !scalar @policy;
1683             }
1684              
1685 0         0 return $fh;
1686             }
1687              
1688              
1689             # --------------------------------------------------------
1690             # File::Util::_release()
1691             # --------------------------------------------------------
1692             sub _release {
1693              
1694 128     128   137 my ( $this, $fh, $opts ) = @_;
1695              
1696 128 50 33     443 return $this->_throw(
1697             'not a filehandle.' => { opts => $opts, argtype => ref $fh } )
1698             unless $fh && ref $fh eq 'GLOB';
1699              
1700 128 50       175 if ( $CAN_FLOCK ) { flock $fh, &Fcntl::LOCK_UN }
  128         429  
1701 128         137 return 1;
1702             }
1703              
1704              
1705             # --------------------------------------------------------
1706             # File::Util::valid_filename()
1707             # --------------------------------------------------------
1708             sub valid_filename {
1709 329     329 1 466 my $f = _myargs( @_ );
1710              
1711 329         563 $f =~ s/$WINROOT//; # windows abs paths would throw this off
1712              
1713 329 100       1009 $f !~ /$ILLEGAL_CHR/ ? 1 : undef;
1714             }
1715              
1716              
1717             # --------------------------------------------------------
1718             # File::Util::strip_path()
1719             # --------------------------------------------------------
1720             sub strip_path {
1721 123     123 1 227 my $arg = _myargs( @_ );
1722              
1723 123         458 my ( $stripped ) = $arg =~ /^.*$DIRSPLIT(.+)/o;
1724              
1725 123 50       375 return $stripped if defined $stripped;
1726              
1727 0         0 return $arg;
1728             }
1729              
1730              
1731             # --------------------------------------------------------
1732             # File::Util::atomize_path()
1733             # --------------------------------------------------------
1734             sub atomize_path {
1735 176     176 1 7529 my $fqfn = _myargs( @_ );
1736              
1737 176         668 $fqfn =~ m/$ATOMIZER/o;
1738              
1739             # root = $1
1740             # path = $2
1741             # file = $3
1742              
1743 176   100     1131 return( $1||'', $2||'', $3||'' );
      100        
      100        
1744             }
1745              
1746              
1747             # --------------------------------------------------------
1748             # File::Util::split_path()
1749             # --------------------------------------------------------
1750             sub split_path {
1751 0     0 1 0 my $path = _myargs( @_ );
1752              
1753             # find root of tree (if path was absolute)
1754 0         0 my ( $root, $branch, $leaf ) = atomize_path( $path );
1755              
1756 0         0 my @path_dirs = split /$DIRSPLIT/o, $branch;
1757              
1758 0 0       0 unshift @path_dirs, $root if $root;
1759 0 0       0 push @path_dirs, $leaf if $leaf;
1760              
1761 0         0 return @path_dirs;
1762             }
1763              
1764              
1765             # --------------------------------------------------------
1766             # File::Util::line_count()
1767             # --------------------------------------------------------
1768             sub line_count {
1769 2     2 1 3 my( $this, $file ) = @_;
1770 2         3 my $buff = '';
1771 2         2 my $lines = 0;
1772 2         4 my $cmd = '<' . $file;
1773              
1774 2 50       51 open my $fh, '<', $file or
1775             return $this->_throw(
1776             'bad open',
1777             {
1778             'filename' => $file,
1779             'mode' => 'read',
1780             'exception' => $!,
1781             'cmd' => $cmd,
1782             }
1783             );
1784              
1785 2         15 while ( sysread( $fh, $buff, 4096 ) ) {
1786              
1787 1         3 $lines += $buff =~ tr/\n//;
1788              
1789 1         4 $buff = '';
1790             }
1791              
1792 2         8 close $fh;
1793              
1794 2         9 return $lines;
1795             }
1796              
1797              
1798             # --------------------------------------------------------
1799             # File::Util::bitmask()
1800             # --------------------------------------------------------
1801             sub bitmask {
1802 5     5 1 15 my $f = _myargs( @_ );
1803              
1804 5 50       117 defined $f and -e $f ? sprintf('%04o',(stat($f))[2] & oct 777) : undef
    50          
1805             }
1806              
1807              
1808             # --------------------------------------------------------
1809             # File::Util::can_flock()
1810             # --------------------------------------------------------
1811 1     1 1 17 sub can_flock { $CAN_FLOCK }
1812              
1813              
1814             # --------------------------------------------------------
1815             # File::Util::can_utf8()
1816             # --------------------------------------------------------
1817 0     0 1 0 sub can_utf8 { $HAVE_UU }
1818              
1819              
1820             # File::Util::--------------------------------------------
1821             # is_readable(), is_writable() -- was: can_read(), can_write()
1822             # --------------------------------------------------------
1823 1 50   1 1 16 sub is_readable { my $f = _myargs( @_ ); defined $f ? -r $f : undef }
  1         17  
1824 5 50   5 1 1017 sub is_writable { my $f = _myargs( @_ ); defined $f ? -w $f : undef }
  5         67  
1825              
1826              
1827             # --------------------------------------------------------
1828             # File::Util::created()
1829             # --------------------------------------------------------
1830             sub created {
1831 5     5 1 13 my $f = _myargs( @_ );
1832              
1833 5 50       115 defined $f and -e $f ? $^T - ((-M $f) * 60 * 60 * 24) : undef
    50          
1834             }
1835              
1836              
1837             # --------------------------------------------------------
1838             # File::Util::ebcdic()
1839             # --------------------------------------------------------
1840 1     1 1 5 sub ebcdic { $EBCDIC }
1841              
1842              
1843             # --------------------------------------------------------
1844             # File::Util::escape_filename()
1845             # --------------------------------------------------------
1846             sub escape_filename {
1847 2     2 1 830 my( $file, $escape, $also ) = _myargs( @_ );
1848              
1849 2 50       4 return '' unless defined $file;
1850              
1851 2 100       3 $escape = '_' if !defined $escape;
1852              
1853 2 100       4 if ( $also ) { $file =~ s/\Q$also\E/$escape/g }
  1         21  
1854              
1855 2         16 $file =~ s/$ILLEGAL_CHR/$escape/g;
1856 2         9 $file =~ s/$DIRSPLIT/$escape/g;
1857              
1858 2         7 $file
1859             }
1860              
1861              
1862             # --------------------------------------------------------
1863             # File::Util::existent()
1864             # --------------------------------------------------------
1865 2 50   2 1 6 sub existent { my $f = _myargs( @_ ); defined $f ? -e $f : undef }
  2         27  
1866              
1867              
1868             # --------------------------------------------------------
1869             # File::Util::touch()
1870             # --------------------------------------------------------
1871             sub touch {
1872 26     26 1 3882 my $this = shift @_;
1873 26   50     54 my $file = shift @_ || '';
1874 26         60 my $opts = $this->_remove_opts( \@_ );
1875 26         20 my $path;
1876              
1877 26 50 33     94 return $this->_throw(
1878             'no input',
1879             {
1880             meth => 'touch',
1881             missing => 'a file name or file handle reference',
1882             opts => $opts,
1883             }
1884             ) unless defined $file && length $file;
1885              
1886 26         46 $path = $this->return_path( $file );
1887              
1888             # see if the file exists already and is a directory
1889 26 50 66     457 return $this->_throw(
1890             'cant touch on a dir',
1891             {
1892             filename => $file,
1893             dirname => $path,
1894             opts => $opts,
1895             }
1896             ) if -e $file && -d $file;
1897              
1898             # it's good to know beforehand whether or not we have permission to open
1899             # and read from this file allowing us to handle such an exception before
1900             # it handles us.
1901              
1902             # first check the readability of the file's housing dir
1903 26 50 66     438 return $this->_throw(
1904             'cant dread',
1905             {
1906             filename => $file,
1907             dirname => $path,
1908             opts => $opts,
1909             }
1910             ) if ( -e $path && !-r $path );
1911              
1912 26 100       178 $this->make_dir( $path ) unless -e $path;
1913              
1914             # create the file if it doesn't exist (like the *nix touch command does)
1915             # except we'll create it in binmode or with UTF-8 encoding if requested
1916             $this->write_file(
1917             $file => '' => { empty_writes_OK => 1, binmode => $opts->{binmode} }
1918 26 100       234 ) unless -e $file;
1919              
1920 26         43 my $now = time();
1921              
1922             # return
1923 26         392 return utime $now, $now, $file;
1924             }
1925              
1926              
1927             # --------------------------------------------------------
1928             # File::Util::file_type()
1929             # --------------------------------------------------------
1930             sub file_type {
1931 8     8 1 762 my $f = _myargs( @_ );
1932              
1933 8 50 33     120 return unless defined $f and -e $f;
1934              
1935 8         10 my @ret;
1936              
1937 8 100       40 push @ret, 'PLAIN' if -f $f; push @ret, 'TEXT' if -T $f;
  8 100       224  
1938 8 100       120 push @ret, 'BINARY' if -B $f; push @ret, 'DIRECTORY' if -d $f;
  8 100       73  
1939 8 50       44 push @ret, 'SYMLINK' if -l $f; push @ret, 'PIPE' if -p $f;
  8 50       36  
1940 8 50       38 push @ret, 'SOCKET' if -S $f; push @ret, 'BLOCK' if -b $f;
  8 50       35  
1941 8 50       33 push @ret, 'CHARACTER' if -c $f;
1942              
1943             ## no critic
1944 8 50       21 push @ret, 'TTY' if -t $f;
1945             ## use critic
1946              
1947 8 50       13 push @ret, 'ERROR: Cannot determine file type' unless scalar @ret;
1948              
1949 8         47 return @ret;
1950             }
1951              
1952              
1953             # --------------------------------------------------------
1954             # File::Util::flock_rules()
1955             # --------------------------------------------------------
1956             sub flock_rules {
1957 5     5 1 7 my $this = shift(@_);
1958 5         10 my @rules = _myargs( @_ );
1959              
1960 5 100       23 return @ONLOCKFAIL unless scalar @rules;
1961              
1962 3         13 my %valid = qw/
1963             NOBLOCKEX NOBLOCKEX
1964             NOBLOCKSH NOBLOCKSH
1965             BLOCKEX BLOCKEX
1966             BLOCKSH BLOCKSH
1967             FAIL FAIL
1968             WARN WARN
1969             IGNORE IGNORE
1970             UNDEF UNDEF
1971             ZERO ZERO /;
1972              
1973             map {
1974 3         5 return $this->_throw('bad flock rules', { 'bad' => $_, 'all' => \@rules })
1975 6 50       13 unless exists $valid{ $_ }
1976             } @rules;
1977              
1978 3         4 @ONLOCKFAIL = @rules;
1979              
1980             @ONLOCKFAIL
1981 3         12 }
1982              
1983              
1984             # --------------------------------------------------------
1985             # File::Util::is_bin()
1986             # --------------------------------------------------------
1987 2 50   2 1 6 sub is_bin { my $f = _myargs( @_ ); defined $f ? -B $f : undef }
  2         66  
1988              
1989              
1990             # --------------------------------------------------------
1991             # File::Util::last_access()
1992             # --------------------------------------------------------
1993             sub last_access {
1994 5   50 5 1 13 my $f = _myargs( @_ ); $f ||= '';
  5         10  
1995              
1996 5 50       53 return unless -e $f;
1997              
1998             # return the last accessed time of $f
1999 5         62 $^T - ((-A $f) * 60 * 60 * 24)
2000             }
2001              
2002              
2003             # --------------------------------------------------------
2004             # File::Util::last_modified()
2005             # --------------------------------------------------------
2006             sub last_modified {
2007 5   50 5 1 12 my $f = _myargs( @_ ); $f ||= '';
  5         8  
2008              
2009 5 50       47 return unless -e $f;
2010              
2011             # return the last modified time of $f
2012 5         55 $^T - ((-M $f) * 60 * 60 * 24)
2013             }
2014              
2015              
2016             # --------------------------------------------------------
2017             # File::Util::last_changed()
2018             # --------------------------------------------------------
2019             sub last_changed {
2020 2   50 2 1 5 my $f = _myargs( @_ ); $f ||= '';
  2         4  
2021              
2022 2 50       19 return unless -e $f;
2023              
2024             # return the last changed time of $f
2025 2         17 $^T - ((-C $f) * 60 * 60 * 24)
2026             }
2027              
2028              
2029             # --------------------------------------------------------
2030             # File::Util::load_dir()
2031             # --------------------------------------------------------
2032             sub load_dir {
2033 6     6 1 1269 my $this = shift @_;
2034 6         50 my $opts = $this->_remove_opts( \@_ );
2035 6         10 my $dir = shift @_;
2036              
2037 6         8 my @files = ( );
2038 6         6 my $dir_hash = { };
2039 6         8 my $dir_list = [ ];
2040              
2041 6   50     12 $dir ||= '';
2042              
2043 6 50       10 return $this->_throw(
2044             'no input' => {
2045             meth => 'load_dir',
2046             missing => 'a directory name',
2047             opts => $opts,
2048             }
2049             ) unless length $dir;
2050              
2051 6         21 @files = $this->list_dir( $dir => { files_only => 1 } );
2052              
2053             # map the content of each file into a hash key-value element where the
2054             # key name for each file is the name of the file
2055 6 100 100     27 if ( !$opts->{as_list} && !$opts->{as_listref} ) {
2056              
2057 1         3 foreach ( @files ) {
2058              
2059 8         23 $dir_hash->{ $_ } = $this->load_file( $dir . SL . $_ );
2060             }
2061              
2062 1         4 return $dir_hash;
2063             }
2064             else {
2065              
2066 5         13 foreach ( @files ) {
2067              
2068 40         176 push @$dir_list, $this->load_file( $dir . SL . $_ );
2069             }
2070              
2071 5 100       22 return $dir_list if $opts->{as_listref};
2072              
2073 1         7 return @$dir_list;
2074             }
2075              
2076 0         0 return $dir_hash;
2077             }
2078              
2079              
2080             # --------------------------------------------------------
2081             # File::Util::make_dir()
2082             # --------------------------------------------------------
2083             sub make_dir {
2084 10     10 1 1530 my $this = shift @_;
2085 10         27 my $opts = $this->_remove_opts( \@_ );
2086 10         16 my( $dir, $bitmask ) = @_;
2087              
2088 10 50       22 $bitmask = defined $bitmask ? $bitmask : $opts->{bitmask};
2089 10   50     38 $bitmask ||= oct 777;
2090              
2091             # if the call to this method didn't include a directory name to create,
2092             # then complain about it
2093 10 50 33     39 return $this->_throw(
2094             'no input',
2095             {
2096             meth => 'make_dir',
2097             missing => 'a directory name',
2098             opts => $opts,
2099             }
2100             ) unless defined $dir && length $dir;
2101              
2102 10 100       18 if ( $opts->{if_not_exists} ) {
2103              
2104 2 50       39 if ( -e $dir ) {
2105              
2106 0 0       0 return $dir if -d $dir;
2107              
2108 0         0 return $this->_throw(
2109             'called mkdir on a file',
2110             {
2111             filename => $dir,
2112             dirname => join( SL, split /$DIRSPLIT/, $dir ) . SL,
2113             opts => $opts,
2114             }
2115             );
2116             }
2117             }
2118             else {
2119              
2120 8 50       140 if ( -e $dir ) {
2121              
2122 0 0       0 return $this->_throw(
2123             'called mkdir on a file',
2124             {
2125             filename => $dir,
2126             dirname => join( SL, split /$DIRSPLIT/, $dir ) . SL,
2127             opts => $opts,
2128             }
2129             ) unless -d $dir;
2130              
2131 0         0 return $this->_throw(
2132             'make_dir target exists',
2133             {
2134             dirname => $dir,
2135             filetype => [ $this->file_type( $dir ) ],
2136             opts => $opts,
2137             }
2138             );
2139             }
2140             }
2141              
2142 10         210 my ( $winroot ) = $dir =~ /^($WINROOT)/;
2143              
2144 10         80 $dir =~ s/^($WINROOT)//;
2145 10         201 $dir =~ s/$DIRSPLIT{2,}/$SL/o;
2146 10 50       198 $dir =~ s/$DIRSPLIT+$//o unless $dir eq SL;
2147 10 50       27 $dir = $winroot . $dir if $winroot;
2148              
2149 10         34 my ( $root, $path ) = atomize_path( $dir . SL );
2150              
2151 10         79 my @dirs_in_path = split /$DIRSPLIT/, $path;
2152              
2153             # if prospective file name has illegal chars then complain
2154 10         18 foreach ( @dirs_in_path ) {
2155              
2156 50 50       64 return $this->_throw(
2157             'bad chars',
2158             {
2159             string => $_,
2160             purpose => 'the name of a file or directory',
2161             opts => $opts,
2162             }
2163             ) if !$this->valid_filename( $_ );
2164             }
2165              
2166             # do this AFTER the above check!!
2167 10 50       28 unshift @dirs_in_path, $root if $root;
2168              
2169             # qualify each subdir in @dirs_in_path by prepending its preceeding dir
2170             # names to it. Above, "/foo/bar/baz" becomes ("/", "foo", "bar", "baz")
2171             # and below it becomes ("/", "/foo", "/foo/bar", "/foo/bar/baz")
2172              
2173 10 50       21 if ( @dirs_in_path > 1 ) {
2174 10         22 for ( my $depth = 1; $depth < @dirs_in_path; ++$depth ) {
2175              
2176 50 100       63 if ( $dirs_in_path[ $depth-1 ] eq SL ) {
2177              
2178 10         24 $dirs_in_path[ $depth ] = SL . $dirs_in_path[ $depth ]
2179             }
2180             else {
2181              
2182 40         105 $dirs_in_path[ $depth ] =
2183             join SL, @dirs_in_path[ ( $depth - 1 ) .. $depth ]
2184             }
2185             }
2186             }
2187              
2188 10         14 my $i = 0;
2189              
2190 10         15 foreach ( @dirs_in_path ) {
2191 60         54 my $dir = $_;
2192 60 100       85 my $up = ( $i > 0 ) ? $dirs_in_path[ $i - 1 ] : '..';
2193              
2194 60         36 ++$i;
2195              
2196 60 50 66     679 if ( -e $dir && !-d $dir ) {
2197              
2198 0         0 return $this->_throw(
2199             'called mkdir on a file',
2200             {
2201             filename => $dir,
2202             dirname => $up . SL,
2203             opts => $opts,
2204             }
2205             );
2206             }
2207              
2208 60 100       316 next if -e $dir;
2209              
2210             # it's good to know beforehand whether or not we have permission to
2211             # create dirs here, which allows us to handle such an exception
2212             # before it handles us.
2213 20 50       136 return $this->_throw(
2214             'cant dcreate',
2215             {
2216             dirname => $dir,
2217             parentd => $up,
2218             opts => $opts,
2219             }
2220             ) unless -w $up;
2221              
2222 20 50       772 mkdir( $dir, $bitmask ) or
2223             return $this->_throw(
2224             'bad make_dir',
2225             {
2226             exception => $!,
2227             dirname => $dir,
2228             bitmask => $bitmask,
2229             opts => $opts,
2230             }
2231             );
2232             }
2233              
2234 10         54 return $dir;
2235             }
2236              
2237              
2238             # --------------------------------------------------------
2239             # File::Util::abort_depth()
2240             # --------------------------------------------------------
2241             sub abort_depth {
2242 3     3 1 346 my $arg = _myargs( @_ );
2243 3         4 my $this = shift @_;
2244              
2245 3 50       6 if ( defined $arg ) {
2246              
2247 0 0       0 return File::Util->new->_throw( 'bad abort_depth' => { bad => $arg } )
2248             if $arg =~ /\D/;
2249              
2250 0         0 $ABORT_DEPTH = $arg;
2251              
2252             $this->{opts}->{abort_depth} = $arg
2253 0 0 0     0 if blessed $this && $this->{opts};
2254             }
2255              
2256 3         10 return $ABORT_DEPTH;
2257             }
2258              
2259             # --------------------------------------------------------
2260             # File::Util::onfail()
2261             # --------------------------------------------------------
2262             sub onfail {
2263 0     0 1 0 my ( $this, $arg ) = @_;
2264              
2265 0 0       0 return unless blessed $this;
2266              
2267 0 0       0 $this->{opts}->{onfail} = $arg if $arg;
2268              
2269 0         0 return $this->{opts}->{onfail};
2270             }
2271              
2272              
2273             # --------------------------------------------------------
2274             # File::Util::read_limit()
2275             # --------------------------------------------------------
2276             sub read_limit {
2277 5     5 1 11 my $arg = _myargs( @_ );
2278 5         6 my $this = shift @_;
2279              
2280 5 50       7 if ( defined $arg ) {
2281              
2282 0 0       0 return File::Util->new->_throw ( 'bad read_limit' => { bad => $arg } )
2283             if $arg =~ /\D/;
2284              
2285 0         0 $READ_LIMIT = $arg;
2286              
2287             $this->{opts}->{read_limit} = $arg
2288 0 0 0     0 if blessed $this && $this->{opts};
2289             }
2290              
2291 5         16 return $READ_LIMIT;
2292             }
2293              
2294              
2295             # --------------------------------------------------------
2296             # File::Util::diagnostic()
2297             # --------------------------------------------------------
2298             sub diagnostic {
2299 0     0 1 0 my $arg = _myargs( @_ );
2300 0         0 my $this = shift @_;
2301              
2302 0 0       0 if ( defined $arg ) {
2303              
2304 0 0       0 $WANT_DIAGNOSTICS = $arg ? 1 : 0;
2305              
2306             $this->{opts}->{diag} = $arg ? 1 : 0
2307 0 0 0     0 if blessed $this && $this->{opts};
    0          
2308             }
2309              
2310 0         0 return $WANT_DIAGNOSTICS;
2311             }
2312              
2313              
2314             # --------------------------------------------------------
2315             # File::Util::needs_binmode()
2316             # --------------------------------------------------------
2317 1     1 1 5 sub needs_binmode { $NEEDS_BINMODE }
2318              
2319              
2320             # --------------------------------------------------------
2321             # File::Util::open_handle()
2322             # --------------------------------------------------------
2323             sub open_handle {
2324 5     5 1 634 my $this = shift @_;
2325 5         16 my $in = $this->_parse_in( @_ );
2326 5         9 my $file = '';
2327 5         6 my $mode = '';
2328 5   50     22 my $bitmask = $in->{bitmask} || oct 777;
2329 5         5 my $raw_name = $file;
2330 5         5 my $fh; # will be the lexical file handle scoped to this method
2331 5         10 my ( $root, $path, $clean_name, @dirs ) =
2332             ( '', '', '', () );
2333              
2334             # get name of file when passed in as a name/value pair...
2335              
2336             $file =
2337             exists $in->{filename} &&
2338             defined $in->{filename} &&
2339             length $in->{filename}
2340             ? $in->{filename}
2341             : exists $in->{file} &&
2342             defined $in->{file} &&
2343             length $in->{file}
2344             ? $in->{file}
2345 5 100 33     59 : '';
    50 66        
2346              
2347             # ...or fall back to support of two-argument form of invocation
2348              
2349 5 50       7 my $maybe_file = shift @_; $maybe_file = '' if !defined $maybe_file;
  5         17  
2350 5 50       6 my $maybe_mode = shift @_; $maybe_mode = '' if !defined $maybe_mode;
  5         10  
2351              
2352 5 100 66     27 $file = $maybe_file if !ref $maybe_file && $file eq '';
2353             $mode =
2354             !ref $maybe_mode &&
2355             !exists $in->{mode}
2356             ? $maybe_mode
2357 5 100 66     25 : $in->{mode};
2358              
2359 5   50     14 $mode ||= 'read';
2360              
2361              
2362 5         100 my ( $winroot ) = $file =~ /^($WINROOT)/;
2363              
2364 5         44 $file =~ s/^($WINROOT)//;
2365 5         97 $file =~ s/$DIRSPLIT{2,}/$SL/o;
2366 5 50       93 $file =~ s/$DIRSPLIT+$//o unless $file eq SL;
2367 5 50       14 $file = $winroot . $file if $winroot;
2368              
2369 5         7 $raw_name = $file; # preserve original filename input before line below:
2370              
2371 5         12 ( $root, $path, $file ) = atomize_path( $file );
2372              
2373             # begin user input validation/sanitation sequence
2374              
2375             # if the call to this method didn't include a filename to which the caller
2376             # wants us to write, then complain about it
2377 5 50       16 return $this->_throw(
2378             'no input',
2379             {
2380             meth => 'open_handle',
2381             missing => 'a file name to create, write, read/write, or append',
2382             opts => $in,
2383             }
2384             ) unless length $file;
2385              
2386 5 50 66     27 if ( $mode eq 'read' && !-e $raw_name ) {
2387              
2388             # if the file doesn't exist, send back an error
2389 0 0       0 return $this->_throw(
2390             'no such file',
2391             {
2392             filename => $raw_name,
2393             opts => $in,
2394             }
2395             ) unless -e $clean_name;
2396             }
2397              
2398             # if prospective filename contains 2+ dir separators in sequence then
2399             # this is a syntax error we need to whine about
2400             {
2401 5         7 my $try_filename = $raw_name;
  5         7  
2402              
2403 5         20 $try_filename =~ s/$WINROOT//; # windows abs paths would throw this off
2404              
2405 5 50       128 return $this->_throw(
2406             'bad chars',
2407             {
2408             string => $raw_name,
2409             purpose => 'the name of a file or directory',
2410             opts => $in,
2411             }
2412             ) if $try_filename =~ /(?:$DIRSPLIT){2,}/;
2413             }
2414              
2415             # determine existance of the file path, make directory(ies) for the
2416             # path if the full directory path doesn't exist
2417 5         26 @dirs = split /$DIRSPLIT/, $path;
2418              
2419             # if prospective file name has illegal chars then complain
2420 5         11 foreach ( @dirs ) {
2421              
2422 8 50       15 return $this->_throw(
2423             'bad chars',
2424             {
2425             string => $_,
2426             purpose => 'the name of a file or directory',
2427             opts => $in,
2428             }
2429             ) if !$this->valid_filename( $_ );
2430             }
2431              
2432             # do this AFTER the above check!!
2433 5 50       16 unshift @dirs, $root if $root;
2434              
2435             # make sure that open mode is a valid mode
2436 5 50 33     34 if (
2437             !exists $in->{use_sysopen} &&
2438             !defined $in->{use_sysopen}
2439             ) {
2440             # native Perl open modes
2441 5 50 33     35 unless (
2442             exists $$MODES{popen}{ $mode } &&
2443             defined $$MODES{popen}{ $mode }
2444             ) {
2445 0         0 return $this->_throw(
2446             'bad openmode popen',
2447             {
2448             meth => 'open_handle',
2449             filename => $raw_name,
2450             badmode => $mode,
2451             opts => $in,
2452             }
2453             )
2454             }
2455             }
2456             else {
2457             # system open modes
2458 0 0 0     0 unless (
2459             exists $$MODES{sysopen}{ $mode } &&
2460             defined $$MODES{sysopen}{ $mode }
2461             ) {
2462 0         0 return $this->_throw(
2463             'bad openmode sysopen',
2464             {
2465             meth => 'open_handle',
2466             filename => $raw_name,
2467             badmode => $mode,
2468             opts => $in,
2469             }
2470             )
2471             }
2472             }
2473              
2474             # cleanup file name - if path is relative, normalize it
2475             # - /foo/bar/baz.txt stays as /foo/bar/baz.txt
2476             # - foo/bar/baz.txt becomes ./foo/bar/baz.txt
2477             # - baz.txt stays as baz.txt
2478 5 50 33     17 if ( !length $root && !length $path ) {
2479              
2480 0         0 $path = '.' . SL;
2481             }
2482             else { # otherwise path normalized at end
2483              
2484 5         6 $path .= SL;
2485             }
2486              
2487             # final clean filename assembled
2488 5         11 $clean_name = $root . $path . $file;
2489              
2490             # create path preceding file if path doesn't exist and not in read mode
2491 5 50 66     88 if ( $mode ne 'read' && !-e $root . $path ) {
2492              
2493 0         0 my $make_dir_ok = 1;
2494              
2495             my $make_dir_return = $this->make_dir(
2496             $root . $path,
2497             exists $in->{dbitmask} &&
2498             defined $in->{dbitmask}
2499             ? $in->{dbitmask}
2500             : oct 777,
2501             {
2502             diag => $in->{diag},
2503             onfail => sub {
2504 0     0   0 my ( $err, $trace ) = @_;
2505              
2506             return $in->{onfail}
2507             if ref $in->{onfail} &&
2508 0 0 0     0 ref $in->{onfail} eq 'CODE';
2509              
2510 0         0 $make_dir_ok = 0;
2511              
2512 0         0 return $err . $trace;
2513             }
2514             }
2515 0 0 0     0 );
2516              
2517 0 0       0 die $make_dir_return unless $make_dir_ok;
2518             }
2519              
2520             # sanity checks based on requested mode
2521 5 100 100     43 if (
    50 66        
      66        
      33        
      33        
2522             $mode eq 'write' ||
2523             $mode eq 'append' ||
2524             $mode eq 'rwcreate' ||
2525             $mode eq 'rwclobber' ||
2526             $mode eq 'rwappend'
2527             ) {
2528             # Check whether or not we have permission to open and perform writes
2529             # on this file.
2530              
2531 4 100       58 if ( -e $clean_name ) {
2532              
2533 3 50       33 return $this->_throw(
2534             'cant fwrite',
2535             {
2536             filename => $clean_name,
2537             dirname => $root . $path,
2538             opts => $in,
2539             }
2540             ) unless -w $clean_name;
2541             }
2542             else {
2543             # If file doesn't exist and the path isn't writable, the error is
2544             # one of unallowed creation.
2545 1 50       11 return $this->_throw(
2546             'cant fcreate',
2547             {
2548             filename => $clean_name,
2549             dirname => $root . $path,
2550             opts => $in,
2551             }
2552             ) unless -w $root . $path;
2553             }
2554             }
2555             elsif ( $mode eq 'read' || $mode eq 'rwupdate' ) {
2556             # Check whether or not we have permission to open and perform reads
2557             # on this file, starting with file's housing directory.
2558 1 50       9 return $this->_throw(
2559             'cant dread',
2560             {
2561             filename => $clean_name,
2562             dirname => $root . $path,
2563             opts => $in,
2564             }
2565             ) unless -r $root . $path;
2566              
2567             # Seems obvious, but we can't read non-existent files
2568 1 50       7 return $this->_throw(
2569             'cant fread not found',
2570             {
2571             filename => $clean_name,
2572             dirname => $root . $path,
2573             opts => $in,
2574             }
2575             ) unless -e $clean_name;
2576              
2577             # Check the readability of the file itself
2578 1 50       7 return $this->_throw(
2579             'cant fread',
2580             {
2581             filename => $clean_name,
2582             dirname => $root . $path,
2583             opts => $in,
2584             }
2585             ) unless -r $clean_name;
2586             }
2587             else {
2588 0         0 return $this->_throw(
2589             'no input',
2590             {
2591             meth => 'open_handle',
2592             missing => q{a valid IO mode. (eg- 'read', 'write'...)},
2593             opts => $in,
2594             }
2595             );
2596             }
2597              
2598             # Final bit of input validation made necessary by the would-be perils
2599             # of IO encoding while using sys(open,read,write,seek,tell,etc) -
2600             # Basically, using :utf8 encoding with syswrite is deprecated
2601 5 0 33     16 if
      0        
      33        
2602             (
2603             ( exists $in->{use_sysopen} && defined $in->{use_sysopen} ) &&
2604             ( $in->{binmode} && lc $in->{binmode} eq 'utf8' )
2605             )
2606             {
2607 0         0 return $this->_throw(
2608             'bad binmode',
2609             {
2610             meth => 'open_handle',
2611             filename => $clean_name,
2612             dirname => $root . $path,
2613             opts => $in,
2614             }
2615             );
2616             }
2617              
2618             # input validation sequence finished
2619              
2620 5 50 33     38 if ( $$in{no_lock} || !$USE_FLOCK ) {
2621 0 0 0     0 if (
2622             !exists $in->{use_sysopen} &&
2623             !defined $in->{use_sysopen}
2624             ) { # perl open
2625             # get open mode
2626 0         0 $mode = $$MODES{popen}{ $mode };
2627              
2628 0 0       0 open $fh, $mode, $clean_name or
2629             return $this->_throw(
2630             'bad open',
2631             {
2632             filename => $clean_name,
2633             mode => $mode,
2634             exception => $!,
2635             cmd => $mode . $clean_name,
2636             opts => $in,
2637             }
2638             );
2639             }
2640             else { # sysopen
2641             # get open mode
2642 0         0 $mode = $$MODES{sysopen}{ $mode };
2643              
2644 0 0       0 sysopen( $fh, $clean_name, $mode ) or
2645             return $this->_throw(
2646             'bad open',
2647             {
2648             filename => $clean_name,
2649             mode => $mode,
2650             exception => $!,
2651             cmd => qq($clean_name, $mode),
2652             opts => $in,
2653             }
2654             );
2655             }
2656             }
2657             else {
2658 5 50 33     25 if (
2659             !exists $in->{use_sysopen} &&
2660             !defined $in->{use_sysopen}
2661             ) { # perl open
2662             # open read-only first to safely check if we can get a lock.
2663 5 100       49 if ( -e $clean_name ) {
2664              
2665 4 50       99 open $fh, '<', $clean_name or
2666             return $this->_throw(
2667             'bad open',
2668             {
2669             filename => $clean_name,
2670             mode => 'read',
2671             exception => $!,
2672             cmd => $mode . $clean_name,
2673             opts => $in,
2674             }
2675             );
2676              
2677             # lock file before I/O on platforms that support it
2678 4         12 my $lockstat = $this->_seize( $clean_name, $fh, $in );
2679              
2680 4 100 50     38 warn "returning $lockstat" && return $lockstat unless fileno $lockstat;
2681              
2682 3 100       9 if ( $mode ne 'read' ) {
2683              
2684             open $fh, $$MODES{popen}{ $mode }, $clean_name or
2685             return $this->_throw(
2686             'bad open',
2687             {
2688             exception => $!,
2689             filename => $clean_name,
2690             mode => $mode,
2691             opts => $in,
2692 2 50       73 cmd => $$MODES{popen}{ $mode } . $clean_name,
2693             }
2694             );
2695             }
2696             }
2697             else {
2698             open $fh, $$MODES{popen}{ $mode }, $clean_name or
2699             return $this->_throw(
2700             'bad open',
2701             {
2702             exception => $!,
2703             filename => $clean_name,
2704             mode => $mode,
2705             opts => $in,
2706 1 50       95 cmd => $$MODES{popen}{ $mode } . $clean_name,
2707             }
2708             );
2709              
2710             # lock file before I/O on platforms that support it
2711 1         7 my $lockstat = $this->_seize( $clean_name, $fh, $in );
2712              
2713 1 50       4 return $lockstat unless $lockstat;
2714             }
2715             }
2716             else { # sysopen
2717             # open read-only first to safely check if we can get a lock.
2718 0 0       0 if ( -e $clean_name ) {
2719              
2720 0 0       0 open $fh, '<', $clean_name or
2721             return $this->_throw(
2722             'bad open',
2723             {
2724             filename => $clean_name,
2725             mode => 'read',
2726             exception => $!,
2727             cmd => $mode . $clean_name,
2728             opts => $in,
2729             }
2730             );
2731              
2732             # lock file before I/O on platforms that support it
2733 0         0 my $lockstat = $this->_seize( $clean_name, $fh, $in );
2734              
2735 0 0       0 return $lockstat unless $lockstat;
2736              
2737 0 0       0 sysopen( $fh, $clean_name, $$MODES{sysopen}{ $mode } )
2738             or return $this->_throw(
2739             'bad open',
2740             {
2741             filename => $clean_name,
2742             mode => $mode,
2743             opts => $in,
2744             exception => $!,
2745             cmd => qq($clean_name, $$MODES{sysopen}{ $mode }),
2746             }
2747             );
2748             }
2749             else { # only non-existent files get bitmask arguments
2750             sysopen(
2751             $fh,
2752             $clean_name,
2753 0 0       0 $$MODES{sysopen}{ $mode },
2754             $bitmask
2755             ) or return $this->_throw(
2756             'bad open',
2757             {
2758             filename => $clean_name,
2759             mode => $mode,
2760             opts => $in,
2761             exception => $!,
2762             cmd => qq($clean_name, $$MODES{sysopen}{$mode}, $bitmask),
2763             }
2764             );
2765              
2766             # lock file before I/O on platforms that support it
2767 0         0 my $lockstat = $this->_seize( $clean_name, $fh, $in );
2768              
2769 0 0       0 return $lockstat unless $lockstat;
2770             }
2771             }
2772             }
2773              
2774             # call binmode on the filehandle if it was requested or UTF-8
2775 4 100       11 if ( $in->{binmode} )
2776             {
2777 2 50       5 if ( lc $in->{binmode} eq 'utf8' )
    0          
2778             {
2779 2 50       3 if ( $HAVE_UU )
2780             {
2781 2         12 binmode $fh, ':unix:encoding(UTF-8)';
2782             }
2783             else
2784             {
2785 0         0 close $fh;
2786              
2787 0         0 return $this->_throw( 'no unicode' => $in );
2788             }
2789             }
2790             elsif ( $in->{binmode} == 1 )
2791             {
2792 0         0 binmode $fh;
2793             }
2794             else
2795             {
2796             binmode $fh, $in->{binmode} # apply user-specified IO layer(s)
2797 0         0 }
2798             }
2799              
2800             # return file handle reference to the caller
2801 4         71 return $fh;
2802             }
2803              
2804              
2805             # --------------------------------------------------------
2806             # File::Util::unlock_open_handle()
2807             # --------------------------------------------------------
2808             sub unlock_open_handle {
2809 2     2 1 296 my( $this, $fh ) = @_;
2810              
2811 2 50       5 return 1 unless $USE_FLOCK;
2812              
2813 2 50 33     9 return $this->_throw(
2814             'not a filehandle' => {
2815             opts => $this->_remove_opts( \@_ ),
2816             argtype => ref $fh,
2817             }
2818             ) unless $fh && fileno $fh;
2819              
2820 2 50       32 return flock( $fh, &Fcntl::LOCK_UN ) if $CAN_FLOCK;
2821              
2822 0         0 return 0;
2823             }
2824              
2825              
2826             # --------------------------------------------------------
2827             # File::Util::return_path()
2828             # --------------------------------------------------------
2829 28     28 1 1517 sub return_path { my $f = _myargs( @_ ); $f =~ s/(^.*)$DIRSPLIT.*/$1/; $f }
  28         529  
  28         75  
2830              
2831              
2832             # --------------------------------------------------------
2833             # File::Util::strict_path()
2834             # --------------------------------------------------------
2835             sub strict_path
2836             {
2837 0     0 1 0 my $path = _myargs( @_ );
2838 0         0 my $copy = $path;
2839              
2840 0         0 ( $path ) = $path =~ /(^.*$DIRSPLIT)/;
2841              
2842 0 0       0 ( $path ) = $copy =~ /(^\.{1,2}$)/ if !defined $path;
2843              
2844 0 0       0 return unless defined $path;
2845              
2846 0 0       0 $path .= SL unless substr $path, -1, 1 =~ /$DIRSPLIT/;
2847              
2848 0 0       0 return $path =~ /$DIRSPLIT/ ? $path : undef;
2849             }
2850              
2851              
2852             # --------------------------------------------------------
2853             # File::Util::default_path()
2854             # --------------------------------------------------------
2855             sub default_path
2856             {
2857 0     0 1 0 my ( $path, $dflt ) = _myargs( @_ );
2858              
2859 0 0       0 $dflt = defined $dflt ? $dflt : '.' . SL;
2860              
2861 0         0 $path = strict_path( $path );
2862              
2863 0 0       0 return defined $path ? $path : $dflt;
2864             }
2865              
2866              
2867             # --------------------------------------------------------
2868             # File::Util::size()
2869             # --------------------------------------------------------
2870 5 50 50 5 1 1221 sub size { my $f = _myargs( @_ ); $f ||= ''; return unless -e $f; -s $f }
  5         8  
  5         55  
  5         45  
2871              
2872              
2873             # --------------------------------------------------------
2874             # File::Util::trunc()
2875             # --------------------------------------------------------
2876 2     2 1 9 sub trunc { $_[0]->write_file( { mode => trunc => file => $_[1] } ) }
2877              
2878              
2879             # --------------------------------------------------------
2880             # File::Util::use_flock()
2881             # --------------------------------------------------------
2882             sub use_flock {
2883 64     64 1 1573 my $arg = _myargs( @_ );
2884              
2885 64 100       99 $USE_FLOCK = !!$arg if defined $arg;
2886              
2887 64         169 return $USE_FLOCK;
2888             }
2889              
2890             # --------------------------------------------------------
2891             # File::Util::AUTOLOAD()
2892             # --------------------------------------------------------
2893             sub AUTOLOAD {
2894              
2895             # The main purpose of using autoload here is to avoid compiling in
2896             # copious amounts of error handling code at compile time, when in
2897             # the majority of cases and in production code-- such errors should
2898             # have already been debugged and the error handling mechanism will
2899             # end up getting invoked seldom if ever. There's no reason to pay
2900             # the performance penalty when it's not necessary.
2901             # The other purpose is to support legacy method names.
2902              
2903 2     2   408 ( my $name = our $AUTOLOAD ) =~ s/.*:://;
2904              
2905             # These are legacy method names, and their current replacements. In order
2906             # to future-proof things, this hashref is used as a dispatch table further
2907             # down in the code in lieu of potentially-growing if/else block, which
2908             # would ugly to maintain
2909              
2910 2         14 my $redirect_methods = {
2911             can_write => \&is_writable,
2912             can_read => \&is_readable,
2913             isbin => \&is_bin,
2914             readlimit => \&read_limit,
2915             max_dives => \&abort_depth,
2916             };
2917              
2918 2 100       8 if ( $name eq '_throw' )
    50          
2919             {
2920             *_throw = sub
2921             {
2922 1     1   2 my $this = shift @_;
2923 1   50     2 my $in = $this->_parse_in( @_ ) || { };
2924 1         1 my $error_class;
2925              
2926             # direct input can override object-global diag default, otherwise
2927             # the object's "want diagnostics" setting is inherited
2928              
2929             $in->{diag} = defined $in->{diag} && !$in->{diag}
2930             ? 0
2931             : $in->{diag}
2932             ? $in->{diag}
2933 1 50 33     10 : $this->{opts}->{diag};
    50          
2934              
2935 1 50 33     43 if
      33        
      33        
      33        
2936             (
2937             $in->{diag} ||
2938             ( $in->{opts} &&
2939             ref $in->{opts} &&
2940             ref $in->{opts} eq 'HASH' &&
2941             $in->{opts}->{diag}
2942             )
2943             )
2944             {
2945 0         0 require File::Util::Exception::Diagnostic;
2946              
2947 0         0 $error_class = 'File::Util::Exception::Diagnostic';
2948              
2949 0         0 unshift @_, $this, $error_class;
2950              
2951 0         0 goto \&File::Util::Exception::Diagnostic::_throw;
2952             }
2953             else
2954             {
2955 1         532 require File::Util::Exception::Standard;
2956              
2957 1         3 $error_class = 'File::Util::Exception::Standard';
2958              
2959 1         4 unshift @_, $this, $error_class;
2960              
2961 1         5 goto \&File::Util::Exception::Standard::_throw;
2962              
2963             }
2964 1         8 };
2965              
2966 1         4 goto \&_throw;
2967             }
2968             elsif ( exists $redirect_methods->{ $name } ) {
2969              
2970 21     21   167567 { no strict 'refs'; *{ $name } = $redirect_methods->{ $name } }
  21         30  
  21         2061  
  1         2  
  1         1  
  1         5  
2971              
2972 1         4 goto \&$name;
2973             }
2974              
2975 0         0 die qq(Unknown method: File::Util::$name\n);
2976             }
2977              
2978              
2979             # --------------------------------------------------------
2980             # File::Util::DESTROY()
2981             # --------------------------------------------------------
2982       1     sub DESTROY { }
2983              
2984             1;
2985              
2986              
2987             __END__