File Coverage

lib/App/Followme/FIO.pm
Criterion Covered Total %
statement 189 202 93.5
branch 65 84 77.3
condition 1 3 33.3
subroutine 27 28 96.4
pod 15 18 83.3
total 297 335 88.6


line stmt bran cond sub pod time code
1             package App::Followme::FIO;
2              
3 23     23   17107 use 5.008005;
  23         87  
4 23     23   131 use strict;
  23         50  
  23         483  
5 23     23   110 use warnings;
  23         43  
  23         637  
6 23     23   11734 use integer;
  23         327  
  23         125  
7 23     23   9356 use lib '../..';
  23         12308  
  23         126  
8              
9 23     23   14417 use IO::Dir;
  23         282210  
  23         1275  
10 23     23   182 use IO::File;
  23         49  
  23         3089  
11 23     23   13114 use Time::Local;
  23         53847  
  23         1433  
12 23     23   14052 use Time::Format;
  23         57655  
  23         169  
13 23         52632 use File::Spec::Functions qw(abs2rel catfile curdir file_name_is_absolute
14 23     23   2393 no_upwards rel2abs splitdir);
  23         52  
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.02";
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 2006 my ($directory, $filename, $ext) = @_;
54              
55 3         11 $filename = rel2abs($filename);
56 3         41 $filename = fio_to_file($filename, $ext);
57 3         16 $filename = abs2rel($filename, $directory);
58              
59 3 50       223 my @path = $filename eq '.' ? () : splitdir($filename);
60              
61 3         19 my $url = join('/', @path);
62 3 100       22 $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 300     300 1 3859 my ($data) = @_;
72              
73 300 100       617 if (ref($data) eq 'HASH') {
74 3         5 my @buffer;
75 3         15 foreach my $key (sort keys %$data) {
76 8         18 my $value = fio_flatten($data->{$key});
77 8         24 push(@buffer, "$key: $value");
78             }
79            
80 3         8 $data = \@buffer;
81             }
82            
83 300 100       569 if (ref($data) eq 'ARRAY') {
84 5         7 my @buffer;
85 5         11 foreach my $value (@$data) {
86 12         27 push(@buffer, fio_flatten($value));
87             }
88            
89 5         14 $data = join(", ", @buffer);
90             }
91            
92 300         632 return $data;
93             }
94              
95             #----------------------------------------------------------------------
96             # Format a date string
97              
98             sub fio_format_date {
99 140     140 1 283 my ($date, $format) = @_;
100            
101 140 100       341 $format = 'yyyy-mm-ddThh:mm:ss' unless defined $format;
102 140         460 $date = time_format($format, $date);
103 140         65532 return $date;
104             }
105              
106             #----------------------------------------------------------------------
107             # Construct the full file name from a relative file name
108              
109             sub fio_full_file_name {
110 154     154 1 2294 my (@directories) = @_;
111              
112 154 100       454 return $directories[-1] if file_name_is_absolute($directories[-1]);
113              
114 138         819 my @dirs;
115 138         220 foreach my $dir (@directories) {
116 276         1158 push(@dirs, splitdir($dir));
117             }
118              
119 138         533 my @new_dirs;
120 138         213 foreach my $dir (@dirs) {
121 1058 50       1811 if (no_upwards($dir)) {
122 1058         5143 push(@new_dirs, $dir);
123             } else {
124 0 0       0 pop(@new_dirs) unless $dir eq '.';
125             }
126             }
127              
128 138         923 return catfile(@new_dirs);
129             }
130              
131             #----------------------------------------------------------------------
132             # Get modification date of file
133              
134             sub fio_get_date {
135 251     251 1 1966 my ($filename) = @_;
136              
137 251         358 my $date;
138 251 100       3988 if (-e $filename) {
139 240         3197 my @stats = stat($filename);
140 240         734 $date = $stats[9];
141             } else {
142 11         28 $date = time();
143             }
144              
145 251         745 return $date;
146             }
147              
148             #----------------------------------------------------------------------
149             # Get size of file
150              
151             sub fio_get_size {
152 1     1 1 2 my ($filename) = @_;
153              
154 1         2 my $size;
155 1 50       19 if (-e $filename) {
156 1         14 my @stats = stat($filename);
157 1         4 $size = $stats[7];
158             } else {
159 0         0 $size = 0;
160             }
161              
162 1         3 return $size;
163             }
164              
165             #----------------------------------------------------------------------
166             # Map filename globbing metacharacters onto regexp metacharacters
167              
168             sub fio_glob_patterns {
169 118     118 1 683 my ($patterns) = @_;
170 118         209 my @globbed_patterns = ();
171              
172 118 100       297 if ($patterns) {
173 86         338 my @patterns = split(/\s*,\s*/, $patterns);
174              
175 86         226 foreach my $pattern (@patterns) {
176 100 50       347 if ($pattern eq '*') {
177 0         0 push(@globbed_patterns, '.');
178              
179             } else {
180 100         159 my $start;
181 100 100       445 if ($pattern =~ s/^\*//) {
182 75         189 $start = '';
183             } else {
184 25         36 $start = '^';
185             }
186              
187 100         214 my $finish;
188 100 100       314 if ($pattern =~ s/\*$//) {
189 25         39 $finish = '';
190             } else {
191 75         127 $finish = '$';
192             }
193              
194 100         282 $pattern =~ s/\./\\./g;
195 100         211 $pattern =~ s/\*/\.\*/g;
196 100         165 $pattern =~ s/\?/\.\?/g;
197              
198 100         375 push(@globbed_patterns, $start . $pattern . $finish);
199             }
200             }
201             }
202              
203 118         427 return \@globbed_patterns;
204             }
205              
206             #----------------------------------------------------------------------
207             # Is the target newer than any source file?
208              
209             sub fio_is_newer {
210 17     17 0 1650 my ($target, @sources) = @_;
211              
212 17 100       351 my $target_date = -e $target ? fio_get_date($target) : 0;
213              
214 17         61 foreach my $source (@sources) {
215 21 50       57 next unless defined $source;
216              
217 21 100       281 next unless -e $source;
218 20 50       94 next if fio_same_file($target, $source);
219              
220 20         57 my $source_date = fio_get_date($source);
221 20 100       134 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 139     139 1 267 my ($file, $patterns) = @_;
232              
233 139         251 foreach my $pattern (@$patterns) {
234 139 100       836 return 1 if $file =~ /$pattern/;
235             }
236              
237 69         196 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 140 my ($directory, $pattern) = @_;
245              
246 37         87 my ($filenames, $directories) = fio_visit($directory);
247              
248 37         835 my $newest_file;
249 37         71 my $newest_date = 0;
250 37         104 my $globs = fio_glob_patterns($pattern);
251              
252 37         94 foreach my $filename (@$filenames) {
253 139         267 my ($dir, $file) = fio_split_filename($filename);
254 139 100       340 next unless fio_match_patterns($file, $globs);
255              
256 70         246 my $file_date = fio_get_date($filename);
257              
258 70 100       236 if ($file_date > $newest_date) {
259 42         76 $newest_date = $file_date;
260 42         93 $newest_file = $filename;
261             }
262             }
263              
264 37         167 return $newest_file;
265             }
266              
267             #----------------------------------------------------------------------
268             # Read a file into a string
269              
270             sub fio_read_page {
271 346     346 1 6022262 my ($filename, $binmode) = @_;
272 346 50       893 return unless defined $filename;
273              
274 346         1382 local $/;
275 346         2070 my $fd = IO::File->new($filename, 'r');
276 346 100       36378 return unless $fd;
277              
278 332 100       982 binmode($fd, $binmode) if defined $binmode;
279 332         8752 my $page = <$fd>;
280 332         3511 close($fd);
281              
282 332         2971 return $page;
283             }
284              
285             #----------------------------------------------------------------------
286             # Check if two filenames are the same in an os independent way
287              
288             sub fio_same_file {
289 44     44 0 3540 my ($filename1, $filename2, $case_sensitivity) = @_;
290 44 100       128 $case_sensitivity = 0 unless defined $case_sensitivity;
291 44 50 33     247 return unless defined $filename1 && defined $filename2;
292              
293 44         147 my @path1 = splitdir(rel2abs($filename1));
294 44         1031 my @path2 = splitdir(rel2abs($filename2));
295 44 100       678 return unless @path1 == @path2;
296              
297 35         102 while(@path1) {
298 264         383 my $part1 = shift(@path1);
299 264         350 my $part2 = shift(@path2);
300              
301 264 50       417 unless ($case_sensitivity) {
302 264         378 $part1 = lc($part1);
303 264         355 $part2 = lc($part2);
304             }
305              
306 264 100       592 return unless $part1 eq $part2;
307             }
308              
309 23         87 return 1;
310             }
311              
312             #----------------------------------------------------------------------
313             # Set modification date of file
314              
315             sub fio_set_date {
316 27     27 1 82 my ($filename, $date) = @_;
317              
318 27 100       124 if ($date =~ /[^\d]/) {
319 12 50       46 die "Can't convert date: $date\n" unless $date =~ /T/;
320 12         62 my @time = split(/[^\d]/, $date);
321 12         38 $time[1] -= 1; # from 1 based to 0 based month
322              
323 12         56 $date = timelocal(reverse @time);
324             }
325              
326 27         1773 return utime($date, $date, $filename);
327             }
328              
329             #----------------------------------------------------------------------
330             # Split filename from directory
331              
332             sub fio_split_filename {
333 1175     1175 1 4238 my ($filename) = @_;
334              
335 1175         2895 $filename = rel2abs($filename);
336              
337 1175         13788 my ($dir, $file);
338 1175 100       21671 if (-d $filename) {
339 60         254 $file = '';
340 60         110 $dir = $filename;
341              
342             } else {
343 1115         4600 my @path = splitdir($filename);
344 1115         8433 $file = pop(@path);
345 1115         6537 $dir = catfile(@path);
346             }
347              
348 1175         4194 return ($dir, $file);
349             }
350              
351             #----------------------------------------------------------------------
352             # Convert filename to index file if it is a directory
353              
354             sub fio_to_file {
355 6     6 1 1153 my ($file, $ext) = @_;
356              
357 6 100       159 $file = catfile($file, "index.$ext") if -d $file;
358 6         48 return $file;
359             }
360              
361             #----------------------------------------------------------------------
362             # Return a list of files and directories in a directory
363              
364             sub fio_visit {
365 235     235 1 1125 my ($directory) = @_;
366 235         542 $directory = rel2abs($directory);
367              
368 235         2413 my @filenames;
369             my @directories;
370 235         1214 my $dd = IO::Dir->new($directory);
371 235 50       18778 die "Couldn't open $directory: $!\n" unless $dd;
372              
373             # Find matching files and directories
374 235         866 while (defined (my $file = $dd->read())) {
375 1562 100       19440 next unless no_upwards($file);
376 1092         9503 my $path = catfile($directory, $file);
377              
378 1092 100       16066 if (-d $path) {
379 114         714 push(@directories, $path);
380             } else {
381 978         5546 push(@filenames, $path);
382             }
383             }
384              
385 235         4304 $dd->close;
386              
387 235         5000 @filenames = sort(@filenames);
388 235         437 @directories = sort(@directories);
389              
390 235         1100 return (\@filenames, \@directories);
391             }
392              
393             #----------------------------------------------------------------------
394             # Write the page back to the file
395              
396             sub fio_write_page {
397 180     180 1 29019 my ($filename, $page, $binmode) = @_;
398              
399 180         465 my ($dir, $base) = fio_split_filename($filename);
400              
401 180 50       2826 if (! -e $dir) {
402 0 0       0 die "Couldn't create directory $dir for $filename: $!\n"
403             unless mkdir($dir);
404             }
405            
406 180         1356 my $fd = IO::File->new($filename, 'w');
407 180 50       24560 die "Couldn't write $filename: $!\n" unless $fd;
408              
409 180 100       527 binmode($fd, $binmode) if defined $binmode;
410 180         1466 print $fd $page;
411 180         30825 close($fd);
412              
413 180         1353 return;
414             }
415              
416             1;
417              
418             =pod
419              
420             =encoding utf-8
421              
422             =head1 NAME
423              
424             App::Followme::FIO - File IO routines used by followme
425              
426             =head1 SYNOPSIS
427              
428             use App::Followme::FIO;
429              
430             =head1 DESCRIPTION
431              
432             This module contains the subroutines followme uses to access the file system
433              
434             =head1 SUBROUTINES
435              
436             =over 4
437              
438             =item $url = fio_filename_to_url($directory, $filename, $ext);
439              
440             Convert a filename into a url. The directory is the top directory of the
441             website. The optional extension, if passed, replaces the extension on the file.
442              
443             =item $str = fio_flatten($data);
444              
445             Converted a nested data sructure containing hashes, arrays, and strings
446             to a string by representing hash key value pairs as a colon separated
447             pairs and then joining the pairs with commas and also joining array
448             elements with commas.
449              
450             =item $date_string = fio_format_date($date, $format);
451              
452             Convert a date to a new format. If the format is omitted, the ISO format is used.
453              
454             =item $filename = fio_full_file_name(@path);
455              
456             Construct a filename from a list of path components.
457              
458             =item $date = $date = fio_get_date($filename);
459              
460             Get the modification date of a file as seconds since 1970 (Unix standard.)
461              
462             =item $size =fio_get_size($filename);
463              
464             Get the size of a file in bytes.
465              
466             =item $globbed_patterns = fio_glob_patterns($pattern);
467              
468             Convert a comma separated list of Unix style filename patterns into a reference
469             to an array of Perl regular expressions.
470              
471             item $test = fio_is_newer($target, @sources);
472              
473             Compare the modification date of the target file to the modification dates of
474             the source files. If the target file is newer than all of the sources, return
475             1 (true).
476              
477             =item $filename = fio_make_dir($filename);
478              
479             Make a new directory for a file to live in if the directory does not already
480             exist. Return the filename if the directory already existed or was created
481             and the empty string if the directory could not be created.
482              
483             =item $flag = fio_match_patterns($filename, $patterns);
484              
485             Return 1 (Perl true) if a filename matches a Perl pattern in a list of
486             patterns.
487              
488             =item $filename = fio_most_recent_file($directory, $patterns);
489              
490              
491             Return the most recently modified file in a directory whose name matches
492             a comma separated list of Unix wildcard patterns.
493              
494             =item $str = fio_read_page($filename, $binmode);
495              
496             Read a file into a string. An the entire file is read from a string, there is no
497             line at a time IO. This is because files are typically small and the parsing
498             done is not line oriented. Binmode is an optional parameter that indicates file
499             type if it is not a plain text file.
500              
501             =item fio_set_date($filename, $date);
502              
503             Set the modification date of a file. Date is either in seconds or
504             is in ISO format.
505              
506             =item $filename = fio_shorten_path($filename);
507              
508             Remove dotted directories ('.' and '..') from filename path.
509              
510             =item ($directory, $filename) = fio_split_filename($filename);
511              
512             Split an absolute filename into a directory and the filename it contains. If
513             the input filename is a directory, the filename is the empty string.
514              
515             =item $filename = fio_to_file($directory, $ext);
516              
517             Convert a directory name to the index file it contains. The extension
518             is used in the index name. If the directory name is a file name,
519             return it unchnged.
520              
521             =item ($filenames, $directories) = fio_visit($top_directory);
522              
523             Return a list of filenames and directories in a directory,
524              
525             =item fio_write_page($filename, $str, $binmode);
526              
527             Write a file from a string. An the entire file is written from a string, there
528             is no line at a time IO. This is because files are typically small. Binmode is
529             an optional parameter that indicates file type if it is not a plain text file.
530              
531             =back
532              
533             =head1 LICENSE
534              
535             Copyright (C) Bernie Simon.
536              
537             This library is free software; you can redistribute it and/or modify
538             it under the same terms as Perl itself.
539              
540             =head1 AUTHOR
541              
542             Bernie Simon E<lt>bernie.simon@gmail.comE<gt>
543              
544             =cut