File Coverage

blib/lib/File/Misc.pm
Criterion Covered Total %
statement 69 505 13.6
branch 0 248 0.0
condition 0 77 0.0
subroutine 23 64 35.9
pod 13 31 41.9
total 105 925 11.3


line stmt bran cond sub pod time code
1             package File::Misc;
2 1     1   652 use strict;
  1         2  
  1         30  
3 1     1   4 use Carp 'croak';
  1         1  
  1         60  
4 1     1   597 use FileHandle;
  1         10553  
  1         5  
5 1     1   342 use Exporter;
  1         1  
  1         36  
6 1     1   5 use File::Find;
  1         1  
  1         57  
7 1     1   5 use File::Path;
  1         1  
  1         51  
8 1     1   664 use String::Util ':all';
  1         5773  
  1         254  
9 1     1   621 use File::chdir;
  1         3109  
  1         105  
10 1     1   7 use Fcntl ':mode', ':flock';
  1         1  
  1         315  
11              
12             # version
13             our $VERSION = '0.10';
14              
15             # debug tools
16             # use Debug::ShowStuff ':all';
17             # use Debug::ShowStuff::ShowVar;
18              
19              
20             #------------------------------------------------------------------------------
21             # opening POD
22             #
23              
24             =head1 NAME
25              
26             File::Misc -- handy file tools
27              
28             =head1 Description
29              
30             File::Misc provides a variety of utilities for working with files. These
31             utilities provides tools for reading in, writing out to, and getting
32             information about files.
33              
34             =head1 SYNOPSIS
35              
36             # slurp in the contents of a file
37             $var = slurp('myfile.txt');
38              
39             # spit content into a file
40             spit 'myfile.txt', $var;
41              
42             # get the lines in a file as an array
43             @arr = file_lines('myfile.txt');
44              
45             # get a list of all the files in a directory
46             @arr = files('/my/dir');
47              
48             # ensure a file is deleted - if it is already deleted return success
49             ensure_unlink('myfile.txt');
50              
51             # ensure a file exists, update its date to now
52             touch('myfile.txt');
53              
54             # many others
55              
56             =head1 INSTALLATION
57              
58             File::Misc can be installed with the usual routine:
59              
60             perl Makefile.PL
61             make
62             make test
63             make install
64              
65             =head1 FUNCTIONS
66              
67             =cut
68              
69             #
70             # opening POD
71             #------------------------------------------------------------------------------
72              
73              
74             #------------------------------------------------------------------------------
75             # import/export
76             #
77 1     1   6 use vars qw[@EXPORT_OK %EXPORT_TAGS @ISA];
  1         2  
  1         4470  
