File Coverage

blib/lib/App/Critique/Command/collect.pm
Criterion Covered Total %
statement 18 137 13.1
branch 0 56 0.0
condition 0 57 0.0
subroutine 6 18 33.3
pod 2 5 40.0
total 26 273 9.5


line stmt bran cond sub pod time code
1             package App::Critique::Command::collect;
2              
3 1     1   905 use strict;
  1         1  
  1         25  
4 1     1   4 use warnings;
  1         1  
  1         38  
5              
6             our $VERSION = '0.04';
7             our $AUTHORITY = 'cpan:STEVAN';
8              
9 1     1   4 use Path::Tiny ();
  1         1  
  1         12  
10 1     1   505 use Term::ANSIColor ':constants';
  1         4832  
  1         560  
11              
12 1     1   6 use App::Critique::Session;
  1         1  
  1         20  
13              
14 1     1   4 use App::Critique -command;
  1         1  
  1         6  
15              
16             sub opt_spec {
17 0     0 1   my ($class) = @_;
18             return (
19 0           [ 'root=s', 'directory to start traversal from (default is root of git work tree)' ],
20             [],
21             [ 'no-violation', 'filter files that contain no Perl::Critic violations ' ],
22             [],
23             [ 'filter|f=s', 'filter files to remove with this regular expression' ],
24             [ 'match|m=s', 'match files to keep with this regular expression' ],
25             [],
26             [ 'dry-run', 'display list of files, but do not store them' ],
27             [],
28             $class->SUPER::opt_spec,
29             );
30             }
31              
32             sub execute {
33 0     0 1   my ($self, $opt, $args) = @_;
34              
35 0           local $Term::ANSIColor::AUTORESET = 1;
36              
37 0           my $session = $self->cautiously_load_session( $opt, $args );
38              
39 0           info('Session file loaded.');
40              
41 0 0         my $root = $opt->root
42             ? Path::Tiny::path( $opt->root )
43             : $session->git_work_tree;
44              
45 0           my @all;
46 0           traverse_filesystem(
47             root => $session->git_work_tree,
48             path => $root,
49             predicate => generate_file_predicate(
50             $session => (
51             filter => $opt->filter,
52             match => $opt->match,
53             no_violation => $opt->no_violation
54             )
55             ),
56             accumulator => \@all,
57             verbose => $opt->verbose,
58             );
59              
60 0           my $num_files = scalar @all;
61 0           info('Collected %d perl file(s) for critique.', $num_files);
62              
63 0           foreach my $file ( @all ) {
64 0           info(
65             ITALIC('Including %s'),
66             Path::Tiny::path( $file )->relative( $root )
67             );
68             }
69              
70 0 0 0       if ( $opt->verbose && $opt->no_violation ) {
71 0           my $stats = $session->perl_critic->statistics;
72 0           info(HR_DARK);
73 0           info('STATISTICS(Perl::Critic)');
74 0           info(HR_LIGHT);
75 0           info(BOLD(' VIOLATIONS : %s'), format_number($stats->total_violations));
76 0           info('== PERL '.('=' x (TERM_WIDTH() - 8)));
77 0           info(' modules : %s', format_number($stats->modules));
78 0           info(' subs : %s', format_number($stats->subs));
79 0           info(' statements : %s', format_number($stats->statements));
80 0           info('== LINES '.('=' x (TERM_WIDTH() - 9)));
81 0           info(BOLD('TOTAL : %s'), format_number($stats->lines));
82 0           info(' perl : %s', format_number($stats->lines_of_perl));
83 0           info(' pod : %s', format_number($stats->lines_of_pod));
84 0           info(' comments : %s', format_number($stats->lines_of_comment));
85 0           info(' data : %s', format_number($stats->lines_of_data));
86 0           info(' blank : %s', format_number($stats->lines_of_blank));
87 0           info(HR_DARK);
88             }
89              
90 0 0         if ( $opt->dry_run ) {
91 0           info('[dry run] %s file(s) found, 0 files added.', format_number($num_files));
92             }
93             else {
94 0           $session->set_tracked_files( @all );
95 0           $session->reset_file_idx;
96 0           info('Sucessfully added %s file(s).', format_number($num_files));
97              
98 0           $self->cautiously_store_session( $session, $opt, $args );
99 0           info('Session file stored successfully (%s).', $session->session_file_path);
100             }
101             }
102              
103             sub traverse_filesystem {
104 0     0 0   my %args = @_;
105 0           my $root = $args{root};
106 0           my $path = $args{path};
107 0           my $predicate = $args{predicate};
108 0           my $acc = $args{accumulator};
109 0           my $verbose = $args{verbose};
110              
111 0 0         if ( $path->is_file ) {
    0          
112              
113             #warn "GOT A FILE: $path";
114              
115             # ignore anything but perl files ...
116 0 0         return unless is_perl_file( $path->stringify );
117              
118             #warn "NOT PERL FILE: $path";
119              
120             # only accept things that match the path
121 0 0         if ( $predicate->( $root, $path ) ) {
122 0 0         info(BOLD('Matched: keeping file (%s)'), $path->relative( $root )) if $verbose;
123 0           push @$acc => $path;
124             }
125             else {
126 0 0         info('Not Matched: skipping file (%s)', $path->relative( $root )) if $verbose;
127             }
128             }
129             elsif ( -l $path ) { # Path::Tiny does not have a test for symlinks
130             ;
131             }
132             else {
133              
134             #warn "GOT A DIR: $path";
135              
136 0           my @children = $path->children( qr/^[^.]/ );
137              
138             # prune the directories we really don't care about
139 0 0         if ( my $ignore = $App::Critique::CONFIG{'IGNORE'} ) {
140 0           @children = grep !$ignore->{ $_->basename }, @children;
141             }
142              
143             # recurse ...
144 0           foreach my $child ( @children ) {
145 0           traverse_filesystem(
146             root => $root,
147             path => $child,
148             predicate => $predicate,
149             accumulator => $acc,
150             verbose => $verbose,
151             );
152             }
153             }
154              
155 0           return;
156             }
157              
158             sub generate_file_predicate {
159 0     0 0   my ($session, %args) = @_;
160              
161 0           my $filter = $args{filter};
162 0           my $match = $args{match};
163 0           my $no_violation = $args{no_violation};
164              
165 0           my $predicate;
166              
167             # ------------------------------#
168             # filter | match | no-violation #
169             # ------------------------------#
170             # 0 | 0 | 0 # collect
171             # ------------------------------#
172             # 1 | 0 | 0 # collect with filter
173             # 1 | 1 | 0 # collect with filter and match
174             # 1 | 1 | 1 # collect with filter match and no violations
175             # 1 | 0 | 1 # collect with filter and no violations
176             # ------------------------------#
177             # 0 | 1 | 0 # collect with match
178             # 0 | 1 | 1 # collect with match and no violations
179             # ------------------------------#
180             # 0 | 0 | 1 # collect with no violations
181             # ------------------------------#
182              
183             # filter only
184 0 0 0       if ( $filter && not($match) && not($no_violation) ) {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
185             $predicate = sub {
186 0     0     my $root = $_[0];
187 0           my $path = $_[1];
188 0           my $rel = $path->relative( $root );
189 0           return $rel !~ /$filter/;
190 0           };
191             }
192             # filter and match
193             elsif ( $filter && $match && not($no_violation) ) {
194             $predicate = sub {
195 0     0     my $root = $_[0];
196 0           my $path = $_[1];
197 0           my $rel = $path->relative( $root );
198 0   0       return $rel =~ /$match/
199             && $rel !~ /$filter/;
200 0           };
201             }
202             # filter and match and check violations
203             elsif ( $filter && $match && $no_violation ) {
204 0           my $c = $session->perl_critic;
205             $predicate = sub {
206 0     0     my $root = $_[0];
207 0           my $path = $_[1];
208 0           my $rel = $path->relative( $root );
209 0   0       return $rel =~ /$match/
210             && $rel !~ /$filter/
211             && scalar $c->critique( $path->stringify );
212 0           };
213             }
214             # filter and check violations
215             elsif ( $filter && not($match) && $no_violation ) {
216 0           my $c = $session->perl_critic;
217             $predicate = sub {
218 0     0     my $root = $_[0];
219 0           my $path = $_[1];
220 0           my $rel = $path->relative( $root );
221 0   0       return $rel !~ /$filter/
222             && scalar $c->critique( $path->stringify );
223 0           };
224             }
225             # match only
226             elsif ( not($filter) && $match && not($no_violation) ) {
227             $predicate = sub {
228 0     0     my $root = $_[0];
229 0           my $path = $_[1];
230 0           my $rel = $path->relative( $root );
231 0           return $rel =~ /$match/;
232 0           };
233             }
234             # match and check violations
235             elsif ( not($filter) && $match && $no_violation ) {
236 0           my $c = $session->perl_critic;
237             $predicate = sub {
238 0     0     my $root = $_[0];
239 0           my $path = $_[1];
240 0           my $rel = $path->relative( $root );
241 0   0       return $rel =~ /$match/
242             && scalar $c->critique( $path->stringify );
243 0           };
244             }
245             # check violations only
246             elsif ( not($filter) && not($match) && $no_violation ) {
247 0           my $c = $session->perl_critic;
248             $predicate = sub {
249 0     0     my $path = $_[1];
250 0           return scalar $c->critique( $path->stringify );
251 0           };
252             }
253             # none of the above
254             else {
255 0           $predicate = sub () { 1 };
256             }
257              
258 0           $session->set_file_criteria({
259             filter => $filter,
260             match => $match,
261             no_violation => $no_violation
262             });
263              
264 0           return $predicate;
265             }
266              
267             # NOTE:
268             # This was mostly taken from the guts of
269             # Perl::Critic::Util::{_is_perl,_is_backup}
270             # - SL
271             sub is_perl_file {
272 0     0 0   my ($file) = @_;
273              
274             # skip all the backups
275 0 0         return 0 if $file =~ m{ [.] swp \z}xms;
276 0 0         return 0 if $file =~ m{ [.] bak \z}xms;
277 0 0         return 0 if $file =~ m{ ~ \z}xms;
278 0 0         return 0 if $file =~ m{ \A [#] .+ [#] \z}xms;
279              
280             # but grab the perl files
281 0 0         return 1 if $file =~ m{ [.] PL \z}xms;
282 0 0         return 1 if $file =~ m{ [.] p[lm] \z}xms;
283 0 0         return 1 if $file =~ m{ [.] t \z}xms;
284 0 0         return 1 if $file =~ m{ [.] psgi \z}xms;
285 0 0         return 1 if $file =~ m{ [.] cgi \z}xms;
286              
287             # if we have to, check for shebang
288 0           my $first;
289             {
290 0 0         open my $fh, '<', $file or return 0;
  0            
291 0           $first = <$fh>;
292 0           close $fh;
293             }
294              
295 0 0 0       return 1 if defined $first && ( $first =~ m{ \A [#]!.*perl }xms );
296 0           return 0;
297             }
298              
299             1;
300              
301             =pod
302              
303             =head1 NAME
304              
305             App::Critique::Command::collect - Collect set of files for current critique session
306              
307             =head1 VERSION
308              
309             version 0.04
310              
311             =head1 DESCRIPTION
312              
313             This command will traverse the critque directory and gather all available Perl
314             files for critiquing. It will then store this list inside the correct critique
315             session file for processing during the critique session.
316              
317             It should be noted that this is a destructive command, meaning that if you have
318             begun critiquing your files and you re-run this command it will overwrite that
319             list and you will loose any tracking information you currently have.
320              
321             =head1 AUTHOR
322              
323             Stevan Little <stevan@cpan.org>
324              
325             =head1 COPYRIGHT AND LICENSE
326              
327             This software is copyright (c) 2016 by Stevan Little.
328              
329             This is free software; you can redistribute it and/or modify it under
330             the same terms as the Perl 5 programming language system itself.
331              
332             =cut
333              
334             __END__
335              
336             # ABSTRACT: Collect set of files for current critique session
337