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   1173261 use warnings;
  27         112  
  27         707  
4 27     27   119  
  27         43  
  27         642  
5             use Code::TidyAll::Cache;
6 27     27   9379 use Code::TidyAll::CacheModel;
  27         103  
  27         918  
7 27     27   12099 use Code::TidyAll::Config::INI::Reader;
  27         97  
  27         906  
8 27     27   11850 use Code::TidyAll::Plugin;
  27         75  
  27         928  
9 27     27   9924 use Code::TidyAll::Result;
  27         94  
  27         886  
10 27     27   10507 use Code::TidyAll::Zglob qw(zglob);
  27         100  
  27         1113  
11 27     27   11406 use Data::Dumper;
  27         73  
  27         1579  
12 27     27   1701 use Date::Format;
  27         17365  
  27         1079  
13 27     27   11238 use Digest::SHA qw(sha1_hex);
  27         168114  
  27         1799  
14 27     27   223 use File::Find qw(find);
  27         56  
  27         1315  
15 27     27   167 use File::pushd qw( pushd );
  27         60  
  27         1747  
16 27     27   10724 use List::SomeUtils qw(uniq);
  27         26700  
  27         1425  
17 27     27   11633 use Module::Runtime qw( use_module );
  27         186214  
  27         2106  
18 27     27   240 use Path::Tiny qw(path);
  27         68  
  27         186  
19 27     27   1607 use Scalar::Util qw(blessed);
  27         55  
  27         1076  
20 27     27   146 use Specio 0.40;
  27         52  
  27         1181  
21 27     27   170 use Specio::Declare;
  27         460  
  27         679  
22 27     27   132 use Specio::Library::Builtins;
  27         51  
  27         261  
23 27     27   4784 use Specio::Library::Numeric;
  27         53  
  27         408  
24 27     27   219670 use Specio::Library::Path::Tiny 0.04;
  27         59  
  27         204  
25 27     27   186966 use Specio::Library::String;
  27         765  
  27         171  
26 27     27   312948 use Time::Duration::Parse qw(parse_duration);
  27         56  
  27         210  
27 27     27   62621 use Try::Tiny;
  27         45678  
  27         173  
28 27     27   1826  
  27         62  
  27         1395  
29             use Moo 2.000000;
30 27     27   146  
  27         634  
  27         194  
31             our $VERSION = '0.82';
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   777  
208 64         1005 my $self = shift;
209             return parse_duration( $self->backup_ttl );
210             }
211              
212 64     64   666 my $self = shift;
213 64         417 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   1851 my ( $self, $data ) = @_;
218 34         103 return sha1_hex( join( ',', @$data ) );
  49         1102  
  34         638  
219 34   50     261 }
220              
221             my $self = shift;
222             return Code::TidyAll::Cache->new( cache_dir => $self->data_dir->child('cache') );
223 34     34   112 }
224 34         824  
225             my $self = shift;
226             return $self->root_dir->child('/.tidyall.d');
227             }
228 66     66   664  
229 66         905 my $self = shift;
230              
231             my $all_plugins = $self->plugins;
232             my %selected = map { $_ => 1 } @{ $self->selected_plugins };
233 68     68   4329 my %plugins;
234 68         298  
235             if (%selected) {
236             my @unknown = sort grep { !$all_plugins->{$_} } keys %selected;
237             die "Asked for unknown plugins: [@unknown]" if @unknown;
238 65     65   606 %plugins = map { $_ => $all_plugins->{$_} } keys %selected;
239             }
240 65         170 elsif ( my $mode = $self->mode ) {
241 65         113 %plugins = map { $_ => $all_plugins->{$_} }
  6         25  
  65         962  
242 65         2480 grep { $self->_plugin_conf_matches_mode( $all_plugins->{$_}, $mode ) }
243             keys %{$all_plugins};
244 65 100       333 }
    50          
245 2         10  
  6         18  
246 2 100       17 return \%plugins;
247 1         4 }
  2         5  
