File Coverage

blib/lib/Code/TidyAll/Plugin.pm
Criterion Covered Total %
statement 91 94 96.8
branch 20 22 90.9
condition 6 12 50.0
subroutine 24 26 92.3
pod 2 6 33.3
total 143 160 89.3


line stmt bran cond sub pod time code
1              
2             use strict;
3 27     27   190 use warnings;
  27         62  
  27         792  
4 27     27   127  
  27         54  
  27         792  
5             use Code::TidyAll::Util::Zglob qw(zglobs_to_regex);
6 27     27   10257 use File::Which qw(which);
  27         66  
  27         1534  
7 27     27   11648 use IPC::Run3 qw(run3);
  27         24267  
  27         1450  
8 27     27   11846 use Scalar::Util qw(weaken);
  27         265469  
  27         1911  
9 27     27   238 use Specio::Declare;
  27         55  
  27         1072  
10 27     27   149 use Specio::Library::Builtins;
  27         58  
  27         294  
11 27     27   5817 use Specio::Library::Numeric;
  27         53  
  27         234  
12 27     27   226707 use Specio::Library::String;
  27         303800  
  27         206  
13 27     27   183705 use Text::Diff 1.44 qw(diff);
  27         93  
  27         226  
14 27     27   50626  
  27         21957  
  27         2037  
15             use Moo;
16 27     27   249  
  27         69  
  27         283  
17             our $VERSION = '0.81';
18              
19             has argv => (
20             is => 'ro',
21             isa => t('Str'),
22             default => q{}
23             );
24              
25             # This really belongs in Code::TidyAll::Role::RunsCommand but moving it there
26             # breaks plugins not in the core distro that expect to just inherit from this
27             # module and be able to specify a cmd attribute.
28             has cmd => (
29             is => 'ro',
30             isa => t('NonEmptyStr'),
31             lazy => 1,
32             builder => '_build_cmd',
33             );
34              
35             has diff_on_tidy_error => (
36             is => 'ro',
37             isa => t('Bool'),
38             default => 0
39             );
40              
41             has is_tidier => (
42             is => 'lazy',
43             isa => t('Bool'),
44             );
45              
46             has is_validator => (
47             is => 'lazy',
48             isa => t('Bool'),
49             );
50              
51             has name => (
52             is => 'ro',
53             required => 1
54             );
55              
56             has select => (
57             is => 'lazy',
58             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
59             );
60              
61             has select_regex => (
62             is => 'lazy',
63             isa => t('RegexpRef'),
64             );
65              
66             has selects => (
67             is => 'lazy',
68             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
69             );
70              
71             has shebang => (
72             is => 'ro',
73             isa => t( 'ArrayRef', of => t('NonEmptyStr') ),
74             );
75              
76             has tidyall => (
77             is => 'ro',
78              
79             # This should be "object_isa_type( class => 'Code::TidyAll' )" but then
80             # we'd need to load Code::TidyAll, leading to a circular require
81             # situation.
82             isa => t('Object'),
83             required => 1,
84             weak_ref => 1
85             );
86              
87             has weight => (
88             is => 'lazy',
89             isa => t('PositiveOrZeroInt'),
90             );
91              
92             with 'Code::TidyAll::Role::HasIgnore';
93              
94             around BUILDARGS => sub {
95             my $orig = shift;
96             my $class = shift;
97              
98             my $args = $class->$orig(@_);
99              
100             for my $key (qw( ignore select shebang )) {
101             if ( defined $args->{$key} && !ref $args->{$key} ) {
102             $args->{$key} = [ $args->{$key} ];
103             }
104             }
105              
106             return $args;
107             };
108              
109             die 'no default cmd specified';
110             }
111 0     0   0  
112             my ($self) = @_;
113             return $self->_parse_zglob_list( $self->select );
114             }
115 74     74   778  
116 74         1104 my ($self) = @_;
117             return zglobs_to_regex( @{ $self->selects } );
118             }
119              
120 45     45   574 my ($self) = @_;
121 45         68 return ( $self->can('transform_source') || $self->can('transform_file') ) ? 1 : 0;
  45         834  
