File Coverage

blib/lib/Dist/Zilla/Role/Bootstrap.pm
Criterion Covered Total %
statement 54 68 79.4
branch 6 14 42.8
condition n/a
subroutine 19 20 95.0
pod n/a
total 79 102 77.4


line stmt bran cond sub pod time code
1 4     4   2943 use 5.006; # our
  4         9  
2 4     4   14 use strict;
  4         5  
  4         73  
3 4     4   22 use warnings;
  4         3  
  4         261  
4              
5             package Dist::Zilla::Role::Bootstrap;
6              
7             our $VERSION = '1.001004';
8              
9             # ABSTRACT: Shared logic for bootstrap things.
10              
11             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
12              
13 4     4   1702 use Moose::Role qw( with has around requires );
  4         295519  
  4         17  
14 4     4   14029 use List::UtilsBy qw( max_by nmax_by );
  4         4690  
  4         256  
15 4     4   1535 use version qw();
  4         5014  
  4         2703  
16              
17              
18              
19              
20              
21              
22              
23              
24              
25              
26              
27              
28              
29             with 'Dist::Zilla::Role::Plugin';
30              
31             around 'dump_config' => sub {
32             my ( $orig, $self, @args ) = @_;
33             my $config = $self->$orig(@args);
34             my $localconf = $config->{ +__PACKAGE__ } = {};
35              
36             $localconf->{distname} = $self->distname;
37             $localconf->{try_built} = $self->try_built;
38             $localconf->{try_built_method} = $self->try_built_method;
39             $localconf->{fallback} = $self->fallback;
40              
41             $localconf->{ q[$] . __PACKAGE__ . '::VERSION' } = $VERSION;
42              
43             return $config;
44             };
45              
46              
47              
48              
49              
50              
51              
52              
53              
54              
55              
56              
57              
58              
59              
60             has distname => ( isa => 'Str', is => ro =>, lazy_build => 1 );
61              
62 3     3   70 sub _build_distname { $_[0]->zilla->name }
63              
64             has _cwd => ( is => ro =>, lazy_build => 1, );
65              
66             sub _build__cwd {
67 3     3   19 require Path::Tiny;
68 3         63 return Path::Tiny::path( $_[0]->zilla->root );
69             }
70              
71              
72              
73              
74              
75              
76              
77              
78              
79              
80              
81              
82              
83              
84              
85             has try_built => ( isa => 'Bool', is => ro =>, lazy_build => 1, );
86 1     1   23 sub _build_try_built { return }
87              
88              
89              
90              
91              
92              
93              
94              
95              
96              
97              
98              
99              
100              
101              
102             has fallback => ( isa => 'Bool', is => ro =>, lazy_build => 1 );
103 3     3   70 sub _build_fallback { 1 }
104              
105              
106              
107              
108              
109              
110              
111              
112              
113              
114              
115              
116              
117              
118              
119              
120              
121              
122              
123             has try_built_method => ( isa => 'Str', is => ro =>, lazy_build => 1, );
124 1     1   24 sub _build_try_built_method { 'mtime' }
125              
126             sub _pick_latest_mtime {
127 1     1   3 my ( undef, @candidates ) = @_;
128 1     3   10 return max_by { $_->stat->mtime } @candidates;
  3         276  
129             }
130              
131             sub _get_candidate_version {
132 3     3   3 my ( $self, $candidate ) = @_;
133 3         71 my $distname = $self->distname;
134 3 50       6 if ( $candidate->basename =~ /\A\Q$distname\E-(.+\z)/msx ) {
135 3         38 my $version = $1;
136 3         3 $version =~ s/-TRIAL\z//msx;
137 3         18 return version->parse($version);
138             }
139              
140             }
141              
142             sub _pick_latest_parseversion {
143 1     1   3 my ( $self, @candidates ) = @_;
144 1     3   7 return max_by { $self->_get_candidate_version($_) } @candidates;
  3         47  
145             }
146              
147             my (%methods) = (
148             mtime => _pick_latest_mtime =>,
149             parseversion => _pick_latest_parseversion =>,
150             );
151              
152             sub _pick_candidate {
153 2     2   4 my ( $self, @candidates ) = @_;
154 2         52 my $method = $self->try_built_method;
155 2 50       8 if ( not exists $methods{$method} ) {
156 0         0 require Carp;
157 0         0 Carp::croak("No such candidate picking method $method");
158             }
159 2         5 $method = $methods{$method};
160 2         9 return $self->$method(@candidates);
161             }
162              
163             has _bootstrap_root => ( is => ro =>, lazy_build => 1 );
164              
165             sub _build__bootstrap_root {
166 3     3   4 my ($self) = @_;
167 3 100       94 if ( not $self->try_built ) {
168 1         24 return $self->_cwd;
169             }
170 2         66 my $distname = $self->distname;
171              
172 2         62 my (@candidates) = grep { $_->basename =~ /\A\Q$distname\E-/msx } grep { $_->is_dir } $self->_cwd->children;
  10         179  
  10         481  
173              
174 2 50       34 if ( 1 == scalar @candidates ) {
175 0         0 return $candidates[0];
176             }
177 2 50       7 if ( scalar @candidates < 1 ) {
178 0 0       0 if ( not $self->fallback ) {
179 0         0 $self->log( [ 'candidates for bootstrap (%s) == 0, and fallback disabled. not bootstrapping', 0 + @candidates ] );
180 0         0 return;
181             }
182             else {
183 0         0 $self->log( [ 'candidates for bootstrap (%s) == 0, fallback to boostrapping <distname>/', 0 + @candidates ] );
184 0         0 return $self->_cwd;
185             }
186             }
187              
188 2         60 $self->log_debug( [ '>1 candidates, picking one by method %s', $self->try_built_method ] );
189 2         421 return $self->_pick_candidate(@candidates);
190             }
191              
192             sub _add_inc {
193 0     0     my ( undef, $import ) = @_;
194 0 0         if ( not ref $import ) {
195 0           require lib;
196 0           return lib->import($import);
197             }
198 0           require Carp;
199 0           return Carp::croak('At this time, _add_inc(arg) only supports scalar values of arg');
200             }
201              
202              
203              
204              
205              
206              
207              
208              
209              
210              
211              
212              
213              
214             requires 'bootstrap';
215              
216             around plugin_from_config => sub {
217             my ( $orig, $plugin_class, $name, $payload, $section ) = @_;
218              
219             my $instance = $plugin_class->$orig( $name, $payload, $section );
220              
221             $instance->bootstrap;
222              
223             return $instance;
224             };
225              
226 4     4   21 no Moose::Role;
  4         4  
  4         25  
