CPAN-Meta

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

  - Metadata merging now does deep hash merging as long as keys
    don't conflict

2.150001  2015-03-09 14:41:39-04:00 America/New_York

  [DOCUMENTED]

  - Include allowed values for license field in 1.x historic licenses rather
    than linking to Module::Build

  - Documented when fragment merging became available

2.143240  2014-11-20 10:26:30-05:00 America/New_York

  [FIXED]

  - Give correct path in nested merges such as resources

  - Removed strings test that should have been removed when
    CPAN::Meta::Requirements was removed to a separate dist

Changes  view on Meta::CPAN

2.141520  2014-05-31 23:41:13-04:00 America/New_York

  [DOCUMENTED]

  - Clarified use of 'file' for the 'provides' field in the Spec

2.141170  2014-04-27 13:03:37-04:00 America/New_York

  [ADDED]

  - Added ability for CPAN::Meta::Converter to convert metadata fragments
    (incomplete portions of a metadata structure)

  [CHANGED]

  - Optimized internal use of JSON for datastructure cloning

  [FIXED]

  - Removed dependency on List::Util 1.33

MANIFEST  view on Meta::CPAN

lib/CPAN/Meta/Prereqs.pm
lib/CPAN/Meta/Spec.pm
lib/CPAN/Meta/Validator.pm
lib/Parse/CPAN/Meta.pm
perlcritic.rc
t/00-report-prereqs.dd
t/00-report-prereqs.t
t/README-data.txt
t/converter-bad.t
t/converter-fail.t
t/converter-fragments.t
t/converter.t
t/data-fail/META-1_0.yml
t/data-fail/META-1_1.yml
t/data-fail/META-1_2.yml
t/data-fail/META-1_3.yml
t/data-fail/META-1_4.yml
t/data-fail/META-2.json
t/data-fixable/107650337-META.yml
t/data-fixable/1598804075-META.yml
t/data-fixable/1927486199-META.yml

lib/CPAN/Meta/Converter.pm  view on Meta::CPAN

  my $new_data = {};
  for my $k ( keys %$element ) {
    $new_data->{$k} = _convert($element->{$k}, $spec, $to_version);
    $new_data->{$k}{version} = _clean_version($element->{$k}{version})
      if exists $element->{$k}{version};
  }
  return $new_data;
}

sub _convert {
  my ($data, $spec, $to_version, $is_fragment) = @_;

  my $new_data = {};
  for my $key ( keys %$spec ) {
    next if $key eq ':custom' || $key eq ':drop';
    next unless my $fcn = $spec->{$key};
    if ( $is_fragment && $key eq 'generated_by' ) {
      $fcn = \&_keep;
    }
    die "spec for '$key' is not a coderef"
      unless ref $fcn && ref $fcn eq 'CODE';
    my $new_value = $fcn->($data->{$key}, $key, $data, $to_version);
    $new_data->{$key} = $new_value if defined $new_value;
  }

  my $drop_list   = $spec->{':drop'};
  my $customizer  = $spec->{':custom'} || \&_keep;

lib/CPAN/Meta/Converter.pm  view on Meta::CPAN

    'requires'            => \&_version_map,

    # other random keys are OK if already valid
    ':custom'             => \&_keep,
  },
);

# for a given field in a spec version, what fields will it feed
# into in the *latest* spec (i.e. v2); meta-spec omitted because
# we always expect a meta-spec to be generated
my %fragments_generate = (
  '2' => {
    'abstract'            =>   'abstract',
    'author'              =>   'author',
    'generated_by'        =>   'generated_by',
    'license'             =>   'license',
    'name'                =>   'name',
    'version'             =>   'version',
    'dynamic_config'      =>   'dynamic_config',
    'release_status'      =>   'release_status',
    'keywords'            =>   'keywords',

lib/CPAN/Meta/Converter.pm  view on Meta::CPAN

    'optional_features'   => 'optional_features',
    'provides'            => 'provides',
    'recommends'          => 'prereqs',
    'requires'            => 'prereqs',
    'resources'           => 'resources',
    'configure_requires'  => 'prereqs',
  },
);
# this is not quite true but will work well enough
# as 1.4 is a superset of earlier ones
$fragments_generate{$_} = $fragments_generate{'1.4'} for qw/1.3 1.2 1.1 1.0/;

