File Coverage

blib/lib/Code/TidyAll.pm
Criterion Covered Total %
statement 326 387 84.2
branch 92 154 59.7
condition 31 49 63.2
subroutine 67 73 91.7
pod 7 12 58.3
total 523 675 77.4


line stmt bran cond sub pod time code
1              
2             use strict;
3 27     27   1224767 use warnings;
  27         122  
  27         742  
4 27     27   119  
  27         49  
  27         663  
5             use Code::TidyAll::Cache;
6 27     27   9927 use Code::TidyAll::CacheModel;
  27         96  
  27         916  
7 27     27   12527 use Code::TidyAll::Config::INI::Reader;
  27         91  
  27         1015  
8 27     27   13480 use Code::TidyAll::Plugin;
  27         136  
  27         1052  
9 27     27   10736 use Code::TidyAll::Result;
  27         101  
  27         895  
10 27     27   10850 use Code::TidyAll::Zglob qw(zglob);
  27         96  
  27         1127  
11 27     27   11475 use Data::Dumper;
  27         69  
  27         1549  
12 27     27   1898 use Date::Format;
  27         17806  
  27         1086  
13 27     27   10931 use Digest::SHA qw(sha1_hex);
  27         172473  
  27         1831  
14 27     27   224 use File::Find qw(find);
  27         49  
  27         1349  
15 27     27   165 use File::pushd qw( pushd );
  27         53  
  27         1776  
16 27     27   10761 use List::SomeUtils qw(uniq);
  27         28145  
  27         1433  
17 27     27   11713 use Module::Runtime qw( use_module );
  27         191940  
  27         2117  
18 27     27   236 use Path::Tiny qw(path);
  27         51  
  27         175  
19 27     27   1563 use Scalar::Util qw(blessed);
  27         58  
  27         1113  
20 27     27   146 use Specio 0.40;
  27         46  
  27         1146  
21 27     27   144 use Specio::Declare;
  27         446  
  27         669  
22 27     27   134 use Specio::Library::Builtins;
  27         53  
  27         175  
23 27     27   4911 use Specio::Library::Numeric;
  27         67  
  27         427  
24 27     27   213131 use Specio::Library::Path::Tiny 0.04;
  27         62  
  27         182  
25 27     27   184765 use Specio::Library::String;
  27         831  
  27         273  
26 27     27   311557 use Time::Duration::Parse qw(parse_duration);
  27         93  
  27         202  
27 27     27   62622 use Try::Tiny;
  27         48030  
  27         171  
28 27     27   1894  
  27         62  
  27         1402  
29             use Moo 2.000000;
30 27     27   150  
  27         615  
  27         200  
31             our $VERSION = '0.81';
32              
33              
34 0     0 0 0 # External
35             has backup_ttl => (
36             is => 'ro',
37             isa => t('NonEmptyStr'),
38             default => '1 hour',
39             );
40              
41             has cache => (
42             is => 'lazy',
43             isa => object_can_type( methods => [qw( get set )] ),
44             );
45              
46             has cache_model_class => (
47             is => 'ro',
48             isa => t('ClassName'),
49             default => 'Code::TidyAll::CacheModel',
50             );
51              
52             has check_only => (
53             is => 'ro',
54             isa => t('Bool'),
55             );
56              
57             has data_dir => (
58             is => 'lazy',
59             isa => t('Path'),
60             coerce => t('Path')->coercion_sub,
61             );
62              
63             has iterations => (
64             is => 'ro',
65             isa => t('PositiveInt'),
66             default => 1,
67             );
68              
69             has jobs => (
70             is => 'ro',
71             isa => t('Int'),
72             default => 1,
73             );
74              
75             has list_only => (
76             is => 'ro',
77             isa => t('Bool'),
78             );
79              
80             has mode => (
81             is => 'ro',
82             isa => t('NonEmptyStr'),
83             default => 'cli',
84             );
85              
86             has msg_outputter => (
87             is => 'ro',
88             isa => t('CodeRef'),
89             builder => '_build_msg_outputter',
90             );
91              
92             has no_backups => (
93             is => 'ro',
94             isa => t('Bool'),
95             );
96              
97             has no_cache => (
98             is => 'ro',
99             isa => t('Bool'),
100             );
101              
102             has output_suffix => (
103             is => 'ro',
104             isa => t('Str'),
105             default => q{},
106             );
107              
108             has plugins => (
109             is => 'ro',
110             isa => t('HashRef'),
111             required => 1,
112             );
113              
114             has selected_plugins => (
115             is => 'ro',
116             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
117             lazy => 1,
118             default => sub { [] },
119             );
120              
121             has quiet => (
122             is => 'ro',
123             isa => t('Bool'),
124             );
125             has recursive => (
126             is => 'ro',
127             isa => t('Bool'),
128             );
129              
130             has refresh_cache => (
131             is => 'ro',
132             isa => t('Bool'),
133             );
134              
135             has root_dir => (
136             is => 'ro',
137             isa => t('RealDir'),
138             coerce => t('RealDir')->coercion_sub,
139             required => 1
140             );
141              
142             has verbose => (
143             is => 'ro',
144             isa => t('Bool'),
145             default => 0,
146             );
147              
148             has inc => (
149             is => 'ro',
150             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
151             default => sub { [] },
152             );
153              
154             has _backup_dir => (
155             is => 'ro',
156             isa => t('Path'),
157             init_arg => undef,
158             lazy => 1,
159             builder => '_build_backup_dir',
160             );
161              
162             has _backup_ttl_secs => (
163             is => 'ro',
164             isa => t('Int'),
165             init_arg => undef,
166             lazy => 1,
167             builder => '_build_backup_ttl_secs',
168             );
169              
170             has _base_sig => (
171             is => 'ro',
172             isa => t('NonEmptyStr'),
173             init_arg => undef,
174             lazy => 1,
175             builder => '_build_base_sig',
176             );
177              
178             has _plugin_objects => (
179             is => 'ro',
180             isa => t( 'ArrayRef', of => object_isa_type('Code::TidyAll::Plugin') ),
181             init_arg => undef,
182             lazy => 1,
183             builder => '_build_plugin_objects',
184             );
185              
186             has _plugins_to_run => (
187             is => 'ro',
188             isa => t( 'HashRef', of => t('HashRef') ),
189             init_arg => undef,
190             lazy => 1,
191             builder => '_build_plugins_to_run',
192             );
193              
194             has _plugins_for_path => (
195             is => 'ro',
196             isa => t( 'HashRef', of => t('HashRef') ),
197             init_arg => undef,
198             lazy => 1,
199             default => sub { {} },
200             );
201              
202             with qw( Code::TidyAll::Role::HasIgnore Code::TidyAll::Role::Tempdir );
203              
204             my $self = shift;
205             return $self->data_dir->child('backups');
206             }
207 64     64   787  
208 64         1090 my $self = shift;
209             return parse_duration( $self->backup_ttl );
210             }
211              
212 64     64   711 my $self = shift;
213 64         556 my $active_plugins = join( q{|}, map { $_->name } @{ $self->_plugin_objects } );
214             return $self->_sig( [ $Code::TidyAll::VERSION || 0, $active_plugins ] );
215             }
216              
217 34     34   2103 my ( $self, $data ) = @_;
218 34         112 return sha1_hex( join( ',', @$data ) );
  49         1380  
  34         709  
