File Coverage

blib/lib/Dist/Zilla/Plugin/Prereqs/Recommend/MatchInstalled.pm
Criterion Covered Total %
statement 81 85 95.2
branch 10 18 55.5
condition 3 5 60.0
subroutine 18 18 100.0
pod 0 3 0.0
total 112 129 86.8


line stmt bran cond sub pod time code
1 2     2   1991735 use 5.006; # our
  2         6  
2 2     2   8 use strict;
  2         3  
  2         49  
3 2     2   14 use warnings;
  2         2  
  2         154  
4              
5             package Dist::Zilla::Plugin::Prereqs::Recommend::MatchInstalled;
6              
7             our $VERSION = '0.003003';
8              
9             # ABSTRACT: Advertise versions of things you have as soft dependencies
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 2     2   451 use Moose qw( with has around );
  2         301118  
  2         13  
14 2     2   8302 use MooseX::Types::Moose qw( HashRef ArrayRef Str );
  2         40837  
  2         19  
15             with 'Dist::Zilla::Role::PrereqSource';
16              
17              
18              
19              
20              
21              
22              
23              
24              
25              
26              
27              
28              
29              
30              
31              
32              
33             has 'applyto_phase' => (
34             is => ro =>,
35             isa => ArrayRef [Str] =>,
36             lazy => 1,
37             default => sub { [qw(build test runtime configure develop)] },
38             );
39              
40              
41              
42              
43              
44              
45              
46              
47              
48              
49              
50              
51              
52              
53              
54              
55              
56              
57              
58              
59             has 'source_relation' => (
60             is => ro =>,
61             isa => Str,
62             lazy => 1,
63             default => sub { 'requires' },
64             );
65              
66              
67              
68              
69              
70              
71              
72              
73              
74              
75              
76              
77              
78              
79              
80              
81              
82              
83              
84              
85              
86             has 'target_relation' => (
87             is => ro =>,
88             isa => Str =>,
89             lazy => 1,
90             default => sub { 'recommends' },
91             );
92              
93              
94              
95              
96              
97              
98              
99              
100              
101              
102              
103              
104              
105              
106              
107              
108              
109              
110              
111              
112              
113             has 'applyto_map' => (
114             is => ro =>,
115             isa => ArrayRef [Str] =>,
116             lazy => 1,
117             builder => _build_applyto_map =>,
118             );
119              
120             sub _mk_phase_entry {
121 5     5   5 my ( $self, $phase ) = @_;
122 5         171 return sprintf q[%s.%s = %s.%s], $phase, $self->source_relation, $phase, $self->target_relation;
123             }
124              
125             sub _build_applyto_map {
126 1     1   2 my ($self) = @_;
127 1         1 my @out;
128 1         2 for my $phase ( @{ $self->applyto_phase } ) {
  1         36  
129 5         8 push @out, $self->_mk_phase_entry($phase);
130             }
131 1         34 return \@out;
132             }
133              
134             has '_applyto_map_hash' => (
135             is => ro =>,
136             isa => ArrayRef [HashRef] =>,
137             lazy => 1,
138             builder => _build__applyto_map_hash =>,
139             );
140              
141             # _Pulp__5010_qr_m_propagate_properly
142             ## no critic (Compatibility::PerlMinimumVersionAndWhy)
143             my $re_phase = qr/configure|build|runtime|test|develop/msx;
144             my $re_relation = qr/requires|recommends|suggests|conflicts/msx;
145              
146             my $combo = qr/(?:$re_phase)[.](?:$re_relation)/msx;
147              
148             sub _parse_map_token {
149 10     10   10 my ( $self, $token ) = @_;
150 10         6 my ( $phase, $relation );
151 10 50       78 if ( ( $phase, $relation ) = $token =~ /\A($re_phase)[.]($re_relation)/msx ) {
152             return {
153 10         31 phase => $phase,
154             relation => $relation,
155             };
156             }
157 0         0 return $self->log_fatal( [ '%s is not in the form <phase.relation>', $token ] );
158              
159             }
160              
161             sub _parse_map_entry {
162 5     5   6 my ( $self, $entry ) = @_;
163 5         4 my ( $source, $target );
164 5 50       139 if ( ( $source, $target ) = $entry =~ /\A\s*($combo)\s*=\s*($combo)\s*\z/msx ) {
165             return {
166 5         8 source => $self->_parse_map_token($source),
167             target => $self->_parse_map_token($target),
168             };
169             }
170 0         0 return $self->log_fatal( [ '%s is not a valid entry for applyto_map', $entry ] );
171             }
172              
173             sub _build__applyto_map_hash {
174 1     1   2 my ($self) = @_;
175 1         2 my @out;
176 1         1 for my $line ( @{ $self->applyto_map } ) {
  1         37  
177 5         11 push @out, $self->_parse_map_entry($line);
178             }
179 1         40 return \@out;
180             }
181              
182             has 'modules' => (
183             is => ro =>,
184             isa => ArrayRef [Str],
185             lazy => 1,
186             default => sub { [] },
187             );
188              
189             has _modules_hash => (
190             is => ro =>,
191             isa => HashRef,
192             lazy => 1,
193             builder => _build__modules_hash =>,
194             );
195              
196             sub _build__modules_hash {
197 1     1   1 my $self = shift;
198 1         2 return { map { ( $_, 1 ) } @{ $self->modules } };
  1         38  
  1         45  
199             }
200              
201             sub _user_wants_upgrade_on {
202 1     1   2 my ( $self, $module ) = @_;
203 1         39 return exists $self->_modules_hash->{$module};
204             }
205              
206 1     1 0 158 sub mvp_multivalue_args { return qw(applyto_map applyto_phase modules) }
207 1     1 0 113 sub mvp_aliases { return { 'module' => 'modules' } }
208              
209             sub _current_version_of {
210 1     1   2 my ( undef, $package ) = @_;
211 1 50       3 if ( 'perl' eq $package ) {
212              
213             # Thats not going to work, Dave.
214 0         0 return $];
215             }
216 1         455 require Module::Data;
217 1         11313 my $md = Module::Data->new($package);
218 1 50       1014 return if not $md;
219 1 50       14 return if not -e $md->path;
220 1 50       164 return if -d $md->path;
221 1         24 return $md->_version_emulate;
222             }
223              
224             around dump_config => sub {
225             my ( $orig, $self, @args ) = @_;
226             my $config = $self->$orig(@args);
227             my $localconf = {};
228             $localconf->{applyto_phase} = $self->applyto_phase;
229             $localconf->{applyto_map} = $self->applyto_map;
230             $localconf->{modules} = $self->modules;
231             $localconf->{source_relation} = $self->source_relation;
232             $localconf->{target_relation} = $self->target_relation;
233             $localconf->{ q[$] . __PACKAGE__ . '::VERSION' } = $VERSION unless __PACKAGE__ eq ref $self;
234             $config->{ +__PACKAGE__ } = $localconf;
235             return $config;
236             };
237              
238             __PACKAGE__->meta->make_immutable;
239 2     2   8256 no Moose;
  2         4  
  2         13  
