File Coverage

blib/lib/File/Collector.pm
Criterion Covered Total %
statement 219 227 96.4
branch 66 90 73.3
condition 5 6 83.3
subroutine 36 36 100.0
pod 16 16 100.0
total 342 375 91.2


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