File Coverage

blib/lib/Module/CPANTS/Kwalitee/MetaYML.pm
Criterion Covered Total %
statement 84 145 57.9
branch 28 84 33.3
condition 12 31 38.7
subroutine 20 30 66.6
pod 3 3 100.0
total 147 293 50.1


line stmt bran cond sub pod time code
1             package Module::CPANTS::Kwalitee::MetaYML;
2 7     7   4071 use warnings;
  7         28  
  7         237  
3 7     7   38 use strict;
  7         16  
  7         160  
4 7     7   36 use File::Spec::Functions qw(catfile);
  7         23  
  7         355  
5 7     7   2141 use CPAN::Meta::YAML;
  7         18191  
  7         599  
6 7     7   4362 use CPAN::Meta::Validator;
  7         39180  
  7         285  
7 7     7   4626 use CPAN::Meta::Converter;
  7         123156  
  7         428  
8 7     7   68 use List::Util qw/first/;
  7         13  
  7         14894  
9              
10             our $VERSION = '1.02';
11             $VERSION =~ s/_//; ## no critic
12              
13 28     28 1 78 sub order { 10 }
14              
15             my $JSON_DECODER = _load_json_decoder() || do { require JSON::PP; JSON::PP->can('decode_json') };
16              
17             ##################################################################
18             # Analyse
19             ##################################################################
20              
21             sub analyse {
22 12     12 1 64 my $class = shift;
23 12         46 my $me = shift;
24 12         343 my $distdir = $me->distdir;
25 12         202 my $meta_yml = catfile($distdir, 'META.yml');
26 12         98 my $meta_json = catfile($distdir, 'META.json');
27 12         91 my $mymeta_yml = catfile($distdir, 'MYMETA.yml');
28              
29             # META.yml is not always the most preferred meta file,
30             # but test it anyway because it may be broken sometimes.
31 12 100 66     334 if (-f $meta_yml && -r _) {
32 3         37 _analyse_yml($me, $meta_yml);
33             }
34              
35             # check also META.json (if exists).
36 12 50 33     299 if (-f $meta_json && -r _) {
37 0         0 _analyse_json($me, $meta_json);
38             }
39              
40             # If, and only if META.yml and META.json don't exist,
41             # try MYMETA.yml
42 12 50 66     374 if (!$me->d->{meta_yml} && -f $mymeta_yml && -r _) {
      33        
43 0         0 _analyse_yml($me, $mymeta_yml);
44             }
45              
46 12 100       695 if (!$me->d->{meta_yml}) {
47 9         122 return;
48             }
49              
50             # Theoretically it might be better to convert 1.* to 2.0.
51             # However, converting 2.0 to 1.4 is much cheaper for CPANTS
52             # website as it's much rarer as of this writing.
53 3 50 50     71 if (($me->d->{meta_yml_spec_version} || '1.0') gt '1.4') {
54 0         0 my $cmc = CPAN::Meta::Converter->new($me->d->{meta_yml});
55 0         0 my $meta_14 = eval { $cmc->convert(version => '1.4') };
  0         0  
56 0 0 0     0 if (!$@ && $meta_14) {
57 0         0 $me->d->{meta_yml} = $meta_14;
58             }
59             }
60              
61 3 50 33     83 $me->d->{dynamic_config} = (!exists $me->d->{meta_yml}{dynamic_config} or $me->d->{meta_yml}{dynamic_config}) ? 1 : 0;
62             }
63              
64             sub _analyse_yml {
65 3     3   21 my ($me, $file) = @_;
66 3         9 my @warnings;
67 3         18 eval {
68             # CPAN::Meta::YAML warns if it finds a duplicate key
69 3     0   83 local $SIG{__WARN__} = sub { push @warnings, @_ };
  0         0  
70 3 50       41 my $meta = CPAN::Meta::YAML->read($file) or die CPAN::Meta::YAML->errstr;
71             # Broken META.yml may return a "YAML 1.0" string first.
72             # eg. M/MH/MHASCH/Date-Gregorian-0.07.tar.gz
73 3 50 33     4757 if (@$meta > 1 or ref $meta->[0] ne ref {}) {
74 0     0   0 $me->d->{meta_yml} = first { ref $_ eq ref {} } @$meta;
  0         0  
75 0         0 $me->d->{error}{meta_yml_is_parsable} = "multiple parts found in META.yml";
76             } else {
77 3         89 $me->d->{meta_yml} = $meta->[0];
78 3         165 $me->d->{meta_yml_is_parsable} = 1;
79             }
80             };
81 3 50       61 if (my $error = $@) {
82 0         0 $error =~ s/ at \S+ line \d+.+$//s;
83 0         0 $me->d->{error}{meta_yml_is_parsable} = $error;
84             }
85 3 50       61 if ($me->d->{meta_yml}) {
86 3         84 my ($spec, $error) = _validate_meta($me->d->{meta_yml});
87 3 50       13 $me->d->{error}{meta_yml_conforms_to_known_spec} = $error if $error;
88 3         84 $me->d->{meta_yml_spec_version} = $spec->{spec};
89             }
90 3 50       73 if (@warnings) {
91 0         0 $me->d->{error}{meta_yml_has_duplicate_keys} = join ',', @warnings;
92             }
93             }
94              
95             sub _analyse_json {
96 0     0   0 my ($me, $file) = @_;
97              
98 0         0 my $meta;
99 0         0 eval {
100 0 0       0 my $json = do { open my $fh, '<', $file or die "$file: $!"; local $/; <$fh> };
  0         0  
  0         0  
  0         0  
101 0         0 $meta = $JSON_DECODER->($json);
102 0         0 $me->d->{meta_json_is_parsable} = 1;
103             };
104 0 0       0 if (my $error = $@) {
105 0         0 $error =~ s/ at \S+ line \d+.+$//s;
106 0         0 $me->d->{error}{meta_json_is_parsable} = $error;
107             }
108 0 0       0 if ($meta) {
109 0         0 my ($spec, $error) = _validate_meta($meta);
110 0 0       0 $me->d->{error}{meta_json_conforms_to_known_spec} = $error if $error;
111 0         0 $me->d->{meta_json_spec_version} = $spec->{spec};
112 0         0 $me->d->{meta_json} = $meta;
113             }
114 0 0       0 if (!$me->d->{meta_yml}) {
115 0         0 $me->d->{meta_yml} = $meta;
116 0         0 $me->d->{meta_yml_spec_version} = $me->d->{meta_json_spec_version};
117 0         0 $me->d->{meta_yml_is_meta_json} = 1;
118             }
119             }
120              
121             sub _load_json_decoder {
122 7   50 7   90 my $json_class = $ENV{CPAN_META_JSON_BACKEND} || $ENV{PERL_JSON_BACKEND} || 'JSON::PP';
123 7 50       451 eval "require $json_class; 1" or return;
124 7         97 $json_class->can('decode_json');
125             }
126              
127             sub _validate_meta {
128 3     3   56 my $meta = shift;
129 3         7 my $error;
130 3         10 my $spec = eval { CPAN::Meta::Validator->new($meta) };
  3         60  
131 3 50       115 if ($error = $@) {
    50          
132 0         0 $error =~ s/ at \S+ line \d+.+$//s;
133             } elsif (!$spec->is_valid) {
134 0         0 $error = join ';', sort $spec->errors;
135             }
136 3         1185 $error =~ s/(SCALAR|ARRAY|HASH|GLOB|REF)\(0x[0-9a-f]+\)/$1(...)/g;
137 3         12 return ($spec, $error);
138             }
139              
140             ##################################################################
141             # Kwalitee Indicators
142             ##################################################################
143              
144             sub kwalitee_indicators{
145             return [
146             {
147             name => 'meta_yml_is_parsable',
148             error => q{The META.yml file of this distribution could not be parsed by the version of CPAN::Meta::YAML.pm CPANTS is using.},
149             remedy => q{Upgrade your YAML generator so it produces valid YAML.},
150             code => sub {
151 12     12   117 my $d = shift;
152 12 50       59 !$d->{error}{meta_yml_is_parsable} ? 1 : 0
153             },
154             details => sub {
155 0     0   0 my $d = shift;
156 0         0 $d->{error}{meta_yml_is_parsable};
157             },
158             },
159             {
160             name => 'meta_json_is_parsable',
161             error => q{The META.json file of this distribution could not be parsed by the version of JSON parser CPANTS is using.},
162             remedy => q{Upgrade your META.json generator so it produces valid JSON.},
163             code => sub {
164 12     12   97 my $d = shift;
165 12 50       63 !$d->{error}{meta_json_is_parsable} ? 1 : 0
166             },
167             details => sub {
168 0     0   0 my $d = shift;
169 0         0 $d->{error}{meta_json_is_parsable};
170             },
171             },
172             {
173             name => 'meta_yml_has_provides',
174             is_experimental => 1,
175             error => q{This distribution does not have a list of provided modules defined in META.yml.},
176             remedy => q{Add all modules contained in this distribution to the META.yml field 'provides'. Module::Build or Dist::Zilla::Plugin::MetaProvides do this automatically for you.},
177             code => sub {
178 12     12   99 my $d = shift;
179 12 50       43 return 1 if !$d->{meta_yml};
180 12 50       39 return 1 if $d->{meta_yml}{provides};
181 12         31 return 0;
182             },
183             details => sub {
184 0     0   0 my $d = shift;
185 0 0       0 return "No META.yml." unless $d->{meta_yml};
186 0         0 return q{No "provides" was found in META.yml.};
187             },
188             },
189             {
190             name => 'meta_yml_conforms_to_known_spec',
191             error => q{META.yml does not conform to any recognised META.yml Spec.},
192             remedy => q{Take a look at the META.yml Spec at https://metacpan.org/pod/CPAN::Meta::History::Meta_1_4 (for version 1.4) or https://metacpan.org/pod/CPAN::Meta::Spec (for version 2), and change your META.yml accordingly.},
193             code => sub {
194 12     12   78 my $d = shift;
195 12 50       48 return 0 if $d->{error}{meta_yml_conforms_to_known_spec};
196 12         30 return 1;
197             },
198             details => sub {
199 0     0   0 my $d = shift;
200 0 0       0 return "No META.yml." unless $d->{meta_yml};
201 0 0       0 return "META.yml is broken." unless $d->{meta_yml_is_parsable};
202 0         0 return $d->{error}{meta_yml_conforms_to_known_spec};
203             },
204             },
205             {
206             name => 'meta_json_conforms_to_known_spec',
207             error => q{META.json does not conform to any recognised META Spec.},
208             remedy => q{Take a look at the META.json Spec at https://metacpan.org/pod/CPAN::Meta::History::Meta_1_4 (for version 1.4) or https://metacpan.org/pod/CPAN::Meta::Spec (for version 2), and change your META.json accordingly.},
209             code => sub {
210 12     12   80 my $d = shift;
211 12 50       53 return 0 if $d->{error}{meta_json_is_parsable};
212 12 50       57 return 0 if $d->{error}{meta_json_conforms_to_known_spec};
213 12         31 return 1;
214             },
215             details => sub {
216 0     0   0 my $d = shift;
217 0 0       0 return "META.json is broken." unless $d->{meta_json_is_parsable};
218 0         0 return $d->{error}{meta_json_conforms_to_known_spec};
219             },
220             },
221             {
222             name => 'meta_yml_declares_perl_version',
223             error => q{This distribution does not declare the minimum perl version in META.yml.},
224             is_extra => 1,
225             remedy => q{If you are using Build.PL define the {requires}{perl} = VERSION field. If you are using MakeMaker (Makefile.PL) you should upgrade ExtUtils::MakeMaker to 6.48 and use MIN_PERL_VERSION parameter. Perl::MinimumVersion can help you determine which version of Perl your module needs.},
226             code => sub {
227 12     12   118 my $d = shift;
228 12         39 my $yaml = $d->{meta_yml};
229 12 50       70 return 1 unless $yaml;
230 12 50 33     88 return ref $yaml->{requires} eq ref {} && $yaml->{requires}{perl} ? 1 : 0;
231             },
232             details => sub {
233 0     0   0 my $d = shift;
234 0         0 my $yaml = $d->{meta_yml};
235 0 0       0 return "No META.yml." unless $yaml;
236 0 0       0 return q{No "requires" was found in META.yml.} unless ref $yaml->{requires} eq ref {};
237 0 0       0 return q{No "perl" subkey was found in META.yml.} unless $yaml->{requires}{perl};
238             },
239             },
240             {
241             name => 'meta_yml_has_repository_resource',
242             is_experimental => 1,
243             error => q{This distribution does not have a link to a repository in META.yml.},
244             remedy => q{Add a 'repository' resource to the META.yml via 'meta_add' accessor (for Module::Build) or META_ADD parameter (for ExtUtils::MakeMaker).},
245             code => sub {
246 12     12   73 my $d = shift;
247 12         30 my $yaml = $d->{meta_yml};
248 12 50       50 return 1 unless $yaml;
249 12 50 33     66 return ref $yaml->{resources} eq ref {} && $yaml->{resources}{repository} ? 1 : 0;
250             },
251             details => sub {
252 0     0   0 my $d = shift;
253 0         0 my $yaml = $d->{meta_yml};
254 0 0       0 return "No META.yml." unless $yaml;
255 0 0       0 return q{No "resources" was found in META.yml.} unless ref $yaml->{resources} eq ref {};
256 0 0       0 return q{No "repository" subkey was found in META.yml.} unless $yaml->{resources}{repository};
257             },
258             },
259 8     8 1 555 ];
260             }
261              
262             q{Barbies Favourite record of the moment:
263             Nine Inch Nails: Year Zero};
264              
265             __END__