122             }
123              
124             my ($self) = @_;
125 0     0   0 return ( $self->can('validate_source') || $self->can('validate_file') ) ? 1 : 0;
126 0 0 0     0 }
127              
128             # default weight
129             my ($self) = @_;
130 22     22   213 return 60 if $self->is_validator;
131 22 100 66     417 return 50;
132             }
133              
134             my ( $self, $params ) = @_;
135              
136 22     22   590 # Strict constructor
137 22 100       331 #
138 18         711 $self->validate_params($params);
139             }
140              
141             my ( $self, $params ) = @_;
142 78     78 0 10441  
143             delete( $params->{only_modes} );
144             delete( $params->{except_modes} );
145             if ( my @bad_params = grep { !$self->can($_) } keys(%$params) ) {
146 78         329 die sprintf(
147             q{unknown option%s %s for plugin '%s'},
148             @bad_params > 1 ? 's' : q{},
149             join( ', ', sort map {qq['$_']} @bad_params ),
150 78     78 0 181 $self->name
151             );
152 78         156 }
153 78         134 }
154 78 100       278  
  288         2502  
155             # No-ops by default; may be overridden in subclass
156             return $_[1];
157             }
158 2 100       18  
  3         102  
159             return $_[1];
160             }
161              
162             my ( $self, $orig_source, $rel_path, $check_only ) = @_;
163              
164             my $new_source = $orig_source;
165             if ( $self->can('transform_source') ) {
166 97     97 1 286 foreach my $iter ( 1 .. $self->tidyall->iterations ) {
167             $new_source = $self->transform_source($new_source);
168             }
169             }
170 83     83 1 209 if ( $self->can('transform_file') ) {
171             my $tempfile = $self->_write_temp_file( $rel_path, $new_source );
172             foreach my $iter ( 1 .. $self->tidyall->iterations ) {
173             $self->transform_file($tempfile);
174 98     98 0 404 }
175             $new_source = $tempfile->slurp_raw;
176 98         147 }
177 98 100       433 if ( $self->can('validate_source') ) {
178 71         379 $self->validate_source($new_source);
179 72         255 }
180             if ( $self->can('validate_file') ) {
181             my $tempfile = $self->_write_temp_file( $rel_path, $new_source );
182 92 100       1826 $self->validate_file($tempfile);
183 11         57 }
184 11         99  
185 11         48 my $diff;
186             if ( $check_only && $new_source ne $orig_source ) {
187 10         4501 $diff = $self->_maybe_diff( $orig_source, $new_source, $rel_path );
188             }
189 91 100       2156  
190 4         11 return ( $new_source, $diff );
191             }
192 90 100       372  
193 10         42 my $self = shift;
194 10         61  
195             return unless $self->diff_on_tidy_error;
196              
197 85         268 my $orig = shift;
198 85 100 66     278 my $new = shift;
199 4         25 my $rel_path = shift;
200              
201             my $orig_file = $self->_write_temp_file( $rel_path . '.orig', $orig );
202 85         1388 my $new_file = $self->_write_temp_file( $rel_path . '.new', $new );
203              
204             return diff( $orig_file->stringify, $new_file->stringify, { Style => 'Unified' } );
205             }
206 4     4   7  
207             my ( $self, $rel_path, $source ) = @_;
208 4 100       64  
209             my $tempfile = $self->tidyall->_tempdir->child($rel_path);
210 1         2 $tempfile->parent->mkpath( { mode => 0755 } );
211 1         2 $tempfile->spew_raw($source);
212 1         2 return $tempfile;
213             }
214 1         3  
215 1         5 my ( $self, $path ) = @_;
216              
217 1         6 return
218             $path =~ $self->select_regex
219             && $path !~ $self->tidyall->ignore_regex
220             && $path !~ $self->ignore_regex;
221 23     23   83 }
222              
223 23         574 1;
224 23         15534  
225 23         2997 # ABSTRACT: Create plugins for tidying or validating code
226 23         10385  
227              
228             =pod
229              
230 49     49 0 181 =encoding UTF-8
231              
232             =head1 NAME
233 49   66     937  
234             Code::TidyAll::Plugin - Create plugins for tidying or validating code
235              
236             =head1 VERSION
237              
238             version 0.81
239              
240             =head1 SYNOPSIS
241              
242             package Code::TidyAll::Plugin::SomeTidier;
243             use Moo;
244             extends 'Code::TidyAll::Plugin';
245              
246             sub transform_source {
247             my ( $self, $source ) = @_;
248             ...
249             return $source;
250             }
251              
252              
253             package Code::TidyAll::Plugin::SomeValidator;
254             use Moo;
255             extends 'Code::TidyAll::Plugin';
256              
257             sub validate_file {
258             my ( $self, $file ) = @_;
259             die 'not valid' if ...;
260             }
261              
262             =head1 DESCRIPTION
263              
264             To use a tidier or validator with C<tidyall> it must have a corresponding
265             plugin class that inherits from this class. This document describes how to
266             implement a new plugin.
267              
268             The easiest way to start is to look at existing plugins, such as
269             L<Code::TidyAll::Plugin::PerlTidy> and L<Code::TidyAll::Plugin::PerlCritic>.
270              
271             =head1 NAMING
272              
273             If you are going to publicly release your plugin, call it C<<
274             Code::TidyAll::Plugin::I<something> >> so that users can find it easily and
275             refer to it by its short name in configuration.
276              
277             If it's an internal plugin, you can call it whatever you like and refer to it
278             with a plus sign prefix in the config file, e.g.
279              
280             [+My::Tidier::Class]
281             select = **/*.{pl,pm,t}
282              
283             =head1 CONSTRUCTOR AND ATTRIBUTES
284              
285             Your plugin constructor will be called with the configuration key/value pairs
286             as parameters. e.g. given
287              
288             [PerlCritic]
289             select = lib/**/*.pm
290             ignore = lib/UtterHack.pm
291             argv = -severity 3
292              
293             then L<Code::TidyAll::Plugin::PerlCritic> would be constructed with parameters
294              
295             Code::TidyAll::Plugin::PerlCritic->new(
296             select => 'lib/**/*.pm',
297             ignore => 'lib/UtterHack.pm',
298             argv => '-severity 3',
299             );
300              
301             The following attributes are part of this base class. Your subclass can declare
302             others, of course.
303              
304             =head2 argv
305              
306             A standard attribute for passing command line arguments.
307              
308             =head2 diff_on_tidy_error
309              
310             This only applies to plugins which transform source. If this is true, then when
311             the plugin is run in check mode it will include a diff in the return value from
312             C<process_source_or_file> when the source is not tidy.
313              
314             =head2 is_validator
315              
316             An attribute that indicates if this is a validator or not; By default this
317             returns true if either C<validate_source> or C<validate_file> methods have been
318             implemented.
319              
320             =head2 name
321              
322             Name of the plugin to be used in error messages etc.
323              
324             =head2 tidyall
325              
326             A weak reference back to the L<Code::TidyAll> object.
327              
328             =head2 weight
329              
330             A number indicating the relative weight of the plugin, used to calculate the
331             order the plugins will execute in. The lower the number the sooner the plugin
332             will be executed.
333              
334             By default the weight will be C<50> for non validators (anything where
335             C<is_validator> returns false) and C<60> for validators (anything where
336             C<is_validator> returns true.)
337              
338             The order of plugin execution is determined first by the value of the C<weight>
339             attribute, and then (if multiple plugins have the same weight>) by sorting by
340             the name of module.
341              
342             =head1 METHODS
343              
344             Your plugin may define one or more of these methods. They are all no-ops by
345             default.
346              
347             =head2 $plugin->preprocess_source($source)
348              
349             Receives source code as a string; returns the processed string, or dies with
350             error. This runs on all plugins I<before> any of the other methods.
351              
352             =head2 $plugin->transform_source($source)
353              
354             Receives source code as a string; returns the transformed string, or dies with
355             error. This is repeated multiple times if --iterations was passed or specified
356             in the configuration file.
357              
358             =head2 $plugin->transform_file($file)
359              
360             Receives filename; transforms the file in place, or dies with error. Note that
361             the file will be a temporary copy of the user's file with the same basename;
362             your changes will only propagate back if there was no error reported from any
363             plugin. This is repeated multiple times if --iterations was passed or specified
364             in the configuration file.
365              
366             =head2 $plugin->validate_source($source)
367              
368             Receives source code as a string; dies with error if invalid. Return value will
369             be ignored.
370              
371             =head2 $plugin->validate_file($file)
372              
373             Receives filename; validates file and dies with error if invalid. Should not
374             modify file! Return value will be ignored.
375              
376             =head2 $plugin->postprocess_source($source)
377              
378             Receives source code as a string; returns the processed string, or dies with
379             error. This runs on all plugins I<after> any of the other methods.
380              
381             =head1 SUPPORT
382              
383             Bugs may be submitted at L<https://github.com/houseabsolute/perl-code-tidyall/issues>.
384              
385             =head1 SOURCE
386              
387             The source code repository for Code-TidyAll can be found at L<https://github.com/houseabsolute/perl-code-tidyall>.
388              
389             =head1 AUTHORS
390              
391             =over 4
392              
393             =item *
394              
395             Jonathan Swartz <swartz@pobox.com>
396              
397             =item *
398              
399             Dave Rolsky <autarch@urth.org>
400              
401             =back
402              
403             =head1 COPYRIGHT AND LICENSE
404              
405             This software is copyright (c) 2011 - 2022 by Jonathan Swartz.
406              
407             This is free software; you can redistribute it and/or modify it under
408             the same terms as the Perl 5 programming language system itself.
409              
410             The full text of the license can be found in the
411             F<LICENSE> file included with this distribution.
412              
413             =cut