78             @ISA = 'Exporter';
79             @EXPORT_OK = qw[
80             slurp
81             spit
82             file_lines
83             files
84             touch
85            
86             ensure_dir
87             dir_ensure
88            
89             size
90             mod_date mod_time
91             mode
92             age
93             search_inc
94             search_isa
95             ensure_unlink unlink_ensure
96             script_dir
97             print_file_contents
98            
99             file_type
100            
101             eq_files eq_file ne_files ne_file
102            
103             build_tree
104             mirror_tree
105             tree_hash
106             lock_file
107            
108             stat_hash
109            
110             tmp_path
111             tmp_dir
112             ];
113              
114             %EXPORT_TAGS = ('all' => [@EXPORT_OK]);
115             #
116             # import/export
117             #------------------------------------------------------------------------------
118              
119              
120              
121             #------------------------------------------------------------------------------
122             # slurp
123             #
124              
125             =head2 slurp
126              
127             Returns the contents of the given file
128              
129             $var = slurp('myfile.txt');
130              
131             B max
132              
133             Sets the maximum amount in bytes to slurp in. By default the maximums is 100k.
134              
135             # set maximum to 1k
136             $var = slurp('myfile.txt', max=>1024);
137              
138             Set max to 0 to set no maximum.
139              
140             B firstline
141              
142             If true, only slurp in the first line.
143              
144             $line = slurp('myfile.txt', firstline=>1);
145              
146             B stdout, stderr
147              
148             If the C option is true, then the contents of the file are sent to
149             STDOUT and are not saved as a scalar at all. C returns true.
150              
151             slurp('myfile.txt', stdout=>1);
152              
153             The C option works the same way except that contents are sent to STDERR.
154             Both options can be set to true, and contents will be sent to both STDOUT and
155             STDERR.
156              
157             =cut
158              
159             sub slurp {
160 0     0 1   my ($path, %opts)=@_;
161 0           my ($chunk, $fh, @rv, $max, $stdout, $stderr, $out, $total);
162 0           $total = 0;
163            
164             # TESTING
165             # println subname(); ##i
166            
167             # don't slurp in more than this amount
168             # default is 100K
169 0 0         if (defined $opts{'max'})
170 0           { $max = $opts{'max'} }
171             else
172 0           { $max = 102400 }
173            
174             # send to stdout or stderr
175 0           $stdout = $opts{'stdout'};
176 0           $stderr = $opts{'stderr'};
177 0   0       $out = $opts{'stdout'} || $opts{'stderr'};
178            
179             # attempt to open
180 0 0         unless ($fh = FileHandle->new($path)){
181 0 0         $opts{'quiet'} and return undef;
182 0           croak "slurp: could not open file [$path] for reading: $!";
183             }
184            
185 0 0         $fh->binmode($fh) if $opts{'bin'};
186            
187             # if first line only
188 0 0         if ($opts{'firstline'}) {
189 0           $chunk = <$fh>;
190 0           $chunk =~ s|[\r\n]+$||s;
191            
192             # output to stdout and|or stderr
193 0 0         if ($stdout)
194 0           { print STDOUT $chunk }
195 0 0         if ($stderr)
196 0           { print STDERR $chunk }
197 0 0         if ($out)
198 0           { return 1 }
199            
200             # return
201 0           return $chunk;
202             }
203            
204             # slurp in everything
205             CHUNKLOOP:
206 0           while (read $fh, $chunk, 1024) {
207 0           push @rv, $chunk;
208 0           $total += length($chunk);
209            
210             # output to stdout and|or stderr
211 0 0         if ($stdout)
212 0           { print STDOUT $chunk }
213 0 0         if ($stderr)
214 0           { print STDERR $chunk }
215            
216 0 0 0       if ( $max && ($total > $max) ) {
217 0 0         if ($out)
218 0           { return 1 }
219            
220             # we're done reading in
221 0           last CHUNKLOOP;
222             }
223             }
224            
225             # return
226 0           return join('', @rv);
227             }
228             #
229             # slurp
230             #------------------------------------------------------------------------------
231              
232              
233              
234             #------------------------------------------------------------------------------
235             # spit
236             #
237              
238             =head2 spit
239              
240             The opposite of slurp(), spit() outputs the given string(s) to the given file
241             in a single command. In its simplest form, C takes a file path, then one
242             or more strings. Those strings are concatenated together and output the given
243             path. So, the following code outputs "hello world" to /tmp/myfile.txt.
244              
245             spit('/tmp/myfile.txt', 'hello world');
246              
247             If you want to append to the file (if it exists) then the first param should be
248             a hashref, with 'path' set to the path to the file and 'append' set to true,
249             like as follows.
250              
251             spit(
252             {path=>'/tmp/myfile.txt', append=>1},
253             'hello world'
254             );
255              
256             =cut
257              
258             sub spit {
259 0     0 1   my ($path, @data) = @_;
260 0           my ($out, $opentype);
261            
262 0 0         if (ref $path) {
263 0           my $opts = $path;
264 0           $path = $opts->{'path'};
265            
266 0 0         if ($opts->{'append'})
267 0           { $opentype = '>>' }
268             else
269 0           { $opentype = '>' }
270             }
271            
272             else {
273 0           $opentype = '>';
274             }
275            
276             #$out = FileHandle->new("$opentype $path")
277             # or croak "cannot open output file handle to $path: $!";
278            
279 0 0         open($out, $opentype , $path) or die $!;
280            
281             # print out data
282 0           print $out @data;
283             }
284             #
285             # spit
286             #------------------------------------------------------------------------------
287              
288              
289             #------------------------------------------------------------------------------
290             # file_lines
291             #
292              
293             =head2 file_lines
294              
295             Cfile_lines> returns the contents of one or more files as an array. Newlines
296             are stripped off the end of each line. So, for example, the following code
297             would the lines from buffer.txt:
298              
299             @lines = file_lines('buffer.txt');
300              
301             If the first param is an arrayref, then every file in the array is read. So,
302             the following code returns lines from buffer.txt and data.txt.
303              
304             @lines = file_lines(['buffer.txt', 'data.txt']);
305              
306             B
307              
308             C sets the maximum number of lines to return. So, the following code
309             indicates to send no more than 100 lines.
310              
311             @lines = file_lines('buffer.txt', max=>100);
312              
313             B
314              
315             If the C option is true, then C does not croak on error.
316             For example:
317              
318             @lines = file_lines('buffer.txt', quiet=>1);
319              
320             B
321              
322             If C is true, then empty lines are not returned. Note that a line
323             with just spaces or tabs is considered empty.
324              
325             @lines = file_lines('buffer.txt', skip_empty=>1);
326              
327             =cut
328              
329             sub file_lines {
330 0     0 1   my ($paths, %opts)=@_;
331 0           my ($total, $max, @rv, $skipempty);
332            
333             # initialize total to zero
334 0           $total = 0;
335            
336             # ensure paths is an array ref
337 0 0         ref($paths) or $paths = [$paths];
338            
339             # default options
340 0           %opts = (max=>0, %opts);
341            
342             # maximum number of lines
343 0           $max = $opts{'max'};
344            
345             # if we should skip empty lines
346 0   0       $skipempty = $opts{'skipempty'} || $opts{'skip_empty'};
347            
348             # loop through paths
349             FILELOOP:
350 0           foreach my $path (@$paths) {
351 0           my ($fh);
352            
353             # open file
354 0 0         unless ($fh = FileHandle->new($path)){
355 0 0         $opts{'quiet'} and next FILELOOP;
356 0           croak "could not open $path for reading: $!";
357             }
358            
359             # ensure bonary mode
360 0 0         $fh->binmode($fh) if $opts{'bin'};
361            
362             # loop through lines
363             LINELOOP:
364 0           while (my $line = <$fh>) {
365             # remove trailing newline
366 0           $line =~ s|[\r\n]+$||s;
367            
368             # skip empty lines if options indicate to do so
369 0 0         if ($skipempty) {
370 0 0         unless ($line =~ m|\S|s)
371 0           { next LINELOOP }
372             }
373            
374             # add to return array
375 0           push @rv, $line;
376            
377             # add to total number of lines
378 0           $total++;
379            
380             # finished looping if max is reached
381 0 0 0       if ( defined($max) && ($max > 0) && ($total > $max) )
      0        
382 0           { last FILELOOP }
383             }
384             }
385            
386             # return
387 0           return @rv;
388             }
389             #
390             # file_lines
391             #------------------------------------------------------------------------------
392              
393              
394             #------------------------------------------------------------------------------
395             # size
396             #
397              
398             =head2 size
399              
400             Returns the size of the given file. If the file doesn't exist, returns undef.
401              
402             =cut
403              
404             sub size {
405 0     0 1   my ($path) = @_;
406 0           my (@stats);
407            
408             # if file doesn't exist, return undef
409 0 0         if (! -e $path)
410 0           { return undef }
411            
412             # get file stats
413 0           @stats = stat($path);
414            
415             # return size
416 0           return $stats[7];
417             }
418             #
419             # size
420             #------------------------------------------------------------------------------
421              
422              
423             #------------------------------------------------------------------------------
424             # mod_date
425             #
426              
427             =head2 mod_time
428              
429             Returns the modification time (in epoch seconds) of the given file. If the file
430             doesn't exist, returns undef.
431              
432             print 'modification time: ', mod_time('myfile.txt'), "\n";
433              
434             If you are familiar with the stat() function, then it may clarify to know that
435             C simply returns the ninth element of stat().
436              
437             =head2 mod_date
438              
439             C does exactly the same thing as C.
440              
441             =cut
442              
443             sub mod_time {
444 0     0 1   my ($path) = @_;
445 0           my (@stats);
446            
447 0           @stats = stat($path);
448 0           return $stats[9];
449             }
450              
451             sub mod_date {
452 0     0 1   return mod_time(@_);
453             }
454             #
455             # mod_date
456             #------------------------------------------------------------------------------
457              
458              
459             #------------------------------------------------------------------------------
460             # age
461             #
462              
463             =head2 age
464              
465             C returns the number of seconds since the given file has been modifed.
466              
467             print 'file age: ', age('myfile.txt'), "\n";
468              
469             C simply returns the current time minus the value of C.
470              
471             =cut
472              
473             sub age {
474 0     0 1   my ($path) = @_;
475 0           my ($mod_time);
476            
477 0           $mod_time = mod_time($path);
478            
479 0           return(time() - $mod_time);
480             }
481             #
482             # age
483             #------------------------------------------------------------------------------
484              
485              
486             #------------------------------------------------------------------------------
487             # files
488             #
489              
490             =head2 files
491              
492             C returns an array of file paths starting at the given directory. In its
493             simplest use, C is called with just a directory path.
494              
495             @myfiles = files('./tmp');
496              
497             That command will return all files within ./tmp, including recursing into
498             nested directories. By default, all paths will be relative to the current
499             directory, so the file list mught look something like this:
500              
501             ./tmp/buffer.txt
502             ./tmp/build
503             ./tmp/build/myfile.txt
504              
505             You can get just the file names with the full_path option, described below.
506              
507             Note that the
508              
509             C has several options, explained below.
510              
511             B
512              
513             By default, C recurses directory structures.
514              
515             B
516              
517             B
518              
519             B
520              
521             B
522              
523             B
524              
525             =cut
526              
527             sub files {
528 0     0 1   my ($base, %opts) = @_;
529             my (
530 0           @rv,
531             %extensions,
532             @alldirs,
533             $dirs,
534             $files,
535             $fullpath,
536             $recurse,
537             $hidden,
538             $prune_file,
539             $dir_slash,
540             $path_rx,
541             $follow_links,
542             );
543            
544             # TESTING
545             # println subname(); ##i
546            
547             # $base must be defined
548 0 0         if (! defined $base)
549 0           { croak '$base must be defined' }
550            
551 0           $base =~ s|\\|/|sg;
552 0           $base =~ s|/$||sg;
553 0           @alldirs = $base;
554            
555             # default options
556 0           %opts = (dirs=>1, files=>1, recurse=>1, %opts);
557            
558             # options
559 0           $dirs = $opts{'dirs'};
560 0           $files = $opts{'files'};
561 0           $recurse = $opts{'recurse'};
562 0           $hidden = $opts{'hidden'};
563 0           $prune_file = $opts{'prune_file'};
564 0 0         defined($opts{'extension'}) and $opts{'extensions'} = $opts{'extension'};
565 0 0         $dir_slash = $opts{'dir_slash'} ? '/' : '';
566 0           $follow_links = $opts{'follow_links'};
567            
568             # full path
569 0 0         if (defined $opts{'full_path'})
    0          
570 0           { $fullpath = $opts{'full_path'} }
571             elsif (defined $opts{'fullpath'})
572 0           { $fullpath = $opts{'fullpath'} }
573             else
574 0           { $fullpath = 1 }
575            
576             # hold on to path rx
577 0           $path_rx = $opts{'rx'};
578            
579             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
580             # build hash of extensions
581             #
582 0           do {
583 0           my ($ext_opt);
584            
585             # get extensions option if it exists
586             EXT_LOOP:
587 0           foreach my $key (qw{extensions extension ext exts}) {
588 0 0         if ( hascontent ($ext_opt = $opts{$key}) ) {
589             # enforce as array
590 0 0         ref($ext_opt) or $ext_opt = [$ext_opt];
591            
592             # normalize extensions
593 0           grep {s|.*\.||s; $_ = lc($_);} @$ext_opt;
  0            
  0            
594            
595             # build %extensions
596 0           @extensions{@$ext_opt} = ();
597             }
598             }
599             };
600             #
601             # build hash of extensions
602             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
603            
604            
605             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
606             # loop through files in directory and sub-directories
607             #
608             DIRLOOP:
609 0           while(my $dir = pop @alldirs) {
610             # if $dir is actually a normal file and normal files are
611             # allowed as root
612 0 0 0       if ( $opts{'allow_file_root'} && (! -d $dir) ) {
613 0           push @rv, $dir;
614             }
615            
616             # else loop through files in directory
617             else {
618 0           my ($dh);
619 0 0         opendir($dh, $dir) or croak "opendir($dir): $!";
620            
621             # read in files in this directory
622             READLOOP:
623 0           foreach my $f (readdir $dh) {
624 0 0 0       next READLOOP if $f eq '.' or $f eq '..';
625            
626             # skip hidden files unless directed to show them
627 0 0 0       if ( ($f =~ m|^\.|s) && (! $hidden) )
628 0           { next READLOOP }
629            
630 0           $f = "$dir/$f";
631            
632             # if it's a directory, and it's not a link OR we should follow links,
633             # then add to the list of directories to recurse
634 0 0 0       if ( -d($f) && ( (! -l($f)) || $follow_links ) ){
    0 0        
      0        
      0        
635             # if there's a prune file
636 0 0 0       if (defined($prune_file) && -e("$f/$prune_file"))
637 0           { next READLOOP }
638            
639             # add to return array if necessary
640             # add / to end of directory name if options indicate to do so
641 0 0 0       if ( $dirs && allowed_ext($f, \%extensions) && allowed_rx($f, $path_rx) )
      0        
642 0           { push @rv, $f . $dir_slash }
643            
644             # add new directory to @alldirs
645 0 0         $recurse and push @alldirs, $f;
646             }
647            
648             # else it's enough of a "normal" file to add to the return array
649             elsif ( $files && allowed_ext($f, \%extensions) && allowed_rx($f, $path_rx) ) {
650 0           push @rv, $f;
651             }
652             }
653             }
654             }
655             #
656             # loop through files in directory and sub-directories
657             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
658            
659            
660 0 0         if (! $fullpath)
661 0           { grep { s|.*/||s } @rv }
  0            
662            
663            
664             # return
665 0           return @rv;
666             }
667              
668              
669             # private sub
670             sub allowed_rx {
671 0     0 0   my ($path, $rx) = @_;
672 0 0         defined($rx) or return 1;
673            
674             # return if the path matches the rx
675 0           return $path =~ m|$rx|s;
676             }
677              
678              
679             # private sub
680             sub allowed_ext {
681 0     0 0   my ($path, $exts) = @_;
682 0 0         %$exts or return 1;
683 0           $path =~ s|.*\.||s;
684 0           $path = lc($path);
685 0           return exists $exts->{$path};
686             }
687              
688             #
689             # files
690             #------------------------------------------------------------------------------
691              
692              
693             #------------------------------------------------------------------------------
694             # earliest_mod_date
695             #
696             #sub earliest_mod_date {
697             # my ($roots, %opts) = @_;
698             # my ($earliest);
699             #
700             # # coerce $roots into array
701             # if (! ref $roots)
702             # { $roots = [$roots] }
703             #
704             # # loop through root directories
705             # foreach my $root (@$roots) {
706             # # if root doesn't exist, throw error
707             # if (! -e $root) {
708             # croak "no file or directory at $root";
709             # }
710             #
711             # # loop through files
712             # foreach my $file (files $root, %opts, allow_file_root=>1) {
713             # my $mod_date = mod_date $file;
714             #
715             # if ( (! defined $earliest) || ($mod_date < $earliest) )
716             # { $earliest = $mod_date }
717             # }
718             # }
719             #
720             # # return
721             # return $earliest;
722             #}
723             #
724             # earliest_mod_date
725             #------------------------------------------------------------------------------
726              
727              
728              
729             #------------------------------------------------------------------------------
730             # latest_mod_date
731             #
732             #sub latest_mod_date {
733             # my ($roots, %opts) = @_;
734             # my ($latest);
735             #
736             # # coerce $roots into array
737             # if (! ref $roots)
738             # { $roots = [$roots] }
739             #
740             # # loop through root directories
741             # foreach my $root (@$roots) {
742             # # loop through files
743             # foreach my $file (files $root, %opts, allow_file_root=>1) {
744             # my $mod_date = mod_date $file;
745             #
746             # if ( (! defined $latest) || ($mod_date > $latest) )
747             # { $latest = $mod_date }
748             # }
749             # }
750             #
751             # # return
752             # return $latest;
753             #}
754             #
755             # latest_mod_date
756             #------------------------------------------------------------------------------
757              
758              
759              
760             #------------------------------------------------------------------------------
761             # search_inc
762             #
763              
764             =head2 search_inc
765              
766             search_inc() searches the @INC directories for a given file and returns the
767             full path to that file. For example, this command:
768              
769             search_inc('JSON/Tiny.pm')
770              
771             might return somethng like this:
772              
773             /usr/local/share/perl/5.18.2/JSON/Tiny.pm
774              
775             The given path must be the full path within the @INC directory. So, for
776             example, this command would not return the path to JSON/Tiny.pm:
777              
778             search_inc('Tiny.pm')
779              
780             That feature might be added later.
781              
782             If you prefer, you can give the path in Perl module format:
783              
784             search_inc('JSON::Tiny')
785              
786             =cut
787              
788             sub search_inc {
789 0     0 1   my ($str) = @_;
790 0           my ($lib, $addpm);
791            
792             # TESTING
793             # println subname(); ##i
794            
795             # if module is given in format Module::Name then change :: to /
796             # also add .pm to end
797 0 0         if ($str =~ s|::|/|g) {
798 0           my $file = $str;
799 0           $file =~ s|.*/||s;
800            
801             # if there isn't already an extension
802 0 0         if ( $file !~ m|\.| )
803 0           { $str .= '.pm' }
804             }
805            
806             # search through library directories
807 0           foreach $lib (reverse @INC) {
808 0 0         if (-e "$lib/$str") {
809 0           my $rv = "$lib/$str";
810 0           $rv =~ s|//+|/|gs;
811 0           return $rv;
812             }
813             }
814            
815             # if we get this far then the file wasn't found, so return undef
816 0           return undef;
817             }
818             #
819             # search_inc
820             #------------------------------------------------------------------------------
821              
822              
823             #------------------------------------------------------------------------------
824             # search_isa
825             #
826             sub search_isa {
827 0     0 0   my ($object, $file_name, %opts) = @_;
828 0           my ($verbose, $indent, @isas, @paths, $object_class);
829            
830             # TESTING
831             # println subname(); ##i
832            
833             # must have params
834 0 0         defined($object) or croak 'must have $object';
835 0 0         defined($file_name) or croak 'must have $file_name';
836            
837             # load necessarey class
838 0           require Class::ISA;
839            
840             # verbosify
841 0 0         if ($opts{'verbose'}) {
842 0           print 'searching @ISA for file : ', $file_name, "\n";
843 0           $indent = indent();
844             }
845            
846             # get class of object
847 0 0         if (ref $object)
848 0           { $object_class = ref $object }
849             else
850 0           { $object_class = $object }
851            
852             # clean up file name
853 0           $file_name =~ s|^/||s;
854              
855             # get list of directories in which to look for message file
856 0           @isas = Class::ISA::self_and_super_path($object_class);
857            
858             # output list of potential directories
859 0           foreach my $isa (@isas) {
860 0           my ($path);
861 0           $isa =~ s|::|/|gs;
862 0           $path = $isa . '/' . $file_name;
863 0           push @paths, $path;
864             }
865            
866             # loop through directories in @INC
867 0           foreach my $dir (@INC) {
868 0           my $dir_use = $dir;
869 0           $dir_use =~ s|/$||s;
870            
871             # loop through paths
872 0           foreach my $path (@paths) {
873 0           my $full = $dir_use . '/' . $path;
874            
875             # return if we found the file
876 0 0         if (-e $full)
877 0           { return $full }
878             }
879             }
880            
881             # didn't find file
882 0 0         $verbose and print 'did not find file ', $file_name, "\n";
883 0           return undef;
884             }
885             #
886             # search_isa
887             #------------------------------------------------------------------------------
888              
889              
890             #------------------------------------------------------------------------------
891             # touch
892             #
893             sub touch {
894 0     0 0   my ($path, %opts) = @_;
895 0           my ($rv);
896            
897             # if file exists, use utime to update
898 0 0         if (-e $path) {
899 0           $rv = utime(undef, undef, $path);
900             }
901            
902             # else create file
903             else {
904 0           my ($hold_umask);
905            
906             # hold on to umask if a temporary umask was sent
907 0 0         if ( defined $opts{'umask'} ) {
908 0           $hold_umask = umask;
909 0           umask($opts{'umask'});
910             }
911            
912             # create file
913 0 0         $rv = FileHandle->new("> $path") ? 1 : 0;
914            
915             # set umask back
916 0 0         if ( defined $opts{'umask'} ) {
917 0           umask($hold_umask);
918             }
919             }
920            
921             # return success|failure
922 0           return $rv;
923             }
924             #
925             # touch
926             #------------------------------------------------------------------------------
927              
928              
929             #------------------------------------------------------------------------------
930             # dirhandle
931             # private sub
932             #
933             sub dirhandle {
934 0     0 0   return File::Misc::DirHandle->new(@_);
935             }
936             #
937             # dirhandle
938             #------------------------------------------------------------------------------
939              
940              
941              
942             #------------------------------------------------------------------------------
943             # ensure_dir
944             #
945             sub ensure_dir {
946 0     0 0   my ($dir, %opts) = @_;
947            
948             # create the directory if it doesn't already exist
949 0 0         if (! -e $dir) {
950 0           my ($rv, $hold_umask);
951            
952             # hold on to umask if a temporary umask was sent
953 0 0         if ( defined $opts{'umask'} ) {
954 0           $hold_umask = umask;
955 0           umask($opts{'umask'});
956             }
957            
958             # create directory
959 0           $rv = mkpath($dir);
960            
961             # set umask back
962 0 0         if ( defined $opts{'umask'} ) {
963 0           umask($hold_umask);
964             }
965            
966             # return
967 0           return $rv;
968             }
969            
970             # return success
971 0           return 1;
972             }
973              
974             # alias dir_ensure to ensure_dir
975             *dir_ensure = \&ensure_dir;
976              
977             #
978             # ensure_dir
979             #------------------------------------------------------------------------------
980              
981              
982             #------------------------------------------------------------------------------
983             # ensure_unlink
984             #
985             sub unlink_ensure {
986 0     0 0   return ensure_unlink(@_);
987             }
988              
989             sub ensure_unlink {
990 0     0 0   my ($path) = @_;
991            
992 0 0         if (-e $path) {
993             # if it's a directory
994 0 0         if (-d $path)
995 0           { return rmtree($path) }
996            
997             # else file
998 0           return unlink($path);
999             }
1000            
1001 0           return 1;
1002             }
1003             #
1004             # ensure_unlink
1005             #------------------------------------------------------------------------------
1006              
1007              
1008             #------------------------------------------------------------------------------
1009             # script_dir
1010             #
1011              
1012             =head2 script_dir
1013              
1014             Returns the directory of the script. The directory is relative the current
1015             directory when the script was called. Call this command before altering $0.
1016              
1017             =cut
1018              
1019             sub script_dir {
1020 0     0 1   my $dir = $0;
1021            
1022             # special case: no /
1023 0 0         unless ($dir =~ m|/|s)
1024 0           { return './' }
1025            
1026             # remove everything after last /
1027 0           $dir =~ s|^(.*)/[^/]*$|$1|s;
1028            
1029             # return
1030 0           return $dir;
1031             }
1032             #
1033             # script_dir
1034             #------------------------------------------------------------------------------
1035              
1036              
1037             #------------------------------------------------------------------------------
1038             # mode
1039             #
1040              
1041             =head2 mode
1042              
1043             mode() returns the file mode (i.e. type and permissions) of the given path.
1044              
1045             =cut
1046              
1047             sub mode {
1048 0     0 1   my ($path) = @_;
1049 0           my $mode = (stat($path))[2];
1050 0           return S_IMODE($mode);
1051             }
1052             #
1053             # mode
1054             #------------------------------------------------------------------------------
1055              
1056              
1057             #------------------------------------------------------------------------------
1058             # build_tree
1059             #
1060             sub build_tree {
1061 0     0 0   my ($root, @paths) = @_;
1062            
1063             # remove trailing / from root
1064 0           $root =~ s|/$||s;
1065            
1066             # ensure root exists
1067 0 0         if (! -e $root) {
1068 0 0         mkpath($root) or die $!;
1069             }
1070            
1071             # loop through files
1072 0           foreach my $path (@paths) {
1073 0           my ($full);
1074 0           $path =~ s|^/||s;
1075 0           $full = "$root/$path";
1076            
1077             # if dir
1078 0 0         if ($path =~ m|/$|s) {
1079 0 0         if (! -e $full) {
1080 0 0         mkpath($full) or die $!;
1081             }
1082             }
1083            
1084             # else create file
1085             else {
1086 0           my ($dir, $rv, $arrows);
1087            
1088            
1089             # create directory
1090 0           $dir = $full;
1091            
1092 0 0         if ($dir =~ s|/[^/]+$||s) {
1093 0 0         if (! -e $dir)
1094 0 0         { mkpath($dir) or die $! }
1095             }
1096            
1097 0 0         if (-e $full)
1098 0           { $arrows = '>>' }
1099             else
1100 0           { $arrows = '>' }
1101            
1102             # $rv = FileHandle->new("$arrows$full") or die $!;
1103 0 0         touch($full) or die $!;
1104             }
1105             }
1106            
1107             # return success
1108 0           return 1;
1109             }
1110             #
1111             # build_tree
1112             #------------------------------------------------------------------------------
1113              
1114              
1115             #------------------------------------------------------------------------------
1116             # mirror_tree
1117             #
1118             our %mirror_tree_results;
1119              
1120             sub mirror_tree {
1121 0     0 0   my ($src, $tgt, %opts) = @_;
1122 0           my ($verbose, $change_count);
1123 0           %mirror_tree_results = ();
1124 0           $verbose = $opts{'verbose'};
1125            
1126            
1127             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1128             # if tgt exists, copy as needed
1129             #
1130 0 0         if (-e $tgt) {
1131 0           my ($tgt_files, $src_files);
1132 0           $tgt_files = tree_hash($tgt);
1133 0           $src_files = tree_hash($src);
1134            
1135             # initialize results
1136 0           $mirror_tree_results{'dirs_copied'} = 0;
1137 0           $mirror_tree_results{'dirs_deleted'} = 0;
1138 0           $mirror_tree_results{'files_copied'} = 0;
1139 0           $mirror_tree_results{'files_deleted'} = 0;
1140 0           $mirror_tree_results{'files_replaced'} = 0;
1141            
1142            
1143             # delete new files in target
1144 0           foreach my $tgt_path (sort keys %$tgt_files) {
1145 0           my $src_full_path = qq|$src/$tgt_path|;
1146            
1147             # if it doesn't exist in src, delete it
1148 0 0         if (! -e $src_full_path) {
1149 0           my $tgt_full_path = qq|$tgt/$tgt_path|;
1150            
1151             # If it still exists in target (it might
1152             # have been deleted with a ancestor tree
1153             # in a previous loop) then delete it.
1154 0 0         if (-e $tgt_full_path) {
1155             # if it's a directory, rmtree it
1156 0 0         if (-d $tgt_full_path) {
1157 0 0         rmtree($tgt_full_path) or croak $!;
1158 0           change_message_during(\%opts);
1159 0           $mirror_tree_results{'dirs_deleted'}++;
1160             }
1161            
1162             # else delete the file
1163             else {
1164 0 0         unlink($tgt_full_path) or croak $!;
1165 0           change_message_during(\%opts);
1166 0           $mirror_tree_results{'files_deleted'}++;
1167             }
1168             }
1169             }
1170             }
1171            
1172             # add files as needed
1173 0           foreach my $src_path (keys %$src_files) {
1174 0           my ($tgt_full_path, $src_full_path, $copy);
1175 0           $tgt_full_path = qq|$tgt/$src_path|;
1176 0           $src_full_path = qq|$src/$src_path|;
1177            
1178             # handle situation where src is a dir and target is not
1179             # by deleting target
1180 0 0 0       if (
      0        
1181             (-e $tgt_full_path) &&
1182             ( -d $src_full_path) &&
1183             (! -d $tgt_full_path)
1184             ) {
1185 0 0         unlink($tgt_full_path) or die $!;
1186             }
1187            
1188             # if it doesn't exist in tgt, add it
1189 0 0         if (! -e $tgt_full_path) {
    0          
1190             # if dir
1191             # if ($src_full_path =~ m|/$|s) {
1192 0 0         if (-d $src_full_path) {
1193 0 0         mkpath($tgt_full_path) or die $!;
1194 0           change_message_during(\%opts);
1195 0           $mirror_tree_results{'dirs_copied'}++;
1196             }
1197            
1198             # else create file
1199             else {
1200 0           my ($dir);
1201            
1202             # create directory
1203 0           $dir = $tgt_full_path;
1204            
1205 0 0         if ($dir =~ s|/[^/]+$||s) {
1206 0 0         if (! -e $dir) {
1207 0 0         mkpath($dir) or die $!;
1208 0           change_message_during(\%opts);
1209 0           $mirror_tree_results{'dirs_copied'}++;
1210             }
1211             }
1212            
1213             # copy file
1214 0           change_message_during(\%opts);
1215 0           $mirror_tree_results{'files_copied'}++;
1216 0           $copy = 1;
1217             }
1218             }
1219            
1220             # if file, check if different
1221             elsif (! -d $src_full_path) {
1222 0           require File::Compare;
1223            
1224 0 0         if (File::Compare::compare($src_full_path, $tgt_full_path)) {
1225 0           $copy = 1;
1226 0           change_message_during(\%opts);
1227 0           $mirror_tree_results{'files_replaced'}++;
1228             }
1229             }
1230            
1231             # copy file if necessary
1232 0 0         if($copy) {
1233 0           my ($result, $dir);
1234 0           require File::Copy;
1235            
1236             # test for strange problem in which
1237 0           $dir = $tgt_full_path;
1238 0           $dir =~ s|/[^/]+$||s;
1239            
1240 0           $result = File::Copy::copy($src_full_path, $tgt_full_path);
1241            
1242 0 0         if (! $result) {
1243 0           die $!;
1244             }
1245             }
1246             }
1247             }
1248             #
1249             # if tgt exists, copy as needed
1250             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1251            
1252            
1253             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1254             # else tgt doesn't exist, so just copy entire tree
1255             #
1256             else {
1257 0           require File::Copy::Recursive;
1258 0 0         File::Copy::Recursive::dircopy($src, $tgt) or die $!;
1259            
1260             # set results as full copy
1261 0           $mirror_tree_results{'full_copy'} = 1;
1262             }
1263             #
1264             # else tgt doesn't exist, so just copy entire tree
1265             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1266            
1267            
1268             # initialize change count to 0
1269 0           $change_count = 0;
1270            
1271            
1272             # count changes and return
1273             # use 0E0 for "zero but true"
1274 0           foreach my $count (values %mirror_tree_results) {
1275 0           $change_count += $count;
1276             }
1277            
1278 0 0 0       if ($change_count && $opts{'change_message_after'})
1279 0           { print $opts{'change_message_after'}, "\n" }
1280            
1281             # set to "zero but true" if necessary
1282 0 0         unless ($opts{'return_count'}) {
1283 0   0       $change_count ||= '0E0';
1284             }
1285            
1286             # return success
1287 0           return $change_count;
1288             }
1289              
1290             sub change_message_during {
1291 0     0 0   my ($opts) = @_;
1292            
1293 0 0 0       if (
1294             $opts->{'change_message_during'} &&
1295             (! $opts->{'change_message_during_done'})
1296             ) {
1297             # print $opts->{'change_message_during'}, "\n";
1298 0           $opts->{'change_message_during_done'} = 1;
1299             }
1300             }
1301             #
1302             # mirror_tree
1303             #------------------------------------------------------------------------------
1304              
1305              
1306             #------------------------------------------------------------------------------
1307             # tree_hash
1308             #
1309             sub tree_hash {
1310 0     0 0   my ($root_node) = @_;
1311 0           my (@files, %rv);
1312            
1313             # change to root
1314 0           local $CWD = $root_node;
1315            
1316             # get list of files
1317 0           @files = files('./');
1318 0           grep {$_ =~ s|^\./||s} @files;
  0            
1319            
1320             # build hash of files
1321 0           @rv{@files} = ();
1322            
1323             # return
1324 0           return \%rv;
1325             }
1326             #
1327             # tree_hash
1328             #------------------------------------------------------------------------------
1329              
1330              
1331             #------------------------------------------------------------------------------
1332             # eq_files, ne_files, and aliases
1333             #
1334             sub eq_files {
1335 0     0 0   my ($path_a, $path_b) = @_;
1336 0           require File::Compare;
1337 0           return ! File::Compare::compare($path_a, $path_b);
1338             }
1339              
1340 0     0 0   sub eq_file { return eq_files(@_) }
1341 0     0 0   sub ne_files { return ! eq_files(@_) }
1342 0     0 0   sub ne_file { return ! eq_files(@_) }
1343             #
1344             # eq_files, ne_files, and aliases
1345             #------------------------------------------------------------------------------
1346              
1347              
1348             #------------------------------------------------------------------------------
1349             # file_type
1350             #
1351             #
1352             # default path to file program
1353             #my $file_program_path = '/usr/bin/file';
1354             #
1355             #sub file_type {
1356             # my ($path, %opts) = @_;
1357             # my ($bin, $fh, $type);
1358             #
1359             # # load Taint::Misc
1360             # require Taint::Misc;
1361             #
1362             # # get path to binary
1363             # $bin = $opts{'bin_path'} || $file_program_path;
1364             #
1365             # # get type from file program
1366             # $fh = Taint::Misc::pipefrom($bin, '--mime', '--brief', $path);
1367             # $type = <$fh>;
1368             # undef $fh;
1369             #
1370             # # parse out mime type
1371             # $type =~ s|;.*||s;
1372             # $type = lc($type);
1373             # $type = nospace($type);
1374             #
1375             # # return
1376             # return $type;
1377             #}
1378             #
1379             # file_type
1380             #------------------------------------------------------------------------------
1381              
1382              
1383             #------------------------------------------------------------------------------
1384             # lock_file
1385             #
1386              
1387             # LockFile::Simple provides similar functionality
1388              
1389             sub lock_file {
1390 0     0 0   my ($path, $exclusive, %opts) = @_;
1391 0           my ($lock, $mode, $wait);
1392            
1393             # determine wait
1394 0 0         if (defined $opts{'wait'})
1395 0           { $wait = $opts{'wait'} }
1396             else
1397 0           { $wait = 1 }
1398            
1399             # must have defined $exclusive
1400 0 0         unless (defined $exclusive)
1401 0           { croak 'must have defined $exclusive' }
1402            
1403             # open file handle
1404 0           $lock = FileHandle->new(">> $path");
1405            
1406             # fail if not able to get filehandle
1407 0 0         if (! $lock) {
1408 0           print STDERR "cannot get lock file $path: $!\n";
1409 0           return 0;
1410             }
1411            
1412             # get lock mode
1413 0 0         if ($exclusive)
1414 0           { $mode = LOCK_EX }
1415             else
1416 0           { $mode = LOCK_SH }
1417            
1418             # if waiting, get lock, fail if we don't
1419 0 0         if ($wait) {
1420 0 0         flock($lock, $mode) or
1421             die "unable to lock file: $!";
1422             }
1423            
1424             # if not waiting, add LOCK_NB, don't wait
1425             else {
1426 0           $mode = $mode | LOCK_NB;
1427 0 0         flock($lock, $mode) or return undef;
1428             }
1429            
1430             # return
1431 0           return $lock;
1432             }
1433             #
1434             # lock_file
1435             #------------------------------------------------------------------------------
1436              
1437              
1438             #------------------------------------------------------------------------------
1439             # stat_hash
1440             #
1441              
1442             # field names
1443             my @stat_fields = qw{
1444             dev
1445             ino
1446             mode
1447             nlink
1448             uid
1449             gid
1450             rdev
1451             size
1452             atime
1453             mtime
1454             ctime
1455             blksize
1456             blocks
1457             };
1458              
1459             sub stat_hash {
1460 0     0 0   my ($path) = @_;
1461 0           my (@vals, %rv);
1462            
1463             # get values
1464 0           @vals = stat($path);
1465            
1466             # if no value, return defined false
1467 0 0         @vals or return 0;
1468            
1469             # populate hash
1470 0           @rv{@stat_fields} = @vals;
1471            
1472             # return
1473 0           return \%rv;
1474             }
1475             #
1476             # stat_hash
1477             #------------------------------------------------------------------------------
1478              
1479              
1480             #------------------------------------------------------------------------------
1481             # tmp_path
1482             #
1483              
1484             =head2 tmp_path
1485              
1486             tmp_path() is for the situation where you want to create a temporary file, then
1487             have that file automatically deleted at the end of your code or code block.
1488              
1489             tmp_path() returns a C object. That object stringifies
1490             to a random path. When the object goes out of scope, the file, if it exists, is
1491             deleted. tmp_path() does B create the file, it just deletes the file if
1492             the file exists.
1493              
1494             tmp_path() takes one required param: the directory in which the file will go.
1495             Here's a simple example:
1496              
1497             # variables
1498             my ($tmp, $fh);
1499              
1500             # get temporary path: file is NOT created
1501             $tmp = tmp_path('./');
1502              
1503             # open a file handle, write stuff to the file, close the handle
1504             $fh = FileHandle->new("> $tmp") or die $!;
1505             print $fh "stuff\n";
1506             undef $fh;
1507              
1508             # do something that might cause a crash
1509             # if there is a crash, $tmp goes out of scope and deletes the file
1510             if ( it_could_happen() ) {
1511             die 'crash!';
1512             }
1513              
1514             # move the file somewhere else
1515             rename($tmp, './permanent') or die $!;
1516              
1517             # the file doesn't exist anymore, so when $tmp object
1518             # goes out of scope, nothing happens
1519              
1520             By default, the path consists of the given directory followed by a random
1521             string of four characters. So in the example above, the path would look
1522             something like this:
1523              
1524             ./fZ96
1525              
1526             No effort is made to ensure that there isn't already a file with that name. It
1527             is simply assumed that four characters is enough to assure a microscopic (but
1528             non-zero) chance of a name conflict.
1529              
1530             Note that L provides a similar functionality, but there is an
1531             important difference. File::Temp creates the temporay file and returns a
1532             file handle for that file. This is useful for situations where you want to
1533             cache data for use in the current scope. It gets a little trickier, however,
1534             if you want to close the file handle and move the temporary file to a permanent
1535             location. tmp_path simply gives you a path that will be deleted if the file
1536             exists, allowing you manipulate and move the file as you like. File::Temp also
1537             goes to some effort to ensure that there are no name conflicts. What you use is
1538             a matter of needs and taste.
1539              
1540             B
1541              
1542             By default the random string is 4 characters long. rand_length gives a
1543             different length to the string. So, for example, the following code indicates
1544             a random string length of 8:
1545              
1546             $tmp = tmp_path('./', rand_length=>8);
1547              
1548             That produces a string like this:
1549              
1550             ./JQd4P6W7
1551              
1552             B
1553              
1554             If the C option is sent and is false, then the file is not
1555             actually deleted when the tmp object goes out of scope. For example:
1556              
1557             $tmp = tmp_path('./', auto_delete=>0);
1558              
1559             This option might seem to defeat the purpose of tmp_path, but it's useful for
1560             debugging your code. By setting the object so that it doesn't automatically
1561             delete the file you can look at the contents of the file later to see if it
1562             actually contains what you thought it should.
1563              
1564             B
1565              
1566             extension allows you to give the path a file extension. For example, the
1567             following code creates a path that ends with '.txt'.
1568              
1569             $tmp = tmp_path('./', extension=>'txt');
1570              
1571             B
1572              
1573             prefix indicates a string that should be put after the directory name but
1574             before the random string. So, for example, the following code puts the prefix
1575             "build-" in the file name:
1576              
1577             $tmp = tmp_path('./', prefix=>'build-');
1578              
1579             giving us something like
1580              
1581             ./build-J3v1
1582              
1583             =cut
1584              
1585             sub tmp_path {
1586 0     0 1   return File::Misc::Tmp::Path->new(@_);
1587             }
1588             #
1589             # tmp_path
1590             #------------------------------------------------------------------------------
1591              
1592              
1593             #------------------------------------------------------------------------------
1594             # tmp_dir
1595             #
1596              
1597             =head2 tmp_dir
1598              
1599             tmp_dir() creates a temporary directory and returns a File::Misc::Tmp::Dir
1600             object. When the object goes out of scope, the directory is deleted.
1601              
1602             =cut
1603              
1604             sub tmp_dir {
1605 0     0 1   return File::Misc::Tmp::Dir->new(@_);
1606             }
1607             #
1608             # tmp_dir
1609             #------------------------------------------------------------------------------
1610              
1611              
1612              
1613             ###############################################################################
1614             # File::Misc::DirHandle
1615             #
1616             package File::Misc::DirHandle;
1617 1     1   6 use strict;
  1         2  
  1         27  
