File Coverage

blib/lib/Data/Schema/Schema/CPANMeta.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Data::Schema::Schema::CPANMeta;
2             our $VERSION = '0.09';
3             # ABSTRACT: Schema for CPAN Meta
4              
5              
6 4     4   110617 use feature 'state';
  4         10  
  4         362  
7 4     4   23 use Test::More;
  4         7  
  4         26  
8 4     4   6471 use Data::Schema;
  0            
  0            
9             use File::Slurp;
10             use JSON;
11             use YAML::Syck; $YAML::Syck::ImplicitTyping = 1;
12             require Exporter;
13             our @ISA = qw(Exporter);
14             our @EXPORT_OK = qw($schema_14 $yaml_schema_14
15             $schema_2 $yaml_schema_2
16             meta_yaml_ok meta_json_ok meta_spec_ok);
17              
18              
19             sub meta_yaml_ok {
20             plan tests => 2;
21             return meta_spec_ok("META.yml", undef, @_);
22             }
23              
24              
25             sub meta_json_ok {
26             plan tests => 2;
27             return meta_spec_ok("META.json", undef, @_);
28             }
29              
30              
31             sub meta_spec_ok {
32             my ($file, $vers, $msg) = @_;
33             $file ||= (-f "META.json") ? "META.json" : "META.yml";
34              
35             if (!$vers) {
36             $vers = 2;
37             } elsif ($vers != 1.4 && $vers != 2) {
38             die "Currently only CPAN META specification versions ".
39             "1.4 or 2 is supported";
40             }
41              
42             unless($msg) {
43             $msg = "$file meets the designated specification";
44             $msg .= " ($vers)" if($vers);
45             }
46              
47             my $file_content = read_file $file;
48             my $meta;
49             if ($file =~ /\.ya?ml$/i) {
50             eval '$meta = Load($file_content)';
51             } else {
52             eval '$meta = from_json($file_content)';
53             }
54             if($@) {
55             ok(0, "$file contains valid YAML/JSON");
56             ok(0, $msg);
57             diag(" ERR: $@");
58             return;
59             } else {
60             ok(1, "$file contains valid YAML/JSON");
61             }
62              
63             my $schema;
64             if ($vers == 2) {
65             $schema = $schema_2;
66             } else {
67             $schema = $schema_14;
68             }
69              
70             my $ds = Data::Schema->new(schema => $schema);
71             my $res = $ds->validate($meta);
72             if ($res->{success}) {
73             ok(1, $msg);
74             } else {
75             ok(0, $msg);
76             diag(" ERR: ".join(", ", @{ $res->{errors} }));
77             }
78             return $yaml;
79             }
80              
81              
82             our $yaml_schema_14 = <<'END_OF_SCHEMA';
83             - hash
84             - required: 1
85             required_keys: [name, abstract, version, author, license, meta-spec]
86             keys:
87              
88             meta-spec:
89             - hash
90             - required: 1
91             required_keys: [version, url]
92             keys:
93             version: [float, {required: 1, is: 1.4}]
94             url: [str, {required: 1}] # XXX type:url
95              
96             name: [str, {required: 1, match: '^\w+(-\w+)*$'}]
97              
98             abstract: [str, {required: 1}]
99              
100             version: [str, {required: 1}]
101              
102             author:
103             - array
104             - required: 1
105             minlen: 1
106             of:
107             - str
108             - required: 1
109             "match:warn": '^\S.* <.+@.+>$'
110             "match:warnmsg": 'preferred format is author-name <email-address>'
111              
112             license:
113             - str
114             - required: 1
115             one_of: [apache, artistic, artistic_2, bsd, gpl, lgpl, mit, mozilla,
116             open_source, perl, restrictive, unrestricted, unknown]
117              
118             distribution_type:
119             - str
120             - required: 1
121             one_of: [module, script]
122              
123             requires: &modlist
124             - hash
125             - required: 1
126             keys_match: '^(perl|[A-Za-z0-9_]+(::[A-Za-z0-9_]+)*)$' # XXX: regex:perl|pkg
127             values_of: [str, {required: 1}] # XXX type:ver
128              
129             build_requires: *modlist
130              
131             configure_requires: *modlist
132              
133             recommends: *modlist
134              
135             conflicts: *modlist
136              
137             optional_features:
138             - hash
139             - values_of:
140             - hash
141             - required: 1
142             keys:
143             description: str
144             requires: *modlist
145             build_requires: *modlist
146             recommends: *modlist
147             conflicts: *modlist
148              
149             dynamic_config: bool
150              
151             provides:
152             - hash
153             - required: 1
154             keys_match: '^[A-Za-z0-9_]+(::[A-Za-z0-9_]+)*$' # XXX regex:pkg
155             values_of:
156             - hash
157             - required_keys: [file, version]
158             keys:
159             file: str
160             version: str # XXX type:ver
161              
162             no_index: &no_index
163             - hash
164             - keys:
165             file: [array, {required: 1, of: [str, {required: 1}]}]
166             directory: [array, {required: 1, of: [str, {required: 1}]}]
167             package: [array, {of: [str, required: 1, match: '^[A-Za-z0-9_]+(::[A-Za-z0-9_]+)*$']}] # XXX regex:pkg
168             namespace: [array, {of: [str, required: 1, match: '^[A-Za-z0-9_]+(::[A-Za-z0-9_]+)*$']}] # XXX regex:pkg
169              
170             private: *no_index
171             # XXX WARN: deprecated
172              
173             keywords: [array, {required: 1, of: [str, {required: 1}]}]
174              
175             resources:
176             - hash
177              
178             generated_by: str
179             END_OF_SCHEMA
180              
181             our $schema_14 = Load($yaml_schema_14);
182              
183             my $v_re = '(\d+(\.\d+(_\d+)?)?|v\d+(\.\d+)+[._]\d+)';
184              
185             our $yaml_schema_2 = q~
186             def:
187             namespace: [str, {match: '^[A-Za-z0-9_]+(::[A-Za-z0-9_]+)*$'}]
188             package: [str, {match: '^[A-Za-z0-9_]+(::[A-Za-z0-9_]+)*$'}]
189             version: [str, {match: '^~.$v_re.q~$'}]
190             version_range: [str, {match: '^(~.$v_re.q~$|(>=?|<=?|==|!=)\s*~.$v_re.q~(,\s*(>=?|<=?|==|!=)\s*~.$v_re.q~)*)$'}]
191             relations:
192             - hash
193             - keys_of: package
194             values_of: [version_range, {required: 1}]
195             prereq:
196             - hash
197             - allowed_keys: [requires, recommends, suggests, conflicts]
198             values_of: relations
199             prereqs:
200             - hash
201             - allowed_keys: [configure, build, test, runtime, develop]
202             values_of: [prereq, {required: 1}]
203             optional_features_prereqs:
204             - prereqs
205             - -allowed_keys: [configure]
206             no_index:
207             - hash
208             - required: 1
209             keys:
210             file: [array, {required: 1, of: [str, {required: 1}]}]
211             directory: [array, {required: 1, of: [str, {required: 1}]}]
212             package: [array, {required: 1, of: [package, {required: 1}]}]
213             namespace: [array, {required: 1, of: [namespace, {required: 1}]}]
214              
215             type: hash
216              
217             attrs:
218             required: 1
219             required_keys: [abstract, author, dynamic_config, generated_by,
220             license, meta-spec, name, release_status, version]
221             keys_regex:
222              
223             '^[Xx]_': any
224              
225             '^abstract$': [str, {required: 1}]
226              
227             '^author$':
228             - array
229             - required: 1
230             minlen: 1
231             of:
232             - str
233             - required: 1
234             "match:warn": '^\S.* <.+@.+>$'
235             "match:warnmsg": 'preferred format is author-name <email-address>'
236              
237             '^build_requires$':
238             - relations
239             - required: 1
240             "forbidden:warn": 1
241             "forbidden:warnmsg": build_requires is deprecated in spec 2 and has been replaced by prereqs
242              
243             '^configure_requires$':
244             - relations
245             - required: 1
246             "forbidden:warn": 1
247             "forbidden:warnmsg": configure_requires is deprecated in spec 2 and has been replaced by prereqs
248              
249             '^conflicts$':
250             - relations
251             - required: 1
252             "forbidden:warn": 1
253             "forbidden:warnmsg": conflicts is deprecated in spec 2 and has been replaced by prereqs
254              
255             '^description$': [str, {required: 1}]
256              
257             '^distribution_type$':
258             - str
259             - required: 1
260             one_of: [module, script]
261             "forbidden:warn": 1
262             "forbidden:warnmsg": distribution_type is deprecated in spec 2 since it is meaningless for many distributions which are hybrid or modules and scripts
263              
264             '^dynamic_config$': [bool, {required: 1}]
265              
266             '^generated_by$': [str, {required: 1}]
267              
268             '^keywords$':
269             - array
270             - required: 1
271             of:
272             - str
273             - required: 1
274             match: '^\S+$'
275              
276             '^license$':
277             - array
278             - required: 1
279             minlen: 1
280             of:
281             - str
282             - required: 1
283             one_of: [agpl_3, apache_1_1, apache_2_0, artistic_1,
284             artistic_2, bsd, freebsd, gfdl_1_2, gfdl_1_3,
285             gpl_1, gpl_2, gpl_3, lgpl_2_1, lgpl_3_0, mit,
286             mozilla_1_0, mozilla_1_1, openssl, perl_5,
287             qpl_1_0, ssleay, sun, zlib, open_source,
288             restricted, unrestricted, unknown]
289              
290             '^license_uri$':
291             - str
292             - required: 1
293             "forbidden:warn": 1
294             "forbidden:warnmsg": license_uri is deprecated in 1.2 and has been replaced by license in resources
295              
296             '^meta-spec$':
297             - hash
298             - required: 1
299             required_keys: [version]
300             keys:
301             version: [float, {required: 1, is: 2}]
302             url: [str, {required: 1}]
303              
304             '^name$': [str, {required: 1, match: '^\w+(-\w+)*$'}]
305              
306             '^no_index$':
307             - no_index
308             - required: 1
309              
310             '^optional_features$':
311             - hash
312             - values_of:
313             - hash
314             - required: 1
315             keys:
316             description: str
317             prereqs:
318             - optional_features_prereqs
319             - required: 1
320             requires:
321             - relations
322             - required: 1
323             "forbidden:warn": 1
324             "forbidden:warnmsg": requires is deprecated in spec 2 and has been replaced by prereqs
325             build_requires:
326             - relations
327             - required: 1
328             "forbidden:warn": 1
329             "forbidden:warnmsg": build_requires is deprecated in spec 2 and has been replaced by prereqs
330             recommends:
331             - relations
332             - required: 1
333             "forbidden:warn": 1
334             "forbidden:warnmsg": recommends is deprecated in spec 2 and has been replaced by prereqs
335             conflicts:
336             - relations
337             - required: 1
338             "forbidden:warn": 1
339             "forbidden:warnmsg": conflicts is deprecated in spec 2 and has been replaced by prereqs
340              
341             '^prereqs$':
342             - prereqs
343             - required: 1
344              
345             '^private$':
346             - no_index
347             - required: 1
348             "forbidden:warn": 1
349             "forbidden:warnmsg": private is deprecated in spec 1.2 and has been renamed to no_index
350              
351             '^provides$':
352             - hash
353             - required: 1
354             keys_of: package
355             values_of:
356             - hash
357             - required: 1
358             required_keys: [file, version]
359             keys:
360             file: [str, {required: 1}]
361             version: [version, {required: 1}]
362              
363             '^recommends$':
364             - relations
365             - required: 1
366             "forbidden:warn": 1
367             "forbidden:warnmsg": recommends is deprecated in spec 2 and has been replaced by prereqs
368              
369             '^release_status$':
370             - str
371             - required: 1
372             one_of: [stable, testing, unstable]
373              
374             '^requires$':
375             - relations
376             - required: 1
377             "forbidden:warn": 1
378             "forbidden:warnmsg": requires is deprecated in spec 2 and has been replaced by prereqs
379              
380             '^resources$':
381             - hash
382             - required: 1
383             allowed_keys: [homepage, license, bugtracker, repository]
384              
385             '^version$': [version, {required: 1}]
386              
387             key_deps:
388             # if version contains underscore, release_status must not be stable
389             - [version, [str, {match: '_'}], release_status, [str, {not: stable}]]
390             ~;
391              
392             our $schema_2 = Load($yaml_schema_2);
393              
394             # XXX remove in DS 0.14
395             sub name {
396             'cpan_meta_2';
397             }
398              
399             our $DS_SCHEMAS = {
400             cpan_meta_14 => $schema_14,
401             cpan_meta_2 => $schema_2,
402             };
403              
404             sub schemas {
405             $DS_SCHEMAS;
406             }
407              
408             1;
409              
410             __END__
411             =pod
412              
413             =head1 NAME
414              
415             Data::Schema::Schema::CPANMeta - Schema for CPAN Meta
416              
417             =head1 VERSION
418              
419             version 0.09
420              
421             =head1 SYNOPSIS
422              
423              
424             # you can use it in test script a la Test::CPAN::Meta
425              
426             use Test::More;
427             use Data::Schema::Schema::CPANMeta qw(meta_yaml_ok);
428             meta_yaml_ok();
429              
430             # test META.json instead of META.yml
431              
432             use Test::More;
433             use Data::Schema::Schema::CPANMeta qw(meta_json_ok);
434             meta_json_ok();
435              
436             # slightly longer example
437              
438             use Test::More tests => ...;
439             use Data::Schema::Schema::CPANMeta qw(meta_spec_ok);
440             meta_spec_ok("META.yml", 1.4, "Bad META.yml!");
441              
442             # JSON version
443              
444             use Test::More tests => ...;
445             use Data::Schema::Schema::CPANMeta qw(meta_spec_ok);
446             meta_spec_ok("META.json", 2, "Bad META.json!");
447              
448             # using outside test script
449              
450             use Data::Schema qw(Schema::CPANMeta);
451             use YAML;
452             use File::Slurp;
453             my $meta = Load(scalar read_file "META.yml");
454             my $res = ds_validate($meta, 'cpan_meta_2');
455              
456             # to get the schema as YAML string
457              
458             use Data::Schema::Schema::CPANMeta qw($yaml_schema_2 $yaml_schema_14);
459              
460             =head1 DESCRIPTION
461              
462              
463             This module contains the schema for CPAN META.yml specification
464             version 1.4 and 2, in L<Data::Schema> language. If you import
465             C<$yaml_schema_14> and C<$yaml_schema_2> (or browse the source of this
466             module), you can find the schema written as YAML.
467              
468              
469             You can use the schema to validate META.yml or META.json files.
470              
471             =head1 FUNCTIONS
472              
473              
474             =head2 meta_yaml_ok([$msg])
475              
476              
477             Basic META.yml wrapper around meta_spec_ok.
478              
479              
480             Returns a hash reference to the contents of the parsed META.yml
481              
482              
483             =head2 meta_json_ok([$msg])
484              
485              
486             Basic META.json wrapper around meta_spec_ok.
487              
488              
489             Returns a hash reference to the contents of the parsed META.json
490              
491              
492             =head2 meta_spec_ok($file, $version [,$msg])
493              
494              
495             Validates the named file against the given specification version. Both
496             $file and $version can be undefined.
497              
498              
499             Returns a hash reference to the contents of the given file, after it
500             has been parsed.
501              
502              
503             Note that unlike with C<meta_yaml_ok()> or C<meta_json_ok()>, this
504             form requires you to specify the number of tests you will be running
505             in your test script (or use C<done_testing()>). Also note that each
506             C<meta_spec_ok()> is actually 2 tests under the hood.
507              
508             =head1 SEE ALSO
509              
510              
511             L<Data::Schema>
512              
513              
514             L<Module::Build>
515              
516              
517             L<Test::CPAN::Meta>
518              
519              
520             CPAN META 1.4 specification document, http://module-build.sourceforge.net/META-spec-v1.4.html
521              
522              
523             CPAN META 2 specification document, L<CPAN::Meta::Spec>
524              
525             =head1 AUTHOR
526              
527             Steven Haryanto <stevenharyanto@gmail.com>
528              
529             =head1 COPYRIGHT AND LICENSE
530              
531             This software is copyright (c) 2010 by Steven Haryanto.
532              
533             This is free software; you can redistribute it and/or modify it under
534             the same terms as the Perl 5 programming language system itself.
535              
536             =cut
537