* initial
[scriptdist.git] / scriptdist
blob189ae04e498a691c2d8c4a174a8a2992437a54be
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
5 use vars qw( %Content );
7 use Cwd;
8 use ExtUtils::Command;
9 use ExtUtils::Manifest;
10 use File::Basename qw(basename);
11 use File::Find qw(find);
12 use File::Spec;
13 use FindBin ();
15 my $Quiet = $ENV{SCRIPTDIST_DEBUG} || 0; # print progress messages
16 print "Quiet is $Quiet\n";
17 my $Name = $FindBin::Script;
18 my $Home = $ENV{HOME} || '';
20 print "Home directory is $Home\n" unless $Quiet;
22 my $Rc_directory = File::Spec->catfile( $Home, "." . $Name );
23 print "RC directory is $Rc_directory\n" unless $Quiet;
24 my $Config_file = File::Spec->catfile( $Home, "." . $Name . "rc" );
26 warn <<"HERE" unless $Home ne '';
27 The environment variable HOME has no value, so I will look in
28 the current directory for $Rc_directory and $Config_file. Set
29 the HOME environment variable to choose another directory.
30 HERE
32 my $Dir_sep = do {
33 if( $^O =~ m/MSWin32/ ) { '\\' }
34 elsif( $^O =~ m/Mac/ ) { ":" }
35 else { '/' }
38 =head1 NAME
40 scriptdist - create a distribution for a perl script
42 =head1 SYNOPSIS
44 % scriptdist script.pl
46 =head1 DESCRIPTION
48 The scriptdist program takes a script file and builds, in the current
49 working directory, a Perl script distribution around it. You can add
50 other files to the distribution once it is in place.
52 This script is designed to be a stand-alone program. You do not need
53 any other files to use it. However, you can create a directory named
54 .scriptdist in your home directory, and scriptdist will look for local
55 versions of template files there. Any files in C<~/.scriptdist/t>
56 will show up as is in the script's t directory (until I code the parts
57 to munge those files). The script assumes you have specified your
58 home directory in the environment variable HOME.
60 You can turn on optional progress and debugging messages by setting
61 the environment variable SCRIPTDIST_DEBUG to a true value.
63 =head2 The process
65 =over 4
67 =item Check for release information
69 The first time the scriptdist is run, or any time the scriptdist cannot
70 find the file C<.scriptdistrc>, it prompts for CPAN and SourceForge
71 developer information that it can add to the .releaserc file. (NOT
72 YET IMPLEMENTED)
74 =item Create a directory named after the script
76 The distribution directory is named after the script name,
77 with a <.d> attached. The suffix is there only to avoid a
78 name conflict. You can rename it after the script is moved
79 into the directory. If the directory already exists, the
80 script stops. You can either move or delete the directory
81 and start again.
83 =item Look for template files
85 The program looks in C<.scriptdistrc> for files to copy into
86 the target script distribution directory. After that, it
87 adds more files unless they already exist (i.e. the script
88 found them in the template directory). The script replaces
89 strings matching C<%%SCRIPTDIST_FOO%%> with the internal
90 value of FOO. The defined values are currently SCRIPT, which
91 substitutes the script name, and VERSION, whose value is
92 currently hard-coded at '0.10'.
94 While looking for files, scriptdist skips directories named
95 "CVS" and ".svn".
97 =item Add Changes
99 A bare bones Changes file
101 =item Create the Makefile.PL
103 =item Create the t directory
105 =item Add compile.t, pod.t, prereq.t
107 =item Create test_manifest
109 =item Copy the script into the directory
111 =item Run make manifest
113 =item prompt for CVS import
115 Prints a friendly message to remind you to add the new directory
116 to your source control system.
118 =back
120 =head2 Creating the Makefile.PL
122 A few things have to show up in the Makefile.PL---the name of
123 the script and the prerequisites modules are the most important.
124 Luckily, scriptdist can discover these things and fill them in
125 automatically.
127 =head1 TO DO
129 * add support for Module::Build (command line switch)
131 * Create Meta.yml file
133 * Automatically generate PREREQ_PM section (needs Module::Info, Module::CoreList)
135 * Copy modules into lib directory (to create module dist)
137 * Command line switches to turn things on and off
139 =head2 Maybe a good idea, maybe not
141 * Add a cover.t and pod coverage test?
143 * Interactive mode?
145 * automatically import into CVS?
147 =head1 SOURCE AVAILABILITY
149 This source is part of a SourceForge project which always has the
150 latest sources in CVS, as well as all of the previous releases.
152 http://sourceforge.net/projects/brian-d-foy/
154 If, for some reason, I disappear from the world, one of the other
155 members of the project can shepherd this module appropriately.
157 =head1 CREDITS
159 Thanks to Soren Andersen for putting this script through its paces
160 and suggesting many changes to actually make it work.
162 =head1 AUTHOR
164 brian d foy, E<lt>bdfoy@cpan.orgE<gt>
166 =head1 COPYRIGHT
168 Copyright 2004, brian d foy, All rights reserved.
170 You may use this program under the same terms as Perl itself.
172 =cut
174 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
175 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
176 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
178 my $Path = $ARGV[0];
179 my $Script = basename( $Path );
181 print "Processing $Script...\n" unless $Quiet;
183 my %Defaults = (
184 script => $Script,
185 version => '0.10',
188 content( \%Defaults );
190 # Make directories
191 my $Directory = "$Script.d";
192 die <<"HERE" if -d $Directory;
193 Directory $Directory already exists! Either delete it or
194 move it out of the way, then rerun this program.
195 HERE
197 foreach my $dir ( map { $_, File::Spec->catfile( $_, "t" ) } $Directory )
199 print "Making directory $dir...\n" unless $Quiet;
200 mkdir $dir, 0755 or die "Could not make [$dir]: $!\n";
203 # Copy local template files
204 print "RC directory is $Rc_directory\n" unless $Quiet;
205 print "cwd is ", getcwd, "\n";
207 if( -d $Rc_directory )
209 print "Looking for local templates...\n" unless $Quiet;
210 foreach my $input ( find_files( $Rc_directory ) )
212 my( $path ) = $input =~ m/\Q$Rc_directory$Dir_sep\E(.*)/g;
214 my @path = File::Spec->splitdir( $path );
215 my $file = pop @path;
217 if( @path )
219 local @ARGV = File::Spec->catfile( $Directory, @path );
220 ExtUtils::Command::mkpath unless -d $ARGV[0];
223 my $output = File::Spec->catfile( $Directory, $path );
224 copy( $input, $output, \%Defaults );
228 # Add distribution files unless they already exist
229 FILE: foreach my $filename ( sort keys %Content )
231 my @path = split m|\Q$Dir_sep|, $filename;
233 my $file = File::Spec->catfile( $Directory, @path );
235 print "Checking for file [$filename]... " unless $Quiet;
236 if( -e $file ) { print "already exists\n"; next FILE }
238 print "Adding file [$filename]...\n" unless $Quiet;
239 open my($fh), "> $file" or do {
240 warn "Could not write to [$file]: $!\n";
241 next FILE;
244 my $contents = $Content{$filename};
246 print $fh $contents;
249 # Add the script itself
251 print "Adding [$Script]...\n";
252 my $dist_script = File::Spec->catfile( $Directory, $Script );
254 if( -e $Path )
256 print "Copying script...\n";
257 copy( $Path, $dist_script );
259 else
261 print "Using script template...\n";
263 open my( $fh ), "> $dist_script";
264 print $fh ( script_template( $Script ) );
268 # Create the MANIFEST file
269 print "Creating MANIFEST...\n";
270 chdir $Directory or die "Could not change to $Directory: $!\n";
271 $ExtUtils::Manifest::Verbose = 0;
272 ExtUtils::Manifest::mkmanifest;
274 print <<"HERE";
275 ------------------------------------------------------------------
276 Remember to commit this directory to your source control system.
277 In fact, why not do that right now? Remember, `cvs import` works
278 from within a directory, not above it.
279 ------------------------------------------------------------------
280 HERE
282 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
283 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
284 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
285 sub prompt
287 my( $query ) = shift;
289 print $query;
291 chomp( my $reply = <STDIN> );
293 return $reply;
296 sub find_files
298 my $directory = shift;
300 my @files = ();
302 find( sub {
303 return unless -f $_;
304 return if $File::Find::name =~ m/\bCVS\b/ ||
305 $File::Find::name =~ m/\b\.svn\b/;
306 print "$File::Find::name\n";
307 push( @files, $File::Find::name );
308 }, $directory
311 return @files;
314 sub copy
316 my( $input, $output, $hash ) = @_;
318 print "Opening input [$input] for output [$output]\n";
320 open my($in_fh), $input or die "Could not open [$input]: $!\n";
321 open my($out_fh), ">" . $output or warn "Could not open [$output]: $!\n";
323 my $count = 0;
325 while( readline $in_fh )
327 $count += s/%%SCRIPTDIST_(.*?)%%/$hash->{ lc $1 } || ''/gie;
328 print $out_fh $_
331 print "Copied [$input] with $count replacements\n" unless $Quiet;
334 sub script_template
336 my $script_name = shift;
338 return <<"HERE";
339 #!/usr/bin/perl
341 =head1 NAME
343 $script_name - this script does something
345 =head1 SYNOPSIS
347 =head1 DESCRIPTION
349 =head1 AUTHOR
351 =head1 COPYRIGHT
353 =cut
355 HERE
358 sub content
360 my $hash = shift;
362 $Content{"Changes"} =<<"CHANGES";
363 # \$Id\$
364 0.10 - @{ [ scalar localtime ] }
365 + initial distribution created with $Name
366 CHANGES
368 $Content{"Makefile.PL"} =<<"MAKEFILE_PL";
369 # \$Id\$
370 use ExtUtils::MakeMaker;
372 eval "use Test::Manifest";
374 unless( \$\@ )
376 no warnings;
378 *ExtUtils::MM_Any::test_via_harness = sub
380 my(\$self, \$perl, \$tests) = \@_;
382 return qq|\t\$perl "-MTest::Manifest" | .
383 qq|"-e" "run_t_manifest(\\\$(TEST_VERBOSE), '\\\$(INST_LIB)', | .
384 qq|'\\\$(INST_ARCHLIB)')"\\n|;
388 my \$script_name = "$$hash{script}";
390 WriteMakefile(
391 'NAME' => \$script_name,
392 'VERSION' => '$$hash{version}',
394 'EXE_FILES' => [ \$script_name ],
396 'PREREQ_PM' => {
399 'MAN1PODS' => {
400 \$script_name => "\\\$(INST_MAN1DIR)/\$script_name.1",
403 clean => { FILES => "*.bak \$script_name-*" },
407 MAKEFILE_PL
409 $Content{"MANIFEST.SKIP"} =<<"MANIFEST_SKIP";
410 # \$Id\$
411 .cvsignore
412 .DS_Store
413 .releaserc
414 $$hash{script}-.*
415 blib
417 Makefile.old
418 Makefile\$
419 MANIFEST.bak
420 MANIFEST.SKIP
421 pm_to_blib
422 MANIFEST_SKIP
424 $Content{".releaserc"} =<<"RELEASE_RC";
425 # \$Id\$
426 cpan_user @{[ $ENV{CPAN_USER} ? $ENV{CPAN_USER} : '' ]}
427 sf_user @{[ $ENV{SF_USER} ? $ENV{SF_USER} : '' ]}
428 sf_group_id @{[ $ENV{SF_GROUP_ID} ? $ENV{SF_GROUP_ID} : '' ]}
429 sf_package_id @{[ $ENV{SF_PACKAGE_ID} ? $ENV{SF_PACKAGE_ID} : '' ]}
430 RELEASE_RC
432 $Content{".cvsignore"} =<<"CVSIGNORE";
433 # \$Id\$
434 .DS_Store
435 .lwpcookies
436 $$hash{script}-*
437 blib
438 Makefile
439 pm_to_blib
440 CVSIGNORE
442 $Content{"t/test_manifest"} =<<"TEST_MANIFEST";
443 compile.t
444 pod.t
445 prereq.t
446 TEST_MANIFEST
448 $Content{"t/pod.t"} = <<"POD_T";
449 # \$Id\$
450 use Test::More;
451 eval "use Test::Pod 1.00";
452 plan skip_all => "Test::Pod 1.00 required for testing POD" if \$@;
453 all_pod_files_ok();
454 POD_T
457 $Content{"t/prereq.t"} = <<"PREREQ_T";
458 # \$Id\$
459 use Test::More;
460 eval "use Test::Prereq";
461 plan skip_all => "Test::Prereq required to test dependencies" if \$@;
462 prereq_ok();
463 PREREQ_T
465 $Content{"t/compile.t"} = <<"COMPILE_T";
466 # \$Id\$
468 use Test::More tests => 1;
470 my \$file = "blib/script/$$hash{script}";
472 print "bail out! Script file is missing!" unless -e \$file;
474 my \$output = `perl -c \$file 2>&1`;
476 print "bail out! Script file is missing!" unless
477 like( \$output, qr/syntax OK\$/, 'script compiles' );
478 COMPILE_T