File Coverage

lib/Module/Provision/Base.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Module::Provision::Base;
2              
3 1     1   629 use namespace::autoclean;
  1         2  
  1         7  
4              
5 1     1   343 use Class::Usul::Constants qw( EXCEPTION_CLASS NUL SPC TRUE );
  0            
  0            
6             use Class::Usul::Functions qw( app_prefix class2appdir classdir
7             distname first_char io is_arrayref throw );
8             use Class::Usul::Time qw( time2str );
9             use CPAN::Meta;
10             use English qw( -no_match_vars );
11             use File::DataClass::Types qw( ArrayRef Directory HashRef NonEmptySimpleStr
12             Object OctalNum Path PositiveInt
13             SimpleStr Undef );
14             use Module::Metadata;
15             use Perl::Version;
16             use Try::Tiny;
17             use Type::Utils qw( enum );
18             use Unexpected::Functions qw( Unspecified );
19             use Moo;
20             use Class::Usul::Options;
21              
22             extends q(Class::Usul::Programs);
23              
24             my %BUILDERS = ( 'DZ' => 'dist.ini', 'MB' => 'Build.PL', 'MI' => 'Makefile.PL');
25             my $BUILDER = enum 'Builder' => [ qw( DZ MB MI ) ];
26             my $VCS = enum 'VCS' => [ qw( git none svn ) ];
27              
28             # Override defaults in base class
29             has '+config_class' => default => sub { 'Module::Provision::Config' };
30              
31             # Object attributes (public)
32             # Visible to the command line
33             option 'base' => is => 'lazy', isa => Path, coerce => TRUE,
34             documentation => 'Directory containing new projects',
35             builder => sub { $_[ 0 ]->config->base }, format => 's';
36              
37             option 'branch' => is => 'lazy', isa => SimpleStr, format => 's',
38             documentation => 'The name of the initial branch to create', short => 'b';
39              
40             option 'builder' => is => 'lazy', isa => $BUILDER, format => 's',
41             documentation => 'Which build system to use: DZ, MB, or MI';
42              
43             option 'license' => is => 'ro', isa => NonEmptySimpleStr, format => 's',
44             documentation => 'License used for the project',
45             builder => sub { $_[ 0 ]->config->license };
46              
47             option 'perms' => is => 'ro', isa => OctalNum, coerce => TRUE,
48             documentation => 'Default permission for file / directory creation',
49             default => '640', format => 'i';
50              
51             option 'plugins' => is => 'ro', isa => ArrayRef[NonEmptySimpleStr],
52             documentation => 'Name of optional plugins to load, comma separated list',
53             builder => sub { [] }, format => 's', short => 'M',
54             coerce => sub { (is_arrayref $_[ 0 ])
55             ? $_[ 0 ] : [ split m{ , }mx, $_[ 0 ] ] };
56              
57             option 'project' => is => 'lazy', isa => NonEmptySimpleStr, format => 's',
58             documentation => 'Package name of the new projects main module';
59              
60             option 'repository' => is => 'ro', isa => NonEmptySimpleStr, format => 's',
61             documentation => 'Directory containing the SVN repository',
62             builder => sub { $_[ 0 ]->config->repository };
63              
64             option 'vcs' => is => 'lazy', isa => $VCS, format => 's',
65             documentation => 'Which VCS to use: git, none, or svn';
66              
67             # Ingnored by the command line
68             has 'appbase' => is => 'lazy', isa => Path, coerce => TRUE;
69              
70             has 'appldir' => is => 'lazy', isa => Path, coerce => TRUE;
71              
72             has 'branch_file' => is => 'lazy', isa => Path, coerce => TRUE,
73             builder => sub { [ $_[ 0 ]->appbase, '.branch' ] };
74              
75             has 'binsdir' => is => 'lazy', isa => Path, coerce => TRUE,
76             builder => sub { [ $_[ 0 ]->appldir, 'bin' ] };
77              
78             has 'default_branch' => is => 'lazy', isa => SimpleStr;
79              
80             has 'dist_module' => is => 'lazy', isa => Path, coerce => TRUE,
81             builder => sub { [ $_[ 0 ]->homedir.'.pm' ] };
82              
83             has 'dist_version' => is => 'lazy', isa => Object;
84              
85             has 'distname' => is => 'lazy', isa => NonEmptySimpleStr,
86             builder => sub { distname $_[ 0 ]->project };
87              
88             has 'exec_perms' => is => 'lazy', isa => PositiveInt;
89              
90             has 'homedir' => is => 'lazy', isa => Path, coerce => TRUE;
91              
92             has 'incdir' => is => 'lazy', isa => Path, coerce => TRUE,
93             builder => sub { [ $_[ 0 ]->appldir, 'inc' ] };
94              
95             has 'initial_wd' => is => 'ro', isa => Directory,
96             builder => sub { io()->cwd };
97              
98             has 'libdir' => is => 'lazy', isa => Path, coerce => TRUE,
99             builder => sub { [ $_[ 0 ]->appldir, 'lib' ] };
100              
101             has 'license_keys' => is => 'lazy', isa => HashRef;
102              
103             has 'manifest_paths' => is => 'lazy', isa => ArrayRef, init_arg => undef;
104              
105             has 'module_abstract' => is => 'lazy', isa => NonEmptySimpleStr;
106              
107             has 'module_metadata' => is => 'lazy', isa => Object | Undef, builder => sub {
108             Module::Metadata->new_from_file
109             ( $_[ 0 ]->dist_module->abs2rel( $_[ 0 ]->appldir ), collect_pod => 1 ) };
110              
111             has 'project_file' => is => 'lazy', isa => NonEmptySimpleStr;
112              
113             has 'stash' => is => 'lazy', isa => HashRef;
114              
115             has 'testdir' => is => 'lazy', isa => Path, coerce => TRUE,
116             builder => sub { [ $_[ 0 ]->appldir, 't' ] };
117              
118             # Private functions
119             my $_builders = sub {
120             return (sort keys %BUILDERS);
121             };
122              
123             my $_get_module_from = sub { # Return main module name from project file
124             return
125             (map { s{ [-] }{::}gmx; $_ }
126             map { m{ \A [q\'\"] }mx ? eval $_ : $_ }
127             map { m{ \A \s* (?:module_name|module|name)
128             \s+ [=]?[>]? \s* ([^,;]+) [,;]? }imx }
129             grep { m{ \A \s* (module|name) }imx }
130             split m{ [\n] }mx, $_[ 0 ])[ 0 ];
131             };
132              
133             my $_parse_manifest_line = sub { # Robbed from ExtUtils::Manifest
134             my $line = shift; my ($file, $comment);
135              
136             # May contain spaces if enclosed in '' (in which case, \\ and \' are escapes)
137             if (($file, $comment) = $line =~ m{ \A \' (\\[\\\']|.+)+ \' \s* (.*) }mx) {
138             $file =~ s{ \\ ([\\\']) }{$1}gmx;
139             }
140             else {
141             ($file, $comment) = $line =~ m{ \A (\S+) \s* (.*) }mx;
142             }
143              
144             return [ $file, $comment ];
145             };
146              
147             my $_get_project_file = sub {
148             my $dir = shift; my $prev;
149              
150             while (not $prev or $prev ne $dir) { # Search for dist.ini first
151             for my $file (grep { $_->exists }
152             map { $dir->catfile( $BUILDERS{ $_ } ) } $_builders->()) {
153             return $file
154             }
155              
156             $prev = $dir; $dir = $dir->parent;
157             }
158              
159             return;
160             };
161              
162             # Construction
163             sub BUILD {
164             my $self = shift;
165              
166             for my $plugin (@{ $self->plugins }) {
167             if (first_char $plugin eq '+') { $plugin = substr $plugin, 1 }
168             else { $plugin = "Module::Provision::TraitFor::${plugin}" }
169              
170             try { Role::Tiny->apply_roles_to_object( $self, $plugin ) }
171             catch {
172             $_ =~ m{ \ACan\'t \s+ locate }mx or throw $_;
173             throw 'Module [_1] not found in @INC', [ $plugin ];
174             };
175             }
176              
177             return;
178             }
179              
180             sub _build_appbase { # Base + distname
181             my $self = shift; my $base = $self->base->absolute( $self->initial_wd );
182              
183             my $appbase = $base->catdir( $self->distname );
184              
185             $appbase->exists and return $appbase;
186              
187             # This is so you can rename the dist directory
188             my $file = $_get_project_file->( $self->initial_wd );
189             my $grand_parent = $file && $file->parent && $file->parent->parent;
190              
191             $grand_parent and $grand_parent->exists
192             and $grand_parent !~ m{ \.build \z }mx and return $grand_parent;
193             return $appbase;
194             }
195              
196             sub _build_appldir {
197             my $self = shift; my $appbase = $self->appbase; my $branch = $self->branch;
198              
199             my $home = $self->config->my_home; my $vcs = $self->vcs;
200              
201             (my $rel_appbase = $appbase) =~ s{ \Q$home\E [\\/] }{}mx;
202              
203             $self->debug and $self->info
204             ( "Appbase: ${rel_appbase}, Branch: ${branch}, VCS: ${vcs}" );
205              
206             return $vcs eq 'none' ? $appbase
207             : $appbase->catdir( '.git' )->exists ? $appbase
208             : $appbase->catdir( '.svn' )->exists ? $appbase
209             : $appbase->catdir( $branch )->exists ? $appbase->catdir( $branch )
210             : undef;
211             }
212              
213             sub _build_branch {
214             my $self = shift; my $branch = $ENV{BRANCH}; $branch and return $branch;
215              
216             $self->branch_file->exists and return $self->branch_file->chomp->getline;
217              
218             return $self->default_branch;
219             }
220              
221             sub _build_builder {
222             my $self = shift; my $appldir = $self->appldir;
223              
224             for (grep { $_->[ 1 ]->exists }
225             map { [ $_, $appldir->catfile( $BUILDERS{ $_ } ) ] } $_builders->()) {
226             return $_->[ 0 ];
227             }
228              
229             return undef;
230             }
231              
232             sub _build_default_branch {
233             return $_[ 0 ]->config->default_branches->{ $_[ 0 ]->vcs } || NUL;
234             }
235              
236             sub _build_dist_version {
237             my $self = shift; my $meta = $self->module_metadata;
238              
239             return Perl::Version->new( $meta ? $meta->version : '0.1.1' );
240             }
241              
242             sub _build_exec_perms {
243             return (($_[ 0 ]->perms & oct '0444') >> 2) | $_[ 0 ]->perms;
244             }
245              
246             sub _build_homedir {
247             return [ $_[ 0 ]->libdir, classdir $_[ 0 ]->project ];
248             }
249              
250             sub _build_license_keys {
251             return {
252             perl => 'Perl_5',
253             perl_5 => 'Perl_5',
254             apache => [ map { "Apache_$_" } qw( 1_1 2_0 ) ],
255             artistic => 'Artistic_1_0',
256             artistic_2 => 'Artistic_2_0',
257             lgpl => [ map { "LGPL_$_" } qw( 2_1 3_0 ) ],
258             bsd => 'BSD',
259             gpl => [ map { "GPL_$_" } qw( 1 2 3 ) ],
260             mit => 'MIT',
261             mozilla => [ map { "Mozilla_$_" } qw( 1_0 1_1 ) ], };
262             }
263              
264             sub _build_manifest_paths {
265             my $self = shift;
266              
267             return [ grep { $_->exists }
268             map { io( $_parse_manifest_line->( $_ )->[ 0 ] ) }
269             grep { not m{ \A \s* [\#] }mx }
270             $self->appldir->catfile( 'MANIFEST' )->chomp->getlines ];
271             }
272              
273             sub _build_module_abstract {
274             my $self = shift; my $meta = $self->module_metadata; my $abstract = NUL;
275              
276             $meta and ($abstract = $meta->pod( 'Name' ) // NUL)
277             =~ s{ \A [^\-]+ \s* [\-] \s* }{}mx; chomp $abstract;
278              
279             return $self->loc( $abstract || $self->config->module_abstract );
280             }
281              
282             sub _build_project {
283             my $self = shift;
284             my $file = $_get_project_file->( $self->initial_wd )
285             or throw 'Path [_1] contains no project file', [ $self->initial_wd ];
286             my $module = $_get_module_from->( $file->all )
287             or throw 'File [_1] contains no module name', [ $file ];
288              
289             return $module;
290             }
291              
292             sub _build_project_file {
293             return $BUILDERS{ $_[ 0 ]->builder };
294             }
295              
296             sub _build_stash {
297             my $self = shift; my $config = $self->config; my $author = $config->author;
298              
299             my $project = $self->project; my $perl_ver = $self->config->min_perl_ver;
300              
301             my $perl_code = $self->method eq 'dist' ? "use ${perl_ver};" : NUL;
302              
303             return { abstract => $self->module_abstract,
304             appdir => class2appdir $self->distname,
305             author => $author,
306             author_email => $config->author_email,
307             author_id => $config->author_id,
308             author_ID => uc $config->author_id,
309             copyright => $ENV{ORGANIZATION} || $author,
310             copyright_year => time2str( '%Y' ),
311             creation_date => time2str,
312             dist_module => $self->dist_module->abs2rel( $self->appldir ),
313             dist_version => NUL.$self->dist_version,
314             distname => $self->distname,
315             first_name => lc ((split SPC, $author)[ 0 ]),
316             home_page => $config->home_page,
317             initial_wd => NUL.$self->initial_wd,
318             last_name => lc ((split SPC, $author)[ -1 ]),
319             lc_distname => lc $self->distname,
320             license => $self->license,
321             license_class => $self->license_keys->{ $self->license },
322             module => $project,
323             perl => $perl_ver,
324             prefix => (split m{ :: }mx, lc $project)[ -1 ],
325             project => $project,
326             pub_repo_prefix => $config->pub_repo_prefix,
327             use_perl => $perl_code,
328             version => $self->VERSION, };
329             }
330              
331             sub _build_vcs {
332             my $self = shift; my $appbase = $self->appbase;
333              
334             return $appbase->catdir( '.git' )->exists ? 'git'
335             : $appbase->catdir( 'master', '.git' )->exists ? 'git'
336             : $appbase->catdir( '.svn' )->exists ? 'svn'
337             : $appbase->catdir( 'trunk', '.svn' )->exists ? 'svn'
338             : $appbase->catdir( $self->repository )->exists ? 'svn'
339             : 'none';
340             }
341              
342             # Public methods
343             sub chdir {
344             my ($self, $dir) = @_;
345              
346             $dir or throw Unspecified, [ 'directory' ];
347             chdir $dir or throw 'Directory [_1] cannot chdir: [_2]', [ $dir, $OS_ERROR ];
348             return $dir;
349             }
350              
351             sub load_meta {
352             my ($self, $dir) = @_;
353              
354             $dir or $dir = $self->builder eq 'DZ'
355             ? io( $self->distname.'-'.$self->dist_version ) : undef;
356              
357             my $path = $dir ? $dir->catfile( 'META.json' ) : io 'META.json';
358              
359             return CPAN::Meta->load_file( "${path}" );
360             }
361              
362             1;
363              
364             __END__
365              
366             =pod
367              
368             =encoding utf8
369              
370             =head1 Name
371              
372             Module::Provision::Base - Immutable data object
373              
374             =head1 Synopsis
375              
376             use Moose;
377              
378             extends 'Module::Provision::Base';
379              
380             =head1 Description
381              
382             Creates an immutable data object required by the methods in the applied roles
383              
384             =head1 Configuration and Environment
385              
386             Defines the following list of attributes which can be set from the
387             command line;
388              
389             =over 3
390              
391             =item C<base>
392              
393             The directory which will contain the new project. Defaults to the users
394             home directory
395              
396             =item C<branch>
397              
398             The name of the initial branch to create. Defaults to F<master> for
399             Git and F<trunk> for SVN
400              
401             =item C<builder>
402              
403             Which of the three build systems to use. Defaults to C<MB>, which is
404             L<Module::Build>. Can be C<DZ> for L<Dist::Zilla> or C<MI> for
405             L<Module::Install>
406              
407             =item C<config_class>
408              
409             The name of the configuration class
410              
411             =item C<initial_wd>
412              
413             The working directory when the command was invoked
414              
415             =item C<license>
416              
417             The name of the license used on the project. Defaults to C<perl>
418              
419             =item C<perms>
420              
421             Permissions used to create files. Defaults to C<644>. Directories and
422             programs have the execute bit turned on if the corresponding read bit
423             is on
424              
425             =item C<plugins>
426              
427             Optional trait to load and apply
428              
429             =item C<project>
430              
431             The class name of the new project. Should be the first extra argument on the
432             command line
433              
434             =item C<repository>
435              
436             Name of the directory containing the SVN repository. Defaults to F<repository>
437              
438             =item C<vcs>
439              
440             The version control system to use. Defaults to C<none>, can be C<git>
441             or C<svn>
442              
443             =back
444              
445             =head1 Subroutines/Methods
446              
447             =head2 BUILD
448              
449             Load and apply optional traits
450              
451             =head2 chdir
452              
453             $directory = $self->chdir( $directory );
454              
455             Changes the current working directory to the one supplied and returns it.
456             Throws if the operation was not successful
457              
458             =head2 load_meta
459              
460             $cpan_meta_object = $self->load_meta( $optional_directory );
461              
462             Loads the F<META.json> file and returns and object
463              
464             =head1 Diagnostics
465              
466             None
467              
468             =head1 Dependencies
469              
470             =over 3
471              
472             =item L<Class::Usul>
473              
474             =item L<File::DataClass>
475              
476             =item L<Module::Metadata>
477              
478             =item L<Module::Provision::Config>
479              
480             =item L<Perl::Version>
481              
482             =back
483              
484             =head1 Incompatibilities
485              
486             There are no known incompatibilities in this module
487              
488             =head1 Bugs and Limitations
489              
490             There are no known bugs in this module.
491             Please report problems to the address below.
492             Patches are welcome
493              
494             =head1 Acknowledgements
495              
496             Larry Wall - For the Perl programming language
497              
498             =head1 Author
499              
500             Peter Flanigan, C<< <pjfl@cpan.org> >>
501              
502             =head1 License and Copyright
503              
504             Copyright (c) 2015 Peter Flanigan. All rights reserved
505              
506             This program is free software; you can redistribute it and/or modify it
507             under the same terms as Perl itself. See L<perlartistic>
508              
509             This program is distributed in the hope that it will be useful,
510             but WITHOUT WARRANTY; without even the implied warranty of
511             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
512              
513             =cut
514              
515             # Local Variables:
516             # mode: perl
517             # tab-width: 3
518             # End: