File Coverage

blib/lib/Test2/Tools/PerlTidy.pm
Criterion Covered Total %
statement 137 139 98.5
branch 37 42 88.1
condition 9 10 90.0
subroutine 19 20 95.0
pod 4 5 80.0
total 206 216 95.3


line stmt bran cond sub pod time code
1             package Test2::Tools::PerlTidy;
2              
3 3     3   553383 use strict;
  3         4  
  3         99  
4 3     3   15 use warnings;
  3         9  
  3         133  
5 3     3   58 use 5.020;
  3         9  
6 3     3   815 use Test2::API qw( context );
  3         94421  
  3         295  
7 3     3   20 use File::Find ();
  3         6  
  3         60  
8 3     3   2451 use Path::Tiny qw( path );
  3         42182  
  3         215  
9 3     3   3977 use Perl::Tidy ();
  3         1609579  
  3         218  
10 3     3   51 use IO::File;
  3         7  
  3         718  
11 3     3   27 use Exporter qw( import );
  3         4  
  3         112  
12 3     3   1798 use experimental qw( signatures );
  3         10758  
  3         24  
13              
14             our @EXPORT = qw( run_tests );
15             our @EXPORT_OK = qw( run_tests is_file_tidy );
16              
17             # ABSTRACT: Test2 check that all of your Perl files are tidy
18             our $VERSION = '0.01'; # VERSION
19              
20              
21 4     4 1 18993 sub run_tests (%args) {
  4         14  
  4         7  
22 4         12 my $ctx = context();
23              
24 4 100       458 if($args{skip_all}) {
25 1         6 $ctx->plan(0, SKIP => 'All tests skipped.');
26             }
27              
28 3         16 my @files = list_files(%args);
29              
30 3 100       62 $ctx->plan(scalar @files) unless $args{no_plan};
31              
32 3         667 foreach my $file (@files) {
33 9         912 my @diag;
34 9         18 my $name = "'$file'";
35 9     2   71 $args{diag} = sub { push @diag, @_ };
  2         10  
36 9         66 my $ok = is_file_tidy($file, $args{perltidyrc}, %args);
37 9 100       231 if($ok) {
38 8         28 $ctx->pass($name);
39             } else {
40 1         8 $ctx->fail($name, @diag);
41             }
42             }
43              
44 3         301 $ctx->release;
45              
46 3         117 ();
47             }
48              
49              
50             package Test2::Tools::PerlTidy::Diff {
51              
52 3     3   3982 use Text::Diff ();
  3         32322  
  3         143  
53 3     3   1892 use Class::Tiny qw( file_to_tidy code_to_tidy perltidyrc is_tidy diff tidied_code logfile errorfile stderr );
  3         7021  
  3         18  
54              
55 12     12 0 633 sub BUILD ($self, $) {
  12         23  
  12         44  
56              
57 12         396 my $code_to_tidy = $self->code_to_tidy;
58 12         73 my $tidied_code = '';
59 12         78 my $logfile = '';
60 12         24 my $errorfile = '';
61              
62 12 50       3649 my $stderr_fh = IO::File->new_tmpfile or die "Unable to open temp file $!";
63 12         117 $stderr_fh->autoflush(1);
64              
65 12         1079 Perl::Tidy::perltidy(
66             source => \$code_to_tidy,
67             destination => \$tidied_code,
68             stderr => $stderr_fh,
69             logfile => \$logfile,
70             errorfile => \$errorfile,
71             perltidyrc => $self->perltidyrc,
72             );
73              
74 12         625938 $stderr_fh->seek(0,0);
75 12         164 my $stderr = do {
76 12         64 local $/;
77 12         584 <$stderr_fh>;
78             };
79              
80 12         522 $self->is_tidy(0);
81              
82 12 100       117 unless($stderr) {
83 11         51 $code_to_tidy =~ s/[\r\n]+$//;
84 11         52 $tidied_code =~ s/[\r\n]+$//;
85              
86 11 100       44 if($code_to_tidy eq $tidied_code) {
87 9         204 $self->diff('');
88 9         184 $self->is_tidy(1);
89             } else {
90 2         18 $self->diff( Text::Diff::diff( \$code_to_tidy, \$tidied_code, { STYLE => 'Table' }) );
91             }
92             }
93              
94 12         40020 $self->tidied_code($tidied_code);
95 12         291 $self->logfile($logfile);
96 12         284 $self->errorfile($errorfile);
97 12         279 $self->stderr($stderr);
98             }
99             }
100              
101 13     13 1 22542 sub is_file_tidy ($file_to_tidy, $perltidyrc=undef, %args) {
  13         26  
  13         35  
  13         24  
  13         21  
102 13         46 my $code_to_tidy = load_file($file_to_tidy);
103              
104 13 100       3245 unless(defined $code_to_tidy) {
105 1 50       5 if($args{return_diff_object}) {
106 0         0 die "Unable to find or read '$file_to_tidy'";
107             } else {
108 1         7 my $ctx = context();
109 1         150 $ctx->diag("Unable to find or read '$file_to_tidy'");
110 1         294 $ctx->release;
111 1         43 return 0;
112             }
113             }
114              
115 12         149 my $diff = Test2::Tools::PerlTidy::Diff->new(
116             file_to_tidy => $file_to_tidy,
117             code_to_tidy => $code_to_tidy,
118             perltidyrc => $perltidyrc,
119             );
120              
121 12 50       277 if($args{return_diff_object}) {
122 0         0 return $diff;
123             }
124              
125 12         62 my $ctx = context();
126 12 50 66 0   1603 my $diag = $args{mute} ? sub { } : $args{diag} || sub { $ctx->diag(shift) };
  4         48  
127              
128 12         24 my @diag;
129              
130 12 100       291 if($diff->stderr) {
131 1         12 $diag->("perltidy reported the following errors:");
132 1         405 $diag->($diff->stderr);
133 1         257 $ctx->release;
134 1         51 return 0;
135             }
136              
137 11 100       252 if($diff->is_tidy) {
138 9         76 $ctx->release;
139 9         197 return 1;
140             } else {
141 2         21 $diag->("The file '$file_to_tidy' is not tidy");
142 2         486 $diag->($diff->diff);
143 2         310 $ctx->release;
144 2         95 return 0;
145             }
146             }
147              
148              
149             sub list_files {
150 9     9 1 315624 my %args;
151             my $path;
152              
153             # path as only argument is for backward compatability with Test::PerlTidy
154 9 100       36 if(@_ > 1) {
155 4         16 %args = @_;
156 4         10 $path = $args{path};
157             } else {
158 5         14 ($path) = @_;
159             }
160              
161 9   100     43 $path ||= '.';
162              
163 9         38 my $ctx = context();
164              
165 9 100       1199 $ctx->bail("$path does not exist") unless -e $path;
166 8 100       92 $ctx->bail("$path is not a directory") unless -d $path;
167              
168 7   100     58 my $excludes = $args{exclude} || [qr/^blib\//]; # exclude blib by default
169              
170 7 100       33 $ctx->bail("exclude must be an array")
171             unless ref $excludes eq 'ARRAY';
172              
173 6         11 my @files;
174              
175             File::Find::find(
176             sub {
177 60     60   338 my $filename = $_;
178 60 100       4925 return if -d $filename;
179 24         109 my $path = path($File::Find::name);
180 24         1087 foreach my $exclude (@$excludes) {
181 24 100       272 return if ref $exclude ? $path =~ $exclude : $path =~ /^$exclude/;
    100          
182             }
183 17 50       895 push @files, $path if $filename =~ /\.(?:pl|pm|PL|t)$/;
184             },
185 6         477 $path,
186             );
187              
188 6         101 $ctx->release;
189              
190 6         283 map { "$_" } sort @files;
  17         141  
191             }
192              
193              
194 17     17 1 4035 sub load_file ($filename=undef) {
  17         34  
  17         24  
195 17 100 100     514 return unless defined $filename && -f $filename;
196 13         70 path($filename)->slurp_utf8;
197             }
198              
199             1;
200              
201             __END__
202              
203             =pod
204              
205             =encoding UTF-8
206              
207             =head1 NAME
208              
209             Test2::Tools::PerlTidy - Test2 check that all of your Perl files are tidy
210              
211             =head1 VERSION
212              
213             version 0.01
214              
215             =head1 SYNOPSIS
216              
217             =head1 DESCRIPTION
218              
219             This module lets you test your code for tidiness. It is more or less a drop in replacement
220             for L<Test::PerlTidy>, except that it is implemented using L<Test2::API>. The L<is_file_tidy>
221             function mentioned below also takes an option C<return_diff_object> below that did not exist
222             in the original.
223              
224             =head1 FUNCTIONS
225              
226             =head2 run_tests
227              
228             run_tests %args;
229              
230             Test all perl files for tidiness. Options:
231              
232             =over 4
233              
234             =item exclude
235              
236             C<run_tests> will look for files to test under the current directory recursively. by default
237             it will exclude files in the C<./blib/> directory. Set C<exclude> to a list reference to
238             exclusion criteria if you need to exclude additional files. Strings are assumed to be
239             path prefixes and regular expressions can be used to match any part of the file path name.
240              
241             Note that unlike L<Test::PerlTidy>, this module does NOT use
242             L<File::Spec|File::Spec>C<< ->canonpath >> before matching is attempted, because that breaks
243             this module on windows. Instead L<Path::Tiny> is used which gives consistent results on both
244             UNIX and Windows.
245              
246             =item path
247              
248             Set C<path> to the path of the top-level directory that contains the files to be
249             tested. Defaults to C<.>.
250              
251             =item perltidyrc
252              
253             By default the usual locations for the B<perltidyrc> file will be searched. You can use
254             this to override a specific tidy file.
255              
256             =item mute
257              
258             Off by default, silence diagnostics.
259              
260             =item skip_all
261              
262             Set C<skip_all> to a true value to skip the whole test file. There isn't really a good
263             reason to use this over the L<Test2::V0> C<skip_all> function.
264              
265             =item no_plan
266              
267             Set C<no_plan> to skip the plan. By default a plan with the number of files to be tested is
268             performed. There isn't really a good reason to use this over a C<done_testing> call, but
269             this is the default to maintain backward compatibility with L<Test::PerlTidy>.
270              
271             =back
272              
273             =head2 is_file_tidy
274              
275             use Test2::Tools::PerlTidy qw( is_file_tidy );
276             my $bool = is_file_tidy $filename;
277             my $bool = is_file_tidy $filename, $perltidyrc, %options;
278              
279             Returns true if the file is tidy or false otherwise. Sends diagnostics via the L<Test2> API.
280             Exportable on request. Available options:
281              
282             =over 4
283              
284             =item mute
285              
286             Do not generate diagnostics.
287              
288             =item return_diff_object
289              
290             Instead of generating diagnostics, and returning a boolean, this will return an instance
291             of L<Test2::Tools::PerlTidy::Diff>.
292              
293             =back
294              
295             =head2 list_files
296              
297             my @files = Test2::Tools::PerlTidy::list_files $path;
298             my @files = Test2::Tools::PerlTidy::list_files %args;
299              
300             Generate the list of files to be tested. Don't use this. Included as part of the public
301             interface for backward compatibility with L<Test::PerlTidy>. Not exported.
302              
303             =head2 load_file
304              
305             my $content = Test2::Tools::PerlTidy::load_file $filename;
306              
307             Load the UTF-8 encoded file to be tested from disk and return the contents. Don't use this.
308             Included as part of the public interface for backward compatibility with L<Test::PerlTidy>.
309             Not exported.
310              
311             =head1 CAVEATS
312              
313             This module uses L<Text::Diff> to compare how untidy files are different from the tidy
314             counterparts. By default L<Text::Diff> escapes Unicode characters, which keeps the tables
315             lined up correctly even when the characters are of different widths. You can change
316             this behavior by setting C<$Text::Diff::Config::Output_Unicode> to a true value. For
317             example:
318              
319             use Test2::Tools::PerlTidy;
320              
321             local $Text::Diff::Config::Output_Unicode = 1;
322             run_tests;
323              
324             will print out any Unicode characters as-is, but may produce table cells that do not
325             line up if the characters are of different widths.
326              
327             =head1 AUTHOR
328              
329             Graham Ollis <plicease@cpan.org>
330              
331             =head1 COPYRIGHT AND LICENSE
332              
333             This software is copyright (c) 2018-2024 by Graham Ollis.
334              
335             This is free software; you can redistribute it and/or modify it under
336             the same terms as the Perl 5 programming language system itself.
337              
338             =cut