File Coverage

blib/lib/App/Critique/Command/process.pm
Criterion Covered Total %
statement 18 141 12.7
branch 0 36 0.0
condition 0 6 0.0
subroutine 6 13 46.1
pod 2 5 40.0
total 26 201 12.9


line stmt bran cond sub pod time code
1             package App::Critique::Command::process;
2              
3 1     1   713 use strict;
  1         1  
  1         23  
4 1     1   2 use warnings;
  1         1  
  1         37  
5              
6             our $VERSION = '0.04';
7             our $AUTHORITY = 'cpan:STEVAN';
8              
9 1     1   4 use Path::Tiny ();
  1         1  
  1         13  
10 1     1   2 use Term::ANSIColor ':constants';
  1         1  
  1         197  
11              
12 1     1   5 use App::Critique::Session;
  1         1  
  1         18  
13              
14 1     1   3 use App::Critique -command;
  1         1  
  1         5  
15              
16             sub opt_spec {
17 0     0 1   my ($class) = @_;
18             return (
19 0           [ 'reset', 'resets the file index to 0', { default => 0 } ],
20             [ 'back', 'back up and re-process the last file', { default => 0 } ],
21             [ 'next', 'skip over processing the current file ', { default => 0 } ],
22             [],
23             $class->SUPER::opt_spec
24             );
25             }
26              
27             sub execute {
28 0     0 1   my ($self, $opt, $args) = @_;
29              
30 0           local $Term::ANSIColor::AUTORESET = 1;
31              
32 0           my $session = $self->cautiously_load_session( $opt, $args );
33              
34 0           info('Session file loaded.');
35              
36 0           my @tracked_files = $session->tracked_files;
37              
38 0 0         if ( $opt->back ) {
39 0           $session->dec_file_idx;
40 0           $tracked_files[ $session->current_file_idx ]->forget_all;
41             }
42              
43 0 0         if ( $opt->next ) {
44 0           $session->inc_file_idx;
45             }
46              
47 0 0         if ( $opt->reset ) {
48 0           $session->reset_file_idx;
49 0           $_->forget_all foreach @tracked_files;
50             }
51              
52 0 0         if ( $session->current_file_idx == scalar @tracked_files ) {
53 0           info(HR_DARK);
54 0           info('All files have already been processed.');
55 0           info(HR_LIGHT);
56 0           info('- run `critique status` to see more information');
57 0           info('- run `critique process --reset` to review all files again');
58 0           info(HR_DARK);
59 0           return;
60             }
61              
62 0           my ($idx, $file);
63              
64             MAIN:
65 0           while (1) {
66              
67 0           info(HR_DARK);
68              
69 0           $idx = $session->current_file_idx;
70 0           $file = $tracked_files[ $idx ];
71              
72 0           my $path = $file->relative_path( $session->git_work_tree );
73              
74 0           info('Running Perl::Critic against (%s)', $path);
75 0           info(HR_LIGHT);
76              
77 0           my @violations = $self->discover_violations( $session, $file, $opt );
78              
79             # remember it the first time we use it
80             # but do not update it for each re-process
81             # which we do after each edit
82 0 0         $file->remember('violations' => scalar @violations)
83             unless $file->recall('violations');
84              
85 0 0         if ( @violations == 0 ) {
86 0           info(ITALIC('No violations found, proceeding to next file.'));
87 0           next MAIN;
88             }
89             else {
90 0           my $should_review = prompt_yn(
91             BOLD(sprintf 'Found %d violations, would you like to review them?', (scalar @violations)),
92             { default => 'y' }
93             );
94              
95 0 0         if ( $should_review ) {
96              
97 0   0       my ($reviewed, $edited) = (
      0        
98             $file->recall('reviewed') // 0,
99             $file->recall('edited') // 0,
100             );
101              
102 0           foreach my $violation ( @violations ) {
103              
104 0           $self->display_violation( $session, $file, $violation, $opt );
105 0           $reviewed++;
106              
107 0           my $should_edit = prompt_yn(
108             BOLD('Would you like to fix this violation?'),
109             { default => 'y' }
110             );
111              
112 0           my $did_commit = 0;
113              
114 0 0         if ( $should_edit ) {
115 0           $did_commit = $self->edit_violation( $session, $file, $violation );
116 0 0         $edited++ if $did_commit;
117             }
118              
119             # keep state on disc ...
120 0           $file->remember('reviewed', $reviewed);
121 0           $file->remember('edited', $edited);
122 0           $self->cautiously_store_session( $session, $opt, $args );
123              
124 0 0         if ( $did_commit ) {
125 0           info(HR_LIGHT);
126 0           info('File was edited, re-processing is required');
127 0           redo MAIN;
128             }
129             }
130             }
131             }
132              
133             } continue {
134              
135 0           $session->inc_file_idx;
136 0           $self->cautiously_store_session( $session, $opt, $args );
137              
138 0 0         if ( ($idx + 1) == scalar @tracked_files ) {
139 0           info(HR_LIGHT);
140 0           info('Processing complete, run `status` to see results.');
141 0           last MAIN;
142             }
143              
144             }
145              
146             }
147              
148             sub discover_violations {
149 0     0 0   my ($self, $session, $file, $opt) = @_;
150              
151 0           my @violations = $session->perl_critic->critique( $file->path->stringify );
152              
153 0           return @violations;
154             }
155              
156              
157             sub display_violation {
158 0     0 0   my ($self, $session, $file, $violation, $opt) = @_;
159 0           info(HR_DARK);
160 0           info(BOLD('Violation: %s'), $violation->description);
161 0           info(HR_DARK);
162 0           info('%s', $violation->explanation);
163             #if ( $opt->verbose ) {
164             # info(HR_LIGHT);
165             # info('%s', $violation->diagnostics);
166             #}
167 0           info(HR_LIGHT);
168 0           info(' policy : %s' => $violation->policy);
169 0           info(' severity : %d' => $violation->severity);
170 0           info(' location : %s @ <%d:%d>' => (
171             Path::Tiny::path( $violation->filename )->relative( $session->git_work_tree ),
172             $violation->line_number,
173             $violation->column_number
174             ));
175 0           info(HR_LIGHT);
176 0           info(ITALIC('%s'), $violation->source);
177 0           info(HR_LIGHT);
178             }
179              
180             sub edit_violation {
181 0     0 0   my ($self, $session, $file, $violation) = @_;
182              
183 0           my $git = $session->git_wrapper;
184 0           my $filename = $violation->filename;
185              
186 0           my $cmd_fmt = $App::Critique::CONFIG{EDITOR};
187 0           my @cmd_args = (
188             $filename,
189             $violation->line_number,
190             $violation->column_number
191             );
192              
193 0           my $cmd = sprintf $cmd_fmt => @cmd_args;
194              
195 0           EDIT:
196             system $cmd;
197              
198 0           my $statuses = $git->status;
199 0           my @changed = $statuses->get('changed');
200 0           my $did_edit = scalar grep { my $from = $_->from; $filename =~ /$from/ } @changed;
  0            
  0            
201              
202 0 0         if ( $did_edit ) {
203 0           info(HR_DARK);
204 0           info('Changes detected, generating diff.');
205 0           info(HR_LIGHT);
206 0           info('%s', join "\n" => $git->diff);
207              
208 0           my $policy_name = $violation->policy;
209 0           $policy_name =~ s/^Perl\:\:Critic\:\:Policy\:\://;
210              
211 0           my $commit_msg = sprintf "%s - critique(%s)" => $violation->description, $policy_name;
212              
213 0           CHOOSE:
214              
215             info(HR_LIGHT);
216 0           my $commit_this_change = prompt_yn(
217             (
218             BOLD('Commit Message:').
219             "\n\n $commit_msg\n\n".
220             BOLD('Choose (y)es to use this message, or (n)o for more options')
221             ),
222             { default => 'y' }
223             );
224              
225 0 0         if ( $commit_this_change ) {
226 0           info(HR_DARK);
227 0           info('Adding and commiting file (%s) to git', $filename);
228 0           info(HR_LIGHT);
229 0           info('%s', join "\n" => $git->add($filename, { v => 1 }));
230 0           info('%s', join "\n" => $git->commit({ v => 1, message => $commit_msg }));
231              
232 0           my ($sha) = $git->rev_parse('HEAD');
233              
234 0 0         $file->remember('shas' => [ @{ $file->recall('shas') || [] }, $sha ]);
  0            
235 0   0       $file->remember('commited' => ($file->recall('commited') || 0) + 1);
236              
237 0           return 1;
238             }
239             else {
240 0           info(HR_LIGHT);
241             my $what_now = prompt_str(
242             BOLD('What would you like to edit? (f)ile, (c)ommit message'),
243 0     0     { valid => sub { $_[0] =~ m/[fc]{1}/ } }
244 0           );
245              
246 0 0         if ( $what_now eq 'c' ) {
    0          
247 0           info(HR_LIGHT);
248 0           $commit_msg = prompt_str( BOLD('Please write a commit message: ') );
249 0           goto CHOOSE;
250             }
251             elsif ( $what_now eq 'f' ) {
252 0           goto EDIT;
253             }
254             }
255             }
256             else {
257 0           info(HR_LIGHT);
258             my $what_now = prompt_str(
259             BOLD('No edits found, would like to (e)dit again, or (s)kip this file?'),
260 0     0     { valid => sub { $_[0] =~ m/[es]{1}/ } }
261 0           );
262              
263 0 0         if ( $what_now eq 'e' ) {
    0          
264 0           goto EDIT;
265             }
266             elsif ( $what_now eq 's' ) {
267 0           return 0;
268             }
269             }
270              
271 0           return 0;
272             }
273              
274             1;
275              
276             =pod
277              
278             =head1 NAME
279              
280             App::Critique::Command::process - Critique all the files.
281              
282             =head1 VERSION
283              
284             version 0.04
285              
286             =head1 DESCRIPTION
287              
288             This command will start or resume the critique session, allowing you to
289             step through the files and critique them. This current state of this
290             processing will be stored in the critique session file and so can be
291             stopped and resumed at any time.
292              
293             Note, this is an interactive command, so ...
294              
295             =head1 AUTHOR
296              
297             Stevan Little <stevan@cpan.org>
298              
299             =head1 COPYRIGHT AND LICENSE
300              
301             This software is copyright (c) 2016 by Stevan Little.
302              
303             This is free software; you can redistribute it and/or modify it under
304             the same terms as the Perl 5 programming language system itself.
305              
306             =cut
307              
308             __END__
309              
310             # ABSTRACT: Critique all the files.
311