227              
228             1;
229              
230             __END__
231              
232             =pod
233              
234             =encoding UTF-8
235              
236             =head1 NAME
237              
238             Dist::Zilla::Role::Bootstrap - Shared logic for bootstrap things.
239              
240             =head1 VERSION
241              
242             version 1.001004
243              
244             =head1 SYNOPSIS
245              
246             For consuming plugins:
247              
248             use Moose;
249             with 'Dist::Zilla::Role::Bootstrap';
250              
251             sub bootstrap {
252             my $bootstrap_root = $_[0]->_bootstrap_root;
253             # Do the actual bootstrap work here
254             $_[0]->_add_inc('./some/path/here');
255             }
256              
257             For users of plugins:
258              
259             [Some::Plugin::Name]
260             try_built = 0 ; # use / as the root to bootstrap
261             try_built = 1 ; # try to use /Dist-Name-.*/ instead of /
262              
263             fallback = 0 ; # don't bootstrap at all if /Dist-Name-.*/ matches != 1 things
264             fallback = 1 ; # fallback to / if /Dist-Name-.*/ matches != 1 things
265              
266             =head1 DESCRIPTION
267              
268             This module is a role that aims to be consumed by plugins that want to perform
269             some very early bootstrap operation that may affect the loading environment of
270             successive plugins, especially with regards to plugins that may wish to build with
271             themselves, either by consuming the source tree itself, or by consuming a previous
272             built iteration.
273              
274             Implementation is quite simple:
275              
276             =over 4
277              
278             =item 1. C<with> this role in your plugin
279              
280             with 'Dist::Zilla::Role::Bootstrap'
281              
282             =item 2. Implement the C<bootstrap> sub.
283              
284             sub bootstrap {
285             my ( $self ) = @_;
286             }
287              
288             =item 3. I<Optional>: Fetch the discovered C<bootstap> root via:
289              
290             $self->_bootstap_root
291              
292             =item 4. I<Optional>: Load some path into C<@INC> via:
293              
294             $self->_add_inc($path)
295              
296             =back
297              
298             =head1 REQUIRED METHODS
299              
300             =head2 C<bootstrap>
301              
302             Any user specified C<bootstrap> method will be invoked during C<plugin_from_config>.
303              
304             This is B<AFTER> C<< ->new >>, B<AFTER> C<< ->BUILD >>, and B<AFTER> C<dzil>'s internal C<plugin_from_config> steps.
305              
306             This occurs within the C<register_component> phase of the plug-in loading and configuration.
307              
308             This also occurs B<BEFORE> C<Dist::Zilla> attaches the plug-in into the plug-in stash.
309              
310             =head1 ATTRIBUTES
311              
312             =head2 C<distname>
313              
314             The name of the distribution.
315              
316             This value is vivified by asking C<< zilla->name >>.
317              
318             Usually this value is populated by C<dist.ini> in the property C<name>
319              
320             However, occasionally, this value is discovered by a C<plugin>.
321              
322             In such a case, that plugin cannot be bootstrapped, because that plugin B<MUST> be loaded prior to bootstrap.
323              
324             =head2 C<try_built>
325              
326             This attribute controls how the consuming C<plugin> behaves.
327              
328             =over 4
329              
330             =item * false B<(default)> : bootstrapping is only done to C<PROJECTROOT/lib>
331              
332             =item * true : bootstrap attempts to try C<< PROJECTROOT/<distname>-<version>/lib >>
333              
334             =back
335              
336             =head2 C<fallback>
337              
338             This attribute is for use in conjunction with C<try_built>
339              
340             =over 4
341              
342             =item * C<false> : When C<< PROJECTROOT/<distname>-<version> >> does not exist, don't perform any bootstrapping
343              
344             =item * C<true> B<(default)> : When C<< PROJECTROOT/<distname>-<version> >> does not exist, bootstrap to C<< PROJECTROOT/lib >>
345              
346             =back
347              
348             =head2 C<try_built_method>
349              
350             This attribute controls how C<try_built> behaves when multiple directories exist that match C<< PROJECTROOT/<distname>-.* >>
351              
352             Two valid options at this time:
353              
354             =over 4
355              
356             =item * C<mtime> B<(default)> : Pick the directory with the most recent C<mtime>
357              
358             =item * C<parseversion> : Attempt to parse versions on all candidate directories and use the one with the largest version.
359              
360             =back
361              
362             Prior to C<0.2.0> this property did not exist, and default behavior was to assume C<0 Candidates> and C<2 or more Candidates> were the same problem.
363              
364             =begin MetaPOD::JSON v1.1.0
365              
366             {
367             "namespace":"Dist::Zilla::Role::Bootstrap",
368             "interface":"role",
369             "does":"Dist::Zilla::Role::Plugin"
370             }
371              
372              
373             =end MetaPOD::JSON
374              
375             =head1 AUTHOR
376              
377             Kent Fredric <kentnl@cpan.org>
378              
379             =head1 COPYRIGHT AND LICENSE
380              
381             This software is copyright (c) 2017 by Kent Fredric <kentfredric@gmail.com>.
382              
383             This is free software; you can redistribute it and/or modify it under
384             the same terms as the Perl 5 programming language system itself.
385              
386             =cut