File Coverage

lib/App/Followme/FIO.pm
Criterion Covered Total %
statement 188 202 93.0
branch 65 84 77.3
condition 1 3 33.3
subroutine 27 28 96.4
pod 15 18 83.3
total 296 335 88.3


line stmt bran cond sub pod time code
1             package App::Followme::FIO;
2              
3 23     23   17495 use 5.008005;
  23         86  
4 23     23   125 use strict;
  23         43  
  23         489  
5 23     23   113 use warnings;
  23         43  
  23         623  
6 23     23   11975 use integer;
  23         332  
  23         119  
7 23     23   9583 use lib '../..';
  23         12572  
  23         130  
8              
9 23     23   15026 use IO::Dir;
  23         287604  
  23         1332  
10 23     23   200 use IO::File;
  23         52  
  23         3165  
11 23     23   13415 use Time::Local;
  23         54198  
  23         1464  
12 23     23   14956 use Time::Format;
  23         58069  
  23         179  
13 23         53462 use File::Spec::Functions qw(abs2rel catfile curdir file_name_is_absolute
14 23     23   2459 no_upwards rel2abs splitdir);
  23         55  
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.01";
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 2384 my ($directory, $filename, $ext) = @_;
54              
55 3         10 $filename = rel2abs($filename);
56 3         37 $filename = fio_to_file($filename, $ext);
57 3         28 $filename = abs2rel($filename, $directory);
58              
59 3 50       222 my @path = $filename eq '.' ? () : splitdir($filename);
60              
61 3         16 my $url = join('/', @path);
62 3 100       20 $url =~ s/\.[^\.]*$/.$ext/ if defined $ext;
63              
64 3         13 return $url;
65             }
66              
67             #----------------------------------------------------------------------
68             # Flatten a data structure to a string
69              
70             sub fio_flatten {
71 287     287 1 4359 my ($data) = @_;
72              
73 287 100       612 if (ref($data) eq 'HASH') {
74 3         5 my @buffer;
75 3         14 foreach my $key (sort keys %$data) {
76 8         32 my $value = fio_flatten($data->{$key});
77 8         20 push(@buffer, "$key: $value");
78             }
79            
80 3         7 $data = \@buffer;
81             }
82            
83 287 100       565 if (ref($data) eq 'ARRAY') {
84 5         8 my @buffer;
85 5         7 foreach my $value (@$data) {
86 12         23 push(@buffer, fio_flatten($value));
87             }
88            
89 5         18 $data = join(", ", @buffer);
90             }
91            
92 287         602 return $data;
93             }
94              
95             #----------------------------------------------------------------------
96             # Format a date string
97              
98             sub fio_format_date {
99 135     135 1 287 my ($date, $format) = @_;
100            
101 135 100       363 $format = 'yyyy-mm-ddThh:mm:ss' unless defined $format;
102 135         474 $date = time_format($format, $date);
103 135         70952 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 2549 my (@directories) = @_;
111              
112 154 100       415 return $directories[-1] if file_name_is_absolute($directories[-1]);
113              
114 136         805 my @dirs;
115 136         218 foreach my $dir (@directories) {
116 272         1210 push(@dirs, splitdir($dir));
117             }
118              
119 136         533 my @new_dirs;
120 136         221 foreach my $dir (@dirs) {
121 1041 50       1742 if (no_upwards($dir)) {
122 1041         4959 push(@new_dirs, $dir);
123             } else {
124 0 0       0 pop(@new_dirs) unless $dir eq '.';
125             }
126             }
127              
128 136         872 return catfile(@new_dirs);
129             }
130              
131             #----------------------------------------------------------------------
132             # Get modification date of file
133              
134             sub fio_get_date {
135 246     246 1 1759 my ($filename) = @_;
136              
137 246         369 my $date;
138 246 100       4027 if (-e $filename) {
139 235         3213 my @stats = stat($filename);
140 235         761 $date = $stats[9];
141             } else {
142 11         37 $date = time();
143             }
144              
145 246         861 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       21 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         5 return $size;
163             }
164              
165             #----------------------------------------------------------------------
166             # Map filename globbing metacharacters onto regexp metacharacters
167              
168             sub fio_glob_patterns {
169 117     117 1 672 my ($patterns) = @_;
170 117         198 my @globbed_patterns = ();
171              
172 117 100       299 if ($patterns) {
173 85         351 my @patterns = split(/\s*,\s*/, $patterns);
174              
175 85         216 foreach my $pattern (@patterns) {
176 99 50       284 if ($pattern eq '*') {
177 0         0 push(@globbed_patterns, '.');
178              
179             } else {
180 99         139 my $start;
181 99 100       447 if ($pattern =~ s/^\*//) {
182 74         141 $start = '';
183             } else {
184 25         42 $start = '^';
185             }
186              
187 99         165 my $finish;
188 99 100       315 if ($pattern =~ s/\*$//) {
189 25         45 $finish = '';
190             } else {
191 74         124 $finish = '$';
192             }
193              
194 99         275 $pattern =~ s/\./\\./g;
195 99         185 $pattern =~ s/\*/\.\*/g;
196 99         169 $pattern =~ s/\?/\.\?/g;
197              
198 99         358 push(@globbed_patterns, $start . $pattern . $finish);
199             }
200             }
201             }
202              
203 117         412 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 1958 my ($target, @sources) = @_;
211              
212 17 100       361 my $target_date = -e $target ? fio_get_date($target) : 0;
213              
214 17         56 foreach my $source (@sources) {
215 21 50       56 next unless defined $source;
216              
217 21 100       271 next unless -e $source;
218 20 50       79 next if fio_same_file($target, $source);
219              
220 20         51 my $source_date = fio_get_date($source);
221 20 100       115 return if $source_date >= $target_date;
222             }
223              
224 3         17 return 1;
225             }
226              
227             #----------------------------------------------------------------------
228             # Return true if filename matches pattern
229              
230             sub fio_match_patterns {
231 134     134 1 235 my ($file, $patterns) = @_;
232              
233 134         253 foreach my $pattern (@$patterns) {
234 134 100       802 return 1 if $file =~ /$pattern/;
235             }
236              
237 68         200 return;
238             }
239              
240             #----------------------------------------------------------------------
241             # Get the most recently modified web file in a directory
242              
243             sub fio_most_recent_file {
244 36     36 1 90 my ($directory, $pattern) = @_;
245              
246 36         89 my ($filenames, $directories) = fio_visit($directory);
247              
248 36         813 my $newest_file;
249 36         67 my $newest_date = 0;
250 36         108 my $globs = fio_glob_patterns($pattern);
251              
252 36         92 foreach my $filename (@$filenames) {
253 134         244 my ($dir, $file) = fio_split_filename($filename);
254 134 100       327 next unless fio_match_patterns($file, $globs);
255              
256 66         210 my $file_date = fio_get_date($filename);
257              
258 66 100       281 if ($file_date > $newest_date) {
259 40         65 $newest_date = $file_date;
260 40         87 $newest_file = $filename;
261             }
262             }
263              
264 36         162 return $newest_file;
265             }
266              
267             #----------------------------------------------------------------------
268             # Read a file into a string
269              
270             sub fio_read_page {
271 331     331 1 6021617 my ($filename, $binmode) = @_;
272 331 50       864 return unless defined $filename;
273              
274 331         1391 local $/;
275 331         2160 my $fd = IO::File->new($filename, 'r');
276 331 100       36408 return unless $fd;
277              
278 317 100       934 binmode($fd, $binmode) if defined $binmode;
279 317         8731 my $page = <$fd>;
280 317         3458 close($fd);
281              
282 317         2973 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 3237 my ($filename1, $filename2, $case_sensitivity) = @_;
290 44 100       130 $case_sensitivity = 0 unless defined $case_sensitivity;
291 44 50 33     217 return unless defined $filename1 && defined $filename2;
292              
293 44         153 my @path1 = splitdir(rel2abs($filename1));
294 44         1012 my @path2 = splitdir(rel2abs($filename2));
295 44 100       628 return unless @path1 == @path2;
296              
297 34         95 while(@path1) {
298 256         405 my $part1 = shift(@path1);
299 256         338 my $part2 = shift(@path2);
300              
301 256 50       427 unless ($case_sensitivity) {
302 256         375 $part1 = lc($part1);
303 256         340 $part2 = lc($part2);
304             }
305              
306 256 100       575 return unless $part1 eq $part2;
307             }
308              
309 23         83 return 1;
310             }
311              
312             #----------------------------------------------------------------------
313             # Set modification date of file
314              
315             sub fio_set_date {
316 27     27 1 93 my ($filename, $date) = @_;
317              
318 27 100       134 if ($date =~ /[^\d]/) {
319 12 50       42 die "Can't convert date: $date\n" unless $date =~ /T/;
320 12         63 my @time = split(/[^\d]/, $date);
321 12         50 $time[1] -= 1; # from 1 based to 0 based month
322              
323 12         68 $date = timelocal(reverse @time);
324             }
325              
326 27         1876 return utime($date, $date, $filename);
327             }
328              
329             #----------------------------------------------------------------------
330             # Split filename from directory
331              
332             sub fio_split_filename {
333 1153     1153 1 5378 my ($filename) = @_;
334              
335 1153         2848 $filename = rel2abs($filename);
336              
337 1153         13778 my ($dir, $file);
338 1153 100       21991 if (-d $filename) {
339 59         197 $file = '';
340 59         205 $dir = $filename;
341              
342             } else {
343 1094         4428 my @path = splitdir($filename);
344 1094         8709 $file = pop(@path);
345 1094         6384 $dir = catfile(@path);
346             }
347              
348 1153         4152 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 1151 my ($file, $ext) = @_;
356              
357 6 100       162 $file = catfile($file, "index.$ext") if -d $file;
358 6         28 return $file;
359             }
360              
361             #----------------------------------------------------------------------
362             # Return a list of files and directories in a directory
363              
364             sub fio_visit {
365 232     232 1 1171 my ($directory) = @_;
366              
367 232         389 my @filenames;
368             my @directories;
369 232         1496 my $dd = IO::Dir->new($directory);
370 232 50       18304 die "Couldn't open $directory: $!\n" unless $dd;
371              
372             # Find matching files and directories
373 232         822 while (defined (my $file = $dd->read())) {
374 1536 100       19220 next unless no_upwards($file);
375 1072         9712 my $path = catfile($directory, $file);
376              
377 1072 100       16265 if (-d $path) {
378 114         649 push(@directories, $path);
379             } else {
380 958         5479 push(@filenames, $path);
381             }
382             }
383              
384 232         4265 $dd->close;
385              
386 232         4957 @filenames = sort(@filenames);
387 232         463 @directories = sort(@directories);
388              
389 232         1136 return (\@filenames, \@directories);
390             }
391              
392             #----------------------------------------------------------------------
393             # Write the page back to the file
394              
395             sub fio_write_page {
396 179     179 1 30233 my ($filename, $page, $binmode) = @_;
397              
398 179         484 my ($dir, $base) = fio_split_filename($filename);
399              
400 179 50       2876 if (! -e $dir) {
401 0         0 $dir = rel2abs($dir);
402 0 0       0 die "Couldn't create directory $dir for $filename: $!"
403             unless mkdir($dir);
404             }
405            
406 179         1399 my $fd = IO::File->new($filename, 'w');
407 179 50       24278 die "Couldn't write $filename: $!\n" unless $fd;
408              
409 179 100       543 binmode($fd, $binmode) if defined $binmode;
410 179         1813 print $fd $page;
411 179         9188 close($fd);
412              
413 179         1314 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