219 34   50     316 }
220              
221             my $self = shift;
222             return Code::TidyAll::Cache->new( cache_dir => $self->data_dir->child('cache') );
223 34     34   101 }
224 34         879  
225             my $self = shift;
226             return $self->root_dir->child('/.tidyall.d');
227             }
228 66     66   700  
229 66         942 my $self = shift;
230              
231             my $all_plugins = $self->plugins;
232             my %selected = map { $_ => 1 } @{ $self->selected_plugins };
233 68     68   4715 my %plugins;
234 68         388  
235             if (%selected) {
236             my @unknown = sort grep { !$all_plugins->{$_} } keys %selected;
237             die "Asked for unknown plugins: [@unknown]" if @unknown;
238 65     65   616 %plugins = map { $_ => $all_plugins->{$_} } keys %selected;
239             }
240 65         279 elsif ( my $mode = $self->mode ) {
241 65         122 %plugins = map { $_ => $all_plugins->{$_} }
  6         25  
  65         1190  
242 65         2623 grep { $self->_plugin_conf_matches_mode( $all_plugins->{$_}, $mode ) }
243             keys %{$all_plugins};
244 65 100       376 }
    50          
245 2         6  
  6         26  
246 2 100       18 return \%plugins;
247 1         4 }
  2         6  
248              
249             my ( $self, $conf, $mode ) = @_;
250 78         278  
251 79         237 if ( my $only_modes = $conf->{only_modes} ) {
252 63         121 return 0 if ( q{ } . $only_modes . q{ } ) !~ / $mode /;
  63         216  
253             }
254             if ( my $except_modes = $conf->{except_modes} ) {
255 64         1217 return 0 if ( q{ } . $except_modes . q{ } ) =~ / $mode /;
256             }
257             return 1;
258             }
259 79     79   222  
260             my $self = shift;
261 79 100       260  
262 2 100       24 # Sort tidiers by weight (by default validators have a weight of 60 and non-
263             # validators a weight of 50 meaning non-validators normally go first), then
264 78 50       222 # alphabetical
265 0 0       0 # TODO: These should probably sort in a consistent way independent of locale
266             return [
267 78         211 sort { ( $a->weight <=> $b->weight ) || ( $a->name cmp $b->name ) }
268             map { $self->_load_plugin( $_, $self->_plugins_to_run->{$_} ) }
269             keys %{ $self->_plugins_to_run }
270             ];
271 65     65   978 }
272              
273             my ( $self, $plugin_name, $plugin_conf ) = @_;
274              
275             # Extract first name in case there is a description
276             #
277             my ($plugin_fname) = ( $plugin_name =~ /^(\S+)/ );
278 27 50       817  
279 79         3887 my $plugin_class = (
280 65         163 $plugin_fname =~ /^\+/
  65         1150  
281             ? substr( $plugin_fname, 1 )
282             : "Code::TidyAll::Plugin::$plugin_fname"
283             );
284             try {
285 79     79   700 use_module($plugin_class) || die 'not found';
286             }
287             catch {
288             die qq{could not load plugin class '$plugin_class': $_};
289 79         317 };
290              
291 79 100       414 return $plugin_class->new(
292             name => $plugin_name,
293             tidyall => $self,
294             %$plugin_conf
295             );
296             }
297 79 50   79   4155  
298             my ( $self, $params ) = @_;
299              
300 1     1   353 # Strict constructor
301 79         740 #
302             if ( my @bad_params = grep { !$self->can($_) } keys(%$params) ) {
303 78         24067 die sprintf(
304             'unknown constructor param%s %s for %s',
305             @bad_params > 1 ? 's' : q{},
306             join( ', ', sort map {qq['$_']} @bad_params ),
307             ref($self)
308             );
309             }
310              
311 69     69 0 4981 unless ( $self->no_backups ) {
312             $self->_backup_dir->mkpath( { mode => 0775 } );
313             $self->_purge_backups_periodically();
314             }
315 69 100       294  
  205         947  