#--------------------------------------------------------------------------#
# Code
#--------------------------------------------------------------------------#

#pod =method new
#pod
#pod   my $cmc = CPAN::Meta::Converter->new( $struct );
#pod
#pod The constructor should be passed a valid metadata structure but invalid
#pod structures are accepted.  If no meta-spec version is provided, version 1.0 will
#pod be assumed.
#pod
#pod Optionally, you can provide a C<default_version> argument after C<$struct>:
#pod
#pod   my $cmc = CPAN::Meta::Converter->new( $struct, default_version => "1.4" );
#pod
#pod This is only needed when converting a metadata fragment that does not include a
#pod C<meta-spec> field.
#pod
#pod =cut

sub new {
  my ($class,$data,%args) = @_;

  # create an attributes hash
  my $self = {
    'data'    => $data,

lib/CPAN/Meta/Converter.pm  view on Meta::CPAN

#pod * author
#pod * license
#pod
#pod =cut

sub convert {
  my ($self, %args) = @_;
  my $args = { %args };

  my $new_version = $args->{version} || $HIGHEST;
  my $is_fragment = $args->{is_fragment};

  my ($old_version) = $self->{spec};
  my $converted = _dclone($self->{data});

  if ( $old_version == $new_version ) {
    $converted = _convert( $converted, $cleanup{$old_version}, $old_version, $is_fragment );
    unless ( $args->{is_fragment} ) {
      my $cmv = CPAN::Meta::Validator->new( $converted );
      unless ( $cmv->is_valid ) {
        my $errs = join("\n", $cmv->errors);
        die "Failed to clean-up $old_version metadata. Errors:\n$errs\n";
      }
    }
    return $converted;
  }
  elsif ( $old_version > $new_version )  {
    my @vers = sort { $b <=> $a } keys %known_specs;
    for my $i ( 0 .. $#vers-1 ) {
      next if $vers[$i] > $old_version;
      last if $vers[$i+1] < $new_version;
      my $spec_string = "$vers[$i+1]-from-$vers[$i]";
      $converted = _convert( $converted, $down_convert{$spec_string}, $vers[$i+1], $is_fragment );
      unless ( $args->{is_fragment} ) {
        my $cmv = CPAN::Meta::Validator->new( $converted );
        unless ( $cmv->is_valid ) {
          my $errs = join("\n", $cmv->errors);
          die "Failed to downconvert metadata to $vers[$i+1]. Errors:\n$errs\n";
        }
      }
    }
    return $converted;
  }
  else {
    my @vers = sort { $a <=> $b } keys %known_specs;
    for my $i ( 0 .. $#vers-1 ) {
      next if $vers[$i] < $old_version;
      last if $vers[$i+1] > $new_version;
      my $spec_string = "$vers[$i+1]-from-$vers[$i]";
      $converted = _convert( $converted, $up_convert{$spec_string}, $vers[$i+1], $is_fragment );
      unless ( $args->{is_fragment} ) {
        my $cmv = CPAN::Meta::Validator->new( $converted );
        unless ( $cmv->is_valid ) {
          my $errs = join("\n", $cmv->errors);
          die "Failed to upconvert metadata to $vers[$i+1]. Errors:\n$errs\n";
        }
      }
    }
    return $converted;
  }
}

#pod =method upgrade_fragment
#pod
#pod   my $new_struct = $cmc->upgrade_fragment;
#pod
#pod Returns a new hash reference with the metadata converted to the latest version
#pod of the CPAN Meta Spec.  No validation is done on the result -- you must
#pod validate after merging fragments into a complete metadata document.
#pod
#pod Available since version 2.141170.
#pod
#pod =cut

sub upgrade_fragment {
  my ($self) = @_;
  my ($old_version) = $self->{spec};
  my %expected =
    map {; $_ => 1 }
    grep { defined }
    map { $fragments_generate{$old_version}{$_} }
    keys %{ $self->{data} };
  my $converted = $self->convert( version => $HIGHEST, is_fragment => 1 );
  for my $key ( keys %$converted ) {
    next if $key =~ /^x_/i || $key eq 'meta-spec';
    delete $converted->{$key} unless $expected{$key};
  }
  return $converted;
}

1;

# ABSTRACT: Convert CPAN distribution metadata structures

lib/CPAN/Meta/Converter.pm  view on Meta::CPAN

  my $cmc = CPAN::Meta::Converter->new( $struct );

The constructor should be passed a valid metadata structure but invalid
structures are accepted.  If no meta-spec version is provided, version 1.0 will
be assumed.

Optionally, you can provide a C<default_version> argument after C<$struct>:

  my $cmc = CPAN::Meta::Converter->new( $struct, default_version => "1.4" );

This is only needed when converting a metadata fragment that does not include a
C<meta-spec> field.

=head2 convert

  my $new_struct = $cmc->convert( version => "2" );

Returns a new hash reference with the metadata converted to a different form.
C<convert> will die if any conversion/standardization still results in an
invalid structure.

lib/CPAN/Meta/Converter.pm  view on Meta::CPAN

=item *

author

=item *

license

=back

=head2 upgrade_fragment

  my $new_struct = $cmc->upgrade_fragment;

Returns a new hash reference with the metadata converted to the latest version
of the CPAN Meta Spec.  No validation is done on the result -- you must
validate after merging fragments into a complete metadata document.

Available since version 2.141170.

=head1 BUGS

Please report any bugs or feature using the CPAN Request Tracker.
Bugs can be submitted through the web interface at
L<http://rt.cpan.org/Dist/Display.html?Queue=CPAN-Meta>

When submitting a bug or request, please include a test-file or a patch to an

lib/CPAN/Meta/Merge.pm  view on Meta::CPAN

  my ($self, @items) = @_;
  my $current = {};
  for my $next (@items) {
    if ( blessed($next) && $next->isa('CPAN::Meta') ) {
      $next = $next->as_struct;
    }
    elsif ( ref($next) eq 'HASH' ) {
      my $cmc = CPAN::Meta::Converter->new(
        $next, default_version => $self->{default_version}
      );
      $next = $cmc->upgrade_fragment;
    }
    else {
      croak "Don't know how to merge '$next'";
    }
    $current = _merge($current, $next, $self->{mapping}, []);
  }
  return $current;
}

1;

# ABSTRACT: Merging CPAN Meta fragments


# vim: ts=2 sts=2 sw=2 et :

__END__

=pod

=encoding UTF-8

=head1 NAME

CPAN::Meta::Merge - Merging CPAN Meta fragments

=head1 VERSION

version 2.150015

=head1 SYNOPSIS

 my $merger = CPAN::Meta::Merge->new(default_version => "2");
 my $meta = $merger->merge($base, @additional);

lib/CPAN/Meta/Merge.pm  view on Meta::CPAN

      extra_mappings => {
          'optional_features' => \&custom_merge_function,
          'x_custom' => 'set_addition',
          'x_meta_meta' => {
              name => 'identical',
              tags => 'set_addition',
          }
      }
  );

