File Coverage

blib/lib/Dist/Zilla/Plugin/Prereqs/Upgrade.pm
Criterion Covered Total %
statement 24 87 27.5
branch 0 20 0.0
condition 0 15 0.0
subroutine 9 17 52.9
pod 1 3 33.3
total 34 142 23.9


line stmt bran cond sub pod time code
1 1     1   460 use 5.006; # our
  1         2  
2 1     1   5 use strict;
  1         1  
  1         21  
3 1     1   11 use warnings;
  1         1  
  1         64  
4              
5             package Dist::Zilla::Plugin::Prereqs::Upgrade;
6              
7             our $VERSION = '0.001001';
8              
9             # ABSTRACT: Upgrade existing prerequisites in place
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 1     1   482 use Moose qw( has with around );
  1         301642  
  1         6  
14 1     1   4164 use Scalar::Util qw( blessed );
  1         1  
  1         60  
15 1     1   574 use Dist::Zilla::Util::ConfigDumper qw( config_dumper );
  1         870  
  1         6  
16              
17             with 'Dist::Zilla::Role::PrereqSource';
18              
19             sub _defaulted {
20 3     3   8 my ( $name, $type, $default, @rest ) = @_;
21 3         20 return has $name, is => 'ro', isa => $type, init_arg => q[-] . $name, lazy => 1, default => $default, @rest;
22             }
23              
24             sub _builder {
25 2     2   5 my ( $name, $type, @rest ) = @_;
26 2         9 return has $name, is => 'ro', isa => $type, init_arg => q[-] . $name, 'lazy_build' => 1, @rest;
27             }
28              
29             has 'modules' => (
30             is => 'ro',
31             isa => 'HashRef[Str]',
32             init_arg => '-modules',
33             required => 1,
34             traits => [qw( Hash )],
35             handles => {
36             '_user_wants_upgrade_on' => 'exists',
37             '_wanted_minimum_on' => 'get',
38             },
39             );
40              
41             _defaulted 'applyto_phase' => 'ArrayRef[Str]' => sub { [qw(build test runtime configure develop)] };
42             _defaulted 'target_relation' => 'Str' => sub { 'recommends' };
43             _defaulted 'source_relation' => 'Str' => sub { 'requires' };
44              
45             _builder 'applyto_map' => 'ArrayRef[Str]';
46             _builder _applyto_map_pairs => 'ArrayRef[HashRef]', init_arg => undef;
47              
48             around dump_config => config_dumper( __PACKAGE__,
49             {
50             attrs => [qw( modules applyto_map applyto_phase target_relation source_relation )],
51             },
52             );
53              
54             __PACKAGE__->meta->make_immutable;
55 1     1   229 no Moose;
  1         2  
  1         5  