1618 1     1   4 use Carp;
  1         2  
  1         61  
1619 1     1   616 use DirHandle;
  1         485  
  1         327  
1620              
1621             # debug tools
1622             # use Debug::ShowStuff ':all';
1623              
1624              
1625             #------------------------------------------------------------------------------
1626             # new
1627             #
1628             sub new {
1629 0     0     my ($class, $path, %opts) = @_;
1630 0           my $self = bless {}, $class;
1631            
1632 0           $self->{'dh'} = DirHandle->new($path);
1633 0           $self->{'path'} = $path;
1634 0           $self->{'untaint'} = $opts{'untaint'};
1635 0   0       $self->{'fullpath'} = $opts{'fullpath'} || $opts{'full_path'};
1636            
1637 0           return $self;
1638             }
1639             #
1640             # new
1641             #------------------------------------------------------------------------------
1642              
1643              
1644             #------------------------------------------------------------------------------
1645             # read
1646             #
1647             sub read {
1648 0     0     my ($self) = @_;
1649 0           my ($rv, $untaint);
1650            
1651             GETLOOP:
1652 0           while (1) {
1653 0           $rv = $self->{'dh'}->read;
1654            
1655 0 0         if (! defined $rv)
1656 0           { return $rv }
1657            
1658 0 0         if ( $rv !~ m|^\.+$|s ){
1659 0 0         if ($self->{'untaint'}) {
1660             # Double check that we've got a real file
1661             # Yeah, I know this is redundant, but I can't
1662             # help myself. I just can't untaint without
1663             # some kind of check.
1664 0 0         if (-e qq|$self->{'path'}/$rv|) {
1665 0 0         unless ($rv =~ m|^(.+)$|s)
1666 0           { die "no-match-for-path:$rv doesn't match m|^(.+)\$|s" }
1667 0           $rv = $1;
1668             }
1669             }
1670            
1671 0 0         if ($self->{'fullpath'})
1672 0           { $rv = qq|$self->{'path'}/$rv| }
1673            
1674             # return
1675 0           return $rv;
1676             }
1677             }
1678              
1679             # should never get to this point
1680 0           die 'error: should never get to this point';
1681             }
1682             #
1683             # read
1684             #------------------------------------------------------------------------------
1685              
1686              
1687             #
1688             # File::Misc::DirHandle
1689             ###############################################################################
1690              
1691              
1692             ###############################################################################
1693             # File::Misc::Tmp::Path
1694             #
1695             package File::Misc::Tmp::Path;
1696 1     1   15 use strict;
  1         2  
  1         23  