248              
249             my ( $self, $conf, $mode ) = @_;
250 78         242  
251 79         201 if ( my $only_modes = $conf->{only_modes} ) {
252 63         94 return 0 if ( q{ } . $only_modes . q{ } ) !~ / $mode /;
  63         274  
253             }
254             if ( my $except_modes = $conf->{except_modes} ) {
255 64         1462 return 0 if ( q{ } . $except_modes . q{ } ) =~ / $mode /;
256             }
257             return 1;
258             }
259 79     79   159  
260             my $self = shift;
261 79 100       197  
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       182 # alphabetical
265 0 0       0 # TODO: These should probably sort in a consistent way independent of locale
266             return [
267 78         214 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   876 }
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 28 50       767  
279 80         3595 my $plugin_class = (
280 65         102 $plugin_fname =~ /^\+/
  65         1011  
281             ? substr( $plugin_fname, 1 )
282             : "Code::TidyAll::Plugin::$plugin_fname"
283             );
284             try {
285 80     80   647 use_module($plugin_class) || die 'not found';
286             }
287             catch {
288             die qq{could not load plugin class '$plugin_class': $_};
289 80         270 };
290              
291 80 100       365 return $plugin_class->new(
292             name => $plugin_name,
293             tidyall => $self,
294             %$plugin_conf
295             );
296             }
297 80 50   80   3821  
298             my ( $self, $params ) = @_;
299              
300 1     1   306 # Strict constructor
301 80         620 #
302             if ( my @bad_params = grep { !$self->can($_) } keys(%$params) ) {
303 79         22936 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 4294 unless ( $self->no_backups ) {
312             $self->_backup_dir->mkpath( { mode => 0775 } );
313             $self->_purge_backups_periodically();
314             }
315 69 100       236  
  205         848  
316             @INC = ( @{ $self->inc }, @INC );
317             }
318              
319 1 50       12 my ($self) = @_;
  2         20  
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       280 $cache->set( 'last_purge_backups', time() );
325 64         1542 }
326 64         30382 }
327              
328             my ($self) = @_;
329 68         2234 $self->msg('purging old backups') if $self->verbose;
  68         1101  
330             find(
331             {
332             follow => 0,
333 64     64   170 wanted => sub {
334 64         1454 unlink $_ if -f && /\.bak$/ && time > ( stat($_) )[9] + $self->_backup_ttl_secs;
335 64   100     27474 },
336 64 100       5946 no_chdir => 1
337 41         3436 },
338 41         4125 $self->_backup_dir,
339             );
340             }
341              
342             my ( $class, $conf_file, %params ) = @_;
343 41     41   84  
344 41 100       147 $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   4217  
      33        