=head2 merge(@fragments)

Merge all C<@fragments> together. It will accept both CPAN::Meta objects and
(possibly incomplete) hashrefs of metadata.

=head1 MERGE STRATEGIES

C<merge> uses various strategies to combine different elements of the CPAN::Meta objects.  The following strategies can be used with the extra_mappings argument of C<new>:

=over

=item identical

lib/CPAN/Meta/Validator.pm  view on Meta::CPAN

}

# _uri_split taken from URI::Split by Gisle Aas, Copyright 2003
sub _uri_split {
     return $_[0] =~ m,(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?,;
}

sub url {
    my ($self,$key,$value) = @_;
    if(defined $value) {
      my ($scheme, $auth, $path, $query, $frag) = _uri_split($value);
      unless ( defined $scheme && length $scheme ) {
        $self->_error( "'$value' for '$key' does not have a URL scheme" );
        return 0;
      }
      unless ( defined $auth && length $auth ) {
        $self->_error( "'$value' for '$key' does not have a URL authority" );
        return 0;
      }
      return 1;
    }

t/converter-fragments.t  view on Meta::CPAN

            'meta-spec' => $spec2,
            license => [ 'perl_5' ],
        },
    },
);

for my $c (@cases) {
    my $cmc = CPAN::Meta::Converter->new(
        $c->{input}, $c->{from} ? (default_version => $c->{from} ) : ()
    );
    my $got = $cmc->upgrade_fragment;
    my $exp = $c->{expect};
    is_deeply( $got, $exp, $c->{label} )
      or diag "GOT:\n", explain($got), "EXPECTED:\n", explain($exp);
}