316             @INC = ( @{ $self->inc }, @INC );
317             }
318              
319 1 50       15 my ($self) = @_;
  2         30  
320             my $cache = $self->cache;
321             my $last_purge_backups = $cache->get('last_purge_backups') || 0;
322             if ( time > $last_purge_backups + $self->_backup_ttl_secs ) {
323             $self->_purge_backups();
324 68 100       327 $cache->set( 'last_purge_backups', time() );
325 64         1272 }
326 64         32518 }
327              
328             my ($self) = @_;
329 68         2613 $self->msg('purging old backups') if $self->verbose;
  68         1347  
330             find(
331             {
332             follow => 0,
333 64     64   199 wanted => sub {
334 64         1616 unlink $_ if -f && /\.bak$/ && time > ( stat($_) )[9] + $self->_backup_ttl_secs;
335 64   100     30640 },
336 64 100       7589 no_chdir => 1
337 41         3643 },
338 41         4640 $self->_backup_dir,
339             );
340             }
341              
342             my ( $class, $conf_file, %params ) = @_;
343 41     41   95  
344 41 100       180 $conf_file = path($conf_file);
345              
346             die qq{no such file '$conf_file'} unless $conf_file->is_file;
347             my $conf_params = $class->_read_conf_file($conf_file);
348             my $main_params = delete( $conf_params->{'_'} ) || {};
349 41 0 33 41   4609  
      33        
350             %params = (
351 41         1005 plugins => $conf_params,
352             root_dir => path($conf_file)->realpath->parent,
353             %{$main_params},
354             %params
355             );
356              
357             # Initialize with alternate class if given
358 3     3 1 34958 #
359             if ( my $tidyall_class = delete( $params{tidyall_class} ) ) {
360 3         15 local @INC = ( @{ $conf_params->{inc} }, @INC ) if $conf_params->{inc};
361             use_module($tidyall_class) or die qq{cannot load '$tidyall_class'};
362 3 50       58 $class = $tidyall_class;
363 3         78 }
364 3   100     19  
365             if ( $params{verbose} ) {
366             my $msg_outputter = $params{msg_outputter} || $class->_build_msg_outputter();
367             $msg_outputter->(
368             'constructing %s with these params: %s', $class,
369 3         17 _dump_params( \%params )
  3         835  
370             );
371             }
372              
373             return $class->new(%params);
374             }
375 3 50       23  
376 0 0       0 my ( $class, $conf_file ) = @_;
  0         0  
377 0 0       0 my $conf_string = $conf_file->slurp_utf8;
378 0         0 my $root_dir = $conf_file->parent;
379             $conf_string =~ s/\$ROOT/$root_dir/g;
380             my $conf_hash = Code::TidyAll::Config::INI::Reader->read_string($conf_string);
381 3 50       14 die qq{'$conf_file' did not evaluate to a hash}
382 0   0     0 unless ( ref($conf_hash) eq 'HASH' );
383 0         0 return $conf_hash;
384             }
385              
386             my $p = shift;
387              
388             return Data::Dumper->new( [ _recurse_dump($p) ] )->Indent(0)->Sortkeys(1)->Quotekeys(0)
389 3         68 ->Terse(1)->Dump;
390             }
391              
392             # This is all a ridiculous workaround around the fact that there is no good
393 3     3   13 # way to tell Data::Dumper how to serialize a Path::Tiny object.
394 3         16 my ($p) = @_;
395 3         1849  
396 3         306 return $p unless ref $p;
397 3         48  
398 3 50       210 if ( ref $p eq 'HASH' ) {
399             my %dump;
400 3         17 for my $k ( keys %{$p} ) {
401             my $v = $p->{$k};
402             if ( blessed $v ) {
403             if ( $v->isa('Path::Tiny') ) {
404 0     0   0 $dump{$k} = $v . q{};
405             }
406 0         0 else {
407             die 'Cannot dump ' . ref($v) . ' object';
408             }
409             }
410             elsif ( ref $v =~ /^(?:HASH|ARRAY)$/ ) {
411             $dump{$k} = _recurse_dump($v);
412             }
413 0     0   0 else {
414             $dump{$k} = $v;
415 0 0       0 }
416             }
417 0 0       0 return \%dump;
    0          
418 0         0 }
419 0         0 elsif ( ref $p eq 'ARRAY' ) {
  0         0  
420 0         0 my @dump;
421 0 0       0 for my $v ( @{$p} ) {
    0          
422 0 0       0 if ( blessed $v ) {
423 0         0 if ( $v->isa('Path::Tiny') ) {
424             push @dump, $v . q{};
425             }
426 0         0 else {
427             die 'Cannot dump ' . ref($v) . ' object';
428             }
429             }
430 0         0 elsif ( ref $v =~ /^(?:HASH|ARRAY)$/ ) {
431             push @dump, _recurse_dump($v);
432             }
433 0         0 else {
434             push @dump, $v;
435             }
436 0         0 }
437             return \@dump;
438             }
439 0         0  
440 0         0 die "_recurse_dump was called with a value that was not a scalar, hashref, or an arrayref: $p";
  0         0  