350             %params = (
351 41         920 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 25623 #
359             if ( my $tidyall_class = delete( $params{tidyall_class} ) ) {
360 3         10 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       47 $class = $tidyall_class;
363 3         59 }
364 3   100     17  
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         12 _dump_params( \%params )
  3         600  
370             );
371             }
372              
373             return $class->new(%params);
374             }
375 3 50       16  
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       9 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         49 ->Terse(1)->Dump;
390             }
391              
392             # This is all a ridiculous workaround around the fact that there is no good
393 3     3   7 # way to tell Data::Dumper how to serialize a Path::Tiny object.
394 3         11 my ($p) = @_;
395 3         1126  
396 3         180 return $p unless ref $p;
397 3         31  
398 3 50       152 if ( ref $p eq 'HASH' ) {
399             my %dump;
400 3         10 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 11903  
464             my ( $self, @paths ) = @_;
465 16         57  
466             unless ( eval { require Parallel::ForkManager; 1; } ) {
467             die 'Running Code::TidyAll with multiple jobs requires Parallel::ForkManager';
468             }
469 63     63 1 56706  
470             my @results;
471             my %path_to_pid;
472 84     84   1997  
473 84 100       5154 my $pm = Parallel::ForkManager->new( $self->jobs );
474 63         144 $pm->set_waitpid_blocking_sleep(0.01);
  84         394  
475             $pm->run_on_finish(
476 63         9869 sub {
477 63 100 66     5947 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         123 }
  64         164  
482             else {
483             push @results, $result;
484             }
485             }
486 5     5   20 );
487              
488 5 50       10 for my $path (@paths) {
  5         50  
  5         15  
489 0         0 if ( my $pid = $pm->start ) {
490             $path_to_pid{$path} = $pid;
491             next;
492 5         20 }
493              
494             $pm->finish( 0, $self->process_path($path) );
495 5         200 }
496 5         19820  
497             $pm->wait_all_children;
498              
499 7     7   2435557 return @results;
500             }
501 7 50       49  
502 0         0 my ( $self, $path ) = @_;
503              
504             if ( $path->is_dir ) {
505 7         53 if ( $self->recursive ) {
506             return $self->process_paths( $path->children );
507             }
508 5         45 else {
509             return ( $self->_error_result( "$path: is a directory (try -r/--recursive)", $path ) );
510 5         60 }
511 14 100       76 }
512 10         15595 elsif ( $path->is_file ) {
513 10         390 return ( $self->process_file($path) );
514             }
515             else {
516 4         13572 return ( $self->_error_result( "$path: not a file or directory", $path ) );
517             }
518             }
519 1         58  
520             my ( $self, $full_path ) = @_;
521 1         49  
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       333 if ( $self->list_only ) {
    100          
528 2 100       54 if ( my @plugins = $self->plugins_for_path($path) ) {
529 1         18 $self->msg( '%s (%s)', $path, join( ', ', map { $_->name } @plugins ) );
530             }
531             return Code::TidyAll::Result->new( path => $path, state => 'checked' );
532 1         11 }
533              
534             my $cache_model = $self->_cache_model_for( $path, $full_path );
535             if ( $self->refresh_cache ) {
536 65         1892 $cache_model->remove;
537             }
538             elsif ( $cache_model->is_cached ) {
539 1         26 $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 2334 my $result = $self->process_source( $contents, $path );
545              
546 67         286 if ( $result->state eq 'tidied' ) {
547 67 50       1106  
548             # backup original contents
549 67         1014 $self->_backup_file( $path, $contents );
550              
551 66 50       2411 # 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         190 # change the in memory contents of the cache (but don't update yet)
559 66 50       61912 $cache_model->file_contents($contents) unless $self->output_suffix;
    100          
560 0         0 }
561              
562             $cache_model->update if $result->ok;
563 4 50       241 return $result;
564 4         68 }
565              
566             my ( $self, $path ) = @_;
567 62   33     2774 die sprintf( q{'%s' is not underneath root dir '%s'!}, $path, $self->root_dir )
568 62         11789 unless index( $path, $self->root_dir ) == 0;
569             return path( substr( $path . q{}, length( $self->root_dir ) + 1 ) );
570 62 100       28909 }
571              
572             my ( $self, $path ) = @_;
573 40         182  
574             $self->_plugins_for_path->{$path}
575             ||= [ grep { $_->matches_path($path) } @{ $self->_plugin_objects } ];
576 40         12657 return @{ $self->_plugins_for_path->{$path} };
577             }
578              
579             my ( $self, $path, $full_path ) = @_;
580 40         185 return $self->cache_model_class->new(
581             path => $path,
582             full_path => $full_path,
583 40 50       12671 ( $self->no_cache ? () : ( cache_engine => $self->cache ) ),
584             base_sig => $self->_base_sig,
585             );
586 62 100       235 }
587 62         665  
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   143 $backup_file->parent->mkpath( { mode => 0775 } );
592 67 100       302 $backup_file->spew_raw($contents);
593             }
594 66         570 }
595              
596             my ( $self, $path ) = @_;
597              
598 84     84 1 5166 return join( q{}, $path, '-', time2str( '%Y%m%d-%H%M%S', time ), '.bak' );
599             }
600              
601 84   100     1681 my ( $self, $contents, $path ) = @_;
  49         1358  
  44         2853  
602 84         1761  
  84         1176  