done_testing;
# vim: ts=8 sts=4 sw=4 et :

t/optional_feature-merge.t  view on Meta::CPAN

		Baz => {
			file => 'lib/Baz.pm',
		},
	},
	'meta-spec' => {
		url => "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
		version => 2,
	},
);

my $fragment1 = {
	'optional_features' => {
		'FeatureName' => {
			'description' => 'desc',
			'x_default' => 1,
			'prereqs' => { 'runtime' => { 'requires' => { 'A' => '0' } } }
		}
	}
};
my $fragment2 = {
	'optional_features' => {
		'FeatureName' => {
			'description' => 'desc',
			'prereqs' => { 'test' => { 'requires' => { 'B' => '0' } } }
		}
	}
};

my $merger = CPAN::Meta::Merge->new(default_version => "2");
my $meta1 = $merger->merge(\%base, $fragment1);

is_deeply(
	$meta1,
	{
		%base,
		%$fragment1,
	},
	'merged first optional_feature fragment into base',
);

my $meta2 = $merger->merge($meta1, $fragment2);

is_deeply(
	$meta2,
	{
		%base,
		'optional_features' => {
			'FeatureName' => {
				'description' => 'desc',
				'x_default' => 1,
				'prereqs' => {
					'runtime' => { 'requires' => { 'A' => '0' } },
					'test' => { 'requires' => { 'B' => '0' } },
				}
			}
		}
	},
	'merged second optional_feature fragment into the first',
);

my $fragment3 = {
	'optional_features' => {
		'FeatureName' => {
			'description' => 'other desc',
			'prereqs' => { 'test' => { 'requires' => { 'B' => '0' } } }
		}
	}
};

is( eval { $merger->merge($meta1, $fragment3) }, undef, 'Trying to merge optional_features with same feature name and different descriptions gives an exception');
like $@, qr/^Cannot merge two optional_features named 'FeatureName' with different 'description' values/, 'Exception looks right';

my $fragment4 = {
	'optional_features' => {
		'FeatureName' => {
			'description' => 'desc',
			'x_default' => 0,
			'prereqs' => { 'test' => { 'requires' => { 'B' => '0' } } }
		}
	}
};

is( eval { $merger->merge($meta1, $fragment4) }, undef, 'Trying to merge optional_features with same feature name and differences in other keys gives an exception');
like $@, qr/^Cannot merge two optional_features named 'FeatureName' with different 'x_default' values/, 'Exception looks right';

my $fragment5 = {
	'optional_features' => {
		'Another FeatureName' => {
			'description' => 'desc',
			'prereqs' => { 'test' => { 'requires' => { 'B' => '0' } } }
		}
	}
};

my $meta5 = $merger->merge($meta1, $fragment5);
is_deeply(
	$meta5,
	{
		%base,
		'optional_features' => {
			'FeatureName' => {
				'description' => 'desc',
				'x_default' => 1,
				'prereqs' => { 'runtime' => { 'requires' => { 'A' => '0' } } },
			},



( run in 1.289 second using v1.01-cache-2.11-cpan-364913b4093 )