441 0 0       0 }
    0          
442 0 0       0  
443 0         0 my $self = shift;
444              
445             return $self->process_paths( $self->find_matched_files );
446 0         0 }
447              
448             my ( $self, @paths ) = @_;
449              
450 0         0 @paths = map {
451             try { $_->realpath }
452             || $_->absolute
453 0         0 } map { path($_) } @paths;
454              
455             my $dir = pushd( $self->root_dir );
456 0         0 if ( $self->jobs > 1 && @paths > 1 ) {
457             return $self->_process_parallel(@paths);
458             }
459 0         0 else {
460             return map { $self->process_path($_) } @paths;
461             }
462             }
463 16     16 0 12120  
464             my ( $self, @paths ) = @_;
465 16         62  
466             unless ( eval { require Parallel::ForkManager; 1; } ) {
467             die 'Running Code::TidyAll with multiple jobs requires Parallel::ForkManager';
468             }
469 63     63 1 59412  
470             my @results;
471             my %path_to_pid;
472 84     84   2175  
473 84 100       5066 my $pm = Parallel::ForkManager->new( $self->jobs );
474 63         171 $pm->set_waitpid_blocking_sleep(0.01);
  84         412  
475             $pm->run_on_finish(
476 63         10335 sub {
477 63 100 66     6260 my ( $pid, $code, $result ) = @_[ 0, 1, 5 ];
478 5         25  
479             if ($code) {
480             warn "Error running tidyall on $path_to_pid{$pid}. Got exit status of $code.";
481 58         128 }
  64         194  
482             else {
483             push @results, $result;
484             }
485             }
486 5     5   10 );
487              
488 5 50       10 for my $path (@paths) {
  5         45  
  5         15  
489 0         0 if ( my $pid = $pm->start ) {
490             $path_to_pid{$path} = $pid;
491             next;
492 5         10 }
493              
494             $pm->finish( 0, $self->process_path($path) );
495 5         180 }
496 5         22390  
497             $pm->wait_all_children;
498              
499 6     6   2765981 return @results;
500             }
501 6 50       41  
502 0         0 my ( $self, $path ) = @_;
503              
504             if ( $path->is_dir ) {
505 6         52 if ( $self->recursive ) {
506             return $self->process_paths( $path->children );
507             }
508 5         40 else {
509             return ( $self->_error_result( "$path: is a directory (try -r/--recursive)", $path ) );
510 5         50 }
511 14 100       90 }
512 10         16935 elsif ( $path->is_file ) {
513 10         468 return ( $self->process_file($path) );
514             }
515             else {
516 4         17130 return ( $self->_error_result( "$path: not a file or directory", $path ) );
517             }
518             }
519 1         57  
520             my ( $self, $full_path ) = @_;
521 1         26  
522             $full_path = path($full_path);
523             die "$full_path is not a file" unless $full_path->is_file;
524              
525 68     68 0 192 my $path = $self->_small_path($full_path);
526              
527 68 100       325 if ( $self->list_only ) {
    100          
528 2 100       49 if ( my @plugins = $self->plugins_for_path($path) ) {
529 1         12 $self->msg( '%s (%s)', $path, join( ', ', map { $_->name } @plugins ) );
530             }
531             return Code::TidyAll::Result->new( path => $path, state => 'checked' );
532 1         9 }
533              
534             my $cache_model = $self->_cache_model_for( $path, $full_path );
535             if ( $self->refresh_cache ) {
536 65         2284 $cache_model->remove;
537             }
538             elsif ( $cache_model->is_cached ) {
539 1         32 $self->msg( '[cached] %s', $path ) if $self->verbose;
540             return Code::TidyAll::Result->new( path => $path, state => 'cached' );
541             }
542              
543             my $contents = $cache_model->file_contents || $full_path->slurp_raw;
544 67     67 1 2768 my $result = $self->process_source( $contents, $path );
545              
546 67         339 if ( $result->state eq 'tidied' ) {
547 67 50       1312  
548             # backup original contents
549 67         1152 $self->_backup_file( $path, $contents );
550              
551 66 50       2732 # write new contents out to disk
552 0 0       0 $contents = $result->new_contents;
553 0         0  
  0         0  
554             # We don't use ->spew because that creates a new file and renames it,
555 0         0 # losing the existing mode setting in the process.
556             path( $full_path . $self->output_suffix )->append_raw( { truncate => 1 }, $contents );
557              
558 66         296 # change the in memory contents of the cache (but don't update yet)
559 66 50       67556 $cache_model->file_contents($contents) unless $self->output_suffix;
    100          
560 0         0 }
561              
562             $cache_model->update if $result->ok;
563 4 50       236 return $result;
564 4         78 }
565              
566             my ( $self, $path ) = @_;
567 62   33     2826 die sprintf( q{'%s' is not underneath root dir '%s'!}, $path, $self->root_dir )
568 62         12434 unless index( $path, $self->root_dir ) == 0;
569             return path( substr( $path . q{}, length( $self->root_dir ) + 1 ) );
570 62 100       30123 }
571              
572             my ( $self, $path ) = @_;
573 40         166  
574             $self->_plugins_for_path->{$path}
575             ||= [ grep { $_->matches_path($path) } @{ $self->_plugin_objects } ];
576 40         49320 return @{ $self->_plugins_for_path->{$path} };
577             }
578              
579             my ( $self, $path, $full_path ) = @_;
580 40         209 return $self->cache_model_class->new(
581             path => $path,
582             full_path => $full_path,
583 40 50       13210 ( $self->no_cache ? () : ( cache_engine => $self->cache ) ),
584             base_sig => $self->_base_sig,
585             );
586 62 100       234 }
587 62         724  
588             my ( $self, $path, $contents ) = @_;
589             unless ( $self->no_backups ) {
590             my $backup_file = $self->_backup_dir->child( $self->_backup_filename($path) );
591 67     67   208 $backup_file->parent->mkpath( { mode => 0775 } );
592 67 100       336 $backup_file->spew_raw($contents);
593             }
594 66         771 }
595              
596             my ( $self, $path ) = @_;
597              
598 84     84 1 5111 return join( q{}, $path, '-', time2str( '%Y%m%d-%H%M%S', time ), '.bak' );
599             }
600              
601 84   100     1814 my ( $self, $contents, $path ) = @_;
  49         1548  
  44         2968  