1697 1     1   4 use Carp 'croak';
  1         1  
  1         89  
1698 1     1   6 use String::Util ':all';
  1         1  
  1         267  
1699 1     1   6 use overload '""'=>\&path, fallback=>1;
  1         2  
  1         7  
1700              
1701             # debug tools
1702             # use Debug::ShowStuff ':all';
1703              
1704             # Objects of this class create a file path (but not the file itself), then
1705             # delete the file upon destruction.
1706              
1707             # object overloading
1708             use overload
1709 0     0   0 '""' => sub{$_[0]->{'path'}}, # stringification
1710 1     1   111 fallback => 1; # operations not defined here
  1         1  
  1         6  
1711              
1712              
1713             #------------------------------------------------------------------------------
1714             # new
1715             #
1716             sub new {
1717 0     0     my ($class, $parent_dir, %opts) = @_;
1718 0           my $self = bless {}, $class;
1719            
1720             # TESTING
1721             # println subname(); ##i
1722             # showvar %opts;
1723            
1724             # error checking: make sure we got a parent directory
1725 0 0         unless (defined $parent_dir)
1726 0           { croak 'did not get defined parent directory' }
1727            
1728             # default options
1729 0           %opts = (rand_length=>4, auto_delete => 1, %opts);
1730            
1731             # automatically delete upon destruction
1732 0           $self->{'auto_delete'} = $opts{'auto_delete'};
1733            
1734             # clean up $parent_dir
1735 0           $parent_dir =~ s|/+$||s;
1736            
1737             # if exact fil name was sent
1738 0 0 0       if ($opts{'exact_path'} || $opts{'full_path'}) {
1739 0           $self->{'path'} = $self->{'file_name'} = $parent_dir;
1740 0           $self->{'file_name'} =~ s|^.*/||s;
1741             }
1742            
1743             # else generate file name
1744             else {
1745             # error checking: make sure parent directory exists
1746 0 0         unless (-e $parent_dir)
1747 0           { croak qq|directory $parent_dir does not exist| }
1748            
1749             # error checking: make sure parent directory is a directory
1750 0 0         unless (-d $parent_dir)
1751 0           { croak qq|$parent_dir is not a directory| }
1752            
1753 0 0         if (defined $opts{'file_name'}) {
1754 0           $self->{'file_name'} = $opts{'file_name'};
1755            
1756             # build full path
1757 0           $self->{'path'} = $parent_dir . '/' . $self->{'file_name'};
1758             }
1759             else {
1760 0   0       while (
1761             (! defined $self->{'path'}) ||
1762             (-e $self->{'path'})
1763             ) {
1764            
1765             # add random string
1766 0           $self->{'file_name'} = randword($opts{'rand_length'});
1767            
1768             # untaint file name
1769 0 0         unless ($self->{'file_name'} =~ m|^([a-z0-9]+)$|si)
1770 0           { die 'unable to untaint' }
1771 0           $self->{'file_name'} = $1;
1772            
1773             # add extension
1774 0 0         if (defined $opts{'extension'})
1775 0           { $self->{'file_name'} .= '.' . $opts{'extension'} }
1776            
1777             # begin with prefix if sent
1778 0 0         if (defined $opts{'prefix'})
1779 0           { $self->{'file_name'} = "$opts{'prefix'}$self->{'file_name'}" }
1780            
1781             # build full path
1782 0           $self->{'path'} = $parent_dir . '/' . $self->{'file_name'};
1783             }
1784             }
1785             }
1786            
1787             # return
1788 0           return $self;
1789             }
1790             #
1791             # new
1792             #------------------------------------------------------------------------------
1793              
1794              
1795             #------------------------------------------------------------------------------
1796             # path
1797             #
1798             sub path {
1799 0     0     return $_[0]->{'path'};
1800             }
1801             #
1802             # path
1803             #------------------------------------------------------------------------------
1804              
1805              
1806             #------------------------------------------------------------------------------
1807             # file_name
1808             #
1809             sub file_name {
1810 0     0     return $_[0]->{'file_name'};
1811             }
1812             #
1813             # file_name
1814             #------------------------------------------------------------------------------
1815              
1816              
1817             #-----------------------------------------------------------------------
1818             # DESTROY
1819             #
1820             DESTROY {
1821 0     0     my ($self) = @_;
1822            
1823 0 0 0       if ( $self->{'auto_delete'} && -e($self->{'path'}) ) {
1824 0 0         unlink($self->{'path'}) or
1825             die "unable to remove $self->{'path'}: $!";
1826             }
1827             }
1828             #
1829             # DESTROY
1830             #-----------------------------------------------------------------------
1831              
1832              
1833             #
1834             # File::Misc::Tmp::Path
1835             ###############################################################################
1836              
1837              
1838              
1839             ###############################################################################
1840             # File::Misc::Tmp::Dir
1841             #
1842             package File::Misc::Tmp::Dir;
1843 1     1   664 use strict;
  1         1  
  1         22  