603             $path = path($path);
604              
605             die 'contents and path required' unless defined($contents) && defined($path);
606 66     66   161 my @plugins = $self->plugins_for_path($path);
607 66 100       2467  
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   114 if ( $self->verbose ) {
617 40 100       149 my @names = join ', ', map { $_->name } @plugins;
618 32         778 $self->msg("[applying the following plugins: @names]");
619 32         5972 }
620 32         4775  
621             my $new_contents = my $orig_contents = $contents;
622             my $plugin;
623             my $error;
624             my @diffs;
625 32     32   289 try {
626             foreach my $method (qw(preprocess_source process_source_or_file postprocess_source)) {
627 32         292 foreach $plugin (@plugins) {
628             my $diff;
629             ( $new_contents, $diff )
630             = $plugin->$method( $new_contents, $path, $self->check_only );
631 83     83 1 23678 if ($diff) {
632             push @diffs, [ $plugin->name, $diff ];
633 83         268 }
634             }
635 83 50 33     1716 }
636 83         268 }
637             catch {
638 83 100       928 chomp;
639 1 0       32 $error = $_;
    50          
640             $error = sprintf( q{*** '%s': %s}, $plugin->name, $_ ) if $plugin;
641             };
642              
643 1         30 my $was_tidied = !$error && ( $new_contents ne $orig_contents );
644             if ( $was_tidied && $self->check_only ) {
645             $error = '*** needs tidying';
646 82 100       274 foreach my $diff (@diffs) {
647 4         14 $error .= "\n\n";
  13         40  
648 4         17 $error .= "$diff->[0] made the following change:\n$diff->[1]";
649             }
650             $error .= "\n\n" if @diffs;
651 82         165 undef $was_tidied;
652 82         193 }
653              
654 82         0 if ( !$self->quiet || $error ) {
655             my $status = $was_tidied ? '[tidied] ' : '[checked] ';
656 82     82   3491 my $plugin_names
657 233         369 = $self->verbose ? sprintf( ' (%s)', join( ', ', map { $_->name } @plugins ) ) : q{};
658 279         339 $self->msg( '%s%s%s', $status, $path, $plugin_names );
659 279         1542 }
660              
661 266 100       744 if ($error) {
662 1         10 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   749 orig_contents => $orig_contents,
669 13         37 new_contents => $new_contents
670 13 50       105 );
671 82         824 }
672             else {
673 82   100     2183 return Code::TidyAll::Result->new( path => $path, state => 'checked' );
674 82 100 100     459 }
675 4         8 }
676 4         12  
677 1         3 my ( $self, $msg, $path, $orig_contents, $new_contents ) = @_;
678 1         4 $self->msg( '%s', $msg );
679             return Code::TidyAll::Result->new(
680 4 100       24 path => $path,
681 4         11 state => 'error',
682             error => $msg,
683             (
684 82 100 100     466 ( defined $orig_contents && length $orig_contents )
685 66 100       253 ? ( orig_contents => $orig_contents )
686             : ()
687 66 100       270 ),
  13         42  
688 66         219 (
689             ( defined $new_contents && length $new_contents )
690             ? ( new_contents => $new_contents )
691 82 100       2704 : ()
    100          
692 17         106 ),
693             );
694             }
695 46         1661  
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         583 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   100 );
709 19         77 }
710 19 100 66     1055 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 47  
765             my $re = join '|', map {quotemeta} @{$shebang};
766 18         396 $re = qr/^#!.*\b(?:$re)\b/;
767 18         773 return grep {
768             my $fh;
769 18         308 open $fh, '<', $_ or die $!;
770 18         42 scalar <$fh> =~ /$re/;
  18         345  
771 29         868 } @matched;
772 29         79 }
773              
774             my ( $self, $globs ) = @_;
775              
776             local $Code::TidyAll::Zglob::NOCASE = 0;
777             my @files;
778 29         53 foreach my $glob (@$globs) {
779 53         106 try {
780 53   100     187 push @files, zglob( join( "/", $self->root_dir, $glob ) );
781 53         86 }
  53         133  
782             catch {
783             die qq{error parsing '$glob': $_};
784             }
785 18         165 }
  40         770  
