File Coverage

blib/lib/App/perlminlint.pm
Criterion Covered Total %
statement 20 158 12.6
branch 0 60 0.0
condition 0 36 0.0
subroutine 7 30 23.3
pod 0 21 0.0
total 27 305 8.8


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