xref: /openbsd-src/gnu/usr.bin/perl/cpan/CPAN-Meta/lib/CPAN/Meta/Converter.pm (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1use 5.006;
2use strict;
3use warnings;
4package CPAN::Meta::Converter;
5our $VERSION = '2.140640'; # VERSION
6
7# =head1 SYNOPSIS
8#
9#   my $struct = decode_json_file('META.json');
10#
11#   my $cmc = CPAN::Meta::Converter->new( $struct );
12#
13#   my $new_struct = $cmc->convert( version => "2" );
14#
15# =head1 DESCRIPTION
16#
17# This module converts CPAN Meta structures from one form to another.  The
18# primary use is to convert older structures to the most modern version of
19# the specification, but other transformations may be implemented in the
20# future as needed.  (E.g. stripping all custom fields or stripping all
21# optional fields.)
22#
23# =cut
24
25use CPAN::Meta::Validator;
26use CPAN::Meta::Requirements;
27use version 0.88 ();
28use Parse::CPAN::Meta 1.4400 ();
29use List::Util 1.33 qw/all/;
30
31sub _dclone {
32  my $ref = shift;
33
34  # if an object is in the data structure and doesn't specify how to
35  # turn itself into JSON, we just stringify the object.  That does the
36  # right thing for typical things that might be there, like version objects,
37  # Path::Class objects, etc.
38  no warnings 'once';
39  local *UNIVERSAL::TO_JSON = sub { return "$_[0]" };
40
41  my $backend = Parse::CPAN::Meta->json_backend();
42  return $backend->new->utf8->decode(
43    $backend->new->utf8->allow_blessed->convert_blessed->encode($ref)
44  );
45}
46
47my %known_specs = (
48    '2'   => 'http://search.cpan.org/perldoc?CPAN::Meta::Spec',
49    '1.4' => 'http://module-build.sourceforge.net/META-spec-v1.4.html',
50    '1.3' => 'http://module-build.sourceforge.net/META-spec-v1.3.html',
51    '1.2' => 'http://module-build.sourceforge.net/META-spec-v1.2.html',
52    '1.1' => 'http://module-build.sourceforge.net/META-spec-v1.1.html',
53    '1.0' => 'http://module-build.sourceforge.net/META-spec-v1.0.html'
54);
55
56my @spec_list = sort { $a <=> $b } keys %known_specs;
57my ($LOWEST, $HIGHEST) = @spec_list[0,-1];
58
59#--------------------------------------------------------------------------#
60# converters
61#
62# called as $converter->($element, $field_name, $full_meta, $to_version)
63#
64# defined return value used for field
65# undef return value means field is skipped
66#--------------------------------------------------------------------------#
67
68sub _keep { $_[0] }
69
70sub _keep_or_one { defined($_[0]) ? $_[0] : 1 }
71
72sub _keep_or_zero { defined($_[0]) ? $_[0] : 0 }
73
74sub _keep_or_unknown { defined($_[0]) && length($_[0]) ? $_[0] : "unknown" }
75
76sub _generated_by {
77  my $gen = shift;
78  my $sig = __PACKAGE__ . " version " . (__PACKAGE__->VERSION || "<dev>");
79
80  return $sig unless defined $gen and length $gen;
81  return $gen if $gen =~ /\Q$sig/;
82  return "$gen, $sig";
83}
84
85sub _listify { ! defined $_[0] ? undef : ref $_[0] eq 'ARRAY' ? $_[0] : [$_[0]] }
86
87sub _prefix_custom {
88  my $key = shift;
89  $key =~ s/^(?!x_)   # Unless it already starts with x_
90             (?:x-?)? # Remove leading x- or x (if present)
91           /x_/ix;    # and prepend x_
92  return $key;
93}
94
95sub _ucfirst_custom {
96  my $key = shift;
97  $key = ucfirst $key unless $key =~ /[A-Z]/;
98  return $key;
99}
100
101sub _no_prefix_ucfirst_custom {
102  my $key = shift;
103  $key =~ s/^x_//;
104  return _ucfirst_custom($key);
105}
106
107sub _change_meta_spec {
108  my ($element, undef, undef, $version) = @_;
109  return {
110    version => $version,
111    url => $known_specs{$version},
112  };
113}
114
115my @open_source = (
116  'perl',
117  'gpl',
118  'apache',
119  'artistic',
120  'artistic_2',
121  'lgpl',
122  'bsd',
123  'gpl',
124  'mit',
125  'mozilla',
126  'open_source',
127);
128
129my %is_open_source = map {; $_ => 1 } @open_source;
130
131my @valid_licenses_1 = (
132  @open_source,
133  'unrestricted',
134  'restrictive',
135  'unknown',
136);
137
138my %license_map_1 = (
139  ( map { $_ => $_ } @valid_licenses_1 ),
140  artistic2 => 'artistic_2',
141);
142
143sub _license_1 {
144  my ($element) = @_;
145  return 'unknown' unless defined $element;
146  if ( $license_map_1{lc $element} ) {
147    return $license_map_1{lc $element};
148  }
149  else {
150    return 'unknown';
151  }
152}
153
154my @valid_licenses_2 = qw(
155  agpl_3
156  apache_1_1
157  apache_2_0
158  artistic_1
159  artistic_2
160  bsd
161  freebsd
162  gfdl_1_2
163  gfdl_1_3
164  gpl_1
165  gpl_2
166  gpl_3
167  lgpl_2_1
168  lgpl_3_0
169  mit
170  mozilla_1_0
171  mozilla_1_1
172  openssl
173  perl_5
174  qpl_1_0
175  ssleay
176  sun
177  zlib
178  open_source
179  restricted
180  unrestricted
181  unknown
182);
183
184# The "old" values were defined by Module::Build, and were often vague.  I have
185# made the decisions below based on reading Module::Build::API and how clearly
186# it specifies the version of the license.
187my %license_map_2 = (
188  (map { $_ => $_ } @valid_licenses_2),
189  apache      => 'apache_2_0',  # clearly stated as 2.0
190  artistic    => 'artistic_1',  # clearly stated as 1
191  artistic2   => 'artistic_2',  # clearly stated as 2
192  gpl         => 'open_source', # we don't know which GPL; punt
193  lgpl        => 'open_source', # we don't know which LGPL; punt
194  mozilla     => 'open_source', # we don't know which MPL; punt
195  perl        => 'perl_5',      # clearly Perl 5
196  restrictive => 'restricted',
197);
198
199sub _license_2 {
200  my ($element) = @_;
201  return [ 'unknown' ] unless defined $element;
202  $element = [ $element ] unless ref $element eq 'ARRAY';
203  my @new_list;
204  for my $lic ( @$element ) {
205    next unless defined $lic;
206    if ( my $new = $license_map_2{lc $lic} ) {
207      push @new_list, $new;
208    }
209  }
210  return @new_list ? \@new_list : [ 'unknown' ];
211}
212
213my %license_downgrade_map = qw(
214  agpl_3            open_source
215  apache_1_1        apache
216  apache_2_0        apache
217  artistic_1        artistic
218  artistic_2        artistic_2
219  bsd               bsd
220  freebsd           open_source
221  gfdl_1_2          open_source
222  gfdl_1_3          open_source
223  gpl_1             gpl
224  gpl_2             gpl
225  gpl_3             gpl
226  lgpl_2_1          lgpl
227  lgpl_3_0          lgpl
228  mit               mit
229  mozilla_1_0       mozilla
230  mozilla_1_1       mozilla
231  openssl           open_source
232  perl_5            perl
233  qpl_1_0           open_source
234  ssleay            open_source
235  sun               open_source
236  zlib              open_source
237  open_source       open_source
238  restricted        restrictive
239  unrestricted      unrestricted
240  unknown           unknown
241);
242
243sub _downgrade_license {
244  my ($element) = @_;
245  if ( ! defined $element ) {
246    return "unknown";
247  }
248  elsif( ref $element eq 'ARRAY' ) {
249    if ( @$element > 1) {
250      if ( all { $is_open_source{ $license_downgrade_map{lc $_} || 'unknown' } } @$element ) {
251        return 'open_source';
252      }
253      else {
254        return 'unknown';
255      }
256    }
257    elsif ( @$element == 1 ) {
258      return $license_downgrade_map{lc $element->[0]} || "unknown";
259    }
260  }
261  elsif ( ! ref $element ) {
262    return $license_downgrade_map{lc $element} || "unknown";
263  }
264  return "unknown";
265}
266
267my $no_index_spec_1_2 = {
268  'file' => \&_listify,
269  'dir' => \&_listify,
270  'package' => \&_listify,
271  'namespace' => \&_listify,
272};
273
274my $no_index_spec_1_3 = {
275  'file' => \&_listify,
276  'directory' => \&_listify,
277  'package' => \&_listify,
278  'namespace' => \&_listify,
279};
280
281my $no_index_spec_2 = {
282  'file' => \&_listify,
283  'directory' => \&_listify,
284  'package' => \&_listify,
285  'namespace' => \&_listify,
286  ':custom'  => \&_prefix_custom,
287};
288
289sub _no_index_1_2 {
290  my (undef, undef, $meta) = @_;
291  my $no_index = $meta->{no_index} || $meta->{private};
292  return unless $no_index;
293
294  # cleanup wrong format
295  if ( ! ref $no_index ) {
296    my $item = $no_index;
297    $no_index = { dir => [ $item ], file => [ $item ] };
298  }
299  elsif ( ref $no_index eq 'ARRAY' ) {
300    my $list = $no_index;
301    $no_index = { dir => [ @$list ], file => [ @$list ] };
302  }
303
304  # common mistake: files -> file
305  if ( exists $no_index->{files} ) {
306    $no_index->{file} = delete $no_index->{file};
307  }
308  # common mistake: modules -> module
309  if ( exists $no_index->{modules} ) {
310    $no_index->{module} = delete $no_index->{module};
311  }
312  return _convert($no_index, $no_index_spec_1_2);
313}
314
315sub _no_index_directory {
316  my ($element, $key, $meta, $version) = @_;
317  return unless $element;
318
319  # cleanup wrong format
320  if ( ! ref $element ) {
321    my $item = $element;
322    $element = { directory => [ $item ], file => [ $item ] };
323  }
324  elsif ( ref $element eq 'ARRAY' ) {
325    my $list = $element;
326    $element = { directory => [ @$list ], file => [ @$list ] };
327  }
328
329  if ( exists $element->{dir} ) {
330    $element->{directory} = delete $element->{dir};
331  }
332  # common mistake: files -> file
333  if ( exists $element->{files} ) {
334    $element->{file} = delete $element->{file};
335  }
336  # common mistake: modules -> module
337  if ( exists $element->{modules} ) {
338    $element->{module} = delete $element->{module};
339  }
340  my $spec = $version == 2 ? $no_index_spec_2 : $no_index_spec_1_3;
341  return _convert($element, $spec);
342}
343
344sub _is_module_name {
345  my $mod = shift;
346  return unless defined $mod && length $mod;
347  return $mod =~ m{^[A-Za-z][A-Za-z0-9_]*(?:::[A-Za-z0-9_]+)*$};
348}
349
350sub _clean_version {
351  my ($element) = @_;
352  return 0 if ! defined $element;
353
354  $element =~ s{^\s*}{};
355  $element =~ s{\s*$}{};
356  $element =~ s{^\.}{0.};
357
358  return 0 if ! length $element;
359  return 0 if ( $element eq 'undef' || $element eq '<undef>' );
360
361  my $v = eval { version->new($element) };
362  # XXX check defined $v and not just $v because version objects leak memory
363  # in boolean context -- dagolden, 2012-02-03
364  if ( defined $v ) {
365    return $v->is_qv ? $v->normal : $element;
366  }
367  else {
368    return 0;
369  }
370}
371
372sub _bad_version_hook {
373  my ($v) = @_;
374  $v =~ s{[a-z]+$}{}; # strip trailing alphabetics
375  my $vobj = eval { version->parse($v) };
376  return defined($vobj) ? $vobj : version->parse(0); # or give up
377}
378
379sub _version_map {
380  my ($element) = @_;
381  return unless defined $element;
382  if ( ref $element eq 'HASH' ) {
383    # XXX turn this into CPAN::Meta::Requirements with bad version hook
384    # and then turn it back into a hash
385    my $new_map = CPAN::Meta::Requirements->new(
386      { bad_version_hook => \&_bad_version_hook } # punt
387    );
388    while ( my ($k,$v) = each %$element ) {
389      next unless _is_module_name($k);
390      if ( !defined($v) || !length($v) || $v eq 'undef' || $v eq '<undef>'  ) {
391        $v = 0;
392      }
393      # some weird, old META have bad yml with module => module
394      # so check if value is like a module name and not like a version
395      if ( _is_module_name($v) && ! version::is_lax($v) ) {
396        $new_map->add_minimum($k => 0);
397        $new_map->add_minimum($v => 0);
398      }
399      $new_map->add_string_requirement($k => $v);
400    }
401    return $new_map->as_string_hash;
402  }
403  elsif ( ref $element eq 'ARRAY' ) {
404    my $hashref = { map { $_ => 0 } @$element };
405    return _version_map($hashref); # cleanup any weird stuff
406  }
407  elsif ( ref $element eq '' && length $element ) {
408    return { $element => 0 }
409  }
410  return;
411}
412
413sub _prereqs_from_1 {
414  my (undef, undef, $meta) = @_;
415  my $prereqs = {};
416  for my $phase ( qw/build configure/ ) {
417    my $key = "${phase}_requires";
418    $prereqs->{$phase}{requires} = _version_map($meta->{$key})
419      if $meta->{$key};
420  }
421  for my $rel ( qw/requires recommends conflicts/ ) {
422    $prereqs->{runtime}{$rel} = _version_map($meta->{$rel})
423      if $meta->{$rel};
424  }
425  return $prereqs;
426}
427
428my $prereqs_spec = {
429  configure => \&_prereqs_rel,
430  build     => \&_prereqs_rel,
431  test      => \&_prereqs_rel,
432  runtime   => \&_prereqs_rel,
433  develop   => \&_prereqs_rel,
434  ':custom'  => \&_prefix_custom,
435};
436
437my $relation_spec = {
438  requires   => \&_version_map,
439  recommends => \&_version_map,
440  suggests   => \&_version_map,
441  conflicts  => \&_version_map,
442  ':custom'  => \&_prefix_custom,
443};
444
445sub _cleanup_prereqs {
446  my ($prereqs, $key, $meta, $to_version) = @_;
447  return unless $prereqs && ref $prereqs eq 'HASH';
448  return _convert( $prereqs, $prereqs_spec, $to_version );
449}
450
451sub _prereqs_rel {
452  my ($relation, $key, $meta, $to_version) = @_;
453  return unless $relation && ref $relation eq 'HASH';
454  return _convert( $relation, $relation_spec, $to_version );
455}
456
457
458BEGIN {
459  my @old_prereqs = qw(
460    requires
461    configure_requires
462    recommends
463    conflicts
464  );
465
466  for ( @old_prereqs ) {
467    my $sub = "_get_$_";
468    my ($phase,$type) = split qr/_/, $_;
469    if ( ! defined $type ) {
470      $type = $phase;
471      $phase = 'runtime';
472    }
473    no strict 'refs';
474    *{$sub} = sub { _extract_prereqs($_[2]->{prereqs},$phase,$type) };
475  }
476}
477
478sub _get_build_requires {
479  my ($data, $key, $meta) = @_;
480
481  my $test_h  = _extract_prereqs($_[2]->{prereqs}, qw(test  requires)) || {};
482  my $build_h = _extract_prereqs($_[2]->{prereqs}, qw(build requires)) || {};
483
484  my $test_req  = CPAN::Meta::Requirements->from_string_hash($test_h);
485  my $build_req = CPAN::Meta::Requirements->from_string_hash($build_h);
486
487  $test_req->add_requirements($build_req)->as_string_hash;
488}
489
490sub _extract_prereqs {
491  my ($prereqs, $phase, $type) = @_;
492  return unless ref $prereqs eq 'HASH';
493  return scalar _version_map($prereqs->{$phase}{$type});
494}
495
496sub _downgrade_optional_features {
497  my (undef, undef, $meta) = @_;
498  return unless exists $meta->{optional_features};
499  my $origin = $meta->{optional_features};
500  my $features = {};
501  for my $name ( keys %$origin ) {
502    $features->{$name} = {
503      description => $origin->{$name}{description},
504      requires => _extract_prereqs($origin->{$name}{prereqs},'runtime','requires'),
505      configure_requires => _extract_prereqs($origin->{$name}{prereqs},'runtime','configure_requires'),
506      build_requires => _extract_prereqs($origin->{$name}{prereqs},'runtime','build_requires'),
507      recommends => _extract_prereqs($origin->{$name}{prereqs},'runtime','recommends'),
508      conflicts => _extract_prereqs($origin->{$name}{prereqs},'runtime','conflicts'),
509    };
510    for my $k (keys %{$features->{$name}} ) {
511      delete $features->{$name}{$k} unless defined $features->{$name}{$k};
512    }
513  }
514  return $features;
515}
516
517sub _upgrade_optional_features {
518  my (undef, undef, $meta) = @_;
519  return unless exists $meta->{optional_features};
520  my $origin = $meta->{optional_features};
521  my $features = {};
522  for my $name ( keys %$origin ) {
523    $features->{$name} = {
524      description => $origin->{$name}{description},
525      prereqs => _prereqs_from_1(undef, undef, $origin->{$name}),
526    };
527    delete $features->{$name}{prereqs}{configure};
528  }
529  return $features;
530}
531
532my $optional_features_2_spec = {
533  description => \&_keep,
534  prereqs => \&_cleanup_prereqs,
535  ':custom'  => \&_prefix_custom,
536};
537
538sub _feature_2 {
539  my ($element, $key, $meta, $to_version) = @_;
540  return unless $element && ref $element eq 'HASH';
541  _convert( $element, $optional_features_2_spec, $to_version );
542}
543
544sub _cleanup_optional_features_2 {
545  my ($element, $key, $meta, $to_version) = @_;
546  return unless $element && ref $element eq 'HASH';
547  my $new_data = {};
548  for my $k ( keys %$element ) {
549    $new_data->{$k} = _feature_2( $element->{$k}, $k, $meta, $to_version );
550  }
551  return unless keys %$new_data;
552  return $new_data;
553}
554
555sub _optional_features_1_4 {
556  my ($element) = @_;
557  return unless $element;
558  $element = _optional_features_as_map($element);
559  for my $name ( keys %$element ) {
560    for my $drop ( qw/requires_packages requires_os excluded_os/ ) {
561      delete $element->{$name}{$drop};
562    }
563  }
564  return $element;
565}
566
567sub _optional_features_as_map {
568  my ($element) = @_;
569  return unless $element;
570  if ( ref $element eq 'ARRAY' ) {
571    my %map;
572    for my $feature ( @$element ) {
573      my (@parts) = %$feature;
574      $map{$parts[0]} = $parts[1];
575    }
576    $element = \%map;
577  }
578  return $element;
579}
580
581sub _is_urlish { defined $_[0] && $_[0] =~ m{\A[-+.a-z0-9]+:.+}i }
582
583sub _url_or_drop {
584  my ($element) = @_;
585  return $element if _is_urlish($element);
586  return;
587}
588
589sub _url_list {
590  my ($element) = @_;
591  return unless $element;
592  $element = _listify( $element );
593  $element = [ grep { _is_urlish($_) } @$element ];
594  return unless @$element;
595  return $element;
596}
597
598sub _author_list {
599  my ($element) = @_;
600  return [ 'unknown' ] unless $element;
601  $element = _listify( $element );
602  $element = [ map { defined $_ && length $_ ? $_ : 'unknown' } @$element ];
603  return [ 'unknown' ] unless @$element;
604  return $element;
605}
606
607my $resource2_upgrade = {
608  license    => sub { return _is_urlish($_[0]) ? _listify( $_[0] ) : undef },
609  homepage   => \&_url_or_drop,
610  bugtracker => sub {
611    my ($item) = @_;
612    return unless $item;
613    if ( $item =~ m{^mailto:(.*)$} ) { return { mailto => $1 } }
614    elsif( _is_urlish($item) ) { return { web => $item } }
615    else { return }
616  },
617  repository => sub { return _is_urlish($_[0]) ? { url => $_[0] } : undef },
618  ':custom'  => \&_prefix_custom,
619};
620
621sub _upgrade_resources_2 {
622  my (undef, undef, $meta, $version) = @_;
623  return unless exists $meta->{resources};
624  return _convert($meta->{resources}, $resource2_upgrade);
625}
626
627my $bugtracker2_spec = {
628  web => \&_url_or_drop,
629  mailto => \&_keep,
630  ':custom'  => \&_prefix_custom,
631};
632
633sub _repo_type {
634  my ($element, $key, $meta, $to_version) = @_;
635  return $element if defined $element;
636  return unless exists $meta->{url};
637  my $repo_url = $meta->{url};
638  for my $type ( qw/git svn/ ) {
639    return $type if $repo_url =~ m{\A$type};
640  }
641  return;
642}
643
644my $repository2_spec = {
645  web => \&_url_or_drop,
646  url => \&_url_or_drop,
647  type => \&_repo_type,
648  ':custom'  => \&_prefix_custom,
649};
650
651my $resources2_cleanup = {
652  license    => \&_url_list,
653  homepage   => \&_url_or_drop,
654  bugtracker => sub { ref $_[0] ? _convert( $_[0], $bugtracker2_spec ) : undef },
655  repository => sub { my $data = shift; ref $data ? _convert( $data, $repository2_spec ) : undef },
656  ':custom'  => \&_prefix_custom,
657};
658
659sub _cleanup_resources_2 {
660  my ($resources, $key, $meta, $to_version) = @_;
661  return unless $resources && ref $resources eq 'HASH';
662  return _convert($resources, $resources2_cleanup, $to_version);
663}
664
665my $resource1_spec = {
666  license    => \&_url_or_drop,
667  homepage   => \&_url_or_drop,
668  bugtracker => \&_url_or_drop,
669  repository => \&_url_or_drop,
670  ':custom'  => \&_keep,
671};
672
673sub _resources_1_3 {
674  my (undef, undef, $meta, $version) = @_;
675  return unless exists $meta->{resources};
676  return _convert($meta->{resources}, $resource1_spec);
677}
678
679*_resources_1_4 = *_resources_1_3;
680
681sub _resources_1_2 {
682  my (undef, undef, $meta) = @_;
683  my $resources = $meta->{resources} || {};
684  if ( $meta->{license_url} && ! $resources->{license} ) {
685    $resources->{license} = $meta->license_url
686      if _is_urlish($meta->{license_url});
687  }
688  return unless keys %$resources;
689  return _convert($resources, $resource1_spec);
690}
691
692my $resource_downgrade_spec = {
693  license    => sub { return ref $_[0] ? $_[0]->[0] : $_[0] },
694  homepage   => \&_url_or_drop,
695  bugtracker => sub { return $_[0]->{web} },
696  repository => sub { return $_[0]->{url} || $_[0]->{web} },
697  ':custom'  => \&_no_prefix_ucfirst_custom,
698};
699
700sub _downgrade_resources {
701  my (undef, undef, $meta, $version) = @_;
702  return unless exists $meta->{resources};
703  return _convert($meta->{resources}, $resource_downgrade_spec);
704}
705
706sub _release_status {
707  my ($element, undef, $meta) = @_;
708  return $element if $element && $element =~ m{\A(?:stable|testing|unstable)\z};
709  return _release_status_from_version(undef, undef, $meta);
710}
711
712sub _release_status_from_version {
713  my (undef, undef, $meta) = @_;
714  my $version = $meta->{version} || '';
715  return ( $version =~ /_/ ) ? 'testing' : 'stable';
716}
717
718my $provides_spec = {
719  file => \&_keep,
720  version => \&_keep,
721};
722
723my $provides_spec_2 = {
724  file => \&_keep,
725  version => \&_keep,
726  ':custom'  => \&_prefix_custom,
727};
728
729sub _provides {
730  my ($element, $key, $meta, $to_version) = @_;
731  return unless defined $element && ref $element eq 'HASH';
732  my $spec = $to_version == 2 ? $provides_spec_2 : $provides_spec;
733  my $new_data = {};
734  for my $k ( keys %$element ) {
735    $new_data->{$k} = _convert($element->{$k}, $spec, $to_version);
736    $new_data->{$k}{version} = _clean_version($element->{$k}{version})
737      if exists $element->{$k}{version};
738  }
739  return $new_data;
740}
741
742sub _convert {
743  my ($data, $spec, $to_version) = @_;
744
745  my $new_data = {};
746  for my $key ( keys %$spec ) {
747    next if $key eq ':custom' || $key eq ':drop';
748    next unless my $fcn = $spec->{$key};
749    die "spec for '$key' is not a coderef"
750      unless ref $fcn && ref $fcn eq 'CODE';
751    my $new_value = $fcn->($data->{$key}, $key, $data, $to_version);
752    $new_data->{$key} = $new_value if defined $new_value;
753  }
754
755  my $drop_list   = $spec->{':drop'};
756  my $customizer  = $spec->{':custom'} || \&_keep;
757
758  for my $key ( keys %$data ) {
759    next if $drop_list && grep { $key eq $_ } @$drop_list;
760    next if exists $spec->{$key}; # we handled it
761    $new_data->{ $customizer->($key) } = $data->{$key};
762  }
763
764  return $new_data;
765}
766
767#--------------------------------------------------------------------------#
768# define converters for each conversion
769#--------------------------------------------------------------------------#
770
771# each converts from prior version
772# special ":custom" field is used for keys not recognized in spec
773my %up_convert = (
774  '2-from-1.4' => {
775    # PRIOR MANDATORY
776    'abstract'            => \&_keep_or_unknown,
777    'author'              => \&_author_list,
778    'generated_by'        => \&_generated_by,
779    'license'             => \&_license_2,
780    'meta-spec'           => \&_change_meta_spec,
781    'name'                => \&_keep,
782    'version'             => \&_keep,
783    # CHANGED TO MANDATORY
784    'dynamic_config'      => \&_keep_or_one,
785    # ADDED MANDATORY
786    'release_status'      => \&_release_status_from_version,
787    # PRIOR OPTIONAL
788    'keywords'            => \&_keep,
789    'no_index'            => \&_no_index_directory,
790    'optional_features'   => \&_upgrade_optional_features,
791    'provides'            => \&_provides,
792    'resources'           => \&_upgrade_resources_2,
793    # ADDED OPTIONAL
794    'description'         => \&_keep,
795    'prereqs'             => \&_prereqs_from_1,
796
797    # drop these deprecated fields, but only after we convert
798    ':drop' => [ qw(
799        build_requires
800        configure_requires
801        conflicts
802        distribution_type
803        license_url
804        private
805        recommends
806        requires
807    ) ],
808
809    # other random keys need x_ prefixing
810    ':custom'              => \&_prefix_custom,
811  },
812  '1.4-from-1.3' => {
813    # PRIOR MANDATORY
814    'abstract'            => \&_keep_or_unknown,
815    'author'              => \&_author_list,
816    'generated_by'        => \&_generated_by,
817    'license'             => \&_license_1,
818    'meta-spec'           => \&_change_meta_spec,
819    'name'                => \&_keep,
820    'version'             => \&_keep,
821    # PRIOR OPTIONAL
822    'build_requires'      => \&_version_map,
823    'conflicts'           => \&_version_map,
824    'distribution_type'   => \&_keep,
825    'dynamic_config'      => \&_keep_or_one,
826    'keywords'            => \&_keep,
827    'no_index'            => \&_no_index_directory,
828    'optional_features'   => \&_optional_features_1_4,
829    'provides'            => \&_provides,
830    'recommends'          => \&_version_map,
831    'requires'            => \&_version_map,
832    'resources'           => \&_resources_1_4,
833    # ADDED OPTIONAL
834    'configure_requires'  => \&_keep,
835
836    # drop these deprecated fields, but only after we convert
837    ':drop' => [ qw(
838      license_url
839      private
840    )],
841
842    # other random keys are OK if already valid
843    ':custom'              => \&_keep
844  },
845  '1.3-from-1.2' => {
846    # PRIOR MANDATORY
847    'abstract'            => \&_keep_or_unknown,
848    'author'              => \&_author_list,
849    'generated_by'        => \&_generated_by,
850    'license'             => \&_license_1,
851    'meta-spec'           => \&_change_meta_spec,
852    'name'                => \&_keep,
853    'version'             => \&_keep,
854    # PRIOR OPTIONAL
855    'build_requires'      => \&_version_map,
856    'conflicts'           => \&_version_map,
857    'distribution_type'   => \&_keep,
858    'dynamic_config'      => \&_keep_or_one,
859    'keywords'            => \&_keep,
860    'no_index'            => \&_no_index_directory,
861    'optional_features'   => \&_optional_features_as_map,
862    'provides'            => \&_provides,
863    'recommends'          => \&_version_map,
864    'requires'            => \&_version_map,
865    'resources'           => \&_resources_1_3,
866
867    # drop these deprecated fields, but only after we convert
868    ':drop' => [ qw(
869      license_url
870      private
871    )],
872
873    # other random keys are OK if already valid
874    ':custom'              => \&_keep
875  },
876  '1.2-from-1.1' => {
877    # PRIOR MANDATORY
878    'version'             => \&_keep,
879    # CHANGED TO MANDATORY
880    'license'             => \&_license_1,
881    'name'                => \&_keep,
882    'generated_by'        => \&_generated_by,
883    # ADDED MANDATORY
884    'abstract'            => \&_keep_or_unknown,
885    'author'              => \&_author_list,
886    'meta-spec'           => \&_change_meta_spec,
887    # PRIOR OPTIONAL
888    'build_requires'      => \&_version_map,
889    'conflicts'           => \&_version_map,
890    'distribution_type'   => \&_keep,
891    'dynamic_config'      => \&_keep_or_one,
892    'recommends'          => \&_version_map,
893    'requires'            => \&_version_map,
894    # ADDED OPTIONAL
895    'keywords'            => \&_keep,
896    'no_index'            => \&_no_index_1_2,
897    'optional_features'   => \&_optional_features_as_map,
898    'provides'            => \&_provides,
899    'resources'           => \&_resources_1_2,
900
901    # drop these deprecated fields, but only after we convert
902    ':drop' => [ qw(
903      license_url
904      private
905    )],
906
907    # other random keys are OK if already valid
908    ':custom'              => \&_keep
909  },
910  '1.1-from-1.0' => {
911    # CHANGED TO MANDATORY
912    'version'             => \&_keep,
913    # IMPLIED MANDATORY
914    'name'                => \&_keep,
915    # PRIOR OPTIONAL
916    'build_requires'      => \&_version_map,
917    'conflicts'           => \&_version_map,
918    'distribution_type'   => \&_keep,
919    'dynamic_config'      => \&_keep_or_one,
920    'generated_by'        => \&_generated_by,
921    'license'             => \&_license_1,
922    'recommends'          => \&_version_map,
923    'requires'            => \&_version_map,
924    # ADDED OPTIONAL
925    'license_url'         => \&_url_or_drop,
926    'private'             => \&_keep,
927
928    # other random keys are OK if already valid
929    ':custom'              => \&_keep
930  },
931);
932
933my %down_convert = (
934  '1.4-from-2' => {
935    # MANDATORY
936    'abstract'            => \&_keep_or_unknown,
937    'author'              => \&_author_list,
938    'generated_by'        => \&_generated_by,
939    'license'             => \&_downgrade_license,
940    'meta-spec'           => \&_change_meta_spec,
941    'name'                => \&_keep,
942    'version'             => \&_keep,
943    # OPTIONAL
944    'build_requires'      => \&_get_build_requires,
945    'configure_requires'  => \&_get_configure_requires,
946    'conflicts'           => \&_get_conflicts,
947    'distribution_type'   => \&_keep,
948    'dynamic_config'      => \&_keep_or_one,
949    'keywords'            => \&_keep,
950    'no_index'            => \&_no_index_directory,
951    'optional_features'   => \&_downgrade_optional_features,
952    'provides'            => \&_provides,
953    'recommends'          => \&_get_recommends,
954    'requires'            => \&_get_requires,
955    'resources'           => \&_downgrade_resources,
956
957    # drop these unsupported fields (after conversion)
958    ':drop' => [ qw(
959      description
960      prereqs
961      release_status
962    )],
963
964    # custom keys will be left unchanged
965    ':custom'              => \&_keep
966  },
967  '1.3-from-1.4' => {
968    # MANDATORY
969    'abstract'            => \&_keep_or_unknown,
970    'author'              => \&_author_list,
971    'generated_by'        => \&_generated_by,
972    'license'             => \&_license_1,
973    'meta-spec'           => \&_change_meta_spec,
974    'name'                => \&_keep,
975    'version'             => \&_keep,
976    # OPTIONAL
977    'build_requires'      => \&_version_map,
978    'conflicts'           => \&_version_map,
979    'distribution_type'   => \&_keep,
980    'dynamic_config'      => \&_keep_or_one,
981    'keywords'            => \&_keep,
982    'no_index'            => \&_no_index_directory,
983    'optional_features'   => \&_optional_features_as_map,
984    'provides'            => \&_provides,
985    'recommends'          => \&_version_map,
986    'requires'            => \&_version_map,
987    'resources'           => \&_resources_1_3,
988
989    # drop these unsupported fields, but only after we convert
990    ':drop' => [ qw(
991      configure_requires
992    )],
993
994    # other random keys are OK if already valid
995    ':custom'              => \&_keep,
996  },
997  '1.2-from-1.3' => {
998    # MANDATORY
999    'abstract'            => \&_keep_or_unknown,
1000    'author'              => \&_author_list,
1001    'generated_by'        => \&_generated_by,
1002    'license'             => \&_license_1,
1003    'meta-spec'           => \&_change_meta_spec,
1004    'name'                => \&_keep,
1005    'version'             => \&_keep,
1006    # OPTIONAL
1007    'build_requires'      => \&_version_map,
1008    'conflicts'           => \&_version_map,
1009    'distribution_type'   => \&_keep,
1010    'dynamic_config'      => \&_keep_or_one,
1011    'keywords'            => \&_keep,
1012    'no_index'            => \&_no_index_1_2,
1013    'optional_features'   => \&_optional_features_as_map,
1014    'provides'            => \&_provides,
1015    'recommends'          => \&_version_map,
1016    'requires'            => \&_version_map,
1017    'resources'           => \&_resources_1_3,
1018
1019    # other random keys are OK if already valid
1020    ':custom'              => \&_keep,
1021  },
1022  '1.1-from-1.2' => {
1023    # MANDATORY
1024    'version'             => \&_keep,
1025    # IMPLIED MANDATORY
1026    'name'                => \&_keep,
1027    'meta-spec'           => \&_change_meta_spec,
1028    # OPTIONAL
1029    'build_requires'      => \&_version_map,
1030    'conflicts'           => \&_version_map,
1031    'distribution_type'   => \&_keep,
1032    'dynamic_config'      => \&_keep_or_one,
1033    'generated_by'        => \&_generated_by,
1034    'license'             => \&_license_1,
1035    'private'             => \&_keep,
1036    'recommends'          => \&_version_map,
1037    'requires'            => \&_version_map,
1038
1039    # drop unsupported fields
1040    ':drop' => [ qw(
1041      abstract
1042      author
1043      provides
1044      no_index
1045      keywords
1046      resources
1047    )],
1048
1049    # other random keys are OK if already valid
1050    ':custom'              => \&_keep,
1051  },
1052  '1.0-from-1.1' => {
1053    # IMPLIED MANDATORY
1054    'name'                => \&_keep,
1055    'meta-spec'           => \&_change_meta_spec,
1056    'version'             => \&_keep,
1057    # PRIOR OPTIONAL
1058    'build_requires'      => \&_version_map,
1059    'conflicts'           => \&_version_map,
1060    'distribution_type'   => \&_keep,
1061    'dynamic_config'      => \&_keep_or_one,
1062    'generated_by'        => \&_generated_by,
1063    'license'             => \&_license_1,
1064    'recommends'          => \&_version_map,
1065    'requires'            => \&_version_map,
1066
1067    # other random keys are OK if already valid
1068    ':custom'              => \&_keep,
1069  },
1070);
1071
1072my %cleanup = (
1073  '2' => {
1074    # PRIOR MANDATORY
1075    'abstract'            => \&_keep_or_unknown,
1076    'author'              => \&_author_list,
1077    'generated_by'        => \&_generated_by,
1078    'license'             => \&_license_2,
1079    'meta-spec'           => \&_change_meta_spec,
1080    'name'                => \&_keep,
1081    'version'             => \&_keep,
1082    # CHANGED TO MANDATORY
1083    'dynamic_config'      => \&_keep_or_one,
1084    # ADDED MANDATORY
1085    'release_status'      => \&_release_status,
1086    # PRIOR OPTIONAL
1087    'keywords'            => \&_keep,
1088    'no_index'            => \&_no_index_directory,
1089    'optional_features'   => \&_cleanup_optional_features_2,
1090    'provides'            => \&_provides,
1091    'resources'           => \&_cleanup_resources_2,
1092    # ADDED OPTIONAL
1093    'description'         => \&_keep,
1094    'prereqs'             => \&_cleanup_prereqs,
1095
1096    # drop these deprecated fields, but only after we convert
1097    ':drop' => [ qw(
1098        build_requires
1099        configure_requires
1100        conflicts
1101        distribution_type
1102        license_url
1103        private
1104        recommends
1105        requires
1106    ) ],
1107
1108    # other random keys need x_ prefixing
1109    ':custom'              => \&_prefix_custom,
1110  },
1111  '1.4' => {
1112    # PRIOR MANDATORY
1113    'abstract'            => \&_keep_or_unknown,
1114    'author'              => \&_author_list,
1115    'generated_by'        => \&_generated_by,
1116    'license'             => \&_license_1,
1117    'meta-spec'           => \&_change_meta_spec,
1118    'name'                => \&_keep,
1119    'version'             => \&_keep,
1120    # PRIOR OPTIONAL
1121    'build_requires'      => \&_version_map,
1122    'conflicts'           => \&_version_map,
1123    'distribution_type'   => \&_keep,
1124    'dynamic_config'      => \&_keep_or_one,
1125    'keywords'            => \&_keep,
1126    'no_index'            => \&_no_index_directory,
1127    'optional_features'   => \&_optional_features_1_4,
1128    'provides'            => \&_provides,
1129    'recommends'          => \&_version_map,
1130    'requires'            => \&_version_map,
1131    'resources'           => \&_resources_1_4,
1132    # ADDED OPTIONAL
1133    'configure_requires'  => \&_keep,
1134
1135    # other random keys are OK if already valid
1136    ':custom'             => \&_keep
1137  },
1138  '1.3' => {
1139    # PRIOR MANDATORY
1140    'abstract'            => \&_keep_or_unknown,
1141    'author'              => \&_author_list,
1142    'generated_by'        => \&_generated_by,
1143    'license'             => \&_license_1,
1144    'meta-spec'           => \&_change_meta_spec,
1145    'name'                => \&_keep,
1146    'version'             => \&_keep,
1147    # PRIOR OPTIONAL
1148    'build_requires'      => \&_version_map,
1149    'conflicts'           => \&_version_map,
1150    'distribution_type'   => \&_keep,
1151    'dynamic_config'      => \&_keep_or_one,
1152    'keywords'            => \&_keep,
1153    'no_index'            => \&_no_index_directory,
1154    'optional_features'   => \&_optional_features_as_map,
1155    'provides'            => \&_provides,
1156    'recommends'          => \&_version_map,
1157    'requires'            => \&_version_map,
1158    'resources'           => \&_resources_1_3,
1159
1160    # other random keys are OK if already valid
1161    ':custom'             => \&_keep
1162  },
1163  '1.2' => {
1164    # PRIOR MANDATORY
1165    'version'             => \&_keep,
1166    # CHANGED TO MANDATORY
1167    'license'             => \&_license_1,
1168    'name'                => \&_keep,
1169    'generated_by'        => \&_generated_by,
1170    # ADDED MANDATORY
1171    'abstract'            => \&_keep_or_unknown,
1172    'author'              => \&_author_list,
1173    'meta-spec'           => \&_change_meta_spec,
1174    # PRIOR OPTIONAL
1175    'build_requires'      => \&_version_map,
1176    'conflicts'           => \&_version_map,
1177    'distribution_type'   => \&_keep,
1178    'dynamic_config'      => \&_keep_or_one,
1179    'recommends'          => \&_version_map,
1180    'requires'            => \&_version_map,
1181    # ADDED OPTIONAL
1182    'keywords'            => \&_keep,
1183    'no_index'            => \&_no_index_1_2,
1184    'optional_features'   => \&_optional_features_as_map,
1185    'provides'            => \&_provides,
1186    'resources'           => \&_resources_1_2,
1187
1188    # other random keys are OK if already valid
1189    ':custom'             => \&_keep
1190  },
1191  '1.1' => {
1192    # CHANGED TO MANDATORY
1193    'version'             => \&_keep,
1194    # IMPLIED MANDATORY
1195    'name'                => \&_keep,
1196    'meta-spec'           => \&_change_meta_spec,
1197    # PRIOR OPTIONAL
1198    'build_requires'      => \&_version_map,
1199    'conflicts'           => \&_version_map,
1200    'distribution_type'   => \&_keep,
1201    'dynamic_config'      => \&_keep_or_one,
1202    'generated_by'        => \&_generated_by,
1203    'license'             => \&_license_1,
1204    'recommends'          => \&_version_map,
1205    'requires'            => \&_version_map,
1206    # ADDED OPTIONAL
1207    'license_url'         => \&_url_or_drop,
1208    'private'             => \&_keep,
1209
1210    # other random keys are OK if already valid
1211    ':custom'             => \&_keep
1212  },
1213  '1.0' => {
1214    # IMPLIED MANDATORY
1215    'name'                => \&_keep,
1216    'meta-spec'           => \&_change_meta_spec,
1217    'version'             => \&_keep,
1218    # IMPLIED OPTIONAL
1219    'build_requires'      => \&_version_map,
1220    'conflicts'           => \&_version_map,
1221    'distribution_type'   => \&_keep,
1222    'dynamic_config'      => \&_keep_or_one,
1223    'generated_by'        => \&_generated_by,
1224    'license'             => \&_license_1,
1225    'recommends'          => \&_version_map,
1226    'requires'            => \&_version_map,
1227
1228    # other random keys are OK if already valid
1229    ':custom'             => \&_keep,
1230  },
1231);
1232
1233#--------------------------------------------------------------------------#
1234# Code
1235#--------------------------------------------------------------------------#
1236
1237# =method new
1238#
1239#   my $cmc = CPAN::Meta::Converter->new( $struct );
1240#
1241# The constructor should be passed a valid metadata structure but invalid
1242# structures are accepted.  If no meta-spec version is provided, version 1.0 will
1243# be assumed.
1244#
1245# =cut
1246
1247sub new {
1248  my ($class,$data) = @_;
1249
1250  # create an attributes hash
1251  my $self = {
1252    'data'    => $data,
1253    'spec'    => _extract_spec_version($data),
1254  };
1255
1256  # create the object
1257  return bless $self, $class;
1258}
1259
1260sub _extract_spec_version {
1261    my ($data) = @_;
1262    my $spec = $data->{'meta-spec'};
1263
1264    # is meta-spec there and valid?
1265    return "1.0" unless defined $spec && ref $spec eq 'HASH'; # before meta-spec?
1266
1267    # does the version key look like a valid version?
1268    my $v = $spec->{version};
1269    if ( defined $v && $v =~ /^\d+(?:\.\d+)?$/ ) {
1270        return $v if defined $v && grep { $v eq $_ } keys %known_specs; # known spec
1271        return $v+0 if defined $v && grep { $v == $_ } keys %known_specs; # 2.0 => 2
1272    }
1273
1274    # otherwise, use heuristics: look for 1.x vs 2.0 fields
1275    return "2" if exists $data->{prereqs};
1276    return "1.4" if exists $data->{configure_requires};
1277    return "1.2"; # when meta-spec was first defined
1278}
1279
1280# =method convert
1281#
1282#   my $new_struct = $cmc->convert( version => "2" );
1283#
1284# Returns a new hash reference with the metadata converted to a different form.
1285# C<convert> will die if any conversion/standardization still results in an
1286# invalid structure.
1287#
1288# Valid parameters include:
1289#
1290# =over
1291#
1292# =item *
1293#
1294# C<version> -- Indicates the desired specification version (e.g. "1.0", "1.1" ... "1.4", "2").
1295# Defaults to the latest version of the CPAN Meta Spec.
1296#
1297# =back
1298#
1299# Conversion proceeds through each version in turn.  For example, a version 1.2
1300# structure might be converted to 1.3 then 1.4 then finally to version 2. The
1301# conversion process attempts to clean-up simple errors and standardize data.
1302# For example, if C<author> is given as a scalar, it will converted to an array
1303# reference containing the item. (Converting a structure to its own version will
1304# also clean-up and standardize.)
1305#
1306# When data are cleaned and standardized, missing or invalid fields will be
1307# replaced with sensible defaults when possible.  This may be lossy or imprecise.
1308# For example, some badly structured META.yml files on CPAN have prerequisite
1309# modules listed as both keys and values:
1310#
1311#   requires => { 'Foo::Bar' => 'Bam::Baz' }
1312#
1313# These would be split and each converted to a prerequisite with a minimum
1314# version of zero.
1315#
1316# When some mandatory fields are missing or invalid, the conversion will attempt
1317# to provide a sensible default or will fill them with a value of 'unknown'.  For
1318# example a missing or unrecognized C<license> field will result in a C<license>
1319# field of 'unknown'.  Fields that may get an 'unknown' include:
1320#
1321# =for :list
1322# * abstract
1323# * author
1324# * license
1325#
1326# =cut
1327
1328sub convert {
1329  my ($self, %args) = @_;
1330  my $args = { %args };
1331
1332  my $new_version = $args->{version} || $HIGHEST;
1333
1334  my ($old_version) = $self->{spec};
1335  my $converted = _dclone($self->{data});
1336
1337  if ( $old_version == $new_version ) {
1338    $converted = _convert( $converted, $cleanup{$old_version}, $old_version );
1339    my $cmv = CPAN::Meta::Validator->new( $converted );
1340    unless ( $cmv->is_valid ) {
1341      my $errs = join("\n", $cmv->errors);
1342      die "Failed to clean-up $old_version metadata. Errors:\n$errs\n";
1343    }
1344    return $converted;
1345  }
1346  elsif ( $old_version > $new_version )  {
1347    my @vers = sort { $b <=> $a } keys %known_specs;
1348    for my $i ( 0 .. $#vers-1 ) {
1349      next if $vers[$i] > $old_version;
1350      last if $vers[$i+1] < $new_version;
1351      my $spec_string = "$vers[$i+1]-from-$vers[$i]";
1352      $converted = _convert( $converted, $down_convert{$spec_string}, $vers[$i+1] );
1353      my $cmv = CPAN::Meta::Validator->new( $converted );
1354      unless ( $cmv->is_valid ) {
1355        my $errs = join("\n", $cmv->errors);
1356        die "Failed to downconvert metadata to $vers[$i+1]. Errors:\n$errs\n";
1357      }
1358    }
1359    return $converted;
1360  }
1361  else {
1362    my @vers = sort { $a <=> $b } keys %known_specs;
1363    for my $i ( 0 .. $#vers-1 ) {
1364      next if $vers[$i] < $old_version;
1365      last if $vers[$i+1] > $new_version;
1366      my $spec_string = "$vers[$i+1]-from-$vers[$i]";
1367      $converted = _convert( $converted, $up_convert{$spec_string}, $vers[$i+1] );
1368      my $cmv = CPAN::Meta::Validator->new( $converted );
1369      unless ( $cmv->is_valid ) {
1370        my $errs = join("\n", $cmv->errors);
1371        die "Failed to upconvert metadata to $vers[$i+1]. Errors:\n$errs\n";
1372      }
1373    }
1374    return $converted;
1375  }
1376}
1377
13781;
1379
1380# ABSTRACT: Convert CPAN distribution metadata structures
1381
1382__END__
1383
1384=pod
1385
1386=encoding UTF-8
1387
1388=head1 NAME
1389
1390CPAN::Meta::Converter - Convert CPAN distribution metadata structures
1391
1392=head1 VERSION
1393
1394version 2.140640
1395
1396=head1 SYNOPSIS
1397
1398  my $struct = decode_json_file('META.json');
1399
1400  my $cmc = CPAN::Meta::Converter->new( $struct );
1401
1402  my $new_struct = $cmc->convert( version => "2" );
1403
1404=head1 DESCRIPTION
1405
1406This module converts CPAN Meta structures from one form to another.  The
1407primary use is to convert older structures to the most modern version of
1408the specification, but other transformations may be implemented in the
1409future as needed.  (E.g. stripping all custom fields or stripping all
1410optional fields.)
1411
1412=head1 METHODS
1413
1414=head2 new
1415
1416  my $cmc = CPAN::Meta::Converter->new( $struct );
1417
1418The constructor should be passed a valid metadata structure but invalid
1419structures are accepted.  If no meta-spec version is provided, version 1.0 will
1420be assumed.
1421
1422=head2 convert
1423
1424  my $new_struct = $cmc->convert( version => "2" );
1425
1426Returns a new hash reference with the metadata converted to a different form.
1427C<convert> will die if any conversion/standardization still results in an
1428invalid structure.
1429
1430Valid parameters include:
1431
1432=over
1433
1434=item *
1435
1436C<version> -- Indicates the desired specification version (e.g. "1.0", "1.1" ... "1.4", "2").
1437Defaults to the latest version of the CPAN Meta Spec.
1438
1439=back
1440
1441Conversion proceeds through each version in turn.  For example, a version 1.2
1442structure might be converted to 1.3 then 1.4 then finally to version 2. The
1443conversion process attempts to clean-up simple errors and standardize data.
1444For example, if C<author> is given as a scalar, it will converted to an array
1445reference containing the item. (Converting a structure to its own version will
1446also clean-up and standardize.)
1447
1448When data are cleaned and standardized, missing or invalid fields will be
1449replaced with sensible defaults when possible.  This may be lossy or imprecise.
1450For example, some badly structured META.yml files on CPAN have prerequisite
1451modules listed as both keys and values:
1452
1453  requires => { 'Foo::Bar' => 'Bam::Baz' }
1454
1455These would be split and each converted to a prerequisite with a minimum
1456version of zero.
1457
1458When some mandatory fields are missing or invalid, the conversion will attempt
1459to provide a sensible default or will fill them with a value of 'unknown'.  For
1460example a missing or unrecognized C<license> field will result in a C<license>
1461field of 'unknown'.  Fields that may get an 'unknown' include:
1462
1463=over 4
1464
1465=item *
1466
1467abstract
1468
1469=item *
1470
1471author
1472
1473=item *
1474
1475license
1476
1477=back
1478
1479=head1 BUGS
1480
1481Please report any bugs or feature using the CPAN Request Tracker.
1482Bugs can be submitted through the web interface at
1483L<http://rt.cpan.org/Dist/Display.html?Queue=CPAN-Meta>
1484
1485When submitting a bug or request, please include a test-file or a patch to an
1486existing test-file that illustrates the bug or desired feature.
1487
1488=head1 AUTHORS
1489
1490=over 4
1491
1492=item *
1493
1494David Golden <dagolden@cpan.org>
1495
1496=item *
1497
1498Ricardo Signes <rjbs@cpan.org>
1499
1500=back
1501
1502=head1 COPYRIGHT AND LICENSE
1503
1504This software is copyright (c) 2010 by David Golden and Ricardo Signes.
1505
1506This is free software; you can redistribute it and/or modify it under
1507the same terms as the Perl 5 programming language system itself.
1508
1509=cut
1510