File Coverage

blib/lib/App/Info/Util.pm
Criterion Covered Total %
statement 82 86 95.3
branch 45 56 80.3
condition 7 24 29.1
subroutine 16 16 100.0
pod 12 12 100.0
total 162 194 83.5


line stmt bran cond sub pod time code
1             package App::Info::Util;
2              
3             =head1 NAME
4              
5             App::Info::Util - Utility class for App::Info subclasses
6              
7             =head1 SYNOPSIS
8              
9             use App::Info::Util;
10              
11             my $util = App::Info::Util->new;
12              
13             # Subclasses File::Spec.
14             my @paths = $util->paths;
15              
16             # First directory that exists in a list.
17             my $dir = $util->first_dir(@paths);
18              
19             # First directory that exists in a path.
20             $dir = $util->first_path($ENV{PATH});
21              
22             # First file that exists in a list.
23             my $file = $util->first_file('this.txt', '/that.txt', 'C:\\foo.txt');
24              
25             # First file found among file base names and directories.
26             my $files = ['this.txt', 'that.txt'];
27             $file = $util->first_cat_file($files, @paths);
28              
29             =head1 DESCRIPTION
30              
31             This class subclasses L and adds its own methods in
32             order to offer utility methods to L classes. Although
33             intended to be used by App::Info subclasses, in truth App::Info::Util's
34             utility may be considered more general, so feel free to use it elsewhere.
35              
36             The methods added in addition to the usual File::Spec suspects are designed to
37             facilitate locating files and directories on the file system, as well as
38             searching those files. The assumption is that, in order to provide useful
39             meta data about a given software package, an App::Info subclass must find
40             relevant files and directories and parse them with regular expressions. This
41             class offers methods that simplify those tasks.
42              
43             =cut
44              
45 15     15   36390 use strict;
  15         32  
  15         727  
46 15     15   77 use File::Spec ();
  15         31  
  15         230  
47 15     15   79 use Config;
  15         33  
  15         610  
48 15     15   79 use vars qw(@ISA $VERSION);
  15         48  
  15         36998  