602 84         1828  
  84         1229  
603             $path = path($path);
604              
605             die 'contents and path required' unless defined($contents) && defined($path);
606 66     66   154 my @plugins = $self->plugins_for_path($path);
607 66 100       2594  
608             if ( !@plugins ) {
609             $self->msg(
610             '[no plugins apply%s] %s',
611             $self->mode ? q{ for mode '} . $self->mode . q{'} : q{}, $path
612             ) if $self->verbose;
613             return Code::TidyAll::Result->new( path => $path, state => 'no_match' );
614             }
615              
616 40     40   154 if ( $self->verbose ) {
617 40 100       177 my @names = join ', ', map { $_->name } @plugins;
618 32         745 $self->msg("[applying the following plugins: @names]");
619 32         6422 }
620 32         5165  
621             my $new_contents = my $orig_contents = $contents;
622             my $plugin;
623             my $error;
624             my @diffs;
625 32     32   385 try {
626             foreach my $method (qw(preprocess_source process_source_or_file postprocess_source)) {
627 32         310 foreach $plugin (@plugins) {
628             my $diff;
629             ( $new_contents, $diff )
630             = $plugin->$method( $new_contents, $path, $self->check_only );
631 83     83 1 28492 if ($diff) {
632             push @diffs, [ $plugin->name, $diff ];
633 83         281 }
634             }
635 83 50 33     1950 }
636 83         337 }
637             catch {
638 83 100       918 chomp;
639 1 0       17 $error = $_;
    50          
640             $error = sprintf( q{*** '%s': %s}, $plugin->name, $_ ) if $plugin;
641             };
642              
643 1         33 my $was_tidied = !$error && ( $new_contents ne $orig_contents );
644             if ( $was_tidied && $self->check_only ) {
645             $error = '*** needs tidying';
646 82 100       336 foreach my $diff (@diffs) {
647 4         11 $error .= "\n\n";
  13         34  
648 4         19 $error .= "$diff->[0] made the following change:\n$diff->[1]";
649             }
650             $error .= "\n\n" if @diffs;
651 82         185 undef $was_tidied;
652 82         227 }
653              
654 82         0 if ( !$self->quiet || $error ) {
655             my $status = $was_tidied ? '[tidied] ' : '[checked] ';
656 82     82   3802 my $plugin_names
657 233         402 = $self->verbose ? sprintf( ' (%s)', join( ', ', map { $_->name } @plugins ) ) : q{};
658 279         333 $self->msg( '%s%s%s', $status, $path, $plugin_names );
659 279         1677 }
660              
661 266 100       766 if ($error) {
662 1         9 return $self->_error_result( $error, $path, $orig_contents, $new_contents );
663             }
664             elsif ($was_tidied) {
665             return Code::TidyAll::Result->new(
666             path => $path,
667             state => 'tidied',
668 13     13   950 orig_contents => $orig_contents,
669 13         39 new_contents => $new_contents
670 13 50       125 );
671 82         961 }
672             else {
673 82   100     2151 return Code::TidyAll::Result->new( path => $path, state => 'checked' );
674 82 100 100     449 }
675 4         12 }
676 4         12  
677 1         2 my ( $self, $msg, $path, $orig_contents, $new_contents ) = @_;
678 1         4 $self->msg( '%s', $msg );
679             return Code::TidyAll::Result->new(
680 4 100       13 path => $path,
681 4         12 state => 'error',
682             error => $msg,
683             (
684 82 100 100     554 ( defined $orig_contents && length $orig_contents )
685 66 100       229 ? ( orig_contents => $orig_contents )
686             : ()
687 66 100       298 ),
  13         95  
688 66         271 (
689             ( defined $new_contents && length $new_contents )
690             ? ( new_contents => $new_contents )
691 82 100       2899 : ()
    100          
692 17         117 ),
693             );
694             }
695 46         1552  
696             my ( $class, $conf_names, $start_dir ) = @_;
697              
698             $start_dir = path($start_dir);
699             my $path1 = $start_dir->absolute;
700             my $path2 = $start_dir->realpath;
701             my $conf_file = $class->_find_conf_file_upward( $conf_names, $path1 )
702             || $class->_find_conf_file_upward( $conf_names, $path2 );
703 19         615 unless ( defined $conf_file ) {
704             die sprintf(
705             'could not find %s upwards from %s',
706             join( ' or ', @$conf_names ),
707             ( $path1 eq $path2 ) ? qq{'$path1'} : qq{'$path1' or '$path2'}
708 19     19   102 );
709 19         77 }
710 19 100 66     1141 return $conf_file;
    100 66        
