File Coverage

blib/lib/CPAN/Meta/Merge.pm
Criterion Covered Total %
statement 99 120 82.5
branch 30 44 68.1
condition 5 9 55.5
subroutine 18 19 94.7
pod 2 2 100.0
total 154 194 79.3


line stmt bran cond sub pod time code
1 2     2   1401 use 5.008001;
  2         9  
2 2     2   31 use strict;
  2         5  
  2         67  
3 2     2   12 use warnings;
  2         4  
  2         263  
4              
5             package CPAN::Meta::Merge;
6              
7             our $VERSION = '2.150013';
8              
9 2     2   15 use Carp qw/croak/;
  2         3  
  2         219  
10 2     2   14 use Scalar::Util qw/blessed/;
  2         15  
  2         207  
11 2     2   23 use CPAN::Meta::Converter 2.141170;
  2         43  
  2         2315  
12              
13             sub _is_identical {
14 34     34   82 my ($left, $right) = @_;
15             return
16 34   33     273 (not defined $left and not defined $right)
17             # if either of these are references, we compare the serialized value
18             || (defined $left and defined $right and $left eq $right);
19             }
20              
21             sub _identical {
22 29     29   68 my ($left, $right, $path) = @_;
23 29 100       74 croak sprintf "Can't merge attribute %s: '%s' does not equal '%s'", join('.', @{$path}), $left, $right
  1         222  
24             unless _is_identical($left, $right);
25 28         127 return $left;
26             }
27              
28             sub _merge {
29 48     48   107 my ($current, $next, $mergers, $path) = @_;
30 48         82 for my $key (keys %{$next}) {
  48         134  
31 202 100       486 if (not exists $current->{$key}) {
    50          
    0          
32 131         272 $current->{$key} = $next->{$key};
33             }
34             elsif (my $merger = $mergers->{$key}) {
35 71         125 $current->{$key} = $merger->($current->{$key}, $next->{$key}, [ @{$path}, $key ]);
  71         203  
36             }
37             elsif ($merger = $mergers->{':default'}) {
38 0         0 $current->{$key} = $merger->($current->{$key}, $next->{$key}, [ @{$path}, $key ]);
  0         0  
39             }
40             else {
41 0         0 croak sprintf "Can't merge unknown attribute '%s'", join '.', @{$path}, $key;
  0         0  
42             }
43             }
44 41         139 return $current;
45             }
46              
47             sub _uniq {
48 10     10   19 my %seen = ();
49 10         35 return grep { not $seen{$_}++ } @_;
  20         106  
50             }
51              
52             sub _set_addition {
53 8     8   20 my ($left, $right) = @_;
54 8         14 return [ +_uniq(@{$left}, @{$right}) ];
  8         42  
  8         20  
55             }
56              
57             sub _uniq_map {
58 7     7   17 my ($left, $right, $path) = @_;
59 7         14 for my $key (keys %{$right}) {
  7         19  
60 8 100 66     25 if (not exists $left->{$key}) {
    100          
    100          
61 3         8 $left->{$key} = $right->{$key};
62             }
63             # identical strings or references are merged identically
64             elsif (_is_identical($left->{$key}, $right->{$key})) {
65 1         2 1; # do nothing - keep left
66             }
67             elsif (ref $left->{$key} eq 'HASH' and ref $right->{$key} eq 'HASH') {
68 2         6 $left->{$key} = _uniq_map($left->{$key}, $right->{$key}, [ @{$path}, $key ]);
  2         8  
69             }
70             else {
71 2         4 croak 'Duplication of element ' . join '.', @{$path}, $key;
  2         364  
72             }
73             }
74 4         15 return $left;
75             }
76              
77             sub _improvise {
78 0     0   0 my ($left, $right, $path) = @_;
79 0         0 my ($name) = reverse @{$path};
  0         0  
80 0 0       0 if ($name =~ /^x_/) {
81 0 0       0 if (ref($left) eq 'ARRAY') {
    0          
82 0         0 return _set_addition($left, $right, $path);
83             }
84             elsif (ref($left) eq 'HASH') {
85 0         0 return _uniq_map($left, $right, $path);
86             }
87             else {
88 0         0 return _identical($left, $right, $path);
89             }
90             }
91 0         0 croak sprintf "Can't merge '%s'", join '.', @{$path};
  0         0  
92             }
93              
94             sub _optional_features {
95 4     4   10 my ($left, $right, $path) = @_;
96              
97 4         6 for my $key (keys %{$right}) {
  4         6  
98 4 100       11 if (not exists $left->{$key}) {
99 1         3 $left->{$key} = $right->{$key};
100             }
101             else {
102 3         3 for my $subkey (keys %{ $right->{$key} }) {
  3         9  
103 4 100       10 next if $subkey eq 'prereqs';
104 3 50       7 if (not exists $left->{$key}{$subkey}) {
105 0         0 $left->{$key}{$subkey} = $right->{$key}{$subkey};
106             }
107             else {
108             Carp::croak "Cannot merge two optional_features named '$key' with different '$subkey' values"
109 2 100   2   17 if do { no warnings 'uninitialized'; $left->{$key}{$subkey} ne $right->{$key}{$subkey} };
  2         5  
  2         3445  
  3         4  
  3         343  
110             }
111             }
112              
113 1         12 require CPAN::Meta::Prereqs;
114             $left->{$key}{prereqs} =
115             CPAN::Meta::Prereqs->new($left->{$key}{prereqs})
116 1         14 ->with_merged_prereqs(CPAN::Meta::Prereqs->new($right->{$key}{prereqs}))
117             ->as_string_hash;
118             }
119             }
120 2         8 return $left;
121             }
122              
123              
124             my %default = (
125             abstract => \&_identical,
126             author => \&_set_addition,
127             dynamic_config => sub {
128             my ($left, $right) = @_;
129             return $left || $right;
130             },
131             generated_by => sub {
132             my ($left, $right) = @_;
133             return join ', ', _uniq(split(/, /, $left), split(/, /, $right));
134             },
135             license => \&_set_addition,
136             'meta-spec' => {
137             version => \&_identical,
138             url => \&_identical
139             },
140             name => \&_identical,
141             release_status => \&_identical,
142             version => \&_identical,
143             description => \&_identical,
144             keywords => \&_set_addition,
145             no_index => { map { ($_ => \&_set_addition) } qw/file directory package namespace/ },
146             optional_features => \&_optional_features,
147             prereqs => sub {
148             require CPAN::Meta::Prereqs;
149             my ($left, $right) = map { CPAN::Meta::Prereqs->new($_) } @_[0,1];
150             return $left->with_merged_prereqs($right)->as_string_hash;
151             },
152             provides => \&_uniq_map,
153             resources => {
154             license => \&_set_addition,
155             homepage => \&_identical,
156             bugtracker => \&_uniq_map,
157             repository => \&_uniq_map,
158             ':default' => \&_improvise,
159             },
160             ':default' => \&_improvise,
161             );
162              
163             sub new {
164 4     4 1 6398 my ($class, %arguments) = @_;
165 4 50       22 croak 'default version required' if not exists $arguments{default_version};
166 4         70 my %mapping = %default;
167 4 100       15 my %extra = %{ $arguments{extra_mappings} || {} };
  4         30  
168 4         18 for my $key (keys %extra) {
169 3 50       13 if (ref($mapping{$key}) eq 'HASH') {
170 0         0 $mapping{$key} = { %{ $mapping{$key} }, %{ $extra{$key} } };
  0         0  
  0         0  
171             }
172             else {
173 3         10 $mapping{$key} = $extra{$key};
174             }
175             }
176             return bless {
177             default_version => $arguments{default_version},
178 4         19 mapping => _coerce_mapping(\%mapping, []),
179             }, $class;
180             }
181              
182             my %coderef_for = (
183             set_addition => \&_set_addition,
184             uniq_map => \&_uniq_map,
185             identical => \&_identical,
186             improvise => \&_improvise,
187             improvize => \&_improvise, # [sic] for backwards compatibility
188             );
189              
190             sub _coerce_mapping {
191 17     17   40 my ($orig, $map_path) = @_;
192 17         30 my %ret;
193 17         30 for my $key (keys %{$orig}) {
  17         84  
194 116         217 my $value = $orig->{$key};
195 116 100       273 if (ref($orig->{$key}) eq 'CODE') {
    100          
    50          
196 100         210 $ret{$key} = $value;
197             }
198             elsif (ref($value) eq 'HASH') {
199 13         19 my $mapping = _coerce_mapping($value, [ @{$map_path}, $key ]);
  13         46  
200             $ret{$key} = sub {
201 17     17   40 my ($left, $right, $path) = @_;
202 17         33 return _merge($left, $right, $mapping, [ @{$path} ]);
  17         48  
203 13         74 };
204             }
205             elsif ($coderef_for{$value}) {
206 3         10 $ret{$key} = $coderef_for{$value};
207             }
208             else {
209 0         0 croak "Don't know what to do with " . join '.', @{$map_path}, $key;
  0         0  
210             }
211             }
212 17         130 return \%ret;
213             }
214              
215             sub merge {
216 15     15 1 15547 my ($self, @items) = @_;
217 15         32 my $current = {};
218 15         32 for my $next (@items) {
219 31 100 66     182 if ( blessed($next) && $next->isa('CPAN::Meta') ) {
    50          
220 1         9 $next = $next->as_struct;
221             }
222             elsif ( ref($next) eq 'HASH' ) {
223             my $cmc = CPAN::Meta::Converter->new(
224             $next, default_version => $self->{default_version}
225 30         141 );
226 30         88 $next = $cmc->upgrade_fragment;
227             }
228             else {
229 0         0 croak "Don't know how to merge '$next'";
230             }
231 31         117 $current = _merge($current, $next, $self->{mapping}, []);
232             }
233 9         109 return $current;
234             }
235              
236             1;
237              
238             # ABSTRACT: Merging CPAN Meta fragments
239              
240              
241             # vim: ts=2 sts=2 sw=2 et :
242              
243             __END__