49             @ISA = qw(File::Spec);
50             $VERSION = '0.57';
51              
52             my %path_dems = (
53             MacOS => qr',',
54             MSWin32 => qr';',
55             os2 => qr';',
56             VMS => undef,
57             epoc => undef
58             );
59              
60             my $path_dem = exists $path_dems{$^O} ? $path_dems{$^O} : qr':';
61              
62             =head1 CONSTRUCTOR
63              
64             =head2 new
65              
66             my $util = App::Info::Util->new;
67              
68             This is a very simple constructor that merely returns an App::Info::Util
69             object. Since, like its File::Spec super class, App::Info::Util manages no
70             internal data itself, all methods may be used as class methods, if one prefers
71             to. The constructor here is provided merely as a convenience.
72              
73             =cut
74              
75 18   33 18 1 249 sub new { bless {}, ref $_[0] || $_[0] }
76              
77             ##############################################################################
78              
79             =head1 OBJECT METHODS
80              
81             In addition to all of the methods offered by its super class,
82             L, App::Info::Util offers the following methods.
83              
84             =head2 first_dir
85              
86             my @paths = $util->paths;
87             my $dir = $util->first_dir(@dirs);
88              
89             Returns the first file system directory in @paths that exists on the local
90             file system. Only the first item in @paths that exists as a directory will be
91             returned; any other paths leading to non-directories will be ignored.
92              
93             =cut
94              
95             sub first_dir {
96 10     10 1 20 shift;
97 10 100       35 foreach (@_) { return $_ if -d }
  14         867  
98 0         0 return;
99             }
100              
101             ##############################################################################
102              
103             =head2 first_path
104              
105             my $path = $ENV{PATH};
106             $dir = $util->first_path($path);
107              
108             Takes the $path string and splits it into a list of directory paths, based on
109             the path delimiter on the local file system. Then calls C to
110             return the first directory in the path list that exists on the local file
111             system. The path delimiter is specified for the following file systems:
112              
113             =over 4
114              
115             =item * MacOS: ","
116              
117             =item * MSWin32: ";"
118              
119             =item * os2: ";"
120              
121             =item * VMS: undef
122              
123             This method always returns undef on VMS. Patches welcome.
124              
125             =item * epoc: undef
126              
127             This method always returns undef on epoch. Patches welcome.
128              
129             =item * Unix: ":"
130              
131             All other operating systems are assumed to be Unix-based.
132              
133             =back
134              
135             =cut
136              
137             sub first_path {
138 1 50   1 1 9 return unless $path_dem;
139 1         7 shift->first_dir(split /$path_dem/, shift)
140             }
141              
142             ##############################################################################
143              
144             =head2 first_file
145              
146             my $file = $util->first_file(@filelist);
147              
148             Examines each of the files in @filelist and returns the first one that exists
149             on the file system. The file must be a regular file -- directories will be
150             ignored.
151              
152             =cut
153              
154             sub first_file {
155 6     6 1 1002 shift;
156 6 100       15 foreach (@_) { return $_ if -f }
  9         162  
157 0         0 return;
158             }
159              
160             ##############################################################################
161              
162             =head2 first_exe
163              
164             my $exe = $util->first_exe(@exelist);
165              
166             Examines each of the files in @exelist and returns the first one that exists
167             on the file system as an executable file. Directories will be ignored.
168              
169             =cut
170              
171             sub first_exe {
172 1     1 1 2 shift;
173 1 100 66     4 foreach (@_) { return $_ if -f && -x }
  3         65  
174 0         0 return;
175             }
176              
177             ##############################################################################
178              
179             =head2 first_cat_path
180              
181             my $file = $util->first_cat_path('ick.txt', @paths);
182             $file = $util->first_cat_path(['this.txt', 'that.txt'], @paths);
183              
184             The first argument to this method may be either a file or directory base name
185             (that is, a file or directory name without a full path specification), or a
186             reference to an array of file or directory base names. The remaining arguments
187             constitute a list of directory paths. C processes each of
188             these directory paths, concatenates (by the method native to the local
189             operating system) each of the file or directory base names, and returns the
190             first one that exists on the file system.
191              
192             For example, let us say that we were looking for a file called either F
193             or F, and it could be in any of the following paths:
194             F, F, F. The method call looks like this:
195              
196             my $httpd = $util->first_cat_path(['httpd', 'apache'], '/usr/local/bin',
197             '/usr/bin/', '/bin');
198              
199             If the OS is a Unix variant, C will then look for the first
200             file that exists in this order:
201              
202             =over 4
203              
204             =item /usr/local/bin/httpd
205              
206             =item /usr/local/bin/apache
207              
208             =item /usr/bin/httpd
209              
210             =item /usr/bin/apache
211              
212             =item /bin/httpd
213              
214             =item /bin/apache
215              
216             =back
217              
218             The first of these complete paths to be found will be returned. If none are
219             found, then undef will be returned.
220              
221             =cut
222              
223             sub first_cat_path {
224 8     8 1 26 my $self = shift;
225 8 100       40 my $files = ref $_[0] ? shift() : [shift()];
226 8         43 foreach my $p (@_) {
227 22         42 foreach my $f (@$files) {
228 45         307 my $path = $self->catfile($p, $f);
229 45 100       1203 return $path if -e $path;
230             }
231             }
232 0         0 return;
233             }
234              
235             ##############################################################################
236              
237             =head2 first_cat_dir
238              
239             my $dir = $util->first_cat_dir('ick.txt', @paths);
240             $dir = $util->first_cat_dir(['this.txt', 'that.txt'], @paths);
241              
242             Functionally identical to C, except that it returns the
243             directory path in which the first file was found, rather than the full
244             concatenated path. Thus, in the above example, if the file found was
245             F, while C would return that value,
246             C would return F instead.
247              
248             =cut
249              
250             sub first_cat_dir {
251 20     20 1 37 my $self = shift;
252 20 100       377 my $files = ref $_[0] ? shift() : [shift()];
253 20         49 foreach my $p (@_) {
254 70         111 foreach my $f (@$files) {
255 376         2632 my $path = $self->catfile($p, $f);
256 376 100       6043 return $p if -e $path;
257             }
258             }
259 5         41 return;
260             }
261              
262             ##############################################################################
263              
264             =head2 first_cat_exe
265              
266             my $exe = $util->first_cat_exe('ick.exe', @paths);
267             $exe = $util->first_cat_exe(['this.exe', 'that.exe'], @paths);
268              
269             Functionally identical to C, except that it returns the full
270             path to the first executable file found, rather than simply the first file
271             found.
272              
273             =cut
274              
275             sub first_cat_exe {
276 39     39 1 91 my $self = shift;
277 39 100       247 my $files = ref $_[0] ? shift() : [shift()];
278 39         160 foreach my $p (@_) {
279 130         233 foreach my $f (@$files) {
280 242         2753 my $path = $self->catfile($p, $f);
281 242 100 66     5701 return $path if -f $path && -x $path;
282             }
283             }
284 5         34 return;
285             }
286              
287             ##############################################################################
288              
289             =head2 search_file
290              
291             my $file = 'foo.txt';
292             my $regex = qr/(text\s+to\s+find)/;
293             my $value = $util->search_file($file, $regex);
294              
295             Opens C<$file> and executes the C<$regex> regular expression against each line
296             in the file. Once the line matches and one or more values is returned by the
297             match, the file is closed and the value or values returned.
298              
299             For example, say F contains the line "Version 6.5, patch level 8",
300             and you need to grab each of the three version parts. All three parts can
301             be grabbed like this:
302              
303             my $regex = qr/Version\s+(\d+)\.(\d+),[^\d]*(\d+)/;
304             my @nums = $util->search_file($file, $regex);
305              
306             Now C<@nums> will contain the values C<(6, 5, 8)>. Note that in a scalar
307             context, the above search would yield an array reference:
308              
309             my $regex = qr/Version\s+(\d+)\.(\d+),[^\d]*(\d+)/;
310             my $nums = $util->search_file($file, $regex);
311              
312             So now C<$nums> contains C<[6, 5, 8]>. The same does not hold true if the
313             match returns only one value, however. Say F contains the line
314             "king of the who?", and you wish to know who the king is king of. Either
315             of the following two calls would get you the data you need:
316              
317             my $minions = $util->search_file($file, qr/King\s+of\s+(.*)/);
318             my @minions = $util->search_file($file, qr/King\s+of\s+(.*)/);
319              
320             In the first case, because the regular expression contains only one set of
321             parentheses, C will simply return that value: C<$minions>
322             contains the string "the who?". In the latter case, C<@minions> of course
323             contains a single element: C<("the who?")>.
324              
325             Note that a regular expression without parentheses -- that is, one that
326             doesn't grab values and put them into $1, $2, etc., will never successfully
327             match a line in this method. You must include something to parenthetically
328             match. If you just want to know the value of what was matched, parenthesize
329             the whole thing and if the value returns, you have a match. Also, if you need
330             to match patterns across lines, try using multiple regular expressions with
331             C, instead.
332              
333             =cut
334              
335             sub search_file {
336 7     7 1 16 my ($self, $file, $regex) = @_;
337 7 50 33     49 return unless $file && $regex;
338 7 50 0     375 open F, "<$file" or require Carp && Carp::croak("Cannot open $file: $!\n");
339 7         14 my @ret;
340 7         145 while () {
341             # If we find a match, we're done.
342 170 100       699 (@ret) = /$regex/ and last;
343             }
344 7         78 close F;
345             # If the match returned an more than one value, always return the full
346             # array. Otherwise, return just the first value in a scalar context.
347 7 100       34 return unless @ret;
348 3 50       28 return wantarray ? @ret : $#ret <= 0 ? $ret[0] : \@ret;
    100          
349             }
350              
351             ##############################################################################
352              
353             =head2 files_in_dir
354              
355             my @files = $util->files_in_dir($dir);
356             @files = $util->files_in_dir($dir, $filter);
357             my $files = $util->files_in_dir($dir);
358             $files = $util->files_in_dir($dir, $filter);
359              
360             Returns an list or array reference of all of the files and directories in the
361             file system directory C<$dir>. An optional second argument is a code reference
362             that filters the files. The code reference should examine the C<$_> for a file
363             name and return true if it's a file that you're interested and false if it's
364             not.
365              
366             =cut
367              
368             sub files_in_dir {
369 4     4 1 27 my ($self, $dir, $code) = @_;
370 4 50       28 return unless $dir;
371 4         26 local *DIR;
372 4 50 0     529 opendir DIR, $dir or require Carp && Carp::croak("Cannot open $dir: $!\n");
373 18         986 my @files = $code
374 4 100       179 ? grep { $code->() } readdir DIR
375             : readdir DIR;
376 4         78 closedir DIR;
377 4 100       97 return wantarray ? @files : \@files;
378             }
379              
380             ##############################################################################
381              
382             =head2 multi_search_file
383              
384             my @regexen = (qr/(one)/, qr/(two)\s+(three)/);
385             my @matches = $util->multi_search_file($file, @regexen);
386              
387             Like C, this method opens C<$file> and parses it for regular
388             expression matches. This method, however, can take a list of regular
389             expressions to look for, and will return the values found for all of them.
390             Regular expressions that match and return multiple values will be returned as
391             array references, while those that match and return a single value will return
392             just that single value.
393              
394             For example, say you are parsing a file with lines like the following:
395              
396             #define XML_MAJOR_VERSION 1
397             #define XML_MINOR_VERSION 95
398             #define XML_MICRO_VERSION 2
399              
400             You need to get each of these numbers, but calling C for each
401             of them would be wasteful, as each call to C opens the file and
402             parses it. With C, on the other hand, the file will be
403             opened only once, and, once all of the regular expressions have returned
404             matches, the file will be closed and the matches returned.
405              
406             Thus the above values can be collected like this:
407              
408             my @regexen = ( qr/XML_MAJOR_VERSION\s+(\d+)$/,
409             qr/XML_MINOR_VERSION\s+(\d+)$/,
410             qr/XML_MICRO_VERSION\s+(\d+)$/ );
411              
412             my @nums = $file->multi_search_file($file, @regexen);
413              
414             The result will be that C<@nums> contains C<(1, 95, 2)>. Note that
415             C tries to do the right thing by only parsing the file
416             until all of the regular expressions have been matched. Thus, a large file
417             with the values you need near the top can be parsed very quickly.
418              
419             As with C, C can take regular expressions
420             that match multiple values. These will be returned as array references. For
421             example, say the file you're parsing has files like this:
422              
423             FooApp Version 4
424             Subversion 2, Microversion 6
425              
426             To get all of the version numbers, you can either use three regular
427             expressions, as in the previous example:
428              
429             my @regexen = ( qr/FooApp\s+Version\s+(\d+)$/,
430             qr/Subversion\s+(\d+),/,
431             qr/Microversion\s+(\d$)$/ );
432              
433             my @nums = $file->multi_search_file($file, @regexen);
434              
435             In which case C<@nums> will contain C<(4, 2, 6)>. Or, you can use just two
436             regular expressions:
437              
438             my @regexen = ( qr/FooApp\s+Version\s+(\d+)$/,
439             qr/Subversion\s+(\d+),\s+Microversion\s+(\d$)$/ );
440              
441             my @nums = $file->multi_search_file($file, @regexen);
442              
443             In which case C<@nums> will contain C<(4, [2, 6])>. Note that the two
444             parentheses that return values in the second regular expression cause the
445             matches to be returned as an array reference.
446              
447             =cut
448              
449             sub multi_search_file {
450 11     11 1 41 my ($self, $file, @regexen) = @_;
451 11 50 33     100 return unless $file && @regexen;
452 11         32 my @each = @regexen;
453 11 50 0     827 open F, "<$file" or require Carp && Carp::croak("Cannot open $file: $!\n");
454 11         29 my %ret;
455 11         257 while (my $line = ) {
456 41         72 my @splice;
457             # Process each of the regular expresssions.
458 41         137 for (my $i = 0; $i < @each; $i++) {
459 138 100       1653 if ((my @ret) = $line =~ /$each[$i]/) {
460             # We have a match! If there's one match returned, just grab
461             # it. If there's more than one, keep it as an array ref.
462 46 100       305 $ret{$each[$i]} = $#ret > 0 ? \@ret : $ret[0];
463             # We got values for this regex, so not its place in the @each
464             # array.
465 46         325 push @splice, $i;
466             }
467             }
468             # Remove any regexen that have already found a match.
469 41         82 for (@splice) { splice @each, $_, 1 }
  46         119  
470             # If there are no more regexes, we're done -- no need to keep
471             # processing lines in the file!
472 41 100       266 last unless @each;
473             }
474 11         144 close F;
475 11 50       36 return unless %ret;
476 11 50       139 return wantarray ? @ret{@regexen} : \@ret{@regexen};
477             }
478              
479             ##############################################################################
480              
481             =head2 lib_dirs
482              
483             my @dirs = $util->lib_dirs;
484              
485             Returns a list of possible library directories to be searched. These are
486             gathered from the C and C Config settings. These are
487             useful for passing to C to search typical directories for
488             library files.
489              
490             =cut
491              
492             sub lib_dirs {
493 80 50       325 grep { defined and length }
  30         93  
494 30         29775 map { split ' ' }
495 10     10 1 18987 grep { defined }
496             # Quote Config access to work around
497             # http://bugs.activestate.com/show_bug.cgi?id=89447
498             "$Config{libsdirs}",
499             "$Config{loclibpth}",
500             '/sw/lib';
501             }
502              
503             1;
504             __END__