File Coverage

lib/App/Followme/FIO.pm
Criterion Covered Total %
statement 193 206 93.6
branch 70 90 77.7
condition 1 3 33.3
subroutine 27 28 96.4
pod 15 18 83.3
total 306 345 88.7


line stmt bran cond sub pod time code
1             package App::Followme::FIO;
2              
3 24     24   17106 use 5.008005;
  24         94  
4 24     24   132 use strict;
  24         48  
  24         502  
5 24     24   110 use warnings;
  24         44  
  24         589  
6 24     24   11864 use integer;
  24         377  
  24         135  
7 24     24   9340 use lib '../..';
  24         12843  
  24         130  
8              
9 24     24   15051 use IO::Dir;
  24         301189  
  24         1292  
10 24     24   171 use IO::File;
  24         49  
  24         3116  
11 24     24   12766 use Time::Local;
  24         54641  
  24         1422  
12 24     24   14110 use Time::Format;
  24         58127  
  24         176  
13 24         55619 use File::Spec::Functions qw(abs2rel catfile curdir file_name_is_absolute
14 24     24   2399 no_upwards rel2abs splitdir);
  24         58  
15              
16             require Exporter;
17             our @ISA = qw(Exporter);
18             our @EXPORT = qw(fio_filename_to_url fio_flatten
19             fio_full_file_name fio_format_date fio_get_date
20             fio_get_size fio_glob_patterns fio_is_newer
21             fio_match_patterns fio_most_recent_file
22             fio_read_page fio_same_file fio_set_date
23             fio_split_filename fio_to_file
24             fio_visit fio_write_page);
25              
26             our $VERSION = "2.03";
27              
28             #----------------------------------------------------------------------
29             # Calculate the check sum for a file
30              
31             sub fio_calculate_checksum {
32 0     0 0 0 my ($filename) = @_;
33              
34 0         0 my $checksum;
35 0         0 my $page = fio_read_page($filename, ':raw');
36              
37 0 0       0 if ($page) {
38 0         0 my $md5 = Digest::MD5->new;
39 0         0 $md5->add($page);
40 0         0 $checksum = $md5->hexdigest;
41            
42             } else {
43 0         0 $checksum = '';
44             }
45              
46 0         0 return $checksum;
47             }
48              
49             #----------------------------------------------------------------------
50             # Convert filename to url
51              
52             sub fio_filename_to_url {
53 3     3 1 2319 my ($directory, $filename, $ext) = @_;
54              
55 3         8 $filename = rel2abs($filename);
56 3         62 $filename = fio_to_file($filename, $ext);
57 3         10 $filename = abs2rel($filename, $directory);
58              
59 3 50       216 my @path = $filename eq '.' ? () : splitdir($filename);
60              
61 3         18 my $url = join('/', @path);
62 3 100       18 $url =~ s/\.[^\.]*$/.$ext/ if defined $ext;
63              
64 3         11 return $url;
65             }
66              
67             #----------------------------------------------------------------------
68             # Flatten a data structure to a string
69              
70             sub fio_flatten {
71 292     292 1 4321 my ($data) = @_;
72              
73 292 100       597 if (ref($data) eq 'HASH') {
74 3         4 my @buffer;
75 3         12 foreach my $key (sort keys %$data) {
76 8         18 my $value = fio_flatten($data->{$key});
77 8         20 push(@buffer, "$key: $value");
78             }
79            
80 3         7 $data = \@buffer;
81             }
82            
83 292 100       550 if (ref($data) eq 'ARRAY') {
84 5         7 my @buffer;
85 5         9 foreach my $value (@$data) {
86 12         26 push(@buffer, fio_flatten($value));
87             }
88            
89 5         14 $data = join(", ", @buffer);
90             }
91            
92 292         673 return $data;
93             }
94              
95             #----------------------------------------------------------------------
96             # Format a date string
97              
98             sub fio_format_date {
99 141     141 1 271 my ($date, $format) = @_;
100            
101 141 100       329 $format = 'yyyy-mm-ddThh:mm:ss' unless defined $format;
102 141         464 $date = time_format($format, $date);
103 141         66132 return $date;
104             }
105              
106             #----------------------------------------------------------------------
107             # Construct the full file name from a relative file name
108              
109             sub fio_full_file_name {
110 145     145 1 2536 my (@directories) = @_;
111              
112 145 100       400 return $directories[-1] if file_name_is_absolute($directories[-1]);
113              
114 129         840 my @dirs;
115 129         231 foreach my $dir (@directories) {
116 258         1175 push(@dirs, splitdir($dir));
117             }
118              
119 129         522 my @new_dirs;
120 129         209 foreach my $dir (@dirs) {
121 989 50       1802 if (no_upwards($dir)) {
122 989         4803 push(@new_dirs, $dir);
123             } else {
124 0 0       0 pop(@new_dirs) unless $dir eq '.';
125             }
126             }
127              
128 129         890 return catfile(@new_dirs);
129             }
130              
131             #----------------------------------------------------------------------
132             # Get modification date of file
133              
134             sub fio_get_date {
135 253     253 1 1980 my ($filename) = @_;
136              
137 253         350 my $date;
138 253 100       3632 if (-e $filename) {
139 242         3041 my @stats = stat($filename);
140 242         775 $date = $stats[9];
141             } else {
142 11         32 $date = time();
143             }
144              
145 253         785 return $date;
146             }
147              
148             #----------------------------------------------------------------------
149             # Get size of file
150              
151             sub fio_get_size {
152 1     1 1 3 my ($filename) = @_;
153              
154 1         1 my $size;
155 1 50       19 if (-e $filename) {
156 1         13 my @stats = stat($filename);
157 1         3 $size = $stats[7];
158             } else {
159 0         0 $size = 0;
160             }
161              
162 1         4 return $size;
163             }
164              
165             #----------------------------------------------------------------------
166             # Map filename globbing metacharacters onto regexp metacharacters
167              
168             sub fio_glob_patterns {
169 118     118 1 809 my ($patterns) = @_;
170 118         219 my @globbed_patterns = ();
171              
172 118 100       282 if ($patterns) {
173 86         353 my @patterns = split(/\s*,\s*/, $patterns);
174              
175 86         210 foreach my $pattern (@patterns) {
176 100 50       248 if ($pattern eq '*') {
177 0         0 push(@globbed_patterns, '.');
178              
179             } else {
180 100         180 my $start;
181 100 100       434 if ($pattern =~ s/^\*//) {
182 75         140 $start = '';
183             } else {
184 25         50 $start = '^';
185             }
186              
187 100         173 my $finish;
188 100 100       310 if ($pattern =~ s/\*$//) {
189 25         57 $finish = '';
190             } else {
191 75         128 $finish = '$';
192             }
193              
194 100         258 $pattern =~ s/\./\\./g;
195 100         200 $pattern =~ s/\*/\.\*/g;
196 100         162 $pattern =~ s/\?/\.\?/g;
197              
198 100         356 push(@globbed_patterns, $start . $pattern . $finish);
199             }
200             }
201             }
202              
203 118         402 return \@globbed_patterns;
204             }
205              
206             #----------------------------------------------------------------------
207             # Is the target newer than any source file?
208              
209             sub fio_is_newer {
210 16     16 0 1918 my ($target, @sources) = @_;
211              
212 16 100       309 my $target_date = -e $target ? fio_get_date($target) : 0;
213              
214 16         59 foreach my $source (@sources) {
215 20 50       58 next unless defined $source;
216              
217 20 100       232 next unless -e $source;
218 19 50       77 next if fio_same_file($target, $source);
219              
220 19         48 my $source_date = fio_get_date($source);
221 19 100       117 return if $source_date >= $target_date;
222             }
223              
224 2         8 return 1;
225             }
226              
227             #----------------------------------------------------------------------
228             # Return true if filename matches pattern
229              
230             sub fio_match_patterns {
231 141     141 1 249 my ($file, $patterns) = @_;
232              
233 141         269 foreach my $pattern (@$patterns) {
234 141 100       852 return 1 if $file =~ /$pattern/;
235             }
236              
237 68         206 return;
238             }
239              
240             #----------------------------------------------------------------------
241             # Get the most recently modified web file in a directory
242              
243             sub fio_most_recent_file {
244 37     37 1 101 my ($directory, $pattern, $exclude_index) = @_;
245              
246 37         91 my ($filenames, $directories) = fio_visit($directory);
247              
248 37         833 my $newest_file;
249 37         69 my $newest_date = 0;
250 37         110 my $globs = fio_glob_patterns($pattern);
251              
252 37         93 foreach my $filename (@$filenames) {
253 141         294 my ($dir, $file) = fio_split_filename($filename);
254 141 100       343 next unless fio_match_patterns($file, $globs);
255              
256 73 100       185 if ($exclude_index) {
257 4         9 my ($base, $ext) = split(/\./, $file, 2);
258 4 100       12 next if $base eq 'index';
259             }
260              
261 72         205 my $file_date = fio_get_date($filename);
262              
263 72 100       238 if ($file_date > $newest_date) {
264 44         77 $newest_date = $file_date;
265 44         97 $newest_file = $filename;
266             }
267             }
268              
269 37         168 return $newest_file;
270             }
271              
272             #----------------------------------------------------------------------
273             # Read a file into a string
274              
275             sub fio_read_page {
276 342     342 1 6025812 my ($filename, $binmode) = @_;
277 342 50       769 return unless defined $filename;
278              
279 342         1244 local $/;
280 342         1893 my $fd = IO::File->new($filename, 'r');
281 342 100       34265 return unless $fd;
282              
283 328 100       891 binmode($fd, $binmode) if defined $binmode;
284 328         9551 my $page = <$fd>;
285 328         3525 close($fd);
286              
287 328         2906 return $page;
288             }
289              
290             #----------------------------------------------------------------------
291             # Check if two filenames are the same in an os independent way
292              
293             sub fio_same_file {
294 43     43 0 3799 my ($filename1, $filename2, $case_sensitivity) = @_;
295 43 100       111 $case_sensitivity = 0 unless defined $case_sensitivity;
296 43 50 33     218 return unless defined $filename1 && defined $filename2;
297              
298 43         140 my @path1 = splitdir(rel2abs($filename1));
299 43         953 my @path2 = splitdir(rel2abs($filename2));
300 43 100       602 return unless @path1 == @path2;
301              
302 35         95 while(@path1) {
303 264         365 my $part1 = shift(@path1);
304 264         349 my $part2 = shift(@path2);
305              
306 264 50       430 unless ($case_sensitivity) {
307 264         377 $part1 = lc($part1);
308 264         363 $part2 = lc($part2);
309             }
310              
311 264 100       595 return unless $part1 eq $part2;
312             }
313              
314 23         102 return 1;
315             }
316              
317             #----------------------------------------------------------------------
318             # Set modification date of file
319              
320             sub fio_set_date {
321 27     27 1 80 my ($filename, $date) = @_;
322              
323 27 100       114 if ($date =~ /[^\d]/) {
324 12 50       50 die "Can't convert date: $date\n" unless $date =~ /T/;
325 12         70 my @time = split(/[^\d]/, $date);
326 12         35 $time[1] -= 1; # from 1 based to 0 based month
327              
328 12         49 $date = timelocal(reverse @time);
329             }
330              
331 27         1549 return utime($date, $date, $filename);
332             }
333              
334             #----------------------------------------------------------------------
335             # Split filename from directory
336              
337             sub fio_split_filename {
338 1163     1163 1 4246 my ($filename) = @_;
339              
340 1163         3149 $filename = rel2abs($filename);
341              
342 1163         13569 my ($dir, $file);
343 1163 100       18653 if (-d $filename) {
344 57         186 $file = '';
345 57         168 $dir = $filename;
346              
347             } else {
348 1106         4406 my @path = splitdir($filename);
349 1106         8582 $file = pop(@path);
350 1106         6331 $dir = catfile(@path);
351             }
352              
353 1163         4162 return ($dir, $file);
354             }
355              
356             #----------------------------------------------------------------------
357             # Convert filename to index file if it is a directory
358              
359             sub fio_to_file {
360 6     6 1 1260 my ($file, $ext) = @_;
361              
362 6 100       159 $file = catfile($file, "index.$ext") if -d $file;
363 6         32 return $file;
364             }
365              
366             #----------------------------------------------------------------------
367             # Return a list of files and directories in a directory
368              
369             sub fio_visit {
370 220     220 1 1158 my ($directory) = @_;
371 220         542 $directory = rel2abs($directory);
372              
373 220         2234 my @filenames;
374             my @directories;
375 220         1035 my $dd = IO::Dir->new($directory);
376 220 50       18176 die "Couldn't open $directory: $!\n" unless $dd;
377              
378             # Find matching files and directories
379 220         836 while (defined (my $file = $dd->read())) {
380 1492 100       18267 next unless no_upwards($file);
381 1052         9123 my $path = catfile($directory, $file);
382              
383 1052 100       13274 if (-d $path) {
384 106         604 push(@directories, $path);
385             } else {
386 946         5747 push(@filenames, $path);
387             }
388             }
389              
390 220         4146 $dd->close;
391              
392 220         4224 @filenames = sort(@filenames);
393 220         419 @directories = sort(@directories);
394              
395 220         1015 return (\@filenames, \@directories);
396             }
397              
398             #----------------------------------------------------------------------
399             # Write the page back to the file
400              
401             sub fio_write_page {
402 182     182 1 32175 my ($filename, $page, $binmode) = @_;
403              
404 182         444 my ($dir, $base) = fio_split_filename($filename);
405              
406 182 50       2539 if (! -e $dir) {
407 0 0       0 die "Couldn't create directory $dir for $filename: $!\n"
408             unless mkdir($dir);
409             }
410            
411 182         1279 my $fd = IO::File->new($filename, 'w');
412 182 50       28045 die "Couldn't write $filename: $!\n" unless $fd;
413              
414 182 100       596 binmode($fd, $binmode) if defined $binmode;
415 182         1622 print $fd $page;
416 182         8024 close($fd);
417              
418 182 50       2994 die "Didn't write page $filename\n" unless -e $filename;
419              
420 182         1255 return;
421             }
422              
423             1;
424              
425             =pod
426              
427             =encoding utf-8
428              
429             =head1 NAME
430              
431             App::Followme::FIO - File IO routines used by followme
432              
433             =head1 SYNOPSIS
434              
435             use App::Followme::FIO;
436              
437             =head1 DESCRIPTION
438              
439             This module contains the subroutines followme uses to access the file system
440              
441             =head1 SUBROUTINES
442              
443             =over 4
444              
445             =item $url = fio_filename_to_url($directory, $filename, $ext);
446              
447             Convert a filename into a url. The directory is the top directory of the
448             website. The optional extension, if passed, replaces the extension on the file.
449              
450             =item $str = fio_flatten($data);
451              
452             Converted a nested data sructure containing hashes, arrays, and strings
453             to a string by representing hash key value pairs as a colon separated
454             pairs and then joining the pairs with commas and also joining array
455             elements with commas.
456              
457             =item $date_string = fio_format_date($date, $format);
458              
459             Convert a date to a new format. If the format is omitted, the ISO format is used.
460              
461             =item $filename = fio_full_file_name(@path);
462              
463             Construct a filename from a list of path components.
464              
465             =item $date = $date = fio_get_date($filename);
466              
467             Get the modification date of a file as seconds since 1970 (Unix standard.)
468              
469             =item $size =fio_get_size($filename);
470              
471             Get the size of a file in bytes.
472              
473             =item $globbed_patterns = fio_glob_patterns($pattern);
474              
475             Convert a comma separated list of Unix style filename patterns into a reference
476             to an array of Perl regular expressions.
477              
478             item $test = fio_is_newer($target, @sources);
479              
480             Compare the modification date of the target file to the modification dates of
481             the source files. If the target file is newer than all of the sources, return
482             1 (true).
483              
484             =item $filename = fio_make_dir($filename);
485              
486             Make a new directory for a file to live in if the directory does not already
487             exist. Return the filename if the directory already existed or was created
488             and the empty string if the directory could not be created.
489              
490             =item $flag = fio_match_patterns($filename, $patterns);
491              
492             Return 1 (Perl true) if a filename matches a Perl pattern in a list of
493             patterns.
494              
495             =item $filename = fio_most_recent_file($directory, $patterns, $exclude_index);
496              
497              
498             Return the most recently modified file in a directory whose name matches
499             a comma separated list of Unix wildcard patterns. Exclude the index if
500             the last argument is true.
501              
502             =item $str = fio_read_page($filename, $binmode);
503              
504             Read a file into a string. An the entire file is read from a string, there is no
505             line at a time IO. This is because files are typically small and the parsing
506             done is not line oriented. Binmode is an optional parameter that indicates file
507             type if it is not a plain text file.
508              
509             =item fio_set_date($filename, $date);
510              
511             Set the modification date of a file. Date is either in seconds or
512             is in ISO format.
513              
514             =item $filename = fio_shorten_path($filename);
515              
516             Remove dotted directories ('.' and '..') from filename path.
517              
518             =item ($directory, $filename) = fio_split_filename($filename);
519              
520             Split an absolute filename into a directory and the filename it contains. If
521             the input filename is a directory, the filename is the empty string.
522              
523             =item $filename = fio_to_file($directory, $ext);
524              
525             Convert a directory name to the index file it contains. The extension
526             is used in the index name. If the directory name is a file name,
527             return it unchnged.
528              
529             =item ($filenames, $directories) = fio_visit($top_directory);
530              
531             Return a list of filenames and directories in a directory,
532              
533             =item fio_write_page($filename, $str, $binmode);
534              
535             Write a file from a string. An the entire file is written from a string, there
536             is no line at a time IO. This is because files are typically small. Binmode is
537             an optional parameter that indicates file type if it is not a plain text file.
538              
539             =back
540              
541             =head1 LICENSE
542              
543             Copyright (C) Bernie Simon.
544              
545             This library is free software; you can redistribute it and/or modify
546             it under the same terms as Perl itself.
547              
548             =head1 AUTHOR
549              
550             Bernie Simon E<lt>bernie.simon@gmail.comE<gt>
551              
552             =cut