786             return uniq(@files);
787             }
788              
789 29     29   51 my ( $self, $format, @params ) = @_;
790 29         43 $self->msg_outputter()->( $format, @params );
791             }
792 7         21  
793 29 50       55 return sub {
  29 50       544  
  29         1062  
794             my $format = shift;
795 29 100 100     565 printf "$format\n", @_;
  63         185  
  66         1645  
796             };
797 29 100       310 }
798              
799             1;
800 1         2  
  2         12  
  1         3  
801 1         32 # ABSTRACT: Engine for tidyall, your all-in-one code tidier and validator
802              
803 1         4  
  6         13  
804 6 50       151 =pod
805 6         188  
806             =encoding UTF-8
807              
808             =head1 NAME
809              
810 58     58   1909 Code::TidyAll - Engine for tidyall, your all-in-one code tidier and validator
811              
812 58         104 =head1 VERSION
813 58         72  
814 58         108 version 0.82
815              
816 42     42   1737 =head1 SYNOPSIS
817              
818             use Code::TidyAll;
819 0     0   0  
820             my $ct = Code::TidyAll->new_from_conf_file(
821 42         331 '/path/to/conf/file',
822 58         951 ...
823             );
824              
825             # or
826 93     93 0 283  
827 93         398 my $ct = Code::TidyAll->new(
828             root_dir => '/path/to/root',
829             plugins => {
830             perltidy => {
831             select => 'lib/**/*.(pl|pm)',
832 93     93   153 argv => '-noll -it=2',
833 93         986 },
834 69     69   9192 ...
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 Andreas Vögele 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             Andreas Vögele <andreas@andreasvoegele.com>
1041              
1042             =item *
1043              
1044             Andy Jack <andyjack@cpan.org>
1045              
1046             =item *
1047              
1048             Bernhard Schmalhofer <Bernhard.Schmalhofer@gmx.de>
1049              
1050             =item *
1051              
1052             Finn Smith <finn@timeghost.net>
1053              
1054             =item *
1055              
1056             George Hartzell <georgewh@gene.com>
1057              
1058             =item *
1059              
1060             Graham Knop <haarg@haarg.org>
1061              
1062             =item *
1063              
1064             Gregory Oschwald <goschwald@maxmind.com>
1065              
1066             =item *
1067              
1068             Joe Crotty <joe.crotty@returnpath.net>
1069              
1070             =item *
1071              
1072             Kenneth Ölwing <kenneth.olwing@skatteverket.se>
1073              
1074             =item *
1075              
1076             Mark Fowler <mark@twoshortplanks.com>
1077              
1078             =item *
1079              
1080             Mark Grimes <mgrimes@cpan.org>
1081              
1082             =item *
1083              
1084             Martin Gruner <martin.gruner@otrs.com>
1085              
1086             =item *
1087              
1088             Mohammad S Anwar <mohammad.anwar@yahoo.com>
1089              
1090             =item *
1091              
1092             Nick Tonkin <ntonkin@bur-ntonkin-m1.corp.endurance.com>
1093              
1094             =item *
1095              
1096             Olaf Alders <olaf@wundersolutions.com>
1097              
1098             =item *
1099              
1100             Pedro Melo <melo@simplicidade.org>
1101              
1102             =item *
1103              
1104             Ricardo Signes <rjbs@cpan.org>
1105              
1106             =item *
1107              
1108             Sergey Romanov <sromanov-dev@yandex.ru>
1109              
1110             =item *
1111              
1112             Shlomi Fish <shlomif@shlomifish.org>
1113              
1114             =item *
1115              
1116             timgimyee <tim.gim.yee@gmail.com>
1117              
1118             =back
1119              
1120             =head1 COPYRIGHT AND LICENSE
1121              
1122             This software is copyright (c) 2011 - 2022 by Jonathan Swartz.
1123              
1124             This is free software; you can redistribute it and/or modify it under
1125             the same terms as the Perl 5 programming language system itself.
1126              
1127             The full text of the license can be found in the
1128             F<LICENSE> file included with this distribution.
1129              
1130             =cut