File Coverage

blib/lib/Dev/Util/File.pm
Criterion Covered Total %
statement 142 172 82.5
branch 58 66 87.8
condition 7 10 70.0
subroutine 35 35 100.0
pod 29 29 100.0
total 271 312 86.8


line stmt bran cond sub pod time code
1             package Dev::Util::File;
2              
3 4     4   1940 use Dev::Util::Syntax;
  4         9  
  4         62  
4 4     4   34 use Exporter qw(import);
  4         7  
  4         164  
5              
6 4     4   3907 use File::Temp;
  4         56983  
  4         439  
7 4     4   37 use IO::Handle;
  4         8  
  4         13400  
8              
9             our $VERSION = version->declare("v2.19.35");
10              
11             our %EXPORT_TAGS = (
12             fattr => [ qw(
13             file_exists
14             file_readable
15             file_writable
16             file_executable
17             file_is_empty
18             file_size_equals
19             file_owner_effective
20             file_owner_real
21             file_is_setuid
22             file_is_setgid
23             file_is_sticky
24             file_is_ascii
25             file_is_binary
26             )
27             ],
28             ftypes => [ qw(
29             file_is_plain
30             file_is_symbolic_link
31             file_is_pipe
32             file_is_socket
33             file_is_block
34             file_is_character
35             )
36             ],
37              
38             dirs => [ qw(
39             dir_exists
40             dir_readable
41             dir_writable
42             dir_executable
43             dir_suffix_slash
44             )
45             ],
46             misc => [ qw(
47             mk_temp_dir
48             mk_temp_file
49             stat_date
50             status_for
51             read_list
52             )
53             ]
54              
55             );
56              
57             # add all the other ":class" tags to the ":all" class, deleting duplicates
58             {
59             my %seen;
60             push @{ $EXPORT_TAGS{ all } }, grep { !$seen{ $_ }++ } @{ $EXPORT_TAGS{ $_ } }
61             foreach keys %EXPORT_TAGS;
62             }
63             Exporter::export_ok_tags('all');
64              
65             sub file_exists {
66 22     22 1 2902 my $file = shift;
67              
68 22 100       723 if ( -e $file ) {
69 12         110 return 1;
70             }
71             else {
72 10         68 return 0;
73             }
74 0         0 return;
75             }
76              
77             sub file_readable {
78 1     1 1 1166 my $file = shift;
79              
80 1 50       19 if ( -e -r $file ) {
81 1         7 return 1;
82             }
83             else {
84 0         0 return 0;
85             }
86 0         0 return;
87             }
88              
89             sub file_writable {
90 1     1 1 875 my $file = shift;
91              
92 1 50       42 if ( -e -w $file ) {
93 1         6 return 1;
94             }
95             else {
96 0         0 return 0;
97             }
98 0         0 return;
99             }
100              
101             sub file_executable {
102 2     2 1 406 my $file = shift;
103              
104 2 100       29 if ( -e -x $file ) {
105 1         5 return 1;
106             }
107             else {
108 1         4 return 0;
109             }
110 0         0 return;
111             }
112              
113             sub file_is_empty {
114 3     3 1 285 my $file = shift;
115              
116 3 100       64 if ( -e -z $file ) {
117 2         10 return 1;
118             }
119             else {
120 1         5 return 0;
121             }
122 0         0 return;
123             }
124              
125             sub file_size_equals {
126 4     4 1 10 my $file = shift;
127 4         6 my $size = shift;
128              
129 4 100       11 unless ( file_exists($file) ) { return 0; }
  1         5  
130              
131 3         25 my $file_size = -s $file;
132 3 100       17 if ( $file_size == $size ) {
133 2         10 return 1;
134             }
135             else {
136 1         4 return 0;
137             }
138 0         0 return;
139             }
140              
141             sub file_owner_effective {
142 1     1 1 2 my $file = shift;
143              
144 1 50       15 if ( -e -o $file ) {
145 1         4 return 1;
146             }
147             else {
148 0         0 return 0;
149             }
150 0         0 return;
151             }
152              
153             sub file_owner_real {
154 1     1 1 418 my $file = shift;
155              
156 1 50       16 if ( -e -O $file ) {
157 1         5 return 1;
158             }
159             else {
160 0         0 return 0;
161             }
162 0         0 return;
163             }
164              
165             sub file_is_setuid {
166 2     2 1 462 my $file = shift;
167              
168 2 100       27 if ( -e -u $file ) {
169 1         6 return 1;
170             }
171             else {
172 1         4 return 0;
173             }
174 0         0 return;
175             }
176              
177             sub file_is_setgid {
178 2     2 1 348 my $file = shift;
179              
180 2 100       50 if ( -e -g $file ) {
181 1         21 return 1;
182             }
183             else {
184 1         7 return 0;
185             }
186 0         0 return;
187             }
188              
189             sub file_is_sticky {
190 2     2 1 784 my $file = shift;
191              
192 2 100       84 if ( -e -k $file ) {
193 1         39 return 1;
194             }
195             else {
196 1         9 return 0;
197             }
198 0         0 return;
199             }
200              
201             sub file_is_ascii {
202 2     2 1 1403 my $file = shift;
203              
204 2 100       359 if ( -e -T $file ) {
205 1         16 return 1;
206             }
207             else {
208 1         22 return 0;
209             }
210 0         0 return;
211             }
212              
213             sub file_is_binary {
214 2     2 1 7 my $file = shift;
215              
216 2 100       159 if ( -e -B $file ) {
217 1         9 return 1;
218             }
219             else {
220 1         21 return 0;
221             }
222 0         0 return;
223             }
224              
225             sub file_is_plain {
226 3     3 1 12 my $file = shift;
227              
228 3 100       60 if ( -e -f $file ) {
229 2         23 return 1;
230             }
231             else {
232 1         50 return 0;
233             }
234 0         0 return;
235             }
236              
237             sub file_is_symbolic_link {
238 2     2 1 1455 my $file = shift;
239              
240 2 100       27 if ( -e -l $file ) {
241 1         13 return 1;
242             }
243             else {
244 1         37 return 0;
245             }
246 0         0 return;
247             }
248              
249             sub file_is_pipe {
250 2     2 1 44 my $file = shift;
251              
252 2 100       43 if ( -e -p $file ) {
253 1         24 return 1;
254             }
255             else {
256 1         10 return 0;
257             }
258 0         0 return;
259             }
260              
261             sub file_is_socket {
262 2     2 1 421 my $file = shift;
263              
264 2 100       35 if ( -e -S $file ) {
265 1         15 return 1;
266             }
267             else {
268 1         15 return 0;
269             }
270 0         0 return;
271             }
272              
273             sub file_is_block {
274 1     1 1 1093 my $file = shift;
275              
276 1 50       29 if ( -e -b $file ) {
277 0         0 return 1;
278             }
279             else {
280 1         20 return 0;
281             }
282 0         0 return;
283             }
284              
285             sub file_is_character {
286 2     2 1 500 my $file = shift;
287              
288 2 100       31 if ( -e -c $file ) {
289 1         11 return 1;
290             }
291             else {
292 1         15 return 0;
293             }
294 0         0 return;
295             }
296              
297             sub dir_exists {
298 5     5 1 3116 my $dir = shift;
299              
300 5 100       554 if ( -e -d $dir ) {
301 3         90 return 1;
302             }
303             else {
304 2         24 return 0;
305             }
306 0         0 return;
307             }
308              
309             sub dir_readable {
310 1     1 1 896 my $dir = shift;
311              
312 1 50       88 if ( -e -d -r $dir ) {
313 1         25 return 1;
314             }
315             else {
316 0         0 return 0;
317             }
318 0         0 return;
319             }
320              
321             sub dir_writable {
322 10     10 1 1724 my $dir = shift;
323              
324 10 100       519 if ( -e -d -w $dir ) {
325 9         61 return 1;
326             }
327             else {
328 1         8 return 0;
329             }
330 0         0 return;
331             }
332              
333             sub dir_executable {
334 1     1 1 465 my $dir = shift;
335              
336 1 50       3 if ( -e -d -x $dir ) {
337 1         25 return 1;
338             }
339             else {
340 0         0 return 0;
341             }
342 0         0 return;
343             }
344              
345             sub dir_suffix_slash {
346              
347             # add a trailing slash to dir name if none exists
348 11     11 1 37 my $dir = shift;
349              
350 11 100       81 $dir .= ( substr( $dir, -1, 1 ) eq '/' ) ? '' : '/';
351 11         103 return $dir;
352             }
353              
354             sub mk_temp_dir {
355 3   50 3 1 869752 my $dir = shift || '/tmp';
356 3         44 my $temp_dir = File::Temp->newdir( DIR => $dir,
357             CLEANUP => 1 );
358              
359 3         2083 return ($temp_dir);
360             }
361              
362             sub mk_temp_file {
363 2   50 2 1 71 my $temp_dir = shift || '/tmp';
364              
365 2         34 my $temp_file = File::Temp->new(
366             DIR => $temp_dir,
367             SUFFIX => '.test',
368             UNLINK => 0
369             );
370 2         1360 $temp_file->autoflush();
371              
372             # print { $temp_file } 'super blood wolf moon' . "\n";
373              
374 2         118 return ($temp_file);
375             }
376              
377             sub stat_date {
378 4     4 1 6067 my $file = shift;
379 4   100     36 my $dir_format = shift || 0;
380 4   100     19 my $date_format = shift || 'daily';
381 4         7 my ( $date, $format );
382              
383 4         50 my $mtime = ( stat $file )[9];
384              
385 4 100       14 if ( $date_format eq 'monthly' ) {
386 2 100       10 $format = $dir_format ? "%04d/%02d" : "%04d%02d";
387             $date = sprintf(
388             $format,
389 2     2   12 sub { ( $_[5] + 1900, $_[4] + 1 ) }
390 2         20 ->( localtime($mtime) )
391             );
392             }
393             else {
394 2 100       8 $format = $dir_format ? "%04d/%02d/%02d" : "%04d%02d%02d";
395             $date = sprintf(
396             $format,
397 2     2   21 sub { ( $_[5] + 1900, $_[4] + 1, $_[3] ) }
398 2         57 ->( localtime($mtime) )
399             );
400             }
401 4         24 return $date;
402             }
403              
404             sub status_for {
405 1     1 1 305 my ($file) = @_;
406 1         75 Readonly my @STAT_FIELDS =>
407             qw( dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks );
408              
409             # The hash to be returned...
410 1         103 my %stat_hash = ( file => $file );
411              
412             # Load each stat datum into an appropriately named entry of the hash...
413 1         16 @stat_hash{ @STAT_FIELDS } = stat $file;
414              
415 1         102 return \%stat_hash;
416             }
417              
418             sub read_list {
419 2     2 1 890 my $input_file = shift;
420 2   50     16 my $sep = shift || "\n";
421              
422 2 100       5 $sep = undef if ( !wantarray );
423 2         16 local $INPUT_RECORD_SEPARATOR = $sep;
424              
425 2         4 my ( $line, @list );
426              
427 2 50       14 open( my $input, '<', $input_file )
428             or die "can't open file, $input_file $!\n";
429             LINE:
430 2         570 while ( defined( $line = <$input> ) ) {
431 5         7 chomp($line);
432 5 100       18 next LINE if ( $line =~ m|^$| ); # remove blank lines
433 4 100       16 next LINE if ( $line =~ m|^#| ); # remove comments
434 3         12 push @list, $line;
435             }
436 2         6 close($input);
437              
438 2 100       347 return wantarray ? @list : $list[0];
439             }
440              
441             1; # End of Dev::Util::File
442              
443             =pod
444              
445             =encoding utf-8
446              
447             =head1 NAME
448              
449             Dev::Util::File - General utility functions for files and directories.
450              
451             =head1 VERSION
452              
453             Version v2.19.35
454              
455             =head1 SYNOPSIS
456              
457             Dev::Util::File - provides functions to assist working with files and dirs, menus and prompts.
458              
459             use Dev::Util::File;
460              
461             my $fexists = file_exists('/path/to/somefile');
462             my $canreadf = file_readable('/path/to/somefile');
463             my $canwritef = file_writable('/path/to/somefile');
464             my $canexecf = file_executable('/path/to/somefile');
465              
466             my $isemptyfile = file_is_empty('/path/to/somefile');
467             my $fileissize = file_size_equals('/path/to/somefile', $number_of_bytes);
468              
469             my $isplainfile = file_is_plain('/path/to/somefile');
470             my $issymlink = file_is_symbolic_link('/path/to/somefile');
471             ...
472              
473             my $dexists = dir_exists('/path/to/somedir');
474             my $canreadd = dir_readable('/path/to/somedir');
475             my $canwrited = dir_writable('/path/to/somedir');
476              
477             my $slash_added_dir = dir_suffix_slash('/dir/path/no/slash');
478              
479             my $td = mk_temp_dir();
480             my $tf = mk_temp_file($td);
481              
482             my $file_date = stat_date( $test_file, 0, 'daily' ); # 20240221
483             my $file_date = stat_date( $test_file, 1, 'monthly' ); # 2024/02
484              
485             my $mtime = status_for($file)->{mtime}
486              
487             my $scalar_list = read_list(FILE);
488             my @array_list = read_list(FILE);
489              
490             =head1 EXPORT_TAGS
491              
492             =over 4
493              
494             =item B<:fattr>
495              
496             =over 8
497              
498             =item file_exists
499              
500             =item file_readable
501              
502             =item file_writable
503              
504             =item file_executable
505              
506             =item file_is_empty
507              
508             =item file_size_equals
509              
510             =item file_owner_effective
511              
512             =item file_owner_real
513              
514             =item file_is_setuid
515              
516             =item file_is_setgid
517              
518             =item file_is_sticky
519              
520             =item file_is_ascii
521              
522             =item file_is_binary
523              
524             =back
525              
526             =item B<:ftypes>
527              
528             =over 8
529              
530             =item file_is_plain
531              
532             =item file_is_symbolic_link
533              
534             =item file_is_pipe
535              
536             =item file_is_socket
537              
538             =item file_is_block
539              
540             =item file_is_character
541              
542             =back
543              
544             =item B<:dirs>
545              
546             =over 8
547              
548             =item dir_exists
549              
550             =item dir_readable
551              
552             =item dir_writable
553              
554             =item dir_executable
555              
556             =item dir_suffix_slash
557              
558             =back
559              
560             =item B<:misc>
561              
562             =over 8
563              
564             =item mk_temp_dir
565              
566             =item mk_temp_file
567              
568              
569             =item stat_date
570              
571             =item status_for
572              
573             =item read_list
574              
575             =back
576              
577             =back
578              
579             =head1 SUBROUTINES
580              
581             =head2 B<file_exists(FILE)>
582              
583             Tests for file existence. Returns true if the file exists, false if it does not.
584              
585             B<All of the subroutines return 1 for true and 0 for false.>
586              
587             C<FILE> a string or variable pointing to a file.
588              
589             my $fexists = file_exists('/path/to/somefile');
590              
591             =head2 B<file_readable(FILE)>
592              
593             Tests for file existence and is readable. Returns true if file is readable, false if not.
594              
595             my $canreadf = file_readable('/path/to/somefile');
596              
597             =head2 B<file_writable(FILE)>
598              
599             Tests for file existence and is writable. Returns true if file is writable, false if not.
600              
601             my $canwritef = file_writable('/path/to/somefile');
602              
603             =head2 B<file_executable(FILE)>
604              
605             Tests for file existence and is executable. Returns true if file is executable, false if not.
606              
607             my $canexecf = file_executable('/path/to/somefile');
608              
609             =head2 B<file_is_empty(FILE)>
610              
611             Check if the file is zero sized. Returns true if file is zero bytes, false if not.
612              
613             my $isemptyfile = file_is_empty('/path/to/somefile');
614              
615             =head2 B<file_size_equals(FILE, BYTES)>
616              
617             Check if the file size equals given size. Returns true if file is the given number of bytes, false if not.
618              
619             C<BYTES> The number of bytes to test for.
620              
621             my $fileissize = file_size_equals('/path/to/somefile', $number_of_bytes);
622              
623             =head2 B<file_owner_effective(FILE)>
624              
625             Check if the file is owned by the effective uid. Returns true if file is owned by the effective user, false if not.
626              
627             my $effectiveuserowns = file_owner_effective('/path/to/somefile');
628              
629             =head2 B<file_owner_real(FILE)>
630              
631             Check if the file is owned by the real uid. Returns true if file is owned by the real user, false if not.
632              
633             my $realuserowns = file_owner_real('/path/to/somefile');
634              
635             =head2 B<file_is_setuid(FILE)>
636              
637             Check if the file has setuid bit set. Returns true if file is setuid, for example: C<.r-Sr--r-->
638              
639             my $isfilesuid = file_is_setuid('/path/to/somefile');
640              
641             =head2 B<file_is_setgid(FILE)>
642              
643             Check if the file has setgid bit set. Returns true if file is setgid, for example: C<.r--r-Sr-->
644              
645             my $isfileguid = file_is_setgid('/path/to/somefile');
646              
647             =head2 B<file_is_sticky(FILE)>
648              
649             Check if the file has sticky bit set. Returns true if file is sticky, for example: C<.r--r--r-T>
650              
651             my $isfilesticky = file_is_sticky('/path/to/somefile');
652              
653             =head2 B<file_is_ascii(FILE)>
654              
655             Check if the file is an ASCII or UTF-8 text file (heuristic guess). Returns true if file is ascii, false if binary.
656              
657             my $isfileascii = file_is_ascii('/path/to/somefile');
658              
659             =head2 B<file_is_binary(FILE)>
660              
661             Check if the file is a "binary" file (opposite of C<file_is_ascii>). Returns true if file is binary, false if ascii.
662              
663             my $isfilebinary = file_is_binary('/path/to/somefile');
664              
665             =head2 B<file_is_plain(FILE)>
666              
667             Tests that file is a regular file. Returns true if file is a plain file, false if not.
668              
669             my $isplainfile = file_is_plain('/path/to/somefile');
670              
671             =head2 B<file_is_symbolic_link(FILE)>
672              
673             Tests that file is a symbolic link. Returns true if file is a symbolic link, for example: C<lr--r--r-->
674              
675             my $issymlink = file_is_symbolic_link('/path/to/somefile');
676              
677             =head2 B<file_is_pipe(FILE)>
678              
679             Tests that file is a named pipe. Returns true if file is a pipe, for example: C<|rw-rw-rw->
680              
681             my $ispipe = file_is_pipe('/path/to/somefile');
682              
683             =head2 B<file_is_socket(FILE)>
684              
685             Tests that file is a socket. Returns true if file is a socket, for example: C<srw------->
686              
687             my $issocket = file_is_socket('/path/to/somefile');
688              
689             =head2 B<file_is_block(FILE)>
690              
691             Tests that file is a block special file. Returns true if file is a block file, for example: C<brw-r----->
692              
693             my $isblock = file_is_block('/path/to/somefile');
694              
695             =head2 B<file_is_character(FILE)>
696              
697             Tests that file is a block character file. Returns true if file is a block character file, for example: C<crw-r----->
698              
699             my $ischarf = file_is_character('/path/to/somefile');
700              
701             =head2 B<dir_exists(DIR)>
702              
703             Tests for dir existence. Returns true if the directory exists, false if not.
704              
705             C<DIR> a string or variable pointing to a directory.
706              
707             my $dexists = dir_exists('/path/to/somedir');
708              
709             =head2 B<dir_readable(DIR)>
710              
711             Tests for dir existence and is readable. Returns true if the directory is readable, false if not.
712              
713             my $canreadd = dir_readable('/path/to/somedir');
714              
715             =head2 B<dir_writable(DIR)>
716              
717             Tests for dir existence and is writable. Returns true if the directory is writable, false if not.
718              
719             my $canwrited = dir_writable('/path/to/somedir');
720              
721             =head2 B<dir_executable(DIR)>
722              
723             Tests for dir existence and is executable. Returns true if the directory is executable, false if not.
724              
725             my $canenterdir = dir_executable('/path/to/somedir');
726              
727             =head2 B<dir_suffix_slash(DIR)>
728              
729             Ensures a dir ends in a slash by adding one if necessary.
730              
731             my $slash_added_dir = dir_suffix_slash('/dir/path/no/slash');
732              
733             =head2 B<mk_temp_dir(DIR)>
734              
735             Create a temporary directory in the supplied parent dir. F</tmp> is the default if no dir given.
736              
737             C<DIR> a string or variable pointing to a directory.
738              
739             my $td = mk_temp_dir();
740              
741             =head2 B<mk_temp_file(DIR)>
742              
743             Create a temporary file in the supplied dir. F</tmp> is the default if no dir given.
744              
745             my $tf = mk_temp_file($td);
746              
747             =head2 B<stat_date(FILE, DIR_FORMAT, DATE_FORMAT)>
748              
749             Return the stat date of a file
750              
751             C<DIR_FORMAT> Style of date, include slashes? 0: YYYYMMDD, 1: YYYY/MM/DD
752              
753             C<DATE_FORMAT> Granularity of date: daily: YYYYMMDD, monthly: YYYY/MM
754              
755             my $file_date = stat_date( $test_file, 0, 'daily' ); # 20240221
756             my $file_date = stat_date( $test_file, 1, 'monthly' ); # 2024/02
757              
758             format: YYYYMMDD,
759             or format: YYYY/MM/DD if dir_format is true
760             or format: YYYYMM or YYYY/MM if date_type is monthly
761              
762             =head2 B<status_for>
763              
764             Return hash_ref of file stat info.
765              
766             my $stat_info_ref = status_for($file);
767             my $mtime = $stat_info_ref->{mtime};
768              
769             Available keys:
770              
771             dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks
772              
773             =head2 B<read_list>
774              
775             Read a list from an input file return an array of lines if called in list context.
776             If called by scalar context it will slurp the whole file and return it as a scalar.
777             Comments (begins with #) and blank lines are skipped.
778              
779             my $scalar_list = read_list(FILE);
780             my @array_list = read_list(FILE);
781              
782             B<Note>: The API for this function is maintained to support the existing code base that uses it.
783             It would probably be better to use C<Perl6::Slurp> or C<File::Slurper> for new code.
784              
785             =head1 AUTHOR
786              
787             Matt Martini, C<< <matt at imaginarywave.com> >>
788              
789             =head1 BUGS
790              
791             Please report any bugs or feature requests to C<bug-dev-util at rt.cpan.org>, or through
792             the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dev-Util>. I will
793             be notified, and then you'll automatically be notified of progress on your bug as I make changes.
794              
795             =head1 SUPPORT
796              
797             You can find documentation for this module with the perldoc command.
798              
799             perldoc Dev::Util::File
800              
801             You can also look for information at:
802              
803             =over 4
804              
805             =item * RT: CPAN's request tracker (report bugs here)
806              
807             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Dev-Util>
808              
809             =item * Search CPAN
810              
811             L<https://metacpan.org/release/Dev-Util>
812              
813             =back
814              
815             =head1 ACKNOWLEDGMENTS
816              
817             =head1 LICENSE AND COPYRIGHT
818              
819             This software is Copyright © 2019-2025 by Matt Martini.
820              
821             This is free software, licensed under:
822              
823             The GNU General Public License, Version 3, June 2007
824              
825             =cut
826              
827             __END__