711             }
712              
713             my ( $class, $conf_names, $search_dir ) = @_;
714              
715             my $cnt = 0;
716             while (1) {
717             foreach my $conf_name (@$conf_names) {
718             my $try_path = $search_dir->child($conf_name);
719             return $try_path if $try_path->is_file;
720             }
721              
722             my $parent = $search_dir->parent;
723             last if $parent eq $search_dir;
724             $search_dir = $parent;
725              
726             die 'inf loop!' if ++$cnt > 100;
727             }
728 0     0 1 0 }
729              
730 0         0 my ($self) = @_;
731 0         0  
732 0         0 my $plugins_for_path = $self->_plugins_for_path;
733 0   0     0 my $root_length = length( $self->root_dir );
734              
735 0 0       0 my @all;
736 0 0       0 for my $plugin ( @{ $self->_plugin_objects } ) {
737             my @matched = $self->_matched_by_plugin($plugin);
738             push @all, @matched;
739              
740             # When we end up in process_source we'll need to know which plugins
741             # match a given file. This could be (re-)calculated When we call
742 0         0 # ->plugins_for_path($file) there but since we already know the path
743             # to plugin mapping, we might as well store it here.
744             for my $file (@matched) {
745             my $path = substr( $file, $root_length + 1 );
746 0     0   0 $plugins_for_path->{$path} ||= [];
747             push @{ $plugins_for_path->{$path} }, $plugin;
748 0         0 }
749 0         0 }
750 0         0  
751 0         0 return map { path($_) } uniq(@all);
752 0 0       0 }
753              
754             my $self = shift;
755 0         0 my $plugin = shift;
756 0 0       0  
757 0         0 my %is_ignored = map { $_ => 1 }
758             $self->_zglob( [ @{ $self->ignores || [] }, @{ $plugin->ignores || [] } ] );
759 0 0       0 my @matched
760             = grep { !$is_ignored{$_} } grep { -f && -s && !-l } $self->_zglob( $plugin->selects );
761              
762             my $shebang = $plugin->shebang
763             or return @matched;
764 18     18 1 49  
765             my $re = join '|', map {quotemeta} @{$shebang};
766 18         426 $re = qr/^#!.*\b(?:$re)\b/;
767 18         953 return grep {
768             my $fh;
769 18         303 open $fh, '<', $_ or die $!;
770 18         41 scalar <$fh> =~ /$re/;
  18         367  
771 29         1152 } @matched;
772 29         73 }
773              
774             my ( $self, $globs ) = @_;
775              
776             local $Code::TidyAll::Zglob::NOCASE = 0;
777             my @files;
778 29         66 foreach my $glob (@$globs) {
779 53         112 try {
780 53   100     205 push @files, zglob( join( "/", $self->root_dir, $glob ) );
781 53         69 }
  53         125  
782             catch {
783             die qq{error parsing '$glob': $_};
784             }
785 18         200 }
  40         707  
