1package Module::Starter::Simple; 2 3use 5.006; 4use strict; 5use warnings; 6 7use Cwd 'cwd'; 8use File::Path qw( make_path remove_tree ); 9use File::Spec (); 10use Carp qw( carp confess croak ); 11use Module::Runtime qw( require_module ); 12 13use Module::Starter::BuilderSet; 14 15=head1 NAME 16 17Module::Starter::Simple - a simple, comprehensive Module::Starter plugin 18 19=head1 VERSION 20 21version 1.77 22 23=cut 24 25our $VERSION = '1.77'; 26 27=head1 SYNOPSIS 28 29 use Module::Starter qw(Module::Starter::Simple); 30 31 Module::Starter->create_distro(%args); 32 33=head1 DESCRIPTION 34 35Module::Starter::Simple is a plugin for Module::Starter that will perform all 36the work needed to create a distribution. Given the parameters detailed in 37L<Module::Starter>, it will create content, create directories, and populate 38the directories with the required files. 39 40=head1 CLASS METHODS 41 42=head2 C<< new(%args) >> 43 44This method is called to construct and initialize a new Module::Starter object. 45It is never called by the end user, only internally by C<create_distro>, which 46creates ephemeral Module::Starter objects. It's documented only to call it to 47the attention of subclass authors. 48 49=cut 50 51sub new { 52 my $class = shift; 53 return bless { @_ } => $class; 54} 55 56=head1 OBJECT METHODS 57 58All the methods documented below are object methods, meant to be called 59internally by the ephemeral objects created during the execution of the class 60method C<create_distro> above. 61 62=head2 postprocess_config 63 64A hook to do any work after the configuration is initially processed. 65 66=cut 67 68sub postprocess_config { 1 }; 69 70=head2 pre_create_distro 71 72A hook to do any work right before the distro is created. 73 74=cut 75 76sub pre_create_distro { 1 }; 77 78=head2 C<< create_distro(%args) >> 79 80This method works as advertised in L<Module::Starter>. 81 82=cut 83 84sub create_distro { 85 my $either = shift; 86 87 ( ref $either ) or $either = $either->new( @_ ); 88 89 my $self = $either; 90 my $modules = $self->{modules} || []; 91 my @modules = map { split /,/ } @{$modules}; 92 croak "No modules specified.\n" unless @modules; 93 for (@modules) { 94 croak "Invalid module name: $_" unless /\A[a-z_]\w*(?:::[\w]+)*\Z/i; 95 } 96 97 if ( ( not $self->{author} ) && ( $^O ne 'MSWin32' ) ) { 98 ( $self->{author} ) = split /,/, ( getpwuid $> )[6]; 99 } 100 101 if ( not $self->{email} and exists $ENV{EMAIL} ) { 102 $self->{email} = $ENV{EMAIL}; 103 } 104 105 croak "Must specify an author\n" unless $self->{author}; 106 croak "Must specify an email address\n" unless $self->{email}; 107 ($self->{email_obfuscated} = $self->{email}) =~ s/@/ at /; 108 109 $self->{license} ||= 'artistic2'; 110 $self->{minperl} ||= '5.006'; 111 $self->{ignores_type} ||= ['generic']; 112 $self->{manifest_skip} = !! grep { /manifest/ } @{ $self->{ignores_type} }; 113 114 $self->{license_record} = $self->_license_record(); 115 116 $self->{main_module} = $modules[0]; 117 if ( not defined $self->{distro} or not length $self->{distro} ) { 118 $self->{distro} = $self->{main_module}; 119 $self->{distro} =~ s/::/-/g; 120 } 121 122 $self->{basedir} = $self->{dir} || $self->{distro}; 123 $self->create_basedir; 124 125 my @files; 126 push @files, $self->create_modules( @modules ); 127 128 push @files, $self->create_t( @modules ); 129 push @files, $self->create_ignores; 130 my %build_results = $self->create_build(); 131 push(@files, @{ $build_results{files} } ); 132 133 push @files, $self->create_Changes; 134 push @files, $self->create_README( $build_results{instructions} ); 135 push @files, $self->create_LICENSE if $self->{genlicense}; 136 137 $self->create_MANIFEST( $build_results{'manifest_method'} ) unless ( $self->{manifest_skip} ); 138 # TODO: put files to ignore in a more standard form? 139 # XXX: no need to return the files created 140 141 return; 142} 143 144=head2 post_create_distro 145 146A hook to do any work after creating the distribution. 147 148=cut 149 150sub post_create_distro { 1 }; 151 152=head2 pre_exit 153 154A hook to do any work right before exit time. 155 156=cut 157 158sub pre_exit { 159 print "Created starter directories and files\n"; 160} 161 162=head2 create_basedir 163 164Creates the base directory for the distribution. If the directory already 165exists, and I<$force> is true, then the existing directory will get erased. 166 167If the directory can't be created, or re-created, it dies. 168 169=cut 170 171sub create_basedir { 172 my $self = shift; 173 174 # Make sure there's no directory 175 if ( -e $self->{basedir} ) { 176 die( "$self->{basedir} already exists. ". 177 "Use --force if you want to stomp on it.\n" 178 ) unless $self->{force}; 179 180 remove_tree $self->{basedir}; 181 182 die "Couldn't delete existing $self->{basedir}: $!\n" 183 if -e $self->{basedir}; 184 } 185 186 CREATE_IT: { 187 $self->progress( "Created $self->{basedir}" ); 188 189 make_path $self->{basedir}; 190 191 die "Couldn't create $self->{basedir}: $!\n" unless -d $self->{basedir}; 192 } 193 194 return; 195} 196 197=head2 create_modules( @modules ) 198 199This method will create a starter module file for each module named in 200I<@modules>. 201 202=cut 203 204sub create_modules { 205 my $self = shift; 206 my @modules = @_; 207 208 my @files; 209 210 for my $module ( @modules ) { 211 my $rtname = lc $module; 212 $rtname =~ s/::/-/g; 213 push @files, $self->_create_module( $module, $rtname ); 214 } 215 216 return @files; 217} 218 219=head2 module_guts( $module, $rtname ) 220 221This method returns the text which should serve as the contents for the named 222module. I<$rtname> is the email suffix which rt.cpan.org will use for bug 223reports. (This should, and will, be moved out of the parameters for this 224method eventually.) 225 226=cut 227 228our $LICENSES = { 229 perl => 'Perl_5', 230 artistic => 'Artistic_1_0', 231 artistic2 => 'Artistic_2_0', 232 mozilla => 'Mozilla_1_1', 233 mozilla2 => 'Mozilla_2_0', 234 bsd => 'BSD', 235 freebsd => 'FreeBSD', 236 cc0 => 'CC0_1_0', 237 gpl => 'GPL_2', 238 lgpl => 'LGPL_2_1', 239 gpl3 => 'GPL_3', 240 lgpl3 => 'LGPL_3_0', 241 agpl3 => 'AGPL_3', 242 apache => 'Apache_2_0', 243 qpl => 'QPL_1_0', 244}; 245 246sub _license_record { 247 my $self = shift; 248 my $key = $LICENSES->{ $self->{license} }; 249 $key = $self->{license} unless defined $key; 250 my $class = $key =~ m/::/ ? $key : "Software::License::$key"; 251 { 252 local $@; 253 undef $class unless eval { require_module $class; 1 } and $class->can('new'); 254 } 255 unless (defined $class) { 256 require Software::LicenseUtils; 257 ($class) = Software::LicenseUtils->guess_license_from_meta_key($key); 258 return undef unless defined $class; 259 } 260 return $class->new( { holder => $self->{author} } ); 261} 262 263sub _license_blurb { 264 my $self = shift; 265 266 my $record = $self->{license_record}; 267 my $license_blurb = defined($record) ? 268 $record->notice : 269 <<"EOT"; 270This software is Copyright (c) @{[ $self->_thisyear ]} by $self->{author}. 271 272This program is released under the following license: 273 274 $self->{license} 275EOT 276 277 chomp $license_blurb; 278 return $license_blurb; 279} 280 281# _create_module: used by create_modules to build each file and put data in it 282 283sub _create_module { 284 my $self = shift; 285 my $module = shift; 286 my $rtname = shift; 287 288 my @parts = split( /::/, $module ); 289 my $filepart = (pop @parts) . '.pm'; 290 my @dirparts = ( $self->{basedir}, 'lib', @parts ); 291 my $SLASH = q{/}; 292 my $manifest_file = join( $SLASH, 'lib', @parts, $filepart ); 293 if ( @dirparts ) { 294 my $dir = File::Spec->catdir( @dirparts ); 295 if ( not -d $dir ) { 296 make_path $dir; 297 $self->progress( "Created $dir" ); 298 } 299 } 300 301 my $module_file = File::Spec->catfile( @dirparts, $filepart ); 302 303 $self->{module_file}{$module} = File::Spec->catfile('lib', @parts, $filepart); 304 $self->create_file( $module_file, $self->module_guts( $module, $rtname ) ); 305 $self->progress( "Created $module_file" ); 306 307 return $manifest_file; 308} 309 310sub _thisyear { 311 return (localtime())[5] + 1900; 312} 313 314sub _module_to_pm_file { 315 my $self = shift; 316 my $module = shift; 317 318 my @parts = split( /::/, $module ); 319 my $pm = pop @parts; 320 my $pm_file = File::Spec->catfile( 'lib', @parts, "${pm}.pm" ); 321 $pm_file =~ s{\\}{/}g; # even on Win32, use forward slash 322 323 return $pm_file; 324} 325 326sub _reference_links { 327 return ( 328 { nickname => 'RT', 329 title => 'CPAN\'s request tracker (report bugs here)', 330 link => 'https://rt.cpan.org/NoAuth/Bugs.html?Dist=%s', 331 }, 332 { title => 'CPAN Ratings', 333 link => 'https://cpanratings.perl.org/d/%s', 334 }, 335 { title => 'Search CPAN', 336 link => 'https://metacpan.org/release/%s', 337 }, 338 ); 339} 340 341=head2 create_Makefile_PL( $main_module ) 342 343This will create the Makefile.PL for the distribution, and will use the module 344named in I<$main_module> as the main module of the distribution. 345 346=cut 347 348sub create_Makefile_PL { 349 my $self = shift; 350 my $main_module = shift; 351 my $builder_name = 'ExtUtils::MakeMaker'; 352 my $output_file = 353 Module::Starter::BuilderSet->new()->file_for_builder($builder_name); 354 my $fname = File::Spec->catfile( $self->{basedir}, $output_file ); 355 356 $self->create_file( 357 $fname, 358 $self->Makefile_PL_guts( 359 $main_module, 360 $self->_module_to_pm_file($main_module), 361 ), 362 ); 363 364 $self->progress( "Created $fname" ); 365 366 return $output_file; 367} 368 369=head2 create_MI_Makefile_PL( $main_module ) 370 371This will create a Module::Install Makefile.PL for the distribution, and will 372use the module named in I<$main_module> as the main module of the distribution. 373 374=cut 375 376sub create_MI_Makefile_PL { 377 my $self = shift; 378 my $main_module = shift; 379 my $builder_name = 'Module::Install'; 380 my $output_file = 381 Module::Starter::BuilderSet->new()->file_for_builder($builder_name); 382 my $fname = File::Spec->catfile( $self->{basedir}, $output_file ); 383 384 $self->create_file( 385 $fname, 386 $self->MI_Makefile_PL_guts( 387 $main_module, 388 $self->_module_to_pm_file($main_module), 389 ), 390 ); 391 392 $self->progress( "Created $fname" ); 393 394 return $output_file; 395} 396 397=head2 Makefile_PL_guts( $main_module, $main_pm_file ) 398 399This method is called by create_Makefile_PL and returns text used to populate 400Makefile.PL; I<$main_pm_file> is the filename of the distribution's main 401module, I<$main_module>. 402 403=cut 404 405sub Makefile_PL_guts { 406 my $self = shift; 407 my $main_module = shift; 408 my $main_pm_file = shift; 409 410 (my $author = "$self->{author} <$self->{email}>") =~ s/'/\'/g; 411 412 my $slname = $self->{license_record} ? $self->{license_record}->meta2_name : $self->{license}; 413 414 my $warnings = sprintf 'warnings%s;', ($self->{fatalize} ? " FATAL => 'all'" : ''); 415 416 return <<"HERE"; 417use $self->{minperl}; 418use strict; 419use $warnings 420use ExtUtils::MakeMaker; 421 422my %WriteMakefileArgs = ( 423 NAME => '$main_module', 424 AUTHOR => q{$author}, 425 VERSION_FROM => '$main_pm_file', 426 ABSTRACT_FROM => '$main_pm_file', 427 LICENSE => '$slname', 428 MIN_PERL_VERSION => '$self->{minperl}', 429 CONFIGURE_REQUIRES => { 430 'ExtUtils::MakeMaker' => '0', 431 }, 432 TEST_REQUIRES => { 433 'Test::More' => '0', 434 }, 435 PREREQ_PM => { 436 #'ABC' => '1.6', 437 #'Foo::Bar::Module' => '5.0401', 438 }, 439 dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, 440 clean => { FILES => '$self->{distro}-*' }, 441); 442 443# Compatibility with old versions of ExtUtils::MakeMaker 444unless (eval { ExtUtils::MakeMaker->VERSION('6.64'); 1 }) { 445 my \$test_requires = delete \$WriteMakefileArgs{TEST_REQUIRES} || {}; 446 \@{\$WriteMakefileArgs{PREREQ_PM}}{keys %\$test_requires} = values %\$test_requires; 447} 448 449unless (eval { ExtUtils::MakeMaker->VERSION('6.55_03'); 1 }) { 450 my \$build_requires = delete \$WriteMakefileArgs{BUILD_REQUIRES} || {}; 451 \@{\$WriteMakefileArgs{PREREQ_PM}}{keys %\$build_requires} = values %\$build_requires; 452} 453 454delete \$WriteMakefileArgs{CONFIGURE_REQUIRES} 455 unless eval { ExtUtils::MakeMaker->VERSION('6.52'); 1 }; 456delete \$WriteMakefileArgs{MIN_PERL_VERSION} 457 unless eval { ExtUtils::MakeMaker->VERSION('6.48'); 1 }; 458delete \$WriteMakefileArgs{LICENSE} 459 unless eval { ExtUtils::MakeMaker->VERSION('6.31'); 1 }; 460 461WriteMakefile(%WriteMakefileArgs); 462HERE 463 464} 465 466=head2 MI_Makefile_PL_guts( $main_module, $main_pm_file ) 467 468This method is called by create_MI_Makefile_PL and returns text used to populate 469Makefile.PL; I<$main_pm_file> is the filename of the distribution's main 470module, I<$main_module>. 471 472=cut 473 474sub MI_Makefile_PL_guts { 475 my $self = shift; 476 my $main_module = shift; 477 my $main_pm_file = shift; 478 479 my $author = "$self->{author} <$self->{email}>"; 480 $author =~ s/'/\'/g; 481 482 my $license_url = $self->{license_record} ? $self->{license_record}->url : ''; 483 484 my $warnings = sprintf 'warnings%s;', ($self->{fatalize} ? " FATAL => 'all'" : ''); 485 486 return <<"HERE"; 487use $self->{minperl}; 488use strict; 489use $warnings 490use inc::Module::Install; 491 492name '$self->{distro}'; 493all_from '$main_pm_file'; 494author q{$author}; 495license '$self->{license}'; 496 497perl_version '$self->{minperl}'; 498 499tests_recursive('t'); 500 501resources ( 502 #homepage => 'http://yourwebsitehere.com', 503 #IRC => 'irc://irc.perl.org/#$self->{distro}', 504 license => '$license_url', 505 #repository => 'git://github.com/$self->{author}/$self->{distro}.git', 506 #repository => 'https://bitbucket.org/$self->{author}/$self->{distro}', 507 bugtracker => 'https://rt.cpan.org/NoAuth/Bugs.html?Dist=$self->{distro}', 508); 509 510configure_requires ( 511 'Module::Install' => '0', 512); 513 514test_requires ( 515 'Test::More' => '0', 516); 517 518requires ( 519 #'ABC' => '1.6', 520 #'Foo::Bar::Module' => '5.0401', 521); 522 523install_as_cpan; 524auto_install; 525WriteAll; 526HERE 527 528} 529 530=head2 create_Build_PL( $main_module ) 531 532This will create the Build.PL for the distribution, and will use the module 533named in I<$main_module> as the main module of the distribution. 534 535=cut 536 537sub create_Build_PL { 538 my $self = shift; 539 my $main_module = shift; 540 my $builder_name = 'Module::Build'; 541 my $output_file = 542 Module::Starter::BuilderSet->new()->file_for_builder($builder_name); 543 my $fname = File::Spec->catfile( $self->{basedir}, $output_file ); 544 545 $self->create_file( 546 $fname, 547 $self->Build_PL_guts( 548 $main_module, 549 $self->_module_to_pm_file($main_module), 550 ), 551 ); 552 553 $self->progress( "Created $fname" ); 554 555 return $output_file; 556} 557 558=head2 Build_PL_guts( $main_module, $main_pm_file ) 559 560This method is called by create_Build_PL and returns text used to populate 561Build.PL; I<$main_pm_file> is the filename of the distribution's main module, 562I<$main_module>. 563 564=cut 565 566sub Build_PL_guts { 567 my $self = shift; 568 my $main_module = shift; 569 my $main_pm_file = shift; 570 571 (my $author = "$self->{author} <$self->{email}>") =~ s/'/\'/g; 572 573 my $slname = $self->{license_record} ? $self->{license_record}->meta2_name : $self->{license}; 574 575 my $warnings = sprintf 'warnings%s;', ($self->{fatalize} ? " FATAL => 'all'" : ''); 576 577 return <<"HERE"; 578use $self->{minperl}; 579use strict; 580use $warnings 581use Module::Build; 582Module::Build->VERSION('0.4004'); 583 584my \$builder = Module::Build->new( 585 module_name => '$main_module', 586 license => '$slname', 587 dist_author => q{$author}, 588 dist_version_from => '$main_pm_file', 589 release_status => 'stable', 590 configure_requires => { 591 'Module::Build' => '0.4004', 592 }, 593 test_requires => { 594 'Test::More' => '0', 595 }, 596 requires => { 597 #'ABC' => '1.6', 598 #'Foo::Bar::Module' => '5.0401', 599 }, 600 add_to_cleanup => [ '$self->{distro}-*' ], 601); 602 603\$builder->create_build_script(); 604HERE 605 606} 607 608=head2 create_Changes( ) 609 610This method creates a skeletal Changes file. 611 612=cut 613 614sub create_Changes { 615 my $self = shift; 616 617 my $fname = File::Spec->catfile( $self->{basedir}, 'Changes' ); 618 $self->create_file( $fname, $self->Changes_guts() ); 619 $self->progress( "Created $fname" ); 620 621 return 'Changes'; 622} 623 624=head2 Changes_guts 625 626Called by create_Changes, this method returns content for the Changes file. 627 628=cut 629 630sub Changes_guts { 631 my $self = shift; 632 633 return <<"HERE"; 634Revision history for $self->{distro} 635 6360.01 Date/time 637 First version, released on an unsuspecting world. 638 639HERE 640} 641 642=head2 create_LICENSE 643 644This method creates the distribution's LICENSE file. 645 646=cut 647 648sub create_LICENSE { 649 my $self = shift; 650 651 my $record = $self->{license_record} || return (); 652 my $fname = File::Spec->catfile( $self->{basedir}, 'LICENSE' ); 653 $self->create_file( $fname, $record->license ); 654 $self->progress( "Created $fname" ); 655 656 return 'LICENSE'; 657} 658 659=head2 create_README( $build_instructions ) 660 661This method creates the distribution's README file. 662 663=cut 664 665sub create_README { 666 my $self = shift; 667 my $build_instructions = shift; 668 669 my $fname = File::Spec->catfile( $self->{basedir}, 'README' ); 670 $self->create_file( $fname, $self->README_guts($build_instructions) ); 671 $self->progress( "Created $fname" ); 672 673 return 'README'; 674} 675 676=head2 README_guts 677 678Called by create_README, this method returns content for the README file. 679 680=cut 681 682sub _README_intro { 683 my $self = shift; 684 685 return <<"HERE"; 686The README is used to introduce the module and provide instructions on 687how to install the module, any machine dependencies it may have (for 688example C compilers and installed libraries) and any other information 689that should be provided before the module is installed. 690 691A README file is required for CPAN modules since CPAN extracts the README 692file from a module distribution so that people browsing the archive 693can use it to get an idea of the module's uses. It is usually a good idea 694to provide version information here so that people can decide whether 695fixes for the module are worth downloading. 696HERE 697} 698 699sub _README_information { 700 my $self = shift; 701 702 my @reference_links = _reference_links(); 703 704 my $content = "You can also look for information at:\n"; 705 706 foreach my $ref (@reference_links){ 707 my $title; 708 $title = "$ref->{nickname}, " if exists $ref->{nickname}; 709 $title .= $ref->{title}; 710 my $link = sprintf($ref->{link}, $self->{distro}); 711 712 $content .= qq[ 713 $title 714 $link 715]; 716 } 717 718 return $content; 719} 720 721sub _README_license { 722 my $self = shift; 723 724 my $license_blurb = $self->_license_blurb(); 725 726return <<"HERE"; 727LICENSE AND COPYRIGHT 728 729$license_blurb 730HERE 731} 732 733sub README_guts { 734 my $self = shift; 735 my $build_instructions = shift; 736 737 my $intro = $self->_README_intro(); 738 my $information = $self->_README_information(); 739 my $license = $self->_README_license(); 740 741return <<"HERE"; 742$self->{distro} 743 744$intro 745 746INSTALLATION 747 748$build_instructions 749 750SUPPORT AND DOCUMENTATION 751 752After installing, you can find documentation for this module with the 753perldoc command. 754 755 perldoc $self->{main_module} 756 757$information 758 759$license 760HERE 761} 762 763=head2 create_t( @modules ) 764 765This method creates a bunch of *.t files. I<@modules> is a list of all modules 766in the distribution. 767 768=cut 769 770sub create_t { 771 my $self = shift; 772 my @modules = @_; 773 774 my %t_files = $self->t_guts(@modules); 775 my %xt_files = $self->xt_guts(@modules); 776 777 my @files; 778 push @files, map { $self->_create_t('t', $_, $t_files{$_}) } keys %t_files; 779 push @files, map { $self->_create_t('xt', $_, $xt_files{$_}) } keys %xt_files; 780 781 return @files; 782} 783 784=head2 t_guts( @modules ) 785 786This method is called by create_t, and returns a description of the *.t files 787to be created. 788 789The return value is a hash of test files to create. Each key is a filename and 790each value is the contents of that file. 791 792=cut 793 794sub t_guts { 795 my $self = shift; 796 my @modules = @_; 797 798 my %t_files; 799 my $minperl = $self->{minperl}; 800 my $warnings = sprintf 'warnings%s;', ($self->{fatalize} ? " FATAL => 'all'" : ''); 801 802 my $header = <<"EOH"; 803#!perl 804use $minperl; 805use strict; 806use $warnings 807use Test::More; 808 809EOH 810 811 $t_files{'pod.t'} = $header.<<'HERE'; 812unless ( $ENV{RELEASE_TESTING} ) { 813 plan( skip_all => "Author tests not required for installation" ); 814} 815 816# Ensure a recent version of Test::Pod 817my $min_tp = 1.22; 818eval "use Test::Pod $min_tp"; 819plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; 820 821all_pod_files_ok(); 822HERE 823 824 $t_files{'manifest.t'} = $header.<<'HERE'; 825unless ( $ENV{RELEASE_TESTING} ) { 826 plan( skip_all => "Author tests not required for installation" ); 827} 828 829my $min_tcm = 0.9; 830eval "use Test::CheckManifest $min_tcm"; 831plan skip_all => "Test::CheckManifest $min_tcm required" if $@; 832 833ok_manifest(); 834HERE 835 836 $t_files{'pod-coverage.t'} = $header.<<'HERE'; 837unless ( $ENV{RELEASE_TESTING} ) { 838 plan( skip_all => "Author tests not required for installation" ); 839} 840 841# Ensure a recent version of Test::Pod::Coverage 842my $min_tpc = 1.08; 843eval "use Test::Pod::Coverage $min_tpc"; 844plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage" 845 if $@; 846 847# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version, 848# but older versions don't recognize some common documentation styles 849my $min_pc = 0.18; 850eval "use Pod::Coverage $min_pc"; 851plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage" 852 if $@; 853 854all_pod_coverage_ok(); 855HERE 856 857 my $nmodules = @modules; 858 my $main_module = $modules[0]; 859 my $use_lines = join( 860 "\n", map { qq{ use_ok( '$_' ) || print "Bail out!\\n";} } @modules 861 ); 862 863 $t_files{'00-load.t'} = $header.<<"HERE"; 864plan tests => $nmodules; 865 866BEGIN { 867$use_lines 868} 869 870diag( "Testing $main_module \$${main_module}::VERSION, Perl \$], \$^X" ); 871HERE 872 873 return %t_files; 874} 875 876=head2 xt_guts( @modules ) 877 878This method is called by create_t, and returns a description of the author 879only *.t files to be created in the xt directory. 880 881The return value is a hash of test files to create. Each key is a filename and 882each value is the contents of that file. 883 884=cut 885 886sub xt_guts { 887 my $self = shift; 888 my @modules = @_; 889 890 my %xt_files; 891 my $minperl = $self->{minperl}; 892 my $warnings = sprintf 'warnings%s;', ($self->{fatalize} ? " FATAL => 'all'" : ''); 893 894 my $header = <<"EOH"; 895#!perl 896use $minperl; 897use strict; 898use $warnings 899use Test::More; 900 901EOH 902 903 my $module_boilerplate_tests; 904 $module_boilerplate_tests .= 905 " module_boilerplate_ok('".$self->_module_to_pm_file($_)."');\n" for @modules; 906 907 my $boilerplate_tests = @modules + 2; 908 $xt_files{'boilerplate.t'} = $header.<<"HERE"; 909plan tests => $boilerplate_tests; 910 911sub not_in_file_ok { 912 my (\$filename, \%regex) = \@_; 913 open( my \$fh, '<', \$filename ) 914 or die "couldn't open \$filename for reading: \$!"; 915 916 my \%violated; 917 918 while (my \$line = <\$fh>) { 919 while (my (\$desc, \$regex) = each \%regex) { 920 if (\$line =~ \$regex) { 921 push \@{\$violated{\$desc}||=[]}, \$.; 922 } 923 } 924 } 925 926 if (\%violated) { 927 fail("\$filename contains boilerplate text"); 928 diag "\$_ appears on lines \@{\$violated{\$_}}" for keys \%violated; 929 } else { 930 pass("\$filename contains no boilerplate text"); 931 } 932} 933 934sub module_boilerplate_ok { 935 my (\$module) = \@_; 936 not_in_file_ok(\$module => 937 'the great new \$MODULENAME' => qr/ - The great new /, 938 'boilerplate description' => qr/Quick summary of what the module/, 939 'stub function definition' => qr/function[12]/, 940 ); 941} 942 943TODO: { 944 local \$TODO = "Need to replace the boilerplate text"; 945 946 not_in_file_ok(README => 947 "The README is used..." => qr/The README is used/, 948 "'version information here'" => qr/to provide version information/, 949 ); 950 951 not_in_file_ok(Changes => 952 "placeholder date/time" => qr(Date/time) 953 ); 954 955$module_boilerplate_tests 956 957} 958 959HERE 960 961 return %xt_files; 962} 963 964sub _create_t { 965 my $self = shift; 966 my $directory = shift; # 't' or 'xt' 967 my $filename = shift; 968 my $content = shift; 969 970 my @dirparts = ( $self->{basedir}, $directory ); 971 my $tdir = File::Spec->catdir( @dirparts ); 972 if ( not -d $tdir ) { 973 make_path $tdir; 974 $self->progress( "Created $tdir" ); 975 } 976 977 my $fname = File::Spec->catfile( @dirparts, $filename ); 978 $self->create_file( $fname, $content ); 979 $self->progress( "Created $fname" ); 980 981 return join('/', $directory, $filename ); 982} 983 984=head2 create_MB_MANIFEST 985 986This methods creates a MANIFEST file using Module::Build's methods. 987 988=cut 989 990sub create_MB_MANIFEST { 991 my $self = shift; 992 $self->create_EUMM_MANIFEST; 993} 994 995=head2 create_MI_MANIFEST 996 997This method creates a MANIFEST file using Module::Install's methods. 998 999Currently runs ExtUtils::MakeMaker's methods. 1000 1001=cut 1002 1003sub create_MI_MANIFEST { 1004 my $self = shift; 1005 $self->create_EUMM_MANIFEST; 1006} 1007 1008=head2 create_EUMM_MANIFEST 1009 1010This method creates a MANIFEST file using ExtUtils::MakeMaker's methods. 1011 1012=cut 1013 1014sub create_EUMM_MANIFEST { 1015 my $self = shift; 1016 my $orig_dir = cwd(); 1017 1018 # create the MANIFEST in the correct path 1019 chdir $self->{'basedir'} || die "Can't reach basedir: $!\n"; 1020 1021 require ExtUtils::Manifest; 1022 $ExtUtils::Manifest::Quiet = 0; 1023 ExtUtils::Manifest::mkmanifest(); 1024 1025 # return to our original path, wherever it was 1026 chdir $orig_dir || die "Can't return to original dir: $!\n"; 1027} 1028 1029=head2 create_MANIFEST( $method ) 1030 1031This method creates the distribution's MANIFEST file. It must be run last, 1032because all the other create_* functions have been returning the functions they 1033create. 1034 1035It receives a method to run in order to create the MANIFEST file. That way it 1036can create a MANIFEST file according to the builder used. 1037 1038=cut 1039 1040sub create_MANIFEST { 1041 my ( $self, $manifest_method ) = @_; 1042 my $fname = File::Spec->catfile( $self->{basedir}, 'MANIFEST' ); 1043 1044 $self->$manifest_method(); 1045 $self->filter_lines_in_file( 1046 $fname, 1047 qr/^xt\/boilerplate\.t$/, 1048 qr/^ignore\.txt$/, 1049 ); 1050 1051 $self->progress( "Created $fname" ); 1052 1053 return 'MANIFEST'; 1054} 1055 1056=head2 get_builders( ) 1057 1058This methods gets the correct builder(s). 1059 1060It is called by C<create_build>, and returns an arrayref with the builders. 1061 1062=cut 1063 1064sub get_builders { 1065 my $self = shift; 1066 1067 # pass one: pull the builders out of $self->{builder} 1068 my @tmp = 1069 ref $self->{'builder'} eq 'ARRAY' ? @{ $self->{'builder'} } 1070 : $self->{'builder'}; 1071 1072 my @builders; 1073 my $COMMA = q{,}; 1074 # pass two: expand comma-delimited builder lists 1075 foreach my $builder (@tmp) { 1076 push( @builders, split( $COMMA, $builder ) ); 1077 } 1078 1079 return \@builders; 1080} 1081 1082=head2 create_build( ) 1083 1084This method creates the build file(s) and puts together some build 1085instructions. The builders currently supported are: 1086 1087ExtUtils::MakeMaker 1088Module::Build 1089Module::Install 1090 1091=cut 1092 1093sub create_build { 1094 my $self = shift; 1095 1096 # get the builders 1097 my @builders = @{ $self->get_builders }; 1098 my $builder_set = Module::Starter::BuilderSet->new(); 1099 1100 # Remove mutually exclusive and unsupported builders 1101 @builders = $builder_set->check_compatibility( @builders ); 1102 1103 # compile some build instructions, create a list of files generated 1104 # by the builders' create_* methods, and call said methods 1105 1106 my @build_instructions; 1107 my @files; 1108 my $manifest_method; 1109 1110 foreach my $builder ( @builders ) { 1111 if ( !@build_instructions ) { 1112 push( @build_instructions, 1113 'To install this module, run the following commands:' 1114 ); 1115 } 1116 else { 1117 push( @build_instructions, 1118 "Alternatively, to install with $builder, you can ". 1119 "use the following commands:" 1120 ); 1121 } 1122 push( @files, $builder_set->file_for_builder($builder) ); 1123 my @commands = $builder_set->instructions_for_builder($builder); 1124 push( @build_instructions, join("\n", map { "\t$_" } @commands) ); 1125 1126 my $build_method = $builder_set->method_for_builder($builder); 1127 $self->$build_method($self->{main_module}); 1128 1129 $manifest_method = $builder_set->manifest_method($builder); 1130 } 1131 1132 return( 1133 files => [ @files ], 1134 instructions => join( "\n\n", @build_instructions ), 1135 manifest_method => $manifest_method, 1136 ); 1137} 1138 1139 1140=head2 create_ignores() 1141 1142This creates a text file for use as MANIFEST.SKIP, .cvsignore, 1143.gitignore, or whatever you use. 1144 1145=cut 1146 1147sub create_ignores { 1148 my $self = shift; 1149 my $type = $self->{ignores_type}; 1150 my %names = ( 1151 generic => 'ignore.txt', 1152 cvs => '.cvsignore', 1153 git => '.gitignore', 1154 hg => '.hgignore', 1155 manifest => 'MANIFEST.SKIP', 1156 ); 1157 1158 my $create_file = sub { 1159 my $type = shift; 1160 my $name = $names{$type}; 1161 my $fname = File::Spec->catfile( $self->{basedir}, $names{$type} ); 1162 $self->create_file( $fname, $self->ignores_guts($type) ); 1163 $self->progress( "Created $fname" ); 1164 }; 1165 1166 if ( ref $type eq 'ARRAY' ) { 1167 foreach my $single_type ( @{$type} ) { 1168 $create_file->($single_type); 1169 } 1170 } elsif ( ! ref $type ) { 1171 $create_file->($type); 1172 } 1173 1174 return; # Not a file that goes in the MANIFEST 1175} 1176 1177=head2 ignores_guts() 1178 1179Called by C<create_ignores>, this method returns the contents of the 1180ignore file. 1181 1182=cut 1183 1184sub ignores_guts { 1185 my ($self, $type) = @_; 1186 1187 my $ms = $self->{manifest_skip} ? "MANIFEST\nMANIFEST.bak\n" : ''; 1188 my $guts = { 1189 generic => $ms.<<"EOF", 1190Makefile 1191Makefile.old 1192Build 1193Build.bat 1194META.* 1195MYMETA.* 1196.build/ 1197_build/ 1198cover_db/ 1199blib/ 1200inc/ 1201.lwpcookies 1202.last_cover_stats 1203nytprof.out 1204pod2htm*.tmp 1205pm_to_blib 1206$self->{distro}-* 1207$self->{distro}-*.tar.gz 1208EOF 1209 # make this more restrictive, since MANIFEST tends to be less noticeable 1210 # (also, manifest supports REs.) 1211 manifest => <<'EOF', 1212# Top-level filter (only include the following...) 1213^(?!(?:script|examples|lib|inc|t|xt|maint)/|(?:(?:Makefile|Build)\.PL|README|LICENSE|MANIFEST|Changes|META\.(?:yml|json))$) 1214 1215# Avoid version control files. 1216\bRCS\b 1217\bCVS\b 1218,v$ 1219\B\.svn\b 1220\b_darcs\b 1221# (.git or .hg only in top-level, hence it's blocked above) 1222 1223# Avoid temp and backup files. 1224~$ 1225\.tmp$ 1226\.old$ 1227\.bak$ 1228\..*?\.sw[po]$ 1229\#$ 1230\b\.# 1231 1232# avoid OS X finder files 1233\.DS_Store$ 1234 1235# ditto for Windows 1236\bdesktop\.ini$ 1237\b[Tt]humbs\.db$ 1238 1239# Avoid patch remnants 1240\.orig$ 1241\.rej$ 1242EOF 1243 }; 1244 $guts->{hg} = $guts->{cvs} = $guts->{git} = $guts->{generic}; 1245 1246 return $guts->{$type}; 1247} 1248 1249=head1 HELPER METHODS 1250 1251=head2 verbose 1252 1253C<verbose> tells us whether we're in verbose mode. 1254 1255=cut 1256 1257sub verbose { return shift->{verbose} } 1258 1259=head2 create_file( $fname, @content_lines ) 1260 1261Creates I<$fname>, dumps I<@content_lines> in it, and closes it. 1262Dies on any error. 1263 1264=cut 1265 1266sub create_file { 1267 my $self = shift; 1268 my $fname = shift; 1269 1270 my @content = @_; 1271 open( my $fh, '>', $fname ) or confess "Can't create $fname: $!\n"; 1272 print {$fh} @content; 1273 close $fh or die "Can't close $fname: $!\n"; 1274 1275 return; 1276} 1277 1278=head2 progress( @list ) 1279 1280C<progress> prints the given progress message if we're in verbose mode. 1281 1282=cut 1283 1284sub progress { 1285 my $self = shift; 1286 print @_, "\n" if $self->verbose; 1287 1288 return; 1289} 1290 1291=head2 filter_lines_in_file( $filename, @compiled_regexes ) 1292 1293C<filter_lines_in_file> goes over a file and removes lines with the received 1294regexes. 1295 1296For example, removing t/boilerplate.t in the MANIFEST. 1297 1298=cut 1299 1300sub filter_lines_in_file { 1301 my ( $self, $file, @regexes ) = @_; 1302 my @read_lines; 1303 open my $fh, '<', $file or die "Can't open file $file: $!\n"; 1304 @read_lines = <$fh>; 1305 close $fh or die "Can't close file $file: $!\n"; 1306 1307 chomp @read_lines; 1308 1309 open $fh, '>', $file or die "Can't open file $file: $!\n"; 1310 foreach my $line (@read_lines) { 1311 my $found; 1312 1313 foreach my $regex (@regexes) { 1314 if ( $line =~ $regex ) { 1315 $found++; 1316 } 1317 } 1318 1319 $found or print {$fh} "$line\n"; 1320 } 1321 close $fh or die "Can't close file $file: $!\n"; 1322} 1323 1324=head1 BUGS 1325 1326Please report any bugs or feature requests to the bugtracker for this project 1327on GitHub at: L<https://github.com/xsawyerx/module-starter/issues>. I will be 1328notified, and then you'll automatically be notified of progress on your bug 1329as I make changes. 1330 1331=head1 AUTHOR 1332 1333Dan Book, L<< <dbook@cpan.org> >> 1334 1335Sawyer X, C<< <xsawyerx@cpan.org> >> 1336 1337Andy Lester, C<< <andy@petdance.com> >> 1338 1339C.J. Adams-Collier, C<< <cjac@colliertech.org> >> 1340 1341=head1 Copyright & License 1342 1343Copyright 2005-2009 Andy Lester and C.J. Adams-Collier, All Rights Reserved. 1344 1345Copyright 2010 Sawyer X, All Rights Reserved. 1346 1347This program is free software; you can redistribute it and/or modify it 1348under the same terms as Perl itself. 1349 1350Please note that these modules are not products of or supported by the 1351employers of the various contributors to the code. 1352 1353=cut 1354 1355sub _module_header { 1356 my $self = shift; 1357 my $module = shift; 1358 my $rtname = shift; 1359 my $warnings = sprintf 'warnings%s;', ($self->{fatalize} ? " FATAL => 'all'" : ''); 1360 1361 my $content = <<"HERE"; 1362package $module; 1363 1364use $self->{minperl}; 1365use strict; 1366use $warnings 1367 1368\=head1 NAME 1369 1370$module - The great new $module! 1371 1372\=head1 VERSION 1373 1374Version 0.01 1375 1376\=cut 1377 1378our \$VERSION = '0.01'; 1379HERE 1380 return $content; 1381} 1382 1383sub _module_bugs { 1384 my $self = shift; 1385 my $module = shift; 1386 my $rtname = shift; 1387 1388 my $bug_email = "bug-\L$self->{distro}\E at rt.cpan.org"; 1389 my $bug_link = 1390 "https://rt.cpan.org/NoAuth/ReportBug.html?Queue=$self->{distro}"; 1391 1392 my $content = <<"HERE"; 1393\=head1 BUGS 1394 1395Please report any bugs or feature requests to C<$bug_email>, or through 1396the web interface at L<$bug_link>. I will be notified, and then you'll 1397automatically be notified of progress on your bug as I make changes. 1398 1399HERE 1400 1401 return $content; 1402} 1403 1404sub _module_support { 1405 my $self = shift; 1406 my $module = shift; 1407 my $rtname = shift; 1408 1409 my $content = qq[ 1410\=head1 SUPPORT 1411 1412You can find documentation for this module with the perldoc command. 1413 1414 perldoc $module 1415]; 1416 my @reference_links = _reference_links(); 1417 1418 return undef unless @reference_links; 1419 $content .= qq[ 1420 1421You can also look for information at: 1422 1423\=over 4 1424]; 1425 1426 foreach my $ref (@reference_links) { 1427 my $title; 1428 my $link = sprintf($ref->{link}, $self->{distro}); 1429 1430 $title = "$ref->{nickname}: " if exists $ref->{nickname}; 1431 $title .= $ref->{title}; 1432 $content .= qq[ 1433\=item * $title 1434 1435L<$link> 1436]; 1437 } 1438 $content .= qq[ 1439\=back 1440]; 1441 return $content; 1442} 1443 1444sub _module_license { 1445 my $self = shift; 1446 1447 my $module = shift; 1448 my $rtname = shift; 1449 1450 my $license_blurb = $self->_license_blurb(); 1451 1452 my $content = qq[ 1453\=head1 LICENSE AND COPYRIGHT 1454 1455$license_blurb 1456]; 1457 1458 return $content; 1459} 1460 1461sub module_guts { 1462 my $self = shift; 1463 my $module = shift; 1464 my $rtname = shift; 1465 1466 # Sub-templates 1467 my $header = $self->_module_header($module, $rtname); 1468 my $bugs = $self->_module_bugs($module, $rtname); 1469 my $support = $self->_module_support($module, $rtname); 1470 my $license = $self->_module_license($module, $rtname); 1471 1472 my $content = <<"HERE"; 1473$header 1474 1475\=head1 SYNOPSIS 1476 1477Quick summary of what the module does. 1478 1479Perhaps a little code snippet. 1480 1481 use $module; 1482 1483 my \$foo = $module->new(); 1484 ... 1485 1486\=head1 EXPORT 1487 1488A list of functions that can be exported. You can delete this section 1489if you don't export anything, such as for a purely object-oriented module. 1490 1491\=head1 SUBROUTINES/METHODS 1492 1493\=head2 function1 1494 1495\=cut 1496 1497sub function1 { 1498} 1499 1500\=head2 function2 1501 1502\=cut 1503 1504sub function2 { 1505} 1506 1507\=head1 AUTHOR 1508 1509$self->{author}, C<< <$self->{email_obfuscated}> >> 1510 1511$bugs 1512 1513$support 1514 1515\=head1 ACKNOWLEDGEMENTS 1516 1517$license 1518 1519\=cut 1520 15211; # End of $module 1522HERE 1523 return $content; 1524} 1525 15261; 1527 1528# vi:et:sw=4 ts=4 1529