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