786             return uniq(@files);
787             }
788              
789 29     29   47 my ( $self, $format, @params ) = @_;
790 29         46 $self->msg_outputter()->( $format, @params );
791             }
792 7         26  
793 29 50       60 return sub {
  29 50       596  
  29         1158  
794             my $format = shift;
795 29 100 100     583 printf "$format\n", @_;
  63         178  
  66         1759  
796             };
797 29 100       283 }
798              
799             1;
800 1         4  
  2         13  
  1         9  
801 1         35 # ABSTRACT: Engine for tidyall, your all-in-one code tidier and validator
802              
803 1         8  
  6         14  
804 6 50       158 =pod
805 6         189  
806             =encoding UTF-8
807              
808             =head1 NAME
809              
810 58     58   2119 Code::TidyAll - Engine for tidyall, your all-in-one code tidier and validator
811              
812 58         101 =head1 VERSION
813 58         72  
814 58         118 version 0.81
815              
816 42     42   1785 =head1 SYNOPSIS
817              
818             use Code::TidyAll;
819 0     0   0  
820             my $ct = Code::TidyAll->new_from_conf_file(
821 42         405 '/path/to/conf/file',
822 58         1045 ...
823             );
824              
825             # or
826 93     93 0 242  
827 93         381 my $ct = Code::TidyAll->new(
828             root_dir => '/path/to/root',
829             plugins => {
830             perltidy => {
831             select => 'lib/**/*.(pl|pm)',
832 93     93   199 argv => '-noll -it=2',
833 93         1105 },
834 69     69   10749 ...
835             }
836             );
837              
838             # then...
839              
840             $ct->process_paths($file1, $file2);
841              
842             =head1 DESCRIPTION
843              
844             This is the engine used by L<tidyall> - read that first to get an overview.
845              
846             You can call this API from your own program instead of executing C<tidyall>.
847              
848             =head1 METHODS
849              
850             This class offers the following methods:
851              
852             =head2 Code::TidyAll->new(%params)
853              
854             The regular constructor. Must pass at least I<plugins> and I<root_dir>.
855              
856             =head2 $tidyall->new_from_conf_file( $conf_file, %params )
857              
858             Takes a conf file path, followed optionally by a set of key/value parameters.
859             Reads parameters out of the conf file and combines them with the passed
860             parameters (the latter take precedence), and calls the regular constructor.
861              
862             If the conf file or params defines I<tidyall_class>, then that class is
863             constructed instead of C<Code::TidyAll>.
864              
865             =head3 Constructor parameters
866              
867             =over 4
868              
869             =item * plugins
870              
871             Specify a hash of plugins, each of which is itself a hash of options. This is
872             equivalent to what would be parsed out of the sections in the configuration
873             file.
874              
875             =item * selected_plugins
876              
877             An arrayref of plugins to be used. This overrides the C<mode> parameter.
878              
879             This is really only useful if you're getting configuration from a config file
880             and want to narrow the set of plugins to be run.
881              
882             Note that plugins will still only run on files which match their C<select> and
883             C<ignore> configuration.
884              
885             =item * cache_model_class
886              
887             The cache model class. Defaults to C<Code::TidyAll::CacheModel>
888              
889             =item * cache
890              
891             The cache instance (e.g. an instance of C<Code::TidyAll::Cache> or a C<CHI>
892             instance.) An instance of C<Code::TidyAll::Cache> is automatically instantiated
893             by default.
894              
895             =item * backup_ttl
896              
897             =item * check_only
898              
899             If this is true, then we simply check that files pass validation steps and that
900             tidying them does not change the file. Any changes from tidying are not
901             actually written back to the file.
902              
903             =item * no_cleanup
904              
905             A boolean indicating if we should skip cleaning temporary files or not.
906             Defaults to false.
907              
908             =item * inc
909              
910             An arrayref of directories to prepend to C<@INC>. This can be set via the
911             command-line as C<-I>, but you can also set it in a config file.
912              
913             This affects both loading and running plugins.
914              
915             =item * data_dir
916              
917             =item * iterations
918              
919             =item * mode
920              
921             =item * no_backups
922              
923             =item * no_cache
924              
925             =item * output_suffix
926              
927             =item * quiet
928              
929             =item * root_dir
930              
931             =item * ignore
932              
933             =item * verbose
934              
935             These options are the same as the equivalent C<tidyall> command-line options,
936             replacing dashes with underscore (e.g. the C<backup-ttl> option becomes
937             C<backup_ttl> here).
938              
939             =item * msg_outputter
940              
941             This is a subroutine reference that is called whenever a message needs to be
942             printed in some way. The sub receives a C<sprintf()> format string followed by
943             one or more parameters. The default sub used simply calls C<printf "$format\n",
944             @_> but L<Test::Code::TidyAll> overrides this to use the C<<
945             Test::Builder->diag >> method.
946              
947             =back
948              
949             =head2 $tidyall->process_paths( $path, ... )
950              
951             This method iterates through a list of paths, processing all the files it
952             finds. It will descend into subdirectories if C<recursive> flag is true.
953             Returns a list of L<Code::TidyAll::Result> objects, one for each file.
954              
955             =head2 $tidyall->process_file( $file )
956              
957             Process the one I<file>, meaning:
958              
959             =over 4
960              
961             =item *
962              
963             Check the cache and return immediately if file has not changed.
964              
965             =item *
966              
967             Apply appropriate matching plugins.
968              
969             =item *
970              
971             Print success or failure result to STDOUT, depending on quiet/verbose settings.
972              
973             =item *
974              
975             Write to the cache if caching is enabled.
976              
977             =item *
978              
979             Return a L<Code::TidyAll::Result> object.
980              
981             =back
982              
983             =head2 $tidyall->process_source( $source, $path )
984              
985             Like C<process_file>, but process the I<source> string instead of a file, and
986             does not read from or write to the cache. You must still pass the relative
987             I<path> from the root as the second argument, so that we know which plugins to
988             apply. Returns a L<Code::TidyAll::Result> object.
989              
990             =head2 $tidyall->plugins_for_path($path)
991              
992             Given a relative I<path> from the root, returns a list of
993             L<Code::TidyAll::Plugin> objects that apply to it, or an empty list if no
994             plugins apply.
995              
996             =head2 $tidyall->find_matched_files
997              
998             Returns a list of sorted files that match at least one plugin in configuration.
999              
1000             =head2 Code::TidyAll->find_conf_file( $conf_names, $start_dir )
1001              
1002             Start in the I<start_dir> and work upwards, looking for a file matching one of
1003             the I<conf_names>. Returns the pathname if found or throw an error if not
1004             found.
1005              
1006             =head1 SUPPORT
1007              
1008             Bugs may be submitted at L<https://github.com/houseabsolute/perl-code-tidyall/issues>.
1009              
1010             =head1 SOURCE
1011              
1012             The source code repository for Code-TidyAll can be found at L<https://github.com/houseabsolute/perl-code-tidyall>.
1013              
1014             =head1 AUTHORS
1015              
1016             =over 4
1017              
1018             =item *
1019              
1020             Jonathan Swartz <swartz@pobox.com>
1021              
1022             =item *
1023              
1024             Dave Rolsky <autarch@urth.org>
1025              
1026             =back
1027              
1028             =head1 CONTRIBUTORS
1029              
1030             =for stopwords Adam Herzog Andy Jack Bernhard Schmalhofer Finn Smith George Hartzell Graham Knop Gregory Oschwald Joe Crotty Kenneth Ölwing Mark Fowler Grimes Martin Gruner Mohammad S Anwar Nick Tonkin Olaf Alders Pedro Melo Ricardo Signes Sergey Romanov Shlomi Fish timgimyee
1031              
1032             =over 4
1033              
1034             =item *
1035              
1036             Adam Herzog <adam@adamherzog.com>
1037              
1038             =item *
1039              
1040             Andy Jack <andyjack@cpan.org>
1041              
1042             =item *
1043              
1044             Bernhard Schmalhofer <Bernhard.Schmalhofer@gmx.de>
1045              
1046             =item *
1047              
1048             Finn Smith <finn@timeghost.net>
1049              
1050             =item *
1051              
1052             George Hartzell <georgewh@gene.com>
1053              
1054             =item *
1055              
1056             Graham Knop <haarg@haarg.org>
1057              
1058             =item *
1059              
1060             Gregory Oschwald <goschwald@maxmind.com>
1061              
1062             =item *
1063              
1064             Joe Crotty <joe.crotty@returnpath.net>
1065              
1066             =item *
1067              
1068             Kenneth Ölwing <kenneth.olwing@skatteverket.se>
1069              
1070             =item *
1071              
1072             Mark Fowler <mark@twoshortplanks.com>
1073              
1074             =item *
1075              
1076             Mark Grimes <mgrimes@cpan.org>
1077              
1078             =item *
1079              
1080             Martin Gruner <martin.gruner@otrs.com>
1081              
1082             =item *
1083              
1084             Mohammad S Anwar <mohammad.anwar@yahoo.com>
1085              
1086             =item *
1087              
1088             Nick Tonkin <ntonkin@bur-ntonkin-m1.corp.endurance.com>
1089              
1090             =item *
1091              
1092             Olaf Alders <olaf@wundersolutions.com>
1093              
1094             =item *
1095              
1096             Pedro Melo <melo@simplicidade.org>
1097              
1098             =item *
1099              
1100             Ricardo Signes <rjbs@cpan.org>
1101              
1102             =item *
1103              
1104             Sergey Romanov <sromanov-dev@yandex.ru>
1105              
1106             =item *
1107              
1108             Shlomi Fish <shlomif@shlomifish.org>
1109              
1110             =item *
1111              
1112             timgimyee <tim.gim.yee@gmail.com>
1113              
1114             =back
1115              
1116             =head1 COPYRIGHT AND LICENSE
1117              
1118             This software is copyright (c) 2011 - 2022 by Jonathan Swartz.
1119              
1120             This is free software; you can redistribute it and/or modify it under
1121             the same terms as the Perl 5 programming language system itself.
1122              
1123             The full text of the license can be found in the
1124             F<LICENSE> file included with this distribution.
1125              
1126             =cut