240              
241             sub _register_applyto_map_entry {
242 5     5   5 my ( $self, $applyto, $prereqs ) = @_;
243 5         5 my ( $phase, $rel );
244 5         43 $phase = $applyto->{source}->{phase};
245 5         5 $rel = $applyto->{source}->{relation};
246             my $targetspec = {
247             phase => $applyto->{target}->{phase},
248             type => $applyto->{target}->{relation},
249 5         12 };
250 5         22 $self->log_debug( [ 'Processing %s.%s => %s.%s', $phase, $rel, $applyto->{target}->{phase}, $applyto->{target}->{relation} ] );
251 5 100 66     1185 if ( not exists $prereqs->{$phase} or not exists $prereqs->{$phase}->{$rel} ) {
252 4         13 $self->log_debug( [ 'Nothing in %s.%s', $phase, $rel ] );
253 4         875 return;
254             }
255 1         5 my $reqs = $prereqs->{$phase}->{$rel}->as_string_hash;
256              
257 1         74 for my $module ( keys %{$reqs} ) {
  1         2  
258 1 50       4 next unless $self->_user_wants_upgrade_on($module);
259 1         4 my $latest = $self->_current_version_of($module);
260 1 50       10906 if ( defined $latest ) {
261 1         34 $self->zilla->register_prereqs( $targetspec, $module, $latest );
262 1         216 next;
263             }
264              
265             $self->log(
266 0         0 [ q[You asked for the installed version of %s,] . q[ and it is a dependency but it is apparently not installed], $module, ],
267             );
268             }
269 1         3 return $self;
270             }
271              
272             sub register_prereqs {
273 1     1 0 65439 my ($self) = @_;
274 1         28 my $zilla = $self->zilla;
275 1         21 my $prereqs = $zilla->prereqs;
276 1   50     24 my $guts = $prereqs->cpan_meta_prereqs->{prereqs} || {};
277              
278 1         7 for my $applyto ( @{ $self->_applyto_map_hash } ) {
  1         37  
279 5         11 $self->_register_applyto_map_entry( $applyto, $guts );
280             }
281 1         3 return $prereqs;
282             }
283              
284             1;
285              
286             __END__
287              
288             =pod
289              
290             =encoding UTF-8
291              
292             =head1 NAME
293              
294             Dist::Zilla::Plugin::Prereqs::Recommend::MatchInstalled - Advertise versions of things you have as soft dependencies
295              
296             =head1 VERSION
297              
298             version 0.003003
299              
300             =head1 SYNOPSIS
301              
302             C<[Prereqs::MatchInstalled]> was a good concept, but its application seemed too strong for some things.
303              
304             This is a variation on the same theme, but instead of upgrading dependencies in-place,
305             it propagates the upgrade to a different relation, to produce a softer dependency map.
306              
307             Below shows the defaults expanded by hand.
308              
309             [Prereqs::Recommend::MatchInstalled]
310             applyto_phase = configure
311             applyto_phase = runtime
312             applyto_phase = test
313             applyto_phase = build
314             applyto_phase = develop
315             source_relation = requires
316             target_relation = recommends
317              
318             And add these stanzas for example:
319              
320             modules = Module::Build
321             modules = Moose
322              
323             And you have yourself a distribution that won't needlessly increase the dependencies
324             on either, but will add increased dependencies to the C<recommends> phase.
325              
326             This way, people doing
327              
328             cpanm YourModule
329              
330             Get only what they I<need>
331              
332             While
333              
334             cpanm --with-recommends YourModule
335              
336             Will get more recent things upgraded
337              
338             =head1 DESCRIPTION
339              
340             The C<[Prereqs::Recommend::MatchInstalled]> is a tool for authors who wish to
341             keep end users informed of which versions of critical dependencies the author
342             has themselves used, as an encouragement for the users to consume at least that
343             version, but without making it a hard requirement.
344              
345             In practice this can be used for anything, but this modules author currently
346             recommends you restrict this approach only to development dependencies,
347             I<mostly> because even a system of auto-recommendation is still too aggressive
348             for most modules, or if you insist this concept on C<CPAN>, use something with
349             "but not larger than" mechanics like C<[Prereqs::Upgrade]>
350              
351             =head1 ATTRIBUTES
352              
353             =head2 C<applyto_phase>
354              
355             [Prereqs::Recommend::MatchInstalled]
356             applyto_phase = SOMEPHASE
357             applyto_phase = SOMEPHASE
358              
359             This attribute can be specified multiple times.
360              
361             Valuable values are:
362              
363             build test runtime configure develop
364              
365             And those are the default values too.
366              
367             =head2 C<source_relation>
368              
369             [Prereqs::Recommend::MatchInstalled]
370             source_relation = requires
371              
372             This attribute specifies the prerequisites to skim for modules to recommend upgrades on.
373              
374             Valuable values are:
375              
376             requires recommends suggests
377              
378             Lastly:
379              
380             conflicts
381              
382             Will probably do I<something>, but I have no idea if that means anything. If you want to conflict with what you've installed with, ... go right ahead.
383              
384             =head2 C<target_relation>
385              
386             [Prereqs::Recommend::MatchInstalled]
387             target_relation = recommends
388              
389             This attribute specifies the relationship type to inject upgrades into.
390              
391             Valuable values are:
392              
393             requires recommends suggests
394              
395             Lastly:
396              
397             conflicts
398              
399             Will probably do I<something>, but I have no idea if that means anything. If you want to conflict with what you've installed
400             with, ... go right ahead.
401              
402             =head2 C<applyto_map>
403              
404             [Prereqs::Recommend::MatchInstalled]
405             applyto_map = runtime.requires = runtime.recommends
406              
407             This attribute is the advanced internals of the other attributes, and it exists for insane, advanced, and niché applications.
408              
409             General format is:
410              
411             applyto_map = <source_phase>.<source_relation> = <target_phase>.<target_relation>
412              
413             And you can probably do everything with this.
414              
415             You could also conceivably emulate C<[Prereqs::MatchInstalled]> in entirety by using this feature excessively.
416              
417             C<applyto_map> may be declared multiple times.
418              
419             =for Pod::Coverage mvp_aliases mvp_multivalue_args register_prereqs
420              
421             =head1 AUTHOR
422              
423             Kent Fredric <kentnl@cpan.org>
424              
425             =head1 COPYRIGHT AND LICENSE
426              
427             This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
428              
429             This is free software; you can redistribute it and/or modify it under
430             the same terms as the Perl 5 programming language system itself.
431              
432             =cut