File Coverage

blib/lib/File/Collector.pm
Criterion Covered Total %
statement 218 226 96.4
branch 65 88 73.8
condition 5 6 83.3
subroutine 36 36 100.0
pod 16 16 100.0
total 340 372 91.4


line stmt bran cond sub pod time code
1             package File::Collector ;
2             $File::Collector::VERSION = '0.037';
3 3     3   370125 use strict; use warnings;
  3     3   24  
  3         87  
  3         16  
  3         6  
  3         80  
4              
5 3     3   14 use Carp;
  3         6  
  3         161  
6 3     3   16 use File::Spec;
  3         6  
  3         66  
7 3     3   15 use File::Basename;
  3         6  
  3         327  
8 3     3   1384 use Role::Tiny::With;
  3         15121  
  3         8950  
9              
10             # public methods
11              
12             sub AUTOLOAD {
13 41     41   8431 our $AUTOLOAD;
14 41         70 my $s = shift;
15 41 100       342 $AUTOLOAD =~ /.*::(next_|isa_|get_)*(\w+)_files*$/ or
16             croak "No such method: $AUTOLOAD";
17              
18 40 100       170 if (!$s->{_files}{"$2_files"}) { $s->_scroak("No such file category exists: '$2' at "); }
  1         5  
19 39 100       94 else { return $s->{_files}{"$2_files"} if !$1; }
20              
21 37 100       85 if ($1 eq 'next_') {
22 23         86 return $s->{_files}{"$2_files"}->next;
23             }
24              
25 14 100       37 if ($1 eq 'isa_') {
26 2         11 return $s->{_files}{"$2_files"}->_isa($s->selected);
27             }
28              
29             # must be a "get" method
30 12         25 my $cat = $2;
31 12         24 my $class = $s->{_processor_map}{$cat};
32             my $obj = $class->new($s->{_files}{all},
33             \($s->{selected}),
34 12         67 $s->{_files}{"${cat}_files"}{_files});
35 12         35 return $obj;
36             }
37              
38             sub new {
39 11     11 1 7437 my $class = shift;
40              
41             # process args
42 11         32 my ($user_opts, $classes, @resources) = _get_args(@_);
43              
44             # get options hash
45 6         21 my $default_opts = { recurse => 1 };
46 6         33 my %opts = (%$default_opts, %$user_opts);
47              
48             # construct object
49 6         37 my $s = bless {
50             _files => { all => {} },
51             _common_dir => '',
52             selected => '',
53             _options => \%opts,
54             _classes => $classes,
55             all => undef,
56             }, $class;
57              
58             # eval class code
59 6         38 foreach my $class ( @$classes ) {
60 5         297 eval "require $class";
61             }
62              
63             # a bit of trickery to make Processor class code consistent with base class
64 6         1759 $s->{all} = $s->{_files}{all};
65              
66             # add rersources and process files
67 6         26 $s->add_resources(@resources);
68              
69 5         26 return $s;
70             }
71              
72             sub add_resources {
73 6     6 1 16 my ($s, @resources) = @_;
74              
75             # collect the files
76 6         13 foreach my $resource (@resources) {
77 7         42 $s->_exists($resource);
78 6 100       78 $s->_add_file($resource) if -f $resource;
79 6 100       85 $s->_get_file_manifest($resource) if -d $resource;
80             }
81              
82 5         25 $s->_generate_short_names; # calculate the short names
83 5         19 $s->_init_all_processors;
84 5         11 foreach my $file (@{$s->{_files}{new}}) {
  5         13  
85 23         37 $s->{selected} = $file;
86 23         123 $s->_classify_all;
87             }
88 5         26 $s->_run_all;
89 5         25 undef $s->{selected};
90 5         15 undef $s->{_files}{new}; # clear the new_file array
91             }
92              
93             sub get_count {
94 3     3 1 919 my $s = shift;
95 3         4 return (scalar keys %{$s->{_files}{all}})
  3         16  
96             }
97              
98             sub get_files {
99 7     7 1 46 my $s = shift;
100              
101 7         13 my @files = sort keys %{$s->{_files}{all}};
  7         55  
102 7         28 return @files;
103             }
104              
105             sub get_file {
106 2     2 1 4281 my ($s, $file) = @_;
107 2 100       32 $s->_scroak('No file argument passed to method. Aborting.') if !$file;
108              
109 1         22 return $s->{_files}{all}{$file};
110             }
111              
112             sub list_files_long {
113 1     1 1 2525 my $s = shift;
114              
115 1         22 my @files = $s->get_files;
116 1         103 print $_ . "\n" for @files;
117             }
118              
119             sub list_files {
120 2     2 1 2945 my $s = shift;
121              
122 2         5 my @files = map { $s->{_files}{all}{$_}{short_path} } sort keys %{$s->{_files}{all}};
  18         34  
  2         17  
123 2         77 print "\nFiles found in '".$s->{_common_dir}."':\n\n";
124 2         145 print $_ . "\n" for @files;
125             }
126              
127       20     sub DESTROY {
128             }
129              
130             # private methods meant for used by subclasses
131              
132             sub _classify {
133 23     23   170 my ($s, @classes) = @_;
134 23         47 foreach my $type (@classes) {
135 23         40 my $t = $type . '_files';
136 23         87 my $file = $s->selected;
137              
138             # die if bad args given
139 23 50       51 die ("No $type argument sent to _classify method. Aborting.") if !$type;
140 23 50       68 die ("No processor called $type exists. Aborting.") if !$s->{_files}{$t};
141              
142 23         86 $s->{_files}{$t}->_add_file($file, $s->{_files}{all}{$file});
143             }
144             }
145              
146             sub _add_obj {
147 23     23   297 my ($s, $type, $obj) = @_;
148 23 50       47 $s->_scroak("Missing args to 'add_obj' method. Aborting.") if (!$obj);
149              
150 23         51 $s->{_files}{all}{$s->selected}{"${type}_obj"} = $obj;
151             }
152              
153             # Methods for iterators
154              
155             sub get_obj_prop {
156 121     121 1 595 my ($s, $obj, $prop) = @_;
157              
158 121 100       225 if (!$prop) {
159 1         17 $s->_scroak ("Missing arguments to get_obj_prop method");
160             }
161              
162             my $file = ref ($s->selected) eq 'HASH'
163             ? $s->selected->{full_path}
164 120 50       186 : $s->selected;
165 120         209 my $attr = "_$prop";
166 120         204 my $o = $obj . '_obj';
167 120         209 my $object = $s->{all}{$file}{$o};
168 120 50       209 if (! exists $object->{$attr} ) {
169 0         0 $s->_scroak ("Non-existent $obj object attribute requested: '_$prop'");
170             }
171 120         155 my $value = $object->{$attr};
172 120 50       202 if (ref $value eq 'ARRAY') {
173 0         0 return @$value;
174             } else {
175 120         248 return $value;
176             }
177             }
178              
179             sub get_obj {
180 23     23 1 144 my ($s, $obj) = @_;
181              
182 23 50       45 if (!$obj) {
183 0         0 _scroak ("Missing arguments to get_obj method");
184             }
185              
186             my $file = ref ($s->selected) eq 'HASH'
187             ? $s->selected->{full_path}
188 23 50       38 : $s->selected;
189 23         41 my $o = $obj . '_obj';
190              
191 23         60 return $s->{all}{$file}{$o};
192             }
193              
194             sub set_obj_prop {
195 23     23 1 141 my ($s, $obj, $prop, $val) = @_;
196              
197 23 50       42 if (!$val) {
198 0         0 $s->_scroak ("Missing arguments to set_obj_prop method");
199             }
200              
201 23         44 my $file = $s->selected;
202              
203 23         41 my $o = $obj . '_obj';
204 23         39 my $object = $s->{all}{$file}{$o};
205 23         37 my $attr = "_$prop";
206 23 50       49 if (! exists $object->{$attr} ) {
207 0         0 $s->_scroak ("Non-existent $obj object attribute requested: '$prop'");
208             }
209              
210 23         55 $object->{$attr} = $val;
211             }
212              
213             sub get_filename {
214 23     23 1 108 my $s = shift;
215 23         36 my $file = $s->selected;
216              
217 23         48 return $s->{all}{$file}{filename};
218             }
219              
220             sub obj_meth {
221             # Keep these args shifted individually
222 23     23 1 110 my $s = shift;
223 23         60 my $obj = shift;
224 23         33 my $meth = shift;
225             my $file = ref ($s->selected) eq 'HASH'
226             ? $s->selected->{full_path}
227 23 50       36 : $s->selected;
228              
229 23 50       46 if (!$meth) {
230 0         0 $s->_scroak ("Missing arguments to obj_meth method");
231             }
232              
233 23         46 my $o = $obj . '_obj';
234 23         41 $obj = $s->{all}{$file}{$o};
235              
236 23 50       83 if (! $obj->can($meth)) {
237 0         0 _scroak ("Non-existent method on $obj object: '$meth'");
238             }
239 23         95 return $obj->$meth($s->_short_name, @_);
240             }
241              
242             sub selected {
243 932     932 1 1329 my $s = shift;
244 932         3498 $s->{selected};
245             }
246              
247             sub has_obj {
248 24     24 1 221 my ($s, $type) = @_;
249              
250 24 100       45 if (!$type) {
251 1         8 $s->_scroak ("Missing argument to has method");
252             }
253              
254 23         39 my $to = "${type}_obj";
255             my $file = ref ($s->selected) eq 'HASH'
256             ? $s->selected->{full_path}
257 23 50       40 : $s->selected;
258 23         96 return defined $s->{all}{$file}{$to};
259             }
260              
261             sub attr_defined {
262 23     23 1 124 my $s = shift;
263 23         30 my $obj = shift;
264 23         30 my $attr = shift;
265              
266 23 50       76 if (ref $s->selected eq 'HASH') {
267 0         0 return exists $s->selected->{"${obj}_obj"}->{"_${attr}"};
268             } else {
269 23         41 return exists $s->{_files}{all}{$s->selected}{"${obj}_obj"}->{"_${attr}"};
270             }
271             }
272              
273             sub print_short_name {
274 30     30 1 61 my $s = shift;
275              
276 30 50       59 $s->_scroak ("The 'print_short_name' method does not accept methods") if @_;
277 30         65 print $s->_short_name . "\n";
278             }
279              
280             # private helper methods
281              
282             sub _short_name {
283 53     53   70 my $s = shift;
284             my $file = ref ($s->selected) eq 'HASH'
285             ? $s->selected->{full_path}
286 53 100       86 : $s->selected;
287 53         662 $s->{all}{$file}{short_path};
288             }
289              
290             sub _exists {
291 7     7   14 my $s = shift;
292 7 100       148 $s->_scroak("'$_[0]' does not exist, aborting call from: ") if ! -e $_[0];
293             }
294              
295             sub _scroak {
296 5     5   13 my $s = shift;
297 5         18 my $msg = shift;
298 5         29 croak($msg . ' ' . (fileparse((caller(1))[1]))[0] . ', line ' . (caller(1))[2] . "\n");
299             }
300              
301             sub _get_file_manifest {
302 10     10   26 my ($s, $dir) = @_;
303              
304 10 50       322 opendir (my $dh, $dir) or die "Can't opendir $dir: $!";
305 10         248 my @dirs_and_files = grep { /^[^\.]/ } readdir($dh);
  48         159  
306              
307 10         24 my @files = grep { -f File::Spec->catfile($dir, $_) } @dirs_and_files;
  28         617  
308 10         93 $s->_add_file( File::Spec->catfile($dir, $_)) for @files;
309              
310 10 100       37 my @dirs = grep { -d File::Spec->catdir( $dir, $_ ) } @dirs_and_files if $s->{_options}{recurse};
  26         449  
311 10         227 foreach my $tdir (@dirs) {
312 5 50       170 opendir (my $tdh, File::Spec->catdir($dir, $tdir)) || die "Can't opendir $tdir: $!";
313 5         63 $s->_get_file_manifest(File::Spec->catdir( $dir, $tdir ));
314             }
315             }
316              
317             sub _run_all {
318 5     5   10 my $s = shift;
319 5         8 my $classes = $s->{_classes};
320 5         10 foreach my $c ( @$classes ) {
321 5         16 my $role = Role::Tiny->apply_roles_to_object ($s, $c);
322 5 50       738 $role->_run_processes if $role->can('_run_processes');;
323             }
324             }
325              
326             sub _generate_short_names {
327 5     5   11 my $s = shift;
328              
329 5         13 my @files = $s->get_files;
330 5         9 my $file = pop @files;
331 5         77 my @letters = split //, $file;
332 5         15 my ($new_string, $longest_string) = '';
333 5         13 foreach my $cfile (@files) {
334 18         151 my @cletters = split //, $cfile;
335 18         23 my $lc = 0;
336              
337 18         25 foreach my $cletter (@cletters) {
338 1072 100 100     2841 if (defined $letters[$lc] && $cletters[$lc] eq $letters[$lc]) {
339 1054         1334 $new_string .= $cletters[$lc++];
340 1054         1365 next;
341             }
342 18         25 $longest_string = $new_string;
343 18         168 @letters = split //, $new_string;
344 18         23 $new_string = '';
345 18         83 last;
346             }
347             }
348              
349 5   66     48 $s->{_common_dir} = $longest_string || (fileparse($file))[1];
350              
351 5 100       30 if (@files) {
352 3         8 foreach my $file ( @files, $file ) {
353 21         31 my $tfile = $file;
354 21         109 $tfile =~ s/\Q$longest_string\E//;
355 21         62 $s->{_files}{all}{$file}{short_path} = $tfile;
356             }
357             } else {
358 2         14 $s->{_files}{all}{$file}{short_path} = $file;
359             }
360             }
361              
362             sub _add_file {
363 23     23   53 my ($s, $file) = @_;
364              
365 23         464 $file = File::Spec->rel2abs( $file );
366 23         100 $s->{_files}{all}{$file}{full_path} = $file;
367 23         338 my $filename = (fileparse($file))[0];
368 23         66 $s->{_files}{all}{$file}{filename} = $filename;
369              
370 23 50       62 push @{$s->{_files}{new}}, $file if !$s->{_files}{$file};
  23         186  
371             }
372              
373             sub _init_all_processors {
374 5     5   9 my $s = shift;
375              
376 5         7 foreach my $c ( @{ $s->{_classes} } ) {
  5         14  
377 5 50       53 my @processors = $c->_init_processors if $c->can('_init_processors');
378 5         29 my $it_class = $c . '::Processor';
379 5         9 foreach my $it ( @processors ) {
380 10 50       32 next if ($s->{_files}{"${it}_files"}); # don't overwrite existing processor
381 10         24 $s->{_processor_map}{$it} = $it_class;
382 10         42 $s->{_files}{"${it}_files"} = $it_class->new($s->{_files}{all}, \($s->{selected}));
383             }
384             }
385             }
386              
387             sub _classify_all {
388 23     23   41 my $s = shift;
389 23         33 foreach my $c ( @{ $s->{_classes} } ) {
  23         44  
390 23         67 my $role = Role::Tiny->apply_roles_to_object ($s, $c);
391 23 50       3678 $role->_classify_file() if $role->can('_classify_file');;
392             }
393             }
394              
395             sub _get_args {
396 11     11   20 my $user_opts = {};
397 11         22 my @resources;
398             my $classes;
399 11         21 foreach my $arg (@_) {
400 24 100       76 if (!ref $arg) {
    100          
    100          
401 10         21 push @resources, $arg;
402             } elsif (ref($arg) eq 'HASH') {
403 4 100       22 die ('Only one option hash allowed in constructor. Aborting.') if %$user_opts;
404 3         8 $user_opts = $arg;
405             } elsif (ref($arg) eq 'ARRAY') {
406 9 100       30 die ('Only one class array allowed in constructor. Aborting.') if $classes;
407 8         15 $classes = $arg;
408             } else {
409 1         9 die ('Unrecognized argument type passed to constructor');
410             }
411             }
412 8 100       38 die('No list of resources passed to constructor. Aborting.') if ! @resources;
413             #die('No Collector class array passed to constructor. Aborting.') if !$classes;
414              
415 6         53 return ($user_opts, $classes, @resources);
416             }
417              
418             1; # Magic true value
419             # ABSTRACT: Base class for custom File::Collector classes for classifying files and calling File::Collector::Processor methods for processing files
420              
421             __END__