File Coverage

blib/lib/App/perlminlint.pm
Criterion Covered Total %
statement 29 177 16.3
branch 0 68 0.0
condition 0 39 0.0
subroutine 10 34 29.4
pod 0 21 0.0
total 39 339 11.5


line stmt bran cond sub pod time code
1             package App::perlminlint; sub MY () {__PACKAGE__}
2             # -*- coding: utf-8 -*-
3 1     1   22621 use 5.009;
  1         5  
4 1     1   9 use strict;
  1         4  
  1         32  
5 1     1   5 use warnings FATAL => 'all';
  1         6  
  1         56  
6              
7             our $VERSION = '0.24';
8              
9 1     1   5 use Carp;
  1         2  
  1         76  
10 1     1   571 use autodie;
  1         20524  
  1         9  
11 1     1   13408 use Encode qw/is_utf8/;
  1         14471  
  1         140  
12 1     1   904 use open qw/:utf8 :std/;
  1         1644  
  1         12  
13              
14             sub CFGFILE () {'.perlminlint.yml'}
15              
16 1         23 use App::perlminlint::Object -as_base,
17             [fields => qw/no_stderr
18             help
19             verbose
20             dryrun
21              
22             no_auto_libdir
23             no_widechar
24             no_force_strict
25              
26             _plugins
27             _lib_list _lib_dict
28             _perl_opts
29 1     1   922 /];
  1         6  
30              
31             require lib;
32             require File::Basename;
33              
34 1     1   898 use Module::Pluggable require => 1, sub_name => '_plugins';
  1         14601  
  1         8  