1844 1     1   7 use Carp 'croak';
  1         1  
  1         45  
1845 1     1   4 use String::Util ':all';
  1         1  
  1         229  
1846 1     1   6 use File::Path;
  1         1  
  1         72  
1847 1     1   5 use overload '""'=>\&path, fallback=>1;
  1         2  
  1         5  
1848              
1849             # debug tools
1850             # use Debug::ShowStuff ':all';
1851              
1852             # Objects of this class create a directory, then
1853             # delete the entire temporary directory (including all
1854             # of its contents) upon destruction.
1855              
1856              
1857             #------------------------------------------------------------------------------
1858             # new
1859             #
1860             sub new {
1861 0     0     my ($class, $parent_dir, %opts) = @_;
1862 0           my $self = bless {}, $class;
1863            
1864             # default options
1865 0           %opts = (auto_delete => 1, %opts);
1866            
1867             # automatically rmtree upon destruction
1868 0           $self->{'auto_delete'} = $opts{'auto_delete'};
1869            
1870             # error checking: make sure we got a parent directory
1871 0 0         unless (defined $parent_dir)
1872 0           { croak 'did not get defined parent directory for Joyis::DirRemover object' }
1873            
1874             # error checking: make sure parent directory exists
1875 0 0         unless (-e $parent_dir)
1876 0           { croak qq|directory $parent_dir does not exist| }
1877            
1878             # error checking: make sure parent directory is a directory
1879 0 0         unless (-d $parent_dir)
1880 0           { croak qq|$parent_dir is not a directory| }
1881            
1882             # normalize path to parent directory
1883 0           $parent_dir =~ s|/$||;
1884            
1885             # if explicit name of temp directory sent, use that, else generate new
1886 0 0         if (defined $opts{'tmp_name'}) {
1887 0           $self->{'path'} = $parent_dir . '/' . $opts{'tmp_name'};
1888             }
1889              
1890             # else generate own
1891             else {
1892 0   0       while (
1893             (! defined $self->{'path'}) ||
1894             (-e $self->{'path'})
1895             ) {
1896 0           $self->{'dir'} = randword(4);
1897 0           $self->{'path'} = $parent_dir . '/' . $self->{'dir'};
1898             }
1899             }
1900            
1901             # create temp directory
1902 0 0         mkdir($self->{'path'}) or
1903             die "unable to create directory $self->{'path'}: $!";
1904            
1905             # return
1906 0           return $self;
1907             }
1908             #
1909             # new
1910             #------------------------------------------------------------------------------
1911              
1912              
1913             #------------------------------------------------------------------------------
1914             # path
1915             #
1916             sub path {
1917 0     0     return $_[0]->{'path'};
1918             }
1919             #
1920             # path
1921             #------------------------------------------------------------------------------
1922              
1923              
1924             #-----------------------------------------------------------------------
1925             # DESTROY
1926             #
1927             DESTROY {
1928 0     0     my ($self) = @_;
1929            
1930 0 0 0       if (
1931             -e($self->{'path'}) &&
1932             $self->{'auto_delete'}
1933             ) {
1934 0 0         rmtree($self->{'path'}) or
1935             die "unable to remove $self->{'path'}: $!";
1936             }
1937             }
1938             #
1939             # DESTROY
1940             #-----------------------------------------------------------------------
1941              
1942              
1943             #
1944             # File::Misc::Tmp::Dir
1945             ###############################################################################
1946              
1947              
1948              
1949             # return true
1950             1;
1951              
1952             __END__