File Coverage

blib/lib/CPAN/Critic/Module/Abstract.pm
Criterion Covered Total %
statement 41 143 28.6
branch 6 56 10.7
condition 2 20 10.0
subroutine 10 22 45.4
pod 1 2 50.0
total 60 243 24.6


line stmt bran cond sub pod time code
1             package CPAN::Critic::Module::Abstract;
2              
3             our $DATE = '2015-08-17'; # DATE
4             our $VERSION = '0.06'; # VERSION
5              
6 1     1   19672 use 5.010;
  1         3  
7 1     1   5 use strict;
  1         2  
  1         19  
8 1     1   4 use warnings;
  1         7  
  1         26  
9 1     1   1101 use Log::Any::IfLOG '$log';
  1         12  
  1         5  
10              
11 1     1   708 use Package::MoreUtil qw(list_package_contents);
  1         873  
  1         71  
12 1     1   839 use Perinci::Sub::DepChecker qw(check_deps);
  1         6110  
  1         70  
13              
14 1     1   8 use Exporter;
  1         1  
  1         223  
15             our @ISA = qw(Exporter);
16             our @EXPORT_OK = qw(
17             critique_cpan_module_abstract
18             declare_policy
19             );
20              
21             our %PROFILES;
22             our %SPEC;
23              
24             sub declare_policy {
25 11     11 0 34 my %args = @_;
26 11 50       29 my $name = $args{name} or die "Please specify name";
27 11 50       27 $SPEC{"policy_$name"} and die "Policy $name already declared";
28             #$args{summary} or die "Please specify summary";
29              
30             my $meta = {
31             v => 1.1,
32             summary => $args{summary},
33 11         28 };
34 11 100       25 $meta->{deps} = $args{deps} if $args{deps};
35             $meta->{args} = {
36 11         61 abstract => {req=>1, schema=>'str*'},
37             stash => {schema=>'hash*'},
38             };
39 11 100       27 if ($args{args}) {
40 10         12 for (keys %{ $args{args} }) {
  10         28  
41 2         6 $meta->{args}{$_} = $args{args}{$_};
42             }
43             }
44 11   50     34 $meta->{"_cpancritic.severity"} = $args{severity} // 3;
45 11   50     42 $meta->{"_cpancritic.themes"} = $args{themes} // [];
46              
47 1     1   5 no strict 'refs';
  1         2  
  1         1843  
48 11         18 *{__PACKAGE__."::policy_$name"} = $args{code};
  11         63  
49 11         39 $SPEC{"policy_$name"} = $meta;
50             }
51              
52             declare_policy
53             name => 'prohibit_empty',
54             severity => 5,
55             code => sub {
56 0     0     my %args = @_;
57 0           my $ab = $args{abstract};
58 0 0         if ($ab =~ /\S/) {
59 0           [200];
60             } else {
61 0           [409];
62             }
63             };
64              
65             declare_policy
66             name => 'prohibit_too_short',
67             severity => 4,
68             args => {
69             min_len => {schema=>['int*', default=>3]},
70             },
71             code => sub {
72 0     0     my %args = @_;
73 0           my $ab = $args{abstract};
74 0   0       my $l = $args{min_len} // 3;
75 0 0         if (!length($ab)) {
    0          
76 0           [412];
77             } elsif (length($ab) >= $l) {
78 0           [200];
79             } else {
80 0           [409];
81             }
82             };
83              
84             declare_policy
85             name => 'prohibit_too_long',
86             severity => 3,
87             args => {
88             max_len => {schema=>['int*', default=>72]},
89             },
90             code => sub {
91 0     0     my %args = @_;
92 0           my $ab = $args{abstract};
93 0   0       my $l = $args{max_len} // 72;
94 0 0         if (length($ab) <= $l) {
95 0           [200];
96             } else {
97 0           [409];
98             }
99             };
100              
101             declare_policy
102             name => 'prohibit_multiline',
103             severity => 3,
104             args => {},
105             code => sub {
106 0     0     my %args = @_;
107 0           my $ab = $args{abstract};
108 0 0         if ($ab !~ /\n/) {
109 0           [200];
110             } else {
111 0           [409];
112             }
113             };
114              
115             declare_policy
116             name => 'prohibit_template',
117             severity => 5,
118             args => {},
119             code => sub {
120 0     0     my %args = @_;
121 0           my $ab = $args{abstract};
122 0 0         if ($ab =~ /^(Perl extension for blah blah blah)/i) {
    0          
    0          
123 0           [409, "Template from h2xs '$1'"];
124             } elsif ($ab =~ /^(The great new )\w+(::\w+)*/i) {
125 0           [409, "Template from module-starter '$1'"];
126             } elsif ($ab =~ /^\b(blah blah)\b/i) {
127 0           [409, "Looks like a template"];
128             } else {
129 0           [200];
130             }
131             };
132              
133             declare_policy
134             name => 'prohibit_starts_with_lowercase_letter',
135             severity => 2,
136             args => {},
137             code => sub {
138 0     0     my %args = @_;
139 0           my $ab = $args{abstract};
140 0 0         if (!length($ab)) {
    0          
141 0           [412];
142             } elsif ($ab =~ /^[[:lower:]]/) {
143 0           [409];
144             } else {
145 0           [200];
146             }
147             };
148              
149             declare_policy
150             name => 'prohibit_ends_with_full_stop',
151             severity => 2,
152             args => {},
153             code => sub {
154 0     0     my %args = @_;
155 0           my $ab = $args{abstract};
156 0 0         if ($ab =~ /\.\z/) {
157 0           [409];
158             } else {
159 0           [200];
160             }
161             };
162              
163             declare_policy
164             name => 'prohibit_redundancy',
165             severity => 3,
166             args => {},
167             code => sub {
168 0     0     my %args = @_;
169 0           my $ab = $args{abstract};
170 0 0         if ($ab =~ /^( (?: (?:a|the) \s+)?
171             (?: perl\s?[56]? \s+)?
172             (?:extension|module|library|interface|xs \s binding)
173             (?: \s+ (?:to|for))?
174             )/xi) {
175 0           [409, "Saying '$1' is redundant, omit it"];
176             } else {
177 0           [200];
178             }
179             };
180              
181             declare_policy
182             name => 'require_english',
183             severity => 2,
184             args => {},
185             deps => {pm=>'Lingua::Identify'},
186             code => sub {
187 0     0     my %args = @_;
188 0           my $ab = $args{abstract};
189 0           my %langs = Lingua::Identify::langof($ab);
190 0 0         return [412, "Empty result from langof"] unless keys(%langs);
191 0           my @langs = sort { $langs{$b}<=>$langs{$a} } keys %langs;
  0            
192 0           my $confidence = Lingua::Identify::confidence(%langs);
193 0           $log->tracef(
194             "Lingua::Identify result: langof=%s, langs=%s, confidence=%s",
195             \%langs, \@langs, $confidence);
196 0 0         if ($langs[0] ne 'en') {
197             [409, "Language not detected as English, ".
198             sprintf("%d%% %s (confidence %.2f)",
199 0           $langs{$langs[0]}*100, $langs[0], $confidence)];
200             } else {
201 0           [200];
202             }
203             };
204              
205             declare_policy
206             name => 'prohibit_shouting',
207             severity => 2,
208             args => {},
209             code => sub {
210 0     0     my %args = @_;
211 0           my $ab = $args{abstract};
212 0 0         if ($ab =~ /!{3,}/) {
213 0           [409, "Too many exclamation points"];
214             } else {
215 0           my $spaces = 0; $spaces++ while $ab =~ s/\s+//;
  0            
216 0           $ab =~ s/\W+//g;
217 0           $ab =~ s/\d+//g;
218 0 0 0       if ($ab =~ /^[[:upper:]]+$/ && $spaces >= 2) {
219 0           return [409, "All-caps"];
220             } else {
221 0           return [200];
222             }
223             }
224             };
225              
226             declare_policy
227             name => 'prohibit_just_module_name',
228             severity => 2,
229             args => {},
230             code => sub {
231 0     0     my %args = @_;
232 0           my $ab = $args{abstract};
233 0 0         if ($ab =~ /^\w+(::\w+)+$/) {
234 0           [409, "Should not just be a module name"];
235             } else {
236 0           [200];
237             }
238             };
239              
240             # policy: don't repeat module name
241             # policy: should be verb + ...
242              
243             $PROFILES{all} = {
244             policies => [],
245             };
246             for (keys %{ { list_package_contents(__PACKAGE__) } }) {
247             next unless /^policy_(.+)/;
248             push @{$PROFILES{all}{policies}}, $1;
249             }
250             $PROFILES{default} = $PROFILES{all};
251             # XXX default: 4/5 if length > 100?
252              
253             $SPEC{critique_cpan_module_abstract} = {
254             v => 1.1,
255             summary => 'Critic CPAN module abstract',
256             args => {
257             abstract => {
258             schema => 'str*',
259             req => 1,
260             pos => 0,
261             },
262             profile => {
263             schema => ['str*' => {default=>'default'}],
264             },
265             },
266             };
267             sub critique_cpan_module_abstract {
268 0     0 1   my %args = @_;
269 0   0       my $abstract = $args{abstract} // "";
270 0   0       my $profile = $args{profile} // "default";
271              
272             # some cleanup for abstract
273 0           for ($abstract) {
274 0           s/\A\s+//; s/\s+\z//;
  0            
275             }
276              
277 0 0         my $pr = $PROFILES{$profile} or return [400, "No such profile '$profile'"];
278              
279 0           my @res;
280 0           $log->tracef("Running critic profile %s on abstract %s ...",
281             $profile, $abstract);
282 0           my $pass;
283 0           my $stash = {};
284 0           for my $pol0 (@{ $pr->{policies} }) {
  0            
285 0           $log->tracef("Running policy %s ...", $pol0);
286 0 0         my $pol = ref($pol0) eq 'HASH' ? %$pol0 : {name=>$pol0};
287 0 0         my $spec = $SPEC{"policy_$pol->{name}"} or
288             return [400, "No such policy $pol->{name}"];
289 0 0         if ($spec->{deps}) {
290 0           my $err = check_deps($spec->{deps});
291 0 0         return [500, "Can't run policy $pol->{name}: ".
292             "dependency failed: $err"] if $err;
293             }
294 1     1   8 no strict 'refs';
  1         3  
  1         263  
295 0           my $code = \&{__PACKAGE__ . "::policy_$pol->{name}"};
  0            
296 0           my $res = $code->(abstract=>$abstract, stash=>$stash); # XXX args
297 0           $log->tracef("Result from policy %s: %s", $pol->{name}, $res);
298 0 0         if ($res->[0] == 409) {
299 0           my $severity = $spec->{"_cpancritic.severity"};
300 0 0         $pass = 0 if $severity >= 5;
301 0   0       push @res, {
302             severity=>$severity,
303             message=>$res->[1] // "Violates $pol->{name}",
304             };
305             }
306             }
307 0   0       $pass //= 1;
308              
309             #[200, "OK", {pass=>$pass, detail=>\@res}];
310 0           [200, "OK", \@res];
311             }
312              
313             1;
314             # ABSTRACT: Critic CPAN module abstract
315              
316             __END__
317              
318             =pod
319              
320             =encoding UTF-8
321              
322             =head1 NAME
323              
324             CPAN::Critic::Module::Abstract - Critic CPAN module abstract
325              
326             =head1 VERSION
327              
328             This document describes version 0.06 of CPAN::Critic::Module::Abstract (from Perl distribution CPAN-Critic-Module-Abstract), released on 2015-08-17.
329              
330             =head1 SYNOPSIS
331              
332             % critic-cpan-module-abstract 'Perl extension for blah blah blah'
333              
334             # customize profile (add/remove policies, modify severities, ...)
335             # TODO
336              
337             =head1 DESCRIPTION
338              
339             This is a proof-of-concept module to critic CPAN module abstract.
340              
341             Dist::Zilla plugin coming shortly.
342              
343             =head1 FUNCTIONS
344              
345              
346             =head2 critique_cpan_module_abstract(%args) -> [status, msg, result, meta]
347              
348             {en_US Critic CPAN module abstract}.
349              
350             Arguments ('*' denotes required arguments):
351              
352             =over 4
353              
354             =item * B<abstract>* => I<str>
355              
356             =item * B<profile> => I<str> (default: "default")
357              
358             =back
359              
360             Returns an enveloped result (an array).
361              
362             First element (status) is an integer containing HTTP status code
363             (200 means OK, 4xx caller error, 5xx function error). Second element
364             (msg) is a string containing error message, or 'OK' if status is
365             200. Third element (result) is optional, the actual result. Fourth
366             element (meta) is called result metadata and is optional, a hash
367             that contains extra information.
368              
369             Return value: (any)
370              
371              
372             =head2 policy_prohibit_empty(%args) -> [status, msg, result, meta]
373              
374             Arguments ('*' denotes required arguments):
375              
376             =over 4
377              
378             =item * B<abstract>* => I<str>
379              
380             =item * B<stash> => I<hash>
381              
382             =back
383              
384             Returns an enveloped result (an array).
385              
386             First element (status) is an integer containing HTTP status code
387             (200 means OK, 4xx caller error, 5xx function error). Second element
388             (msg) is a string containing error message, or 'OK' if status is
389             200. Third element (result) is optional, the actual result. Fourth
390             element (meta) is called result metadata and is optional, a hash
391             that contains extra information.
392              
393             Return value: (any)
394              
395              
396             =head2 policy_prohibit_ends_with_full_stop(%args) -> [status, msg, result, meta]
397              
398             Arguments ('*' denotes required arguments):
399              
400             =over 4
401              
402             =item * B<abstract>* => I<str>
403              
404             =item * B<stash> => I<hash>
405              
406             =back
407              
408             Returns an enveloped result (an array).
409              
410             First element (status) is an integer containing HTTP status code
411             (200 means OK, 4xx caller error, 5xx function error). Second element
412             (msg) is a string containing error message, or 'OK' if status is
413             200. Third element (result) is optional, the actual result. Fourth
414             element (meta) is called result metadata and is optional, a hash
415             that contains extra information.
416              
417             Return value: (any)
418              
419              
420             =head2 policy_prohibit_just_module_name(%args) -> [status, msg, result, meta]
421              
422             Arguments ('*' denotes required arguments):
423              
424             =over 4
425              
426             =item * B<abstract>* => I<str>
427              
428             =item * B<stash> => I<hash>
429              
430             =back
431              
432             Returns an enveloped result (an array).
433              
434             First element (status) is an integer containing HTTP status code
435             (200 means OK, 4xx caller error, 5xx function error). Second element
436             (msg) is a string containing error message, or 'OK' if status is
437             200. Third element (result) is optional, the actual result. Fourth
438             element (meta) is called result metadata and is optional, a hash
439             that contains extra information.
440              
441             Return value: (any)
442              
443              
444             =head2 policy_prohibit_multiline(%args) -> [status, msg, result, meta]
445              
446             Arguments ('*' denotes required arguments):
447              
448             =over 4
449              
450             =item * B<abstract>* => I<str>
451              
452             =item * B<stash> => I<hash>
453              
454             =back
455              
456             Returns an enveloped result (an array).
457              
458             First element (status) is an integer containing HTTP status code
459             (200 means OK, 4xx caller error, 5xx function error). Second element
460             (msg) is a string containing error message, or 'OK' if status is
461             200. Third element (result) is optional, the actual result. Fourth
462             element (meta) is called result metadata and is optional, a hash
463             that contains extra information.
464              
465             Return value: (any)
466              
467              
468             =head2 policy_prohibit_redundancy(%args) -> [status, msg, result, meta]
469              
470             Arguments ('*' denotes required arguments):
471              
472             =over 4
473              
474             =item * B<abstract>* => I<str>
475              
476             =item * B<stash> => I<hash>
477              
478             =back
479              
480             Returns an enveloped result (an array).
481              
482             First element (status) is an integer containing HTTP status code
483             (200 means OK, 4xx caller error, 5xx function error). Second element
484             (msg) is a string containing error message, or 'OK' if status is
485             200. Third element (result) is optional, the actual result. Fourth
486             element (meta) is called result metadata and is optional, a hash
487             that contains extra information.
488              
489             Return value: (any)
490              
491              
492             =head2 policy_prohibit_shouting(%args) -> [status, msg, result, meta]
493              
494             Arguments ('*' denotes required arguments):
495              
496             =over 4
497              
498             =item * B<abstract>* => I<str>
499              
500             =item * B<stash> => I<hash>
501              
502             =back
503              
504             Returns an enveloped result (an array).
505              
506             First element (status) is an integer containing HTTP status code
507             (200 means OK, 4xx caller error, 5xx function error). Second element
508             (msg) is a string containing error message, or 'OK' if status is
509             200. Third element (result) is optional, the actual result. Fourth
510             element (meta) is called result metadata and is optional, a hash
511             that contains extra information.
512              
513             Return value: (any)
514              
515              
516             =head2 policy_prohibit_starts_with_lowercase_letter(%args) -> [status, msg, result, meta]
517              
518             Arguments ('*' denotes required arguments):
519              
520             =over 4
521              
522             =item * B<abstract>* => I<str>
523              
524             =item * B<stash> => I<hash>
525              
526             =back
527              
528             Returns an enveloped result (an array).
529              
530             First element (status) is an integer containing HTTP status code
531             (200 means OK, 4xx caller error, 5xx function error). Second element
532             (msg) is a string containing error message, or 'OK' if status is
533             200. Third element (result) is optional, the actual result. Fourth
534             element (meta) is called result metadata and is optional, a hash
535             that contains extra information.
536              
537             Return value: (any)
538              
539              
540             =head2 policy_prohibit_template(%args) -> [status, msg, result, meta]
541              
542             Arguments ('*' denotes required arguments):
543              
544             =over 4
545              
546             =item * B<abstract>* => I<str>
547              
548             =item * B<stash> => I<hash>
549              
550             =back
551              
552             Returns an enveloped result (an array).
553              
554             First element (status) is an integer containing HTTP status code
555             (200 means OK, 4xx caller error, 5xx function error). Second element
556             (msg) is a string containing error message, or 'OK' if status is
557             200. Third element (result) is optional, the actual result. Fourth
558             element (meta) is called result metadata and is optional, a hash
559             that contains extra information.
560              
561             Return value: (any)
562              
563              
564             =head2 policy_prohibit_too_long(%args) -> [status, msg, result, meta]
565              
566             Arguments ('*' denotes required arguments):
567              
568             =over 4
569              
570             =item * B<abstract>* => I<str>
571              
572             =item * B<max_len> => I<int> (default: 72)
573              
574             =item * B<stash> => I<hash>
575              
576             =back
577              
578             Returns an enveloped result (an array).
579              
580             First element (status) is an integer containing HTTP status code
581             (200 means OK, 4xx caller error, 5xx function error). Second element
582             (msg) is a string containing error message, or 'OK' if status is
583             200. Third element (result) is optional, the actual result. Fourth
584             element (meta) is called result metadata and is optional, a hash
585             that contains extra information.
586              
587             Return value: (any)
588              
589              
590             =head2 policy_prohibit_too_short(%args) -> [status, msg, result, meta]
591              
592             Arguments ('*' denotes required arguments):
593              
594             =over 4
595              
596             =item * B<abstract>* => I<str>
597              
598             =item * B<min_len> => I<int> (default: 3)
599              
600             =item * B<stash> => I<hash>
601              
602             =back
603              
604             Returns an enveloped result (an array).
605              
606             First element (status) is an integer containing HTTP status code
607             (200 means OK, 4xx caller error, 5xx function error). Second element
608             (msg) is a string containing error message, or 'OK' if status is
609             200. Third element (result) is optional, the actual result. Fourth
610             element (meta) is called result metadata and is optional, a hash
611             that contains extra information.
612              
613             Return value: (any)
614              
615              
616             =head2 policy_require_english(%args) -> [status, msg, result, meta]
617              
618             Arguments ('*' denotes required arguments):
619              
620             =over 4
621              
622             =item * B<abstract>* => I<str>
623              
624             =item * B<stash> => I<hash>
625              
626             =back
627              
628             Returns an enveloped result (an array).
629              
630             First element (status) is an integer containing HTTP status code
631             (200 means OK, 4xx caller error, 5xx function error). Second element
632             (msg) is a string containing error message, or 'OK' if status is
633             200. Third element (result) is optional, the actual result. Fourth
634             element (meta) is called result metadata and is optional, a hash
635             that contains extra information.
636              
637             Return value: (any)
638              
639             =for Pod::Coverage ^(.*)$
640              
641             =head1 HOMEPAGE
642              
643             Please visit the project's homepage at L<https://metacpan.org/release/CPAN-Critic-Module-Abstract>.
644              
645             =head1 SOURCE
646              
647             Source repository is at L<https://github.com/sharyanto/perl-CPAN-Critic-Module-Abstract>.
648              
649             =head1 BUGS
650              
651             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=CPAN-Critic-Module-Abstract>
652              
653             When submitting a bug or request, please include a test-file or a
654             patch to an existing test-file that illustrates the bug or desired
655             feature.
656              
657             =head1 AUTHOR
658              
659             perlancar <perlancar@cpan.org>
660              
661             =head1 COPYRIGHT AND LICENSE
662              
663             This software is copyright (c) 2015 by perlancar@cpan.org.
664              
665             This is free software; you can redistribute it and/or modify it under
666             the same terms as the Perl 5 programming language system itself.
667              
668             =cut