35              
36              
37             sub usage {
38 0     0 0   (my MY $app) = @_;
39 0           die <
40 0           Usage: @{[$app->basename($0)]} [opts..] YOUR_SCRIPT
41              
42             Options:
43             -v --verbose
44             -n --dryrun
45             -w -c -wc (just ignored)
46              
47             Pass-through Options:
48             -IDIR
49             -Mmodule
50             -mmodule
51             -dDEBUG
52             END
53             }
54              
55             sub run {
56 0     0 0   my ($pack, $argv) = @_;
57              
58 0           my MY $app = $pack->new($pack->parse_argv
59             ($argv, {h => 'help'
60             # Just to ignore -w -c -wc
61             , w => '', c => '', wc => ''
62             , v => 'verbose'
63             , n => 'dryrun'
64             }
65             , qr{^-[ImMd]}, my $perl_opts = []
66             ));
67              
68             # -IDIR, -mmod, -MMod
69 0           push @{$app->{_perl_opts}}, @$perl_opts;
  0            
70              
71 0 0 0       if ($app->{help} or not @$argv) {
72 0           $app->usage;
73             }
74              
75 0           $app->find_and_load_config_from(@$argv);
76              
77 0 0         if ($app->{no_stderr}) {
78 0           close STDERR;
79 0           open STDERR, '>&STDOUT';
80             }
81              
82 0 0         $app->add_lib_to_inc_for(@$argv) if not $app->{no_auto_libdir};
83              
84 0           my @res = $app->lint(@$argv);
85 0 0         if (@res) {
86 0 0 0       print join("\n", @res), "\n" unless @res == 1 and ($res[0] // '') eq '';
      0        
87             } else {
88 0           print "OK\n";
89             }
90             }
91              
92             sub after_new {
93 0     0 0   (my MY $self) = @_;
94 0           foreach my $lib (@INC) {
95 0           $self->{_lib_dict}{$lib}++;
96             }
97             }
98              
99             sub upward_first_file_from (&@) {
100 0     0 0   my ($code, $lookfor, $startFn) = @_;
101 0           my @dirs = MY->splitdir(MY->rel2abs($startFn));
102 0           pop @dirs;
103 0           local $_;
104 0           while (@dirs) {
105 0 0         -e (my $fn = MY->catdir(@dirs, $lookfor))
106             or next;
107 0 0         $code->($_ = $fn)
108             and last;
109             } continue {
110 0           pop @dirs;
111             }
112             }
113              
114             sub add_lib_to_inc_for {
115 0     0 0   (my MY $self, my $fn) = @_;
116              
117             my $adder = sub {
118 0     0     my ($libdir) = @_;
119 0 0         if (not $self->{_lib_dict}{$libdir}) {
120 0           import lib $libdir;
121 0           push @{$self->{_lib_list}}, $libdir;
  0            
122             }
123 0           };
124              
125             upward_first_file_from {
126 0     0     my ($libdir) = @_;
127 0 0         if (-d $libdir) {
128 0           $adder->($libdir);
129              
130             # Auto add carton's local/lib/perl5 too.
131 0           my $carton = $self->catdir($self->dirname($self->rel2abs($libdir))
132             , qw(local lib perl5));
133 0 0         if (-d $carton) {
134 0           $adder->($carton);
135             }
136              
137 0           1;
138             }
139 0           } lib => $fn;
140             }
141              
142             sub find_and_load_config_from {
143 0     0 0   (my MY $self, my $fn) = @_;
144             upward_first_file_from {
145 0     0     $self->load_config($_);
146 0           } CFGFILE, $fn;
147             }
148              
149             sub load_config {
150 0     0 0   (my MY $self, my $fn) = @_;
151 0 0         if ($self->{verbose}) {
152 0           print STDERR "# loading config: $fn\n";
153             }
154              
155 0           eval {require YAML::Tiny};
  0            
156 0 0         if ($@) {
157 0           die "Can't load '$fn'. Please install YAML::Tiny\n";
158             }
159              
160 0           my $yaml = YAML::Tiny->read($fn);
161 0 0 0       if (not $yaml->[0] and ref $yaml->[0] eq 'HASH') {
162 0           die "Invalid data in $fn. Only HASH is allowed\n";
163             }
164              
165 0           $self->configure($yaml->[0]);
166             }
167              
168             sub lint {
169 0     0 0   (my MY $self, my $fn) = @_;
170              
171 1 0 0 1   1946 if ($fn =~ /\P{ASCII}/ and not is_utf8($fn)) {
  1         17  
  1         17  
  0            
172 0           Encode::_utf8_on($fn);
173             }
174              
175 0           my @fallback;
176 0           foreach my $plugin ($self->plugins) {
177              
178 0 0         if (my $obj = $self->apply_to($plugin, handle_match => $fn)) {
    0          
179             #
180 0 0         my @res = $obj->handle_test($fn)
181             or next;
182              
183 0           return @res;
184              
185             } elsif ($plugin->is_generic) {
186              
187 0           push @fallback, $plugin;
188             }
189             }
190              
191 0 0         unless (@fallback) {
192 0           die "Don't know how to lint $fn\n";
193             }
194              
195 0           foreach my $plugin (@fallback) {
196              
197 0 0         my @res = $self->apply_to($plugin, handle_test => $fn)
198             or next;
199              
200 0           return @res;
201             }
202              
203 0           return "";
204             }
205              
206             sub apply_to {
207 0     0 0   (my MY $self, my ($plugin, $method, @args)) = @_;
208              
209 0           $plugin->new(app => $self)->$method(@args);
210             }
211              
212             sub plugins {
213 0     0 0   (my MY $self) = @_;
214             my $plugins = $self->{_plugins}
215 0   0       //= [sort {$b->priority <=> $a->priority} $self->_plugins];
  0            
216 0 0         wantarray ? @$plugins : $plugins;
217             }
218              
219             sub run_perl {
220 0     0 0   my MY $self = shift;
221 0           my @opts;
222 0 0         push @opts, '-C' unless $self->{no_widechar};
223 0 0         push @opts, '-Mstrict' unless $self->{no_force_strict};
224 0           push @opts, lexpand($self->{_perl_opts});
225 0           push @opts, map {"-I$_"} lexpand($self->{_lib_list});
  0            
226 0 0 0       if ($self->{verbose} || $self->{dryrun}) {
227 0           print STDERR join(" ", "#", $^X, @opts, @_), "\n";
228             }
229 0 0         if ($self->{dryrun}) {
230 0           return;
231             }
232 0 0         system($^X, @opts, @_) == 0
233             or exit $? >> 8;
234             }
235              
236             sub read_file {
237 0     0 0   (my MY $self, my $fn) = @_;
238 0           open my $fh, '<:utf8', $fn;
239 0           local $/;
240 0           scalar <$fh>;
241             }
242              
243             sub basename {
244 0     0 0   shift; File::Basename::basename(@_);
  0            
245             }
246              
247             sub dirname {
248 0     0 0   shift; File::Basename::dirname(@_);
  0            
249             }
250              
251             sub rootname {
252 0     0 0   shift;
253 0           my $fn = shift;
254 0           $fn =~ s/\.\w+$//;
255 0           join "", $fn, @_;
256             }
257              
258             sub lexpand {
259 0 0   0 0   if (not defined $_[0]) {
    0          
260 0 0         wantarray ? () : 0;
261             } elsif (not ref $_[0]) {
262 0           $_[0]
263             } else {
264 0           @{$_[0]};
  0            
265             }
266             }
267              
268             sub inc_opt {
269 0     0 0   my ($app, $file, $modname) = @_;
270 0           (my $no_pm = $file) =~ s/\.\w+$//;
271 0           my @filepath = $app->splitdir($app->rel2abs($no_pm));
272 0           my @modpath = grep {$_ ne ''} split "::", $modname;
  0            
273 0           my @popped;
274 0   0       while (@modpath and @filepath and $modpath[-1] eq $filepath[-1]) {
      0        
275 0           unshift @popped, pop @modpath;
276 0           pop @filepath;
277             }
278 0 0         if (@modpath) {
279 0           die "Can't find library root directory of $modname in file $file\n@modpath\n";
280             }
281 0           '-I' . $app->catdir(@filepath);
282             }
283              
284             sub read_shbang_opts {
285 0     0 0   (my MY $app, my $fn) = @_;
286              
287 0           my @opts;
288              
289 0           my $body = $app->read_file($fn);
290              
291 0           my (@shbang) = $app->parse_shbang($body);
292              
293 0 0         if (grep {$_ eq "-T"} @shbang) {
  0            
294 0           push @opts, "-T";
295             }
296              
297 0           @opts;
298             }
299              
300             sub parse_shbang {
301 0     0 0   my MY $app = shift;
302 0 0         my ($shbang) = $_[0] =~ m{^(\#![^\n]+)}
303             or return;
304 0           split " ", $shbang;
305             }
306              
307             # XXX: Real new and options...
308              
309             sub parse_argv {
310 0     0 0   my ($pack, $list, $alias, $special_re, $special_list) = @_;
311 0           my @opts;
312 0           while (@$list) {
313 0 0 0       if ($special_re and $list->[0] =~ $special_re) {
    0          
314 0           push @$special_list, $list->[0]
315             } elsif (my ($k, $v) = $list->[0] =~ /^--?(\w[-\w]*)(?:=(.*))?/) {
316 0           $k =~ s/-/_/g;
317 0   0       my $opt = $alias->{$k} // $k;
318 0 0         next if $opt eq ''; # To drop compat-only option.
319 0   0       push @opts, $opt => ($v // 1);
320             } else {
321 0           last;
322             }
323             } continue {
324 0           shift @$list;
325             }
326 0           @opts;
327             }
328              
329             sub parse_perl_opts {
330 0     0 0   (my MY $self, my $list) = @_;
331              
332 0           my @opts;
333 0   0       while (@$list and defined $list->[0]
      0        
334             and $list->[0] =~ m{^-[ImMd]}) {
335 0           push @opts, shift @$list;
336             }
337              
338 0           @opts;
339             }
340              
341             1; # End of App::perlminlint
342              
343             __END__