56              
57              
58              
59              
60              
61 0     0 0   sub mvp_multivalue_args { return qw(-applyto_map -applyto_phase) }
62              
63             sub register_prereqs {
64 0     0 0   my ($self) = @_;
65 0           my $zilla = $self->zilla;
66 0           my $prereqs = $zilla->prereqs;
67 0   0       my $guts = $prereqs->cpan_meta_prereqs->{prereqs} || {};
68              
69 0           for my $applyto ( @{ $self->_applyto_map_pairs } ) {
  0            
70 0           $self->_register_applyto_map_entry( $applyto, $guts );
71             }
72 0           return $prereqs;
73             }
74              
75             sub BUILDARGS {
76 0     0 1   my ( undef, $config, @extra ) = @_;
77 0 0 0       if ( 'HASH' ne ( ref $config || q[] ) or scalar @extra ) {
      0        
78 0           $config = { $config, @extra };
79             }
80 0           my $modules = {};
81 0           for my $key ( keys %{$config} ) {
  0            
82 0 0         next if $key =~ /\A-/msx;
83 0 0         next if 'plugin_name' eq $key;
84 0 0         next if blessed $config->{$key};
85 0 0         next if 'zilla' eq $key;
86 0           $modules->{$key} = delete $config->{$key};
87             }
88 0           return { '-modules' => $modules, %{$config} };
  0            
89             }
90              
91             sub _register_applyto_map_entry {
92 0     0     my ( $self, $applyto, $prereqs ) = @_;
93 0           my ( $phase, $rel );
94 0           $phase = $applyto->{source}->{phase};
95 0           $rel = $applyto->{source}->{relation};
96             my $targetspec = {
97             phase => $applyto->{target}->{phase},
98             type => $applyto->{target}->{relation},
99 0           };
100 0           $self->log_debug( [ 'Processing %s.%s => %s.%s', $phase, $rel, $applyto->{target}->{phase}, $applyto->{target}->{relation} ] );
101 0 0 0       if ( not exists $prereqs->{$phase} or not exists $prereqs->{$phase}->{$rel} ) {
102 0           $self->log_debug( [ 'Nothing in %s.%s', $phase, $rel ] );
103 0           return;
104             }
105              
106 0           my $reqs = $prereqs->{$phase}->{$rel}->as_string_hash;
107              
108 0           for my $module ( keys %{$reqs} ) {
  0            
109 0 0         next unless $self->_user_wants_upgrade_on($module);
110 0           my $v = $self->_wanted_minimum_on($module);
111              
112             # Get the original requirement and see if applying the new minimum changes anything
113 0           my $fake_target = $prereqs->{$phase}->{$rel}->clone;
114 0           my $old_string = $fake_target->as_string_hash->{$module};
115 0           $fake_target->add_string_requirement( $module, $v );
116              
117             # Dep changed in the effective source spec
118 0 0         next if $fake_target->as_string_hash->{$module} eq $old_string;
119              
120 0           $self->log_debug( [ 'Upgrading %s %s to %s', $module, "$old_string", "$v" ] );
121              
122             # Apply the change to the target spec to to it being an upgrade.
123 0           $self->zilla->register_prereqs( $targetspec, $module, $fake_target->as_string_hash->{$module} );
124             }
125 0           return $self;
126             }
127              
128             sub _build_applyto_map {
129 0     0     my ($self) = @_;
130 0           my (@out);
131 0           for my $phase ( @{ $self->applyto_phase } ) {
  0            
132 0           push @out, sprintf '%s.%s = %s.%s', $phase, $self->source_relation, $phase, $self->target_relation;
133             }
134 0           return \@out;
135             }
136              
137             # _Pulp__5010_qr_m_propagate_properly
138             ## no critic (Compatibility::PerlMinimumVersionAndWhy)
139             my $re_phase = qr/configure|build|runtime|test|develop/msx;
140             my $re_relation = qr/requires|recommends|suggests|conflicts/msx;
141              
142             my $combo = qr/(?:$re_phase)[.](?:$re_relation)/msx;
143              
144             sub _parse_map_token {
145 0     0     my ( $self, $token ) = @_;
146 0           my ( $phase, $relation ) = $token =~ /\A($re_phase)[.]($re_relation)/msx;
147 0 0 0       if ( not defined $phase or not defined $relation ) {
148 0           return $self->log_fatal( [ '%s is not in the form <phase.relation>', $token ] );
149             }
150 0           return { phase => $phase, relation => $relation, };
151             }
152              
153             sub _parse_map_entry {
154 0     0     my ( $self, $entry ) = @_;
155 0           my ( $source, $target ) = $entry =~ /\A\s*($combo)\s*=\s*($combo)\s*\z/msx;
156 0 0 0       if ( not defined $source or not defined $target ) {
157 0           return $self->log_fatal( [ '%s is not a valid entry for -applyto_map', $entry ] );
158             }
159             return {
160 0           source => $self->_parse_map_token($source),
161             target => $self->_parse_map_token($target),
162             };
163             }
164              
165             sub _build__applyto_map_pairs {
166 0     0     my ($self) = @_;
167 0           return [ map { $self->_parse_map_entry($_) } @{ $self->applyto_map } ];
  0            
  0            
168             }
169              
170             1;
171              
172             __END__
173              
174             =pod
175              
176             =encoding UTF-8
177              
178             =head1 NAME
179              
180             Dist::Zilla::Plugin::Prereqs::Upgrade - Upgrade existing prerequisites in place
181              
182             =head1 VERSION
183              
184             version 0.001001
185              
186             =head1 DESCRIPTION
187              
188             This allows you to automatically upgrade selected prerequisites
189             to selected versions, if, and only if, they're already prerequisites.
190              
191             This is intended to be used to compliment C<[AutoPrereqs]> without adding dependencies.
192              
193             [AutoPrereqs]
194              
195             [Prereqs::Upgrade]
196             Moose = 2.0 ; Moose 2.0 is added as a minimum to runtime.recommends to 2.0 if a lower version is in runtime.requires
197              
198             This is intended to be especially helpful in C<PluginBundle>'s where one may habitually
199             always want a certain version of a certain dependency every time they use it, but don't want to be burdened
200             with remembering to encode that version of it.
201              
202             =for Pod::Coverage mvp_multivalue_args register_prereqs
203              
204             =head1 USAGE
205              
206             =head2 BASICS
207              
208             For most cases, all you'll need to do is:
209              
210             [Prereqs::Upgrade]
211             My::Module = Version Spec that is recommended
212              
213             And then everything in C<PHASE.requires> will be copied to C<PHASE.recommends>
214             if it is determined that doing so will cause the dependency to be changed.
215              
216             For instance, you may want to do:
217              
218             [Prereqs::Upgrade]
219             Moose = 2.0
220             Moo = 1.008001
221              
222             Note that this will not imply Moo unless Moo is B<ALREADY> a requirement, and won't imply Moose unless Moose is B<ALREADY>
223             a requirement.
224              
225             And this will transform:
226              
227             { runtime: { requires: { Moose: 0 }}
228              
229             Into
230              
231             { runtime: {
232             requires: { Moose: 0 },
233             recommends: { Moose: 2.0 }
234             }}
235              
236             =head3 C<-target_relation>
237              
238             By default, the target relationship type is C<recommends>.
239              
240             However, this can be adjusted with the C<-target_relation> attribute.
241              
242             [Prereqs::Upgrade]
243             ; -target_relation = requires ; Not recommended and way more strict
244             -target_relation = suggests ; Makes upgrades suggestions instead of recommendations
245             Moose = 2.0
246             Moo = 1.008001
247              
248             =head3 C<-source_relation>
249              
250             By default, this tool assumes you have a single relation type
251             that you wish to translate into a L<< C<target>|/-target_relation >>,
252             and thus the default C<-source_relation> is C<requires>.
253              
254             [Prereqs::Upgrade]
255             ; This example doesn't make much sense but it would work
256             -source_relation = recommends
257             -target_relation = suggests
258             Moose = 2.0
259              
260             This would add a C<PHASE.suggests> upgrade to C<2.0> if C<Moose> was found in C<PHASE.recommends>
261              
262             =head3 C<-applyto_phase>
263              
264             By default, this tool applies upgrades from C<-source_relation> to C<-target_relation>
265             C<foreach> C<-applyto_phase>, and this lists default contents is:
266              
267             [Prereqs::Upgrade]
268             -applyto_phase = build
269             -applyto_phase = configure
270             -applyto_phase = test
271             -applyto_phase = runtime
272             -applyto_phase = develop
273              
274             =head2 ADVANCED USAGE
275              
276             =head3 C<-applyto_map>
277              
278             Advanced users can define arbitrary transform maps, which the L<basic|/BASIC USAGE> parameters
279             are simplified syntax for.
280              
281             Under the hood, you can define any source C<PHASE.RELATION> and map it as an upgrade to any target C<PHASE.RELATION>, even if it doesn't make much sense to do so.
282              
283             This section is material that often seems like C<YAGNI> but I find I end up needing it somewhere,
284             because its not very straight forward to demonstrate a simple case where it would be useful.
285              
286             However, in this example: If a distribution uses Moose, then the distribution itself is permitted to have version = C<0>
287              
288             But a C<runtime.recommends> of C<2.0> is injected, and a C<develop.requires> of C<2.0> is injected.
289              
290             [Prereqs::Upgrade]
291             -applyto_map = runtime.requires = runtime.recommends
292             -applyto_map = runtime.requires = develop.requires
293             Moose = 2.0
294              
295             =head1 SEE ALSO
296              
297             =over 4
298              
299             =item * L<< C<[Prereqs::MatchInstalled]>|Dist::Zilla::Plugin::Prereqs::MatchInstalled >>
300              
301             Upgrades stated dependencies to whatever you have installed, which is
302             significantly more flippant than having some auto-upgrading base versions.
303              
304             =item * L<< C<[Prereqs::Recommend::MatchInstalled]>|Dist::Zilla::Plugin::Prereqs::Recommend::MatchInstalled >>
305              
306             Like the above, except supports C<requires> → C<recommends> translation ( and does that by default )
307              
308             =item * L<< C<[Prereqs::MatchInstalled::All]>|Dist::Zilla::Plugin::Prereqs::MatchInstalled::All >>
309              
310             The most hateful way you can request C<CPAN> to install all the latest things for your module.
311              
312             =back
313              
314             =head1 AUTHOR
315              
316             Kent Fredric <kentnl@cpan.org>
317              
318             =head1 COPYRIGHT AND LICENSE
319              
320             This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
321              
322             This is free software; you can redistribute it and/or modify it under
323             the same terms as the Perl 5 programming language system itself.
324              
325             =cut